opencv实战小结-银行卡号识别

实战1-银行卡号识别

项目来源:opencv入门

项目目的:识别传入的银行卡照片中的卡号

难点:银行卡上会有一些干扰项,如何排除这些干扰项,并且打印正确的号码是一个问题

最终效果如上图

实现这样的功能需要以下几个步骤:

  1. 首先必须有与银行卡中卡号数字基本一样的数字模板,将模板中的数字提取出来并存储起来(0-9)
  2. 将需要检测的银行卡图片中的数字提取出来
  3. 将银行卡的数字与模板数字一一对比,最终找到一个匹配度最高的数字,并把数字标注上

整个思路很简单,但是难点就在于如何将图片处理得更加容易让计算机识别数字,所以整个项目要围绕着图片得的处理来做

第一步-提取数字模板

这是事先准备好的数字模板

接下来要将图片中的数字都找到,也就是找到各个数字在整个图片上的像素点坐标(轮廓)

首先得到图片的灰度图,再进行二值化处理(这一切都是为了让图片中的数字更易于识别)

py 复制代码
# 灰度图
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值图像
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]#超过阈值部分取maxval ( 最大值 ),否则取0

然后会得到这样的图像

好了,现在图片已经很清晰了,不需要再进行其它的处理了,直接将其提取

那怎么提取呢?

可以通过cv2.findContours()找到数字的轮廓

函数 cv2.findContours() 有三个参数,第一个是输入图像,第二个是 轮廓检索模式,第三个是轮廓近似方法。返回值有三个,第一个是图像,第二个 是轮廓,第三个是(轮廓的)层析结构。轮廓(第二个返回值)是一个 Python 列表,其中存储这图像中的所有轮廓。每一个轮廓都是一个 Numpy 数组,包 含对象边界点(x,y)的坐标。

注意新版本中这个api的返回值有变化

返回两个参数contours和 hierarchy,contours就是每个数字的轮廓数组,包含边界点的坐标

其中cv2.RETR_EXTERNAL是获取外轮廓

python 复制代码
contours, hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

接下来可以将轮廓画出来看看

会用到cv2.drawContours()函数

函数 cv2.drawContours() 可以被用来绘制轮廓。它可以根据你提供 的边界点绘制任何形状。它的第一个参数是原始图像,第二个参数是轮廓,一 个 Python 列表。第三个参数是轮廓的索引(在绘制独立轮廓是很有用,当设置 -1时绘制所有轮廓)。接下来的参数是轮廓的颜色和厚度等。

python 复制代码
cv2.drawContours(img,contours,-1, (0, 0, 255), 3)

看下效果

好,现在轮廓都找到了,并且我们也有了轮廓的坐标,这个时候我们应该将每个数字的像素点位置都存起来(并不是将图片分割!,整个图片仍然没有任何变化)

好,现在有一个要注意的点,那就是我们在上面得到的contours数组并不是按图片中各个数字从左到右排列的,也就是说数组中第一个坐标可能是图片中8的坐标,那这个时候我们就必须对数组进行排序,排序顺序就是从左到右存

那排序怎么实现呢,其实就是根据x坐标从小到大排序就行了

排完序之后,contours中0下标存的就是数字0的模板,这里很好的利用了数组下标的优点

好的,排序完之后,我们就可以来存这个数字的模板了

思路是遍历contours数组,得到每个模板的坐标以及宽高,利用x+w就能得到图片的x轴范围,y+h就能得到y轴的范围,把他们存起来就得到一个数字的模板了

python 复制代码
digits = {}
#遍历每一个轮廓
for (i,c) in enumerate(contours):
    #计算外接矩形并resize合适的大小
    (x, y, w, h) = cv2.boundingRect(c)
    # cv2.rectangle(img,(x,y),(x + w, y + h),(0, 0, 255), 2)
    roi = ref[y:y + h, x:x + w]
    # 第二个参数是输出图像的宽高
    roi = cv2.resize(roi, (57, 88))
    # 每一个数字对应每一个模板
    digits[i] = roi

至此,我们项目的第一步就完成了

接下来就是将要检测的图像中的数字提取出来,其实整个提取思路都是一样的,但是银行卡的图像比我们的模板往往更加复杂,所以我们要对图片增加一些处理的步骤

跟着上面的来说,我们对复杂图片的处理需要引入卷积核,这里我们定义两个卷积核

python 复制代码
# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

一个是9x3的矩阵,一个是5x5

下面对图像进行处理,老规矩,取灰度图

然后进行礼帽处理,目的是为了突出更明亮的区域

python 复制代码
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)

接下来再用 Sobel核子对图片进行卷积,目的的为了得到图像梯度,也就是边缘检测

我们现在要做的是把可能为数字的区域都找出来

python 复制代码
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")

上图看上去更加模糊了,但是数字和非数字区域的明亮度变了

好的,接下来可以通过闭操作(先膨胀,再腐蚀),将数字连起来(是为了最后找到数字区域,因为卡号是4个数字连在一起的,我们把4个数字的区域找出来)

