图像识别技术(OCR)在iOS 中的简单应用

最近,公司的项目中用到了图像识别技术,通过拍照来识别身份证号,之前没有做过,经过一番的研究,总算是搞定了,下面就将整个的实现过程分享一下:
图像识别主要用到了两个第三方的框架:OpenCV和TesseractOCR,OpenCV用来做图像处理,定位到身份证号码的区域,TesseractOCR则是对定位到的区域内的内容进行识别。

框架的导入

1、OpenCv

OpenCV的导入比较简单,首先从OpenCV官网上下载iOS对应的framework,然后将其直接拖入到工程中即可。

OpenCV.png

OpenCV是一套C++的框架,所以在使用到OpenCV的类中要将.m文件的后缀改为.mm文件。如果在.pch文件中导入头文件的话,需要将头文件放到下面的代码中,表示只有C++文件才会编译:

#ifdef __cplusplus

#endif

然后倒入一下三个头文件

#import <opencv2/opencv.hpp>
#import <opencv2/imgproc/types_c.h>
#import <opencv2/imgcodecs/ios.h>

2、TesseractOCR

TesseractOCR是谷歌开源的一个OCR引擎,从github上下载Tesseract的工程和语言包(tessdata),默认TesseractOCR使用的是英文包(名称:eng.traineddata),如果要识别中文,还需要将中文包(名称:chi_sim.traineddata)导入到tessdata中(一个简单的方法是将中文包导入到tessdata中,并将名称改为eng.traineddata,这样其他的地方就不用做改变了)。

TesseractOCR.png

注意:在将tessdata文件夹导入到工程中时,要选择Create folder refrences,如图:

tessdata.png

获取图片

要识别图像,首先得能获取图像,可以通过拍照,也可以从相册中获取,下面是我为身份证识别做的一下准备工作:
在控制器中添加一个label(显示结果)、一个imageView(显示图像)、两个button(点击分别拍照和进入相册),添加UIImagePickerController的全局变量,并初始化。

@interface ViewController ()<UINavigationControllerDelegate, UIImagePickerControllerDelegate>
{
    UIImagePickerController *_imagePickerVC;
}
@property (weak, nonatomic) IBOutlet UILabel *resultLabel;
@property (weak, nonatomic) IBOutlet UIImageView *cardImageView;

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    _imagePickerVC = [[UIImagePickerController alloc] init];
    _imagePickerVC.delegate = self;
    _imagePickerVC.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
    _imagePickerVC.allowsEditing = YES;
}
- (IBAction)cameraAction:(id)sender {
    //判断是否可以打开照相机
    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
        _imagePickerVC.sourceType = UIImagePickerControllerSourceTypeCamera;
        //设置摄像头模式(拍照,录制视频)为拍照
        _imagePickerVC.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
        [self presentViewController:_imagePickerVC animated:YES completion:^{
        }];
    }

}
- (IBAction)photoLibaryAction:(id)sender {
    _imagePickerVC.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    [self presentViewController:_imagePickerVC animated:YES completion:nil];
}
#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
    UIImage *srcImage = nil;
    NSString *mediaType=[info objectForKey:UIImagePickerControllerMediaType];
    //判断资源类型
    if ([mediaType isEqualToString:@"public.image"]){
        srcImage = info[UIImagePickerControllerEditedImage];
        self.cardImageView.image = srcImage;//得到图片并显示

    }
    [_imagePickerVC dismissViewControllerAnimated:YES completion:^{
    }];
}

图像处理和识别

接下来,就说一下图像处理的过程,得到最适合的识别区域:
首先,在工程中创建一个图像识别处理的工具类:RecognizeUtil,在.h文件中声明如下方法,并在.m中实现。
RecognizeUtil工具类中的结构基本如下:

#import <Foundation/Foundation.h>
@class UIImage;
@interface RecognizeUtil : NSObject
/*
 单例
 */
+ (instancetype)recognizeUtil;
 /*
  身份证号码识别
 */
- (void)recognizeCardNumWithImage:(UIImage *)image complete:(void(^)(NSString *resultNum,UIImage *resultImage))complete;
@end
#import "RecognizeUtil.h"
#import <opencv2/opencv.hpp>
#import <opencv2/imgproc/types_c.h>
#import <opencv2/imgcodecs/ios.h>
#import "TesseractOCRiOS/TesseractOCR/TesseractOCR.h"
@implementation RecognizeUtil
static RecognizeUtil *recognizeUtil = nil;
+ (instancetype)recognizeUtil {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        recognizeUtil = [[RecognizeUtil alloc] init];
    });
    return recognizeCardManager;
}
- (void)recognizeCardNumWithImage:(UIImage *)image complete:(void(^)(NSString *resultNum))complete{
    UIImage *handledImage = [self opencvHandleImage:image];
    [self tesseractRecognizeImage:handledImage complete:^(NSString *resultNum,UIImage *image) {
        complete(resultNum, image);
    }];
}
/*
 OpenCV 图像处理 返回处理过后的图片
 */
