【OpenCV 实战】零基础搞定银行卡号识别(下):卡号定位 + 模板匹配 + 完整代码落地

【OpenCV 实战】零基础搞定银行卡号识别(下):卡号定位 + 模板匹配 + 完整代码落地 目录一、核心流程回顾二、银行卡图像预处理1. 图像读取与基础预处理2. 顶帽运算突出亮区域抑制背景干扰3. 闭操作 二值化合并数字区域形成连通块三、卡号区域轮廓筛选与定位1. 轮廓检测2. 轮廓筛选保留卡号区域四、单数字分割与模板匹配识别1. 遍历数字组分割单个数字2. 模板匹配识别单个数字3. 结果渲染与输出五、最终结果输出与完整代码1. 结果打印与展示2. 完整主程序代码3. 代码运行方式六、识别效果展示七、项目优化方向八、总结承接上篇《零基础搞定银行卡号识别上核心原理 工具封装 模板预处理》本文我们将完成银行卡图像的全流程处理实现卡号区域定位、单数字分割、模板匹配识别最终完成整个银行卡号识别系统的落地代码完全可复现一、核心流程回顾整个银行卡号识别的完整流程如下上篇我们已经完成了前两步本篇我们完成后续所有环节通用工具函数封装 ✅数字模板预处理构建模板库 ✅银行卡图像预处理突出卡号区域卡号区域轮廓筛选与精准定位单数字分割与尺寸归一化模板匹配输出识别结果结果渲染与卡类型判断二、银行卡图像预处理银行卡图像通常背景复杂、有 logo、芯片、有效期等干扰信息我们需要通过形态学运算把卡号区域从复杂背景中提取出来。1. 图像读取与基础预处理-------信用卡的图像处理------- # 读取输入的银行卡图像 image cv2.imread(args[image]) # 等比例缩放到宽度300统一尺寸保证后续处理稳定性 image myutils.resize(image,width300) # 转为灰度图消除色彩干扰 gray cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)2. 顶帽运算突出亮区域抑制背景干扰银行卡的卡号通常是凸起的亮色字体背景偏暗我们用顶帽运算来突出亮的卡号区域消除大面积的背景干扰。# 初始化形态学卷积核(9,3)适配银行卡数字的宽高比 rectKernel cv2.getStructuringElement(cv2.MORPH_RECT,(9,3)) sqKernel cv2.getStructuringElement(cv2.MORPH_RECT,(5,5)) # 顶帽运算 原始图像 - 开运算结果突出图像中的亮细节 tophat cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernel)3. 闭操作 二值化合并数字区域形成连通块顶帽运算后我们需要把分散的数字合并成一个连通的数字块方便后续定位卡号区域。-------找到数字边框------- # 闭操作先膨胀后腐蚀把相邻的数字连在一起形成数字组 closeX cv2.morphologyEx(tophat,cv2.MORPH_CLOSE,rectKernel) # OTSU二值化自动寻找合适的阈值适合双峰图像无需手动设置阈值 thresh cv2.threshold(closeX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 二次闭操作填补数字孔洞让轮廓更完整 thresh cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel)三、卡号区域轮廓筛选与定位经过预处理后图像中还有很多干扰轮廓我们需要通过筛选精准定位到卡号的 4 组数字区域。1. 轮廓检测# 计算外轮廓 cnts,h cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) # 绘制所有轮廓调试用 cnts_img image.copy() cv2.drawContours(cnts_img,cnts,-1,(0,0,255),3)2. 轮廓筛选保留卡号区域我们通过坐标范围筛选排除 logo、芯片、有效期等干扰区域只保留卡号所在的区域。# 遍历轮廓筛选符合条件的卡号区域 locs [] for c in cnts: (x,y,w,h) cv2.boundingRect(c) #计算外接矩形 # 坐标筛选适配我们缩放后的300宽度图像的卡号位置 if 20x270: if 75y105: locs.append((x,y,w,h)) # 将符合的轮廓从左到右排序保证卡号顺序正确 locs sorted(locs,keylambda x:x[0])拓展优化这里也可以通过宽高比筛选银行卡号每组 4 个数字宽高比通常在 2.5-4 之间适配性会更强本代码保留原始逻辑不做修改。四、单数字分割与模板匹配识别定位到 4 组数字区域后我们需要把每组的 4 个数字单独分割出来和模板库逐一匹配得到识别结果。1. 遍历数字组分割单个数字output [] # 遍历每一个4位数字的轮廓 for (gx,gy,gw,gh) in locs: groupOutput [] # 提取数字组ROI适当加5像素的边界避免截断数字 group gray[gy-5:gygh5,gx-5:gxgw5] # 二值化分割数字 group cv2.threshold(group,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 计算单数字的轮廓 digitCnts,hierarchy cv2.findContours(group.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) # 从左到右排序保证数字顺序正确 digitCnts myutils.sort_contours(digitCnts,methodleft-to-right)[0]2. 模板匹配识别单个数字# 计算每一组中的每一个数值 for c in digitCnts: # 找到当前数字的轮廓resize成和模板一致的大小 (x,y,w,h) cv2.boundingRect(c) roi group[y:yh,x:xw] roi cv2.resize(roi,(57,88)) # 和模板尺寸完全一致 ------使用模板匹配计算匹配得分------- scores [] # 遍历模板库计算和每个数字的匹配得分 for(digit,digitROI) in dig.items(): # 模板匹配使用相关系数匹配法值越高匹配度越高 result cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF) (_,score,_,_) cv2.minMaxLoc(result) scores.append(score) # 得分最高的就是识别出的数字 groupOutput.append(str(np.argmax(scores)))3. 结果渲染与输出# 在原图上绘制卡号区域的矩形框 cv2.rectangle(image,(gx-5,gy-5),(gxgw5,gygh5),(0,255,0),1) # 在原图上绘制识别出的卡号 cv2.putText(image,.join(groupOutput),(gx,gy-15),cv2.FONT_HERSHEY_SIMPLEX,0.65,(0,255,0),1) # 把识别结果加入总输出 output.extend(groupOutput)五、最终结果输出与完整代码1. 结果打印与展示# 输出银行卡类型和完整卡号 print(Credit Card Type: {}.format(FIRST_NUMBER[output[0]])) print(Credit Card #: {}.format(.join(output))) # 展示识别结果 cv2.imshow(Image, image) cv2.waitKey (0)2. 完整主程序代码任务书: 要为某家银行设计一套智能卡号识别的系统。 要求:传入一张图片就自动输出信用卡图片中的数字 import cv2 import numpy as np import argparse import myutils -i card1.png -t kahao.png #设置参数 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) -------模板图像数字的定位处理-------- 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) #计算轮廓:cV2.findContours()函数接受的参数为二值图即黑白的(不是灰度图) # CV2.RETR_EXTERNAL只检测外轮廓CV2.CHAIN_APPROX_SIMPLE只保留终点坐标 refCnts,hierarchy cv2.findContours(ref,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(img,refCnts,-1,(0,0,255),3) # cv_show(refcnts,img) refCnts myutils.sort_contours(refCnts,methodleft-to-right)[0] # 排序,从左到右,从上到下 dig {} #保存模板中每个数字对应的像素值 for(i,c) in enumerate(refCnts): #遍历每一个轮廓 (x,y,w,h) cv2.boundingRect(c) #计算外接矩形并且resize成合适大小 roi ref[y:yh,x:xw] roi cv2.resize(roi,(57,88)) #缩放到固定大小 # cv_show(ro,roi) dig[i] roi # 每一个数字对应每一个模板 -------信用卡的图像处理------- #去取输入图像,预处理 image cv2.imread(args[image]) # cv_show(image,image) image myutils.resize(image,width300)#设置图片大小 gray cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) cv_show(gray,gray) #顶帽操作突出图像中的亮细节清除背景图原因是背景颜色变化小不被腐蚀掉 rectKernel cv2.getStructuringElement(cv2.MORPH_RECT,(9,3)) #初始化卷积核 sqKernel cv2.getStructuringElement(cv2.MORPH_RECT,(5,5)) tophat cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernel) #顶帽运算 原始图像-开运算结果 # open cv2.morphologyEx(gray,cv2.MORPH_OPEN,rectKernel)#开运算先腐蚀后膨胀 # cv_show(open,open) cv_show(tophat,tophat) -------找到数字边框------- #1.通过闭操作先膨胀后腐蚀将数字连一起 closeX cv2.morphologyEx(tophat,cv2.MORPH_CLOSE,rectKernel) cv_show(closeX,closeX) # THRESH_0TSU会自动寻找合适的阈值适合双峰需把阈值参数设置为0 thresh cv2.threshold(closeX,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] cv_show(thresh,thresh) #再来个闭操作 thresh cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,sqKernel) #闭操作 cv_show(close2,thresh) #计算轮廓 cnts,h cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) cnts_img image.copy() cv2.drawContours(cnts_img,cnts,-1,(0,0,255),3) cv_show(cnts_img,cnts_img) #遍历轮廓找到数字部分像素区域 locs [] for c in cnts: (x,y,w,h) cv2.boundingRect(c) #计算外接矩形 # ar w / float(h) # if 2.5ar4: # if (40w55) and (10h20): # locs.append((x,y,w,h)) if 20x270: if 75y105: locs.append((x,y,w,h)) #将符合的轮廓从左到右排序 locs sorted(locs,keylambda x:x[0]) print(locs) output [] #遍历每一个轮廓中的数字 for (gx,gy,gw,gh) in locs: groupOutput [] group gray[gy-5:gygh5,gx-5:gxgw5]#适当加边界 cv_show(group,group) #预处理 group cv2.threshold(group,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] cv_show(group,group) #计算每一组的轮廓 digitCnts,hierarchy cv2.findContours(group.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) digitCnts myutils.sort_contours(digitCnts,methodleft-to-right)[0] #计算每一组中的每一个数值 for c in digitCnts: #找到当前数值的轮廓resize成合适大小 (x,y,w,h) cv2.boundingRect(c) roi group[y:yh,x:xw] roi cv2.resize(roi,(57,88)) cv_show(roi,roi) ------使用模板匹配计算匹配得分------- scores [] #在模板中计算每一个得分 for(digit,digitROI) in dig.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),(gxgw5,gygh5),(0,255,0),1) # cv2.putText()是OpenCV库中的一个函数用于在图像上添加文本。 cv2.putText(image,.join(groupOutput),(gx,gy-15),cv2.FONT_HERSHEY_SIMPLEX,0.65,(0,255,0),1) 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)3. 代码运行方式把myutils.py和主程序放在同一目录下执行以下命令即可运行python 银行卡识别.py -i 你的银行卡图片路径.png -t 数字模板图片路径.png六、识别效果展示我们用不同银行、不同卡组织的银行卡测试均能实现精准识别运行后控制台输出示例七、项目优化方向本项目是基础的 OpenCV 模板匹配实现想要适配更复杂的场景可以从以下方向优化倾斜校正针对拍摄倾斜的银行卡通过霍夫变换实现角度校正提升识别率模糊图像增强对拍摄模糊的图片通过锐化滤波、超分辨率提升图像清晰度通用轮廓筛选替换坐标筛选为宽高比 面积筛选适配不同尺寸、不同布局的银行卡多字体适配扩充模板库适配不同银行的卡号字体提升通用性融合深度学习针对复杂场景可接入轻量级 OCR 模型如 CRNN兼顾轻量性和识别率八、总结本文通过上下两篇内容带大家从零实现了一套基于 OpenCV 的银行卡号识别系统全程零深度学习依赖代码可直接复现。整个项目的核心思路就是通过形态学运算突出目标区域、通过轮廓检测定位卡号、通过模板匹配完成数字识别这也是传统图像处理在字符识别场景中的经典应用。非常适合新手入门 OpenCV理解图像处理的核心逻辑。