OpenCV 信用卡号识别

在计算机视觉领域,模板匹配是识别固定格式目标(如数字、文字)的常用技术。本文将以信用卡号自动识别为例,详细讲解如何使用 Python+OpenCV 实现从命令行参数定义、模板图像处理、轮廓识别到最终卡号匹配的完整流程,并提供可直接运行的代码与操作步骤,适合 OpenCV 初学者入门实践。

一、项目背景与核心技术

1. 项目目标

实现对信用卡图像中卡号的自动识别,包括:

  • 读取用户指定的信用卡图像和数字模板;
  • 定位信用卡中的卡号区域;
  • 分割单个数字并与模板匹配;
  • 输出卡号及对应信用卡类型(如 Visa、MasterCard)。

2. 核心技术

  • 命令行参数解析:使用argparse模块让用户灵活输入图像路径;
  • 图像预处理:灰度化、二值化、形态学操作(顶帽、闭操作)突出目标区域;
  • 轮廓识别与排序cv2.findContours提取轮廓,自定义函数按位置排序;
  • 模板匹配cv2.matchTemplate实现数字识别,计算匹配得分筛选最优结果。

二、自定义命令行参数


1、什么是自定义命令行参数


        自定义命令行参数是指在编写命令行程序时,由程序开发者定义并期望用户通过命令行界面(CLI)输入的参数。这些参数允许用户向程序传递额外的信息或配置,以便程序能够根据这些信息执行不同的操作或行为。

        在Python中,使用argparse模块可以方便地定义和解析自定义命令行参数。argparse模块提供了丰富的功能来定义参数的类型、默认值、是否必需等属性,并能够在用户没有按预期提供参数时给出友好的帮助信息。

2、自定义命令行参数的类型


        1)位置参数(Positional Arguments)
                位置参数是按照位置顺序提供的参数。在命令行中,它们紧跟在程序名称之后,不需要使用--或-前缀。位置参数的数量和顺序通常是固定的,用户必须按照程序的要求提供它们。

        2)可选参数(Optional Arguments)
                可选参数,也称为选项或开关,是可选提供的参数。它们通常使用--(长选项)或-(短选项)前缀来标识。可选参数可以指定默认值,这样即使用户没有提供它们,程序也能正常运行。可选参数还可以指定为必需的,这样如果用户没有提供它们,程序就会报错或显示帮助信息。

        3)子命令(Sub-commands)
                子命令允许程序具有多个相关的功能,每个功能都通过一个特定的子命令来调用。子命令是可选参数的一种特殊形式,它们通常用于实现类似git这样的命令行工具的复杂功能。

3、定义参数时注意


参数名称:选择一个简短、直观且易于理解的参数名称。
参数类型:确定参数应该接受的数据类型(如字符串、整数、浮点数等)。
默认值:为可选参数指定一个默认值,以便在用户没有提供参数时程序能够正常运行。
是否必需:确定参数是否必需提供。对于必需参数,如果用户没有提供,程序应该报错或显示帮助信息。
帮助信息:为每个参数提供清晰的帮助信息,以便用户了解参数的作用和用法。


4、代码案例

import argparse  
  
# 创建ArgumentParser对象  
parser = argparse.ArgumentParser(description='This is a sample script.')  
  
# 添加位置参数  
parser.add_argument('name', type=str, help='Your name')  
  
# 添加可选参数  
parser.add_argument('--age', type=int, default=25, help='Your age')  
parser.add_argument('-c', '--city', type=str, help='Your city')  
  
# 解析命令行参数  
args = parser.parse_args()  
  
# 使用解析后的参数  
print(f'Hello, {args.name}!')  
print(f'You are {args.age} years old.')  
if args.city:  
    print(f'You live in {args.city}.')

运行方法:

1)pycharm中右击鼠标,打开修改运行配置

                           

输入参数,格式与定义时一致:

2)终端运行

打开终端,输入python + 代码地址,再输入参数内容,注意参数顺序

二、项目实施

1、案例图片

2、自定义参数

定义参数内容为待识别的信用卡号,以及0-1的数字模版

import numpy as np
import argparse   # py内置参数,用于自定义命令行参数
import cv2
import myutils    # 自定义的函数用法
#
"""
-i card1.png
-t kahao.png 
"""
ap = argparse.ArgumentParser()  # 创建一个自定义命令行参数对象,其中存放参数的内容
ap.add_argument("-i","--image",required=True,help="path to input image")  # 定义一个参数,命名为i或image,require=True表示参数是必须参数,help表示提示作用
ap.add_argument('-t',"--template",required=True,help="path to template OCA-A image")  # 同样定义一个必须参数t或template
args = vars(ap.parse_args())  # parse_args表示解析命令行参数,默认从sys.argv读取,得到一个命名空间,vars表示将这个空间转换为字典类型
 
 
FIRST_NUMBER = {   # 定义一个字典,用于存放信用卡类型的标识
    "3":"American Express"
    ,"4":"Visa"
    ,"5":"MasterCard"
    ,"6":"Discover Card"
}

3、处理模版图像

img = cv2.imread(args["template"])  # 读取图片,args["template"]为上述定义的参数用法的字典
cv_show('img',img)  # 调用自定义函数用法,展示原图
 
ref = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)   # 灰度图
cv_show('ref',ref)
# 对灰度图进行二值化处理,灰度值大于10转变为255,小于10转变为0,cv2.THRESH_BINARY_INV表示反二值化,即黑白颠倒(小于10的转变为255,大于10的转变为0)
# 返回两个参数,第一个是阈值10,一个是二值化图像,使用索引[1]只返回后者
ref = cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]
cv_show('ref_thr',ref)

运行结果:

4、识别数字轮廓

# 使用cv2.findContours识别轮廓,ref.copy()表示生成图像副本其为待识别图像
# cv2.RETR_EXTERNAL只检索最外层的轮廓
# cv2.CHAIN_APPROX_SIMPLE表示使用轮廓近似法,只保留轮廓端点
_,refCnts,hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)  # 返回三个参数,一个是原图,一个是轮廓的列表(全是点),以及轮廓的层次结构
cv2.drawContours(img,refCnts,-1,(0,0,255),3)  # 绘制轮廓,在原图上绘制轮廓,轮廓集为refCnts,-1表示绘制所有轮廓
cv_show('img',img)
 
refCnts = myutils.sort_contours(refCnts,method="left-to-right")[0]  # 调用自定义模块中的方法sort_contours对轮廓图进行排序返回索引对应的值和,利用索引[0],返回排序后的轮廓和轮廓的边界信息
digits = {}    # 定义一个空字典,保存模板中每个数字对应的像素值
for (i,c) in enumerate(refCnts):  # 使用numerate将每一个轮廓组成一个索引序列,返回轮廓索引和轮廓,此时i值表示轮廓中的值0-9
    (x,y,w,h) = cv2.boundingRect(c)  # 计算轮廓外接矩形,返回坐标和高宽
    roi = ref[y:y + h,x:x + w]    # 裁剪出每个数字对应的轮廓
    roi = cv2.resize(roi,(57,88))   # 缩放轮廓到指定的大小
    digits[i] = roi   # 每一个数字对应每一个模板,将他们存入字典

5、处理信用卡图片

1)读取图片

image = cv2.imread(args['image'])  # 读取图像
cv_show('image',image)  # 展示图像
image = myutils.resize(image,width=300)   # 缩放图像,将原始图像的宽度变为300像素值,高度根据比例自动改变
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)   # 灰度图
cv_show('gray',gray)   # 展示灰度图

运行结果:

2)顶帽操作突出亮区域

# 顶帽操作,实出更明亮的区域,清除背景图,原因是背景颜色变化小,不被腐蚀掉。
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(9,3))   # 初始化卷积核,创建一个9x3的卷积核用于腐蚀膨胀等操作,cv2.MORPH_RECT表示结构为矩形
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
# cv2.morphologyEx表示图像形态学操作
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)   # 对信用卡的灰度图进行顶帽操作,cv2.MORPH_TOPHAT表示顶帽,突出亮色,rectKernel表示卷积核,顶帽 = 厡始图像 - 开运算结果(先腐蚀后膨胀)
cv_show('tophat',tophat)  # 展示顶帽后的图片

6、定位卡号轮廓

# """ 找到数字边框- """
# 图像形态学处理,通过闭操作(先膨胀,再腐蚀)将数字连在一起
closeX =cv2.morphologyEx(tophat, cv2.MORPH_CLOSE, rectKernel)
cv_show('gradX',closeX)
# 将灰度图转变为二值化图像,cv2.THRESH_BINARY | cv2.THRESH_OTSU为阈值类型和方法的组合,前者指定二值化,后者自动寻找合适阈值
thresh = cv2.threshold(closeX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
# 再来一个闭操作,sqKernel为上述定义的卷积核
thresh = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel)
cv_show('thresh',thresh)
 
 
# 检索最外层的轮廓,使用轮廓近似法,只保留轮廓端点,返回原图,轮廓的列表(全是点),以及轮廓的层次结构
t_,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))   # 将符合条件的轮廓存放进列表

运行结果如下:

7、识别卡号的每个数字

