文章目录
- 前言
- 一、项目整体介绍与环境准备
- 二、代码详解与介绍
- 代码整体执行流程
- 导入库
- 命令行参数
- 业务字典
- [自定义图像显示函数 cv_show](#自定义图像显示函数 cv_show)
- 图像预处理
- 轮廓检测
- 轮廓排序、单个数字区域提取
- 总结
前言
本文基于 OpenCV + Python ,实现银行卡卡号数字提取、模板数字匹配功能,面向编程、计算机视觉零基础学习者。
一、项目整体介绍与环境准备
日常生活中银行卡卡号由固定字体、固定大小的数字组成,我们先准备一张标准数字模板图(0~9 十个数字依次排列),通过 OpenCV 图像处理技术:
-
读取数字模板图片;
-
对图片做灰度、二值化预处理,让数字和背景区分更明显;
-
检测图片中每一个数字的轮廓;
-
对轮廓从左到右排序,保证数字顺序不乱;
-
逐个截取 0~9 单个数字,保存为模板,供后续识别真实银行卡图片使用。
银行卡图片:

卡号图片:

首先安装环境:
c
# 安装OpenCV、数值计算库
pip install opencv-python numpy
然后再准备两个文件:
图片文件:card1.png(待识别银行卡)、kahao.png(数字模板图,0-9 横向排列)
myutils.py(自定义工具脚本,用作轮廓排序)。
c
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
二、代码详解与介绍
代码整体执行流程
-
导入所有依赖库;
-
创建命令行参数,接收银行卡图片和数字模板图片路径;
-
定义银行卡卡号首位数字与卡种的映射字典;
-
定义自定义窗口显示函数;
-
读取数字模板图片;
-
图像预处理:彩色图 → 灰度图 → 二值黑白图;
-
检测图像中所有数字轮廓,并绘制轮廓;
-
对轮廓从左到右排序,保证数字顺序为 0~9;
-
逐个截取单个数字区域,统一尺寸,存入字典;
-
打印最终提取的数字模板字典,程序结束。
导入库
c
import numpy as np
import argparse
import cv2
import myutils
import argparse是Python 内置命令行参数解析模块。
import myutils会导入自定义工具脚本 myutils.py。
把重复使用的工具函数(轮廓排序、图像缩放、旋转等)单独封装,简化主代码。
命令行参数
c
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())
c
ap = argparse.ArgumentParser()
创建一个空的参数管理器,后续所有输入参数都由它管理。就像创建一张 "表格",用来登记我们需要的外部参数。
c
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')
-i:短参数名(命令行简写,使用方便);--image:长参数名(代码内部使用的变量名,语义清晰);required=True:必传参数。运行代码时必须传入该参数,否则程序直接报错,强制用户输入图片路径;help:参数说明,输入 --help 时会展示提示文字,方便使用者理解参数作用。
c
args = vars(ap.parse_args())
vars():将解析后的参数对象,转为 Python 字典;
我们进入程序后右击鼠标,进去设置脚本形参:


设置结束点击'Apply'应用即可,否则程序将会报错。
业务字典
c
FIRST_NUMBER={'3':'American Express',
'4':"Visa",
"5": "MasterCard",
"6":"Discover Card"}
卡号以 3 开头 → American Express
卡号以 4 开头 → Visa
卡号以 5 开头 → MasterCard
卡号以 6 开头 → Discover Card
根据银行卡第一位卡号数字判断银行卡类型。
自定义图像显示函数 cv_show
c
def cv_show(name,img):
cv2.imshow(name,img)
cv2.waitKey(0)
封装为工具函数,一行调用。
使用格式:
c
cv_show('xxx', img)
图像预处理
c
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)
读取图像
c
img = cv2.imread(args['template'])
从命令行参数字典中,取出数字模板图 kahao.png 的路径。

彩色图转灰度图
c
ref = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
即使图片是黑白色的,我们依旧要使用转灰度图,这是因为彩图有3个颜色通道,灰度图只有一个通道,计算快捷,简化运算。
图像二值化
c
ref = cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]
将灰度图只保留两种像素值:纯黑 (0)、纯白 (255)。
但是代码使用的是反向二值化,这是为了边缘检测时不会检测到边缘的那一圈轮廓

使用反向二值化就可以避免这个问题。

轮廓检测
c
refCnts = cv2.findContours(ref,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cv2.drawContours(img,refCnts,-1,(0,0,255),3)
只检测最外层轮廓,忽略内部嵌套轮廓,数字内部也是有轮廓的。
refCnts是上一步检测到的轮廓列表,-1绘制所有轮廓,(0,0,255):绘制颜色,OpenCV BGR 格式 → (0,0,255) = 红色,3:轮廓线条宽度,数值越大线条越粗。
轮廓排序、单个数字区域提取
c
refCnts = myutils.sort_contours(refCnts,method='lert=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))
cv_show('roi',roi)
digits[i] = roi
print(digits)
调用自定义工具 myutils.sort_contours,对轮廓排序,排序规则 从左到右。

检测到10个最小外接矩阵,对x排序进行排序,x越小,数字越小。
截取感兴趣区域 ROI
对图片进行切片,截取单个数字。
c
roi = ref[y:y+h,x:x+w]
统一数字尺寸 resize
c
roi = cv2.resize(roi,(57,88))
将所有数字统一缩放为 宽 57、高 88


总结
读者朋友们可以通过学习前半段代码,熟练掌控模块匹配算法,后续会有完整项目放出,请耐心等待,谢谢。