引言
从CSDN下载Demo源码:https://download.csdn.net/download/u011018979/19265912
1、功能:可自动快速读出中国二代身份证上的信息(姓名、性别、民族、住址、身份证号码)并截取到身份证图像
2、应用场景:信用卡网申、商户进件、实名认证流程为了用户体验提供扫一扫证件识别身份证号码功能,常用于物流类型app。
3、原理:
3.1、自定义相机并利用第三方库SDK libexidcardios
进行识别
3.2、添加自定义的扫描界面(中间有一个镂空窗口和来回移动的扫描线)
3.3、人脸小框检测:人脸区域是否在这个人脸小框内,若在,说明用户的确将身份证头像放在了这个框里,那么此时这一帧身份证图像大小正好合适且完整,接下来才捕获该帧,就获得了完整的身份证截图。
4、如果无法下载Demo,请关注公众号:【iOS逆向】,进行获取
I 、 OCR 之身份证识别 (正反)
1.1 原理
1、自定义相机并利用第三方库SDK libexidcardios
进行识别
2、添加自定义的扫描界面(中间有一个镂空窗口和来回移动的扫描线)
3、人脸小框检测:人脸区域是否在这个人脸小框内,若在,说明用户的确将身份证头像放在了这个框里,那么此时这一帧身份证图像大小正好合适且完整,接下来才捕获该帧,就获得了完整的身份证截图。
只要身份证号码处于摄像头预览图层中时,即不用完全对准身份证也可以读取到身份证号码,但此时截取到的身份证图像并不完整。
1.2 Usage
涉及的素材
idcard_first.png、idcard_first_head.png、idcard_back.png、nav_back.png、nav_torch_on.png、nav_torch_off.png
目录结构
Category、Tool、libexidcard、Model、View、Controller这六个文件夹
Info.plist文件中添加权限描述(Key Value)
Privacy - Camera Usage Description
是否允许访问相机Privacy - Photo Library Usage Description
是否允许访问相册
II、demo 源码
从CSDN下载Demo源码:https://download.csdn.net/download/u011018979/19265912
1、功能:可自动快速读出中国二代身份证上的信息(姓名、性别、民族、住址、身份证号码)并截取到身份证图像
2、应用场景:身份证号码采集:信用卡网申、商户进件、实名认证
3、原理:
3.1、自定义相机并利用第三方库SDK
libexidcardios
进行识别3.2、添加自定义的扫描界面(中间有一个镂空窗口和来回移动的扫描线)
3.3、人脸小框检测:人脸区域是否在这个人脸小框内,若在,说明用户的确将身份证头像放在了这个框里,那么此时这一帧身份证图像大小正好合适且完整,接下来才捕获该帧,就获得了完整的身份证截图。
4、如果无法下载Demo,请关注公众号:【iOS逆向】,进行获取
2.1 获取实时图像进行信息识别
#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate #pragma mark 从输出的数据流捕捉单一的图像帧 // AVCaptureVideoDataOutput获取实时图像,这个代理方法的回调频率很快,几乎与手机屏幕的刷新频率一样快 -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { if ([self.outPutSetting isEqualToNumber:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]] || [self.outPutSetting isEqualToNumber:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]]) { CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); if ([captureOutput isEqual:self.videoDataOutput]) { // 身份证信息识别 [self IDCardRecognit:imageBuffer]; // 身份证信息识别完毕后,就将videoDataOutput的代理去掉,防止频繁调用AVCaptureVideoDataOutputSampleBufferDelegate方法而引起的“混乱” if (self.videoDataOutput.sampleBufferDelegate) { [self.videoDataOutput setSampleBufferDelegate:nil queue:self.queue]; } } } else { NSLog(@"输出格式不支持"); } } #pragma mark - 身份证信息识别 - (void)IDCardRecognit:(CVImageBufferRef)imageBuffer { CVBufferRetain(imageBuffer); // Lock the image buffer if (CVPixelBufferLockBaseAddress(imageBuffer, 0) == kCVReturnSuccess) { size_t width= CVPixelBufferGetWidth(imageBuffer);// 1920 size_t height = CVPixelBufferGetHeight(imageBuffer);// 1080 CVPlanarPixelBufferInfo_YCbCrBiPlanar *planar = CVPixelBufferGetBaseAddress(imageBuffer); size_t offset = NSSwapBigIntToHost(planar->componentInfoY.offset); size_t rowBytes = NSSwapBigIntToHost(planar->componentInfoY.rowBytes); unsigned char* baseAddress = (unsigned char *)CVPixelBufferGetBaseAddress(imageBuffer); unsigned char* pixelAddress = baseAddress + offset; static unsigned char *buffer = NULL; if (buffer == NULL) { buffer = (unsigned char *)malloc(sizeof(unsigned char) * width * height); } memcpy(buffer, pixelAddress, sizeof(unsigned char) * width * height); unsigned char pResult[1024]; int ret = EXCARDS_RecoIDCardData(buffer, (int)width, (int)height, (int)rowBytes, (int)8, (char*)pResult, sizeof(pResult)); if (ret <= 0) { NSLog(@"ret=[%d]", ret); } else { NSLog(@"ret=[%d]", ret); // 播放一下“拍照”的声音,模拟拍照 AudioServicesPlaySystemSound(1108); if ([self.session isRunning]) { [self.session stopRunning]; } char ctype; char content[256]; int xlen; int i = 0; IDInfo *iDInfo = [[IDInfo alloc] init]; ctype = pResult[i++]; // iDInfo.type = ctype; while(i < ret){ ctype = pResult[i++]; for(xlen = 0; i < ret; ++i){ if(pResult[i] == ' ') { ++i; break; } content[xlen++] = pResult[i]; } content[xlen] = 0; if(xlen) { NSStringEncoding gbkEncoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000); if(ctype == 0x21) { iDInfo.num = [NSString stringWithCString:(char *)content encoding:gbkEncoding]; } else if(ctype == 0x22) { iDInfo.name = [NSString stringWithCString:(char *)content encoding:gbkEncoding]; } else if(ctype == 0x23) { iDInfo.gender = [NSString stringWithCString:(char *)content encoding:gbkEncoding]; } else if(ctype == 0x24) { iDInfo.nation = [NSString stringWithCString:(char *)content encoding:gbkEncoding]; } else if(ctype == 0x25) { iDInfo.address = [NSString stringWithCString:(char *)content encoding:gbkEncoding]; } else if(ctype == 0x26) { iDInfo.issue = [NSString stringWithCString:(char *)content encoding:gbkEncoding]; } else if(ctype == 0x27) { iDInfo.valid = [NSString stringWithCString:(char *)content encoding:gbkEncoding]; } } } if (iDInfo) {// 读取到身份证信息,实例化出IDInfo对象后,截取身份证的有效区域,获取到图像 NSLog(@"\n正面\n姓名:%@\n性别:%@\n民族:%@\n住址:%@\n公民身份证号码:%@\n\n反面\n签发机关:%@\n有效期限:%@",iDInfo.name,iDInfo.gender,iDInfo.nation,iDInfo.address,iDInfo.num,iDInfo.issue,iDInfo.valid); CGRect effectRect = [RectManager getEffectImageRect:CGSizeMake(width, height)]; CGRect rect = [RectManager getGuideFrame:effectRect]; UIImage *image = [UIImage getImageStream:imageBuffer]; UIImage *subImage = [UIImage getSubImage:rect inImage:image]; // 推出IDInfoVC(展示身份证信息的控制器) IDInfoViewController *IDInfoVC = [[IDInfoViewController alloc] init]; IDInfoVC.IDInfo = iDInfo;// 身份证信息 IDInfoVC.IDImage = subImage;// 身份证图像 dispatch_async(dispatch_get_main_queue(), ^{ [self.navigationController pushViewController:IDInfoVC animated:YES]; }); } } CVPixelBufferUnlockBaseAddress(imageBuffer, 0); } CVBufferRelease(imageBuffer); }
2.2 添加自定义的扫描界面
- (void)drawRect:(CGRect)rect { rect = _IDCardScanningWindowLayer.frame; // 人像提示框 UIBezierPath *facePath = [UIBezierPath bezierPathWithRect:_facePathRect]; facePath.lineWidth = 1.5; [[UIColor whiteColor] set]; [facePath stroke]; // 水平扫描线 CGContextRef context = UIGraphicsGetCurrentContext(); static CGFloat moveX = 0; static CGFloat distanceX = 0; CGContextBeginPath(context); CGContextSetLineWidth(context, 2); CGContextSetRGBStrokeColor(context,0.3,0.8,0.3,0.8); CGPoint p1, p2;// p1, p2 连成水平扫描线; moveX += distanceX; if (moveX >= CGRectGetWidth(rect) - 2) { distanceX = -2; } else if (moveX <= 2){ distanceX = 2; } p1 = CGPointMake(CGRectGetMaxX(rect) - moveX, rect.origin.y); p2 = CGPointMake(CGRectGetMaxX(rect) - moveX, rect.origin.y + rect.size.height); CGContextMoveToPoint(context,p1.x, p1.y); CGContextAddLineToPoint(context, p2.x, p2.y); /* // 竖直扫描线 static CGFloat moveY = 0; static CGFloat distanceY = 0; CGPoint p3, p4;// p3, p4连成竖直扫描线 moveY += distanceY; if (moveY >= CGRectGetHeight(rect) - 2) { distanceY = -2; } else if (moveY <= 2) { distanceY = 2; } p3 = CGPointMake(rect.origin.x, rect.origin.y + moveY); p4 = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + moveY); CGContextMoveToPoint(context,p3.x, p3.y); CGContextAddLineToPoint(context, p4.x, p4.y); */ CGContextStrokePath(context); }
2.3 设置人脸扫描区域
- 为什么做人脸扫描?
由于预览图层是全屏的,当用户有时没有将身份证对准拍摄框边缘时,也会成功读取身份证上的信息,即也会捕获到不完整的身份证图像。
因此,为了截取到比较完整的身份证图像,在自定义扫描界面的合适位置上加了一>个身份证头像框,让用户将该小框对准身份证上的头像,最终目的是使程序截取到完整的>身份证图像。
当该小框检测到人脸时,再对比人脸区域是否在这个小框内,若在,说明用户的确>将身份证头像放在了这个框里,那么此时这一帧身份证图像大小正好合适且完整,接下来>才捕获该帧,就获得了完整的身份证截图。
有了文字、拍摄区域的提示,99%的用户会主动将身份证和拍摄框边缘对齐,就能够获得完整的身份证图像,不做人脸区域的检测也是可以的
从输出的元数据中捕捉人脸
检测人脸是为了获得“人脸区域”,做“人脸区域”与“身份证人像框”的区域对比,当前者在后者范围内的时候,才能截取到完整的身份证图像
#pragma mark - AVCaptureMetadataOutputObjectsDelegate -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{ if (metadataObjects.count) { AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects.firstObject; AVMetadataObject *transformedMetadataObject = [self.previewLayer transformedMetadataObjectForMetadataObject:metadataObject]; CGRect faceRegion = transformedMetadataObject.bounds; if (metadataObject.type == AVMetadataObjectTypeFace) { NSLog(@"是否包含头像:%d, facePathRect: %@, faceRegion: %@",CGRectContainsRect(self.faceDetectionFrame, faceRegion),NSStringFromCGRect(self.faceDetectionFrame),NSStringFromCGRect(faceRegion)); if (CGRectContainsRect(self.faceDetectionFrame, faceRegion)) {// 只有当人脸区域的确在小框内时,才再去做捕获此时的这一帧图像 // 为videoDataOutput设置代理,程序就会自动调用下面的代理方法,捕获每一帧图像 if (!self.videoDataOutput.sampleBufferDelegate) { [self.videoDataOutput setSampleBufferDelegate:self queue:self.queue]; } } } } }
2.4 去掉人脸扫描区域检测
如果你不想加入人脸识别这一功能,你的app无需这么精细的话
1 、注释掉所有metadataOutput的代码及AVCaptureMetadataOutputObjectsDelegate代理方法
(-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection)
2、在懒加载videoDataOutput对象时,设置setSampleBufferDelegate
AVCaptureVideoDataOutput *)videoDataOutput { if (_videoDataOutput == nil) { _videoDataOutput = [[AVCaptureVideoDataOutput alloc] init]; _videoDataOutput.alwaysDiscardsLateVideoFrames = YES; _videoDataOutput.videoSettings = @{(id)kCVPixelBufferPixelFormatTypeKey:self.outPutSetting}; [_videoDataOutput setSampleBufferDelegate:self queue:self.queue]; } return _videoDataOutput; } #pragma mark metadataOutput -(AVCaptureMetadataOutput *)metadataOutput { if (_metadataOutput == nil) { _metadataOutput = [[AVCaptureMetadataOutput alloc]init]; // [_metadataOutput setMetadataObjectsDelegate:self queue:self.queue]; } return _metadataOutput; }
III、经典案例:识别身份证号码
IV、遇到的问题
4.1 -[UIViewController init]
must be used from main thread only
解决方案:
dispatch_async(dispatch_get_main_queue(), ^{ // 推出IDInfoVC(展示身份证信息的控制器) IDInfoViewController *IDInfoVC = [[IDInfoViewController alloc] init]; IDInfoVC.IDInfo = iDInfo;// 身份证信息 IDInfoVC.IDImage = subImage;// 身份证图像 [self.navigationController pushViewController:IDInfoVC animated:YES]; });
see also:光学文字识别-储蓄卡
libexbankcardcore exbankcard.h BankCard.h
从CSDN下载Demo源码:https://download.csdn.net/download/u011018979/19268420
1、功能:扫描银行卡识别信息( 银行名称、 银行卡号)并截取银行卡图像
2、应用场景:快速填充银行卡号的场景,比如商户进件、实名认证
3、原理:
3.1、自定义相机并利用第三方库SDK
libexbankcardios.a
、libbexbankcard.a
进行识别(识别次数无限,免费
)3.2、添加自定义的扫描界面(中间有一个镂空窗口和来回移动的扫描线)
4、原理文章:https://kunnan.blog.csdn.net/article/details/117421214
5、如果无法下载Demo,请关注公众号:【iOS逆向】,进行获取