OpenCV篇——项目(一)OCR识别读取银行卡号码

目录

信用卡数字识别系统:前言与代码解析

前言

项目代码

​​​​​​结果演示

代码模块解析

[1. 参数解析模块](#1. 参数解析模块)

[2. 轮廓排序函数](#2. 轮廓排序函数)

[3. 图像预处理模块](#3. 图像预处理模块)

[4. 输入图像处理流程](#4. 输入图像处理流程)

[5. 卡号区域定位](#5. 卡号区域定位)

[6. 数字识别与输出](#6. 数字识别与输出)

系统优势


信用卡数字识别系统:前言与代码解析

前言

信用卡数字识别是金融自动化处理的核心技术之一,通过计算机视觉技术自动提取卡面信息,可应用于支付验证、身份认证等场景。本系统基于模板匹配和图像处理技术,实现对信用卡卡号的自动识别。系统通过预处理、轮廓检测和特征匹配三个关键阶段,准确识别信用卡上的16位数字,并自动判断发卡机构(Visa/MasterCard等)。以下将详细解析代码各模块功能。


项目代码

复制代码
import argparse
import imutils
import numpy as np
import myutils
from imutils import contours
import cv2

 # 设置参数结果
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
                        help="path to input image containing credit card")
ap.add_argument("-t", "--template", required=True,help="path to input template image")
args = vars(ap.parse_args())

 # 指定信用卡类型
FIRST_NUMBER = {
            "3": "American Express",
            "4": "Visa",
            "5": "MasterCard",
            "6": "Discover Card"
}


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]  # 用一个最小的矩形,把找到的形状包起来x,y,h,w
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
    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)
    return resized

# 绘图展示
def cv_show(name,img):
    cv2.imshow(name,img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    return None

# 读取一个模板
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)

# 计算轮廓果
ref_,refCnts = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,ref_,-1,(0,0,255),3)
cv_show("img",img)
print(np.array(refCnts).shape)
refCnts =sort_contours(ref_, 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))
    digits[i] = roi

# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))
squareKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))

# 读取输入图像、预处理
image = cv2.imread(args["image"])
cv_show("image",image)
image = resize(image,width=300)
gary= cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv_show("gary",gary)

# 礼帽操作,突出更明亮的区域
tophat = cv2.morphologyEx(gary,cv2.MORPH_TOPHAT,rectKernel)
cv_show("tophat",tophat)
gradx=cv2.Sobel(tophat,ddepth=cv2.CV_32F,dx=1,dy=0,ksize=-1)
gradx = np.absolute(gradx)
(minVal,maxVal) = (np.min(gradx),np.max(gradx))
gradx = (255 * ((gradx - minVal) / (maxVal - minVal)))
gradx = gradx.astype("uint8")
print(np.array(gradx).shape)
cv_show("gradx",gradx)

# 通过闭操作(先膨胀,再腐蚀)将数字连在一起
gradx = cv2.morphologyEx(gradx,cv2.MORPH_CLOSE,rectKernel)
cv_show("gradx",gradx)
# THRESH_OTSU会自动寻找全局阈值,适合双峰,需要把阈值参数设置为0
thresh = cv2.threshold(gradx,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show("thresh",thresh)

# 再来一个闭操作
thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,squareKernel)
cv_show("thresh",thresh)

