客户端答题卡识别算法,iOS身份证号码识别

作者:计算机网络

原来的书文品公布在:

转自:http://www.jianshu.com/p/ac4c4536ca3e#

OpenCV for iOS

由于粤语语境下,学习 OpenCV 的资料其实少有,未为不可或缺教师已经不适那时候候宜de 1.x 版内容《学习 OpenCV》,正是各路博主碎片化的学习心得,《OpenCV 2 计算机视觉编程手册》能够说是读书 OpenCV 的最棒入门渠道了。这段日子亟需将卷积神经网络的 Matlab 代码转变来 C 的,我也向实验室同房间的一个人学弟借了此书,差不多看了一晃,入眼看了第 1 章、第 2 章、第 6 章和附录。

方今布置学习一些图像管理方面包车型客车知识,第不时间想到了作用强盛的 OpenCV Lib。

一、前言
  居民身份证识别,又称OC翼虎技能。OCOdyssey工夫是光学字符识其余缩写,是由此扫描等光学输入方式将各类票据、报刊、书籍、文稿及其它印刷品的文字转变为图像音讯,再接纳文字识别技艺将图像音讯转变为能够选用的微机输入本领。
  因为项目供给,所以这个天查阅了有关材料,想在英特网看看有未有大神封装的现存的demo能够用。可是无果,英特网有关ocr这一块的材质超少,比较可信赖的都以要收取费用的,并且价格也不便于。可是在天朝,收取金钱感到心里难熬,所以就决定自身研讨一番。
  先上三个结尾贯彻的功能(倘诺mac不是retain显示器的,分辨率会有震慑,必要在真机上调治)

至于图片处理


搭乘飞机科学和技术的上扬,AI、机器学习、AHighlander、VEnclave等已经逐步走进生活,情势识别、图像捕捉、图片拼接等曾经化为个中的首要环节。因而,图像处理技能在现在会被移位端普及接受。个中,有无数C 的库的使用广泛,常用的有:OpenCV、FreeImage、CImg和CxImage。

有关那多少个库的性情和优短处能够参谋图像识别第四次全国代表大会图像库相比较。

因为那书是手册性质的,都以部分函数实例,所以记录下来,以便日后再用。下文是书中有个别内容的剪辑,夹杂一些自己的知道。全文也可能有一些长,遂给出目录如下:

早在一年多前出来香江实习的时候,实习公司的叁个短录像管理 App 在中期的技术选型的时候就将 OpenCV 作为主要解决方案之一。无语的是,当初自己是叁个没毕业的 iOS 小菜鸡,初露锋芒又不懂的 C ,还要担负起独立开辟的大旗,实乃搞不懂也没时间搞这样高精尖的 Lib。在放任 OpenCV Lib 以至尝试过 AVFoundation 框架后,因而项目最后选取了 GPUImage 来贯彻,当然那都未来话了。

末段兑现的效果.gif

关于OpenCV


第1章 接触图像
第2章 操作像素
第6章 图像滤波
附录 OpenCV3 介绍及代码导读

本次的 OpenCV 的求学,一方面是为着弥补之前的手艺空白,另一面也是为了和谐能在图像管理上全部领悟,能站在有手艺的人的肩膀上提高一下自个儿本领的维度。

二、要求利用的手艺
搜了大多质感,开采要开展身份ID编号的辨别,须要采用以下两种才能:
图像管理手艺
席卷灰度化管理,二值化,腐蚀,轮廊检查实验等等。
灰度化管理

简介

