计算机视觉:使用opencv实现银行卡号识别

1 概述

1.1 opencv介绍

OpenCV是Open Source Computer Vision Library(开源计算机视觉库)的简称,由Intel公司在1999年提出建立,现在由Willow Garage提供运行支持,它是一个高度开源发行的计算机视觉库,可以实现Windows、Linux、Mac等多平台的跨平台操作。opencv是一个用于图像处理、分析、机器视觉方面的开源函数库,已经成为学习计算机视觉强大的工具。在入侵检测、特定目标跟踪、目标检测、人脸检测、人脸识别、人脸跟踪等领域,opencv可谓大显身手。在这篇文章中,主要使用opencv进行银行卡号识别。

1.2 银行卡号识别步骤

银行卡号的识别过程,主要包含读入图片的基本图像操作,用模板去匹配处理后的银行卡,最终识别出银行卡的卡号。所涉及的图像操作包括:灰度转换、二值转换、阈值分割、轮廓检测、礼帽操作、梯度运算、闭操作、模板匹配。

1.2.1 预处理模板图像

首先需要将模板里的数字单独切出来,然后把银行卡上的数字也单独切出来,最后对银行卡的数字一个一个对比模板(0-9,10个数字)。

原始图像如下:

存储路径为:"../data/card_template.jpg"

假设把模板的每个数字切成矩形,可以先对每个数字求外轮廓,然后根据轮廓可得外接矩形,便可切出,其中对于外轮廓处理需传入二值图。于是步骤如下:

  • 读入图像模板
template = cv2.imread('../data/card_template.jpg')
ShowImage('template', template)

  • 转化为灰度图
# 将图像转化为灰度图
image_Gray = cv2.cvtColor(template,   cv2.COLOR_RGB2GRAY)   
ShowImage('gray', image_Gray)

  • 转化为二值图 
# 转换为二值化图像,[1]表示返回二值化图像,[0]表示返回阈值177
image_Binary = cv2.threshold(image_Gray, 177, 255, cv2.THRESH_BINARY_INV)[1]   
ShowImage('binary', image_Binary)

  • 画出0-9这10个数字的外轮廓
# 提取轮廓
refcnts, his = cv2.findContours(image_Binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(template, refcnts, -1, (0,0,255), 2)
ShowImage('contour', template)

  • 计算外接矩形并且resize成合适大小
# 遍历每一个轮廓
for (i, c) in enumerate(refCnts):# 计算外接矩形并且resize成合适大小(x, y, w, h) = cv2.boundingRect(c) #外接矩形roi = ref[y:y + h, x:x + w]roi = cv2.resize(roi, (57, 88))# 每一个数字对应每一个模板digits[i] = roi

 1.2.2 预处理银行卡图像

对于银行卡图像,需要过滤掉背景,保留主要信息(下文1-6步)。上文模板是按矩形切出来的,那么卡号也按矩形切割,便于匹配。银行卡卡号位置是四位一组,可以先处理一组,再对每一组的每一个数字切割,进行模板匹配。其中可以通过长宽比过滤掉银行卡上不是卡号的其他信息。

银行卡图片存储路径:“../data/credit03.jpg”

  • 读入需识别的银行卡并化为灰度图
# 读取图像,进行预处理
image = cv2.imread("../data/credit03.jpg")
ShowImage('card', image)

显示结果如下:

image = resize(image, width=300)
# 将图像转化为灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)      
ShowImage('card_gray', gray)

显示结果如下:

  • 礼帽操作:礼帽操作可以突出更明亮的区域(原始输入-开运算(先腐蚀再膨胀))
# 通过顶帽操作,突出更明亮的区域
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
ShowImage('tophat_card', tophat)

 显示结果如下:

  • 梯度运算(Sobel算子):边缘检测,可计算出轮廓

gradx = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
grady = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1)
gradx = np.absolute(gradx)
minVal = np.min(gradx)
maxVal = np.max(gradx)
# (minVal, maxVal) = (np.min(gradx), np.max(gradx))
# 保证值的范围在0-255之间
gradx = (255 * ((gradx - minVal) / (maxVal - minVal)))     
gradx = gradx.astype("uint8")print(np.array(gradx).shape)
ShowImage('gradx_card', gradx)

