当前位置: 首页 > news >正文

计算机视觉(opencv)——基于模板匹配的信用卡号识别系统

实战:基于模板匹配的信用卡号识别系统

任务书:为某家银行设计一套智能卡号识别系统。要求:传入一张图片,就自动输出信用卡图片中的数字。


1. 概览(整套系统做了什么)

这套系统采用**模板匹配(template matching)**思想进行光学数字识别(OCR-like),总体流程:

  1. 用一张包含 0–9 数字的小模板图片(kahao.png)构建数字模板(每个数字对应一个二值模板)。

  2. 对待识别的信用卡图像(card1.png)做一系列图像预处理(缩放、灰度、形态学操作)以突出数字区域。

  3. 找到可能包含每组 4 位数字的外接矩形(locs)。

  4. 对每个数字位置进一步二值化、分割出单个数字轮廓,并将其 resize 到模板大小后,用 cv2.matchTemplate 与模板集合逐一匹配,取分数最高的模板作为识别结果。

  5. 在原图上标注识别出的每组数字、输出卡号与卡种,保存结果图 card_result.jpg 并在屏幕上显示。


2. 运行前准备(依赖与输入)

必备环境与文件:

  • Python(建议 3.7+)

  • OpenCV(opencv-python),Numpy

  • 你代码中引用的 myutils:需包含至少两个函数:

    • sort_contours(cnts, method="left-to-right") —— 对轮廓排序并返回 (sorted_cnts, boundingBoxes)(代码中只用到第 0 个返回值)

    • resize(image, width=...) —— 将图片按宽度等比例缩放(或等效实现)

  • 输入文件(需存在于脚本工作目录):

    • kahao.png —— 包含 0–9 的模板图片(要求黑底白字或类似,代码中通过二值反转确保黑底白字)

    • card1.png —— 待识别的信用卡照片

  • 运行方式:把脚本保存为 card_recog.py(或任意名字),运行:

    python card_recog.py
    

    执行后会弹出若干 cv2.imshow() 的窗口用于中间调试(模板图、二值图、每组数字的分割图等),最终会弹出 result 窗口并在控制台打印识别结果,同时生成 card_result.jpg

注意:cv2.imshow 在无 GUI 的服务器(比如纯终端)上无法显示;在这种环境下可注释/移除这些显示调用,或把中间结果保存到文件以便检查。


图片准备:

kahao.png

card1.png

card2.png

card3.png

card4.png

card5.png


3. 代码核心模块详解(一行行读懂流程)

运行结果:

自建模块:myutils.py

import cv2"""
myutils.py - 自定义工具函数模块
包含轮廓排序和图像缩放两个常用功能,
用于银行卡号识别系统中的图像处理
"""
def sort_contours(cnts, method='left-to-right'):"""对轮廓进行排序(按指定方向)参数:cnts: 轮廓列表,由cv2.findContours()返回method: 排序方法,可选值包括:'left-to-right' (默认) - 从左到右'right-to-left' - 从右到左'top-to-bottom' - 从上到下'bottom-to-top' - 从下到上返回:排序后的轮廓列表和对应的边界框列表"""# 初始化排序方向标志和排序依据索引reverse = False  # 是否反向排序i = 0  # 排序依据的维度索引(0表示x轴,1表示y轴)# 确定是否需要反向排序if method == 'right-to-left' or method == 'bottom-to-top':reverse = True# 确定排序依据是x轴还是y轴# 垂直方向排序(上下)用y坐标,水平方向排序(左右)用x坐标if method == 'top-to-bottom' or method == 'bottom-to-top':i = 1  # 使用y坐标排序# 为每个轮廓计算边界框(x, y, w, h)boundingBoxes = [cv2.boundingRect(c) for c in cnts]# 将轮廓与对应的边界框组合,按指定维度排序后再拆分# sorted()的key参数指定按边界框的第i个值(x或y)排序(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),key=lambda b: b[1][i],  # b[1]是边界框,b[1][i]是x或y坐标reverse=reverse))return cnts, boundingBoxesdef resize(image, width=None, height=None, inter=cv2.INTER_AREA):"""按比例缩放图像(保持原图宽高比)参数:image: 输入图像width: 目标宽度(若为None则按height计算)height: 目标高度(若为None则按width计算)inter: 插值方法,默认cv2.INTER_AREA(适合缩小图像)返回:缩放后的图像"""# 初始化目标尺寸dim = None# 获取原图高度和宽度(h, w) = image.shape[:2]  # 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

下面按逻辑模块逐步解释代码里每块做的事与设计动机。

3.1 模板图像中数字定位(构建数字模板字典)

