本篇博客的学习视频: 基于python的Opencv项目实战-09项目实战-信用卡数字识别
输出序列:4000 1234 5678 9010
使用模板匹配
要
选择合适的模板
,例如上图中的两幅图中的8的模样要是相同的,6、1等数字都要相同才可以称作一个模板。
轮廓检测
具体步骤:
- 对模板和输入图像进行轮廓检测,一般得到两个轮廓:外轮廓、内轮廓。本项目选择
外轮廓
。- 得到轮廓之后需要得到当前检测的轮廓的
外接矩形
。(模板和输入图像都需要)- 使用信用卡中的数字对模板中的数字进行for循环(10次:一共10个数字),匹配过程中需要
resize
(保证外接矩形大小相同)。- 需要过滤信用卡中的不符合数字的轮廓
关于这个项目的源码网络上有很多,在此给大家一个链接: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)
最后结果
参考博客
最后我又在jupyter notebook中用完整的代码从新测试了一下,匹配效果非常好,看图:
这个是我完全未接触的领域,到现在为止,还是无法完全理解其中的某些参数的含义以及个别方法的具体使用环境,如果有好的通俗易懂的教程希望可以分享给我啊