计算机视觉:使用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.rhkb.cn/news/190696.html

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

相关文章

ESP32 C3 smartconfig一键配网报错

AP配网 在调试我的esp32c3的智能配网过程中&#xff0c;发现ap配网使用云智能App是可以正常配置的。 切记用户如果在menu菜单里使能AP配网&#xff0c;默认SSID名字为adh_PK值_MAC后6位。用户可以修改这个apssid的键值&#xff0c;但是要使用云智能app则这个名字的开头必须为ad…

电源基础元件

文章目录 电源基础元件理想电压源理想电流源受控电源 电源基础元件 理想电压源 定义 其两端电压总能保持定值或一定的时间函数&#xff0c;其值与流过它的电流i无关的元件叫理想电压源 理想电压源的电压、电流关系 1.电源两端电压由电源本身决定&#xff0c;与外电路无关&…

windows安装nginx

一、下载安装Nginx 1、官网下载地址&#xff1a;nginx: download 2、下载教程&#xff1a;选择最新的Stable version&#xff08;稳定版本&#xff09;下载到本地 3、下载完成后&#xff0c;解压放入本地非中文的文件夹中&#xff1a; 4、启动nginx&#xff1a;切勿直接双击n…

2390 高校实验室预约系统JSP【程序源码+文档+调试运行】

摘要 本文介绍了一个高校实验室预约系统的设计和实现。该系统包括管理员、教师和学生三种用户&#xff0c;具有基础数据管理、学生管理、教师管理、系统公告管理、实验室管理、实验室预约管理和系统管理等模块。通过数据库设计和界面设计&#xff0c;实现了用户友好的操作体验…

taro(踩坑) npm run dev:weapp 微信小程序开发者工具预览报错

控制台报错信息&#xff1a; VM72:9 app.js错误: Error: module vendors-node_modules_taro_weapp_prebundle_chunk-JUEIR267_js.js is not defined, require args is ./vendors-node_modules_taro_weapp_prebundle_chunk-JUEIR267_js.js 环境&#xff1a; node 版本&#x…

Python数据容器(序列操作)

序列 1.什么是序列 序列是指&#xff1a;内容连续、有序。可以使用下标索引的一类数据容器 列表、元组、字符串。均可以视为序列 2.序列的常用操作 - 切片 语法&#xff1a;序列[起始下标:结束下标:步长]起始下标表示从何处开始&#xff0c;可以留空&#xff0c;留空视作从…

华为ensp:为vlan配置ip

配置对应vlan的ip vlan1 interface Vlanif 1 进入vlan1 ip address 192.168.1.254 24配置IP为192.168.1.254 子网掩码为24位 这样就配置上ip了 vlan2 interface Vlanif 2 ip address 192.168.2.254 24 vlan3 interface Vlanif 3 ip address 192.168.3.254 24 查看结果 …

JDK更换版本不生效问题

JDK版本更换 问题: 当本地电脑拥有多个版本jdk时, 切换jdk版本不生效 解决方案: 1.查看环境变量(高版本的jdk安装时自动注入环境变量) 2.将Path里面的jdk的bin配置上移到最上面 3.查看jdk版本, java -version 启动项目,显示如下使用了jdk17

【Python小程序】浮点矩阵加减法

一、内容简介 本文使用Python编写程序&#xff0c;实现2个m * n矩阵的加、减法。具体过程如下&#xff1a; 给定两个m*n矩阵A和B&#xff0c;返回A与B的和或差。 二、求解方法 将两个矩阵对应位置上的元素相加。 三、Python代码 import numpy as np# 用户输入两个矩阵的维…

贝锐向日葵如何实现无人值守远程控制?

1.适用场景 &#xff08;1&#xff09;远程公司电脑应急办公&#xff08;2&#xff09;远程家里电脑游戏挂机&#xff08;3&#xff09;异地远程传输文件 2.操作步骤 &#xff08;1&#xff09;电脑安装向日葵个人版并登录贝锐账号&#xff08;点击注册&#xff09;&#xf…