显示结果如下:

  • 闭操作:通过闭操作(先膨胀,再腐蚀)将数字连在一起,便于后面求矩形框
# 通过闭操作,先膨胀后腐蚀,将数字连接在一块
gradx = cv2.morphologyEx(gradx, cv2.MORPH_CLOSE,rectKernel)
ShowImage('gradx_card', gradx)

显示结果如下:

  • 阈值分割:利用阈值对图片进行二值化处理,聚焦处理部分
# THRESH_OTSU会自动寻找合适的阈值,适合双峰,需要把阈值设置为0
thresh = cv2.threshold(gradx, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
ShowImage('thresh_card', thresh)

显示结果如下:

  • 再进行闭操作:把图中连接的数字填饱满一点
# 再来一个闭合操作,填充白框内的黑色区域
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
ShowImage('thresh2_card', thresh)

显示结果如下:

  • 计算外轮廓:经过上文一系列操作,对银行卡中是数字的地方有了清晰的候选,同处理模板对象一样把可能是数字的地方通过外轮廓把全部矩形框画出来。后续再做筛选即可。

# 计算轮廓
threshCnts, his = 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), 2)
ShowImage('contour_card', 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 < 5.0:if (w > 40 and w < 85) and (h > 10 and h < 20):# 把符合的留下locs.append((x,y,w,h))# 将符合的轮廓根据x的值,从左到右排序
locs = sorted(locs, key=lambda x: x[0])
  • 对每一个矩形框进行单独处理
output =[]
# 遍历轮廓中的每一个数字
for (i,(gx, gy, gw, gh)) in  enumerate(locs):# 初始化链表groupOutput = []# 根据坐标提取每一个组,往外多取一点,要不然看不清楚group = gray[gy-5:gy+gh+5,gx-5:gx+gw+5]ShowImage('group', group)# 预处理group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]  # 二值化ShowImage('group', group)# 找到每一组的轮廓digitCnts, his = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# digitCnts = sortContours(digitCnts, method="LefttoRight")[0]# 对找到的轮廓进行排序digitCnts = sort_contours(digitCnts, method="left-to-right")[0] 

1.2.3 模板匹配计算得分

# 计算每一组中的每一个数值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))ShowImage('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)))

1.2.4 绘制结果

# 画矩形和字体cv2.rectangle(image, (gx - 5, gy - 5), (gx+gw+5, gy+gh+5), (0,0,255),1)cv2.putText(image, "".join(groupOutput), (gx, gy-15), cv2.FONT_HERSHEY_SIMPLEX,0.65, (0,0,255),2)# 得到结果output.extend(groupOutput)

2 银行卡号识别完整代码

import cv2
import numpy as npdef ShowImage(name, image):cv2.imshow(name, image)cv2.waitKey(0)  # 等待时间,0表示任意键退出cv2.destroyAllWindows()def sort_contours(cnts, method="left-to-right"):# reverse = False 表示升序,若不指定reverse则默认升序reverse = Falsei = 0if method == "right-to-left" or method == "bottom-to-top":reverse = True  # reverse = True 表示降序if method == "top-to-bottom" or method == "bottom-to-top":i = 1# 用一个最小的矩形,把找到的形状包起来,用x,y,h,w表示boundingBoxes = [cv2.boundingRect(c) for c in cnts]# zip函数用于打包可迭代数据,得到最终输出的cnts和boundingBoxes(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),key=lambda b: b[1][i], reverse=reverse))return cnts, boundingBoxesdef 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 imageif 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)  # 使用cv库的resize函数return resizedtemplate = cv2.imread('../data/card_template.jpg')
ShowImage('template', template)# 将图像转化为灰度图
image_Gray = cv2.cvtColor(template,   cv2.COLOR_RGB2GRAY)
ShowImage('gray', image_Gray)# 转换为二值化图像,[1]表示返回二值化图像,[0]表示返回阈值177
image_Binary = cv2.threshold(image_Gray, 177, 255, cv2.THRESH_BINARY_INV)[1]
ShowImage('binary', image_Binary)# 提取轮廓
refcnts, his = cv2.findContours(image_Binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(template, refcnts, -1, (0, 0, 255), 2)
ShowImage('contour', template)refcnts = sort_contours(refcnts, method="left-to-right")[0]
digits = {}# 遍历每个轮廓
for (i, c) in enumerate(refcnts):  # enumerate函数用于遍历序列中的元素以及它们的下标(x, y, w, h) = cv2.boundingRect(c)roi = image_Binary[y:y+h, x:x+w]roi = cv2.resize(roi, (57, 88))digits[i] = roi# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))# 读取图像,进行预处理
image = cv2.imread("../data/credit03.jpg")
ShowImage('card', image)image = resize(image, width=300)
# 将图像转化为灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ShowImage('card_gray', gray)# 通过顶帽操作,突出更明亮的区域
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
ShowImage('tophat_card', tophat)gradx = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
grady = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1)
gradx = np.absolute(gradx)
minVal = np.min(gradx)
maxVal = np.max(gradx)
# (minVal, maxVal) = (np.min(gradx), np.max(gradx))
# 保证值的范围在0-255之间
gradx = (255 * ((gradx - minVal) / (maxVal - minVal)))
gradx = gradx.astype("uint8")print(np.array(gradx).shape)
ShowImage('gradx_card', gradx)# 通过闭操作,先膨胀后腐蚀,将数字连接在一块
gradx = cv2.morphologyEx(gradx, cv2.MORPH_CLOSE,rectKernel)
ShowImage('gradx_card', gradx)# THRESH_OTSU会自动寻找合适的阈值,适合双峰,需要把阈值设置为0
thresh = cv2.threshold(gradx, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
ShowImage('thresh_card', thresh)# 再来一个闭合操作,填充白框内的黑色区域
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
ShowImage('thresh2_card', thresh)# 计算轮廓
threshCnts, his = 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), 2)
ShowImage('contour_card', 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 < 5.0:if (w > 40 and w < 85) and (h > 10 and h < 20):# 把符合的留下locs.append((x,y,w,h))# 将符合的轮廓根据x的值,从左到右排序
locs = sorted(locs, key=lambda x: x[0])output =[]
# 遍历轮廓中的每一个数字
for (i,(gx, gy, gw, gh)) in  enumerate(locs):# 初始化链表groupOutput = []# 根据坐标提取每一个组,往外多取一点,要不然看不清楚group = gray[gy-5:gy+gh+5,gx-5:gx+gw+5]ShowImage('group', group)# 预处理group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]  # 二值化ShowImage('group', group)# 找到每一组的轮廓digitCnts, his = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# digitCnts = sortContours(digitCnts, method="LefttoRight")[0]# 对找到的轮廓进行排序digitCnts = 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))ShowImage('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(image, "".join(groupOutput), (gx, gy-15), cv2.FONT_HERSHEY_SIMPLEX,0.65, (0,0,255),2)# 得到结果output.extend(groupOutput)ShowImage('card_result', image)

运行结果:


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/171423.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云国际站:应用实时监控服务

文章目录 一、阿里云应用实时监控服务的概念 二、阿里云应用实时监控服务的优势 三、阿里云应用实时监控服务的功能 四、写在最后 一、阿里云应用实时监控服务的概念 应用实时监控服务 (Application Real-Time Monitoring Service) 作为一款云原生可观测产品平台&#xff…

数据库 并发控制

多用户数据库系统&#xff1a;允许多个用户同时使用同一个数据库的数据库系统 交叉并发方式&#xff1a;在单处理机系统中&#xff0c;事务的并行执行实际上是这些并行事务的并行操作轮流交叉运行 同时并发方式&#xff1a;在多处理机系统中&#xff0c;每个处理机可以运行一个…

No200.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

centos利用find提权反弹shell

需要说明的是利用find命令进行提权的方式已经不存在了&#xff0c;因为Linux默认不会为find命令授予suid权限&#xff0c;这里只是刻意的制造出了一种存在提权的环境 首先我们先介绍一下find命令&#xff0c;find命令主要用来在Linux中查找文件使用&#xff0c;它可以进行最基础…

html5 初步了解

1、html5 含义 简而言之&#xff0c;html5 其实就是新的一代html标准&#xff01; 2、html5的优缺点 优点 语义化html 增加了很多语义化的标签&#xff0c;让html结构更加清晰&#xff0c;更具可读性由于增加了很多语义化的标签&#xff0c;对SEO更加友好 缺点 其他主流浏…

node插件MongoDB(四)—— 库mongoose 的个性话读取(字段筛选、数据排序、数据截取)(四)

文章目录 一、字段筛选二、数据排序三、数据截取1. skip 跳过2. limit 限定![在这里插入图片描述](https://img-blog.csdnimg.cn/c7067b1984ee4c6686f8bbe07cae9176.png) 一、字段筛选 字段筛选&#xff1a;只读取指定的数据&#xff0c;比如集合&#xff08;表&#xff09;中有…

如何在电脑和手机设备上编辑只读 PDF

我们大多数人更喜欢以 PDF 格式共享和查看文件&#xff0c;因为它更专业、更便携。但是&#xff0c;通常情况下您被拒绝访问除查看之外的内容编辑、复制或评论。如果您希望更好地控制您的 PDF 或更灵活地编辑它&#xff0c;请弄清楚为什么您的 PDF 是只读的&#xff0c;然后使用…

微信小程序:仅前端实现对象数组的模糊查询

效果 核心代码 //对数组进行过滤&#xff0c;返回数组中每一想满足name值包括变量query的 let result array.filter(item > { return item.name.includes(query); }); 完整代码 wxml <input type"text" placeholder"请输入名称" placeholder-styl…

基因检测技术的发展与创新:安全文件数据传输的重要作用

基因是生命的密码&#xff0c;它决定了我们的身体特征、健康状况、疾病风险等。随着基因检测技术的高速发展&#xff0c;我们可以通过对基因进行测序、分析和解读&#xff0c;更深入地认识自己&#xff0c;预防和治疗各种遗传性疾病&#xff0c;甚至实现个性化医疗和精准健康管…

Mysql基本知识

1.SQL分类 DDL【data definition language】 数据定义语言&#xff0c;用来维护存储数据的结构 代表指令: create, drop, alter DML【data manipulation language】 数据操纵语言&#xff0c;用来对数据进行操作 代表指令&#xff1a; insert&#xff0c;delete&#xff0c;up…

LLM大语言模型(典型ChatGPT)入门指南

文章目录 一、基础概念学习篇1.1 langchain视频学习笔记1.2 Finetune LLM视频学习笔记 二、实践篇2.1 预先下载模型&#xff1a;2.2 LangChain2.3 Colab demo2.3 text-generation-webui 三、国内项目实践langchain-chatchat 一、基础概念学习篇 1.1 langchain视频学习笔记 lan…

【设计原则篇】聊聊开闭原则

开闭原则 其实就是对修改关闭&#xff0c;对拓展开放。 是什么 OCP&#xff08;Open/Closed Principle&#xff09;- 开闭原则。关于开发封闭原则&#xff0c;其核心的思想是&#xff1a;模块是可扩展的&#xff0c;而不可修改的。也就是说&#xff0c;对扩展是开放的&#xf…