变成这样

再来一次阈值操作

python 复制代码
thresh = cv2.threshold(gradX, 0, 255,
                       cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

变成这样

矩形区域好像白色没有填满,再来一次闭操作

ok了,现在疑似数字的区域都很明显了吧,那下一步就是将这个区域进行排除,找到真正为银行卡号的区域,其他的区域就不要了

那怎么做呢?我们先把他们的轮廓都找出来,然后判断这些轮廓的宽度,符合银行卡号区域宽的的留下,不符合的去掉就可以了

python 复制代码
# 计算轮廓
contours_, hierarchy_ = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = contours_
cur_img = image.copy()
cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 3)
cv_show('img', cur_img)
locs = []
# 遍历轮廓
for (i, c) in enumerate(cnts):
    # 计算矩形
    (x, y, w, h) = cv2.boundingRect(c)
    ar = w / float(h)

    # 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
    if ar > 2.5 and ar < 4.0:

        if (w > 40 and w < 55) and (h > 10 and h < 20):
            # 符合的留下来
            locs.append((x, y, w, h))

得到卡号轮廓后,同样对其从左至右排序

好了,那接下来干嘛呢,我们刚刚得到的是四个数字组成的区域的轮廓,这个时候我们应该遍历这些区域,得到里面的四个数字的轮廓

同样也是个遍历操作

python 复制代码
for (i, (gX, gY, gW, gH)) in enumerate(locs):
     groupOutput = []
    # 根据坐标提取每一个组
    group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
    cv_show('group', group)

会得到四个这样的组

然后就获取这个组的轮廓,就像第一步骤一样,将数字提取出来就可以了

python 复制代码
#计算每一组的轮廓
    digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    # 从左到右排序
    digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0]

好的,接下来就是最重要的第三部操作了,将模板与上面得到的数字匹配,找到匹配度最高的那个模板数字就是我们要找的数字了

python 复制代码
 #计算每一组的每一个数值
    for c in digitCnts:
        # 找到当前数值的轮廓,resize成合适的的大小
        (x, y, w, h) = cv2.boundingRect(c)
        roi = group[y:y + h, x:x + w]
        roi = cv2.resize(roi, (57, 88))
        # cv_show('roi', roi)

        # 计算匹配得分
        scores = []
        for (digit, digitROI) in digits.items():
            # 模板匹配
            result = cv2.matchTemplate(roi, digitROI,cv2.TM_CCOEFF)
            # print('result',result)
            # 获取匹配度最高的数值
            (_, score, _, _) = cv2.minMaxLoc(result)
            scores.append(score)
        print("scores",scores)
        # 得到最合适的数字
        groupOutput.append(str(np.argmax(scores)))
        print('groupOutput',groupOutput)

完成上述步骤之后,我们的groupOutput就存放了我们识别出来的银行卡号了,我们只需要在图片上将卡号绘制出来就可以了

python 复制代码
  # 画出来
    cv2.rectangle(image, (gX - 5, gY - 5),(gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
    cv2.putText(image, "".join(groupOutput), (gX, gY - 15),cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
    # 得到结果
    output.extend(groupOutput)

最终效果如下


好了,以上就是此小项目的实现过程

总结:这是我学cv的第一个小实战项目,确实感觉蛮有意思的,学之前觉得这个东西很神奇,学习之后会发现其实一切都是按照逻辑一步步来的,没有那么"高大上",继续努力吧

相关推荐
HPC_fac130520678161 小时前
以科学计算为切入点:剖析英伟达服务器过热难题
服务器·人工智能·深度学习·机器学习·计算机视觉·数据挖掘·gpu算力
安静读书4 小时前
Python解析视频FPS(帧率)、分辨率信息
python·opencv·音视频
小陈phd4 小时前
OpenCV从入门到精通实战(九)——基于dlib的疲劳监测 ear计算
人工智能·opencv·计算机视觉
如若1239 小时前
主要用于图像的颜色提取、替换以及区域修改
人工智能·opencv·计算机视觉
加密新世界11 小时前
优化 Solana 程序
人工智能·算法·计算机视觉
lsjweiyi11 小时前
极简AI工具箱网站开源啦!
opencv·开源·微信支付·支付宝支付·百度ai·极简ai工具箱·ai图像处理
WeeJot嵌入式14 小时前
OpenCV:计算机视觉的瑞士军刀
计算机视觉
思通数科多模态大模型14 小时前
10大核心应用场景,解锁AI检测系统的智能安全之道
人工智能·深度学习·安全·目标检测·计算机视觉·自然语言处理·数据挖掘
学不会lostfound14 小时前
三、计算机视觉_05MTCNN人脸检测
pytorch·深度学习·计算机视觉·mtcnn·p-net·r-net·o-net
Jack黄从零学c++15 小时前
opencv(c++)---自带的卷积运算filter2D以及应用
c++·人工智能·opencv