iOS OCR 之身份证识别 (正反面)

本文涉及的产品
车辆物流识别,车辆物流识别 200次/月
企业资质识别,企业资质识别 200次/月
票证核验,票证核验 50次/账号
简介: iOS OCR 之身份证识别 (正反面)

引言

从CSDN下载Demo源码:https://download.csdn.net/download/u011018979/19265912

image.png

1、功能:可自动快速读出中国二代身份证上的信息(姓名、性别、民族、住址、身份证号码)并截取到身份证图像

2、应用场景:信用卡网申、商户进件、实名认证流程为了用户体验提供扫一扫证件识别身份证号码功能,常用于物流类型app。

3、原理:

3.1、自定义相机并利用第三方库SDK libexidcardios 进行识别

3.2、添加自定义的扫描界面(中间有一个镂空窗口和来回移动的扫描线)

3.3、人脸小框检测:人脸区域是否在这个人脸小框内,若在,说明用户的确将身份证头像放在了这个框里,那么此时这一帧身份证图像大小正好合适且完整,接下来才捕获该帧,就获得了完整的身份证截图。

4、如果无法下载Demo,请关注公众号:【iOS逆向】,进行获取

image.png

image.png

image.png

image.png

image.png

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、经典案例:识别身份证号码

image.png

image.png

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.alibbexbankcard.a进行识别(识别次数无限,免费)

3.2、添加自定义的扫描界面(中间有一个镂空窗口和来回移动的扫描线)

4、原理文章:https://kunnan.blog.csdn.net/article/details/117421214

5、如果无法下载Demo,请关注公众号:【iOS逆向】,进行获取

image.png


目录
相关文章
|
5月前
|
机器学习/深度学习 人工智能 文字识别
文本,文字扫描01,OCR文本识别技术展示,一个安卓App,一个简单的设计,文字识别可以应用于人工智能,机器学习,车牌识别,身份证识别,银行卡识别,PaddleOCR+SpringBoot+Andr
文本,文字扫描01,OCR文本识别技术展示,一个安卓App,一个简单的设计,文字识别可以应用于人工智能,机器学习,车牌识别,身份证识别,银行卡识别,PaddleOCR+SpringBoot+Andr
|
7月前
|
编解码 文字识别 自然语言处理
印刷文字识别产品使用合集之身份证识别接口有哪些
印刷文字识别(Optical Character Recognition, OCR)技术能够将图片、扫描文档或 PDF 中的印刷文字转化为可编辑和可搜索的数据。这项技术广泛应用于多个领域,以提高工作效率、促进信息数字化。以下是一些印刷文字识别产品使用的典型场景合集。
|
移动开发 人工智能 文字识别
uniapp 前端实现文字识别,身份证识别,营业执照识别 (兼容APP、H5、小程序 不需要任何SDK)
本文将介绍如何使用uniapp和百度AI开放平台的OCR(光学字符识别)API实现身份证、营业执照等卡证的识别和文字识别功能。以上就是uniapp使用百度AI平台OCR API实现卡证识别和文字识别的整体实现过程全部内容了,有不懂的,或者我代码有误的地方,希望大家多多交流。具体详细代码示例可以私信问我要哈!
704 0
|
iOS开发
iOS formData形式上传身份证正反面图片到服务器
iOS formData形式上传身份证正反面图片到服务器
230 0
|
机器学习/深度学习 文字识别 算法
基于opencv-python的身份证识别(KNN与OCR两种算法)
本文是学习opencv之初的一个阶段性小任务,主要做练习使用,并没有过多的追求准确率和高可用性,比如对输入身份证照片有要求,必须是完全的身份证照片,不能有背景,如需改进,可以通过增加轮廓检测和透视变换来裁剪出身份证照片;还有对身份证号区域的检测,采用的是先裁剪出一个固定大小的模板,进行模板匹配,当时学的比较浅,所以直接草率的这样做了,其实也可以通过轮廓检测排序,从而查找到身份证号区域。 另外,利用KNN算法做识别,也是我当时刚接触KNN算法,一时兴起做的,这算是我初次做算法模型训练,然后进行检测,也是为后来学习的各种深度学习算法打基础。效果并不是很好,因为我训练用到的数据集就是
基于opencv-python的身份证识别(KNN与OCR两种算法)
|
移动开发 文字识别 开发工具
iOS小技能: OCR 之银行卡/身份证信息识别(免费次数无限)
1. 功能:扫描银行卡识别信息( 银行名称、 银行卡号)并截取银行卡图像 2. 应用场景:快速填充银行卡号的场景,比如商户进件、实名认证
422 0
iOS小技能: OCR 之银行卡/身份证信息识别(免费次数无限)
|
文字识别 开发工具 iOS开发
iOS小技能: OCR 之身份证识别 (正反面) 【 应用场景:物流类型app进行实名认证】
1、功能:可自动快速读出中国二代身份证上的信息(姓名、性别、民族、住址、身份证号码)并截取到身份证图像 2、应用场景:信用卡网申、商户进件、实名认证流程为了用户体验提供扫一扫证件识别身份证号码功能。
731 0
iOS小技能: OCR 之身份证识别 (正反面) 【 应用场景:物流类型app进行实名认证】
|
14天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
5天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
7天前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。