最近在恶补opencv,在前期不太那么认真的学习状态下,着手搞了一下这个小项目实战,基于模板匹配下的银行卡卡号识别。
整体思路:
首先对于该项目而言,我们所需要考虑的是,该如何让计算机成功识别到银行卡的卡号,并正确识别出每个卡号数字所对应的数字。那么我们在这里提供一种思路,首先通过对银行卡图像进行轮廓识别,然后拿到我们需要的对应的卡号的轮廓部分,然后再在轮廓中进行进一步轮廓识别,拿到每个数字的轮廓,最后通过画出每个数字的外接矩形,然后根据事先识别好的模板的外接矩形轮廓进行模板匹配,对应就能成功识别出正确的数字。
准备工作:
1.将图像转化为灰度图
2.对图像作二值化处理
3.画轮廓,根据轮廓比的不同拿到我们需要的轮廓部分(此处使用的是将轮廓的长宽进行相比,然后判断每个轮廓的长宽比,然后通过限定条件拿到正确的长宽比部分),同时过滤掉银行卡上的其他图像信息
4.最后再做一些形态学操作,比如一些开、闭合操作,将数字图像信息更明显,更精准
5.训练好模板,这里需要注意的是,我们针对银行卡卡号,需要找到与银行卡卡号数字样式相近的数字模板,不然会影响后续的匹配结果;
匹配结果:
测试图以及模板图
处理过程:
模板图像处理
整个预处理的过程就是灰度图、二值化、轮廓查找、画轮廓、resize()轮廓的大小并将所有轮廓进行从0~9的顺序排序,以方便后续匹配完成后的数字获取;
完成上诉操作后的模板图:
相关代码
#1.读取模板
img=cv2.imread('number.png')
cv_show('number',img)
#2.模板转换为灰度图
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)
#3.换为二值图像
ref,thre=cv2.threshold(gray,10,255,cv2.THRESH_BINARY_INV)
cv_show('ref',thre)
#4.计算轮廓
refCnts,hierarchy=cv2.findContours(thre.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,refCnts,-1,(0,0,255),3)#画轮廓,此处只画外轮廓,一共10个轮廓0~9
cv_show('imgLK',img)
refCnts=sort(refCnts,"left-to-right")[0]
digits={}
#遍历每一个轮廓:
for(i,c) in enumerate(refCnts):#计算外接矩形并且resize成合适大小
(x,y,w,h)=cv2.boundingRect(c)
roi=thre[y:y+h,x:x+w]
roi=cv2.resize(roi,(57,88))
#每个数字都有一个模板
digits[i]=roi#0~9的数字模板对应
原图像处理
原图像的处理相较模板而言,首要的操作一致,都是先灰度处理,然后二值化,多的就是一些形态学的操作,其主要目的是为了去噪点,然后强化轮廓和一些重要特征的强化,完成这些之后,我们通过轮廓的长宽比拿到我们需要的部分轮廓信息,拿到之后再继续进行轮廓处理,画外接矩形,对比,最后得到结果。
对原图进行灰度和二值化之后,我们做一次顶帽操作;
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
效果图:
然后求一次X方向的梯度,拿到上图中高亮的部分:
然后做一次闭操作,让高亮区域成块出现:
接着做一次二值化,让成块部分更高亮,同时去除一下图像中其他杂余信息:
随后做一次闭操作,补全区域的空白部分;
完成上述操作之后,便可以进行画轮廓,然后筛选轮廓,最后拿到需要的轮廓之后,再进行上述的操作,拿到每个轮廓里面的数据信息,然后进行模板匹配;
完整代码段:
import cv2
import numpy as np
from imutils import contours
from matplotlib import pyplot as plt
#银行卡实战小项目
def cv_show(name,img):
cv2.imshow(name,img)
cv2.waitKey(0)
cv2.destroyAllWindows()
def sort(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]#用一个最小的矩形,把找到的形状包起来,然后对最小坐标进行排序
(cnts,boundingBoxes) = zip(*sorted(zip(cnts,boundingBoxes),key=lambda b:b[1][i],reverse=reverse))
return cnts,boundingBoxes
#1.读取模板
img=cv2.imread('number.png')
cv_show('number',img)
#2.模板转换为灰度图
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)
#3.换为二值图像
ref,thre=cv2.threshold(gray,10,255,cv2.THRESH_BINARY_INV)
cv_show('ref',thre)
#4.计算轮廓
refCnts,hierarchy=cv2.findContours(thre.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,refCnts,-1,(0,0,255),3)#画轮廓,此处只画外轮廓,一共10个轮廓0~9
cv_show('imgLK',img)
refCnts=sort(refCnts,"left-to-right")[0]
digits={}#用来存放模板数字对应的数字
#遍历每一个轮廓:
for(i,c) in enumerate(refCnts):#计算外接矩形并且resize成合适大小
(x,y,w,h)=cv2.boundingRect(c)
roi=thre[y:y+h,x:x+w]
roi=cv2.resize(roi,(57,88))
#每个数字都有一个模板
digits[i]=roi#0~9的数字模板对应
#初始化卷积核,做形态学处理,核的大小可以自己定义,根据实际情况进行定义
rectKernel=cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
sqKernel=cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
#读取原始图像,预处理
image=cv2.imread('bank_testI.png')
cv_show('card',image)
image=cv2.resize(image,(300,200))
#灰度处理
image_gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv_show('gray',image_gray)
#顶帽操作,突出明亮区域
tophat=cv2.morphologyEx(image_gray,cv2.MORPH_TOPHAT,rectKernel)
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")
cv_show('gradX',gradX)
#执行闭操作,让图像信息成块出现
gradX=cv2.morphologyEx(gradX,cv2.MORPH_CLOSE,rectKernel)
cv_show('gradX',gradX)
thresh=cv2.threshold(gradX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]#阈值设为0是因为函数中设置了自动判断阈值,一般适用于双峰情况
cv_show('gradX_t',thresh)
#再进行一次闭操作,让图像信息成团出现,补齐空白部分
thresh=cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel)
conts,hiera=cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cont=conts
curImag=image.copy()
tmp=cv2.drawContours(curImag,cont,-1,(0,0,255),3)#在经过一系列处理后的图像中画出轮廓
locs=[]
for(i,c) in enumerate(cont):
(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])#将筛选之后的轮廓数据进行排序
output=[]
for(i,(gx,gy,gw,gh)) in enumerate(locs):
groupOuput=[]
group=image_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]#再次对每个大框里面的数据进行二值化、测边界
digitsCont,hieraD=cv2.findContours(group.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#再进行轮廓检测
digitsCont=sort(digitsCont,method="left-to-right")[0]
for c in digitsCont:#计算每个小框的值
(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)#做10次匹配,取最大值
scores.append(score)
groupOuput.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(groupOuput),(gx,gy-15),cv2.FONT_HERSHEY_SIMPLEX,0.65,(0,0,255),2)
output.extend(groupOuput)
print("The Card's number is :{}",format("".join(output)))
cv_show('image_result', image)
总结:
整个实战项目的难度不算很大,但对于我这个新手来说比较麻烦,毕竟是第一次实战,所以对整个流程还是处在一个模仿的阶段,整个流程没有自己独立的思考,更多的是对整个流程的个人理解和学习,故待到后续进行深入学习之后,可以再继续对项目进行进一步的改善。如果很幸运有大佬看到我的这篇文章,如果您有比较好的学习建议和意见,请在留言区留言哟~或者你有不理解的地方,也可以在留言区留言,一起探讨学习!!