计算机视觉——Opencv(银行卡号识别)

一、核心原理

本项目的核心实现逻辑是模板匹配轮廓检测的结合:

  1. 先制作 0-9 数字的标准模板,对模板进行预处理提取数字轮廓并保存;

  2. 对信用卡待识别图像进行一系列预处理(灰度化、顶帽操作、形态学操作等),突出数字区域并消除背景干扰;

  3. 通过轮廓检测定位信用卡上的数字组,再对每个数字组内的单个数字进行提取;

  4. 将提取到的单个数字与预先制作的标准模板进行匹配,得分最高的模板即为识别结果;

  5. 根据信用卡卡号首位数字判断信用卡类型。

二、详细步骤

代码如下:

自定义myutils.py工具类(必须与主程序放在同一目录下):

python 复制代码
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, boundingBoxes


def 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

resize函数:

保持图像长宽比进行缩放,避免图像拉伸变形

sort_counters函数:

解决轮廓检测结果无序的问题,保证数字 / 数字组的顺序与视觉上的左右 / 上下顺序一致。

method="left-to-right",本项目默认按从左到右排序,对应信用卡卡号的阅读顺序。

导入依赖与全局配置

首先导入所需库,并定义信用卡类型映射字典,同时封装一个图像显示辅助函数,方便调试过程中查看每一步的处理结果。

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

注意:FIRST_NUMBER 是一个字典

模板图像中数字的定位处理

准备的模板图片:

代码如下:

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

反相二值化(cv2.THRESH_BINARY_INV):

将图像转为黑底白字,因为轮廓检测对白色前景(高亮区域)的识别效果更好,同时能突出数字的边缘特征

cv2.RETR_EXTERNAL

只检测最外层轮廓,忽略内部子轮廓,数字是独立无嵌套的,该参数能精准提取每个数字的轮廓,避免冗余信息。

digits 字典:

保存标准化后的数字模板,建立 "索引 - 数字模板" 的映射关系

信用卡图像预处理(消除干扰,突出数字)

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

因为顶帽操作的本质是 "原始图像 - 开运算结果",开运算会消除小的高亮区域、平滑大区域边界,从而保留数字这类相对规整的高亮区域。

找到数字边框

python 复制代码
# 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])

两次闭操作的分工:

  • 第一次闭操作(rectKernel):将同一数字组内的单个数字连为一个完整的长条区域,因为信用卡数字是 4 个一组排列,单个数字之间有间隙,闭操作(先膨胀后腐蚀)能填充这些间隙,方便后续定位数字组。

  • 第二次闭操作(sqKernel):消除单个数字内部的小间隙(如数字 8、0 的内部空洞),使数字轮廓更完整,避免后续提取单个数字时出现轮廓断裂。

自动阈值二值化(cv2.THRESH_OTSU):

自动寻找最优阈值,无需手动调整,适合信用卡这类双峰图像(数字高亮、背景较暗,存在两个明显的像素值峰值);使用时需将阈值参数设为 0,算法会自动计算并返回最优阈值。

模板匹配实现数字识别

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

gY - 5:gY + gH + 5, gX - 5:gX + gW + 5适当扩大数字组的提取边界,避免因轮廓定位偏差导致丢失数字边缘信息,提升后续单个数字提取的完整性。

cv2.matchTemplate(..., cv2.TM_CCOEFF):使用相关系数匹配方法,得分越高表示匹配度越好

np.argmax(scores):选取得分最高的模板索引,该索引对应的数据即为识别出的数字,实现数字的精准匹配。

cv2.rectangle:在信用卡图像上绘制数字组的边界框,直观展示数字组的定位结果。

FIRST_NUMBER.get(output[0], 'Unknown Card Type'):根据卡号首位数字判断卡种,同时设置默认值,避免未知卡号首位导致程序报错,提升代码健壮性。

三、运行结果

相关推荐
Juicedata3 小时前
JuiceFS 企业版 5.3 特性详解:单文件系统支持超 5,000 亿文件,首次引入 RDMA
大数据·人工智能·机器学习·性能优化·开源
Piar1231sdafa3 小时前
蓝莓目标检测——改进YOLO11-C2TSSA-DYT-Mona模型实现
人工智能·目标检测·计算机视觉
愚公搬代码3 小时前
【愚公系列】《AI短视频创作一本通》002-AI引爆短视频创作革命(短视频创作者必备的能力)
人工智能
数据猿视觉3 小时前
新品上市|奢音S5耳夹耳机:3.5g无感佩戴,178.8元全场景适配
人工智能
蚁巡信息巡查系统3 小时前
网站信息发布再巡查机制怎么建立?
大数据·人工智能·数据挖掘·内容运营
AI浩3 小时前
C-RADIOv4(技术报告)
人工智能·目标检测
Purple Coder3 小时前
AI赋予超导材料预测论文初稿
人工智能
Data_Journal3 小时前
Scrapy vs. Crawlee —— 哪个更好?!
运维·人工智能·爬虫·媒体·社媒营销
云边云科技_云网融合3 小时前
AIoT智能物联网平台:架构解析与边缘应用新图景
大数据·网络·人工智能·安全
康康的AI博客3 小时前
什么是API中转服务商?如何低成本高稳定调用海量AI大模型?
人工智能·ai