木板上的蚂蚁(c++题解)

题目描述 有一块木板&#xff0c;长度为 n 个 单位 。一些蚂蚁在木板上移动&#xff0c;每只蚂蚁都以 每秒一个单位 的速度移动。其中&#xff0c;一部分蚂蚁向 左 移动&#xff0c;其他蚂蚁向 右 移动。 当两只向 不同 方向移动的蚂蚁在某个点相遇时&#xff0c;它们会同时改…

找工作的网站都有哪些

吉鹿力招聘网作为一家知名的招聘网站&#xff0c;因其功能完善和用户隐私保护而备受用户青睐。它不仅可以与企业直接沟通&#xff0c;还可以提供在线聊工作的机会。通过吉鹿力招聘网&#xff0c;用户可以自主选择工作地点、时间和工作类型&#xff0c;大大提高了找到合适工作的…

jupyter lab常用插件集合

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

不同优化器的应用

简单用用&#xff0c;优化器具体参考 深度学习中的优化器原理(SGD,SGDMomentum,Adagrad,RMSProp,Adam)_哔哩哔哩_bilibili 收藏版&#xff5c;史上最全机器学习优化器Optimizer汇总 - 知乎 (zhihu.com) import numpy as np import matplotlib.pyplot as plt import torch # …

面向切面:AOP

面向切面&#xff1a;AOP 大家好&#xff0c;今天本篇博客我们来了解Spring里边的另一个重要部分&#xff0c;叫做AOP&#xff0c;也就是我们说的面向切面编程。 1、场景模拟 首先第一部分&#xff0c;咱们做一个场景模拟。我们先写一个简单的例子&#xff0c;然后通过例子引…

22.能被7整除,并且求和。

#include<stdio.h>int main(){int i ,sum0;printf("1-1000能被7整除的数字有&#xff1a;\n");for(i1;i<1000;i){if(i%70){printf("%d ",i);sumsumi;} }printf("\n");printf("能被7整除的数字的和是&#xff1a;%d ",sum);re…

Django之路由层

文章目录 路由匹配语法路由配置注意事项转换器注册自定义转化器 无名分组和有名分组无名分组有名分组 反向解析简介普通反向解析无名分组、有名分组之反向解析 路由分发简介为什么要用路由分发&#xff1f;路由分发实现 伪静态的概念名称空间虚拟环境什么是虚拟环境&#xff1f…

P5906 【模板】回滚莫队不删除莫队

这一题&#xff0c;虽说在洛谷标的是模板题&#xff0c;但可能没有“历史研究”那一题更加模板。 这一题相对于回滚莫队的模板题&#xff0c;可能在回滚的处理上稍微复杂了一点。对于回滚莫队就不多解释了&#xff0c;可以看一下 回滚莫队模板题 这一篇博客&#xff0c;稍微简单…

DMDEM部署说明-详细步骤-(DM8达梦数据库)

DMDEM部署说明-详细步骤-DM8达梦数据库 环境介绍1 部署DM8 数据库1.1 创建一个数据库作为DEM后台数据库1.2 创建数据库用户 DEM1.3 使用DEM用户导入dem_init.sql 2 配置tomcat2.1 配置/tomcat/conf/server.xml2.2 修改jvm启动参数 3 配置JAVA 1.8及以上版本的运行时环境3.1 配置…

使用Java实现一个简单的贪吃蛇小游戏

一. 准备工作 首先获取贪吃蛇小游戏所需要的头部、身体、食物以及贪吃蛇标题等图片。 然后&#xff0c;创建贪吃蛇游戏的Java项目命名为snake_game&#xff0c;并在这个项目里创建一个文件夹命名为images&#xff0c;将图片素材导入文件夹。 再在src文件下创建两个包&#xff…