声明 本文暂时禁止任何形式的转载, 以下示例图片为了不侵犯个人行驶证隐私,全部做打码处理。
感想
做了近一个月的图形图像识别,从互联网,书籍上了解了最基础的图形图像知识以及处理知识。对图形图像可以说做了基本入门。基础算法是最重要的!这个为以后调整参数和自己分析图片帮助都很大。业务需求不一样,也许用同样的处理流程,调整不同的参数和逻辑就可以达到目的。目前有个现象,机器学习的人不多,机器学习还被分为例如语音识别,自然语言处理,图像识别等等,图像识别又有做各种业务的,所以能碰上业务要求一样的少之又少。所以互联网资料甚少,开源能商业用的产品基本不要想,3、4百人的群可能一天都超不多20句技术沟通,作者问问题几乎得不到回答。底层库是C/C++所写,没有这方面经验的人看代码,写代码会非常辛苦。这也是该技术门槛高,回报大的所在。因此学习机器学习人首先要有毅力,独立学习,独立解决问题,独立探索,开放性思维,与大家共勉之!
思路分析
一、我们常常会有各种证件来证明自己,比如出生证,护照。那么“行驶证如何证明自己是一张行驶证呢?” 这句话非常绕口,但实际就是这么一回事。随便一张照片如何判断是不是行驶证,在实际用户上传的照片中,用户最有可能上传的就是行驶证照片,本人照片,小猫小狗车照片等三大类。 我们发现行驶证最明显的特征是,在其最上方明确的写着“中华人民共和国机动车行驶证”字样,那么好, 如果照片上有这个字样,不就说明是一张行驶证了么!
二、因为一张用户上传的图可能会很大,比如1080x720的,再加上是彩色图,可能都有几百K,这个识别对人类眼睛来讲不会造成很大负担,你可能0.几秒就可以判断出到底是不是行驶证,但对于机器(电脑)来说,他只懂得0和1,如何判断?我们可以通过对图片做一系列处理挖掘其特征,分割我们感兴趣矩形区域,这样机器处理起来就相对容易很多了,下面会讲。
三、我们判断一个物体是 太阳?月亮? 是如何判别呢。是小时候我们还在上幼儿园时,老师指着 🌞 = 太阳 🌛 = 月亮 也就是图像+标签方式。机器就是小时候的我们,他不知道,他需要我们作为老师教授。SVM 支持向量机就是这个原理。我们把认识的过程叫训练,人类会把这个训练记在脑海里形成记忆片段,而机器会生成的叫训练模型,我们取个名字叫《 行驶证判断模型》 有了这个模型,机器下次看到图片是 会告诉我们,这到底是不是行驶证。
行驶证特征提取大概流程
1.原图
2.图像统一宽度,防止图片过小,影响现有参数,所以取相同宽度等比缩放
3.图像滤波(高斯低滤波 也叫高斯模糊)
4.灰度化处理
5.二值化(局部处理算法)
6.形态学操作 (闭操作)
7.取轮廓
8.去掉逻辑上不符合要求的矩形,做过滤
9.截取感兴趣区域并输出
接下来一步步解释采用这种方式处理图片的作用以及效果
原图展示
image.png原图统一宽度
image.png我设置的是宽度1000px 高度根据图片的比例自适应,这样的好处是避免一些图片极端过大或者过小,影响以后的参数,按理论上讲图片大小不会影响识别率,但是统一尺寸可以让参数更好调整。举个例子形态学闭操作的核大小参数如果适应宽度1000px的图像,如果放在宽度300px的图像上,那结果完全不是自己想要的。但是如果图像统一了宽度(或者高度)按比例缩放了,那么参数就容易调整的多。
高斯模糊
image.png高斯模糊主要是为了去掉图片上的噪点。噪点是什么呢? 可以想一下你在马路上打电话,这个时候周围环境的车的喇叭声,周围人说声音,小鸟叽叽喳喳的叫声对你听筒声音有了干扰,就是噪声。在相机成像过程中也会出现类似的噪点。
去掉噪点的好处是,忽略了图片上无用信息,减少了数据量。你可以认为去掉了图片中你不关心的像素。
具体讲解高斯滤波请看阮一峰讲的高斯滤波
话说阮大侠讲的也不是很清楚,其实滤波分为高低之分,高斯模糊叫高斯低通道滤波才准确的哈。详细了解滤波知识
代码
//! 图像滤波,处理噪点
cv::Mat image_filter;
int k_width = 7;
int k_height = 7 ;
// boxFilter(image, image_filter, -1,cv::Size(3,3));//方块滤波
// blur(image, image_filter, cv::Size(3, 3));//均值滤波
GaussianBlur(image, image_filter, cv::Size( k_width, k_height), 0, 0 );//高斯滤波
if( !image_filter.data ){
std::cout << "image is not exist" << std::endl;
return -1 ;
}
cv::imwrite(filter_name,image_filter);
cv::namedWindow("image", CV_WINDOW_AUTOSIZE);
cv::imshow("image_filter", image_filter);
cv::waitKey();
k_width
、 k_height
分别是高斯内核的宽和高,两个值可以不相等,但必须是正值,奇数。亦可为0
灰度化处理
image.png图像的灰度化即是将彩色图像转化成为灰度图像的过程成为图像的灰度化处理。彩色图像中的每个像素的颜色有R、G、B三个分量决定,而每个分量有255中值可取,这样一个像素点可以有1600多万(255255255)的颜色的变化范围。而灰度图像是R、G、B三个分量相同的一种特殊的彩色图像,其一个像素点的变化范围为255种,所以在数字图像处理种一般先将各种格式的图像转变成灰度图像以使后续的图像的计算量变得少一些。
OpenCV中代码实现
//! 转成灰度图并展示
cv::Mat grey;
cvtColor(image, grey, CV_BGR2GRAY);
if( !grey.data ){
std::cout << "image_gary is not exist" << std::endl;
return -1 ;
}
cv::imwrite(grey_name,grey);
cv::namedWindow("image", CV_WINDOW_AUTOSIZE);
cv::imshow("image_grey", grey);
cv::waitKey();
图片的灰度化的最有利的是颜色变少,数据量变少,提高计算效率。
但黑白图片的识别率会相应降低。举个例子,猫有很多品种,如果是彩色图片你可容易记住它的特征。但是如果是黑白图片,你下次再见到相同品种的猫时,恐怕你就不太容易辨认了。机器学习一样的道理。要时刻记住机器学习就是模拟人类的记忆,总结,判断,推测。
直方图均衡化 ✘
直方图如果有摄影的朋友肯定会知道,照片的对比度,曝光度,直方图就是展示了这一关系。直方图越均衡,像素分布及起伏越均匀则照片的颜色对比和反差越小。
处理后的图片
我们来看下代码
//! 直方图均衡化 用这个效果似乎不太好 暂时舍掉
cv::Mat image_hist;
equalizeHist(grey,image_hist);
if( !grey.data ){
std::cout << "image_hist is not exist" << std::endl;
return -1 ;
}
cv::imwrite(hist_name,image_hist);
cv::namedWindow("image", CV_WINDOW_AUTOSIZE);
cv::imshow("image_grey", image_hist);
cv::waitKey();
经过大量图片对比,我肉眼发现,很多图片不用直方图均衡化处理会更加的好。因为很多人在拍摄照片时,底部背景是黑色车座椅,或者方向盘之类的,总之会导致本来清晰的图片,整个都变的灰色了。这样二值化反而不太好。
!!!直方图均衡化暂时舍掉!!!
二值化
二值化是选定一个像素等级的阈值 0~255
之间,图片只存在0或者255 也就是生产黑白图,这样去把图像的里的物体轮廓展示出来。那么这个阈值到底选择哪个好呢,有一个现成的算法叫OTSU,它会根据图像中具体的灰度值选择一个合理值,处理结果看图
但是如果图片的有光照不均匀如图
image.png再经过OSTU算法进行二值化处理会是什么样子呢?如图
图片.png哇塞,怎么变成这样了。我的文字区域呢?我一番思考知道原因了,原来二值化做的是全局处理,但是这张图片正好有光照从图片上明显看出分隔,这样一个阈值就有些不合理了,似乎应该分块处理,是了,opencv想到这个情况了,提供了局部处理函数,我们来看下整个代码,包括全局处理的代码。
//! 二值化
int blockSize = 25;
int constValue = 10;
cv::Mat image_threshold;
// cv::threshold(image_hist , image_threshold ,0,255,CV_THRESH_OTSU); //整体二值化
cv::adaptiveThreshold(grey, image_threshold, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY_INV, blockSize, constValue);//局部二值化
cv::imwrite(threshold_name,image_threshold);
cv::imshow("image_threshold", image_threshold);
cv::waitKey();
换成局部二值化处理,效果好多了,问题解决。如图
图片.png形态学操作
形态学有诸如 开/闭/顶帽/黑帽/腐蚀/膨胀操作 ,形态学操作的目的是为了使得原本白色区域连成一片的效果,如下:
图片.png请忽略我黄色圆圈,这是我后来通过作图软件添加上去的,是为了更好说明,经过形态学闭操作处理后的图片,并不会产生黄圈
我们看到,白色区域就是我们之前想要的文字区域啊,真的是太好了! 已经找到有价值的区域位置了,这样我们在原图上截取有价值图片,然后计算机处理的数据进一步减少!速度更快!准确性更高!
当然,这种方式肯定会有一些区域不是我们想要的。比如左下角的区域,原图是红色水印位置。其实这个信息对于咱们来讲是无用信息,如果能把它去掉,不用说 对于咱们的识别速度+准确度又会提高了不少。
图片的水印是红色的,另外驾驶证背景注意看会有浅红色,如果使用HSV调整参数是可以找到红章部分的,但是每张图情况不一样这个参数调整必须是动态的,就要有另外限制条件,比如二值化后的图像是个矩形才去掉,等等。还有一种思路,红章肯定都是在标准化行驶证图像的左下角,在左下角做生长算法,找到合适种子点就可以完整的取到红章矩形,这俩种思路都可以继续往深入研究,如果其他朋友有思路,可以聊聊。
取轮廓
代码
vector<vector<Point> > contours;
findContours(morphology, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
我们做了取轮廓操作,同时为了直观看到效果,我加入了代码在原图上画轮廓,看下效果
image.png我在原图上画出了我们得到的所有矩形,我们想要的头部带有“中华人民共和国机动车行驶证”字样的矩形,那么其他矩形对我们来说就属于Negative类型图样。尽量的在合理逻辑下过滤,这个过滤条件有个度的把握,比如如果就针对这一张图片,通过Positive图片面积就能把其他矩形做到完全过滤,但是不同图片这个矩形几乎不能一样,所以这样你的这个流程意义就不大了。我们的目的是尽可能过滤掉明显不是Positive的矩形。所以有了下面的过滤参数设置。
if ( !(1.0 * rect.y > 100 || 1.0 * rect.width * rect.height < 510.0f || rect.x == 0 || rect.y == 0) ) {
outRects.push_back(rect);
cv::Mat tempImg;
zoom(rect).copyTo(tempImg);
string filename = folder_out + "/"+VLUtil::getFileName(file_path_,false)+"_"+VLUtil::int2str(i) + ".jpg";
files.push_back(filename);
imwrite(filename, tempImg);
}
这段代码的过滤条件是
- 矩形的y轴 >100的去掉,我参考了很多张图片,得出的数据
2.矩形的宽*高 = 面积 小于510 去掉,去掉了过小的矩形 -
矩形的开始坐标x,y是0的去掉
然后就得了相对少的矩形,如图所示
image.png
我们会发现剩下的矩形不多了,其中包含我们想要的特征 - 带有中华人民共和国机动车行驶证字样的矩形。OK!非常棒!
这些矩形就是SVM的训练样本,请继续关注下一篇文章,如果做SVM训练,完成行驶证的识别工作。
网友评论