C/C++教程

Opencv-项目实战:信用卡数字识别

本文主要是介绍Opencv-项目实战:信用卡数字识别,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

本篇博客的学习视频: 基于python的Opencv项目实战-09项目实战-信用卡数字识别

文章目录

  • 实现与分析
  • 一、代码步骤解析
  • 从新测试
  • 总结


实现与分析

  • 信用卡
    在这里插入图片描述

输出序列:4000 1234 5678 9010

  • 如何实现?

使用模板匹配

在这里插入图片描述

选择合适的模板,例如上图中的两幅图中的8的模样要是相同的,6、1等数字都要相同才可以称作一个模板。

  • 现在有模板了,那如何单独拿出模板中的数字呢?

轮廓检测

在这里插入图片描述

具体步骤

  1. 对模板和输入图像进行轮廓检测,一般得到两个轮廓:外轮廓、内轮廓。本项目选择外轮廓
  2. 得到轮廓之后需要得到当前检测的轮廓的外接矩形。(模板和输入图像都需要)
  3. 使用信用卡中的数字对模板中的数字进行for循环(10次:一共10个数字),匹配过程中需要resize(保证外接矩形大小相同)。
  4. 需要过滤信用卡中的不符合数字的轮廓

一、代码步骤解析

  • 项目源码

关于这个项目的源码网络上有很多,在此给大家一个链接:python OpenCV 信用卡数字识别

  • 步骤详细解析

我的步骤和别人的步骤不同,我是将所有代码写在一个文件,具体看代码中的调用就会发现

import cv2
import numpy as np
# 对轮廓进行排序
def sort_contours(cnts, method="left-to-right"):
    reverse = False  # 正序排列
    i = 0
 
    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True # 倒叙排列
 
    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1
    
    #用一个最小的矩形,把找到的形状包起来x,y,h,w
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]
#     print(boundingBoxes)
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
    print('i=',i)
    return cnts,boundingBoxes

zip(*sorted(zip(cnts, boundingBoxes),key=lambda b: b[1][i], reverse=reverse))

  • 这句代码有疑问的看这篇文章:python-zip()函数、lambda、map的单独与结合使用
    这篇文章也是我写的,因为我第一次看到这句代码时也是不懂得,自己就查资料总结在一起

在这里插入图片描述

# 重新调整大小
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    dim = None
    (h, w) = image.shape[:2]
    if width is None and height is None:
        return image
    if width is None:
        r = height / float(h)
        dim = (int(w * r), height)
    else:
        r = width / float(w)
        dim = (width, int(h * r))
    resized = cv2.resize(image, dim, interpolation=inter)
    return resized
# 显示图像 -写成了方法
def cv_show(name,img):
    cv2.imshow(name,img)
    # cv2.imshow()方法一定要和下面两条代码一起使用
    cv2.waitKey(0)
    cv2.destroyAllWindows()

到此为止,代码中需要调用的方法就是这些,下面开始对模板图片和输入图片进行处理

  • 处理模板图片
    模板图片
# 模板图片
img = cv2.imread('./photo/template.jpg')
cv_show('img',img)

# 模板 --> 灰度图
ref = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv_show('ref-hui',ref)

# 模板 
# 轮廓检测输入的是二值图像  所以需要二值图像
ref = cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1] # 阈值处理  [1]表示第二个参数
cv_show('ref-er',ref)

模板图片
模板灰度图片

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

# 开始轮廓检测
# cv2.RETR_EXTERNAL:只检测外轮廓  
# cv2.CHAIN_APPROX_SIMPLE:只保留终点坐标
# 返回的list中每个元素都是图像中的一个轮廓

refCnts,hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

# 画轮廓
cv2.drawContours(img,refCnts,-1,(0,0,255),3)# -1是所有轮廓
cv_show('img-lun',img)

'''
print(np.array(refCnts).shape) # refCnts是列表类型  
(10,) # 代表个轮廓

'''

视频中作者效果
我的旧模板图像新模板图片

# 对轮廓进行排序
# 分析模板图片 0:位置1  1:位置2  2:位置3  ..
# 返回的是排序完的轮廓
# print(refCnts)
refCnts = sort_contours(refCnts,method='left-to-right')[0] # 返回第一个参数

digits = {} # 保存模板中每个数字对应的数值

#遍历每一个轮廓
for(i,c) in enumerate(refCnts):
    #计算外接矩形并且resize成合适大小
    (x,y,w,h)=cv2.boundingRect(c)
    roi=ref[y:y+h,x:x+w] # 获取区域
    roi=cv2.resize(roi,(57,58))

    #每一个数字对应一个模板
    digits[i]=roi

模板轮廓排序

到此模板处理完毕,接下来需要处理输入图像

  • 处理输入图像
# 为了最后判断时什么类型的卡片
FIRST_NUMBER = {
    "3": "American Express",
    "4": "Visa",
    "5": "MasterCard",
    "6": "Discover Card"
}
# 初始化卷积核  -- 两个
# 大小的确定是根据实际的任务进行确定的
rectKernal = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
sqKernal = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
# 读取输入图像,预处理
image = cv2.imread('./photo/id_card1.png')
cv_show('image',image)
image = resize(image,width=300)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)

输入图像
输入图像的灰度图

# 顶帽操作  突出更明亮的区域
tophat = cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernal)# 这个函数可以方便的对图像进行一系列的膨胀腐蚀组合
cv_show('tophat',tophat)

# 使用索贝尔算子
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') # 归一化后要把数据改成uint8的形式

print(np.array(gradX).shape)
cv_show('gradX',gradX)

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

# 如何将数字连在一起成为块 ---信用卡中数字分成4块

# 通过闭操作(先膨胀,再腐蚀) --将数字连在一起
gradX = cv2.morphologyEx(gradX,cv2.MORPH_CLOSE,rectKernal)
cv_show('gradX',gradX)

# THRESH_OTSU自动寻找合适的的阈值, 适合双峰 需把阈值参数设为0(0是和THRESH_OTSU搭配的)
thresh = cv2.threshold(gradX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] 
cv_show('thresh',thresh)

# 再来闭操作
thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernal)
cv_show('thresh',thresh)

在这里插入图片描述
在这里插入图片描述
我的第二个闭操作

视频中作者的第二个闭操作

# 计算轮廓
threshCnts, hierarchy = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

# 得到后在原图中进行展示
cnts = threshCnts
cur_img = image
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) #根据外接矩形的长宽比来筛选有用的矩形,并将其添加到元组中
    print(w)
    
    # 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
    if ar > 3.0 and ar < 4.0:
        if (w >45 and w < 55) and (h > 10 and h <15):
            # 符合的留下来
            locs.append((x, y, w, h))

# 符合的轮廓进行排序
locs = sorted(locs,key=lambda x:x[0], reverse = False)


# print(locs)

此处的区域选择,你会发现我的个别数值和网上的不大相同,这是需要你自己选择适合你的图像的,你可以通过print(w)、print(h)、print(ar)来确定你的数值区间

# 现在已经选出合适的轮廓了。需要把各个轮廓提出来,最后进行分别的识别操作
# 通过遍历轮廓中的每一个数字,寻找合适的参数

output = []
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'+str(i),group)
    # 预处理
    group = cv2.threshold(group,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] 
    cv_show('group',group)
    
    # 计算四组轮廓中每一组轮廓中的小轮廓
    digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

    digitCnts = 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)) # 与模板的大小相同
        cv_show('roi',roi)
        
    # 计算匹配得分
        scores  = []
    
    # 在模板中计算每一个得分
        for (digit,digitROI)in digits.items():#在模板预处理中建立了数值的字典类型,一个为索引、一个为值
           #匹配,返回与之匹配度最高的数值  每次都会有匹配度 选择最高的
            result=  cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)
            
            (_,score,_,_) = cv2.minMaxLoc(result)#做10次匹配,取最大值(注意:取最大值还是最小值跟选取的模板匹配方法有关)
            scores.append(score)
        # 得到合适的数字
        groupOutput.append(str(np.argmax(scores)))
    # 画框
    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)
    
print("Credit Card Type: {}".format(FIRST_NUMBER[output[0]]))
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image",image)

cv2.waitKey(0)
cv2.destroyAllWindows()
  • GIF动态图
    在这里插入图片描述

  • cv_show(‘group’+str(i),group)
    输入图像第一个轮廓
    输入图像第二个轮廓
    输入图像第三个轮廓
    输入图像第四个轮廓

  • cv_show(‘group’,group)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • cv_show(‘roi’,roi)
    轮廓-4
    轮廓-0
    轮廓-0
    轮廓-0
    轮廓-1
    轮廓-2
    轮廓-3
    轮廓-4
    轮廓-5
    轮廓-6
    轮廓-7
    轮廓-8
    轮廓-9
    轮廓-0
    轮廓-1
    轮廓-0

  • 最后结果
    视频中作者的最终结果
    在这里插入图片描述
    在这里插入图片描述

  • 参考博客

  1. 信用卡识别⭐⭐(这篇文章讲解的特别详细,有兴趣的朋友可以看看学习)

从新测试

最后我又在jupyter notebook中用完整的代码从新测试了一下,匹配效果非常好,看图:

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


总结

这个是我完全未接触的领域,到现在为止,还是无法完全理解其中的某些参数的含义以及个别方法的具体使用环境,如果有好的通俗易懂的教程希望可以分享给我啊

这篇关于Opencv-项目实战:信用卡数字识别的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!