• 【Opencv入门到项目实战】(九):项目实战|信用卡识别|模板匹配|(附代码解读)


    所有订阅专栏的同学可以私信博主获取源码文件

    0.背景介绍

    接下来我们正式进入项目实战部分,这一章要介绍的是一个信用卡号识别的项目。首先,我们来明确一下研究的问题,假设我们有一张信用卡如下所示,我们要做的就是识别出这上面卡号信息,然后会输出一个序列,第一个序列就是4020,第二序列是3400,第三个序列0234,第四个序列5678,也就是说此时我们不光是把这个数输出来,我们还要知道对应的位置。

    image-20230804161241495

    之前我们已经介绍了Opencv的各种图像基本操作,例如形态学操作、模板匹配、轮廓检测,我们现在要做的就是把这些方法全部应用到一起,相当于把我们以前所学的知识点全部穿插到咱们这个项目当中了。

    我们先来看一下要完成这个项目的基本思路。

    思考一: 首先最核心的问题是我们如何判断一个数字是几呢?这里我们要用到模板匹配

    假设我们有一个数字模板如下:

    image-20230804162917771

    现在我们要做的就是将信用卡上每一个数字和模板上的数字进行匹配,看一下它与模板上的哪一个数字最接近,我们就把这个数字输出。因此我们第一步需要得到一个与目标信用卡数字字体非常接近的一个模板。

    思考二: 如何每一个数字单独拿出来?

    我们之前介绍轮廓检测,但是直接得到的轮廓各个数字之间非常不规则,我们可以利用轮廓的外接矩形或者外接圆来进行操作。

    总体就是分为以上两个步骤,具体过程我们还需要对图像进行各种预处理操作,我们在后面在代码中细致介绍。

    以下是项目的主要框架,想要源码的可以私信我获取。

    image-20230804160702361

    1.模板处理

    1.1模板读取

    首先我们将目标模板读取

    # 导入工具包
    from imutils import contours
    import numpy as np
    import argparse
    import cv2
    import myutils
    
    
    # 指定模板和目标图像位置
    target = 'images/credit_card_02.png'
    template = 'images/ocr_a_reference.png'
    
    # 定义图像展示函数
    def cv_show(name,img):
    	cv2.imshow(name, img)
    	cv2.waitKey(0)
    	cv2.destroyAllWindows()
    
    # 读取模板图像
    img = cv2.imread(template)
    
    cv_show(im)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    image-20230804164820268

    1.2预处理

    接下来对模板进行预处理,转换为二值图,因为我们后续轮廓检测时只接受二值图输入。

    # 灰度图
    ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cv_show('ref',ref)
    
    # 二值图像
    ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1] #阈值设为10
    cv_show('ref',ref)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    image-20230804181757920

    现在得到了二值图像之后我们就可以进行图像轮廓检测了。

    1.3轮廓计算

    在这里我们使用cv2.findContours()函数,其只接收一个二值图像,cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标。返回参数我们只需要用refCnts即可,它返回的是我们的轮廓信息

    # 计算轮廓
    
    #返回的list中每个元素都是图像中的一个轮廓
    
    ref_, 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',img) #展示轮廓
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20230804181831455

    现在我们得到了0-9每个数字的外轮廓信息,但是直接返回的轮廓顺序不一定是按照我们模板从左到右排序的,接下来我们需要对轮廓进行排序,让它按照从左到右0,1,2,3,4…9的顺序排列,这里我们定义了函数,sort_contours,我们直接根据我们的坐标排序,就可以得到按照0-9排列的轮廓

    import cv2
    
    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
        boundingBoxes = [cv2.boundingRect(c) for c in cnts] #计算外接矩形,用一个最小的矩形,返回x,y,h,w,分别表示坐标和高宽
        (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                            key=lambda b: b[1][i], reverse=reverse))#排序
    
        return cnts, boundingBoxes
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    现在得到了排序好后的轮廓,接下来我们需要把模板中每个数字轮廓单独拿出来放到一个字典中方便我们后续进行匹配,通过cv.boundingRect()得到轮廓坐标和长宽信息,然后利用我们之前的ROI读取方法即可,最后我们更改一下轮廓的大小。

    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, 88))
    
    	# 每一个数字对应每一个模板
    	digits[i] = roi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接下来我们就得到了每个数字模板的轮廓信息,并保留在digits字典中,接下来我们需要对输入图像进行处理。

    2.输入图像处理

    2.1图形读取

    这里我们初始化了两个卷积核,分别为9×35×5的,这里大家可以根据自己的任务更换别的卷积核大小。然后我们把目标图像读取进来并转换为灰度图

    # 初始化卷积核
    rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
    sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    
    
    #读取输入图像,预处理
    
    image = cv2.imread(target)
    cv_show('image',image)
    image = myutils.resize(image, width=300)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 转换为灰度图
    cv_show('gray',gray)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20230804182424417

    2.2预处理

    得到灰度图之后,我们需要进行更细节的预处理,因为我们想要检测的是银行卡号,我们关注的是这样的数字部分,也就是更亮的区域,因此我们在这里进行了礼帽操作,来突出我们想要研究的信息,在实际应用中,可以根据具体想要研究的任务来选择其他的处理方式

    #礼帽操作,突出更明亮的区域
    tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) 
    cv_show('tophat',tophat) 
    
    • 1
    • 2
    • 3

    image-20230804182553963

    接下来我们进一步的利用sobel算子来进行边缘检测

    gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, #ksize=-1相当于用3*3的
    	ksize=-1) # 使用Sobel算子处理
    
    
    gradX = np.absolute(gradX)
    (minVal, maxVal) = (np.min(gradX), np.max(gradX))
    gradX = (255 * ((gradX - minVal) / (maxVal - minVal))) # 归一化处理
    gradX = gradX.astype("uint8")
    
    
    print (np.array(gradX).shape)
    cv_show('gradX',gradX)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20230804182603478

    得到边缘之后,我们希望将这些数字分块放到一起,每四个数字为一个小方块。我们可以利用之前介绍过的先膨胀,再腐蚀的操作。

    #通过闭操作(先膨胀,再腐蚀)将数字连在一起
    gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) 
    cv_show('gradX',gradX)
    
    #二值化处理:THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
    thresh = cv2.threshold(gradX, 0, 255,
    	cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] 
    cv_show('thresh',thresh)
    
    #再来一个闭操作
    thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) #再来一个闭操作
    cv_show('thresh',thresh)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    首先经过一个闭操作后,得到下列结果

    image-20230804200816726

    然后我们进一步使用二值化处理,将图片转换为二值图像

    image-20230804201517823

    现在得到的结果中,还有一部分空隙,我们再进行一次闭操作,得到结果如下:

    image-20230804201115159

    现在得到的结果是一个完全闭合的状态了,此时我们再检测它的外轮廓,会更准确一些。

    2.3轮廓计算

    接下来我们计算轮廓,和之前在处理模板一样,我们调用cv2.findContours()函数计算轮廓信息。

    # 计算轮廓
    
    thresh_, threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
    	cv2.CHAIN_APPROX_SIMPLE)#
    
    cnts = threshCnts #轮廓信息
    cur_img = image.copy()
    cv2.drawContours(cur_img,cnts,-1,(0,0,255),3)#在原始图像上绘制轮廓 
    cv_show('img',cur_img)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    image-20230804201800741

    这里需要说明的是,我们这里是用经过一切预处理后得到的轮廓信息,然后绘制在原始图像中。但是我们得到的轮廓有点多,且有一些不太规则的形状,有些可能不是我们想要轮廓,我们需要把这些轮廓过滤,我们只要四组数字轮廓。我们可以根据他们的坐标位置进行筛选,具体的筛选范围大家要根据自己的实际任务选择.

    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))
    
    # 将符合的轮廓从左到右排序
    locs = sorted(locs, key=lambda x:x[0])
    locs
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    [(34, 111, 47, 14), (95, 111, 48, 14), (157, 111, 47, 14), (219, 111, 48, 14)]
    
    • 1

    可以看到我们现在得到了四个轮廓,并且进行了排序,接下来我们怎么进行模板匹配呢?我们不是拿这四个大轮廓去匹配,而是在每一个大轮廓中,再去分隔成小轮廓,然后去和我们之前保存的10个数的模板进行匹配。

    2.4计算匹配得分

    接下来我们要做的是去遍历每一个轮廓当中的数字,然后将其与模板中的10个数字计算匹配得分,从而识别出对于的数字,我们先来看第一个轮廓:

    image-20230804205345172

    有了这个之后,就和我们最开始处理模板一样,先进行二值化处理,得到下图

    image-20230804205539684

    然后计算每一组的轮廓,并按照从左到右的顺序排列,以第一个数字为例,得到结果如下

    image-20230804205635093

    然后我们就是将这每一个数字与模板上的10个数字进行对比,看一下和哪一个最相似。具体做法跟之前都是一样的吧,先去找到外接矩形,然后对外接矩形进行resize。然后我们就要计算得分了,我们利用模板匹配中的方法,使用cv2.TM_CCOEFF计算得分,然后找到最匹配的数字,这样就完成了我们所有的步骤了

    output = []
    # 遍历每一个轮廓中的数字
    for (i, (gX, gY, gW, gH)) in enumerate(locs):
    	# initialize the list of group digits
    	groupOutput = []
    
    	# 根据坐标提取每一个组
    	group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5] # 扩张一下轮廓
    	cv_show('group',group)
    	# 预处理
    	group = cv2.threshold(group, 0, 255,
    		cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    	cv_show('group',group)
    	# 计算每一组的轮廓
    	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))
    		cv_show('roi',roi)
    
    		# 计算匹配得分
    		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(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: {}".format("".join(output)))
    cv2.imshow("Image", image)
    cv2.waitKey(0)
    
    • 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
    Credit Card: 4020340002345678
    
    • 1

    image-20230804211052032

    3.小结

    我们这个项目主要分两步。第一步,定位到目标数字在什么位置。第二步,基于定位好的区域,在模板当中去匹配它到底是一个什么样的值。中间利用了我们之前介绍过的各种图像处理方法。今天我们这个项目是做一个信用卡号识别,如果说大家想做车牌识别、学生卡、身份证识别,都是一个类似的做法,我们主需要更改一下对于的照片模板以及其中的一些参数即可。

    🔎本章的介绍到此介绍,如果文章对你有帮助,请多多点赞、收藏、评论、订阅支持!!《Opencv入门到项目实战》

  • 相关阅读:
    R语言时间序列数据算术运算:使用diff函数计算时间序列数据的逐次差分、使用除法将两个长度不等时间序列数据进行相除、使用固定值乘以指定的时间序列
    基于SSM的课程管理系统
    兼职管理系统
    9.2.4 DATETIME类型
    软件测试高频面试题真实分享/网上银行转账是怎么测的,设计一下测试用例。
    数据治理-元数据扫描
    口袋参谋:如何提升宝贝的点击率?这两种方法超简单!
    网易数帆自主创新再获认可:轻舟微服务入选信创技术图谱
    Java版企业电子招标采购系统源码Spring Cloud + Spring Boot +二次开发+ MybatisPlus + Redis
    【超级大坑】万恶的Received fatal alert: handshake_failure
  • 原文地址:https://blog.csdn.net/weixin_45052363/article/details/132178708