opencv 银行卡号识别案例
目录
一.参数设置
二.指定信用卡的类型
三.模板图像中的数字定位处理
1.读取数字模板照片做灰度图处理并进行二值化处理成黑底白字
2.得到所有数字轮廓
3.给所有数字轮廓排序
4.将数字和对应的外接矩形图片即数字图片保存在字典中
四.信用卡的图像处理
1.银行卡图片的基本处理
①读取图片
②通过自定义的方法将图像等比缩放到宽度为300后提取灰度图
③顶帽操作(顶帽=原始图像-开运算结果(先腐蚀后膨胀))
2.找到银行卡图片中的数字边框
①通过闭操作(先膨胀后腐蚀),将数字连在一起
编辑
②对闭操作后的图进行二值化处理
③对二值化后的黑白图载进行一次闭操作
④找出最后闭操作的图中所有的轮廓
⑤遍历找出的轮廓,找到数字部分像素区域轮廓的最小外接矩形并保存
3.遍历上面得到的每一个轮廓矩形中的数字,再匹配每一个值
①切片获取数字区域
②对数字区域group图片进行二值化处理得到黑底白字
③找出group的中的所有数字外轮廓,再排序
④匹配group中的每一个数值
⑤使用模板匹配,计算匹配得分
⑥在遍历完一个group里的所有数值后直接在银行卡原图片画出该group外界矩阵并在上面写出对应数字
4.遍历所有的数字轮廓矩形后打印结果
本案例根据银行卡的图片识别出银行卡号,效果如下 :
第一个数字用来代表银行卡的类型,在代码中我们可以使用字典来保存
一.参数设置
导入相关库和文件,其中myutils是一个自己创建的工具包,后面会介绍
import numpy as np
import argparse # python内置库
import cv2
import myutils
我们使用argparse类来设置参数,只需在运行配置中填入参数即可
# 设置参数
ap = argparse.ArgumentParser() #创建 ArgumentParser 对象,这个对象将用于定义和解析命令行参数
ap.add_argument("-i", "--image", required=True,help="path to input image")
ap.add_argument("-t", "--template", required=True,help="path to template OCR-A image")
args = vars(ap.parse_args()) # vars()是Python中的一个内置函数,用于返回对象的属性和值的字典。
在运行配置中填入-i card1.png -t kahao.pn,args=vars()是Python中的一个内置函数,用于返回对象的属性和值的字典,则args['image']='card1.png',args['template']='kakao.png'
二.指定信用卡的类型
我们直接用字典来保存不同数字对应的银行卡类型
# 指定信用卡类型
FIRST_NUMBER = {"3": "American Express","4": "Visa","5": "MasterCard","6": "Discover Card"}
自定义一个展示图片的函数减小代码量
def cv_show(name, img): # 绘图展示cv2.imshow(name, img)cv2.waitKey(0)
三.模板图像中的数字定位处理
1.读取数字模板照片做灰度图处理并进行二值化处理成黑底白字
img=cv2.imread(args['template'])
cv_show('img',img)
ref=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv_show('ref',ref)
ref=cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]#二值图像黑底白字,方便找到轮廓
cv_show('ref',ref)
cv2.THRESH_BINARY_INV模式像素值大于thresh(10)的变为0,小于thresh的变成255,取索引[1]是因为函数有两个返回结果第二个才是需要的二值图
2.得到所有数字轮廓
cv2.RETR_EXTERNAL模式代表只检测图像的外轮廓,
cv2.CHAIN_APPROX_SIMPLE表示只简单保留轮廓的终点坐标即只保留完整轮廓的其中一部分
_,refCnts,hierarchy=cv2.findContours(ref,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#RETR_EXTERNAL只检测外轮廓,CHAIN_APPROX_SIMPLE只保留终点坐标
cv2.drawContours(img,refCnts,-1,(0,0,255),3)
cv_show('img',img)
drawContours()方法表示在直接img上画出轮廓,-1表示绘制所有轮廓
3.给所有数字轮廓排序
由于我们前面提取到的所有轮廓refCnts中的轮廓列表并不是和我们图片中的数字一个是按顺序拍好的,所有我们在myutils.py中自定义了一个排序方法sort_contours()来将refCnts列表中的轮廓按对应的数字0,1,2,3,4,5,6,7,8,9排好
def sort_contours(cnts,method='left-to-right'):reverse=Falsei=0if method=='right-to-left'or method=='bottom-to-top':reverse=Trueif method=='bottom-to-top'or method=='bottom-to-top':i=1boundingBoxes=[cv2.boundingRect(c) for c in cnts](cnts,boundingBoxes)=zip(*sorted(zip(cnts,boundingBoxes),key=lambda a:a[1][i],reverse=reverse))return cnts,boundingBoxes
boundingBoxes=[cv2.boundingRect(c) for c in cnts]是遍历轮廓列表中的所有轮廓,分别得到他们的最小外接矩形后,以列表的形式存储在boundingsBoxes中
sorted(zip(cnts,boundingBoxes),key=lambda a:a[1][i],reverse=reverse)是先将轮廓列表和对应的外接矩阵列表用zip()组和在一组里,然后根据外接矩阵列表中的第一个元素即外界矩行的x坐标的大小来排序,从小到大排序,对应的轮廓就是从0到9,排序完成之后在用zip()压缩成组返回,最后return轮廓列表和对应的最小外接矩行列表
refCnts,boundingBoxes= myutils.sort_contours(refCnts, method='left-to-right')
调用方法得到排序后的轮廓列表和对应的最小外接矩形列表
4.将数字和对应的外接矩形图片即数字图片保存在字典中
利用上面返回的最小外接矩形列表通过索引得到对应数字外接矩形的x,y,w和h,直接在黑底白字的二值图ref上进行切片操作来截取对应的数字图片,最后一遍遍的保存在字典中(键:数字,值:对应的图片)
digits={}
for i in range(10):(x,y,w,h)=boundingBoxes[i]roi=ref[y:y+h,x:x+w]roi=cv2.resize(roi,(57,88))cv_show('roi',roi)digits[i]=roi
print(digits)
可以来显示对应的数字图片
四.信用卡的图像处理
1.银行卡图片的基本处理
①读取图片
image=cv2.imread(args['image'])
cv_show('image',image)
②通过自定义的方法将图像等比缩放到宽度为300后提取灰度图
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 imageif 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)#默认为cv2.INTER_AREA,即面积插值适用于缩放图像return resized
image= myutils.resize(image, width=300)
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)
③顶帽操作(顶帽=原始图像-开运算结果(先腐蚀后膨胀))
突出图像中的亮细节,清楚背景图,原因是背景颜色变化小不被腐蚀掉
直接使用cv2.getStructuringElement()方法生成卷积核(全是1的矩阵)一个9*3的一个5*5的
在cv2.morphologyEx()方法中cv2.MORPH_TOPHAT表示顶帽模式卷积核我们选择使用9*3的卷积核对银行卡的灰度图进行顶帽处理
rectKernel=cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))#初始化卷积核
sqKernel=cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
tophat=cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernel)#顶帽=原始图像-开运算结果(先腐蚀后膨胀)
cv_show('tophat',tophat)
2.找到银行卡图片中的数字边框
①通过闭操作(先膨胀后腐蚀),将数字连在一起
将之前得到的顶帽后的图片进行闭操作,卷积核选择9*3的
closeX=cv2.morphologyEx(tophat,cv2.MORPH_CLOSE,rectKernel)
cv_show('closeX',closeX)
②对闭操作后的图进行二值化处理
cv2.THRESH_OTSU会自动寻找适合的阈值,适合双峰,需要把阈值参数设置为0
#cv2.THRESH_OTSU会自动寻找适合的阈值,适合双峰,需要把阈值参数设置为0
thresh=cv2.threshold(closeX,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1]
cv_show('thresh_1',thresh)
③对二值化后的黑白图载进行一次闭操作
这一次卷积核使用5*5的
#再来一次闭操作
thresh=cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel)
cv_show('thresh_2',thresh)
④找出最后闭操作的图中所有的轮廓
_,threshCnts,h=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('cur_img',cur_img)
⑤遍历找出的轮廓,找到数字部分像素区域轮廓的最小外接矩形并保存
locs集和用来存放最后符合条件的四块数字区域轮廓的最小外界矩形的x,y,w和h的元组
具体的判定条件,选择合适的区域,根据实际任务
locs=[]
for (i,c) in enumerate(cnts):(x,y,w,h)=cv2.boundingRect(c)ar=w/float(h)#选择合适的区域,根据实际任务if 2.5<ar<4.0:if 40<w<55 and 10<h<20:locs.append((x,y,w,h))
将符合的轮廓外界矩形从左到右排序,根据x坐标
locs=sorted(locs,key=lambda x:x[0])
3.遍历上面得到的每一个轮廓矩形中的数字,再匹配每一个值
output列表用来存放最后匹配得到的所有数字
①切片获取数字区域
可以利用上面存储外界矩形信息的列表locs直接在银行卡的灰度图中切片出一块数字区域的外界矩形可以适当增加一些边界
group_output列表存放每一个group中的所有数字
output=[]
#遍历每一个轮廓中的数字
for (i,(gX,gY,gW,gH)) in enumerate(locs):group_output=[]group=gray[gY-5:gY+gH+5,gX-5:gX+gW+5]#适当加一点边界cv_show('group',group)
这里我们直接将四次遍历后的图片都放出来但实际上是一次循环后才会出来后面的group图片
注意:后续代码的缩进是表示还在此for循环中
②对数字区域group图片进行二值化处理得到黑底白字
group=cv2.threshold(group,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1]cv_show('group', group)
③找出group的中的所有数字外轮廓,再排序
group_,digitCnts,hierarchy=cv2.findContours(group.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)digitCnts= myutils.sort_contours(digitCnts, method='left-to-right')[0]
④匹配group中的每一个数值
对上面得到的group中的所有数字轮廓进行遍历,得到对应的外接矩形的信息后直接对group进行切片操作获取该数字的图片
#计算每一组中的每一个数值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)
⑤使用模板匹配,计算匹配得分
遍历前面存储数字和对应图片的字典
cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)在字典中的数字图片匹配当前roi返回结果
scores列表用来存放当前roi对每个字典中的数字图片的得分
scores=[]#在模板中计算每一个得分for (digit,digitROI) in digits.items():#模板匹配result=cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)(_,score,_,_)=cv2.minMaxLoc(result)scores.append(score)
往group_output中添加遍历字典后最大得分的索引直接对应相应的数字
#得到合适的数字group_output.append(str(np.argmax(scores)))
⑥在遍历完一个group里的所有数值后直接在银行卡原图片画出该group外界矩阵并在上面写出对应数字
cv2.rectangle(image,(gX-5,gY-5),(gX+gW+5,gY+gH+5),(0,0,255),3)cv2.putText(image,''.join(group_output),(gX,gY-15),cv2.FONT_HERSHEY_SIMPLEX,0.65,(0,0,255),2)output.extend(group_output)
4.遍历所有的数字轮廓矩形后打印结果
Credit Card Type可以用最开始的银行类型字典来获取,output[0]就是第一位银行卡号
Credit Card就是将output列表的内容用‘’.join()直接连接起来返回字符串
最后显示识别后的银行卡号图
print('Credit Card Type:{}'.format(FIRST_NUMBER[output[0]]))
print('Credit Card #:{}'.format(''.join(output)))
cv2.imshow('Image',image)
cv2.waitKey(0)