SDK简介
Shecare智能试纸识别SAAS SDK主要功能利用手机端的拍照功能对于目前市面上的排卵、早孕试纸进行自动拍照识别;该技术基于深度学习,在背景相对复杂的情况下识别宽松准确率可以达到90%,几乎接近人眼的识别水平;该SDK对于孕橙试纸识别宽松准确率超过95%,超过人眼的识别水平;
由于采用了深度学习技术,Shecare的试纸识别算法可以在半人工干预的情况下实现自我反馈、学习以及更新,随着学习样本库的增加,Shecare 智能试纸识别SAAS SDK识别的准确率会越来越高;
另外为了增强用户体验,Shecare 智能试纸识别SAAS SDK对于iOS和Android端采取native的集成方式,为了让集成快速落地,我们的API设计遵循接口简单化、高度配置化原则;
Shecare 智能试纸识别SAAS SDK目前支持市面上所有的条形、笔形以及卡型等排卵、早孕试纸;
宽松准确率 所谓的宽松准确率,指的是:
a.试纸的阴性和阳性的判断不能错误;
b.在满足条件1的前提下,读值相差一个档位不算错误;
孕橙APP的排卵试纸和早孕试纸模块同样调用了该SDK,如果想快速了解该SDK,可以扫描下方的二维码下载APP进行最终的用户体验;
SDK更新日志iOS
更新时间
版本
更新内容
2019-12-27
1.0
初始版本
Android
更新时间
版本
更新内容
2019-12-27
1.0
初始版本
2020-03-02
1.5.2
添加试纸单周期分析添加停止试纸分析网络请求
2020-04-02
1.5.3
优化sdk识别速度添加判断手机性能是否满足sdk要求
2020-06-24
1.5.5
优化sdk抠图速度
支持平台目前孕橙智能试纸识别算法SDK支持以下平台:
iOS
Android
体验DEMO安装为了让开发者快速上手,孕橙智能试纸识别算法SDK提供了体验Demo,并且该Demo提供完整的源码下载;详细的源码下载链接见下面的SDK代码说明;
iOS安装下载地址iOS fir.im
Android安装下载地址Android fir.im
测试图片下载为了快速体验Demo,可以下载下面的试纸,然后使用“图片选择“模式去体验我们的Demo:
https://yunchengfile.oss-cn-beijing.aliyuncs.com/others/shecare_45.jpeg
https://yunchengfile.oss-cn-beijing.aliyuncs.com/others/shecare_0.jpeg
三种识别模式目前SDK Demo支持三种模式,即“智能扫描”模式、“手动裁剪”模式以及“选择图片”模式,每一种模式都有不同的应用场景;
智能扫描模式所谓抠图模式,整个体验过程类似于二维码的“扫一扫”,具体的示例Demo的UI如下图,该模式有以下几点特性:
对于市面上所有的条形试纸均有效,但是对于非条形(如笔形、卡型等)试纸无效;
自动扫描、抠图以及自动出结果;
该模式需要用户主动点击“拍照”按钮;
注意该模式下需要把整条试纸放入“取景框”内;
一旦识别出结果,我们的API会弹出试纸“抠图确认”页面,在该页面我们的Demo可以指示出T线、C线位置,并且如果T、C线的位置如果错误,用户可以手动拖动T、C线进行修改;
打开拍照开关,会有“惊喜”;
手动裁剪模式所谓裁剪模式,即需要用户把试纸的T、C线放入取景框中示意的裁剪框“中央”,并且尽量保证T、C线与水平线“竖直”,具体的示例Demo的UI如下图:
模式是解决非条形(如笔形、卡型等)试纸的“杀手锏”;
该模式对于条形试纸同样有效;
该模式需要用户手动点击“拍照”按钮;
一旦识别出结果,我们的API会弹出试纸“抠图确认”页面,在该页面我们的Demo可以指示出T线、C线位置,并且如果T、C线的位置如果错误,用户可以手动拖动T、C线进行修改;
该模式需要引导用户T线位于C线的左边;
选择图片模式所谓“选择图片模式”,即对于用户已经拍照的、并且放入图片库的试纸进行识别,点击下图红色字体的指示按钮即可体验:
在该模式下,一旦选择的图片被识别,我们的API会弹出试纸“抠图确认”页面,在该页面我们的Demo可以指示出T线、C线位置,并且如果T、C线的位置如果错误,用户可以手动拖动T、C线进行修改;
如果这种模式下找不到试纸,我们的API可以弹出“裁剪图片”功能,让用户通过拖动、旋转、移动以及缩放把试纸区域拖动到裁剪区域,我们的APP基于此进行识别;
试纸识别结果确认框在所有的模式下,只要API识别出试纸,我们的API都会弹出试纸识别确认框,如下图:
该确认框会指示出T、C线的位置;
用户如果认为T、C线不正确,可以拖动这2条线;
如果试纸左右识别颠倒,用户可以点击“水平翻转”进行翻转修改;
如果用户对于试纸识别结果不满意,可以点击“返回”按钮返回;
调用流程我们的API,整体来说有两个接口:
一个接口的输入是视频流,然后对于视频流中的图片进行逐帧分析,然后给出结果,我们的“抠图模式”就是调用的这个API;
另外一个接口输入是图片,然后我们的API对于该图片进行分析,我们的“裁剪模式”、“选择图片模式”就是调用的这个API;
流程图输入参数是图片的API流程
流程描述
调用方输入一张图片给SDK;
SDK判断图片中是否有试纸图片,如果无则返回错误;
如果有,则弹出试纸识别结果确认框;
在该弹出对话框上,用户可以根据识别结果进行T、C线拖动调整,翻转调整;
如果用户在页面点击了“返回”按钮,则试纸结果确认对话框消失,调用重新进行下一张图片的识别;
如果用户在页面点击了“确认”按钮,则试纸结果确认对话框消失,调用方获取返回的试纸T、C线位置,抠出的图片内容以及LH Level等;
1234567几点约定: 1.对于输入的图片,我们要求必须是正方形,对于传入的长方形的图片,偶尔可能成功,但是不建议使用; 2.返回的T、C线为算法经过算法计算一个均值线; 3.虽然目前试纸API返回了试纸T、C线的4条边界“直线”,但是这4条“直线”边界本身可能不是一个严格的边界,有些看起来是白色区域,但是可能被包含在T、C线区域内,具体原因如下: a.试纸的T、C边界本身就不是一个boundry clear的边界; b.有时候肉眼无法看出的细微的“红色”,但是在计算机图形学里面可能就是一个色值,然后从T、C线的角度来说,可能会被计算在内;
输入参数是视频流的API流程图
流程描述
调用方输入视频流给SDK;
SDK对于视频流进行解析成一帧帧图片;
选取其中的一帧图片,判断图片中是否有试纸图片,如果无则继续分析下一帧图片;
如果有,则弹出试纸识别结果确认框;
在该弹出对话框上,用户可以根据识别结果进行T、C线拖动调整,翻转调整;
如果用户在页面点击了“返回”按钮,则试纸结果确认对话框消失,调用重新进行下一张图片的识别;
如果用户在页面点击了“确认”按钮,则试纸结果确认对话框消失,调用方获取返回的试纸T、C线位置,抠出的图片内容以及LH Level等;
12345几点约定: 1.返回的T、C线为算法经过算法计算一个均值线; 2.虽然目前试纸API返回了试纸T、C线的4条边界“直线”,但是这4条“直线”边界本身可能不是一个严格的边界,有些看起来是白色区域,但是可能被包含在T、C线区域内,具体原因如下: a.试纸的T、C边界本身就不是一个boundry clear的边界; b.有时候肉眼无法看出的细微的“红色”,但是在计算机图形学里面可能就是一个色值,然后从T、C线的角度来说,可能会被计算在内;
模块结构图
模块描述我们的试纸识别技术基于深度学习框架,共分为三个模块:
试纸识别API;
错误反馈机制;
Shecare Cloud模块;
试纸识别API整个识别过程共分为二个阶段,即一阶段的抠图(从图片或者视频流中抠出试纸条)以及二阶段的颜色识别;目前一阶段在本地实现,而二阶段是在云端实现,阶段二依赖于阶段一:也即是如果阶段一不成功,则不会进行阶段二;由于仅仅阶段二涉及到网络传输,并且这个仅仅传输抠出的非常小的试纸条,所以一次成功的识别网络传输的代价非常小;
错误反馈机制由于我们的试纸识别技术基于深度学习,所以我们会对于一些错误的case进行搜集,譬如在试纸确认页面,如果用户拖动了T、C线;再譬如在试纸确认页面用户选择了返回等,这些反馈对于我们算法的优化提供数据支撑;
Shecare Cloud模块该模块对于T、C线的值进行识别,并且对于用户输入的反馈进行处理,基于深度学习技术半自动化的优化我们的算法;
定制试纸识别结果确认框对话框说明
一旦智能试纸识别API从图片中识别出试纸,则弹出定制的试纸识别结果确认框(如下图),该确认框有如下作用:
试纸识别结果识别是一件从“医疗级”来说非常严肃的事情,所以经过用户的double confirm是必要的;
如果用户发现C、T线识别的位置不对,可以手动拖动进行修改,给试纸的结果识别带来一个潜在的“补救”机会;
如果用户发现识别出试纸“左右颠倒”,可以手动拖动进行修改,给试纸的结果识别带来一个潜在的“补救”机会;
调用方不需要处理确认页的“拖动”以及“点击”事件,更专注于自己的“业务逻辑”;
由于该确认框由SDK弹出,所以为了保证该弹出框能够适配调用方不同UI风格,该确认框高度灵活配置;
相关配置项如下图,定制项如下:
确认界面的语言以及对应的描述;
“确认”以及“返回”按钮的颜色以及图片;
抠出图片宽度外延的像素点;
试纸识别值的映射目前Shecare智能试纸识别SAAS SDK对于排卵试纸的值返回共有8个值:0、5、10、15、20、25、45、65,在这8个值中:
0代表无;
25属于阴阳的分界,并且25属于阳;
65最高值,属于强阳;
其他的值由调用者根据自己的业务进行灵活的映射和配置;
SDK代码说明iOS代码示例Demo 地址:https://e.coding.net/yuncheng/shecarepasdkdemo-ios/master.git (SDK 在 Demo 里)
函数说明SDK Config12345678910111213141516171819202122232425262728293031323334353637383940/** 设置 SDK 环境。默认是测试环境 YCSEnvironmentDebug */@property (assign, nonatomic) YCSEnvironment environment;/** 应用授权相关的 appID */@property (nonatomic, copy) NSString *appID;/** 应用授权相关的 appSecret */@property (nonatomic, copy) NSString *appSecret;/** 固定值,不需要设置 */@property (nonatomic, copy) NSString *sessionId;/** 用户身份标识符,全局唯一且同一个用户固定不变 */@property (nonatomic, copy) NSString *userID;/** 结果确认页 UI 主色调 */@property (nonatomic, strong) UIColor *mainColor;/** 结果确认页 字体 主色调 */@property (nonatomic, strong) UIColor *textColor;/** 结果确认页 “取消” 按钮图片 */@property (nonatomic, strong) UIImage *cancelImg;/** 结果确认页 “确认” 按钮图片 */@property (nonatomic, strong) UIImage *confirmImg;/** 结果确认页 T 滑块图片 */@property (nonatomic, strong) UIImage *tImage;/** 结果确认页 C 滑块图片 */@property (nonatomic, strong) UIImage *cImage;/** 结果确认页 标题 */@property (nonatomic, copy) NSString *title;/** 结果确认页 “翻转” 按钮标题 */@property (nonatomic, copy) NSString *flipTitle;/** 结果确认页 说明 */@property (nonatomic, copy) NSString *comment;/** 算法返回的图片是否需要 “外扩”,默认否 */@property (nonatomic, assign, getter=isExtended) ``BOOL` `extended;/** “外扩” 的像素(仅在 extended=true 时有效) */@property (nonatomic, assign) NSInteger pixelOfExtended;/** 扫描超时时长。默认为 15s,最少为 1s(仅在视频流扫描模式下有效) */@property (nonatomic, assign) CFTimeInterval timeIntervalOfScan;/** “连续成功” 的最少次数,默认为 5 次。为保证扫描结果准确性,建议采用 “连续多次扫描成功才认为整个流程成功” 的判定方法。(仅在视频流扫描模式下有效) */@property (nonatomic, assign) NSUInteger numberOfSuccess;/** 相同错误码连续出现的次数,默认为 3 次。算法可能在短时间内返回很多错误码,为保证用户体验,建议设置此值。用于控制 “相同错误码连续出现若干次,才在 UI 上提示用户” (仅在视频流扫描模式下有效) */@property (nonatomic, assign) NSUInteger numberOfErrors;/** 0未知;1 相册;2 拍照 */@property (nonatomic, assign) SCImageType source;
主函数调用12345678/*! @brief 获取一张图片的扫描和分析结果* @param image 用于扫描的试纸照片* @param completion 完成回调,用于返回扫描和分析的结果*/-(``void``)getScanResultFromImage:(UIImage *)image completion:(``void` `(^)(SCPaperAnalysiserResult *result))completion;/*! @brief 结束扫描和分析流程*/-(``void``)closeSession:(SCPaperAnalysiserResult * _Nullable)result;
返回值1234567891011121314151617181920212223242526272829303132333435363738/** 最终返回的抠图结果 */@property (nonatomic, strong, readonly) UIImage *finalImage;/** 算法返回的 四边形 坐标点 */@property (nonatomic, strong, nullable) NSArray *maskPoints;/** 算法返回的 错误码 */@property (nonatomic, assign) SCErrorCode errorCode;/** 算法返回的 模糊度 */@property (nonatomic, assign) CGFloat blurExtent;/** 算法返回的 C 线位置 */@property (nonatomic, assign) CGFloat cPosition;/** 算法返回的 T 线位置 */@property (nonatomic, assign) CGFloat tPosition;/** 算法返回的 T 线区域左边缘位置 */@property (nonatomic, assign) CGFloat lhTlineLeft;/** 算法返回的 T 线区域右边缘位置 */@property (nonatomic, assign) CGFloat lhTlineRight;/** 算法返回的 C 线区域左边缘位置 */@property (nonatomic, assign) CGFloat lhClineLeft;/** 算法返回的 C 线区域右边缘位置 */@property (nonatomic, assign) CGFloat lhClineRight;/** 用户确认的 C 线位置 */@property (nonatomic, assign) CGFloat newCPosition;/** 用户确认的 T 线位置 */@property (nonatomic, assign) CGFloat newTPosition;/** 是否翻转 */@property (nonatomic, assign) ``BOOL` `flipped;/** 算法返回的试纸分析结果 */@property (nonatomic, assign) NSInteger lhResult;/** 用户确认的试纸结果 */@property (nonatomic, assign) NSInteger newLHResult;/** 算法返回的 Ratio 值 */@property (nonatomic, assign) CGFloat lhRatio;/** 算法返回的试纸类型 */@property (nonatomic, assign) SCPaperType paperType;/** 试纸测试时间 */@property (nonatomic, strong) NSDate *lhTime;/** 试纸图片来源:1 相册;2 拍照 */@property (nonatomic, assign) SCImageType source;
错误描述123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051/// 试纸抠图、分析错误码typedef NS_ENUM(NSInteger, SCErrorCode) { /// 抠图成功、分析成功,流程内没有错误产生; SCErrorCodeNoError = 0, /// SDK 校验失败或无效 SCErrorCodeSDKError = -2, /// 未知错误(默认值); SCErrorCodeUnknownError = -1, /* 阶段 1 中间过程错误码(或单张照片扫描结果的错误码) */ /// 没有找到试纸; SCErrorCodeNoPaper = 1, /// 距离过远; SCErrorCodeTooFar = 2, /// 背景过脏; SCErrorCodeTooDirty = 3, /// 距离过近; SCErrorCodeTooClose = 4, /// 试纸不全; SCErrorCodeNotCompleted = 5, /// 神经网络加载错误; SCErrorCodeHedNetError = 6, /// 背景内不止一张试纸; SCErrorCodeTooManyPapers = 7, /// 曝光不足; SCErrorCodeUnderExposure = 8, /// 曝光过度; SCErrorCodeExposed = 9, /// 试纸局部曝光过度; SCErrorCodePartlyExposed = 10, /// 画面模糊; SCErrorCodeBlurred = 11, /// 曝光不足(OpenCV 二次确认); SCErrorCodeUnderExposure2 = 12, /* 阶段 1 结束错误码 */ /// 抠图失败,视频流扫描超时 SCErrorCodeVideoOutofDate = 17, /* 阶段 2 错误码 */ /// 抠图成功,但是分析失败 SCErrorCodeGetValueError = 101, /* 阶段 3 错误码 */ /// 抠图成功,分析成功,但是用户取消确认抠图和分析结果 SCErrorCodeUserCanceled = 201, /// 抠图成功,分析成功,但未检测到参考线,请确认试纸有参考线显示 SCErrorCodeNoCLine = 202,};
调用场景代码片段“抠图模式”代码调用片段123456789101112131415161718192021222324252627282930313233343536373839404142434445// “视频抠图模式” 下,调用方需实现以下三个代理方法-(void)analysiser:(SCPaperAnalysiser *)analysiser didGetVideoResult:(SCPaperAnalysiserResult *)result bkImage:(UIImage *)bkImage { self.curOutputImage = bkImage; // 根据 SDK 返回的 “试纸区域” 顶点坐标,绘制 “试纸区域” 到页面 UI if (result.maskPoints.count == 0) { // 没有返回顶点坐标时,刷新页面 [self setupMaskLayer:[self nullPoints]]; } else { [self addMaskLayerWithPoints:result.maskPoints onImage:bkImage]; }} -(void)analysiser:(SCPaperAnalysiser *)analysiser didFinishVideoScan:(SCPaperAnalysiserResult *)result { // SDK 回调,后续处理取决于业务需求,Demo 仅为示例 // SDK 成功返回抠图和分析结果 if (result.error.code == SCErrorCodeNoError) { NSLog(@"Result: %@", result); dispatch_async(dispatch_get_main_queue(), ^{ YCLHResultViewController *vc = [[YCLHResultViewController alloc] initWithResult:result]; [self.navigationController pushViewController:vc animated:YES]; }); } else if (SCErrorCodeVideoOutofDate == result.error.code) { // 扫描超时,切换到手动裁剪模式 dispatch_async(dispatch_get_main_queue(), ^{ [self setupUIWithScanning:false]; self.switchBtn.selected = false; }); } else { // 其他错误 [self analysiser:analysiser didGetVideoError:[self analysiser:analysiser messageWithError:result.error]]; }} // SDK 返回错误信息时的处理-(void)analysiser:(SCPaperAnalysiser *)analysiser didGetVideoError:(NSString *)errInfo { dispatch_async(dispatch_get_main_queue(), ^{ if (errInfo.length > 0) { [self.errorLbl setTitle:errInfo forState:UIControlStateNormal]; self.errorLbl.alpha = 1.0f; } else { self.errorLbl.alpha = 0.0f; } });}
“裁剪模式”代码调用片段123456789101112131415161718192021// 调用方使用以下方法处理 “照片手动裁剪” 模式下的 SDK 调用// 传入的 image 是 “经过了用户拖动、缩放、旋转等操作后的当前页面截图”// points 是 “裁剪框” 的左上角和右下角顶点坐标[[SCPaperAnalysiser shared] getScanResultFromSnapShot:image points:points completion:^(SCPaperAnalysiserResult * _Nonnull result) { result.source = SCImageSourceAlbum; dispatch_async(dispatch_get_main_queue(), ^{ YCLHResultViewController *vc = [[YCLHResultViewController alloc] initWithResult:result]; [self.navigationController pushViewController:vc animated:YES]; });}]; // 调用方使用以下方法处理 “视频手动裁剪” 模式下的 SDK 调用,类似 “照片手动裁剪” 模式// 传入的 image 是 “用户点击拍照时的那一帧”// points 是 “裁剪框” 的左上角和右下角顶点坐标[[SCPaperAnalysiser shared] getScanResultFromSnapShot:self.curOutputImage points:[self defaultPoints:self.curOutputImage] completion:^(SCPaperAnalysiserResult * _Nonnull result) { result.source = SCImageSourceCamera; dispatch_async(dispatch_get_main_queue(), ^{ YCLHResultViewController *vc = [[YCLHResultViewController alloc] initWithResult:result]; [self.navigationController pushViewController:vc animated:YES]; });}];
“图片选择模式”代码调用片段123456789101112131415161718// 调用方使用以下方法实现 “图片选择模式” 下的 SDK 方法调用SCPaperAnalysiserConfiguration.shared.source = SCImageSourceAlbum;YCWeakSelf(self)[[SCPaperAnalysiser shared] getScanResultFromImage:oriImage completion:^(SCPaperAnalysiserResult * _Nonnull result) { YCStrongSelf(self) // SDK 回调,后续处理取决于业务需求,Demo 仅为示例 if (result.error.code == SCErrorCodeNoError || result.error.code == SCErrorCodeNoCLine) { NSLog(@"Result: %@", result); dispatch_async(dispatch_get_main_queue(), ^{ YCLHResultViewController *vc = [[YCLHResultViewController alloc] initWithResult:result]; [self.navigationController pushViewController:vc animated:YES]; }); } else { NSLog(@"Error: %@", result.error); }}];
“试纸结果识别确认框”样式代码调用片段12345678910111213// SDK 配置参数,详细说明见 SDK Config 或 framework 头文件SCPaperAnalysiserConfiguration *scConfig = [SCPaperAnalysiserConfiguration shared];scConfig.extended = true;scConfig.pixelOfExtended = 2;scConfig.cancelImg = [UIImage imageNamed:@"test_paper_return"];scConfig.confirmImg = [UIImage imageNamed:@"test_paper_confirm"];scConfig.title = @"拍照结果";scConfig.flipTitle = @"水平翻转";scConfig.comment = @"请拖动TC线以确认TC线标注正确,水平翻转保证MAX箭头指向左边";scConfig.cImage = [UIImage imageNamed:@"c_line_slices"];scConfig.tImage = [UIImage imageNamed:@"t_line_slices"];scConfig.mainColor = [UIColor colorWithHex:0xFF7486];scConfig.textColor = [UIColor colorWithHex:0x444444];
如何debugSDK 的日志会和应用里的 NSLog(Swift 对应 print)输出到一起,分别以 Shecare(Debug)、Shecare(Info)、Shecare(Error) 开头表示不同的日志等级。需要 Debug 时,把 Shecare 开头的日志上传即可。
Android代码示例Demo 地址:https://e.coding.net/yuncheng/ScPaperAnalysiserDemo_Android.git
ScPaperAnalysiserDemo_Android一.引入试纸sdk库 1api 'com.ikangtai.papersdk:ScPaperAnalysiserLib:1.5.5'
二.添加依赖库地址 1maven { url 'https://dl.bintray.com/ikangtaijcenter123/ikangtai' }
三.使用方法 123456789//网络配置需要在初始化sdk之前//使用测试网络Config.setTestServer(true);//网络超时时间Config.setNetTimeOut(30);//判断手机性能是否满足sdk要求1.SupportDeviceUtil.isSupport(getContext(),AppConstant.appId, AppConstant.appSecret)#第一次校验不准2.application初始化中调用SupportDeviceUtil.isSupport(getContext(),AppConstant.appId, AppConstant.appSecret),实际判断处调用SupportDeviceUtil.isSupport(getContext())
1.初始化
12//初始化sdkpaperAnalysiserClient = new PaperAnalysiserClient(getContext(), appId, appSecret, "xyl1@qq.com");
2.常规配置
12345678910111213141516/*** log默认路径/data/Android/pageName/files/Documents/log.txt,可以通过LogUtils.getLogFilePath()获取* 自定义log文件有两种方式,设置一次即可* 1. {@link Config.Builder#logWriter(Writer)}* 2. {@link Config.Builder#logFilePath(String)}*/String logFilePath = new File(FileUtil.createRootPath(getContext()), "log_test.txt").getAbsolutePath();BufferedWriter logWriter = null;try { logWriter = new BufferedWriter(new FileWriter(logFilePath, true), 2048);} catch (IOException e) { e.printStackTrace();}//试纸识别sdk相关配置Config config = new Config.Builder().pixelOfdExtended(true).paperMinHeight(PxDxUtil.dip2px(getContext(), 20)).uiOption(uiOption).logWriter(logWriter).build();paperAnalysiserClient = new PaperAnalysiserClient(getContext(), appId, appSecret, "xyl1@qq.com",config);
3.UI配置
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576//定制试纸Ui显示/** * 标题 */String titleText = getContext().getString(com.ikangtai.papersdk.R.string.paper_result_dialog_title);/** * 标题颜色 */int titleTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_444444);/** * 标尺线 */int tagLineImageResId = com.ikangtai.papersdk.R.drawable.paper_line;/** * t滑块图标 */int tLineResId = com.ikangtai.papersdk.R.drawable.test_paper_t_line;/** * c滑块图标 */int cLineResId = com.ikangtai.papersdk.R.drawable.test_paper_c_line;/** * 水平翻转文字 */String flipText = getContext().getString(com.ikangtai.papersdk.R.string.paper_result_dialog_flip);/** * 水平翻转文字颜色 */int flipTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_67A3FF);/** * 提示文字 */String hintText = getContext().getString(com.ikangtai.papersdk.R.string.paper_result_dialog_hit);/** * 提示文字颜色 */int hintTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_444444);/** * 返回按钮 */int backResId = com.ikangtai.papersdk.R.drawable.test_paper_return;/** * 确认按钮 */int confirmResId = com.ikangtai.papersdk.R.drawable.test_paper_confirm;/** * 返回按钮文字颜色 */int backButtonTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_444444);/** * 确认按钮文字颜色 */int confirmButtonTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_444444);/** * 显示底部按钮 */boolean visibleBottomButton = false;UiOption uiOption = new UiOption.Builder() .titleText(titleText) .tagLineImageResId(tagLineImageResId) .titleTextColor(titleTextColor) .tLineResId(tLineResId) .cLineResId(cLineResId) .flipText(flipText) .flipTextColor(flipTextColor) .hintText(hintText) .hintTextColor(hintTextColor) .backResId(backResId) .confirmResId(confirmResId) .backButtonTextColor(backButtonTextColor) .confirmButtonTextColor(confirmButtonTextColor) .visibleBottomButton(visibleBottomButton) .build();//试纸识别sdk相关配置Config config = new Config.Builder().pixelOfdExtended(true).margin(50).uiOption(uiOption).netTimeOutRetryCount(1).build();paperAnalysiserClient.init(config);
4.调用识别试纸图片
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465paperAnalysiserClient.analysisBitmap(fileBitmap, new IBitmapAnalysisEvent() { @Override public void showProgressDialog() { //显示加载框 LogUtils.d("Show Loading Dialog"); that.showProgressDialog(new View.OnClickListener() { @Override public void onClick(View v) { //停止网络请求 paperAnalysiserClient.stopShowProgressDialog(); } }); } @Override public void dismissProgressDialog() { //隐藏加载框 LogUtils.d("Hide Loading Dialog"); } @Override public void cancel() { LogUtils.d("取消试纸结果确认"); //试纸结果确认框取消 ToastUtils.show(getContext(), AiCode.getMessage(AiCode.CODE_201)); } @Override public void save(PaperResult paperResult) { LogUtils.d("保存试纸分析结果:\n"+paperResult.toString()); //试纸结果确认框确认 显示试纸结果 if (paperResult.getErrNo() != 0) { ToastUtils.show(getContext(), AiCode.getMessage(paperResult.getErrNo())); } } @Override public boolean analysisSuccess(PaperCoordinatesData paperCoordinatesData, Bitmap originSquareBitmap, Bitmap clipPaperBitmap) { LogUtils.d("试纸自动抠图成功"); return false; } @Override public void analysisError(PaperCoordinatesData paperCoordinatesData, String errorResult, int code) { LogUtils.d("试纸自动抠图出错 code:" + code + " errorResult:" + errorResult); //试纸抠图失败结果 ToastUtils.show(getContext(), AiCode.getMessage(code)); } @Override public void saasAnalysisError(String errorResult, int code) { LogUtils.d("试纸分析出错 code:" + code + " errorResult:" + errorResult); //试纸saas分析失败 ToastUtils.show(getContext(), AiCode.getMessage(code)); } @Override public void paperResultDialogShow(PaperResultDialog paperResultDialog) { paperResultDialog.getHintTv().setGravity(Gravity.LEFT); paperResultDialog.setSampleResId(R.drawable.confirm_sample_pic_lh); } });
5.识别视频流 TextureView视频预览
123456789101112131415Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() { @Override public void onPreviewFrame(final byte[] data, final Camera camera) { if (paperAnalysiserClient.isObtainPreviewFrame()) { return; } startTime = System.currentTimeMillis(); //视频上半部分正方形图片 //Bitmap originSquareBitmap= TensorFlowTools.convertFrameToBitmap(data, camera, surfaceView.getWidth(), surfaceView.getWidth(), TensorFlowTools.getDegree(getActivity())); //Bitmap originSquareBitmap = TensorFlowTools.convertFrameToBitmap(data, camera, TensorFlowTools.getDegree(getActivity())); Bitmap originSquareBitmap = ImageUtil.topCropBitmap(textureView.getBitmap()); paperAnalysiserClient.analysisCameraData(originSquareBitmap); } };cameraUtil.initCamera(getActivity(), textureView, mPreviewCallback);
SurfaceView视频预览
123456789101112131415161718192021222324252627282930Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() { @Override public void onPreviewFrame(final byte[] data, final Camera camera) { if (paperAnalysiserClient.isObtainPreviewFrame()) { return; } startTime = System.currentTimeMillis(); //视频上半部分正方形图片 Bitmap originSquareBitmap = TensorFlowTools.convertFrameToBitmap(data, camera, TensorFlowTools.getDegree(getActivity())); paperAnalysiserClient.analysisCameraData(originSquareBitmap); }};cameraUtil.initCamera(getActivity(), surfaceView, mPreviewCallback);//需要在analysisSuccess和analysisResult回调方法进行坐标转换ICameraAnalysisEvent iCameraAnalysisEvent = new ICameraAnalysisEvent() { @Override public boolean analysisSuccess(PaperCoordinatesData paperCoordinatesData, Bitmap originSquareBitmap, Bitmap clipPaperBitmap) { .... PaperCoordinatesData newPaperCoordinatesData = TensorFlowTools.convertPointToScreen(cameraUtil.getCurrentCamera(), surfaceView.getWidth(), surfaceView.getHeight(), paperCoordinatesData); smartPaperMeasureContainerLayout.showAutoSmartPaperMeasure(newPaperCoordinatesData, originSquareBitmap); return false; } @Override public void analysisResult(PaperCoordinatesData paperCoordinatesData) { .... PaperCoordinatesData newPaperCoordintaesData = TensorFlowTools.convertPointToScreen(cameraUtil.getCurrentCamera(), surfaceView.getWidth(), surfaceView.getHeight(), paperCoordinatesData); smartPaperMeasureContainerLayout.showAutoSmartPaperMeasure(newPaperCoordintaesData, null); }}
7.调用完成释放资源
1paperAnalysiserClient.closeSession();
8.混淆代码过滤
12345-dontwarn com.ikangtai.papersdk.**-keep class com.ikangtai.papersdk.** {*;}-keepclasseswithmembernames class *{ native
函数说明主函数调用123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960public class PaperAnalysiserClient {/** * @param context * @param appId appId * @param appSecret appSecret * @param unionId 用户唯一id */public PaperAnalysiserClient(Context context, String appId, String appSecret, String unionId);public PaperAnalysiserClient(Context context, String appId, String appSecret, String unionId,Config config);/** * 初始化 * @param config */public void init(Config config);/** * 识别试纸图片并弹出试纸识别结果弹框 * @param fileBitmap 试纸图片bitmap * @param event 识别结果回调 */public void analysisBitmap(final Bitmap fileBitmap, final IBitmapAnalysisEvent event);/** * 处理Camera 视频流 * 需要配置Config.analysisSuccessCount,默认三次成功即显示试纸识别结果 */public void analysisCameraData(final Bitmap originSquareBitmap);/** * 从视频拍照调用手动裁剪模式 * @param bitmap * @param upLeftPoint 左上角坐标 * @param rightBottomPoint 右下角坐标 * @param event 回调 */public void analysisClipBitmapFromCamera(Bitmap bitmap, Point upLeftPoint, Point rightBottomPoint, final IBaseAnalysisEvent event);/** * 从相册选择调用手动裁剪模式 * @param bitmap * @param upLeftPoint 左上角坐标 * @param rightBottomPoint 右下角坐标 * @param event 回调 */public void analysisClipBitmapFromPhoto(Bitmap bitmap, Point upLeftPoint, Point rightBottomPoint, final IBaseAnalysisEvent event);/** * 手动修改lhValue值 */public void updatePaperValue(int paperValue) ;/** * 试纸单周期分析算法 * @param papers 单周期papers * @param event 回调 */public void paperCycleAnalysis(ArrayList
SDK Config123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 //试纸sdk配置public class Config {/** * debug模式控制台打印日志 */private static boolean debug = true;/** * 使用测试服 */private static boolean testServer = false;/** * 模糊阈值 */private int blurLimitValue = 25;/** * 同一个错误码连续出现2次 */private int analysisErrorCount = 2; /** * 试纸存储文件夹路径 */private String filePath; /** * 日志存储路径 */private String logFilePath;/** * 是否外扩 */private boolean pixelOfdExtended;/** * 外扩像素 */private int margin;/** * 弹框ui配置 */private UiOption uiOption;/** * 网络请求超时时间 */private static int netTimeOut = 10;/** * 日志文件句柄 */private Writer logWriter;} //弹框Ui配置public class UiOption {/** * 标题 */private String titleText;/** * 标题颜色 */private int titleTextColor;/** * 标尺线 */private int tagLineImageResId;/** * t滑块图标 */private int tLineResId;/** * c滑块图标 */private int cLineResId;/** * 水平翻转文字 */private String flipText;/** * 水平翻转文字颜色 */private int flipTextColor;/** * 提示文字 */private String hintText;/** * 提示文字颜色 */private int hintTextColor;/** * 返回图片 */private int backResId;/** * 确认图片 */private int confirmResId;/** * tc线宽度 */private float tcLineWidth;/** * 返回按钮背景 */private int backButtonBgResId;/** * 确认按钮背景 */private int confirmButtonBgResId;/** * 返回按钮文字 */private String backButtonText;/** * 确认按钮文字 */private String confirmButtonText;/** * 显示底部按钮 */private boolean visibleBottomButton;/** * 返回按钮文字颜色 */private int backButtonTextColor;/** * 确认按钮文字颜色 */private int confirmButtonTextColor;} //基类回调public interface IBaseAnalysisEvent { /** * 显示加载框 */ void showProgressDialog(); /** * 隐藏加载框 */ void dismissProgressDialog(); /** * 试纸结果弹框取消 */ void cancel(); /** * 试纸结果弹框保存 * * @param paperResult 最终确认保存数据 */ void save(PaperResult paperResult); /** * SaaS识别错误 * @param errorResult 错误描述 * @param code 错误码 */ void saasAnalysisError(String errorResult, int code); /** * 试纸结果确认弹框 * @param PaperResultDialog * */ void paperResultDialogShow(PaperResultDialog var1); }//试纸抠图回调public interface IBitmapAnalysisEvent extends IBaseAnalysisEvent{ /** * 试纸抠图成功返回结果 * * @param paperCoordinatesData 抠图结果数据,包括坐标点、模糊值 * @param originSquareBitmap 试纸正方形bitmap * @param clipPaperBitmap 抠图试纸条 */ boolean analysisSuccess(PaperCoordinatesData paperCoordinatesData, Bitmap originSquareBitmap, Bitmap clipPaperBitmap); /** * 试纸抠图错误 * @param paperCoordinatesData 抠图结果数据,包括坐标点、模糊值可能为空 * @param errorResult 错误描述 * @param code 错误码 */ void analysisError(PaperCoordinatesData paperCoordinatesData,String errorResult, int code); }//视频分析回调public interface ICameraAnalysisEvent extends IBitmapAnalysisEvent { /** * AI分析过程中产生的结果 * * @param paperCoordinatesData */ void analysisResult(PaperCoordinatesData paperCoordinatesData); /** * AI分析15s结束 * * @param originSquareBitmap 结束时原始正方形 * @param code */ void analysisEnd(Bitmap originSquareBitmap, int code, String errorResult); /** * AI分析取消 * * @param originSquareBitmap 取消时原始正方形 * @param code */ void analysisCancel(Bitmap originSquareBitmap, int code, String errorResult);} //试纸识别结果public class PaperCoordinatesData { /** * 错误码 */ private int code; /** * 模糊值 */ private double blurValue; /** * 上下左右坐标 */ private Point point1; private Point point2; private Point point3; private Point point4; /** * 抠图结果mat对象,可转换成试纸条bitmap */ private Mat imageL;}//试纸识别弹框保存数据public class PaperResult { /** * id */ private String paperId; /** * 试纸时间 */ private String paperTime; /** * 错误码 */ private int errNo; /** * 错误描述 */ private String errMsg; /** * tc线比例 */ private double tLinePos; private double cLinePos; /** * 试纸类型 */ private int paperType; /** * 失败结果 */ private double paperValue; /** * ratio结果 */ private double ratioValue; /** * 是否翻转 */ private boolean flip; /** * 模糊度 */ private double blurValue; /** * 试纸抠图结果 */ private Bitmap paperBitmap; /** * 试纸抠图不加margin结果 */ private Bitmap noMarginBitmap; /** * tc线左右边界位置 */ private double clineLeft; private double clineRight; private double tlineLeft; private double tlineRight;}
错误描述1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556//图片错误codes.put(CODE_IMAGE_ERROR, "图片错误");//未知错误codes.put(CODE_ERROR, "未知错误");//抠图成功codes.put(CODE_0, "抠图成功");//没有试纸;错误提示文案:没有找到试纸codes.put(CODE_1, "没有找到试纸");//距离过远;错误提示文案:距离过远,请调整拍摄距离codes.put(CODE_2, "距离过远,请调整拍摄距离");// 背景过脏;错误提示文案:背景有干扰,请在浅色纯背景下拍摄codes.put(CODE_3, "背景有干扰,请在浅色纯背景下拍摄");// 距离过近;错误提示文案:距离过近,请调整拍摄距离codes.put(CODE_4, "距离过近,请调整拍摄距离");//有残缺;错误提示文案:试纸不全,请保持全部试纸处在取景框内codes.put(CODE_5, "试纸不全,请保持全部试纸处在取景框内");// 神经网络处理错误;错误提示文案:算法处理中,请稍候codes.put(CODE_6, "算法处理中,请稍候");// 两张试纸;错误提示文案:一次只能拍摄一张试纸codes.put(CODE_7, "一次只能拍摄一张试纸");// 曝光不足;错误提示文案:光线太暗,请调整光线或打开闪光灯后拍摄codes.put(CODE_8, "光线太暗,请调整光线或打开闪光灯后拍摄");// 曝光过度;错误提示文案:光线太强,请调整光线或关掉闪光灯后拍摄codes.put(CODE_9, "光线太强,请调整光线或关掉闪光灯后拍摄");// 试纸局部过度曝光;错误提示文案:局部光线太强,请调整光线或关掉闪光灯后拍摄codes.put(CODE_10, "局部光线太强,请调整光线或关掉闪光灯后拍摄");// 画面模糊;错误提示文案:画面模糊,请保持手机稳定或重新对焦codes.put(CODE_11, "画面模糊,请保持手机稳定或重新对焦");// 曝光不足;错误提示文案:光线太暗,请调整光线或打开闪光灯后拍摄codes.put(CODE_12, "光线太暗,请调整光线或打开闪光灯后拍摄");//视频流扫描超时codes.put(CODE_17, "视频流扫描超时");/// 手动裁剪失败,请检查传入的图片和坐标点codes.put(CODE_50, "手动裁剪失败,请检查传入的图片和坐标点");//抠图成功,分析成功,但是用户取消确认抠图和分析结果。codes.put(CODE_201, "用户取消");//抠图成功,分析成功,但未检测到参考线,请确认试纸有参考线显示codes.put(CODE_202, "未检测到参考线,请确认试纸有参考线显示");//抠图成功,但是分析失败codes.put(CODE_101, "试纸分析出错");//SDK 校验失败或无效codes.put(CODE_SDK_ERROR, "SDK 校验失败或无效");//网络错误codes.put(CODE_NET_ERROR, "网络错误");codes.put(CODE_501, "缺少相关参数");codes.put(CODE_503, "频率过高,关入小黑屋");codes.put(CODE_500, "认证错误");codes.put(CODE_502, "超时,认证无效");codes.put(CODE_301, "参数不合法");codes.put(CODE_302, "分析结果转化json出错");codes.put(CODE_303, "分析失败,服务返回");codes.put(CODE_304, "图片base64编解码失败");codes.put(CODE_510, "分析失败");codes.put(CODE_511, "试纸无效"); 通过AiCode.getMessage(CODE)获取错误描述
调用场景代码片段“抠图模式”代码调用片段123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144"智能扫描"代码调用片段 /** * 在Camera 使用TextureView实时预览回调进行识别抠图识别 */private Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() { @Override public void onPreviewFrame(final byte[] data, final Camera camera) { if (paperAnalysiserClient.isObtainPreviewFrame()) { return; } //视频上半部分正方形图片 //Bitmap originSquareBitmap = TensorFlowTools.convertFrameToBitmap(data, camera, TensorFlowTools.getDegree(getActivity())); Bitmap originSquareBitmap = ImageUtil.topCropBitmap(textureView.getBitmap()); paperAnalysiserClient.analysisCameraData(originSquareBitmap); }}; cameraUtil.initCamera(getActivity(), textureView, mPreviewCallback);//抠图结果回调private ICameraAnalysisEvent iCameraAnalysisEvent = new ICameraAnalysisEvent() { @Override public boolean analysisSuccess(PaperCoordinatesData paperCoordinatesData, Bitmap originSquareBitmap, Bitmap clipPaperBitmap) { if (scanMode == MANUALSMART) { return false; } LogUtils.d("试纸自动抠图成功"); ToastUtils.show(getContext(), "抠图最终结果"); return false; } @Override public void analysisResult(PaperCoordinatesData paperCoordinatesData) { if (scanMode == MANUALSMART) { return; } LogUtils.d("试纸自动抠图画线"); Log.d("xyl", "抠图耗时 " + (System.currentTimeMillis() - startTime)); ToastUtils.show(getContext(), "抠图中间结果"); } @Override public void analysisEnd(Bitmap originSquareBitmap, int code, String errorResult) { LogUtils.d("试纸抠图结束 code:" + code + " errorResult:" + errorResult); ToastUtils.show(getContext(), "抠图超时"); scanMode = MANUALSMART; } @Override public void analysisCancel(Bitmap originSquareBitmap, int code, String errorResult) { LogUtils.d("试纸抠图取消 code:" + code + " errorResult:" + errorResult); ToastUtils.show(getContext(), "切换抠图模式,取消视频流分析"); } @Override public void showProgressDialog() { LogUtils.d("Show Loading Dialog"); that.showProgressDialog(new View.OnClickListener() { @Override public void onClick(View v) { //停止网络请求 paperAnalysiserClient.stopShowProgressDialog(); } }); } @Override public void dismissProgressDialog() { LogUtils.d("Hide Loading Dialog"); } @Override public void cancel() { LogUtils.d("取消试纸结果确认"); } @Override public void save(PaperResult paperResult) { LogUtils.d("保存试纸分析结果:\n" + paperResult.toString()); smartPaperMeasureContainerLayout.showAutoSmartPaperMeasure(null, null); if (paperResult.getErrNo() != 0) { ToastUtils.show(getContext(), AiCode.getMessage(paperResult.getErrNo())); } } @Override public void analysisError(PaperCoordinatesData paperCoordinatesData, String errorResult, int code) { if (scanMode == MANUALSMART) { return; } LogUtils.d("试纸抠图出错 code:" + code + " errorResult:" + errorResult); LogUtils.d("抠图耗时 " + (System.currentTimeMillis() - startTime)); } @Override public void saasAnalysisError(String errorResult, int code) { LogUtils.d("试纸分析错误 code:" + code + " errorResult:" + errorResult); ToastUtils.show(getContext(), errorResult + code); } @Override public void paperResultDialogShow(PaperResultDialog paperResultDialog) { }};/** * 在Camera 使用SurfaceView实时预览回调进行识别抠图识别 */ Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() { @Override public void onPreviewFrame(final byte[] data, final Camera camera) { if (paperAnalysiserClient.isObtainPreviewFrame()) { return; } startTime = System.currentTimeMillis(); //视频上半部分正方形图片 Bitmap originSquareBitmap = TensorFlowTools.convertFrameToBitmap(data, camera, TensorFlowTools.getDegree(getActivity())); paperAnalysiserClient.analysisCameraData(originSquareBitmap); } };cameraUtil.initCamera(getActivity(),surfaceView, mPreviewCallback);//需要在analysisSuccess和analysisResult回调方法进行坐标转换ICameraAnalysisEvent iCameraAnalysisEvent = new ICameraAnalysisEvent() { @Override public boolean analysisSuccess(PaperCoordinatesData paperCoordinatesData, Bitmap originSquareBitmap, Bitmap clipPaperBitmap) { .... PaperCoordinatesData newPaperCoordinatesData = TensorFlowTools.convertPointToScreen(cameraUtil.getCurrentCamera(), surfaceView.getWidth(), surfaceView.getHeight(), paperCoordinatesData); smartPaperMeasureContainerLayout.showAutoSmartPaperMeasure(newPaperCoordinatesData, originSquareBitmap); return false; } @Override public void analysisResult(PaperCoordinatesData paperCoordinatesData) { .... PaperCoordinatesData newPaperCoordintaesData = TensorFlowTools.convertPointToScreen(cameraUtil.getCurrentCamera(), surfaceView.getWidth(), surfaceView.getHeight(), paperCoordinatesData); smartPaperMeasureContainerLayout.showAutoSmartPaperMeasure(newPaperCoordintaesData, null); }} paperAnalysiserClient.setCameraDataCallback(iCameraAnalysisEvent);
“裁剪模式”代码调用片段12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091"手动裁剪模式"代码调用片段//从相册选择抠图失败手动裁剪模式试纸识别paperAnalysiserClient.analysisClipBitmapFromPhoto(clipBitmap, upLeftPoint, rightBottomPoint, new IBaseAnalysisEvent() { @Override public void showProgressDialog() { LogUtils.d("Show Loading Dialog"); that.showProgressDialog(new View.OnClickListener() { @Override public void onClick(View v) { //停止网络请求 paperAnalysiserClient.stopShowProgressDialog(); } }); } @Override public void dismissProgressDialog() { LogUtils.d("Hide Loading Dialog"); } @Override public void cancel() { LogUtils.d("取消试纸结果确认"); } @Override public void save(PaperResult paperResult) { LogUtils.d("保存试纸分析结果:\n"+paperResult.toString()); if (paperResult.getErrNo() != 0) { ToastUtils.show(getContext(), AiCode.getMessage(paperResult.getErrNo())); } } @Override public void saasAnalysisError(String errorResult, int code) { LogUtils.d("试纸分析出错 code:" + code + " errorResult:" + errorResult); } @Override public void paperResultDialogShow(PaperResultDialog paperResultDialog) { }}); //从视频拍照页面手动裁剪模式试纸识别paperAnalysiserClient.analysisClipBitmapFromCamera(fileBitmap, upLeftPoint, rightBottomPoint, new IBaseAnalysisEvent() { @Override public void showProgressDialog() { LogUtils.d("Show Loading Dialog"); that.showProgressDialog(new View.OnClickListener() { @Override public void onClick(View v) { //停止网络请求 paperAnalysiserClient.stopShowProgressDialog(); } }); } @Override public void dismissProgressDialog() { LogUtils.d("Show Loading Dialog"); } @Override public void cancel() { LogUtils.d("取消试纸结果确认"); ToastUtils.show(getContext(), AiCode.getMessage(AiCode.CODE_201)); } @Override public void save(PaperResult paperResult) { LogUtils.d("保存试纸分析结果:\n" + paperResult.toString()); if (paperResult.getErrNo() != 0) { ToastUtils.show(getContext(), AiCode.getMessage(paperResult.getErrNo())); } } @Override public void saasAnalysisError(String errorResult, int code) { LogUtils.d("试纸分析出错 code:" + code + " errorResult:" + errorResult); ToastUtils.show(getContext(), AiCode.getMessage(code)); } @Override public void paperResultDialogShow(PaperResultDialog paperResultDialog) { }});
“图片选择模式”代码调用片段12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364"图片选择模式"代码调用片段paperAnalysiserClient.analysisBitmap(fileBitmap, new IBitmapAnalysisEvent() { @Override public void showProgressDialog() { //显示加载框 LogUtils.d("Show Loading Dialog"); that.showProgressDialog(new View.OnClickListener() { @Override public void onClick(View v) { //停止网络请求 paperAnalysiserClient.stopShowProgressDialog(); } }); } @Override public void dismissProgressDialog() { //隐藏加载框 LogUtils.d("Hide Loading Dialog"); } @Override public void cancel() { LogUtils.d("取消试纸结果确认"); //试纸结果确认框取消 ToastUtils.show(getContext(), AiCode.getMessage(AiCode.CODE_201)); } @Override public void save(PaperResult paperResult) { LogUtils.d("保存试纸分析结果:\n"+paperResult.toString()); if (paperResult.getErrNo() != 0) { ToastUtils.show(getContext(), AiCode.getMessage(paperResult.getErrNo())); } } @Override public boolean analysisSuccess(PaperCoordinatesData paperCoordinatesData, Bitmap originSquareBitmap, Bitmap clipPaperBitmap) { LogUtils.d("试纸自动抠图成功"); //试纸抠图成功结果 endTime = System.currentTimeMillis(); return false; } @Override public void analysisError(PaperCoordinatesData paperCoordinatesData, String errorResult, int code) { LogUtils.d("试纸自动抠图出错 code:" + code + " errorResult:" + errorResult); //试纸抠图失败结果 ToastUtils.show(getContext(), AiCode.getMessage(code)); } @Override public void saasAnalysisError(String errorResult, int code) { LogUtils.d("试纸分析出错 code:" + code + " errorResult:" + errorResult); //试纸saas分析失败 ToastUtils.show(getContext(), AiCode.getMessage(code)); } @Override public void paperResultDialogShow(PaperResultDialog paperResultDialog) { }});
“试纸结果识别确认框”样式代码调用片段123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103//定制试纸Ui显示/** ``* 标题 */String titleText = getContext().getString(com.ikangtai.papersdk.R.string.paper_result_dialog_title);/** * 标题颜色 */int titleTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_444444);/** * 标尺线 */int tagLineImageResId = com.ikangtai.papersdk.R.drawable.paper_line;/** * t滑块图标 */int tLineResId = com.ikangtai.papersdk.R.drawable.test_paper_t_line;/** * c滑块图标 */int cLineResId = com.ikangtai.papersdk.R.drawable.test_paper_c_line;/** * 水平翻转文字 */String flipText = getContext().getString(com.ikangtai.papersdk.R.string.paper_result_dialog_flip);/** * 水平翻转文字颜色 */int flipTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_67A3FF);/** * 提示文字 */String hintText = getContext().getString(com.ikangtai.papersdk.R.string.paper_result_dialog_hit);/** * 提示文字颜色 */int hintTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_444444);/** * 返回图片 */int backResId = com.ikangtai.papersdk.R.drawable.test_paper_return;/** * 确认图片 */int confirmResId = com.ikangtai.papersdk.R.drawable.test_paper_confirm;/** * tc线默认值宽度 */float tcLineWidth = getContext().getResources().getDimension(com.ikangtai.papersdk.R.dimen.dp_2);/** * 返回按钮背景 */int backButtonBgResId = com.ikangtai.papersdk.R.drawable.paper_button_drawable;/** * 确认按钮背景 */int confirmButtonBgResId = com.ikangtai.papersdk.R.drawable.paper_button_drawable;/** * 返回按钮文字 */String backButtonText = getContext().getString(com.ikangtai.papersdk.R.string.paper_result_back);/** * 确认按钮文字 */String confirmButtonText = getContext().getString(com.ikangtai.papersdk.R.string.paper_result_confirm);/** * 返回按钮文字颜色 */int backButtonTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_444444);/** * 确认按钮文字颜色 */int confirmButtonTextColor = getContext().getResources().getColor(com.ikangtai.papersdk.R.color.color_444444);/** * 显示底部按钮 */boolean visibleBottomButton = false; UiOption uiOption = new UiOption.Builder() .titleText(titleText) .tagLineImageResId(tagLineImageResId) .titleTextColor(titleTextColor) .tLineResId(tLineResId) .cLineResId(cLineResId) .flipText(flipText) .flipTextColor(flipTextColor) .hintText(hintText) .hintTextColor(hintTextColor) .backResId(backResId) .confirmResId(confirmResId) .tcLineWidth(tcLineWidth) .backButtonBgResId(backButtonBgResId) .backButtonText(backButtonText) .confirmButtonBgResId(confirmButtonBgResId) .confirmButtonText(confirmButtonText) .backButtonTextColor(backButtonTextColor) .confirmButtonTextColor(confirmButtonTextColor) .visibleBottomButton(visibleBottomButton) .build();//试纸识别sdk相关配置Config config = new Config.Builder().pixelOfdExtended(true).margin(10).uiOption(uiOption).build();//初始化sdkpaperAnalysiserClient = new PaperAnalysiserClient(getContext(), AppConstant.appId, AppConstant.appSecret, "xyl1@qq.com",config);
如何debug-log tag:paper-analysis-log-log默认路径:/data/Android/pageName/files/Documents/log.txt,通过LogUtils.getLogFilePath()获取到-可以自定义log 文件写入流new Config.Builder().logWriter(),或者设置log文件路径:new Config.Builder().logFilePath()
术语说明N/A
Q&A抠图模式的网络传输量大吗?在抠图模式下,我们的SDK一次识别仅仅传输很小的试纸条(注意不是全景图,而是抠出的试纸条),仅仅这个这个小小的试纸条会被发送到云端做识别,所以整个网络传输200K都不到,这是一个非常小的网络传输;而视频流仅仅在本地做识别,没有任何的网络传输。
关于代码中unionID的说明对于调用方来说,调用方负责uniondID的生成,uniodID有以下约束:
每一个用户有一个独立的unionID,该unionID不可与其他用户的unionID重复,并且在该用户的整个生命周期内该unionID应该是始终保持不变;
孕橙要求uniodID字符串最大长度是45,以16进制表示的话最短是40个字节,一个最简单的unionID的生成方式就是SHA1(userID);
unionID的作用:
是为了保证shecare SAAS可以全生命周期的追踪这个用户的试纸数据,为用户单周期的试纸解读提供一个基本的数据备份;
一旦用户出现问题,可以更方便以及快速的去debug以定位原因;
如何查看Debug日志?在我们的体验Demo里面,可以直接看到我们的API输出结果
OpenCV兼容性问题Android OpenCV版本 :3.2.0
测试环境和正式环境区别SDK识别调用频率限制 同一IP限制200次/分钟
输入的试纸图片数据是正方形Shecare SDK识别库是在正方形图片基础上进行的机器学习,抠图识别正方形正确率也会比较高,暂时只保证正方形抠图结果的正确性
输入试纸图片文件大小限制限制输入图片大小在5M以内
输出抠图结果试纸条分辨率Android SDK会把宽度限制在1080以内,防止处理大图出现OOM
输出抠图试纸太窄可以控制SDK输出试纸条外扩像素,实现试纸条有一定的外边距
Android:
1new Config.Builder().pixelOfdExtended(true).margin(10)
输出抠图结果试纸条会出现透明缺角在进行试纸外扩的时候,需要使用试纸抠图坐标数据转换,造成了可能会丢失一些边角
试纸在复杂背景抠图结果可能不一致识别算法也在学习升级过程中,如有出错率比较大的照片,麻烦联系客服提供原始照片,Shecare这边会对算法相应学习
试纸logo类型未定义联系对接客服,提供相应的LOGO图片素材,进行试纸LOGO打标学习
替换或隐藏试纸结果确认框样例说明联系对接客服,进行相关配置
替换或隐藏power by shecare说明联系对接客服,进行相关配置
APP添加信任正常情况下,由于iOS的APP是由测试证书打包的,所以无法启动,需要改动以下设置才可以保证demo正常运行,具体设置如下图示: