• Opencv实现信用卡识别


    1.前置知识点

    (1)图像的二值化

    https://mydreamambitious.blog.csdn.net/article/details/125249121

    (2)Opencv中获取卷积核

    https://mydreamambitious.blog.csdn.net/article/details/125265838

    (3)Opencv中绘制直线,矩形,圆,椭圆,多边形(包括多边形填充),绘制文本

    https://mydreamambitious.blog.csdn.net/article/details/125392536

    (4)Opencv中的findContours()和drawContours()

    https://mydreamambitious.blog.csdn.net/article/details/125339604

    (5)Opencv中的形态字梯度,顶帽,黑帽

    https://mydreamambitious.blog.csdn.net/article/details/125315200

    (6)Sobel(索贝尔)

    https://mydreamambitious.blog.csdn.net/article/details/125227338

    (7)Opencv中的开运算和闭运算操作讲解

    https://mydreamambitious.blog.csdn.net/article/details/125298061

    (8)模版匹配

    https://blog.csdn.net/weixin_42081389/article/details/87935735


    2.正文

    (1)整体实现思路

    a.首先要知道一张信用卡上面不仅仅是信用卡号,而且还有其他的标志和数字,所以为了提取信用卡上面的卡号,那么就需要将除卡号之外的都得从图像中去除或者通过设置阈值的方式排除掉(轮廓的周长和轮廓的面积)。

    b.但是要找到图像中的数字和其他标志的轮廓,需要对查找轮廓的函数有所了解,而查找轮廓之前也需要对图像进行二值化处理和灰度处理,这是处理图像的基本方式。

    c.信用卡号是四位一组,如果将这四位数画成一个轮廓,但是数字之间是有小空隙的,为了处理这种小空隙,使得这个轮廓成为一个全白(便于后面排除其他轮廓),那么就需要进行腐蚀和膨胀操作了,去除大图像中的小图像。

    d.做完上面的大概之后基本上就可以从图像中通过设置阈值的方式,将数字的轮廓给提出来,提出数字的轮廓同时也得到了数字在应用卡上的坐标,通过坐标和轮廓的高宽就可以从灰度图中提取感兴趣区域(也就是数字的区域).

    e.最后通过模版匹配的方式找到对应的数字并显示,而这里的模版以什么作为模版呢?就是给定义一张图,从0-9的数字,信用卡上的数字匹配对应与这个模版上的数字,所以在对信用卡处理之前需要对模版图像进行处理。大概的思路就是这样。
    在这里插入图片描述

    (2)实现步骤

    (1)读取模版匹配图像

    #按比例缩放图像
    def ScalePic(img):
        height,width,channel=img.shape
        height=int(height*(300/width))
        img=cv2.resize(img,dsize=(300,height))
        return img
    
    #读取模版图像
    img=cv2.imread('ocr_a_reference.png')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    (2)图像的灰度处理和二值化处理

    #对模版图进行灰度处理和二值化处理
    gray=cv2.cvtColor(imgSize,code=cv2.COLOR_BGR2GRAY)
    ret,threshold=cv2.threshold(src=gray,thresh=10,maxval=255,type=cv2.THRESH_BINARY)
    
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    (3)查找模版中数字的轮廓和绘制轮廓

    #查找模版中数字的轮廓和绘制轮廓
    ret,contours_,hierarch=cv2.findContours(image=threshold,mode=cv2.RETR_EXTERNAL,method=cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(image=img,contours=contours_,contourIdx=-1,
                     color=(0,255,0),thickness=3)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    (4)根据绘制的轮廓的坐标对其进行排序

    #根据绘制的轮廓的坐标对其进行排序,得到0-9的数字,返回排序之后的每个数字的左上角坐标和
    #绘制的矩形的长宽
    contours_2=contours.sort_contours(contours_1,method='left-to-right')
    #利用字典的形式存储数字对应的模版图像中的数字
    dictRoi={}
    for step,c in enumerate(contours_2):
        (x,y,w,h)=cv2.boundingRect(c)
        roi=threshold[y:y+h,x:x+w]
        dictRoi[step]=roi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (5)读取信用卡图片

    注意卷积核大小的选取。这个直接决定你输出的效果好不好。

    #获取卷积核
    kernel=cv2.getStructuringElement(shape=cv2.MORPH_RECT,ksize=(7,1))
    #读取要检测的信用卡图片
    imgCard=cv2.imread('images//credit_card_01.png')
    #按长宽比缩放图片
    imgCardSize=ScalePic(imgCard)
    imgCardGray=cv2.cvtColor(src=imgCardSize,code=cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(imgCardGray, 0, 255,
    	cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    (6)顶帽操作

    思考:为什么要进行顶帽操作?
    顶帽=原图-开运算,其本身的作用就是得到大图像外的小图像;由于信用卡上的数字和标志都比较少,而如果我们直接进行开运算的话,那么导致最后图像上的数字和标志直接被腐蚀掉,但是又想要把图像中的其他小图(无用的小图)去除,那么首先进行开运算再使用原图减去开运算之后的图即可达到目的。

    #进行顶帽操作
    imgToPhat=cv2.morphologyEx(src=thresh,op=cv2.MORPH_TOPHAT,
                               kernel=kernel,iterations=1)
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    (7)进行索贝尔算子

    进行索贝尔算子的主要原因是因为我们的信用卡号是水平的,所以对X方向求梯度即可,结果输出的是一张二值化的图像。

    imgCardGradX=cv2.Sobel(src=imgToPhat,ddepth=-1,dx=1,dy=0,ksize=-1)
    
    • 1

    在这里插入图片描述

    (8)进行闭运算操作

    #进行闭运算操作,主要是为了将数字之间的空隙连成一片
    imgCardClosed=cv2.morphologyEx(src=imgCardGradX,op=cv2.MORPH_CLOSE,kernel=kernel,iterations=2)
    
    #对信用卡号进行轮廓查找
    ret_,contours_3,hierarchy=cv2.findContours(image=imgCardClosed,mode=cv2.RETR_EXTERNAL,
                                               method=cv2.CHAIN_APPROX_SIMPLE)
    #对信用卡号进行绘制轮廓
    cv2.drawContours(image=imgCardSize,contours=contours_3,contourIdx=-1,
                     color=(0,255,0),thickness=3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    在这里插入图片描述

    (9)筛选出符合要找的目标

    #筛选出卡号轮廓
    LocateCard=[]
    for step,c in enumerate(contours_3):
        (x,y,w,h)=cv2.boundingRect(c)
        #根据绘制的卡号轮廓,可以看到宽度与长度比有一定的规律
        rate=w/float(h)
        #进行筛选
        if rate>2.5 and rate<4.0:
            if (w>40 and w<55) and (h>10 and h<20):
                LocateCard.append((x,y,w,h))
    
    LocateCard=sorted(LocateCard,key=lambda x:x[0])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (10)遍历轮廓中的每个数字

    #遍历每个轮廓中的数字
    for (step,(gX,gY,gW,gH)) in enumerate(LocateCard):
        groupOutput=[]
    
        #提取每一个卡号的轮廓
        group=imgCardGray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
        # 预处理
        group = cv2.threshold(group, 0, 255,
                              cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
        # 计算每一组的轮廓
        group_, digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
                                                        cv2.CHAIN_APPROX_SIMPLE)
        digitCnts = contours.sort_contours(digitCnts,
                                           method="left-to-right")[0]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    (11)找到遍历的每个数字进行模版匹配找到最可能的数字

    # 计算每一组中的每一个数值
        for c in digitCnts:
            # 找到当前数值的轮廓,缩放成和之前模版同样大小的图
            (x, y, w, h) = cv2.boundingRect(c)
            roi = group[y:y + h, x:x + w]
            roi = cv2.resize(roi, (57, 88))
    
            # 计算和模版匹配的相似度
            scores = []
    
            # 在模板中计算每一个得分
            for (dictRoi, digitROI) in dictRoi.items():
                # 模板匹配相似度
                result = cv2.matchTemplate(roi, digitROI,
                                           cv2.TM_CCOEFF)
                (_, score, _, _) = cv2.minMaxLoc(result)
                scores.append(score)
    
            # 得到最合适的数字
            groupOutput.append(str(np.argmax(scores)))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (12)输出结果

    # 将结果绘制矩形框标出
        cv2.rectangle(imgCardSize, (gX - 5, gY - 5),
                      (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
        cv2.putText(imgCardSize, "".join(groupOutput), (gX, gY - 15),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
    
        # 最终的结果
        Output.extend(groupOutput)
    
    print("Credit Card #: {}".format("".join(Output)))
    cv2.imshow("Image", imgCardSize)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    在这里插入图片描述

    (13)整体代码

    import os
    import cv2
    import cvzone
    import numpy as np
    from imutils import contours
    
    #按比例缩放图像
    def ScalePic(img):
        height,width,channel=img.shape
        height=int(height*(300/width))
        img=cv2.resize(img,dsize=(300,height))
        return img
    
    #读取模版图像
    img=cv2.imread('ocr_a_reference.png')
    
    #对模版图进行灰度处理和二值化处理
    gray=cv2.cvtColor(img,code=cv2.COLOR_BGR2GRAY)
    ret,threshold=cv2.threshold(src=gray,thresh=10,maxval=255,type=cv2.THRESH_BINARY_INV)
    
    #查找模版中数字的轮廓和绘制轮廓
    ret_1,contours_1,hierarch=cv2.findContours(image=threshold,mode=cv2.RETR_EXTERNAL,method=cv2.CHAIN_APPROX_SIMPLE)
    cv2.drawContours(image=img,contours=contours_1,contourIdx=-1,
                     color=(0,255,0),thickness=3)
    
    #根据绘制的轮廓的坐标对其进行排序,得到0-9的数字,返回排序之后的每个数字的左上角坐标和
    #绘制的矩形的长宽
    contours_2,ret_2=contours.sort_contours(contours_1,method='left-to-right')
    #利用字典的形式存储数字对应的模版图像中的数字
    digits={}
    for step,c in enumerate(contours_2):
        (x,y,w,h)=cv2.boundingRect(c)
        roi=threshold[y:y+h,x:x+w]
        roi=cv2.resize(src=roi,dsize=(57,88))
        digits[step]=roi
    
    #获取卷积核
    kernel=cv2.getStructuringElement(shape=cv2.MORPH_RECT,ksize=(7,1))
    #读取要检测的信用卡图片
    imgCard=cv2.imread('images//credit_card_05.png')
    #按长宽比缩放图片
    imgCardSize=ScalePic(imgCard)
    cv2.imshow('img',imgCardSize)
    imgCardGray=cv2.cvtColor(src=imgCardSize,code=cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(imgCardGray, 0, 255,
    	cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    #进行顶帽操作
    imgToPhat=cv2.morphologyEx(src=thresh,op=cv2.MORPH_TOPHAT,
                               kernel=kernel,iterations=1)
    #进行索贝尔算子
    imgCardGradX=cv2.Sobel(src=imgToPhat,ddepth=-1,dx=1,dy=0,ksize=-1)
    
    #进行闭运算操作,主要是为了将数字之间的空隙连成一片
    imgCardClosed=cv2.morphologyEx(src=imgCardGradX,op=cv2.MORPH_CLOSE,kernel=kernel,iterations=2)
    
    #对信用卡号进行轮廓查找
    ret_,contours_3,hierarchy=cv2.findContours(image=imgCardClosed,mode=cv2.RETR_EXTERNAL,                                           method=cv2.CHAIN_APPROX_SIMPLE)
    #对信用卡号进行绘制轮廓
    cv2.drawContours(image=imgCardSize,contours=contours_3,contourIdx=-1,
                     color=(0,255,0),thickness=3)
    #筛选出卡号轮廓
    LocateCard=[]
    for step,c in enumerate(contours_3):
        (x,y,w,h)=cv2.boundingRect(c)
        #根据绘制的卡号轮廓,可以看到宽度与长度比有一定的规律
        rate=w/float(h)
        #进行筛选
        if rate>2.5 and rate<4.0:
            if (w>40 and w<55) and (h>10 and h<20):
                LocateCard.append((x,y,w,h))
    LocateCard=sorted(LocateCard,key=lambda x:x[0])
    #输出保存结果
    Output=[]
    #遍历每个轮廓中的数字
    for (step,(gX,gY,gW,gH)) in enumerate(LocateCard):
        groupOutput=[]
    
        #提取每一个卡号的轮廓
        group=imgCardGray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
        # 预处理
        group = cv2.threshold(group, 0, 255,
                              cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
        # 计算每一组的轮廓
        group_, digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,
                                                      cv2.CHAIN_APPROX_SIMPLE)
        digitCnts = contours.sort_contours(digitCnts,
                                           method="left-to-right")[0]
        # 计算每一组中的每一个数值
        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))
            # 计算匹配得分
            scores = []
            # 在模板中计算每一个得分
            for (digit, digitROI) in digits.items():
                # 模板匹配
                result = cv2.matchTemplate(roi, digitROI,
                                           cv2.TM_CCOEFF)
                (_, score, _, _) = cv2.minMaxLoc(result)
                scores.append(score)
    
            # 得到最合适的数字
            groupOutput.append(str(np.argmax(scores)))
        # 画出来
        cv2.rectangle(imgCardSize, (gX - 5, gY - 5),
                      (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
        cv2.putText(imgCardSize, "".join(groupOutput), (gX, gY - 15),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
        # 得到结果
        Output.extend(groupOutput)
    
    # 打印结果
    print("Credit Card #: {}".format("".join(Output)))
    cv2.imshow("Image", imgCardSize)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    if __name__ == '__main__':
        print('Pycharm')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120

    3.参考资料

    参考视频链接:https://www.bilibili.com/video/BV1vv4y1P75B?spm_id_from=333.337.search-card.all.click&vd_source=b2eaaddb2c69bf42517a2553af8444ab

  • 相关阅读:
    KMM 入门(七)处理 HTTP 网络请求
    Java NIO全面详解(看这篇就够了)
    Unity UGUI的ScrollRect(滚动视图)组件的介绍及使用
    Java基础final,finally,finalize的区别
    WSL Ubuntu 22.04.2 LTS 安装paddlepaddle-gpu==2.5.1踩坑日记
    【电商】电商后台设计—优惠券
    Go采集代理框架
    Weblogic SSRF漏洞
    linux实用命令
    WPF使用CommunityToolkit.Mvvm开发项目-App.xaml.cs配置
  • 原文地址:https://blog.csdn.net/Keep_Trying_Go/article/details/125696328