引言
- 视频地址:https://mp.weixin.qq.com/s?__biz=MzI0MjU5MzU5Ng==&mid=2247487540&idx=1&sn=1f711bcdd36c6aa692cf4b4b66c3b236&chksm=e978aa67de0f237175167f3d7a6aaed710f3b646272d877438af8de85f01b2d3e98e504f52cc&mpshare=1&scene=1&srcid=0714MyTFaWS6DjCTyVXgOXfg&sharer_sharetime=1626241300941&sharer_shareid=38c24777c9b84b8b44c56026b3aa9bd7&version=3.1.8.90238&platform=mac#rd
- Demo源码:https://download.csdn.net/download/u011018979/19265912
1、功能:可自动快速读出中国二代身份证上的信息(姓名、性别、民族、住址、身份证号码)并截取到身份证图像
2、应用场景:信用卡网申、商户进件、实名认证流程为了用户体验提供扫一扫证件识别身份证号码功能。
3、原理:3.1、自定义相机并利用第三方库SDK
libexidcardios
进行识别3.2、添加自定义的扫描界面(中间有一个镂空窗口和来回移动的扫描线)
3.3、人脸小框检测:人脸区域是否在这个人脸小框内,若在,说明用户的确将身份证头像放在了这个框里,那么此时这一帧身份证图像大小正好合适且完整,接下来才捕获该帧,就获得了完整的身份证截图。
4、原理文章:https://kunnan.blog.csdn.net/article/details/117414741
5、如果无法下载Demo,请关注mp:【iOS逆向】,进行获取
[video(video-4u9nEmy4-1626172763106)(type-csdn)(url-https://live.csdn.net/v/embed/169263)(image-https://vedu.csdnimg.cn/2f283d34dd104883ba47cea551403796/snapshots/d181f061db784808b7a6db4bd029cb72-00001.jpg)(title-iOS OCR:自定义相机进行身份证信息识别(正反面))]
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、原理文章:https://kunnan.blog.csdn.net/article/details/117414741
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、经典案例:识别身份证号码
https://mp.weixin.qq.com/s/C-k_oZCYoxEUO0ujQZmQ2Q
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,请关注mp:【iOS逆向】,进行获取