import numpy as np
import cv2
import myutils# 指定信用卡类型
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("kahao.png")
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, 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 = cv2.resize(ref[y:y + h, x:x + w], (57, 88))  # 缩放成指定的大小# cv_show('roi', roi)digits[i] = roi  # 每一个数字对应每一个模板
print(digits)
  • kahao.png 变灰度、用阈值并进行了 反转THRESH_BINARY_INV)使得模板是白字黑底或黑字白底得到统一输入(代码注释写的是“黑底白字,方便找轮廓”)。

  • cv2.findContours 找外轮廓(RETR_EXTERNAL),这样能得到 10 个数字的轮廓(假设模板图整齐排列)。

  • 通过 myutils.sort_contours(..., "left-to-right") 保证 0–9 的序列顺序正确(很重要)。

  • 将每个轮廓的 ROI resize 为固定大小 (57, 88) —— 以后对检测到的数字也会统一缩放到相同尺寸,方便 template matching 得分比较。

  • 最终 digits 是一个字典,digits[i] 存放数字 i 的模板图像(i 的顺序依赖 kahao.png 中数字的排列,通常从左到右对应 0,1,2...)。

关键点:模板图的排布必须清晰、无重叠,并且 sort_contours 的返回顺序要和数字索引映射一致,否则识别会混乱。

3.2 信用卡图像预处理(突出数字区域)

"""----------信用卡的图像处理----------"""
image = cv2.imread("card1.png")
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)
  • 将输入图像缩放到宽度 300(保持比例),减少计算量并统一尺度。

  • 顶帽变换(tophat)用于突出图中比周围更亮的细节(卡号通常是凸起或亮于背景,通过顶帽可以增强这些亮区),rectKernel 为长条形结构元素,适合横向数字排布。

3.3 找到数字组(闭操作 + 阈值 + 轮廓)

"""-----------找到数字边框-----------"""
# 1、通过闭操作(先膨胀,再腐蚀)将数字连在一起
closeX = cv2.morphologyEx(tophat, cv2.MORPH_CLOSE, rectKernel)
cv_show('gradX', closeX)
# 2、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('thresh1', thresh)
# 3、计算轮廓
_, 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)
# 4、遍历轮廓,找到数字部分轮廓区域
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])
  • 先用 闭操作(膨胀后腐蚀)把相邻数字连成一块,便于一次性找到一组 4 位数字的边框。

  • 使用 THRESH_OTSU 自动分割(适合双峰直方图)。

  • 通过经验阈值筛选轮廓:长宽比在 2.5 ~ 4.0、宽度在 40~55、高度在 10~20(这些阈值是基于缩放后宽 300 的图像得到的经验值)。

  • 最后按 x(横向)排序,保证识别顺序正确(从左到右)。

提示:如果你换了输入图片尺寸或缩放参数(比如缩放到 600 宽),这些经验阈值需要重新调参。

3.4 逐组分割并模板匹配识别单个数字

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.imwrite('card_result.jpg', image)
cv2.imshow("result", image)
cv2.waitKey(0)
  • 对每组区域单独二值化并计算内部轮廓(每个轮廓对应一个数字)。

  • 再次用 myutils.sort_contours(..., "left-to-right") 确保单组内数字顺序。

  • 将每个单数字 ROI resize 到模板大小 (57,88) 后,用 cv2.matchTemplate 和每个模板比对,取最大得分对应的索引作为预测数字。

  • 在原图画框并写上识别出的字符串,结果追加到 output 列表。

  • 脚本最后用 FIRST_NUMBER[output[0]] 判断卡种(依据卡号首位数字定义在字典 FIRST_NUMBER 中)。

模板匹配选择了 cv2.TM_CCOEFF,这是一个基于相关系数的方法,对亮度对比比较敏感。你也可以尝试其它方法(如 TM_CCOEFF_NORMED)以提升对亮度/缩放差异的鲁棒性。


4. 输出与保存

  • 控制台会打印:

    Credit Card Type: <卡种>
    Credit Card #: <识别出的卡号>
    
  • 识别结果图保存为 card_result.jpg,并弹出窗口显示最终带标注的结果图。


5. 常见问题与调试建议

  1. 模板不匹配 / 识别全部错乱

    • 确认 kahao.png 中数字顺序与 myutils.sort_contours 排序方式一致(一般左到右)。如果 kahao.png 中字体或尺寸和目标图差异大,template match 容易误判。

    • 检查 digits 内容:在构建模板阶段使用 cv2.imshow 查看 roi 是否正确对应 0–9。

  2. 找不到任何 locs(没有检测到数字组)

    • 经验阈值(长宽比和 w/h)是对缩放后图片的经验设定,若图片尺寸或文字大小不同需调节以下参数:myutils.resizewidthar 范围和 w,h 范围。

    • 检查顶帽参数 rectKernel 的大小。不同的卡号刻印/印刷样式对核大小敏感。

  3. 中间窗口太多或在服务器上无法显示

    • 将所有 cv_show(...)(即 cv2.imshow)调用注释掉,或改为保存中间结果到文件夹供离线查看。

  4. 光照/倾斜/遮挡导致识别失败

    • 可先做透视矫正(当信用卡图像是从角度拍摄时),或者使用更鲁棒的二值化(自适应阈值)和更强的形态学处理。

    • 对于复杂场景,建议换用基于 CNN 的数字识别模型,能更好处理变形、噪声与字体差异(见改进建议)。


