前言:
前天领导问,类似扫描文件识别图中文字的功能如何实现,找一下第三方的开源库,尝试下,于是有了这篇文章;
分析:
识别场景中,识别身份证信息当属典型,查阅了几篇文章,后续的实现中也多导入了其代码;
OpenCV:
OpenCV(开源计算机视觉库)是在BSD许可下发布的,因此对学术和商业使用都是免费的。它有c++、Python和Java接口,支持Windows、Linux、Mac OS、iOS和Android。OpenCV是为计算效率而设计的,并且非常注重实时应用。用优化的C/ c++编写的库可以利用多核处理。使用OpenCL,它可以利用底层异构计算平台的硬件加速。
OpenCV在全球范围内被采用,拥有超过4.7万的用户社区,估计下载量超过1400万。它用途广泛。
OCR:
OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程;即,针对印刷体字符,采用光学的方式将纸质文档中的文字转换成为黑白点阵的图像文件,并通过识别软件将图像中的文字转换成文本格式,供文字处理软件进一步编辑加工的技术。如何除错或利用辅助信息提高识别正确率,是OCR最重要的课题,ICR(Intelligent Character Recognition)的名词也因此而产生。衡量一个OCR系统性能好坏的主要指标有:拒识率、误识率、识别速度、用户界面的友好性,产品的稳定性,易用性及可行性等。
TesseractOCR:
TesseractOCR是谷歌开源的一个OCR引擎,从github上下载Tesseract的工程和语言包(tessdata),TesseractOCR并没有自带语言包,如果要识别中文,还需要将中文包(名称:chi_sim.traineddata)导入到tessdata中。
下载:
项目实践:
新建项目IdentiferIDCard,编辑Podfile;
项目安装使用:
使用Cocoapods;安装等待的时间可能会很长;
Xcode编译前修改:
关于语言包:
这个语言包比较大,如果只需要中文的语言包可以只下载中文的;
遇到的各种问题及解决办法:(参考链接如下)
编译运行的两个错误是因为NO宏引起的 由于系统不支持 改为CNO即可;
【身份证识别】
为了图片更加可辨识,使用了一个第三方的剪裁图片库:
核心代码(摘录):
IdentifierIDCardManager.h
#import <Foundation/Foundation.h>
@class UIImage;
typedef void (^CompleateBlock)(NSString *text);
@interface IdentifierIDCardManager : NSObject
/**
* 初始化一个单例
*
* @return 返回一个RecogizeCardManager的实例对象
*/
+ (instancetype)recognizeCardManager;
/**
* 根据身份证照片得到身份证号码
*
* @param cardImage 传入的身份证照片
* @param compleate 识别完成后的回调
*/
- (void)recognizeCardWithImage:(UIImage *)cardImage compleate:(CompleateBlock)compleate;
@end
IdentifierIDCardManager.mm
#import "IdentifierIDCardManager.h"
#import <opencv2/opencv.hpp>
#import <opencv2/imgproc/types_c.h>
#import <opencv2/imgcodecs/ios.h>
#import <TesseractOCR/TesseractOCR.h>
@implementation IdentifierIDCardManager
+ (instancetype)recognizeCardManager {
static IdentifierIDCardManager *recognizeCardManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
recognizeCardManager = [[IdentifierIDCardManager alloc] init];
});
return recognizeCardManager;
}
- (void)recognizeCardWithImage:(UIImage *)cardImage compleate:(CompleateBlock)compleate {
//扫描身份证图片,并进行预处理,定位号码区域图片并返回
UIImage *numberImage = [self opencvScanCard:cardImage];
if (numberImage == nil) {
compleate(nil);
}
//利用TesseractOCR识别文字
[self tesseractRecognizeImage:numberImage compleate:^(NSString *numbaerText) {
compleate(numbaerText);
}];
}
//扫描身份证图片,并进行预处理,定位号码区域图片并返回
- (UIImage *)opencvScanCard:(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);
//算法原理
if (rect.width > numberRect.width && rect.width > rect.height * 5) {
numberRect = rect;
}
}
//身份证号码定位失败
if (numberRect.width == 0 || numberRect.height == 0) {
return nil;
}
*/
//不只取身份证 而是取整个照片区域
cv::Rect numberRect = cv::Rect(0,0,image.size.width,image.size.height);
//定位成功成功,去原图截取身份证号码区域,并转换成灰度图、进行二值化处理
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;
}
//利用TesseractOCR识别文字
- (void)tesseractRecognizeImage:(UIImage *)image compleate:(CompleateBlock)compleate {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
G8Tesseract *tesseract = [[G8Tesseract alloc] initWithLanguage:@"chi_sim"];
tesseract.image = [image g8_blackAndWhite];
tesseract.image = image;
// Start the recognition
[tesseract recognize];
//执行回调
compleate(tesseract.recognizedText);
});
}
@end
编辑好demo之后,我们编译运行:
我们在Demo中附上了一张辨识度很高的图片作为测试;
实际的测试中,我发现用原生的库识别黑体准确度较高,但是识别其他字体要差很多;
可以通过训练获得自己的字体库来提高识别的准确度,尤其类似对身份证这种具体识别需求的字体库训练;
Demo截图:
关于Demo:
由于导入的OpenCV很大,导致Demo实在是有点大,没能传到Github上,码云也不OK,有需要的留言吧。
补充:
有关图像进一步识别可以参考下面这篇文章,通过对图片的有效处理,最终的识别度很高;