OpenCV项目实战——数字识别代码及食用指南
源代码
https://gitee.com/zirui-shu/programs
进入网站下载IDidentify文件夹。
项目前瞻
本项目综合使用了python语言OpenCV框架的各个处理手段,以达到从图片银行卡中识别出卡号的目的,结果图如下:
以及输出台输出的卡号:
而在文章之后的部分,我会详细的介绍项目整个实现的流程方法,以及适用于其他情况时的代码改进建议,但不会讲解使用函数的原理,若有兴趣请自行查阅资料。
项目流程
1.数字模板的处理
进行数字识别肯定需要0~9的数字模板。在此文件中,我已经将所有模板一一保存至文件夹中,若是将此代码应用于其他识别功能,可以自行从样例图片中提取。
在拿到了数字模板之后,再对模板依次进行如下操作:
1.灰度处理
将读取的模板灰度处理,降低复杂度。
2.二值化
突出数字的特征,去掉多余背景的影响。
3.寻找轮廓
找到模板图片的数字,提取它们的特征并存储起来。
2.待识别图像的处理
对于读取的待识别图像,依次进行如下处理。
1.灰度化
同上文,降低复杂度。
2.高斯模糊
减少图片中噪声数据的影响。
3.礼帽操作
突出明亮的区域,便于卡号提取。
4.Sobel算子梯度计算
划分出边缘,便于卡号提取。
5.闭操作
连接数字相邻的区域,便于后续划分出卡号的区域。
6.寻找轮廓
找到卡号所对应的号码块,并分成一个个数字便于模板匹配。
7.模板匹配
将对应的数字找出来并存储,输出最终卡号。
代码改进
接下来,我会详细讲解在数字识别中可能出现的各个问题及可能的解决方案,便于提升项目的鲁棒性。
1.图片的选取
对于模板图片而言,最好选择正立且正好包含模板数字的图片,便于后续的特征提取及模板匹配工作;同理,待识别图片也是如此,并且最好统一大小、视角,这样才能提高准确率。如我所写的同意大小标准:
# 把图片调成长1300,宽1000image = cv2.resize(image, (1300, 1000))
2.模板轮廓并不对应
按照常理而言,我们希望的是每个模板都提取到一个轮廓,并且这个轮廓就是模板数字的轮廓。但是有些时候,模板图片的背景会影响轮廓的识别,这些噪声数据识别出来的模板会覆盖digits数组中原本需要的模板轮廓。那么,就如我在代码中所写的:
# 过滤小面积和大面积轮廓area = cv2.contourArea(cnt)if not (100 < area):continue# 过滤异常长宽比(x, y, w, h) = cv2.boundingRect(cnt)aspect_ratio = w / float(h)if not (0.5 < aspect_ratio < 0.8):continue
对于所寻找到的轮廓图片,进行一个筛选,调整area及aspect_ratio的阈值,可以很好的帮助我们得到想要的模板轮廓。当然,对于不同的图片,需要不同的阈值,需根据实际情况调整。
3.卷积核尺寸选择
我在代码中定义了三个这样的变量:
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
tempKernal = cv2.getStructuringElement(cv2.MORPH_RECT, (14, 14))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 17))
分别对应了三个操作的卷积核矩阵参数。对于rectKernal,这个礼帽操作的参数而言,尺寸越大那么进行操作位置位置就会更受周围因素影响,这个几乎不用改。对于tempKernal及sqKernal,是闭操作的参数,先来看看操作后的图片:
其中画上红圈的就是我们要找的卡号的位置,也是我们需要注意的区域。首先观察相邻数字是否连接,即红圈部分是否连成一块了,若是没有就需要加大参数尺寸;再看数字块之间是否连接,及相邻红圈是否连接,若是连接则需减小尺寸。若是调整之后还是无法做到上图的样子,或许可以调整图片来源。
4.数字检测时混进其他板块
就如上一幅图所展示的,除了数字块还有其他的轮廓会被检测出来。这个时候就需要做一个筛选,如我所写的:
# 遍历轮廓for (i, c) in enumerate(cnts):(x, y, w, h) = cv2.boundingRect(c)ar = w / float(h)if 1.6 < ar < 3.0:if (100 < w < 200) and (50 < h < 100):locs.append((x, y, w, h))
对ar与w、h值做一个阈值检测,过滤掉不需要的检测区域,当然阈值还是需要根据实际情况调整。
而出乎意料的是,有些区域跟检测区域具有相似外形,那么还有另外一层处理:
if scores[max] > 0.7:groupOutput.append(str(max))if groupOutput != []:cv2.rectangle(image, (gx - 5, gy - 5), (gx + gw + 5, gy + gh + 5), (0, 0, 255), 2)cv2.putText(image, "".join(groupOutput), (gx, gy - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)image_show(image)output.extend(groupOutput)
可以输出后续模板操作的分数,再根据匹配的分数在调整分数最小阈值,得到正确的号码。