6. 性能与改进建议(可选,但很实用)

  1. 数据增强与深度学习替代

    • 若需要高鲁棒性(不同字体、不同光照、部分遮挡),考虑训练一个小型 CNN(例如基于 LeNet 或轻量级 MobileNet)做单字符分类,替代模板匹配。模板匹配对字体/噪声敏感。

  2. 更鲁棒的候选区域筛选

    • 使用 MSER、或结合边缘检测 + Hough 线(检测卡号的直线排布)来定位数字行,降低对固定阈值的依赖。

  3. 引入字符校验/格式化

    • 卡号遵循 Luhn 校验算法,可以用 Luhn 校验判定识别结果的合理性并对错误位进行重识别或提示。

  4. 多模板或尺度金字塔匹配

    • 对于不同大小的数字,准备多组模板(多尺度),或对检测到的单字符先做尺度归一化(或在模板匹配时用归一化相关系数 TM_CCOEFF_NORMED)。

  5. 速度优化

    • 将模板全部转换为浮点并预处理,使用 cv2.matchTemplate 的归一化方法以降低计算量,或只在检测到的 ROI 上进行匹配以减少无效匹配次数。


7. 运行示例(重申,便于复制)

  • 目录应该包含:

    card_recog.py       # 你的脚本(包含上述代码)
    kahao.png           # 模板数字图
    card1.png           # 待识别卡片图
    myutils.py          # 包含 sort_contours 与 resize 等函数
    
  • 运行:

    python card_recog.py
    
  • 成功后控制台会看到卡种和卡号,并生成 card_result.jpg


8. 小结(为什么这套方案实用)

  • 优点:实现简单、直观;不依赖训练数据,适合样式固定(银行内部模版、印刷一致)的场景;可以快速原型验证。

  • 局限:对字体、光照、尺度、背景噪声比较敏感;对拍摄角度、遮挡、印刷变化的鲁棒性较差,若目标场景复杂建议过渡到训练的字符识别模型。


文章转载自:

http://C9edkfHY.zcckq.cn
http://KdhQHijC.zcckq.cn
http://aGvbMOZk.zcckq.cn
http://6nai6Mxd.zcckq.cn
http://wn8PpsX6.zcckq.cn
http://FNsuCk7N.zcckq.cn
http://MnJvzGAO.zcckq.cn
http://ygDYtQTL.zcckq.cn
http://7Vr5d1P9.zcckq.cn
http://WO6HNkA5.zcckq.cn
http://MqCxM8T6.zcckq.cn
http://ohAbpLkX.zcckq.cn
http://60Va4SMY.zcckq.cn
http://3WZllF7B.zcckq.cn
http://NkR5ZoyW.zcckq.cn
http://mCmZuP81.zcckq.cn
http://0t6ydNKO.zcckq.cn
http://r3yUMvW8.zcckq.cn
http://lazleJFY.zcckq.cn
http://oqh78q3E.zcckq.cn
http://hrXHcEJi.zcckq.cn
http://HaZnBik5.zcckq.cn
http://d2JRlKIk.zcckq.cn
http://8i5vrQ69.zcckq.cn
http://NrUBgrB7.zcckq.cn
http://jihYGbEN.zcckq.cn
http://YYUfuJKR.zcckq.cn
http://uJIRFMow.zcckq.cn
http://DMoArNra.zcckq.cn
http://jFZZSdzL.zcckq.cn
http://www.dtcms.com/a/375399.html

相关文章:

  • STM32中EXTI原理及其运用
  • 如何在项目中融合Scrum和Kanban
  • 【华为OD】最大子矩阵和
  • 课前准备--空间转录组联合GWAS进行数据分析(gsMap)
  • RPC 与http对比
  • OpenEuler安装gitlab,部署gitlab-runner
  • 电池热管理新突破!《Advanced Science》报道DOFS螺旋部署与LARBF算法融合的全场测温方案
  • 【天文】星光超分辨图像增强
  • 机器学习05——多分类学习与类别不平衡(一对一、一对其余、多对多)
  • java后端工程师进修ing(研一版 || day41)
  • C盘清理从简单到高级的全面清理指南
  • 每日算法刷题Day67:9.9:leetcode bfs10道题,用时2h30min
  • PCL 基于法向量进行颜色插值赋色
  • 四数之和
  • MySql案例详解之事务
  • golang 语言核心
  • 【项目】在AUTODL上使用langchain实现《红楼梦》知识图谱和RAG混合检索(二)RAG部分
  • 安卓学习 之 贞布局FrameLayout
  • 【ISP】Charlite工具实操
  • IntelliJ IDEA断点调试全攻略
  • OceanBase存储过程基本使用
  • 使用 OBD 交互式部署单点OceanBase数据库
  • 内存管理这一块
  • 【深度学习新浪潮】什么是具身智能?
  • Linux tc 常用命令总结(网卡限速、延迟、丢包与整形)
  • Windows 命令行:路径末端的反斜杠
  • Shell脚本编程基本认识
  • Redis 面试
  • 大学地理信息科学该如何学习才能好就业
  • 浅谈“SVMSPro视频切片”技术应用场景