iOS_SDK
简介
本文档描述货架拼接iOS SDK如何使用。
系统支持
系统:iOS 9.0 以上
硬件:armv7 arm64 (Starndard architectures)(暂不支持模拟器)
Release Notes
时间 | 版本 | 说明 |
---|---|---|
2022.12.22 | 5.0.0 | 更名为门店拜访SDK,新增门脸文字识别功能、防窜拍功能 |
2021.12.22 | 4.1.0 | 新增手机端实时拼接模糊图像检测功能 |
2021.10.20 | 4.0.0 | 新增手机端实时拼接功能 |
2021.03.09 | 3.0.1 | 新增光线和手机方向检测功能 |
2020.12.30 | 3.0.0 | 新增支持拍摄图片,云端拼接功能 |
2020.11.12 | 2.0.0 | 新增支持排面统计占比 |
2019.08.30 | 1.0.0 | 支持拍摄视频,端上抽帧,云端拼接 |
集成指南
库依赖
SDK依赖以下静态库/动态库,需正确集成至项目中并配置Framework Search Paths / Header Search Paths / Library Search Paths:
- opencv2.framework:OpenCV V4.5.2,必须引入
- libmontage_algo.a:手机端实时拼接功能库,可选引入,集成时请一并拷贝头文件目录montage_algo至项目合适路径
- libEasyDL.a:模糊图像检测引擎库,可选引入,集成时请一并拷贝头文件目录EasyDL至项目合适路径
- libpaddle_api_full_bundled.a:模糊图像检测引擎库,可选引入,集成时请一并拷贝头文件目录paddlelite至项目合适路径
libEasyDL.a 和 libpaddle_api_full_bundled.a 需同时引入才可支持模糊图像检测
集成摄像头相关逻辑
UI部分包括摄像头代码均开源,可参考以下文件,用户拷贝相关代码至项目中即可,并修改相应文件名以避免符号冲突:
- easydl-stitch-ios/ViewController/ImagePickerViewController:云端拼接拍照逻辑,重合度算法开源
- easydl-stitch-ios/ViewController/MBStitchCameraViewController:手机端实时拼接拍照逻辑,拼接等相关算法依赖 libmontage_algo.a
- easydl-stitch-ios/ViewController/VideoStitchViewController#startUIImagePicker():云端拼接视频逻辑入口,参考该方法内对系统UIImagePickerController的使用
拍照拼接参数配置
// easydl-stitch-ios/EasyDLStitch/EasyDLStitchParams.h
#define kThreshold 75 // 判断重合的阈值,0~100之间
#define kStrategy "phash" // 重合算法,类型:["ahash","phash","dhash"]
#define kThetaZ 60 // 手机倾斜Z轴角度阈值
#define kThetaXY 20 // 手机倾斜XY轴角度阈值
#define kOverLapControl true // 是否与遮罩重合才可以拍照
#define kOrientationControl true // 是否手机持握方向符合要求才可以拍照
#define kSkipFrames 3 // 跳过视频帧比对的数量,比如3为每3帧比对一次
#define kBrightnessLow -3 // 光线强度阈值,-99~99之间
#define kBrightnessHigh 5 // 光线强度阈值,-99~99之间
#define kBrightnessControl false // 是否光线符合要求才可以拍照
参数说明:
- 获取重合度有"ahash","phash","dhash"三种算法,返回0~100之间的数值,越大表示重合度越高。不同算法返回的数值有区别,需相应调整阈值
- 手机倾斜XY轴指左右倾斜,Z轴是前后倾斜,当倾斜角度过大会影响拼接效果
- 设置只有手机倾斜角度、待拍摄图片与上一张图片重合度、环境光线亮度等条件符合要求才拍摄图片,保证拍摄效果
- 相机默认为每秒30帧,修改kSkipFrames的值调节做重合度对比的速度,避免卡顿或拍摄状态切换过快
视频拼接参数配置
体验APP中对视频截取帧的频率为1秒1帧,由于每个视频的帧数不能大于60,所以体验APP不能拼接长度大于60s的视频。开发者可根据实际情况调整截帧的频率,并相应限制视频长度。调整频率方法:
// easydl-stitch-ios/ViewController/StitchViewController.m
static int frameInterval = 1;//截帧间隔(秒)
并在合适的地方提示视频长度限制:
UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:@"选择对象(视频长度不能超过60s)" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
SDK工程结构
EasyDL-Image-Stitching-iOS
|- LIB
|- include
|- montage_algo/ // 手机端实时拼接功能库头文件
|- EasyDL/ // 手机端实时拼接模糊图像检测引擎头文件
|- paddlelite/ // 手机端实时拼接模糊图像检测引擎头文件
|- libs
|- opencv2.framework // OpenCV库
|- libmontage_algo.a // 手机端实时拼接功能库
|- libEasyDL.a // 手机端实时拼接模糊图像检测引擎
|- libpaddle_api_full_bundled.a // 手机端实时拼接模糊图像检测引擎
|- EasyDLStitch
|- images/ // 资源文件
|- easydl-stitch-ios/ // Demo工程文件
|- RES/
|- conf.json // API配置文件
|- fuzzy_model/ // 模糊图像检测模型
SDK调用流程
获取鉴权
- 进入EasyDL零售版的百度智能云控制台应用列表页面,如下图所示:
- 如果还未创建应用,请点击「创建应用」按钮进行创建。创建应用后,参考鉴权参考文档,使用API Key(AK)和Secret Key(SK)获取access_token
下载SDK包后,填写ak、sk等信息。在RES/conf.json相应位置填入:
{
"ak": "Mz0zhObvEZ6lnG1K3renXXXX", // API Key的值
"sk": "188fRHYvLPmlPrNCDpBnkhL3ydXXXXX", // Secret Key的值
"apiUrl": "https://aip.baidubce.com/rpc/2.0/ai_custom_retail/v1/detection/XXXX" // 定制商品检测服务API
}
云端非实时拼接调用流程
- 创建任务:开始拼接整个流程
- 加货架图:上传图片
- 开始任务:启动货架拼接离线任务
- 查询任务:查询拼接任务的状态和结果
其他:
- 取消任务:取消正在进行或者等待的任务
- 任务列表:查询所有状态的任务列表
- 终止任务:终止正在进行或者等待的任务
调用API
云端拼接API的调用逻辑已封装在EasyDLStitchApiService文件中,以下为SDK调用货架拼接API的方法说明。货架拼接API接口的返回值及其他信息参见文档货架拼接API调用方法。
创建拼接任务
- (void)createSpliceTaskWithConfig:(NSDictionary *)config successHandler:(SuccessBlock)successHandler failHandler:(FailureBlock)failHandler;
其中config为参数,后面两个回调block。参数取值及描述:
参数名称 | 是否必需 | 参数类型 | 描述 | 参数值限制 |
---|---|---|---|---|
api_url | 是 | string | 商品检测服务的url | 无 |
row_image_nums | 是 | array[number] | 各行待拼接货架图片的数量,array长度为货架图片的行数,array[i]为第i行的货架图片数量 | 行数不大于3,行内图片数量不大于60 |
detection_threshold | 否 | float | 商品检测服务的阈值 | 默认值为商品检测服务的阈值,取值范围[0,1] |
nms_iou_threshold | 否 | float | 检测框矫准去重的阈值 | 默认值为0.45, 取值范围[0.2,0.8] |
上传图片
- (void)uploadImageWithConfig:(NSDictionary *)config successHandler:(SuccessBlock)successHandler failHandler:(FailureBlock)failHandler;
其中config为参数,后面两个回调block。参数取值及描述:
参数名称 | 是否必需 | 参数类型 | 描述 | 参数值限制 |
---|---|---|---|---|
task_id | 是 | string | 货架拼接任务id | 无 |
row | 是 | number | 图片对应行的index | 取值从0开始,需小于创建任务参数row_image_nums的长度 |
column | 是 | number | 图片在行内所在的index | 取值从0开始,需小于创建任务参数row_image_nums[row]的取值 |
image | 是 | string | 上传图片的base64编码 |
启动拼接任务
-(void)startSpliceTaskWithConfig:(NSDictionary *)config successHandler:(SuccessBlock)successHandler failHandler:(FailureBlock)failHandler;
其中config为参数,后面两个回调block。参数取值及描述:
参数名称 | 是否必需 | 参数类型 | 描述 | 参数值限制 |
---|---|---|---|---|
task_id | 是 | string | 货架拼接任务id | 无 |
查询任务状态
-(void)queryTaskResultWithConfig:(NSDictionary *)config successHandler:(SuccessBlock)successHandler failHandler:(FailureBlock)failHandler;
其中config为参数,后面两个回调block。参数取值及描述:
参数名称 | 是否必需 | 参数类型 | 描述 | 参数值限制 |
---|---|---|---|---|
task_id | 是 | string | 货架拼接任务id | 无 |
查询任务列表
-(void)listTaskWithConfig:(NSDictionary *)config successHandler:(SuccessBlock)successHandler failHandler:(FailureBlock)failHandler;
其中config为参数,后面两个回调block。参数取值及描述:
参数名称 | 是否必需 | 参数类型 | 描述 | 参数值限制 |
---|---|---|---|---|
task_ids | 否 | array[string] | 只返回指定id的任务信息 | 无 |
begin_time | 否 | number | 只返回begin_time以后创建的任务信息 | 时间戳 |
end_time | 否 | number | 只返回end_time之前创建的任务信息 | 时间戳 |
终止任务
-(void)terminateTaskWithConfig:(NSDictionary *)config successHandler:(SuccessBlock)successHandler failHandler:(FailureBlock)failHandler;
其中config为参数,后面两个回调block。参数取值及描述:
参数名称 | 是否必需 | 参数类型 | 描述 | 参数值限制 |
---|---|---|---|---|
task_id | 是 | string | 货架拼接任务id | 无 |
手机端实时拼接调用流程
- 准备目录
- 对比图片:新图片(now)在参与实时拼接前需先与上一张参与拼接的图片(last)进行对比,如果now与last的对比特征合法则可以成功拼接,否则无法得到理想实时拼接结果
- 实时拼接:SDK会寻找now.jpg并进行实时拼接得到新拼接结果
- 上传云端,得到结果
其他:
- 撤销拼接:撤销最后一次拼接结果
SDK的调用
手机端实时拼接的调用逻辑已封装在EasyDLMBStitchApiService文件中,包括商品检测API以及拼接算法的调用,以下为SDK调用的方法说明。
准备目录
实时拼接过程中产生的文件需保存在本地,首先初始化准备保存的目录。单个拼接任务的保存目录不可发生变化。
- (void)prepare;
// 调用示例
[[EasyDLMBStitchApiService sharedService] prepare];
对比图片
- (void)compareImage:(UIImage *)image firstFrame:(BOOL)isFirstFrame completionHandler:(CompletionHandler)completionHandler;
// 调用示例
[[EasyDLMBStitchApiService sharedService] compareImage:image firstFrame:_firstFrameCompare completionHandler:^(id responseObject, NSError *error) {
if (error) {
NSLog(@"%@", error.localizedDescription);
} else {
// 回调结果
CompareResult *compareResult = responseObject;
// do something
}
}];
参数说明:
- image:当前要参与对比的图片
-
isFirstFrame:是否是第一帧
- 第一帧的定义取决于上一张参与拼接的图片(last)是否已经被对比过。假设有图片A和B,先用A与last对比,且last是初次被对比,此时isFirstFrame应为true,再用B与last对比,此时isFirstFrame应为false
- completionHandler:异步回调,在主线程
CompareResult
// montage_algo/EasyDLStitchAlgo.h
// 当前图片相对上一张参与拼接的图片的方位
@property(nonatomic) ImageDirection direction;
/**
* 是否需要判断方向,如当拍摄完图片过近时,direction可能由于两图过于相似而不可靠,这种情况不需要判断方向,即该值=false
* 一般direction不可靠时,该值=false
* 对比的两张图是第一次对比时,该值=false
* 当该值=true时,请在调用实时拼接API前确认方法是否合法,否则可能导致拼接失败
*/
@property(nonatomic, getter = needCheckDirection) BOOL checkDirection;
// 对比结果中的方位是否合法,非法的方位将无法完成拼接
@property(nonatomic) BOOL directionValid;
// 两张图片重叠部分的点位
@property(nonatomic, retain) NSArray<NSValue *> *points;
// 两张图片重叠状态
@property(nonatomic) OverlapStatus overlapStatus;
// 最后一次参与拼接的图片序号,从1开始
@property(nonatomic) int lastImgIndex;
对比结果的合法性判断参考
switch (compareResult.overlapStatus) {
case OverlapStatus_Correct:
if (compareResult.needCheckDirection && !compareResult.directionValid) {
// 非法,当前参与对比的图片方位不正确,无法拼接
} else {
// 合法
}
break;
case OverlapStatus_TooFar:
// 非法,两张图重叠度过低
break;
case OverlapStatus_TooClose:
// 非法,两张图重叠度过高
break;
}
实时拼接
- 建议调用拼接前参考【对比结果的合法性判断】,用不合法的对比结果进行实时拼接将无法获得正确输出
- 将要参与拼接的图片必须命名为“now.jpg”(也可使用EasyDLMBStitchApiService.FILENAME_IMAGE_NOW),并保存在[EasyDLMBStitchApiService sharedService].currentTask.workDir指向的目录下,否则实时拼接无法正常工作。成功拼接后"now.jpg"会被SDK重新命名为{index}.jpg,其中{index}代表图片序号。
- 为获得更快的拼接效率,建议减小参与拼接的图片尺寸;为了保证拼接效果,缩放后的图片尺寸应不小于宽648和高864
- (void)stitchImageWithCompareResult:(CompareResult *)compareResult completionHandler:(CompletionHandler)completionHandler;
// 调用示例
[[EasyDLMBStitchApiService sharedService] stitchImageWithCompareResult:_latestCompareResult completionHandler:^(id responseObject, NSError *error) {
if (error) {
NSLog(@"%@", error.localizedDescription);
} else {
// 回调结果
StitchResult *stitchResult = responseObject;
// do something
}
}];
// 保存now.jpg示例
NSURL *workDirUrl = [EasyDLMBStitchApiService sharedService].currentTask.workDir;
/* 小图为拼接,大图为获得更好的商品检测效果 */
[EasyDLFileManager saveImage:image toUrl:[workDirUrl URLByAppendingPathComponent:FILENAME_IMAGE_NOW] andResizeTo:CGSizeMake(648, 864)];
// SDK默认使用保存的一系列`{index}.jpg`调用商品检测API并取得结果。
// 由于建议减小该系列图片尺寸以获得更优的拼接效率,但同时更小尺寸的图片对商品检测精度有一定影响,因此为提高精度,建议同时保存最佳尺寸的图片用于上传云端。
[EasyDLFileManager saveImageForBestInfer:image toUrl:[workDirUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@/%@", DIR_NAME_FULL_IMAGE, FILENAME_IMAGE_NOW]]];
参数说明:
- compareResult:对比图片回调返回的结果
- completionHandler:异步回调,在主线程。该回调在拼接任务全部完成后到达,如需更快获取缩略图和完整拼接图,可配置EasyDLMBAPIStitchDelegate协议
StitchResult
// montage_algo/EasyDLStitchAlgo.h
// 拼接错误码,0表示成功,其他为错误
@property(nonatomic) int errCode;
// 最近一张参与拼接的图片的序号
@property(nonatomic) int latestImgIndex;
// 缩略拼接图路径
@property(nonatomic, retain) NSURL *thumbnailUrl;
// 完整拼接图路径
@property(nonatomic, retain) NSURL *fullImageUrl;
EasyDLMBAPIStitchDelegate
// easydl-montage-ios/EasyDLStitch/EasyDLMBStitchApiService.h
@protocol EasyDLMBAPIStitchDelegate <NSObject>
- (void)onStitchThumbnailGenerated:(NSURL *)thumbnailURL;
- (void)onStitchFullImageGenerated:(NSURL *)fullImageURL;
@end
// 配置示例
[EasyDLMBStitchApiService sharedService].stitchDelegate = self;
上传云端,得到结果
- (void)mergeDetectedResultsWithCompletionHandler:(CompletionHandler)completionHandler;
// 调用示例
[[EasyDLMBStitchApiService sharedService] mergeDetectedResultsWithCompletionHandler:^(id responseObject, NSError *error) {
if (error) {
NSLog(@"%@", error.localizedDescription);
} else {
// 回调结果
MergeResult *mergeResult = responseObject;
// do something
}
}];
参数说明:
- completionHandler:异步回调,在主线程。该回调在检测任务全部完成后到达,如需感知检测任务的开始和进度更新,可配置EasyDLMBAPIMergeDelegate协议
MergeResult
// montage_algo/EasyDLStitchAlgo.h
// 错误码,0表示成功,其他为错误
@property(nonatomic) int errCode;
// 商品检测并去重合并后的结果
@property(nonatomic, retain) NSDictionary *correctSKUDict;
EasyDLMBAPIMergeDelegate
// easydl-montage-ios/EasyDLStitch/EasyDLMBStitchApiService.h
@protocol EasyDLMBAPIMergeDelegate <NSObject>
- (void)onMergeResultsStarted:(int)totalImageCount;
- (void)onMergeProgressUpdated:(int)totalImageCount completedCount:(int)completedCount;
@end
// 配置示例
[EasyDLMBStitchApiService sharedService].mergeDelegate = self;
撤销拼接
SDK支持撤销最后一次拼接结果,如需撤销多张,请多次操作
- (void)undoLastTakenImageWithCompletionHandler:(CompletionHandler)completionHandler;
// 调用示例
[[EasyDLMBStitchApiService sharedService] undoLastTakenImageWithCompletionHandler:^(id responseObject, NSError *error) {
if (error) {
NSLog(@"%@", error.localizedDescription);
} else {
// 回调结果,包含撤销后,当前最后一次参与拼接的图片信息
ImageInfo *lastImageInfo = responseObject;
// do something
}
}];
参数说明:
- completionHandler:异步回调,在主线程
ImageInfo
// easydl-montage-ios/EasyDLStitch/EasyDLMBStitchApiService.h
// 图片序号
@property(nonatomic) int index;
// 图片列坐标
@property(nonatomic) int x;
// 图片行坐标
@property(nonatomic) int y;
模糊图像检测
手机端实时拼接已接入AI模型以支持模糊图像检测,除参考库依赖正确引入依赖库,需保证 RES/fuzzy_model 目录下的模型文件存在。调用示例如下,也可参考 EasyDL-Stitch/easydl-stitch-ios/ViewController/MBStitchCameraViewController.m 文件中对 FuzzyModelProxy 的使用:
// 模型初始化
- (NSError *)modelInit;
// 推理图像判断是否模糊
- (void)inferImage:(UIImage *)image completionHandler:(void (^)(BOOL fuzzy, NSError *error))completionHandler;
// 调用示例
FuzzyModelProxy *fuzzyModelProxy = [[FuzzyModelProxy alloc] init];
[fuzzyModelProxy modelInit];
if (fuzzyModelProxy && fuzzyModelProxy.engineActive) {
[fuzzyModelProxy inferImage:image completionHandler:^(BOOL fuzzy, NSError *error) {
if (!error && !fuzzy) {
// 图像非模糊
} else {
// 图像模糊或推理失败
}
}];
}
阈值设置
手机端实时拼接支持设置:
- 最小IOU置信度
- 最大IOU置信度
- NMS置信度
- 商品检测API最大重试次数
// easydl-montage-ios/EasyDLStitch/EasyDLMBStitchApiService.h
/**
* 最小拼接引导置信度
*/
@property(nonatomic) CGFloat minIOUThreshold;
/**
* 最大拼接引导置信度
*/
@property(nonatomic) CGFloat maxIOUThreshold;
/**
* 去重置信度
*/
@property(nonatomic) CGFloat nmsIOUThreshold;
/**
* 商品检测API重试次数
*/
@property(nonatomic) int detectRetryTimes;
/**
* 商品检测API最大并发数
*/
@property(nonatomic) int maxQPS;
门脸文字识别调用流程
- 初始化门店定位
- 门脸图片上传云端
SDK 调用
门脸文字识别流程通过 EasyDLDoorAPIService 调用,具体使用和返回参数见下
初始化门店定位
[[EasyDLDoorAPIService sharedService] startLocation];
门脸图片上传云端,获取门店检测结果
// easydl-montage-ios/EasyDLDoor/EasyDLDoorAPIService.h
// 开始门脸文字识别
[[EasyDLDoorAPIService sharedService] detectDoorImage:image];
// 获取门脸识别结果
[EasyDLDoorAPIService sharedService].blockNSDictionary = ^(NSDictionary * _Nonnull blockNSDictionary, NSError * _Nonnull error) {
if(blockNSDictionary != nil && error == nil) {
// 门脸图片识别成功
..............
// 校验门店结果
[EasyDLStitchAlgo checkDoorData:documentsDirectory ocrInfo:ocrJson];
}
}
模糊图像检测
门脸文字识别已接入AI模型以支持模糊图像检测,除参考库依赖正确引入依赖库,需保证 RES/fuzzy_model 目录下的模型文件存在。 调用示例可参考【手机端实时拼接调用流程-模糊图像检测】,也可参考【门脸文字识别调用流程】 EasyDL-Stitch/easydl-stitch-ios/ViewController/DoorCameraViewController.mm 文件中对 FuzzyModelProxy 的使用。
防止图片窜拍开关参数设置
// easydl-montage-ios/EasyDLDoor/CheckImageConfig.h
- (BOOL)getPirateImageCheck;
// 调用示例
/**
* 窜拍开关默认开启
*/
[CheckImageConfig sharedService].pirateImageCheck = true;
// 获取开关状态
[[CheckImageConfig sharedService] getPirateImageCheck];
错误码
以下为SDK使用的错误码,API接口错误码参见货架拼接API错误码。
错误码 | 说明 |
---|---|
200002 | 模型配置错误,请检查传入的配置文件是否有效 |
100006 | API,AK/SK 换取token失败 |
100007 | API,请求 API 失败 |
100008 | API,请求商品检测API失败 |
200001 | 手机端实时拼接 - 前端引导对比图像出错 |
200003 | 手机端实时拼接 - 撤销上一次拼接结果出错 |
200004 | 手机端实时拼接 - 商品检测+去重过程中出错 |
200005 | 手机端实时拼接 - 拼接出错 |
300001 | 手机端实时拼接 - 模糊图像检测出错 |