OpenCV (Open Source Computer Vision Library卡塔尔国是二个在BSD许可下公布的开源库,因此它是无需付费提必要学术和商业用项。有C 、C、Python和Java接口,帮助Windows、Linux、MacOS、iOS和Android等连串。OpenCV是为总括功效而规划的,并且稳重关怀实时应用程序的向上和支撑。该库用优化的C/C 编写,能够动用于多核管理。在启用OpenCL的底工上,它能够选择底层的异构总括平台的硬件加快。
——opencv.org

勘误
自己的吸引
下一步安插

指标及结果

本篇的创作目的是记录学习 OpenCV Lib 以至选拔到答题卡识别的有关进程,並且也是对新学习知识点的梳理和重新组织。

本文的达成目标是:利用 OpenCV Lib 识别一张特定的答题卡照片,並且识别出学生填涂的选项。

新葡京8455 1opencv_result新葡京8455 2opencv_origin

:图片灰度化管理正是将点名图片每个像素点的QX56GB多少个轻重通过一定的算法总括出该像素点的灰度值,使图像只含亮度而不含色彩新闻。

OpenCV的模块

从官方文书档案中大家得以看出其满含模块以至对iOS的扶持情形。

  • core:简洁的中央模块,定义了中央的数据布局,包括稠密多维数组 Mat 和别的模块须要的主干函数。
  • imgproc:图像管理模块,包含线性和非线性图像滤波、几何图像转变(缩放、仿射与透视调换、日常性基于表的重映射卡塔尔(قطر‎、颜色空间更改、直方图等等。
  • video:摄像深入分析模块,包罗移动测度、背景消释、物体追踪算法。
  • calib3d:包蕴宗旨的多视角几何算法、单体和立体相机的标定、对象姿态推测、双眼立体相称算法和因素的三维重新创建。
  • features2d:包罗了招摇过市特点检查评定算法、描述算子和算子相配算法。
  • objdetect:物体格检查测和有个别预订义的物体的检验(如人脸、眼睛、高柄杯、人、小车等卡塔尔。
  • ml:各类机器学习算法,如 K 均值、支持向量机和神经网络。
  • highgui:四个粗略易用的接口,提供录制捕捉、图像和摄像编码等成效,还会有轻松的 UI 接口 (iOS 上可用的仅是其四个子集卡塔尔。
  • gpu:OpenCV 中分歧模块的 GPU 加速速总括法 (iOS 上不可用State of Qatar。
  • ocl:使用 OpenCL 落成的通用算法 (iOS 上不可用卡塔尔。
  • 一部分别样帮扶模块,如 Python 绑定和客户进献的算法。

<div id="Section1">第1章 接触图像</div>

  • OpenCV 库的布局
  • 载入、呈现及保存图像

技艺方案

急需评释的是,在攻读 OpenCV 的幼功知识时,无意间开掘唐巧大神多年前写的 猿题库iOS客商端的手艺细节:答题卡扫描算法 一文。文中提到,在篇章表露时有关的识别算法还在进展专利申请,何况在专利申请甘休会透露算法细节,但是可惜的是辅车相依的算法细节并从未当面。

可是幸运的是,唐巧大神提供了一套不错的减轻方案,笔者本身的算法正是依照那几个思路实行的,方案如下:

  • 图像预管理,压缩图像;
  • 将彩色图片转为灰度图像;
  • 二值化灰度图像,识别答题卡区域;
  • 透视转变,图像纠正偏差或趋向;
  • 答案区域 ROI 识别;
  • ROI 色值总结,标定答案。

灰度图.png

大家得以行使OpenCV在iOS上做哪些

根据OpenCV,iOS应用程序能够兑现无数有趣的效果,也可以把大多繁琐的职业轻巧化。平常可用来:

  • 对图纸实行灰度管理(官方示例)
  • 人脸识别,即特征追踪(官方示例)
  • 教练图片特征库(可用于格局识别)
  • 领到一定图像内容(依照须求还原有用图像消息)
    ……

OpenCV 库的布局

  • sources文件夹下的子文件夹:
    • doc 文件夹中隐含的是文书档案 include 文件夹中是颇有头文件
    • modules 文件夹中带有全数的源程序
    • samples 文件夹中则是好些个简短的上学用模范

第 2 页讲了下怎么编写翻译的,对于新版 OpenCV 来讲已经未有须求了,解压后的 build 文件夹正是编写翻译好的源委。

第 3 页介绍了各模块的意义,还只怕有推荐的宣示情势,为啥要用这种证明方式啊?

第 6 页提到,为了信守 ANSI C 标准,在用 Visual Studio 创立工程时精选 Application Settings 时,没有勾选 Precompiled Header 选项,那是 Visual Studio 的预编写翻译头文件特性,能够加快编译进程。

准备

设想到 OpenCV 是依赖 C/C 可跨平台的通用 Lib,为了减弱学习花销,便将总体学习和尝试集成到 iOS 的开垦条件里了。中期要做如下几上边包车型地铁预备职业:

  1. 下载编写翻译 OpenCV Lib,只怕直接下载最新的 iOS OpenCV.framework 的 Release 版本;
  2. 将自动编写翻译或 Release 版 OpenCV.framework 导入 iOS 项目工程中;
  3. 因为 OpenCV 中的 MIN 宏和 UIKit 的 MIN 宏有冲突,所以须要在 .pch 文件中,先定义 OpenCV 的头文件,不然会有编写翻译错误;
  4. 将急需混编 C 和 Objective-C 的文本后缀改为 .mm;
  5. 为 UIImage 增添 Category,方便与OpenCV 图象格式的数量 cv::Mat 相互调换。因这个麻烦的安插难题不是本文写作体贴,而且英特网不乏部分详尽表明,推荐参谋在MacOS和iOS系统中选用OpenCV 一文,这里就不再赘言。

二值化

导入OpenCV


opencv如今分成四个版本八种:opencv2.4.x和opencv3.x。
导入项指标二种方式:

载入、显示及保存图像

  • 声称图像变量
cv::Mat image;

创建宽高都为0的图像,重回值是贰个布局体,

  • 图像读取、解码以至内部存款和储蓄器分配
image = cv::imread("img.jpg");
  • 检查图疑似否被正确读取
if (!image.data) {
    // 图像尚未创建……
}

这里的成员变量data事实上是指向已分配的内部存款和储蓄器块的指针,蕴含图像数据。当不设有数量时,它被轻易设置为0.

  • 宣称一个急需开展图像呈现的窗口,接着内定须要呈现的图像:
cv::namedWindow("Original Image");  // 定义窗口
cv::imshow("Original Image", image); // 显示图像

展现图像的这条语句之所以还要现身窗口名称,是为了内定毕竟把图像展示到哪个窗口去,因为或者存在多少个窗口。

  • 新葡京8455,图像翻转
cv::Mat result;
cv::flip(image, result, 1); // 1表示水平翻转
                            // 0表示垂直翻转
                            // 负数表示既有水平也有垂直翻转
  • 伺机客户输入
cv::waitKey(0); //括号中填的数字是毫秒数,0为一直等待

万一未有那句话,显示的图像会一闪而过。

  • 将图像写到磁盘
cv::imwrite("output.bmp", result);

文本的后缀名决定了图像保存时的编码格式。

  • 点名初叶尺寸
cv::Mat ima(240, 320, CV_8U, cv::Scalar(100));

CV_8U对应的是单字节的像素图象,字母U意味着无符号的(Unsigned)。对于彩色图片,须要钦点3个通道(CV_8UC3)。

当 cv::Mat 对象离开功效域后,分配的内部存款和储蓄器将自动释放,进而防止内部存款和储蓄器泄漏的干扰。
别的,cv::Mat 达成了援用计数以至浅拷贝,当图像之间张开赋值时,图像数据并从未爆发复制,五个对象都针对同一块内部存款和储蓄器块。那也可用来参数字传送值的图像,以至重临值传值的图像。援引计数的效果与利益是当全部引用内部存款和储蓄器数据的对象都被析构后,才会释放内部存款和储蓄器块。如若你指望创造的图像具备原始图像的崭新拷贝,那么能够选用copyTo(卡塔尔(قطر‎方法。

cv::Mat image2, image3;
image2 = result; // 两幅图像拥有同一份数据
result.copyTo(image3); // 创建新的拷贝

一经翻转output图像,并出示image2和image3,能够看看image2页翻转了,而image3未有变。

同理,函数重回其实也是贰次浅拷贝进程。

cv::Mat function() {
    // 创建图像
    cv::Mat ima(240, 320, CV_8U, cv::Scalar(100));
    // 并返回它
    return ima;
}

// 得到灰度图
cv::Mat gray = function();

在函数function内,ima只是个部分变量,在离开功效域时应有被析构掉,但由于他所关联的援用计数表示个中图像正在被另一个对象gray所引用,由此内部存款和储蓄器块并不会被放飞。

现实贯彻

以下为全方位解决方案的分步落到实处算法及成效图,绝超越58%均使用的是 OpenCV Lib 标准 API,具体效果以致参数表明可活动查阅官方文档。

  • 图像预管理、压缩图像

因 iOS 系统的图样数据为 UIImage 类型,在利用 OpenCV Lib 管理图片是供给预管理成为 cv::Mat 类型,然后将预处理后的 cv::Mat 图像数据作为 inputMat 并对其进展裁减管理,减弱 CPU 运算负荷。

// 压缩cv::resize(inputMat, outputMat, cv::Size(inputMat.rows / 1.5, inputMat.cols/ 1.5));
  • 将彩图转为灰度图像

将减少管理后的 cv::Mat 彩图数据进行灰度管理,便于接下去的二值化。

// 灰度处理cv::cvtColor(inputMat, outputMat, CV_BGR2GRAY);

管理结果:

新葡京8455 3opencv_grayMat

  • 图像降噪、二值化

在图像举办二值化以前,须求对灰度图像做一回降噪管理,用以湮灭图像模糊的噪声,进步中二年级值化的清晰度。在对待过均值滤波、高斯滤波、中值滤波后,选用了意义稍显然的均值滤波格局,代码如下:

// 滤波 去噪声cv::blur(inputMat, outputMat, cv::Size;// 二值化cv::threshold(inputMat, outputMat, 100, 255, cv::THRESH_BINARY_INV);

管理结果:

新葡京8455 4opencv_binary

  • 直线检查实验

对二值化图像进行直线检查评定,指标是检验出答题卡的四方,为了视觉效果尤其分明,这里将检验的直线,直接绘制在降低的图像上,並且颜色设置为铜绿。

// 直线检测std::vector<cv::Vec4i> lines;cv::HoughLinesP(outputMat, lines, 1, CV_PI/180, resizeMat.rows / 4, resizeMat.rows / 2, 5);for (size_t i = 0; i < lines.size { // 获取直线收尾两点 cv::Vec4i line = lines[i]; cv::Point point_1 = cv::Point(line[0],line[1]); cv::Point point_2 = cv::Point(line[2],line[3]); // 绘制直线 cv::line(resizeMat, point_1, point_2, cv::Scalar(255,0,0,1));}

处理结果:

新葡京8455 5opencv_line

  • 直线过滤

因须求识别答题卡的ROI区域(即每一道题的选项地方),大家须要先识别出答题卡方框区域的五个尖峰,以便依据七个极点举行透视转变。

八个终端的职责可以依附上、下、左、右四条直线,两两相交的性质分别求出,不过特别不幸,如上海体育场所所示,进行直线检验时通过安装合理的阈值参数,能检查实验处在出边框范围内的浩大条直线,由此在思索七个交点在此以前,还亟需先创立的过滤出上、下、左、右四条直线。

为便于起见,这里直接在检查测试出直线的时候实行过滤,过滤的平整很简短,依照直线四个端点分别针锋相投于图像大旨点的岗位,决断出目前直线归属内外左右的哪三个方向,而且只保留该方向第一条被检查测验到的直线。

直线检查评定及过滤的代码如下:

// 直线检测std::vector<cv::Vec4i> lines;cv::HoughLinesP(outputMat, lines, 1, CV_PI/180, resizeMat.rows / 4, resizeMat.rows / 2, 5);cv::Vec4i filtLines[4]; // 过滤的线 [上,左,下,右]int filtLineFlag[4] = {0};cv::Point originPoint = cv::Point(resizeMat.rows / 2, resizeMat.cols / 2); // 图像中心点for (size_t i = 0; i < lines.size { cv::Vec4i line = lines[i]; cv::Point point_1 = cv::Point(line[0],line[1]); cv::Point point_2 = cv::Point(line[2],line[3]);// 过滤线 if (point_1.y > originPoint.y && point_2.y > originPoint.y && filtLineFlag[0] == 0) { filtLines[0] = line; filtLineFlag[0] = 1; cv::line(resizeMat, point_1, point_2, cv::Scalar(255,0,0,1)); } if (point_1.x < originPoint.x && point_2.x < originPoint.x && filtLineFlag[1] == 0) { filtLines[1] = line; filtLineFlag[1] = 1; cv::line(resizeMat, point_1, point_2, cv::Scalar(255,0,0,1)); } if (point_1.y < originPoint.y && point_2.y < originPoint.y && filtLineFlag[2] == 0) { filtLines[2] = line; filtLineFlag[2] = 1; cv::line(resizeMat, point_1, point_2, cv::Scalar(255,0,0,1)); } if (point_1.x > originPoint.x && point_2.x > originPoint.x && filtLineFlag[3] == 0) { filtLines[3] = line; filtLineFlag[3] = 1; cv::line(resizeMat, point_1, point_2, cv::Scalar(255,0,0,1)); } cv::line(resizeMat, point_1, point_2, cv::Scalar(255,0,0,1));}
  • 测算四个极端

地方通过轻便的过滤算法,得到了区别方面包车型地铁四条直线,并贮存在 cv::Vec4i filtLines[4] 的容器内,容器内的线条和呼应方位为:[上,左,下,右]。

接下去变能够分别抽取对应地点的两条线段总计交点,计算交点需求接纳简便的数学公式,代码如下,不再赘言:

// 计算直线交点cv::Point CrossPointWithLine(cv::Vec4i & line1, cv::Vec4i & line2) { int l1_1_x = line1[0]; int l1_1_y = line1[1]; int l1_2_x = line1[2]; int l1_2_y = line1[3]; float a = (l1_1_y - l1_2_y) / ((l1_1_x - l1_2_x) == 0 ? 1.0 :(l1_1_x - l1_2_x)); float b = l1_1_y - l1_1_x * a; int l2_1_x = line2[0]; int l2_1_y = line2[1]; int l2_2_x = line2[2]; int l2_2_y = line2[3]; float c = (l2_1_y - l2_2_y) / ((l2_1_x - l2_2_x) == 0 ? 1.0 : (l2_1_x - l2_2_x)); float d = l2_1_y - l2_1_x * c; float x =  / ; float y = (a*d - b*c) / ; return cv::Point;}

利用容器将计算的交点,依照岗位有序存储:

std::vector<cv::Point> filtPoints; // 存放计算的焦点filtPoints.push_back(CrossPointWithLine(filtLines[0], filtLines[1]));filtPoints.push_back(CrossPointWithLine(filtLines[0], filtLines[3]));filtPoints.push_back(CrossPointWithLine(filtLines[1], filtLines[2]));filtPoints.push_back(CrossPointWithLine(filtLines[3], filtLines[2]));

已总括的多个交点为圆心画圆,查看效果(多个终端的圆画的有一些小,凑合看吧):

新葡京8455 6opencv_crossPoint

  • 图像纠正偏差或倾向

依照上述的八个极点,布局透视调换的转换矩阵,利用 OpenCV Lib 的透视转变,对灰度图像,举办图像纠正偏差或偏侧管理。

// 构造变换矩阵cv::Point2f src_vertices[4];src_vertices[0] = filtPoints[0];src_vertices[1] = filtPoints[1];src_vertices[2] = filtPoints[2];src_vertices[3] = filtPoints[3];cv::Point2f dst_vertices[4];dst_vertices[0] = cv::Point(0,resizeMat.cols);dst_vertices[1] = cv::Point(resizeMat.rows,resizeMat.cols);dst_vertices[2] = cv::Point;dst_vertices[3] = cv::Point(resizeMat.rows,0);// 透视变换cv::Mat transform = cv::getPerspectiveTransform(src_vertices,dst_vertices);cv::warpPerspective(grayMat, output, transform, cv::Size(resizeMat.rows, resizeMat.cols));

职能如下:

新葡京8455 7opencv_transform

  • 设置选项区域 ROI

观看纠正偏差或偏侧后的灰度图像的规律,可以根据每5道题设置三个ROI,并标定出相应的岗位,这里供给特别注意的是,上下左右以致 ROI 间距的安装须求依附整个纠正偏差或偏侧后的图像大小的百分比来显明,为了轻易起见,这里一向写成固定值。算法如下:

// 设置ROI, 使用容器记录ROIstd::vector<cv::Rect> ROIRect;int leading = 40, trailing = 15, top = 5, bottom = 5, margin_col = 50, margin_row = 10, width = 0, height = 0, row = 8, col = 4;width = (grayMat.cols - leading - trailing - margin_col *  / col;height = (grayMat.rows - top - bottom - margin_row *  / row;for (int i = 0; i < row; i  ) { for (int j = 0; j < col; j  ) { cv::Rect rect = cv::Rect(j * (width   margin_col)   leading, i * (height   margin_row   0.7)   top, width, height); ROIRect.push_back; cv::rectangle(writeMat, rect, cv::Scalar(255,0,0,1)); }}

将分开的 ROI 使用方框标志,效果如下:

新葡京8455 8opencv_ROIRect

  • 依赖选项区域,设置每道题的 ROI

这一步对接收区域特别拆分,总括出每一道题的 ROI。能够和上面总计区域的办法统一,直接进行每道题的 ROI 拆分,能使得减弱循环及总结次数,减弱 CPU 负荷,那大约也是最终识别耗时比唐巧大佬多出0.03秒的缘由之一,这里不再查究。

// 遍历ROI,设置并记录每道题的ROIstd::vector<cv::Rect> ROIItemRect;for (int i = 0; i < ROIRect.size { cv::Rect rect = ROIRect[i]; int height = 0, margin_height = 0; height = (rect.height - margin_height * 4) / 5; for (int k = 0; k < 5; k  ) { cv::Rect itemRect = cv::Rect(rect.x, rect.y   (height   margin_height) * k, rect.width, height); ROIItemRect.push_back; cv::rectangle(writeMat, itemRect, cv::Scalar(255,0,0,1)); }}

效率如下:

新葡京8455 9opencv_ROIItemRect

  • 二值化纠正偏差或偏侧后的灰度图像,便于接下去的色值总括

这里对纠正偏差或倾向后的灰度图举行二值化操作,其实不是必需的,为了质量升高减弱耗时才做,在开展上面包车型地铁图像纠正偏差或偏侧时方可直接对第一遍二值化的图像进行改革操作。可是在促成的长河中因为要调节每一步的展现效果,这里多做了一回拍卖,可以忽略。

// 二值化 灰度图cv::Mat binaryMat;cv::threshold(grayMat, binaryMat, 100, 255, cv::THRESH_BINARY);
  • 划分选项,总括色值,计算有效应对

对纠正偏差或趋向后的二值化图像,依据上述计算的每道题的 ROI,依照选项横向均等划分为 5 个区域,对应答题卡中的 ABCDE 四个筛选,分别对每道题的 ROI 的 5 个区域的像素举办色值总计,总括优异值等于 0 的像素点个数,个数抢先该选项总像素点的 60% 时即为有效回应,而且 log 出最近的题号和选项值。算法如下:

for (int i = 0; i < ROIItemRect.size { // 遍历每道题 cv::Rect rect = ROIItemRect[i]; // 分割选项 int width = rect.width / 5; for (int k = 0; k < 5; k  ) { cv::Rect itemRect = cv::Rect(rect.x   width * k, rect.y, width, rect.height); cv::Mat roiMat = binaryMat; // 截取ROI cv::rectangle(writeMat, itemRect, cv::Scalar(255,0,0,1)); int count = 0; // 统计色值 for (int x = 0; x < roiMat.rows; x  ) { for (int y = 0; y < roiMat.cols; y  ) { if (roiMat.at<uchar> == 0) { count   ; } } } // 超过 25% 算作有效答案 if (count > roiMat.rows * roiMat.cols * 0.25) { switch  { case 0: NSLog(@"第 %d 题:A",i   1); break; case 1: NSLog(@"第 %d 题:B",i   1); break; case 2: NSLog(@"第 %d 题:C",i   1); break; case 3: NSLog(@"第 %d 题:D",i   1); break; case 4: NSLog(@"第 %d 题:E",i   1); break; default: break; } continue; } }}

最终的识别结果如下:

新葡京8455 10opencv_result

:二值化管理就是将经过灰度化管理的图片调换为只满含蟹灰和深青莲二种颜色的图像,他们之间一贯不此外灰度的改动。在二值图中用255就是反动,0意味着水泥灰。

1.从官方网站下载框架,引进工程。

  1. 前往OpenCV官网或OpenCV国语官方网站下载相关iOS版本framework文件,从类型引入,
  2. 导入OpenCV依赖库
  • libc .tbd
  • AVFoundation.framework
  • CoreImage.framework
  • QuartzCore.framework
  • Accelerate.framework
  • CoreVideo.framework
  • CoreMedia.framework
  • AssetsLibrary.framework
  1. 引进相关头文件
#import <opencv2/opencv.hpp>

#import <opencv2/imgproc/types_c.h>

#import <opencv2/imgcodecs/ios.h>

#import <opencv2/highgui/highgui_c.h>

注:使用OpenCV的类必得帮衬C 的编写翻译景况,把.m文件改为.mm就可以。

<div id="Section2">第2章 操作像素</div>

  • 彩色或灰度图像存取像素值
void salt(cv::Mat &image, int n) {
    for (int k = 0; k < n; k  ) {
        // rand() 是随机数生成函数
        int i = rand() % image.cols;
        int j = rand() % image.rows;
        if (image.channels() == 1) { // 灰度图
            image.at<uchar>(j,i) = 255;
        } else if (image.channels() == 3) { // 彩色图
            image.at<cv::Vec3b>(j,i)[0] = 255;
            image.at<cv::Vec3b>(j,i)[1] = 255;
            image.at<cv::Vec3b>(j,i)[2] = 255;
        }
    }
}
  • 类 cv::Mat 有许多分子函数能够拿走图像的性质。公有成员变量 cols 和 rows 给出了图像的宽和高。成员函数 at(int y, int x卡塔尔能够用来存取图像成分。 然则必需在编写翻译期知道图像的数据类型,因为 cv::Mat 能够寄存自便数据类型的因素。那也是其一函数用模板函数来达成的因由。所以 at 方法要钦命数据类型,并且 at 方法自身不会开展其余数据类型转变。

  • cv::Vec3b,即由四个 unsigned char 组成的向量。

image.at<cv::Vec3b>(j,i)[channel] = value;

索引值 channel 标注了颜色通道号。
就好像的,还应该有二成分向量类 cv::Vec2b 和四成分向量类 cv::Vec4b,s 代表 short,i 代表 int,f 代表 float,d 代表 double。全体这个项目都以接收模板类 cv::Vect<T, N> 定义的,当中 T 代表类型,N 代表向量中的成分个数。

  • 有时候使用 cv::Mat 的积极分子函数会很劳苦,因为重返值的类型必需通过在调用时通过沙盘模拟经营参数钦定。由此,OpenCV 提供了类 cv::Mat_,它是 cv::Mat 的三个模板子类。在先行知道矩阵类型的动静下,使用 cv::Mat_ 能够拉动一些低价。那个类额外定义了一部分办法,可是并未有任何成员变量,所以此类的指针大概引用能够直接进行相互类型转换。该类重载了操作符 (卡塔尔(قطر‎,允许大家能够透过它直接存取矩阵成分。由此,若是有二个 uchar 类型的矩阵,我们得以如此写:
cv::Mat_<uchar> im2 = image; // im2 指向 image
im2(50, 100) = 0; // 存取第 50 行,100列

由于 cv::Mat_ 的成分类型在开立实例的时候已经宣称,操作符 (卡塔尔(قطر‎在编写翻译期就知晓要回来的数据类型。使用操作符 (卡塔尔国 获得重临值和使用 cv::Mat 的 at 方法取得的重返值是完全一致的,何况写起来特别简明。

  • 再一次循环遍历全部像素值:
void colorReduce(cv::Mat &image, int div = 64) {
    int nl = image.rows; // 行数
    int nc = image.cols * image.channels();
    for (int j = 0; j < nl; j  ) {
        // 得到第 j 行的首地址
        uchar* data = image.ptr<uchar>(j);
        for (int i = 0; i < nc; i  ) {
            data[i] = data[i] / div * div   div / 2;
        }
    }
}
  • OpenCV 暗许使用 BG奥德赛 的坦途顺序,何况 size 成员函数重临的先是宽,然后是高,成员变量 cols 代表图像的增长幅度(列数),rows 代表图像的冲天,step 代表以字节为单位的图像的卓有功用宽度,就算你的图像成分类型不是 uchar,step 依然带代表着行的字节数。图像的通道数能够由 channels 方法取得,total 函数再次来到矩阵的像素个数,像素大小能够从 elemSize 函数到手,对于叁个三通道的 short 型矩阵 CV_16SC3, elemSize 返回 6。

  • 为了简化指针运算,cv::Mat 提供了 ptr 函数能够得到图像放肆行的首地址。 ptr 函数是五个模板函数,它回到第 j 行的首地址:

uchar* data = image.ptr<uchar>(j);

等效地使用指针运算从一列移到下一列,所以,也能够那样些:

*data   = *data / div * div   div / 2;
  • 有一个知识,跟能不能够飞速遍历图像有关,供给超前明白,那就是:

鉴于功能的假造,OpenCV 或许会给矩阵的每行增补一些非常成分。那是因为,如果行的长度是 4 或 8 的倍数,一些多媒体管理晶片(如 英特尔 的 MMX 布局)能够更便捷地拍卖图像。那个额外的像素不会被出示恐怕封存,增加补充的值将被忽视。OpenCV将增加补充后一行的长短钦赐为主要字。倘使图像还没对行进行补充,那么图像的卓有成效宽度就等于图像的真人真事宽度。
当不对行进行抵补的时候,图像能够被视为一个长为 W*H 的一维数组。大家得以经过 cv::Mat 的四个成员函数 isContinuous 来剖断这幅图疑似否对行举行了补偿。假诺 isContinuous 方法重返值为真的话,表明这幅图像还未有对行进行补给。在部分图像处清理计算法中,我们得以应用图像的延续性,把全副管理进程使用三个巡回实现;

void colorReduce(cv::Mat &image, int div = 64) {
    int nl = image.rows; // 行数
    int nc = image.cols * image.channels();
    if (image.isContinuous()) {
        // 没有额外的填补像素
        nc = nc * nl;
        nl = 1; // it is now a 1D array
    }
    // 对于连续图像,本循环只执行一次
    for (int j = 0; j < nl; j  ) {
        // 得到第 j 行的首地址
        uchar* data = image.ptr<uchar>(j);
        for (int i = 0; i < nc; i  ) {
            data[i] = data[i] / div * div   div / 2;
        }
    }
}

当我们因此 isContinuous 函数获知图像尚未对行实行补给之后,我们即可将宽装置为 1,高度设置为 W*H,进而消亡外层循环。注意,大家也得以行使 reshape 方法来重写这段代码:

if (image.isContinous()) {
    // no padded pixels
    image.reshape(1, image.cols*image.rows); // 分别是行数和通道数
}
int nl = image.rows; // 列数
int nc = image.cols * image.channels();

reshape 不供给内部存款和储蓄器拷贝只怕重新分配就能够改造矩阵的维度。八个参数分别为新的通道数和新的行数。矩阵的列数能够依赖新的通道数和行数来自适应。
在此些视野中,内部存储器循环一回拍卖图像的全部像素。这些法子在同期管理几个小图像时会很有优势。

总结

那套应用方案的达成思路来源于唐巧大神的猿题库iOS客户端的技术细节:答题卡扫描算法 一文,本文达成的算法未有通过过多的测量试验,仅能确定保障那张图纸的识别率在95%上述。其余,在 CPU 运算耗费时间方面,那个总计方法未有进展优化亦不是最优解。

唐巧大神未有开源此算法,作者那菜鸡代码大家聚拢看呢,源代码不分包 opencv2.framework,请自行下载后增加进项目中。答题卡识别 德姆o 地址:

如有疑问,请联系自身:

-EOF-

二值图.png

2.使用CocoaPods安装。

很简单。

pod 'OpenCV'

底层指针运算

在类 cv::Mat 中,图像数据以 unsigned char 方式保留在一块内存中。那块内部存款和储蓄器的首地址能够由此 data 成员变量获得。data 是一个 unsigned char 型的指针,uoyi循环能够以如下形式带头:

uchar *data = image.data;

从日前行到下一行能够经过对指针加上行宽达成:

data  = image.step; // 下一行

step 代表图像的行宽(包蕴添补像素)。平时来讲,你能够透过如下格局赢得第 j 行、第 i 列像素的地址:

// (j, i) 处像素的地址为 &image.at(j, i)
data = image.data   j * image.step   i * image.elemSize();

但是,即便这种措施确实立竿见影,大家照旧不提议利用这种管理格局。因为这种情势除了轻松失误,还不适用于含有“感兴趣区域”的图像。

腐蚀

OpenCV的简易利用


拍卖图片能够创造二个UIImage的归类,OpenCV图像管理的有关代码都得以在此个类中得以完毕。
代码可以知道作者Github项目地址

使用迭代器遍历图像

在面向对象的编制程序中,遍历数据集结日常是因此迭代器来形成的。迭代器是一种非常的类,它特别用来遍历集结中的各样要素,同期隐蔽了在给定的成团元宵素迭代的切实贯彻际处境势。这种音讯蒙蔽原则的采纳使得遍历会集尤其便于。其余,不管数据类型是何许,我们都得以运用雷同的办法遍历集合。标准模板库 STL 为各样容器类型都提供了迭代器,OpenCV 肖似为 cv::Mat 提供了与 STL 迭代器包容的迭代器。
四个 cv::Mat 实例的迭代器可以经过创办三个 cv::MatIterator_ 的实例来赢得。相同于子类 cv::Mat_,下划线意味着 cv::MatIterator_ 是二个模板类。之所以这么是由于经过迭代器来存取图像的成分,就必须在编写翻译期知道图像成分的数据类型。三个图像迭代器能够用如下方式宣示:

cv::MatIterator_<cv::Vec3b> it;

别的一种方法是应用定义在 Mat_ 内部的迭代器类型:

cv::Mat_<cv::Vec3b>::iterator it;

诸有此类就能够因此正规的 begin 和 end 那三个迭代器方法来遍历全数像素。值得提出的是,假如使用后一种办法,那么 begin 和 end 方法也亟供给利用相应的模板化的版本。那样,颜色裁减函数就足以重写为:

void colorReduce(cv::Mat &image, int div = 64) {
    // 得到初始位置的迭代器
    cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
    // 得到终止位置的迭代器
    cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();    
    // 遍历所有像素
    for (; it != itend;   it) {
        (*it)[0] = (*it)[0] / div * div   div / 2;
        (*it)[1] = (*it)[1] / div * div   div / 2;
        (*it)[2] = (*it)[2] / div * div   div / 2;
    }
}

小心,因为我们那边管理的彩图,所以迭代器重临的是 cv::Vec3b,种种颜色分量能够经过操作符 [] 得到。

应用迭代器遍历任何款式的会晤都信守千篇一律的情势。首先,创设三个迭代器特化版本的实例。在大家的演示代码中,就是cv::Mat_<cv::Vec3b>::iterator (或者 cv::MatIterator_<cv::Vec3b>).
下一场,使用集合起来地点(图像的左上角)的迭代器对其进行开始化。开头位置的迭代器平常是经过 begin 方法获得的。对于一个 cv::Mat 的实例,你能够由此image.begin<cv::Vec3b>(卡塔尔(قطر‎来收获图像左上角地点的迭代器。你也能够经过对迭代器进行代数运算。譬喻:假如你想从图像的第二行初阶,那么您能够用 image.begin<cv::Vec3b>(卡塔尔国 image.rows 来最早化迭代器。集结终止地方的迭代器能够经过 end 方法获得。可是end 方法取得的迭代器其实早已超过了汇集。那也象征迭代进程必得在迭代器达到那么些职位时甘休。end 方法得到的迭代器也足以开展代数运算。假若,你期望迭代进度在图像最终一行在此以前截止,那么迭代器的终止地方应该是 image.end<cv::Vec3b>(卡塔尔(قطر‎ - image.rows。一旦迭代器初阶化完结之后,你就足以创建三个循环遍历全部的要素知道达到终止地方。二个独占鳌头的 while 循环如下所示:

while (it != itend) {
    // do something
    ...
      it;
}

操作符 用来将迭代器从日前岗位移动到下一个职位,你也足以运用更加大的补给,比方,用it =10将迭代器每一次活动 10px。
在循环体内部,你可以应用解援引操作符 * 来读写当前因素。都操作使用 element = *it,写操作使用 *it = element。注意:假如您的操作对象是 const cv::Mat,恐怕您想重申当前循环不会对 cv::Mat 的实例进行退换,那么您就应当创建常量迭代器。常量迭代器的证明如下:

cv::MatConstIterator_<cv::Vec3b> it;

或者

cv::Mat_<cv::Vec3b>::const_iterator it;

在本例中,迭代器的启幕地方和终止地点是由此沙盘模拟经营函数 begin 和 end 获得的。借使我们在本章第一则秘籍中所做的那么,大家得以透过 cv::Mat_ 的实例来获取他们。那样可避防止在采纳 begin 和 end 方法的时候还要置顶迭代器的花色。之所以能够这么,是因为二个 cv::Mat_ 引用在创设的时候就隐式证明了迭代器的品类。

cv::Mat_<cv::Vec3b> cimage = image;
cv::Mat_<cv::Vec3b>::iterator  it = cimage.begin();
cv::Mat_<cv::Vec3b>::iterator  itend = cimage.end();

之所以那个事例能够而眼下那多少个例子不得以是因为,后边那一个例子的图像类型是 cv::Mat, 而这些事例的图像类型是 cv::Mat_。

:图片的腐蚀便是将得到的二值图中的浅深黄块进行扩充。即三回九转图片中相邻月光蓝像素点的因素。通过腐蚀能够把居民身份证上的身份ID号码连接在一块儿产生二个矩形区域。

图像灰度管理

1.在.h文件中声称四个类

@property (nonatomic, readonly) cv::Mat CVMat;

@property (nonatomic, readonly) cv::Mat CVGrayscaleMat;

2.申明Mat与UIImage相互转变以至灰度管理并赶回UIImage对象的外界方法

/**

 cv::Mat --> UIImage



 @return UIImage

 */

  (UIImage *)imageWithCVMat:(const cv::Mat&)cvMat;

/**

 UIImage --> cv::Mat



 @param image image

 @return cv::Mat

 */

  (cv::Mat)cvMatFromUIImage:(UIImage *)image;

/**

 UIImage --> cv::Mat (gray)

 @param image image

 @return cv::Mat

 */

  (cv::Mat)cvMatGrayFromUIImage:(UIImage *)image;

3.在.m中贯彻相关措施
生成cv::Mat对象

- (cv::Mat)CVMat {

    CGColorSpaceRef colorSpace = CGImageGetColorSpace(self.CGImage);

    CGFloat cols = self.size.width;

    CGFloat rows = self.size.height;



    cv::Mat cvMat(rows,cols,CV_8UC(4)); // 8 bits per component,4 channels



    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                // Pointer to backing data

                                                    cols,                      // Width of bitmap

                                                    rows,                      // Height of bitmap

                                                    8,                         // Bits per conponent

                                                    cvMat.step[0],             // Bytes per row

                                                    colorSpace,                // Colorspace

                                                    kCGImageAlphaNoneSkipLast |

                                                    kCGBitmapByteOrderDefault);// Bitmap info flags



    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), self.CGImage);

    CGContextRelease(contextRef);



    return cvMat;

}

变迁灰度cv::Mat对象

- (cv::Mat)CVGrayscaleMat {

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();

    CGFloat cols = self.size.width;

    CGFloat rows = self.size.height;



    cv::Mat cvMat = cv::Mat(rows,cols,CV_8SC1); // 8 bits per conponpent,1 channel



    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                // Pointer to backing data

                                                    cols,                      // Width of bitmap

                                                    rows,                      // Height of bitmap

                                                    8,                         // Bits of bitmap

                                                    cvMat.step[0],             //Bytes per row

                                                    colorSpace,                // Colorspace

                                                    kCGImageAlphaNone |

                                                    kCGBitmapByteOrderDefault);// Bitmap info flags



    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), self.CGImage);

    CGContextRelease(contextRef);

    CGColorSpaceRelease(colorSpace);



    return cvMat;

}

cv::Mat --> UIImage

  (UIImage *)imageWithCVMat:(const cv::Mat &)cvMat {

    return [[UIImage alloc] initWithCVMat:cvMat];

}

  (cv::Mat)cvMatFromUIImage:(UIImage *)image {

    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);

    CGFloat cols = image.size.width;

    CGFloat rows = image.size.height;



    cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels (color channels   alpha)



    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to  data

                                                    cols,                       // Width of bitmap

                                                    rows,                       // Height of bitmap

                                                    8,                          // Bits per component

                                                    cvMat.step[0],              // Bytes per row

                                                    colorSpace,                 // Colorspace

                                                    kCGImageAlphaNoneSkipLast |

                                                    kCGBitmapByteOrderDefault); // Bitmap info flags



    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);

    CGContextRelease(contextRef);



    return cvMat;

}

UIimage --> Gray cv::Mat

  (cv::Mat)cvMatGrayFromUIImage:(UIImage *)image {

    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);

    CGFloat cols = image.size.width;

    CGFloat rows = image.size.height;



    cv::Mat cvMat(rows, cols, CV_8UC1); // 8 bits per component, 1 channels



    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to data

                                                    cols,                       // Width of bitmap

                                                    rows,                       // Height of bitmap

                                                    8,                          // Bits per component

                                                    cvMat.step[0],              // Bytes per row

                                                    colorSpace,                 // Colorspace

                                                    kCGImageAlphaNoneSkipLast |

                                                    kCGBitmapByteOrderDefault); // Bitmap info flags



    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);

    CGContextRelease(contextRef);



    return cvMat;

}

4.在调节器中调用UIImage OpenCV相关代码,完成图片灰度处理

UIImage *image = [UIImage imageNamed:@"icon.jpg"];

cv::Mat inputMat = [UIImage cvMatFromUIImage:image];

cv::Mat greyMat;

cv::cvtColor(inputMat, greyMat, CV_BGR2GRAY);

//cv::Mat greyMat = [UIImage cvMatGrayFromUIImage:image];

UIImage *greyImage = [UIImage imageWithCVMat:greyMat];

self.imageView.image = greyImage;

5.效果

原图

处理后

得到代码运转时间

OpenCV 有三个可怜实用的函数 cv::getTickCount(卡塔尔能够用来度量一段代码的运营时刻。这么些函数重临从上次开机算起的时钟周期数。由于大家需求的是某些代码段运转的纳秒数,因此还索要别的叁个cv::getTickFrequency(卡塔尔国。此函数再次回到没秒内的机械石英钟周期数,用于总结函数(或一段代码)耗时的措施如下:

double duration;
duration = static_cast<double>(cv::getTickCount());
colorReduce(image); // 被测试的函数
duration = static_cast<double>(cv::getTickCount()) - duration;
duration /= cv::getTickFrequency(); // 运行时间,以 ms 为单位
访问方式 时间
data[i] = data[i] / div * div div / 2; 37ms
*data = *data / div * div div / 2; 37ms
*data = v - v % div div / 2; 52ms
*data = *data&mask div / 2; 35ms
colorReduce(input, output); 44ms
i<image.cols*image.channels()>; 65ms
MatIterator 67ms
.at(j,i) 80ms
3-channel loop 29ms

当输出图像需求被重新分配并不是以原地(in-place卡塔尔(قطر‎格局管理时(第5行),运转时刻为44ms,比 in-place的要慢。额外的光阴费用来自于内部存款和储蓄器分配。在循环体内部存储器,对于可提早计算的变量应制止重新总计。

腐蚀图.png

人脸识别

有关人脸识别的达成,能够参照他事他说加以考察依赖OpenCV的人脸识别。那是ObjC中华夏儿女民共和国上一篇译文,小编是外国大咖,那片博客写得要命详尽。
笔者的德姆o中不带有拍照部分,间接对一张图片中的人脸举行辨认,其促成如下:
1.成立二个Objective-C 的类FaceRecognition(即把.m文件.mm文件,扶持Objective-C与C 混编)
2.引进头文件:

#import <opencv2/opencv.hpp>

#import <opencv2/imgproc/types_c.h>

#import <opencv2/imgcodecs/ios.h>

#import "UIImage OpenCV.h"

3.对图纸张开管理转变

  (UIImage *)convertImage: (UIImage *)image {

    // 初始化一个图片的二维矩阵cvImage

    cv::Mat cvImage;



    // 将图片UIImage对象转为Mat对象

    cvImage = [UIImage cvMatFromUIImage:image];



    if (!cvImage.empty()) {

        cv::Mat gray;



        // 进一步将图片转为灰度显示

        cv::cvtColor(cvImage, gray, CV_RGB2GRAY);



        // 利用搞死滤镜去除边缘

        cv::GaussianBlur(gray, gray, cv::Size(5, 5), 1.2, 1.2);



        // 计算画布

        cv::Mat edges;

        cv::Canny(gray, edges, 0, 50);



        // 使用白色填充

        cvImage.setTo(cv::Scalar::all(225));



        // 修改边缘颜色

        cvImage.setTo(cv::Scalar(0,128,255,255),edges);



        // 将Mat转换为UIImage

        return [UIImage imageWithCVMat:cvImage];

    }    

    return nil;

}

4.读取图片中人脸的相干数据并蕴藏

  (NSArray*)facePointDetectForImage:(UIImage*)image{

    static cv::CascadeClassifier faceDetector;

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        // 添加xml文件

        NSString* cascadePath = [[NSBundle mainBundle]

                                 pathForResource:@"haarcascade_frontalface_alt2"

                                 ofType:@"xml"];

        faceDetector.load([cascadePath UTF8String]);

    });

    cv::Mat faceImage;

    faceImage = [UIImage cvMatFromUIImage:image];

    // 转为灰度

    cv::Mat gray;

    cvtColor(faceImage, gray, CV_BGR2GRAY);



    // 检测人脸并储存

    std::vector<cv::Rect>faces;

    faceDetector.detectMultiScale(gray, faces,1.1,2,CV_HAAR_FIND_BIGGEST_OBJECT,cv::Size(30,30));



    NSMutableArray *array = [NSMutableArray array];

    for(unsigned int i= 0;i < faces.size();i  )

    {

        const cv::Rect& face = faces[i];

        float height = (float)faceImage.rows;

        float width = (float)faceImage.cols;

        CGRect rect = CGRectMake(face.x/width, face.y/height, face.width/width, face.height/height);

        [array addObject:[NSNumber valueWithCGRect:rect]];

    }    

    return [array copy];

}

5.检查评定人脸并在图纸上人脸部分增多青蓝矩形线框以标志

  (UIImage*)faceDetectForImage:(UIImage*)image {

    static cv::CascadeClassifier faceDetector;

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{



        NSString* cascadePath = [[NSBundle mainBundle]

                                 pathForResource:@"haarcascade_frontalface_alt"

                                 ofType:@"xml"];

        faceDetector.load([cascadePath UTF8String]);

    });



    cv::Mat faceImage;

    faceImage = [UIImage cvMatFromUIImage:image];



    // 转为灰度

    cv::Mat gray;

    cvtColor(faceImage, gray, CV_BGR2GRAY);



    // 检测人脸并储存

    std::vector<cv::Rect>faces;

    faceDetector.detectMultiScale(gray, faces,1.1,2,0,cv::Size(30,30));



    // 在每个人脸上画一个红色四方形

    for(unsigned int i= 0;i < faces.size();i  )

    {

        const cv::Rect& face = faces[i];

        cv::Point tl(face.x,face.y);

        cv::Point br = tl   cv::Point(face.width,face.height);

        // 四方形的画法

        cv::Scalar magenta = cv::Scalar(255, 0, 0, 255);

        cv::rectangle(faceImage, tl, br, magenta, 2, 2, 0);

    }   

    return [UIImage imageWithCVMat:faceImage];

}

6.运维效果

Face Recognition

图像邻域操作的一个例子

void sharpen(const cv::Mat &image, cv::Mat &result) {
    // 如有必要则分配内存
    result.create(image.size(), image.type());
    for(int j = 1; j < image.rows-1; j  ) { // 处理除了第一行和最后一行之外的所有行
        const uchar* previous = image.ptr<const uchar>(j-1); // 上一行
        const uchar* current = image.ptr<const uchar>(j); // 当前行
        const uchar* next  = image.ptr<const uchar>(j 1); // 下一行
        for(int i = 1; i < image.cols - 1; i  ) {
            *output   = cv::saturate_cast<uchar>(5*current[i]-current[i-1]-current[i 1]-previous[i]-next[i]);
        }
    }
    // 将未处理的像素设置为0
    result.row(0).setTo(cv::Scalar(0));
    result.row(result.rows-1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols-1).setTo(cv::Scalar(0));
}

在总结输出像素值时,模板函数 cv::saturate_cast 被用来对计量结果进行品级。
setTo 函数能够用来安装矩阵的值,那些函数会将矩阵的富有因素都设为钦点的值。对于一个三通道的彩图,须要用 cv::Scalar(a,b,cState of Qatar 来钦定像素多少个通道的靶子值。

void sharpen2D(const cv::Mat &image, cv::Mat &result) {
    // 构造核(所有项都初始化为 0)
    cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
    // 对核元素进行赋值
    kernel.at<float>(1,1) = 5.0;
    kernel.at<float>(0,1) = -1.0;
    kernel.at<float>(2,1) = -1.0;
    kernel.at<float>(1,1) = -1.0;
    kernel.at<float>(1,2) = -1.0;
    // 对图像进行滤波
    cv::filter2D(image, result, image.depth(), kernel);
}
  • 函数 cv::split 将彩图的八个通道分别拷贝到多少个单身的 cv::Mat 实例中,然后在对那一个通道单独管理。
// 创建一个图像向量
std::vector<cv::Mat> planes;
// 讲一个三通道图像分离为三个单通道图像
cv::split(image1, planes);
planes[0]  = image2;
// 将三个单通道图像重新合并为一个三通道图像
cv::merge(planes, result);

轮廊检验

Objective-C与C 混编


众多地点需求用到Objective-C与C 混编,来减轻一些对象的传递调换难题。

领到兴趣区域(其实正是slicing)

imageROI = image(cv::Rect(colId, rowId, logo.cols, logo.rows));

定义ROI的一种方法是行使 cv::Rect,看名称就能够想到其意义,cv::Rect 代表一个矩形区域。钦命矩形的左上角坐标(布局函数的前七个参数)和矩形的长度宽度(布局函数的后多个参数)就足以定义多少个矩形区域。
另一种定义ROI的形式是点名感兴趣行或列的范围(Range)。Range是指从开始索引到终止索引(不蕴涵终止索引)的一段连接种类。cv::Range 能够用来定义Range。就算用 cv::Range 来定义 ROI,那么前例中定义 ROI 的代码能够重写为:

cv::Mat imageROI = image(cv::Range(270,270 logo.rows), cv::Range(385,385 logo.cols));

cv::Mat 的 (卡塔尔国 操作符重返另三个 cv::Mat 实例,这些实例能够用在接下去的函数调用中,因为ROI和原有图像分享数据缓冲区,对ROI的别的转换都会潜濡默化到原始图像的附和区域。由于创设ROI时不会拷贝数据,所以无论是ROI的轻重怎样,创立ROI的周转时刻都以常量。
假杜撰创建包罗原始图像特定行的ROI,能够应用如下代码:

cv::Mat imageROI = image.rowRange(start, end);

类似地,对于列:

cv::Mat imageROI = image.colRange(start, end);

在秘籍“遍历图像和邻域操作”中动用到的row方法和col方法其实是rowRange和colRange方法的特例,即先导索引等于终止索引,等于是概念了叁个单行或单列的ROI。

:图片经过腐蚀操作后相邻点会一连在一起产生三个大的区域,那个时候经过轮廊检查测量试验就足以把各类大的区域找寻来,那样就能够稳定到身份ID上边号码的区域。

字符串的更改

在C 中,字符串对象为char *,而在Objective-C中字符串对象为NSString,在编制程序中日常必要在二者之间相互调换。
1.NSString转换为char *

/**

 NSString --> char *



 @param string NSString (Objective-C)

 @return char *         (C  )

 */

char * string2Char(NSString *string) {

    const char *charString = [string UTF8String];

    char *result = new char[strlen(charString)   1];

    strcpy(result, charString);

    //    delete[] result;

    return result;

}

2.char *转换为NSString

NSString *OCString = [NSString stringWithUTF8String:cppString];

<div id="Section6">第6章 图像滤波</div>

  • 均值滤波
cv::blur(image, result, cv::Size(5,5));
  • 高斯滤波
cv::GaussianBlur(image, result, cv::Size(5,5), 1.5);

本条 1.5 正是高斯函数的$sigma$,决定高斯函数平坦与否。

  • 生成 1 维高斯核
cv::Mat gauss = cv::getGaussianKernel(9, sigma, CV_32F);

9正是一维高斯核向量的长度。

  • 先对原图应用低通滤波,然后隔行、隔列抽出像素
cv::Mat reducedImage; // 包含缩小后的图像
cv::pyrDown(image, reducedImage); //将图像尺寸减半

同理,还存在 cv::pyrUp 函数将图像尺寸放大学一年级倍。

  • 点名目的图像的尺寸
cv::Mat reducedImage; // 包含改变尺寸后的图像
cv::resize(image, reducedImage, cv::Size(image.cols/3, image.rows/3)); // 改变为 1/3 大小

还论及了 cv::boxFilter 和 cv::filter2D 函数

  • 中值滤波
cv::medianBlur(image, result, 5);
  • Sobel函数
    cv::Sobel
    cv::minMaxLoc
    sobel.convertTo
    cv::threshold
    cv::cartToPolar
    cv::Scharr

轮廊图.png

积攒cv::Mat图片对象

/**

 Write image to Document

 @param imageName image name

 @param img cv::Mat

 @return if complete

 */

bool writeImage2Document(const char *imageName, cv::Mat img) {

    UIImage *stitchedImage = [[UIImage alloc] initWithCVMat:img];

    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

    NSString *imagePath = [docPath stringByAppendingString:[NSString stringWithFormat:@"/%@",[NSString stringWithUTF8String:imageName]]];

    // 将UIImage对象转换成NSData对象

    NSData *data = UIImageJPEGRepresentation(stitchedImage, 0);

    [data writeToFile:imagePath atomically:YES];

    return true;

}

<div id="Section7">附录

把附录的剧情全方位敲下来,因为令你越来越好地明白OpenCV的集体构造,以致它是如何,能完毕怎样?还也可能有便是samples/cpp/ 文件夹中的楷模介绍,应该有最严肃的OpenCV编制程序风格,能够用来学习。

OpenCV3的转移在哪?
C风格的API极快将会毁灭,完全被C 的API替代,代码风格更精练,不易出错。读者若是想依赖OpenCV最新的职能,记得清理代码中C风格API
C API将进而精简
具有的算法都将三回九转自 cv::Algorithm 接口
巨型的模块拆分为小模块,模块将要前面继续教师。

OpenCV 3 的源代码文件夹:

  • 3rdparty/: 满含第三方库,如用录像解码用的 ffmpeg、jpg、png、tiff 等图片的解码库。
  • apps/: 包涵进行 Haar 分类器训练的工具,OpenCV 举办人脸检验就是依赖Haar 分类器。倘令你想检验人脸以外的图纸,千万不要错过那个工具。
  • cmake/: 包括生成工程项目时 cmake 的依赖性文件,用于只好寻觅第三方库,普通开拓者无需关切那几个文件夹的剧情。
  • data/: 包蕴 OpenCV 库及轨范中用到的能源文件,Haar 物体格检查测的分类器坐落于 haarcascades 子文件中。
  • doc/: 包罗生成文书档案所需的源文件及救助脚本。
  • include/: 包罗入口头文件。OpenCV 子文件夹中是 C 语言风格的 API,也正是《学习 OpenCV》中呈报的 API 函数,官方将逐步淘汰 C 风格函数,由此我不推荐我们利用该公文夹中的头文件。OpenCV 2 子文件夹中唯有二个 opencv.hpp 文件,那是 OpenCV 2 及 OpenCV 3 推荐应用的头文件。
  • modules/: 富含焦点代码,OpenCV 真正的代码都在此个文件夹中。OpenCV 从 2.0 发轫以模块的办法组织各个成效,近八年模块的多寡进步得快速,前面作者会依次介绍种种模块的作用。
  • platforms/: 包涵交叉编译所需的工具链及附加的代码,交叉编写翻译指的是在三个操作系统中编写翻译供另三个连串使用的文件。
  • samples/: 那是大户人家最赏识的表率文件夹,前边小编也会更为讲明。

文字识别本领
通过辨认图像,将图像音信转变为能够利用的微微处理器输入才能。举个例子上面那张带有一串数字的图形,通过ocr识别才干能够将图纸中隐含的数字消息以字符串的方法出口。

总结


OpenCV实乃太强盛了,小编决定潜研,暂不总结,所以未有下结论。

CPU模块

  • androidcamera/: 仅用于 Android 平台,使得能够通过与别的平台雷同的接口来调整 Android 设备的双反相机。
  • core/: 大旨成效模块,定义了骨干的数据布局,包涵最根本的 Mat 类、XML 读写、OpenGL 三个维度渲染等。
  • imgproc/: 全称为 Image Processing,即图像管理,满含图像滤波、集结图像调换、直方图计算、形状描述子等。图像管理是计算机视觉的显要工具。
  • highgui/: 高级图形分界面及多媒体文件读写,富含客商分界面、Qt、对图像及摄像文件的读写操作。
  • video/: 摄像分析模块,包蕴背景提取、光流跟踪、Carl曼滤波等,做摄像监察和控制的开辟者会通常使用这几个模块。
  • calib3d/: 相机标定及空间维度重新建立。相机标定用于收取相机自己短处招致的画面形变,还原真实的境况,确认保证总计的准头。三个维度重新组建经常用在眼睛视觉(立体视觉),即多个标定后的摄像头观望同叁个场合,通过计算两幅画面中的相关性来推测像素深度。
  • features2d/: 蕴含 2D 特征值检查测量检验的框架。富含各样特征值检验器及描述子,如 FAST、MSETiguan、OBRB、B奔驰G级ISK 等。各种特征值具备统一的算法接口,因而在不影响程序逻辑的气象下得以轮番替换。
  • objdetect/: 物体检查评定模块,包含 Haar 分类器、SVM 检查评定器及文字检查测试。
  • ml/: 全称为 Machine Learning,即机器学习。满含总计模型、K 近日邻、援救向量机、决策树、神经互连网等精髓的机器学习算法。
  • flann/: 用于在多维空间内聚类及搜索的雷同算法,做图像检索的开荒者对它不会不熟悉。
  • photo/: 计算油画学,满含图像修补、去噪、HD讴歌MDX成像、非真实感渲染等。即使读者想完毕 Photoshop 的高等功用,那么这么些模块不可贫乏。
  • stitching:/ 图像拼接,可用于创设全景图。
  • nonfree/: 受专利爱慕的算法,包蕴 SIFT 和 SURAV4F。从成效上的话,这多少个算法归于 features2d 模块,但鉴于它们都以受专利珍惜的,相拥在档期的顺序中大概必要专利方的批准。
  • contrib:/ 包含新加上的试验性质的代码。开荒者期望已久的人脸识别作用便坐落于那几个模块内,名叫FaceRecognizer。
  • legacy/: 拉脱维亚语含义为遗产,即废弃已久的代码,官方不引入应用这些模块中的作用。
  • optim/: 全称为 Optimization,那个模块包蕴通用的数值优化。包涵线性规划等算法。
  • shape/: 形状匹配算法模块,用于描述形象、比较形状。
  • softcascade/: 另一种物体格检查估测计算法,Soft 卡斯卡特分类器,满含检查测量试验模块和教练模块。
  • superres/: 全称为 Super Resolution,用于抓好图像的分辨率。
  • videostab/: 全称为 Video Stabilization,用于消灭相机移动录制时录像相当不够稳固的难点。
  • viz/: 三个维度可视化模块。能够感觉这一个模块完毕了三个简洁明了的三个维度可视化引擎,有各种UI 空间和键盘、鼠标人机联作形式。底层完毕基于 CTK 这么些第三方库。

富含数字的图片.png

CUDA模块

那个模块的名称都是 cuda 起先,cuda 是显卡创设商 NVIDIA 推出的通用计算语言,在 OpenCV 3 中有恢宏的模块已经被移植到了 cuda 语言。让大家逐一看一下。

  • cuda/: CUDA- 加快的微电脑视觉算法,满含数据布局 cuda::GpuMat、基于 cuda 的双反相机标定及三维重新建构等。
  • cudaarithm/: CUDA- 加快的矩阵运算模块。
  • cudabgsegm/: CUDA- 加快的背景分割模块,经常用于录制监察和控制。
  • cudacodec/: CUDA- 加快的摄像编码与解码。
  • cudafeatures2d/: CUDA- 加速的性格检查评定与汇报模块,与 features2d/ 模块功效周边。
  • cudafilters/: CUDA- 加快的图像滤波。
  • cudaimgproc/: CUDA- 加快的图像处清理计算法,包括直方图总结、霍夫调换等。
  • cudaoptflow/: CUDA- 加快的光流质量评定算法。
  • cudastereo/: CUDA- 加快的立体视觉相称算法。
  • cudawarping/: 实现 CUDA- 加快的立时图像调换,包罗透视调换、旋转、改革尺寸等。

三、开源框架OpenCV和TesseractOCRiOS
OpenCV(完结图像处理技巧)
  OpenCV是三个开源的跨平台Computer视觉和机器学习库,通俗点的说,就是她给计算机提供了一双目睛,一双可以从图纸中获取新闻的近视镜,进而实现年人脸识别、身份ID识别、去红眼、追踪移动物体等等的图像相关的效用。opencv官网

samples/ 文件夹

  • android/: Android 平台的圭表。既有一起是 Java 的工程,也许有一起是 C 的工程,也可以有越发广阔的 Java 与 C 共存的工程。
  • c/: 使用 C API 的表率。在 C API 渐渐淡出历史舞台后,这一个文件夹也理应会跟着消失吗。
  • cpp/: 由于 OpenCV 是一款 C 库,由此 C 的返利是最多的,后边将重视介绍。
  • directx/: directx (d3d卡塔尔 是微软的个体三维图像 API,这些文件夹中的楷模覆盖了 d3d9、d3d10、d3d11.
  • gpu/: 利用 cuda 加快的楷模。
  • java/: OpenCV 3 官方扶助 Java 语言绑定,因而这里演示如何使用 Java 版本的 OpenCV。
  • python2/: OpenCV 3 官方支持 Python 语言绑定,因而这里演示使用 Python 2 本子的模范。
  • tapi/: tapi 是 OpenCV 3 的一个新特征,使用 cv::UMat 替代cv::Mat,达成 CPU 和 GPU 的演算使用统一的接口,不再须要显式地在 CPU 和 GPU 之间传递数据,方便开拓职员。
  • winrt/: Windows RT 平台的楷模,开采语言是微软的 C “方言”.

TesseractOCRiOS(完毕文字识别本事)
  Tesseract是时下可用的最可相信的开源OCLAND引擎,能够读取种种格式的图形并将她们调换到种种语言文本。而TesseractOCRiOS则是照准iOS平台封装的Tesseract引擎库。

samples/cpp/ 文件夹中的楷模介绍

  • 3calibration.cpp/: 同一时间标定三台水平放置的相机。

  • bagofwords_classification.cpp/: 使用图像检查评定达成简易的图像找寻效率。

  • bgfg_gmg.cpp/: 演示 GMG 背景检查测量试验算法的选拔办法。

  • bgfg_segm.cpp/: 演示高斯混合背景检验算法的运用方法。

  • brief_match_test.cpp/: 使用 B讴歌MDXIEF 特征值来合营两张图像。

  • build3dmodel.cpp/: 演示怎么着行使根基矩阵和特征值来创设三个维度模型。

  • calibration.cpp/: 完整的多用处标定程序。

  • calibration_artificial.cpp/: 在程序中生成三个伪造的相机,并拓宽标定。

  • camshiftdemo.cpp/: 读取实时的视频头数量,并演示基于均值偏移算法的录像追踪。

  • chamfer.cpp/: 使用 Chamfer 算法相称两副边缘图像。

  • cloning_demo.cpp/: 命令行格局的图像克隆。

  • cloning_gui.cpp/: 图形界面交互作用的图像克隆。

  • connected_components.cpp/: 查找并绘制图像中的连通区域。

  • contours2.cpp/: 查找并绘制图像中的概略。

  • convexhull.cpp/: 查找并绘制由点的聚合组成的凸包。

  • cout_mat.cpp/: 使用 cout 来输出各个格式化的 Mat 对象。

  • create_mask.cpp/: 演示如何创建黑白掩码图像。

  • dbt_face_detection.cpp/: 基于检查测量检验的人脸追踪代码。

  • delaunay2.cpp/: 通过鼠标人机联作式地转移 Delaunay 三角形。

  • demhist.cpp/: 演示直方图的用法。

  • descriptor_extractor_matcher.cpp/: 演示 features2d 检测框架的用法。

  • detection_based_tracker_sample.cpp/: 与 dbt_face_detection.cpp 类似。

  • detector_descriptor_evaluation.cpp/: 评估种种特色检测器和描述子。

  • detector_descriptor_matcher_evaluation.cpp/: 评估种种风味检查评定器和相称器。

  • dft.cpp/: 演示一幅图像的离散傅里叶转换。

  • distrans.cpp/: 显示边缘图像的间距调换值。

  • drawing.cpp/: 演示美术和文字展现效果。

  • edge.cpp/: 演示 Canny 边缘检查测量试验。

  • em.cpp/: 对自由变化的数总局进行 EM 聚类。

  • fabmap_sample.cpp/: 演示 FAB-MAP 图像检索算法。

  • facerec_demo.cpp/: 人脸识别。

  • fback.cpp/: 实时的 Farneback 光流追踪。

  • ffilldemo.cpp/: 演示 floodFill(卡塔尔 像素填充算法。

  • filestorage.cpp/: 演示种类化到表面文件,如yml、xml等。

  • fitellipse.cpp/: 将轮廓点相称到椭圆。

  • freak_demo.cpp/: 演示 FREAK 特征值的用法。

  • gencolors.cpp/: 演示 generateColors()。

  • generic_descriptor_match.cpp/: 基于 SULANDF 的两幅图像间的协作。

  • grabcut.cpp/: 演示 GrabCut 分割算法。

  • houghcircles.cpp/: 用霍夫算法检查测量检验圆。

  • houghlines.cpp/: 用霍夫算法检查实验直线。

  • hybridtrackingsample.cpp/: 混合追踪算法(Hybrid Tracker)的示范。

  • image.cpp/: 来回转变 cv::Mat 和 IplImage。

  • image_alignment.cpp/: 演示 findTransformECC() 函数。

  • image_sequence.cpp/: 使用 VideoCapture 对象读取类别帧。

  • imagelist_creator.cpp/: 创制图像列表到 xml 文件。

  • inpaint.cpp/: 使用鼠标交互作用地张开图像修补。

  • intelperc_capture.cpp/: AMD 感知计算设备相关的函数。

  • kalman.cpp/: 使用Carl曼滤波举行二维追踪。

  • kmeans.cpp/: Kmeans 聚类算法的示范。

  • laplace.cpp/: 拉普Russ边缘检查实验。

  • latentsvm_multidetect.cpp/: latentSVM 检测器。

  • letter_recog.cpp/: 字母识别。

  • linemod.cpp/: 基于 OpenNI 的体感设备选择。

  • lkdemo.cpp/: 演示Lukas-Kanade 光流法。

  • logpolar_bsm.cpp/: 演示 LogPolar 盲点模型。

  • lsd_lines.cpp/: LSD 线段检验。

  • matcher_simple.cpp/: SU锐界F 特征检验。

  • matching_to_many_images.cpp/: 一对多的特点检验。

  • meanshift_segmentation.cpp/: 演示基于均值漂移的色彩分割函数——meanShiftSegmentation(卡塔尔。

  • minarea.cpp/: 找出最小包围盒、包围圆。

  • morphology2.cpp/: 形态学图像管理。

  • npr_demo.cpp/: 演示种种非真实感渲染效果。

  • opencv_version.cpp/: 输出 OpenCV 库的版本号。

  • openni_capture.cpp/: 演示 OpenNI 相关的体感设备。

  • pca.cpp/: 基于 PCA 的人脸识别。

  • peopledetect.cpp/: 基于 cascade 或 hog 举行物体(人)检查测试。

  • phase_corr.cpp/: 演示 phaseCorrelate() 函数。

  • points_classifier.cpp/: 演示各样机器学习算法。

  • rgbdodometry.cpp/: 对纵深传感器如 Kinect 的数据举行拍卖。

  • segment_objects.cpp/: 实时地在摄像或相机镜头中检查评定前程物体。

  • shape_example.cpp/: 相比并招来形状。

  • shape_transformation.cpp/: 用 SUOdysseyF 特征值检查评定形状并扩充调换。

  • squares.cpp/: 检验图像中的方块形状。

  • starter_imagelist.cpp/: 四个 “hello worl” 性质的入门范例。

  • starter_video.cpp/: 另叁个 “hello worl” 性质的入门轨范。

  • stereo_calib.cpp/: 双眼视觉的标定。

  • stereo_match.cpp/: 总计左右视觉的图像的出入,生成点云文件。

  • stitching.cpp/: 演示图像拼接算法。

  • stitching_detailed.cpp/: 演示越多参数的图像拼接算法。

  • textdetection.cpp/: 实时场景中的文字定位与识别。

  • train_HOG.cpp/: 训练 HOG 分类器。

  • ufacedetect.cpp/: 人脸检查评定。

  • video_homography.cpp/: 使用 FAST 特征值来追踪平面物体。

  • videostab.cpp/: 演示 videostab 中逐个参数的用法。

  • watershed.cpp/: 演示著名的山峦图像分割算法。

本书程序代码及彩色图片下载:
http://www.sciencep.com/downloads/
https://github.com/ITpublishing

四、实战演示
创办叁个iOS项目

<div id="Section8">勘误</div>

  • P249 页,尾数第 5-6 行,分别有八个“图像中”多余。

用CocoPods导入上边三个库
由于OpenCV库文件非常大,所以时间会有一点久一点,耐烦等待正是。

<div id="Section9">作者的郁结</div>

  • 深拷贝 image.clone(卡塔尔(قطر‎ 和 copyTo 有哪些界别?不是一致的吧

podfile文件.png

<div id="Section10">下一步安插</div>

初写于 2014-04-05,未完待续。
首发于 Yimian Dai's Homepage卡塔尔国,转发请评释出处。

导入完结今后运转项目,会发觉报如下错误

Bitode报错.png

出于导入的库不扶助Bitcode机制,需求关闭,在工程->TA哈弗GETS->Build Setting-> Enable Bitcode设置为NO就ok。

关掉Bitcode.png

导入TesseractOCRiOS须要的语言包
  TesseractOCRiOS库中尚无自带的语言包,需求大家温馨手动导入,大家那边平昔到tesseract-ocr网址,tessdata正是大家需求运用的语言包。下载下来的语言包有400多兆。这里大家只必要用到越南语语言包,所以就只导入eng.traineddata就ok,其余的都删掉。

导入语言包种需求静心几点:
语言包要求放在tessdata目录下。TesseractOCRiOS中寻觅语言包是在tessdata目录下开展检索的,所以我们无法独立把eng.traineddata导入项目中,而急需放在tessdata目录下导入项目中。
将tessdata导入xcode项目,须要勾选Create folder refrences。上面已经涉嫌了语言包必要放在tessdata目录下,所以导入文本到xcode的时候必要创建文件夹的样式,并非创建组的方式。如下图:

导入tessdata文件夹的格局.png

成立叁个RecogizeCardManager用来治本人份证识别相关的代码。
鉴于OpenCV和TesseractOCRiOS库都以基于c 编写的,所以须要把RecogizeCardManager.m后缀的.m改成.mm

RecogizeCardManager.png

RecogizeCardManager中的代码

.h文件

import <Foundation/Foundation.h>@class UIImage;typedef void (^CompleateBlock)(NSString text);@interface RecogizeCardManager : NSObject/** 带头化一个单例** @return 再次来到一个RecogizeCardManager的实例对象/ (instancetype)recognizeCardManager;/** 根据居民身份许可证片取得居民身份证编号** @param cardImage 传入的身份ID照片* @param compleate 识别完毕后的回调*/- (void)recognizeCardWithImage:(UIImage *)cardImage compleate:(CompleateBlock)compleate;@end

.m文件

import "RecogizeCardManager.h"#import <opencv2/opencv.hpp>#import <opencv2/imgproc/types_c.h>#import <opencv2/imgcodecs/ios.h>#import <TesseractOCR/TesseractOCR.h>@implementation RecogizeCardManager (instancetype)recognizeCardManager { static RecogizeCardManager *recognizeCardManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ recognizeCardManager = [[RecogizeCardManager alloc] init]; }); return recognizeCardManager;}- (void)recognizeCardWithImage:(UIImage *卡塔尔(قطر‎cardImage compleate:(CompleateBlock卡塔尔(قطر‎compleate { //扫描身份ID图片,并开展预管理,定位号码区域图片并回到 UIImage *numberImage = [self opencvScanCard:cardImage]; if (numberImage == nil卡塔尔(قطر‎ { compleate(nilState of Qatar; } //利用TesseractOC大切诺基识别文字 [self tesseractRecognizeImage:numberImage compleate:^(NSString *numbaerText) { compleate(numbaerText); }];}//扫描身份ID图片,并拓宽预管理,定位号码区域图片并再次来到- (UIImage *)opencvScanCard:(UIImage 卡塔尔image { //将UIImage调换到Mat cv::Mat resultImage; UIImageToMat(image, resultImage卡塔尔; //转为灰度图 cvtColor(resultImage, resultImage, cv::COLO奥迪Q5_BG福特Explorer2GRAY卡塔尔; //利用阈值二值化 cv::threshold(resultImage, resultImage, 100, 255, CV_THRESH_BINAPAJEROY卡塔尔国; //腐蚀,填充(腐蚀是让天蓝点变大) cv::Mat erodeElement = getStructuringElement(cv::MORPH_RECT, cv::Size(26,26卡塔尔国State of Qatar; cv::erode(resultImage, resultImage, erodeElementState of Qatar; //轮廊检查评定 std::vector<std::vector<cv::Point>> contours;//定义贰个容器来累积全体格检查测到的轮廊 cv::findContours(resultImage, contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cvPoint(0, 0State of Qatar卡塔尔(قطر‎; //收取居民身份证号码区域 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 == 0State of Qatar { return nil; } //定位打响中标,去原图截取居民身份证号码区域,并调换到灰度图、举办二值化处理 cv::Mat matImage; UIImageToMat(image, matImage卡塔尔(قطر‎; resultImage = matImage(numberRect卡塔尔; cvtColor(resultImage, resultImage, cv::COLO悍马H2_BGR2GRAY); cv::threshold(resultImage, resultImage, 80, 255, CV_THRESH_BINARY); //将Mat转换成UIImage UIImage *numberImage = MatToUIImage(resultImage卡塔尔国; return numberImage;}//利用TesseractOC奥迪Q5识别文字- (void卡塔尔tesseractRecognizeImage:(UIImage *)image compleate:(CompleateBlock)compleate { 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]; //施行回调 compleate(tesseract.recognizedText卡塔尔(قطر‎; }State of Qatar;}

RecognizeCardViewController代码

传说版构造分界面

传说版构造分界面.png

.m文件

import "RecognizeCardViewController.h"#import "RecogizeCardManager.h"@interface RecognizeCardViewController ()<UINavigationControllerDelegate, UIImagePickerControllerDelegate>{ UIImagePickerController *imgagePickController;}@property (weak, nonatomic) IBOutlet UIImageView *imgView;@property (weak, nonatomic) IBOutlet UILabel *textLabel;- (IBAction)cameraAction:(id)sender;- (IBAction)photoAction:(id)sender;@end@implementation RecognizeCardViewController- (void)viewDidLoad { [super viewDidLoad]; self.imgView.contentMode = UIViewContentModeScaleAspectFit; imgagePickController = [[UIImagePickerController alloc] init]; imgagePickController.delegate = self; imgagePickController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal; imgagePickController.allowsEditing = YES;}- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated.}//拍照- (IBActionState of QatarcameraAction:(id卡塔尔sender { //判定是还是不是足以张开单反 if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]卡塔尔 { imgagePickController.sourceType = UIImagePickerControllerSourceTypeCamera; //设置录像头格局(拍照,录制录像)为雕塑 imgagePickController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto; [self presentViewController:imgagePickController animated:YES completion:nil]; } else { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"设备不能够开发相机" delegate:self cancelButtonTitle:@"知道了" otherButtonTitles: nil]; [alert show]; }}//相册- (IBAction)photoAction:(id)sender { imgagePickController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; [self presentViewController:imgagePickController animated:YES completion:nil];}#pragma mark - UIImagePickerControllerDelegate//适用获取具备媒体能源,只需决断财富类型- (void卡塔尔(قطر‎imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{ NSString *mediaType=[info objectForKey:UIImagePickerControllerMediaType]; UIImage *srcImage = nil; //剖断财富类型 if ([mediaType isEqualToString:@"public.image"]){ srcImage = info[UIImagePickerControllerEditedImage]; self.imgView.image = srcImage; //识别身份ID self.textLabel.text = @"图片插入成功,正在甄别中..."; [[RecogizeCardManager recognizeCardManager] recognizeCardWithImage:srcImage compleate:^(NSString *text) { if (text != nil) { self.textLabel.text = [NSString stringWithFormat:@"识别结果:%@",text]; }else { self.textLabel.text = @"请选拔照片"; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:@"照片识别战败,请选取清晰、未有复杂背景的身份ID照片重试!" delegate:self cancelButtonTitle:@"知道了" otherButtonTitles: nil]; [alert show]; } }]; } [self dismissViewControllerAnimated:YES completion:nil];}//步向拍照页面点击撤废开关- (void卡塔尔国imagePickerControllerDidCancel:(UIImagePickerController *)picker { [self dismissViewControllerAnimated:YES completion:nil];}@end

总结
  通过地点的尝试,该程序对地位证识其余正确率大致能够高达80%,剩下的十分之一重视在于图像的预管理,预管理程序是整套识别系统的关键所在。该系统的法规相近也适用于获取居民身份证上其余的消息,也足以行使于银行卡、车牌号等的辨认。最终针对贯彻的机能开展一步计算。
鉴定识别的精确率
默不作声在于腐蚀、收取居民身份证编号区域(轮廊提取)的算法那多少个关键点。腐蚀:
腐蚀的参数很关键,关于腐蚀的有的介绍,可以参见这篇文章 腐蚀与膨胀(Eroding and Dilating卡塔尔(قطر‎
抽取居民身份证号码区域的算法(轮廊提取):
具备的管理皆感觉了在图纸中一定到身份ID号码的区域,轮廊提取正是那般三个操作。筛选轮廊图的算法很注重不过也是个难题。作者从那篇博客iOS居民身份证号码识别中找到了思路。要提取居民身份证编号区域的轮廊,算法的法规正是该轮廊的增长幅度是负有中最宽的,且上升的幅度的尺寸必需高于高度的5倍。可是这么些算法仍旧存在不菲主题材料。不常或许图片背景相比复杂会潜移暗化到轮廊的检查实验,基于那么些主题材料:一方面能够经过对图纸的预管理来张开优化,减弱对检测居民身份证编号区域的纷扰
第二个方面就是优化算法。

识别速度
利用TesseractOCRiOS对比较清楚的文字举行辨认速度是超快的,笔者试过用一张未经处理的写着数字的图片来管理,识别速度低于5s。但通过二值图管理今后识别的进程就跌落了,小编以为能够对二值化管理后的图纸进一层处理,举例对二值图进行细化描出骨架,然后在对骨架做均匀的膨大处理,那样获得的身份ID编号大概会清楚超级多。

此处贴上多少个有关OpenCV的学习网址OpenCV官方学习文书档案OpenCV入门指南OPEN CV for iOS
该品种曾经开源在github RecognizeCard 上了,假如喜欢能够点个赞。有何难题能够留言,笔者也是率先次接触,一齐前进,大家加油。

文/wythetan(简书小编)原来的小说链接:http://www.jianshu.com/p/ac4c4536ca3e#作品权归作者全体,转发请联系小编得到授权,并标注“简书小编”。

本文由新葡京8455发布,转载请注明来源

关键词: