前言
前期我们已经学了图像检测,边缘检测,轮廓检测,模板匹配,现在我们通过一个实战来综合运用以上知识点。
一、项目需求
基于OpenCV计算机视觉算法,对银行卡图像进行处理,精准识别并提取图片中的完整银行卡卡号。

项目配套提供0-9标准数字模板图像,无需自行采集数字样本,依托模板匹配算法完成数字识别。

二、项目思路分析
-
处理数字模板图像:提取模板中0-9单个数字的有效区域,建立数字与图像的对应关系并保存,构建标准数字模板库。
-
处理银行卡原图:通过图像预处理、轮廓检测等操作,精准定位银行卡号区域,分割提取出每一个独立数字图像。
-
执行模板匹配:将银行卡提取的单个数字图像,与提前构建的0-9标准模板逐一比对匹配,判定对应的真实数字。
-
整合输出结果:按从左至右的正确顺序拼接识别出的数字,最终输出完整的银行卡号。
三、开始实战
1. 导包
python
python
import numpy as np
import argparse
import cv2
import myutils
2. 使用 argparse 解析命令行参数
argparse 模块用于解析命令行参数,当需要传递多个参数时,它可以避免固定位置传参的繁琐,使脚本调用更加灵活和规范。
4. 对模板进行处理
4.1 读取模板图片、灰度转换、反二值化
读取图片,将彩色图片处理成灰度图,二值化处理,偏白变成黑色,偏黑变成白色,最后我们会得到一张黑底白字的照片,方便寻找轮廓。
python
python
img = cv2.imread(args['template'])
cv_show('template',img)
#处理成灰度图
ref = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#二值化处理,黑底白字
ref = cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]
cv_show('ref',ref)

4.2 查找并绘制数字轮廓
寻找轮廓,通过findContours 返回所有父类轮廓和主要像素点矩阵,该轮廓子轮廓统统不要。findContour有返回值,取倒数第二个。
python
#寻找轮廓
refCnts = cv2.findContours(ref,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
#在原图上画出检测的轮廓
cv2.drawContours(img,refCnts,-1,(0,255,0),3)
cv_show('refCnts',img)

4.3 轮廓排序,遍历保存数字模板字典
通过自定义包的函数,对获取的轮廓矩阵进行排序,遍历所有数字轮廓,裁剪ROI并统一尺寸,存入字典对应0-9数字。
python
#轮廓排序
refCnts = myutils.sort_contours(refCnts)[0]
digits = {}
#存储进字典中
for i ,j in enumerate(refCnts):
(x,y,w,h) = cv2.boundingRect(j)
#提取对应的像素矩阵
roi = ref[y:y+h,x:x+w]
#重置大小
roi = cv2.resize(roi,(57,88))
cv_show('roi',roi)
digits[i] = roi
print(digits)
裁剪示例:


字典:

自定义工具 (myutils.py)
将该文件与主程序放置在同一项目目录下即可直接调用。
自定义工具包 myutils 包含两个核心工具函数,其中 sort_contours 用于对检测得到的无序轮廓矩阵进行规整排序。其原理为:获取所有轮廓的外接矩形,通过矩形左上角横坐标完成排序。数字模板中0-9的排布从左至右有序,对应的轮廓外接矩形横坐标依次递增,因此可通过x轴坐标大小判定轮廓先后顺序。该函数最终返回排序完成的轮廓矩阵与对应外接矩形。由于 OpenCV 检测到的轮廓默认无序、随机排列,必须通过排序规整顺序,才能精准匹配0-9对应的数字轮廓,保证后续模板匹配结果准确。
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] #返回x,y,w,h
(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) #默认为cv2.INTER_AREA,即面积插值,适用于缩放图像。
return resized
5. 处理银行卡号
5.1 读取银行卡图片、缩放、灰度转换
读取银行卡原图,对图像进行等比例缩放统一尺寸,再将彩色图像转为灰度图,为后续形态学操作、轮廓检测做预处理。
python
image = cv2.imread(args['img'])
cv_show('img',image)
image = myutils.resize(image,width=300)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv_show('gray',gray)

5.2 顶帽运算,去除背景噪声、突出字符细节
灰度图像存在大量背景干扰噪声,通过顶帽形态学运算,抑制复杂背景,提亮、突出卡号字符细节。
python
#对灰度图进行操作,灰度图中有过多的噪声(背景)
#顶帽,可以去除背景,突出细节
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)

5.3 第一次闭运算,聚合字符连通区域
闭运算为先膨胀、后腐蚀,可将分散的数字像素点聚合为完整连通块,让卡号区域更加规整集中。
python
#先闭运算形成区域,二值化
#闭运算,先膨胀后腐蚀,形成成块的区域
closeX = cv2.morphologyEx(tophat,cv2.MORPH_CLOSE,rectKernel)
cv_show('closeX',closeX)

5.4 OTSU自适应二值化,实现黑白分明
采用OTSU自适应阈值二值化,自动筛选最优阈值,将图像转化为纯黑白效果,彻底分离前景字符与背景。
python
#二值化,自动确定阈值,黑白分明
thresh = cv2.threshold(closeX,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)

5.5 第二次闭运算,填充孔洞、完善区域
再次执行闭运算,填补字符内部细小孔洞,补齐残缺区域,保证卡号连通块完整连续。
python
#再次进行闭运算,补齐区域,保证成块完整
closeX = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel)
cv_show('thresh',closeX)

5.6 查找并绘制所有轮廓
提取图像最外层轮廓,过滤无效子轮廓,在原图绘制轮廓,直观观察所有连通区域范围。
python
#寻找轮廓
#计算轮廓,在原图上找到对应的轮廓
cnts = cv2.findContours(closeX,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cnts_img = image.copy()
cv2.drawContours(cnts_img,cnts,-1,(0,255,0),3)
cv_show('cnts_img',cnts_img)

5.7 轮廓筛选,精准定位卡号区域
通过限定轮廓长宽比、宽高尺寸范围,过滤无效干扰轮廓,仅保留银行卡号所在的连通区域,并从左到右排序,保证卡号顺序正确。
python
#在找到的轮廓当中筛选包含银行卡号的轮廓,通过限定条件
locs = []
for cnt in cnts:
(x,y,w,h) = cv2.boundingRect(cnt)
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)
6. 循环匹匹配
检测到的银行卡号轮廓分为多处分散区域,需要逐一对每个区域进行处理,分割提取出区域内的单个数字,再与提前构建的数字模板逐一完成匹配识别。
区域示例: 二值化:

裁剪单个数字:




python
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("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image",image)
cv2.waitKey(0)


运行方式:
命令行传参:
python card.py -i 银行卡图片.jpg -t 数字模板图片.png
pycharm脚本形参输入:

四、本章小结
- 模板处理:灰度图→反二值化→查找外层轮廓→轮廓从左到右排序→裁剪 ROI,构建 0‑9 数字字典。
- 银行卡预处理:灰度、顶帽运算去除背景噪声,两次闭运算把卡号数字合并成连通区域;依靠长宽比筛选卡号分组位置。
- 识别逻辑:对每一组卡号分割单个数字,统一尺寸,和模板逐个模板匹配,取最大得分作为识别结果。
- 关键点:模板与待识别数字尺寸必须一致;轮廓必须排序,保证数字顺序正确。