银行卡号识别案例
代码实现:
import cv2
import numpy as np
import argparse
import myutils
'''
-i moban.png
-t card1.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())
FIRST_NUMBER ={ "3":"American Express","4": "Visa","5":"MasterCard","6":"Discover Card"}
def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)''' --------模板图像中数字的定位处理----------- '''
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)
_,refCnts, hierarchy = cv2.findContours(ref, 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)roi = ref[y:y + h, x:x + w]roi =cv2.resize(roi,(57,88))cv_show('ro',roi)digits[i]= roi
print(digits)
''' --------信用卡的图像处理----------- '''
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)
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)
closeX = cv2.morphologyEx(tophat,cv2.MORPH_CLOSE,rectKernel)
cv_show('gradX',closeX)
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('thresh1',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('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))
locs = sorted(locs , key=lambda x: x[0])
print(locs)
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', 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 + gW + 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("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)
这段代码实现了基于模板匹配的信用卡数字识别,核心流程分为「模板图像预处理」「信用卡图像预处理」「轮廓检测与分组」「数字模板匹配与识别」四个阶段,以下是详细解析:
1. 初始化与参数解析
- 参数解析:通过
argparse
解析命令行参数,-i
指定信用卡输入图像路径,-t
指定数字模板图像(包含 0-9 数字的参考图)。 - 信用卡品牌映射:
FIRST_NUMBER
字典通过卡号首数字映射信用卡品牌(如 Visa 以 4 开头)。 - 辅助函数:
cv_show
用于调试时显示图像并等待按键。
2. 模板图像预处理(制备数字模板)
目的是从模板图像中提取 0-9 的标准数字模板,供后续匹配使用:
- 读入与灰度转换:读取模板图像并转为灰度图。
- 反相二值化:
cv2.threshold(..., THRESH_BINARY_INV)
将数字转为白色、背景转为黑色,便于轮廓检测。 - 轮廓检测与排序:
cv2.findContours
提取所有外部轮廓(每个数字对应一个轮廓)。myutils.sort_contours
(自定义函数)按从左到右排序轮廓,确保数字顺序与视觉一致。
- 提取数字 ROI 并标准化:遍历每个轮廓,提取数字区域(ROI),并缩放到统一尺寸
(57, 88)
,存储到digits
字典(键为索引,值为数字模板)。
3. 信用卡图像预处理(增强数字区域)
目的是从信用卡图像中突出数字区域,便于后续轮廓检测:
- 读入与尺寸调整:读取信用卡图像,按比例缩放到宽度 300(方便统一处理)。
- 灰度转换:转为灰度图,消除颜色干扰。
- 形态学操作(增强数字):
- 顶帽操作(TOPHAT):用长方形结构核
rectKernel
突出比背景亮的区域(即数字区域)。 - 闭运算(CLOSE):先膨胀后腐蚀,用
rectKernel
连接水平方向的数字,使 “4 位一组的数字” 形成整体区域;再用正方形核sqKernel
连接数字内部间隙,让轮廓更完整。
- 顶帽操作(TOPHAT):用长方形结构核
- 二值化(OTSU 自适应阈值):
cv2.threshold(..., THRESH_BINARY | THRESH_OTSU)
自动确定阈值,将图像转为黑白二值图(数字为白,背景为黑)。
4. 轮廓筛选与数字分组
目的是从预处理后的图像中,筛选出 ** 信用卡数字组(通常 4 位一组)** 的轮廓:
- 遍历所有轮廓,通过宽高比(
ar
)和宽 / 高的数值范围筛选符合 “数字组特征” 的轮廓(如信用卡数字组宽高比约 2.5-4,宽度 40-55,高度 10-20,需根据实际图像调整)。 - 按轮廓的 x 坐标排序,确保从左到右处理数字组。
5. 数字模板匹配与识别
对每个筛选出的数字组,逐数字进行模板匹配:
- 提取数字组 ROI:扩展数字组的边界(
gY -5
等),避免数字被截断。 - 数字组二值化:对提取的区域再次二值化,与模板格式一致。
- 单数字轮廓检测与排序:提取数字组内的单个数字轮廓,按从左到右排序。
- 模板匹配:
- 对每个单数字 ROI,缩放到模板尺寸
(57, 88)
。 - 用
cv2.matchTemplate
(方法为TM_CCOEFF
)与digits
中所有数字模板匹配,计算 “匹配得分”。 - 选择得分最高的模板,其索引对应数字(如索引 0 对应模板中最左侧数字)。
- 对每个单数字 ROI,缩放到模板尺寸
- 结果绘制与存储:在原图上绘制数字组的矩形框,并将识别出的数字用
cv2.putText
显示;同时将数字存入output
列表,最终拼接为完整卡号。
6. 信用卡品牌与卡号输出
- 从
output
中取首数字,通过FIRST_NUMBER
字典判断信用卡品牌。 - 打印品牌与完整卡号,并显示最终图像。
关键注意事项
- 参数适配:形态学核的尺寸、轮廓筛选的宽高比 / 范围,需根据图像分辨率、数字大小调整。
- 模板质量:模板图像的清晰度、数字完整性,直接影响匹配准确率。
- 排序依赖:
myutils.sort_contours
(自定义轮廓排序函数)是确保 “数字顺序正确” 的关键。 - 匹配方法:
TM_CCOEFF
适合黑白模板匹配,因它对亮度差异不敏感,更关注形状相关性。