感谢 http://www.jianshu.com/p/ac4c4536ca3e 很早就想做的一个demo
写在前面
用到的技术:
图像处理技术, 包括包括灰度化处理,二值化,腐蚀,轮廊检测,
文字识别技术: 通过ocr识别技术可以将图片中包含的数字信息以字符串的方式输出。
3.开源库
开源框架OpenCV和TesseractOCRiOS
OpenCV(完成图像处理技术)
OpenCV是一个开源的跨平台计算机视觉和机器学习库,通俗点的说,就是他给计算机提供了一双眼睛,一双可以从图片中获取信息的眼镜,从而完成人脸识别、身份证识别、去红眼、追踪移动物体等等的图像相关的功能。opencv官网
TesseractOCRiOS(完成文字识别技术)
Tesseract是目前可用的最准确的开源OCR引擎,可以读取各种格式的图片并将他们转换成各种语言文本。而TesseractOCRiOS则是针对iOS平台封装的Tesseract引擎库。必须保证背景纯净
灰度化处理:图片灰度化处理就是将指定图片每个像素点的RGB三个分量通过一定的算法计算出该像素点的灰度值,使图像只含亮度而不含色彩信息。
二值化:二值化处理就是将经过灰度化处理的图片转换为只包含黑色和白色两种颜色的图像,他们之间没有其他灰度的变化。在二值图中用255便是白色,0表示黑色。
腐蚀:图片的腐蚀就是将得到的二值图中的黑色块进行放大。即连接图片中相邻黑色像素点的元素。通过腐蚀可以把身份证上的身份证号码连接在一起形成一个矩形区域。
轮廊检测:图片经过腐蚀操作后相邻点会连接在一起形成一个大的区域,这个时候通过轮廊检测就可以把每个大的区域找出来,这样就可以定位到身份证上面号码的区域。
说完就开始吧
首先, 用CocoPods导入上面两个库, 这个demo做完差不多快200M
由于导入的库不支持Bitcode机制,需要关掉,在工程->TARGETS->Build Setting-> Enable Bitcode设置为NO
TesseractOCRiOS库中没有自带的语言包,需要我们自己手动导入,我们这里直接到tesseract-ocr网站,tessdata即是我们需要用到的语言包。下载下来的语言包有400多兆。这里我们只需要用到英语语言包,所以就只导入eng.traineddata就ok,其他的都删掉。
导入语言包种需要注意几点:
- 语言包需要放在tessdata目录下。TesseractOCRiOS中查找语言包是在tessdata目录下进行查找的,所以我们不能单独把eng.traineddata导入项目中,而需要放在tessdata目录下导入项目中。
- 将tessdata导入xcode项目,需要勾选Create folder refrences。上面已经提到了语言包需要放在tessdata目录下,所以导入文件到xcode的时候需要创建文件夹的形式,而不是创建组的形式。
然后, 创建一个RecogizeCardManager用来管理身份证识别相关的代码。
由于OpenCV和TesseractOCRiOS库都是基于c++编写的,所以需要把RecogizeCardManager.m后缀的.m改成.mm
VC里面子控件布局以及取怎么使用拍照和去相册取照片以前写过, 不再写, 只写manager里面的东西
创建一个NAReconizeIdCardManager继承于NSObject
引入
#import <opencv2/opencv.hpp>
//#import <opencv2/imgproc/types_c.h>
#import <opencv2/imgcodecs/ios.h>
#import <TesseractOCR/TesseractOCR.h>
写个单例声明出去
+ (instancetype)sharedManager{
static NAReconizeIdCardManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[NAReconizeIdCardManager alloc] init];
});
return manager;
}
外部传入UIImage然后进行处理, 回调一个NSString作为身份证码
- (void)reconizeWithImage:(UIImage *)image complete:(void (^)(NSString *))complete{
//扫描身份证图片, 并进行预处理, 定位号码区域图片并返回
UIImage *numberImage = [self opencvScanCard:image];
if (numberImage == nil) {
complete(nil);
}
//利用TesseractOCR识别文字
[self tesseractReconizeImage:numberImage completion:^(NSString *numberText) {
complete(numberText);
}];
}
#pragma mark - 预处理图片, 定位号码区域并返回image
- (UIImage *)opencvScanCard:(UIImage *)image{
//将UIImage转换成Mat
cv::Mat resultImage; //#import <opencv2/opencv.hpp>
UIImageToMat(image, resultImage);//#import <opencv2/imgcodecs/ios.h>
//转为灰度图
cvtColor(resultImage, resultImage, cv::COLOR_BGR2GRAY);
//利用阈yu值二值化
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);
//算法原理
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;
}
#pragma mark - 利用TesseractOCR识别文字
//#import <TesseractOCR/TesseractOCR.h>
- (void)tesseractReconizeImage:(UIImage *)image completion:(void (^)(NSString *))completion{
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;
//开始识别
[tesseract recognize];
//回调结果
completion(tesseract.recognizedText);
});
}