文章目录
- 银行卡号识别
- 详细流程
- 一、设置参数
- 二、函数准备
- 三、具体步骤
- 1. 得到每个数字的信息
- 2. 银行卡处理
- 3. 找到数字边框
- 4. 模板匹配
- 总结
银行卡号识别
经过了几篇关于轮廓检测的学习,本篇我们来尝试完成对银行卡号的检测识别:
目标,识别出下面银行卡的卡号:
识别主要流程:
- 得到每个数字的信息,为卡号的模板匹配做准备
- 定位到银行卡上卡号的位置
- 遍历卡号的每个数字,进行模板匹配
详细流程
一、设置参数
import numpy as np
import argparse #python内置库不太熟,自行学习
import cv2
import myutils
"""
-i card1.png
-t kahao.png
"""
ap = argparse.ArgumentParser()
ap.add_argument("-i","--image",required=True,
help="path to input image")
ap.add_argument("-t","--template", required=True,
help="path to template 0CR-A image")
args = vars(ap.parse_args())# vars()是Python中的一个内置函数,用于返回对象的属性和值的字典。# 指定信用卡类型
FIRST_NUMBER ={"3":"American Express",
"4":"Visa",
"5": "MasterCard",
"6":"Discover Card"}
二、函数准备
- 图像展示:
def cv_show(name,img):# 绘图展示
cv2.imshow(name,img)
cv2.waitKey(0)
- 排序:
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]
(cnts,boundingBoxes)=zip(*sorted(zip(cnts,boundingBoxes),
key=lambda b:b[1][i],reverse=reverse))#zip(*...)使用星号操作符解包排序后的元组列表,并将其重新组合成两个列表:一个包含所有轮廓,另一个包含所有边界框。
return cnts,boundingBoxes
- 调整大小:
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)#默认为cV2.INTER_AREA,即面积插值,适用于缩放图像。
return resized
三、具体步骤
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.findcontours()函数接受的参数为二值图,即黑白的(不是灰度图)
# cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
_,refCnts, hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,refCnts,-1,(0,0,255),3)
cv_show('img',img)
refCnts = myutils.sort_contours(refCnts,method="left-to-right")[0] # 排序,从左到右,从上到下
digits = {} # 保存模板中每个数字对应的像素值
for (i,c) in enumerate(refCnts): # 遍历每一个轮廓
(x,y,w,h)=cv2.boundingRect(c) #计算外接矩形并且resize成合适大小
roi = ref[y:y + h,x:x + w] # 从二值图上截取区域图
roi=cv2.resize(roi,(57,88))#缩放到指定的大小
digits[i]= roi # 每一个数字对应每一个模板
print(digits)
-----------------举例输出:数字0对应它的像素信息
{0: array([[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]], dtype=uint8)}
2. 银行卡处理
对银行卡进行顶帽处理,突出比周围区域更亮的区域。
image = cv2.imread(args["image"])
cv_show('image',image)
image = myutils.resize(image,width=300)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)
# cv2.getStructuringElement()获取结构原件
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
# cv2.MORPH_TOPHAT它通过从原始图像中减去其开运算结果来实现,这样可以突出比周围区域更亮的区域。
tophat = cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernel)
cv_show('tophat',tophat)
3. 找到数字边框
目标效果:
通过闭操作将银行卡的信息都连接在一起,这样就可以得到一块块轮廓的区域:
# 通过闭操作(先膨胀后腐蚀)将数字连在一起
closeX = cv2.morphologyEx(tophat,cv2.MORPH_CLOSE,rectKernel)
cv_show('gradX',closeX)
# 二值化
# cv2.THRESH_OTSU会自动寻找合适的阈值,适合双峰,需要把阈值参数设置成0
thresh = cv2.threshold(closeX,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)
# 计算轮廓
t_,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('img',cur_img)
# 遍历轮廓,找到数字部分像素区域
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))
4. 模板匹配
遍历每一个符合的轮廓中的每一个数字,明明是一张图片,如何遍历数字呢?
在轮廓中,截取每一个数字所在的区域,将其同之前得到的digits字典对应的元素进行模板匹配,计算匹配得分,从而得到对应的数字。
# 将符合的轮廓,从左到右排序
locs = sorted(locs,key=lambda x:x[0])
output = []
# 遍历每一个轮廓中的数字
for (i,(gX,gY,gW,gH)) in enumerate(locs):# enumerate()返回序列中元素的索引(从0开始)和元素本身
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 = myutils.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 + gY + 5,gY + gH + 5),(0,0,255),1)
# cv2.putText()是OpenCV库中的一个函数,用于图像上添加文本
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("Creadit Card #:{}".format("".join(output)))
cv2.imshow("Image",image)
cv2.waitKey(0)
---------------------
Credit Card Type:Visa
Creadit Card #:4020340002345678
这样我们就识别出了一个银行卡的卡号。
总结
本篇介绍了:
检测识别银行卡卡号的完整流程。
- 得到每个数字的信息,为卡号的模板匹配做准备
- 定位到银行卡上卡号的位置
- 遍历卡号的每个数字,进行模板匹配