OpenCV之轮廓检测(检测银行卡上的黑色磁条)

最近有需要做银行卡上黑色磁条的提取的工作。因为这是比较典型的轮廓检测问题。用DL的方法需要大量的标注数据集,所以想到用openCV来做。下面梳理一下流程:

前言

这篇博文的目的是应用计算机视觉和图像处理技术,展示一个银行卡上黑色磁条的基本实现。
需要注意的是,这个算法并不是对所有银行卡都有效,但会给你基本的关于应用什么类型的技术的直觉,这种感觉的积累对于解决工程问题来说,是有益的。

假设我们要检测下图银行卡中的黑色磁条:
《OpenCV之轮廓检测(检测银行卡上的黑色磁条)》
下面的代码是基于新版的opencv3.3的。
opencv3.x跟网上很多基于opencv2.x版本的api和调用都有不同了,注意调整。

代码放在这里:tomguluson92/card_detection
还有一个效果比较好的用canny算子得到的边缘。

解决思路

① 图片读取,转换成灰度图片

    import numpy as np
    import cv2
    img = cv2.imread(image) # image为你的图片地址
    grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换了灰度化

② 使用Scharr操作(指定使用ksize = -1)构造灰度图在水平和竖直方向上的梯度幅值表示。

    gradX = cv2.Sobel(grey, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
    gradY = cv2.Sobel(grey, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1)
    gradient = cv2.subtract(gradX, gradY)
    gradient = cv2.convertScaleAbs(gradient)

这一步,使用Scharr操作(指定使用ksize = -1)构造灰度图在水平和竖直方向上的梯度幅值表示。Scharr操作之后,我们从x-gradient中减去y-gradient,通过这一步减法操作,最终得到包含高水平梯度和低竖直梯度的图像区域。

得到的处理后图片如下:
《OpenCV之轮廓检测(检测银行卡上的黑色磁条)》

③ 注意黑色条形区域是怎样通过梯度操作检测出来的。下一步将通过去噪仅关注磁条区域。

# blur and threshold the image
    blurred = cv2.blur(gradient, (16, 16))
    retval, grey = cv2.threshold(blurred, 30, 255, cv2.THRESH_BINARY_INV)

这里要做的第一件事是使用16 * 16的内核对梯度图进行平均模糊,这将有助于平滑梯度表征的图形中的高频噪声。对我这个例子,越大越好。
此外,cv2.THRESH_BINARY_INV是跟cv.THRESH_BINARY正好相反的:作用是把像素点(pixel)低于30全转成0(黑色),其他的像素点(pixel)变成白色。

得到图片如图:
《OpenCV之轮廓检测(检测银行卡上的黑色磁条)》

④ 上面处理的结果发现磁条不是完整的,有缝隙,需要填充

为了消除这些缝隙,并使我们的算法更容易检测到条形码中的“斑点”状区域,我们需要进行一些基本的形态学操作:

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 10))
    closed = cv2.morphologyEx(grey, cv2.MORPH_CLOSE, kernel)

上面代码的含义是:我们首先使用cv2.getStructuringElement构造一个长方形内核kernel。这个内核的宽度大于长度,因此我们可以消除条形码中垂直条之间的缝隙。

然后把这个kernel扔进去,进行形态学操作。将上一步得到的内核应用到我们的二值图中,以此来消除竖杠间的缝隙。

《OpenCV之轮廓检测(检测银行卡上的黑色磁条)》

当然,现在图像中还有一些小斑点,不属于真正条形码的一部分,但是可能影响我们的轮廓检测。

让我们来消除这些小斑点:

closed = cv2.erode(closed, None, iterations=5)
grey = cv2.dilate(closed, None, iterations=5)

我们这里所做的是首先进行5次腐蚀(erosion),然后进行5次膨胀(dilation)。腐蚀操作将会腐蚀图像中白色像素,以此来消除小斑点,而膨胀操作将使剩余的白色像素扩张并重新增长回去。

如果小斑点在腐蚀操作中被移除,那么在膨胀操作中就不会再出现。

经过我们这一系列的腐蚀和膨胀操作,可以看到我们已经成功地移除小斑点并得到条形码区域。

可以看出,小的斑点被清除了。
《OpenCV之轮廓检测(检测银行卡上的黑色磁条)》

⑤ 最后,画出磁条的矩形轮廓

image, contours, hierarchy = cv2.findContours(grey.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 找到图像中的最大轮廓
c = sorted(contours, key=cv2.contourArea, reverse=True)[0]
rect = cv2.minAreaRect(c)
box = np.int0(cv2.boxPoints(rect))
cv2.drawContours(img, [box], -1, (0, 0, 255), 3)
cv2.imshow('Image', img)
cv2.waitKey()

这一部分比较容易,我们简单地找到图像中的最大轮廓,如果我们正确完成了图像处理步骤,这里应该对应于磁条区域。
然后用cv2.minAreaRect为最大轮廓确定最小边框。

最后就是显示检测到的磁条:
《OpenCV之轮廓检测(检测银行卡上的黑色磁条)》
虽然不是完全取到,但是思路是这样的。

cv2.findContours接收的参数中,第一个参数是要检索的图片,必须是为二值图,即黑白的(不是灰度图)。第二个参数是轮廓的检索模式:

  • cv2.RETR_EXTERNAL表示只检测外轮廓。
  • cv2.RETR_LIST检测的轮廓不建立等级关系。
  • cv2.RETR_CCOMP建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层。
  • cv2.RETR_TREE建立一个等级树结构的轮廓。

第三个参数为轮廓的近似方法:

  • cv2.存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
  • cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息

cv2.drawContours在图像上绘制轮廓。

第一个参数是指明在哪幅图像上绘制轮廓
第二个参数是轮廓本身,在Python中是一个list
第三个参数指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓
第四个参数是轮廓线条的颜色 ((0, 0, 255), (0, 255, 0), …)
第五个参数是轮廓线条的粗细 (1,2,3…)

总结

算法概要如下:

  • 计算x方向和y方向上的Scharr梯度幅值表示
  • 将x-gradient减去y-gradient来显示磁条区域
  • 模糊并二值化图像
  • 对二值化图像应用闭运算内核
  • 进行系列的腐蚀、膨胀
  • 找到图像中的最大轮廓,大概便是磁条区域

需要注意的是,该方法做了关于图像梯度表示的假设,因此只对水平磁条有效。

如果你想实现一个更加鲁棒的条形码检测算法,你需要考虑图像的方向,或者更好的,应用机器学习技术如Haar级联或者HOG + Linear SVM去扫描图像的磁条区域。

参考资料:

[1] 用 Python 和 OpenCV 检测图片上的条形码
[2] 使用Python和OpenCV检测图像中的物体并将物体裁剪下来
[3] 《Opencv2 Computer Vision Application Programming Cookbook》
[4] OpenCV-Python教程(4、形态学处理)

    原文作者:银行家问题
    原文地址: https://blog.csdn.net/g11d111/article/details/78094687
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