# # 将符合的轮廓从左到右排序
locs = sorted(locs,key=lambda x:x[0])
output =[]
# 遍历每一个对应的索引和轮廓边界信息
for (i,(gX,gY,gW,gH)) in enumerate(locs):  # enumerate用于返回一个可迭代对象的索引和数据,这里是轮廓索引、x坐标、y坐标、宽高
    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]  # 将每个轮廓进行二值化处理(黑白),返回两个值,一个是阈值,一个是图像,索引[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]  # 调用自定义模块中的函数将识别的数字轮廓进行排序,排序依据为x轴大小,即卡面上的先后顺序,返回排序后的轮廓和轮廓边界
 
    # 计算每一组中的每一个数值
    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))   # 缩放每个数字轮廓
        cv_show('roi', roi)

运行结果:

                                            

8、使用模板匹配,计算匹配得分,并绘制

(此处需缩进,在上述for循环下)

        """  使用模板匹配,计算匹配得分  """
 
        scores = []
        # 在模板中计算每一个得分
        for (digit,digitROI) in digits.items():  # 此处digits为上述定义并存放这数字模版中数字轮廓及其对应的数字代号
            # 模板匹配
            result = cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF)  # 对卡号数字轮廓和数字对应的轮廓进行模板匹配,使用相关系数匹配法
            (_,score,_,_) = cv2.minMaxLoc(result)   # 找到上述模板匹配相关系数最大值,只要score,其他返回值忽略
            scores.append(score)
 
        # 得到最合适的数字
        groupOutput.append(str(np.argmax(scores)))   # 返回scores中最大相关系数的索引,然后转换为字符串类型增加到上述定义的空列表
 
    # 画出来
    cv2.rectangle(image,(gX-5,gY-5),(gX+gW+5,gY+gH+5),(0,0,255),1)  # 在image图像上绘制矩形,其后参数表示左上角坐标、右下角坐标
    # 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)

运行结果如下:

### 基于OpenCV的信用卡卡号数字识别 以下是基于OpenCV和Python实现信用卡卡号数字识别的一个完整示例。此方法结合了图像预处理、模板匹配以及字符分割的技术。 #### 图像预处理 在开始之前,需要加载信用卡图片并对它进行灰度转换和形态学操作来增强数字区域的效果[^4]: ```python import cv2 # 加载信用卡图片并调整尺寸 cardImg = cv2.imread('credit_card_01.png') cardImg = cv2.resize(cardImg, (300, int(float(300 / cardImg.shape[1]) * cardImg.shape[0])), interpolation=cv2.INTER_AREA) # 转换为灰度图 cardImg_gray = cv2.cvtColor(cardImg, cv2.COLOR_BGR2GRAY) # 定义矩形核用于礼帽操作 rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # 执行礼帽操作以突出亮区中的暗细节 cardImg_tophat = cv2.morphologyEx(cardImg_gray, cv2.MORPH_TOPHAT, rectKernel) ``` 接着可以通过闭运算填充数字之间的间隙,并使用Otsu阈值法二值化图像[^3]: ```python # 应用闭运算以连接数字间的缝隙 closing = cv2.morphologyEx(cardImg_tophat, cv2.MORPH_CLOSE, sqKernel) # 使用Otsu阈值法二值化图像 _, thresh = cv2.threshold(closing, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) ``` #### 数字检测与模板匹配 为了定位信用卡号码的位置,可以寻找轮廓并将它们按面积筛选出来。之后对这些候选区域执行模板匹配[^2]: ```python # 寻找轮廓 contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 存储模板字典 { '0': template0, ..., '9': template9 } digitTemplates = {} for i in range(10): digitTemplates[str(i)] = cv2.imread(f'digit_template_{i}.png', 0) outputDigits = [] # 对每个轮廓进行分析 for contour in contours: (x, y, w, h) = cv2.boundingRect(contour) # 如果宽度和高度不符合预期,则跳过该区域 if not (w >= 15 and h >= 30): continue roi = thresh[y:y+h, x:x+w] bestScore = -1 matchedDigit = '?' # 将ROI与每张数字模板逐一比较 for digit, template in digitTemplates.items(): result = cv2.matchTemplate(roi, template, cv2.TM_CCOEFF_NORMED) (_, score, _, _) = cv2.minMaxLoc(result) if score > bestScore: bestScore = score matchedDigit = digit outputDigits.append(matchedDigit) print("".join(outputDigits)) ``` 上述代码片段展示了如何通过模板匹配找到最接近的数字模板,并将其加入最终输出列表中[^2]。 #### 结果展示 最后一步是将解析得到的结果打印至控制台或者绘制回原图上以便验证效果: ```python cv2.putText(cardImg, "".join(outputDigits), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow("Credit Card", cardImg) cv2.waitKey(0) ``` 这样就完成了整个信用卡卡号数字识别的过程[^1]。 --- 问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值