# 计算轮廓
thresh_,threshCnts = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = thresh_
cur_img=image.copy()
cv2.drawContours(cur_img,cnts,-1,(0,0,255),3)
cv_show("cur_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])
output=[]
# 遍历每一个轮廓中的数字
for (i,(gx,gy,gw,gh)) in enumerate(locs):
    # initialzie the list of group digits
    groupOutput = []
    # 根据坐标提取数字的区域
    group = gary[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_,groupCnts = cv2.findContours(group.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    group_ = contours.sort_contours(group_,method="left-to-right")[0]
    # 计算每一个组中的数字
    for c in group_:
        (x,y,w,h) = cv2.boundingRect(c)
        digit = group[y:y+h,x:x+w]
        digit = cv2.resize(digit,(57,88))
        cv_show("digit",digit)
        # 计算匹配得分
        scores = []
        # 在模板中计算每一个得分
        for (digitt,digitTempl) in digits.items():
            result = cv2.matchTemplate(digit,digitTempl,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(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)

注意事项:

由于作者用于较新的PyCharm版本是2024版本,它与传统的PyCharm有区分,而且作者在学习过程中同样发现一些问题,这个代码前面可以不用写排序,而最新版本里面不知什么原因调用接口时候报错说没有这个模块函数,于是作者只能将排序等需要的模块分开出来,写入项目中。

​​​​​​结果演示

代码模块解析

1. 参数解析模块
复制代码
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True, help="输入信用卡图像路径")
ap.add_argument("-t", "--template", required=True, help="模板图像路径")
args = vars(ap.parse_args())
  • 功能:通过命令行参数接收输入图像和模板图像路径
  • 参数说明
    • -i/--image:待识别的信用卡图像
    • -t/--template:数字模板图像(包含0-9标准数字)

PyCharm2024通过argparse模块操作命令行设置方法如下,接下来的步骤是中文展示,作者PyCharm已经汉化,若未汉化也可记作者选择按钮的位置点击:

第一步:将需要的图片存放在本人知道的文件路径

原图片如下:

模板图片如下:

我存放的路径如下:

第二步:右击项目代码,选择"更多运行/调试"后,在选择下拉列表中的"修改运行配置"

第三步:在新打开的对话框选择"运行"栏下最后一行点击"展开"

第四步:输入之前存放模板和原图片的路径

注:由于作者存放图片和模板与项目同一个文件夹目录下,因此可以省略具体的路径,若用户存放不在同一个文件夹目录下,那么需要你加上如下格式:

--image

盘符:\文件夹1名称\文件夹2名称\.....\图片名称和后缀名(.png,.jpg等等)

--template

盘符:\文件夹1名称\文件夹2名称\.....\图片名称和后缀名(.png,.jpg等等)

2. 轮廓排序函数
复制代码
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]  # 用一个最小的矩形,把找到的形状包起来x,y,h,w
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
    return cnts, boundingBoxes
  • 功能:对检测到的轮廓按空间位置排序
  • 排序逻辑
    • 计算每个轮廓的边界框 (x,y,w,h)
    • method参数选择排序基准(X轴或Y轴坐标)
    • 支持四种排序方向:左→右、右→左、上→下、下→上
3. 图像预处理模块
复制代码
# 模板预处理流程
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 灰度化
ref = cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]  # 二值化反转
  • 关键操作
    • 灰度转换:将RGB图像转为单通道灰度图
    • 二值化:通过阈值处理突出数字区域
    • 轮廓提取findContours定位每个数字的独立轮廓
    • 模板存储 :将0-9数字按索引存入字典digits
4. 输入图像处理流程
复制代码
# 核心处理链
image = resize(image, width=300)  # 尺寸标准化
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 灰度化
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)  # 顶帽运算
gradx = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0)  # Sobel边缘检测
gradx = cv2.morphologyEx(gradx, cv2.MORPH_CLOSE, rectKernel)  # 闭运算
  • 处理阶段
    1. 尺寸归一化:固定宽度为300像素,保持比例
    2. 顶帽运算:突出亮色区域(信用卡数字通常为亮色)
    3. Sobel算子:检测垂直边缘(数字的竖直笔画)
    4. 形态学闭操作:连接数字笔画形成连续区域
5. 卡号区域定位
复制代码
# 数字区域筛选
for (i,c) in enumerate(cnts):
    (x,y,w,h) = cv2.boundingRect(c)
    ar = w / float(h)  # 宽高比
    if 2.5 < ar < 4.0 and 40<w<55 and 10<h<20:
        locs.append((x,y,w,h))  # 保存候选区域
  • 筛选条件
    • 宽高比 2.5 \< \\frac{w}{h} \< 4.0(信用卡数字的典型比例)
    • 宽度 40 \< w \< 55 像素
    • 高度 10 \< h \< 20 像素
  • 结果 :获得4组数字区域的坐标 locs
6. 数字识别与输出
复制代码
# 模板匹配识别
for c in group_:
    digit = cv2.resize(roi, (57,88))  # 标准化尺寸
    scores = []
    for digitTempl in digits.values():
        score = cv2.matchTemplate(digit, digitTempl, cv2.TM_CCOEFF)
        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(image, "".join(groupOutput), (gx,gy-15), cv2.FONT_HERSHEY_SIMPLEX, ...)
# 得到结果
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. 截取单个数字区域并缩放至模板尺寸
    2. 与0-9模板进行相似度匹配(TM_CCOEFF相关系数法)
    3. 选择最高匹配得分对应的数字
  • 输出形式
    • 红色矩形框标记数字组区域
    • 在区域上方显示识别出的4位数字

系统优势

  1. 形态学操作链:通过顶帽、闭操作等组合优化数字区域提取
  2. 动态模板匹配:适应不同字体和尺寸的数字
  3. 空间约束:利用宽高比和尺寸过滤误检区域
  4. 实时可视化:各阶段结果可实时显示便于调试

该系统实现了从原始图像到卡号识别的完整流程,准确率依赖模板质量和图像清晰度,可通过优化预处理参数进一步提升性能。