计算机视觉:使用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.mzph.cn/news/140889.shtml

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

相关文章

我国陆地遥感卫星发展现状与展望

一、引言 从20世纪90年代末至今&#xff0c;我国陆地遥感卫星事业历经二十多年&#xff0c;实现了从无到有、从小到大、从弱到强的跨越发展。随着高分辨率对地观测系统重大专项&#xff08;高分专项&#xff09;、《陆海观测卫星业务发展规划&#xff08;2011—2020年&#xff…

vue3响应式api

响应式api——compositon api setup&#xff1a; 不要再想this问题执行是在beforeCreated之前 beforeCreated&#xff1a;也就是创建了一个实例 created&#xff1a;挂载了数据 通过形参props接收&#xff0c;只读 以后所有代码都写到setup中 判断是否只读&#xff1a;isReadon…

【04】Istio的pilot流量分发机制

4.1 Pilot配置分发机制 Pilot负责网格数据平面相关配置信息的获取&#xff0c;生成&#xff0c;和分发&#xff0c;它通过Service Registry获取网格配置信息并将其转换为XDS接口的标准数据格式&#xff0c;而后经gRPC分发至相关的Envoy; Service Registry&#xff1a;服务注册表…

EXIT(1)

EXTI介绍 EXTI是片上外设 NVIC是cpu内的外设 回忆起之前的GPIO和AFIO 我们是如何检测按键按下的 我们是一直用while循环读取IDR寄存器的对应位置的值 一直检测判断按键是否被按下 那么是否有第二种方式检测按键是否被按下了呢&#xff1f; 通过EXTI 当EXTI检测到按键的电平发生…

【C++】【Opencv】minMaxLoc()函数详解和示例

minMaxLoc&#xff08;&#xff09;函数 是 OpenCV 库中的一个函数&#xff0c;用于找到一个多维数组中的最小值和最大值&#xff0c;以及它们的位置。这个函数对于处理图像和数组非常有用。本文通过参数和示例详解&#xff0c;帮助大家理解和使用该函数。 参数详解 函数原型…

将 ONLYOFFICE 文档编辑器与 С# 群件平台集成

在本文中&#xff0c;我们会向您展示 ONLYOFFICE 文档编辑器与其自有的协作平台集成。 ONLYOFFICE 是一款开源办公套件&#xff0c;包括文本文档、电子表格和演示文稿编辑器。这款套件支持用户通过文档编辑组件扩展第三方 web 应用的功能&#xff0c;可直接在应用的界面中使用。…

搭建 Makefile+OpenOCD+CMSIS-DAP+Vscode arm-none-eabi-gcc 工程模板

Embedded-GCC-Template 第三方资源集合 STM32F407-GCC-Template Arm-none-eabi-gcc MakefileOpenOCDCMSIS-DAPVscode工程模板 一、本次环境搭建所用的软硬件 1&#xff09;Windows or Linux (本文以Windows为主) 2&#xff09;JLink、Daplink、Wch-Link烧录器 3&#xff…

MySQL join原理及优化

MySQL的JOIN原理是基于索引和算法的。在执行JOIN查询时&#xff0c;MySQL会根据连接字段上的索引来查找匹配的记录。 这种算法在链接查询的时候&#xff0c;驱动表会根据关联字段的索引进行查找&#xff0c;当在索引上找到了符合的值&#xff0c;再回表进行查询&#xff0c;也就…

C#中的扩展方法---Extension

C#中扩展方法是C# 3.0/.NET 3.x 新增特性&#xff0c;能够实现向现有类型中“添加”方法&#xff0c;以下主要介绍C#中扩展方法的声明及使用。 1、扩展方法的声明 扩展方法使能够向现有类型“添加”方法&#xff0c;而无需创建新的派生类型、重新编译或以其他方式修改原始类型…

Flink在汽车行业的应用【面试加分系列】

很多同学问我为什么要发这些大数据前沿汇报&#xff1f; 一方面是自己学习完后觉得非常好&#xff0c;然后总结发出来方便大家阅读&#xff1b;另外一方面&#xff0c;看这些汇报对你的面试帮助会很大&#xff0c;特别是面试前可以看看即将面试公司在大数据前沿的发展动向&…

