【计算机视觉入门】基于 OpenCV 的银行卡号识别综合案例

文章目录

  • 一、案例概述
  • 二、环境与依赖
  • [三、 `argparse` 简单教学](#三、 argparse 简单教学)
  • 四、工具函数模块(myutils.py)
    • [4.1 轮廓排序函数 sort_contours](#4.1 轮廓排序函数 sort_contours)
    • [4.2 等比例缩放函数 resize](#4.2 等比例缩放函数 resize)
  • 五、主程序完整流程详解
    • [5.1 命令行参数解析](#5.1 命令行参数解析)
    • [5.2 卡组织映射字典](#5.2 卡组织映射字典)
    • [5.3 模板图像预处理](#5.3 模板图像预处理)
    • [5.4 银行卡图像预处理](#5.4 银行卡图像预处理)
    • [5.5 数字组区域筛选](#5.5 数字组区域筛选)
    • [5.6 模板匹配识别数字](#5.6 模板匹配识别数字)
    • [5.7 结果绘制与输出](#5.7 结果绘制与输出)
  • 六、完整代码实现
  • 七、总结

一、案例概述

本文将详细讲解一个基于 OpenCV 的银行卡号识别案例,采用模板匹配的经典方案实现。案例通过形态学处理定位银行卡数字区域,再利用模板匹配算法逐个识别数字,最终输出完整卡号与卡组织类型。

案例包含三个核心文件:

  • myutils.py:封装轮廓排序、图像缩放等通用工具函数
  • 银行卡识别.py:主程序,包含完整的识别流程
  • args.py:argparse 命令行参数解析基础示例

二、环境与依赖

依赖库 作用
OpenCV (cv2) 图像处理、形态学操作、轮廓检测、模板匹配
NumPy 数值计算与数组操作
argparse Python 内置库,解析命令行参数

三、 argparse 简单教学

python 复制代码
import argparse

# 1. 创建解析器对象
parser = argparse.ArgumentParser()

# 2. 定义我们期望接收的参数
# --a 和 --b 是参数的名字,type=str 表示期望接收字符串类型,default 是默认值
parser.add_argument('--a', type=str, default='hello', help='a的值')
parser.add_argument('--b', type=str, default='python', help='b的值')

# 3. 解析参数
# 这一步会去检查你运行脚本时输入的命令,并把参数值提取出来
opt = parser.parse_args()

# 4. 使用参数
# 通过 opt.参数名 的方式来获取值
c = opt.a
d = opt.b
print(c, d)

运行效果演示:

假设我们将上面的代码保存为 test.py,那么会有以下几种运行方式:(终端中输入)

  1. 不传任何参数
  • 命令:python test.py
  • 结果:hello python
  • 解释:因为没有提供 --a--b,程序会使用我们在 default 中设置的默认值。
  1. 传入部分或全部参数
  • 命令:python test.py --a "你好" --b "世界"
  • 结果:你好 世界
  • 解释:程序接收到了我们传入的值,并用它们覆盖了默认值。

为什么要用它?

在银行卡识别的案例中,使用 argparse 的好处非常明显:

  • 灵活性:我们不需要为了测试不同的银行卡图片而去反复修改代码里的 image_path = 'card1.png'
  • 便捷性:只需在命令行中运行 python 银行卡识别.py -i 新的卡号图片.png -t 模板图片.png 即可,非常方便。

四、工具函数模块(myutils.py

该文件封装了两个高频使用的图像处理工具函数,是案例的基础组件。

4.1 轮廓排序函数 sort_contours

python 复制代码
import cv2

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))
    return cnts, boundingBoxes

功能说明:

  • 支持四种排序方式:left-to-right(从左到右)、right-to-left、top-to-bottom、bottom-to-top
  • 核心原理:先计算每个轮廓的外接矩形(x, y, w, h),再按 x 坐标(横向排序)或 y 坐标(纵向排序)进行排序
  • zip(*...) 解包语法:将排序后的 (轮廓,外接矩形) 元组列表重新拆分为两个独立列表

4.2 等比例缩放函数 resize

python 复制代码
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)
    return resized

功能说明:

  • 传入 widthheight 其中一个参数,即可保持宽高比等比例缩放
  • 默认插值方法为 cv2.INTER_AREA(面积插值),适合图像缩小场景

注意:cv2.resizedsize 参数格式为 (宽,高),与 shape 返回的 (高,宽) 顺序相反。


五、主程序完整流程详解

5.1 命令行参数解析

python 复制代码
import numpy as np
import argparse
import cv2
import myutils

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 OCR-A image')
args = vars(ap.parse_args())
  • 使用 argparse 接收两个必填参数:输入银行卡图片名称、数字模板图片名称。

  • vars() 将解析结果转为字典,通过 args['image']args['template'] 取值。

5.2 卡组织映射字典

python 复制代码
FIRST_NUMBER = {
    '3': 'American Express',
    '4': 'Visa',
    '5': 'MasterCard',
    '6': 'Discover Card'
}

根据银行卡号首位数字判断卡组织。

5.3 模板图像预处理

模板图像是包含 0-9 十个数字的图片,预处理目标是提取每个数字的轮廓并建立模板库。

步骤 1:灰度化 + 二值化

python 复制代码
img = cv2.imread(args['template'])
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
  • 转为灰度图减少计算量
  • THRESH_BINARY_INV 反向二值化,得到黑底白字,便于后续轮廓检测

步骤 2:轮廓检测与排序

python 复制代码
refCnts = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
refCnts = myutils.sort_contours(refCnts, method='left-to-right')[0]
  • cv2.RETR_EXTERNAL 只检测最外层轮廓
  • cv2.CHAIN_APPROX_SIMPLE 压缩轮廓点,只保留端点
  • [-2] 是兼容写法:OpenCV3 返回 3 个值,OpenCV4 返回 2 个值,取倒数第二个始终能得到轮廓列表

步骤 3:建立数字模板字典

python 复制代码
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))
    digits[i] = roi

遍历每个数字轮廓,裁剪出数字区域并统一缩放到 57×88 尺寸,存入 digits 字典,键为数字 0-9,值为对应数字的二值图像。

5.4 银行卡图像预处理

目标:从复杂的卡面背景中定位出数字区域。

步骤 1:读取与基础预处理

python 复制代码
image = cv2.imread(args['image'])
image = myutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

步骤 2:顶帽运算(Top Hat)

python 复制代码
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
  • 顶帽 = 原图 - 开运算(先腐蚀后膨胀)
  • 作用:突出亮区域细节,抑制大面积暗背景,消除卡面背景纹理,保留数字等亮细节
  • 使用 9×3 的长方形卷积核,适配数字横向长条特征

步骤 3:闭运算 + 二值化

python 复制代码
closeX = cv2.morphologyEx(tophat, cv2.MORPH_CLOSE, rectKernel)
thresh = cv2.threshold(closeX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
  • 闭运算(先膨胀后腐蚀):将同组内分散的数字像素连接成整体块
  • THRESH_OTSU:大津法自动计算最优阈值,适合双峰直方图,阈值参数设为 0 即可

步骤 4:二次闭运算

python 复制代码
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)

使用正方形核再次闭运算,填充数字块内部的小孔洞,让轮廓更完整。

5.5 数字组区域筛选

python 复制代码
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
locs = []
for c in 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))

locs = sorted(locs, key=lambda x: x[0])

核心筛选逻辑:

  • 计算每个轮廓外接矩形的宽高比 ar,银行卡每组 4 位数字宽高比通常在 2.5~4.0 之间
  • 再通过宽、高的像素范围进一步过滤噪声轮廓
  • 最后按 x 坐标从左到右排序,保证数字组顺序正确

注意:此处的宽高范围是基于 width=300 缩放后的图像设定的,如果修改了缩放尺寸,需要对应调整阈值。

5.6 模板匹配识别数字

遍历每一组数字区域,逐个识别单数字。

python 复制代码
output = []
for (gX, gY, gW, gH) in locs:
    groupOutput = []
    # 裁剪数字组区域,留5像素边距避免切到数字
    group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]
    # 二值化
    group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    # 检测组内单个数字轮廓
    digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
    digitCnts = myutils.sort_contours(digitCnts, method="left-to-right")[0]

对每个数字轮廓进行模板匹配:

python 复制代码
    for c in digitCnts:
        (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)
            scores.append(score)

        groupOutput.append(str(np.argmax(scores)))

模板匹配原理:

  • 使用 cv2.TM_CCOEFF(相关系数匹配法),值越大匹配度越高
  • cv2.minMaxLoc 返回匹配结果的最小值、最大值及对应坐标
  • 取 10 个模板中得分最高的对应数字作为识别结果

5.7 结果绘制与输出

python 复制代码
    cv2.rectangle(image, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
    cv2.putText(image, ''.join(groupOutput), (gX, gY - 15), 
                cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
    output.extend(groupOutput)

print(f'Credit Card Type:{FIRST_NUMBER[output[0]]}')
print(f'Credit Card #:{"".join(output)}')
cv2.imshow('Image', image)
cv2.waitKey(0)

最终在原图上绘制识别框与识别结果,控制台输出卡组织类型和完整卡号。


六、完整代码实现

python 复制代码
import numpy as np
import argparse   # python内置库
import cv2
import myutils

"""
-i card1.png
-t kahao.png
"""

# 设置参数
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中的一个内置函数,用于返回对象的属性和值的字典。

# 指定信用卡类型
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)

# 计算轮廓: cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),
# cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
refCnts = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cv2.drawContours(img,refCnts,-1,(0,0,255),3)
cv_show('refCnts',img)

refCnts = myutils.sort_contours(refCnts,method='left-to-right')[0]   # 排序,从左到右,从上到下
digits = {}   # 保存模板中每个数字对应的像素值

# i in (0:)   c in refCnts
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))   # 缩放到指定的大小
    cv_show('roi',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)

# --------找到数字边框--------
# 1、通过闭操作(先膨胀,再腐蚀)将数字连在一起
closeX = cv2.morphologyEx(tophat,cv2.MORPH_CLOSE,rectKernel)
cv_show('closeX',closeX)

# 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('close2',thresh)

# 计算轮廓
cnts = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cnts_img = image.copy()
cv2.drawContours(cnts_img,cnts,-1,(0,0,255),3)
cv_show('cnts_img',cnts_img)

# 遍历轮廓,找到数字部分像素区域
locs = []
for c in 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))

