本文将带大家用纯 OpenCV 传统图像处理零深度学习依赖实现银行卡号的自动识别全程代码可直接复现新手也能轻松上手一、项目背景与核心效果在金融、支付等场景中银行卡号自动识别是非常高频的需求。相比于动辄大模型的 OCR 方案基于 OpenCV 的传统图像处理方案具有轻量、高效、易部署、零训练成本的优势非常适合嵌入式、本地快速部署等场景。本项目最终实现效果传入一张银行卡图片自动输出卡号、卡类型Visa/MasterCard 等并在原图上标注识别结果适配市面上主流的信用卡 / 储蓄卡样式。核心技术栈编程语言Python 3.x核心库OpenCV-Python、NumPy核心算法形态学运算、轮廓检测、模板匹配整体实现流程整个识别流程分为两大核心阶段也是我们上下两篇文章的核心内容上篇核心工具函数封装 数字模板预处理构建匹配用的数字基准库下篇核心银行卡图像预处理 卡号区域定位 单数字分割 模板匹配识别最终输出结果二、环境准备一行命令完成所有依赖安装pip install opencv-python numpy argparse三、通用工具函数封装我们先把项目中高频复用的功能封装成工具函数myutils.py后续主流程直接调用让代码更简洁易维护。完整工具代码import cv2 def sort_contours(cnts,methodleft-to-right): reverseFalse #排序方向False 表示正序从小到大True 表示反序从大到小 i0 # 排序坐标轴0 表示按 x 坐标排序左右方向1 表示按 y 坐标排序上下方向 # 如果是“右到左”或“下到上”需要反序排列 if methodright-to-left or methodbottom-to-top: reverseTrue # 如果是“上到下”或“下到上”需要按 y 坐标排序 if methodtop-to-bottom or methodbottom-to-top: i1 boundingBoxes[cv2.boundingRect(c) for c in cnts] (cnts,boundingBoxes)zip(*sorted(zip(cnts,boundingBoxes), keylambda b:b[1][i],reversereverse)) #zip(*...) 使用星号操作符解包排序后的元组列表并将其重新组合成两个列表一个包含所有轮廓另一个包含所有边界框。 return cnts,boundingBoxes def resize(image,widthNone,heightNone,intercv2.INTER_AREA): dimNone (h,w)image.shape[:2] if width is None and height is None: return image if width is None: rheight/float(h) dim(int(w*r),height) else: rwidth/float(w) dim(width,int(h*r)) resizedcv2.resize(image,dim,interpolationinter) #默认为cv2.INTER_AREA即面积插值适用于缩放图像。 return resized函数逐行解析1. sort_contours轮廓排序函数这个函数是整个项目的核心基础解决了轮廓检测后数字顺序混乱的问题。核心功能支持 4 种方向对轮廓进行排序左到右、右到左、上到下、下到上保证我们提取的数字顺序和实际卡号顺序一致排序方法reverse 值i 值排序逻辑left-to-rightFalse0按 x 坐标从小到大左→右right-to-leftTrue0按 x 坐标从大到小右→左top-to-bottomFalse1按 y 坐标从小到大上→下bottom-to-topTrue1按 y 坐标从大到小下→上实现逻辑根据排序方向设置排序的正反序reverse和排序坐标轴ix 轴对应左右排序y 轴对应上下排序用cv2.boundingRect计算每个轮廓的外接矩形获取轮廓的坐标信息用sorted函数根据外接矩形的坐标完成排序最后解包返回排序后的轮廓和外接矩形(cnts, boundingBoxes) zip(*sorted(zip(cnts, boundingBoxes), keylambda b: b[1][i], reversereverse))这一行代码虽然短但包含了三个关键操作zip绑定、sorted排序、zip(*)解包我们逐一拆解。操作 1zip (cnts, boundingBoxes) —— 轮廓与外接矩形绑定zip函数可以把两个列表 “打包” 成一个元组列表。比如原来的cnts [轮廓1, 轮廓2, 轮廓3]原来的boundingBoxes [矩形1, 矩形2, 矩形3]zip后变成[(轮廓1, 矩形1), (轮廓2, 矩形2), (轮廓3, 矩形3)]为什么要绑定因为我们排序的依据是 “外接矩形的坐标”但最终要返回的是 “排序后的轮廓”。绑定后排序时轮廓会跟着它对应的外接矩形一起移动不会错位。操作 2sorted (..., keylambda b: b [1][i], reversereverse) —— 按规则排序sorted是 Python 的内置排序函数这里的关键是key参数lambda b: b[1][i]是一个匿名函数意思是对于每个元组b格式是(轮廓, 矩形)取b[1]即矩形再取矩形的第i个元素i0是 xi1是 y作为排序依据。reverse控制正序还是反序和我们之前设置的一致。操作 3zip (*...) —— 解包排序后我们得到的还是一个元组列表[(排序后轮廓1, 排序后矩形1), (排序后轮廓2, 排序后矩形2), ...]。zip(*...)中的*是 “解包操作符”它能把元组列表重新拆成两个独立的列表第一个列表所有排序后的轮廓第二个列表所有排序后的外接矩形最后我们把这两个列表分别赋值给cnts和boundingBoxes并返回。2. resize等比例图像缩放函数解决了不同银行卡图片尺寸不一导致后续处理不稳定的问题。核心功能不改变图像宽高比的前提下按指定宽度 / 高度缩放图像实现逻辑获取原图的宽高若未指定宽高直接返回原图根据指定的宽 / 高计算缩放比例计算出目标尺寸用cv2.resize完成缩放默认使用INTER_AREA插值该插值方式在图像缩小时效果最优边缘保留更好四、数字模板预处理构建匹配基准库模板匹配的核心逻辑是用银行卡上提取的单个数字和我们提前准备好的 0-9 数字模板逐一比对匹配度最高的就是识别结果。所以第一步我们需要把模板图片处理成标准的数字模板库。模板素材我们使用的是银行卡标准字体的 0-9 模板图如下所示模板处理核心流程模板处理的核心目标是把一张包含 0-9 的模板图拆分成单个数字的二值图像按 0-9 的顺序存入字典供后续匹配使用。1. 基础配置与辅助函数首先我们先定义主程序的基础配置以及调试用的图像展示函数这里用到了argparse库不太懂的同学可以看我这篇sys.argv 与 argparse 深度对比import cv2 import numpy as np import argparse import myutils #设置命令行参数 ap argparse.ArgumentParser() ap.add_argument(-i, --image, requiredTrue, helppath to input image) ap.add_argument(-t,--template, requiredTrue, helppath to template COR-A image) args vars(ap.parse_args()) #指定银行卡类型卡号首位对应卡组织 FIRST_NUMBER { 3: American Express, 4: visa, 5: MasterCard, 6: DIscover } #调试用图像展示函数 def cv_show(name,img): cv2.imshow(name,img) cv2.waitKey(0)2. 模板图像读取与基础预处理-------模板图像数字的定位处理-------- # 读取模板图像 img cv2.imread(args[template]) # 转为灰度图消除色彩通道干扰 ref cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化反转黑底白字更适合轮廓检测 ref cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]核心细节这里用THRESH_BINARY_INV反二值化把白底黑字的模板变成黑底白字因为 OpenCV 的轮廓检测默认对亮区域更敏感能更精准的提取数字轮廓。3. 轮廓检测与排序# 计算轮廓只检测外轮廓只保留终点坐标减少数据量 refCnts,hierarchy cv2.findContours(ref,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓调试用 cv2.drawContours(img,refCnts,-1,(0,0,255),3) # 从左到右排序轮廓对应0-9的数字顺序 refCnts myutils.sort_contours(refCnts,methodleft-to-right)[0]核心细节RETR_EXTERNAL只检测最外层轮廓排除数字内部的孔洞轮廓避免干扰CHAIN_APPROX_SIMPLE压缩轮廓点只保留终点坐标大幅减少计算量排序是关键模板是 0-9 从左到右排列的排序后才能保证轮廓顺序和数字一一对应4. 构建数字模板字典dig {} #保存模板中每个数字对应的像素值 for(i,c) in enumerate(refCnts): #遍历每一个轮廓 (x,y,w,h) cv2.boundingRect(c) #计算外接矩形抠出单个数字 roi ref[y:yh,x:xw] roi cv2.resize(roi,(57,88)) #缩放到固定大小保证匹配时尺寸一致 dig[i] roi # 每一个数字对应每一个模板核心细节必须把所有数字模板缩放到完全相同的尺寸(57,88)否则模板匹配时会因为尺寸不一致导致匹配结果完全错误。五、上篇总结与下篇预告到这里我们已经完成了整个银行卡号识别项目的基础搭建封装了轮廓排序、图像缩放两个核心工具函数完成了数字模板的预处理构建了 0-9 的标准模板库为后续匹配做好了准备下篇我们将进入核心的银行卡识别环节带大家完成银行卡图像预处理、卡号区域精准定位、单数字分割、模板匹配识别最终实现卡号的自动输出完整代码可直接运行
【OpenCV 实战】零基础搞定银行卡号识别(上):核心原理 + 工具封装 + 模板预处理
本文将带大家用纯 OpenCV 传统图像处理零深度学习依赖实现银行卡号的自动识别全程代码可直接复现新手也能轻松上手一、项目背景与核心效果在金融、支付等场景中银行卡号自动识别是非常高频的需求。相比于动辄大模型的 OCR 方案基于 OpenCV 的传统图像处理方案具有轻量、高效、易部署、零训练成本的优势非常适合嵌入式、本地快速部署等场景。本项目最终实现效果传入一张银行卡图片自动输出卡号、卡类型Visa/MasterCard 等并在原图上标注识别结果适配市面上主流的信用卡 / 储蓄卡样式。核心技术栈编程语言Python 3.x核心库OpenCV-Python、NumPy核心算法形态学运算、轮廓检测、模板匹配整体实现流程整个识别流程分为两大核心阶段也是我们上下两篇文章的核心内容上篇核心工具函数封装 数字模板预处理构建匹配用的数字基准库下篇核心银行卡图像预处理 卡号区域定位 单数字分割 模板匹配识别最终输出结果二、环境准备一行命令完成所有依赖安装pip install opencv-python numpy argparse三、通用工具函数封装我们先把项目中高频复用的功能封装成工具函数myutils.py后续主流程直接调用让代码更简洁易维护。完整工具代码import cv2 def sort_contours(cnts,methodleft-to-right): reverseFalse #排序方向False 表示正序从小到大True 表示反序从大到小 i0 # 排序坐标轴0 表示按 x 坐标排序左右方向1 表示按 y 坐标排序上下方向 # 如果是“右到左”或“下到上”需要反序排列 if methodright-to-left or methodbottom-to-top: reverseTrue # 如果是“上到下”或“下到上”需要按 y 坐标排序 if methodtop-to-bottom or methodbottom-to-top: i1 boundingBoxes[cv2.boundingRect(c) for c in cnts] (cnts,boundingBoxes)zip(*sorted(zip(cnts,boundingBoxes), keylambda b:b[1][i],reversereverse)) #zip(*...) 使用星号操作符解包排序后的元组列表并将其重新组合成两个列表一个包含所有轮廓另一个包含所有边界框。 return cnts,boundingBoxes def resize(image,widthNone,heightNone,intercv2.INTER_AREA): dimNone (h,w)image.shape[:2] if width is None and height is None: return image if width is None: rheight/float(h) dim(int(w*r),height) else: rwidth/float(w) dim(width,int(h*r)) resizedcv2.resize(image,dim,interpolationinter) #默认为cv2.INTER_AREA即面积插值适用于缩放图像。 return resized函数逐行解析1. sort_contours轮廓排序函数这个函数是整个项目的核心基础解决了轮廓检测后数字顺序混乱的问题。核心功能支持 4 种方向对轮廓进行排序左到右、右到左、上到下、下到上保证我们提取的数字顺序和实际卡号顺序一致排序方法reverse 值i 值排序逻辑left-to-rightFalse0按 x 坐标从小到大左→右right-to-leftTrue0按 x 坐标从大到小右→左top-to-bottomFalse1按 y 坐标从小到大上→下bottom-to-topTrue1按 y 坐标从大到小下→上实现逻辑根据排序方向设置排序的正反序reverse和排序坐标轴ix 轴对应左右排序y 轴对应上下排序用cv2.boundingRect计算每个轮廓的外接矩形获取轮廓的坐标信息用sorted函数根据外接矩形的坐标完成排序最后解包返回排序后的轮廓和外接矩形(cnts, boundingBoxes) zip(*sorted(zip(cnts, boundingBoxes), keylambda b: b[1][i], reversereverse))这一行代码虽然短但包含了三个关键操作zip绑定、sorted排序、zip(*)解包我们逐一拆解。操作 1zip (cnts, boundingBoxes) —— 轮廓与外接矩形绑定zip函数可以把两个列表 “打包” 成一个元组列表。比如原来的cnts [轮廓1, 轮廓2, 轮廓3]原来的boundingBoxes [矩形1, 矩形2, 矩形3]zip后变成[(轮廓1, 矩形1), (轮廓2, 矩形2), (轮廓3, 矩形3)]为什么要绑定因为我们排序的依据是 “外接矩形的坐标”但最终要返回的是 “排序后的轮廓”。绑定后排序时轮廓会跟着它对应的外接矩形一起移动不会错位。操作 2sorted (..., keylambda b: b [1][i], reversereverse) —— 按规则排序sorted是 Python 的内置排序函数这里的关键是key参数lambda b: b[1][i]是一个匿名函数意思是对于每个元组b格式是(轮廓, 矩形)取b[1]即矩形再取矩形的第i个元素i0是 xi1是 y作为排序依据。reverse控制正序还是反序和我们之前设置的一致。操作 3zip (*...) —— 解包排序后我们得到的还是一个元组列表[(排序后轮廓1, 排序后矩形1), (排序后轮廓2, 排序后矩形2), ...]。zip(*...)中的*是 “解包操作符”它能把元组列表重新拆成两个独立的列表第一个列表所有排序后的轮廓第二个列表所有排序后的外接矩形最后我们把这两个列表分别赋值给cnts和boundingBoxes并返回。2. resize等比例图像缩放函数解决了不同银行卡图片尺寸不一导致后续处理不稳定的问题。核心功能不改变图像宽高比的前提下按指定宽度 / 高度缩放图像实现逻辑获取原图的宽高若未指定宽高直接返回原图根据指定的宽 / 高计算缩放比例计算出目标尺寸用cv2.resize完成缩放默认使用INTER_AREA插值该插值方式在图像缩小时效果最优边缘保留更好四、数字模板预处理构建匹配基准库模板匹配的核心逻辑是用银行卡上提取的单个数字和我们提前准备好的 0-9 数字模板逐一比对匹配度最高的就是识别结果。所以第一步我们需要把模板图片处理成标准的数字模板库。模板素材我们使用的是银行卡标准字体的 0-9 模板图如下所示模板处理核心流程模板处理的核心目标是把一张包含 0-9 的模板图拆分成单个数字的二值图像按 0-9 的顺序存入字典供后续匹配使用。1. 基础配置与辅助函数首先我们先定义主程序的基础配置以及调试用的图像展示函数这里用到了argparse库不太懂的同学可以看我这篇sys.argv 与 argparse 深度对比import cv2 import numpy as np import argparse import myutils #设置命令行参数 ap argparse.ArgumentParser() ap.add_argument(-i, --image, requiredTrue, helppath to input image) ap.add_argument(-t,--template, requiredTrue, helppath to template COR-A image) args vars(ap.parse_args()) #指定银行卡类型卡号首位对应卡组织 FIRST_NUMBER { 3: American Express, 4: visa, 5: MasterCard, 6: DIscover } #调试用图像展示函数 def cv_show(name,img): cv2.imshow(name,img) cv2.waitKey(0)2. 模板图像读取与基础预处理-------模板图像数字的定位处理-------- # 读取模板图像 img cv2.imread(args[template]) # 转为灰度图消除色彩通道干扰 ref cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化反转黑底白字更适合轮廓检测 ref cv2.threshold(ref,10,255,cv2.THRESH_BINARY_INV)[1]核心细节这里用THRESH_BINARY_INV反二值化把白底黑字的模板变成黑底白字因为 OpenCV 的轮廓检测默认对亮区域更敏感能更精准的提取数字轮廓。3. 轮廓检测与排序# 计算轮廓只检测外轮廓只保留终点坐标减少数据量 refCnts,hierarchy cv2.findContours(ref,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓调试用 cv2.drawContours(img,refCnts,-1,(0,0,255),3) # 从左到右排序轮廓对应0-9的数字顺序 refCnts myutils.sort_contours(refCnts,methodleft-to-right)[0]核心细节RETR_EXTERNAL只检测最外层轮廓排除数字内部的孔洞轮廓避免干扰CHAIN_APPROX_SIMPLE压缩轮廓点只保留终点坐标大幅减少计算量排序是关键模板是 0-9 从左到右排列的排序后才能保证轮廓顺序和数字一一对应4. 构建数字模板字典dig {} #保存模板中每个数字对应的像素值 for(i,c) in enumerate(refCnts): #遍历每一个轮廓 (x,y,w,h) cv2.boundingRect(c) #计算外接矩形抠出单个数字 roi ref[y:yh,x:xw] roi cv2.resize(roi,(57,88)) #缩放到固定大小保证匹配时尺寸一致 dig[i] roi # 每一个数字对应每一个模板核心细节必须把所有数字模板缩放到完全相同的尺寸(57,88)否则模板匹配时会因为尺寸不一致导致匹配结果完全错误。五、上篇总结与下篇预告到这里我们已经完成了整个银行卡号识别项目的基础搭建封装了轮廓排序、图像缩放两个核心工具函数完成了数字模板的预处理构建了 0-9 的标准模板库为后续匹配做好了准备下篇我们将进入核心的银行卡识别环节带大家完成银行卡图像预处理、卡号区域精准定位、单数字分割、模板匹配识别最终实现卡号的自动输出完整代码可直接运行