- (UIImage *)opencvHandleImage:(UIImage *)image{
    return nil;
}
/*
 tesseract 图像识别 block回调识别结果
*/
- (void)recognizeCardNumWithImage:(UIImage *)image complete:(void(^)(NSString *resultNum,UIImage *resultImage))complete;
    complete(resultNum,resultImage);
}
@end

下面到了最关键的环节,就是图像处理和识别的具体方法:

1、处理

opencvHandleImage:方法实现

/*
 OpenCV 图像处理 返回处理过后的图片
 */
- (UIImage *)opencvHandleImage:(UIImage *)image{
    //将UIImage转换成Mat
    cv::Mat resultImage;
    UIImageToMat(image, resultImage);
    //转为灰度图
    cvtColor(resultImage, resultImage, cv::COLOR_BGR2GRAY);
    //阈值二值化
    cv::threshold(resultImage, resultImage, 100, 255, CV_THRESH_BINARY);
    //腐蚀,填充(腐蚀是让黑色点变大)
    cv::Mat erodeElement = getStructuringElement(cv::MORPH_RECT, cv::Size(26,26));
    cv::erode(resultImage, resultImage, erodeElement);
    //定义一个容器来存储所有检测到的轮廓
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(resultImage, contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0));
    //取出身份证号码区域
    std::vector<cv::Rect> rects;
    cv::Rect numberRect = cv::Rect(0,0,0,0);
    std::vector<std::vector<cv::Point>>::const_iterator itContours = contours.begin();
    for ( ; itContours != contours.end(); ++itContours) {
        cv::Rect rect = cv::boundingRect(*itContours);
        rects.push_back(rect);//push_back 在容器尾部插入一个数据
        //算法原理(宽度最大,并且宽度大于高度的5倍)
        if (rect.width > numberRect.width && rect.width > rect.height * 5) {
            numberRect = rect;
        }
    }
    //定位失败
    if (numberRect.width == 0 || numberRect.height == 0) {
        return nil;
    }
    //得到身份证号的区域,去原图中后去该区域,并转成二值图
    cv::Mat matImage;
    UIImageToMat(image, matImage);
    resultImage = matImage(numberRect);
    cvtColor(resultImage, resultImage, cv::COLOR_BGR2GRAY);
    cv::threshold(resultImage, resultImage, 80, 255, CV_THRESH_BINARY);
    //将Mat转换成UIImage
    UIImage *numberImage = MatToUIImage(resultImage);
    return numberImage;
}

上面主要利用到了OpenCV中的一些简单的处理图像的方法:灰度处理、二值化、腐蚀、边缘检测等等。

灰度处理和二值化的作用是将图像变为只有黑白两种颜色的二值图;

腐蚀的作用是将黑色的区域 扩大,这样,相邻的黑色区域就会连在一起,便于将号码的区域隔离出来;

边缘检测的作用就是获取各个黑色区域的值

最后通过比较各个黑色区域的特点,就可以将号码的区域分离出来,得到号码区域的值,再从原图中截取号码区域即可。

在处理之前,先要将UIImage类型转换为OpenCV中的图像类型:Mat

UIImageToMat()

2、识别

识别就比较简单了,代码基本上都是从Demo中拷贝的,选择好语言,将图片作为参数传入,就可以返回识别的内容。

/*
 tesseract 图像识别 block回调识别结果
*/
- (void)tesseractRecognizeImage:(UIImage *)image complete:(void(^)(NSString *resultNum,UIImage *image))complete{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        G8Tesseract *tesseract = [[G8Tesseract alloc] initWithLanguage:@"eng"];
        tesseract.image = [image g8_blackAndWhite];
        tesseract.image = image;
        // Start the recognition
        [tesseract recognize];
        dispatch_async(dispatch_get_main_queue(), ^{
            //执行回调
            complete(tesseract.recognizedText,image);
            
        });
    });
}

总结:

关于图像识别,最关键的应该就是处理的环节了,在以上的代码中,所做的处理比较简单,所以对照片的要求比较高,拍照时,身份证的背景为纯白色,识别的几率为更高。
关于OpenCV,其中还有好多对图像处理的方法,使用OpenCV对图像进行最合适的预处理,可以很大程度的提高识别概率。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 转自:http://www.jianshu.com/p/ac4c4536ca3e# 一、前言  身份证识别,又称O...
    ZhangCc_阅读 1,480评论 1 11
  • 这些年计算机视觉识别和搜索这个领域非常热闹,后期出现了很多的创业公司,大公司也在这方面也花了很多力气在做。做视觉搜...
    方弟阅读 6,317评论 6 24
  • 最近不少简友说git上下载下来的代码报各种问题,因为包含的库都比较大,所以大家在pod的时候耐心等待,另外我已经将...
    peaktan阅读 38,047评论 159 325
  • 身份证识别,又称OCR技术。OCR技术是光学字符识别的缩写,是通过扫描等光学输入方式将各种票据、报刊、书籍、文稿及...
    SunshineAutumn阅读 6,278评论 12 16
  • 它要是高兴,能比谁都温柔可亲:同身子蹭你的腿,把脖儿伸出来要求给抓痒。或是在你写作的时候,跳上桌来,在稿纸上踩印几...
    小梅弄堂阅读 290评论 0 1