# 将符合的轮廓从左到右排序
locs = sorted(locs,key=lambda x:x[0])
print(locs)

output = []

# 遍历每一个轮廓中的数字
for (gX,gY,gW,gH) in 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)

    # 计算每一组的轮廓
    digitCnts = cv2.findContours(group.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
    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(f'Credit Card Type:{FIRST_NUMBER[output[0]]}')
print(f'Credit Card #:{"".join(output)}')
cv2.imshow('Image',image)
cv2.waitKey(0)

注意:

  • 运行前右键修改运行配置,在脚本形参中输入如:-i card1.png -t kahao.png
  • template.png为模板图, card.png为银行卡图,都在同级目录下

运行效果:


七、总结

本文完整拆解了基于模板匹配的银行卡号识别案例,从工具函数到主流程,涵盖了形态学处理、轮廓检测、筛选定位、模板匹配全链路。这是一个非常经典的 OpenCV 入门案例,能帮助理解传统图像处理的基本思路。

相关推荐
龙虾PRO1 小时前
2026 Headless CMS实操指南:区别传统与解耦CMS,落地全渠道内容复用
人工智能
fīɡЙtīиɡ ℡1 小时前
RAG优化
人工智能
语音之家1 小时前
多方言ASR自蒸馏:学会方言,不忘普通话
人工智能·消融实验·方言 sft
禁默1 小时前
2026年Mac被控远控深度测评横评:六大主流远程软件哪款更能打
人工智能·远程
AR-26710-1 小时前
机器学习复习Day1——线性回归编程作业
人工智能·机器学习
呆呆敲代码的小Y1 小时前
10 分钟搞懂 cua:开源 AI 操作电脑基础设施,附 Python 沙箱与 Agent 上手代码
人工智能·python·开源·ai agent·awesome·llm应用·cua
工具分享1 小时前
带店托管必用爆单AI选品,一人公司轻松掌握
人工智能·python
l1258651 小时前
# RAG低延迟架构设计:从5秒到500ms的优化全链路
数据库·人工智能·python·langchain
JavaPub-rodert1 小时前
DeepSeek 终于能看图了:V4 Flash Vision 上线,我觉得真正重要的是这件事
人工智能·codex