OmniFocus Pro for Mac(GTD时间管理软件) OmniFocus Mac版

OmniFocus Pro 3 for Mac 中文激活版是一款功能强大且灵活的 GTD 时间管理工具&#xff0c;可为您提供无干扰的环境&#xff0c;帮助您可以轻松地从邮件&#xff0c;消息&#xff0c;Safari 和任何其他第三方应用程序中安排任务&#xff0c;编写备注和剪辑信息。您可以快速轻松…

计算机网络:概述

0 学时安排及讨论题目 0.1讨论题目&#xff1a; CSMA/CD协议交换机基本原理ARP协议及其安全子网划分IP分片路由选择算法网络地址转换NATTCP连接建立和释放再论网络体系结构 0.2 本节主要内容 计算机网络在信息时代中的作用 互联网概述 互联网的组成 计算机网络在我国的发展 …

键盘打字盲打练习系列之认识键盘——0

一.欢迎来到我的酒馆 盲打&#xff0c;yyds&#xff01; 目录 一.欢迎来到我的酒馆二.键盘规格三.键盘分区 二.键盘规格 经常看视频&#xff0c;看到别人在键盘上一通干净利索的操作&#xff0c;就打出想要的文字。心里突然来一句&#xff1a;卧槽&#xff0c;打字贼快啊&#…

JavaScript_动态表格_添加功能

1、动态表格_添加功能.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>动态表格_添加功能</title><style>table{border: 1px solid;margin: auto;width: 100%;}td,th{text-align: ce…

期中成绩这样发

数字化时代&#xff0c;成绩查询系统已经成为学校里不可或缺的一部分。老师们需要一种方便、快捷、准确的方式来发布和查询成绩&#xff0c;而学生们则需要一种安全、可靠的方式来获取自己的成绩。那么&#xff0c;如何实现这一目标呢&#xff1f;我来给大家介绍几种简单实用的…

uniapp发行web页面在老版本浏览器打开一片空白

uniapp发行的web页面&#xff08;菜单->发行->网站-PC Web或手机H5&#xff09;&#xff0c;对于一些老的浏览器&#xff08;或内核&#xff09;&#xff0c;打开一片空白&#xff1b; 而在新版本的浏览器中打开却正常。这是因为那些版本较低的浏览器不支持ES6的语法和新…

SpringBoot系列-2 自动装配

背景&#xff1a; Spring提供了IOC机制&#xff0c;基于此我们可以通过XML或者注解配置&#xff0c;将三方件注册到IOC中。问题是每个三方件都需要经过手动导入依赖、配置属性、注册IOC&#xff0c;比较繁琐。 基于"约定优于配置"原则的自动装配机制为该问题提供了一…

用excel计算矩阵的乘积

例如&#xff0c;我们要计算两个矩阵的乘积&#xff0c; 第一个矩阵是2*2的&#xff1a; 1234 第2个矩阵是2*3的&#xff1a; 5697810 在excel中鼠标点到其它空白的地方&#xff0c;用来存放矩阵相乘的结果&#xff1a; 选择插入-》函数&#xff1a; 选中MMULT&#xff0c;…

淘宝天猫京东苏宁1688等平台关键词监控价格API接口(店铺商品价格监控API接口调用展示)

淘宝天猫京东苏宁1688等平台关键词监控价格API接口&#xff08;店铺商品价格监控API接口调用展示&#xff09;代码对接如下&#xff1a; item_get-获得淘宝商品详情 公共参数 请求地址: https://o0b.cn/anzexi 名称类型必须描述keyString是调用key&#xff08;必须以GET方式…

如何准确分析需求

业务驱动的需求思想 要做好软件需求工作&#xff0c;业务驱动需求思想是核心。传统的需求分析是站在技术视角展开的&#xff0c;关注的是“方案级需求”&#xff1b;而业务驱动的需求思想则是站在用户视角展开的&#xff0c;关注的是“问题级需求”。 变更/优化型需求分析任务…