当前位置: 首页 > news >正文

opencv实战小结-银行卡号识别

实战1-银行卡号识别

项目来源:opencv入门

项目目的:识别传入的银行卡照片中的卡号

难点:银行卡上会有一些干扰项,如何排除这些干扰项,并且打印正确的号码是一个问题

在这里插入图片描述

最终效果如上图

实现这样的功能需要以下几个步骤:

  1. 首先必须有与银行卡中卡号数字基本一样的数字模板,将模板中的数字提取出来并存储起来(0-9)
  2. 将需要检测的银行卡图片中的数字提取出来
  3. 将银行卡的数字与模板数字一一对比,最终找到一个匹配度最高的数字,并把数字标注上

整个思路很简单,但是难点就在于如何将图片处理得更加容易让计算机识别数字,所以整个项目要围绕着图片得的处理来做

第一步-提取数字模板

这是事先准备好的数字模板

在这里插入图片描述

接下来要将图片中的数字都找到,也就是找到各个数字在整个图片上的像素点坐标(轮廓)

首先得到图片的灰度图,再进行二值化处理(这一切都是为了让图片中的数字更易于识别)

# 灰度图
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值图像
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]#超过阈值部分取maxval ( 最大值 ),否则取0

然后会得到这样的图像
在这里插入图片描述

好了,现在图片已经很清晰了,不需要再进行其它的处理了,直接将其提取

那怎么提取呢?

可以通过cv2.findContours()找到数字的轮廓

函数 cv2.findContours() 有三个参数,第一个是输入图像,第二个是 轮廓检索模式,第三个是轮廓近似方法。返回值有三个,第一个是图像,第二个 是轮廓,第三个是(轮廓的)层析结构。轮廓(第二个返回值)是一个 Python 列表,其中存储这图像中的所有轮廓。每一个轮廓都是一个 Numpy 数组,包 含对象边界点(x,y)的坐标。

注意新版本中这个api的返回值有变化

返回两个参数contours和 hierarchy,contours就是每个数字的轮廓数组,包含边界点的坐标

其中cv2.RETR_EXTERNAL是获取外轮廓

contours, hierarchy = cv2.findContours(ref.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

接下来可以将轮廓画出来看看

会用到cv2.drawContours()函数

函数 cv2.drawContours() 可以被用来绘制轮廓。它可以根据你提供 的边界点绘制任何形状。它的第一个参数是原始图像,第二个参数是轮廓,一 个 Python 列表。第三个参数是轮廓的索引(在绘制独立轮廓是很有用,当设置 -1时绘制所有轮廓)。接下来的参数是轮廓的颜色和厚度等。

cv2.drawContours(img,contours,-1, (0, 0, 255), 3)

看下效果

在这里插入图片描述

好,现在轮廓都找到了,并且我们也有了轮廓的坐标,这个时候我们应该将每个数字的像素点位置都存起来(并不是将图片分割!,整个图片仍然没有任何变化)

好,现在有一个要注意的点,那就是我们在上面得到的contours数组并不是按图片中各个数字从左到右排列的,也就是说数组中第一个坐标可能是图片中8的坐标,那这个时候我们就必须对数组进行排序,排序顺序就是从左到右存

那排序怎么实现呢,其实就是根据x坐标从小到大排序就行了

排完序之后,contours中0下标存的就是数字0的模板,这里很好的利用了数组下标的优点

好的,排序完之后,我们就可以来存这个数字的模板了

思路是遍历contours数组,得到每个模板的坐标以及宽高,利用x+w就能得到图片的x轴范围,y+h就能得到y轴的范围,把他们存起来就得到一个数字的模板了

digits = {}
#遍历每一个轮廓
for (i,c) in enumerate(contours):#计算外接矩形并resize合适的大小(x, y, w, h) = cv2.boundingRect(c)# cv2.rectangle(img,(x,y),(x + w, y + h),(0, 0, 255), 2)roi = ref[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))

一个是9x3的矩阵,一个是5x5

下面对图像进行处理,老规矩,取灰度图

在这里插入图片描述

然后进行礼帽处理,目的是为了突出更明亮的区域

tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)

在这里插入图片描述

接下来再用 Sobel核子对图片进行卷积,目的的为了得到图像梯度,也就是边缘检测

我们现在要做的是把可能为数字的区域都找出来

gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal)))
gradX = gradX.astype("uint8")

在这里插入图片描述

上图看上去更加模糊了,但是数字和非数字区域的明亮度变了

好的,接下来可以通过闭操作(先膨胀,再腐蚀),将数字连起来(是为了最后找到数字区域,因为卡号是4个数字连在一起的,我们把4个数字的区域找出来)

变成这样

在这里插入图片描述

再来一次阈值操作

thresh = cv2.threshold(gradX, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

变成这样

在这里插入图片描述

矩形区域好像白色没有填满,再来一次闭操作

在这里插入图片描述

ok了,现在疑似数字的区域都很明显了吧,那下一步就是将这个区域进行排除,找到真正为银行卡号的区域,其他的区域就不要了

那怎么做呢?我们先把他们的轮廓都找出来,然后判断这些轮廓的宽度,符合银行卡号区域宽的的留下,不符合的去掉就可以了

在这里插入图片描述

# 计算轮廓
contours_, hierarchy_ = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = contours_
cur_img = image.copy()
cv2.drawContours(cur_img, cnts, -1, (0, 0, 255), 3)
cv_show('img', 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 < 4.0:if (w > 40 and w < 55) and (h > 10 and h < 20):# 符合的留下来locs.append((x, y, w, h))

得到卡号轮廓后,同样对其从左至右排序

好了,那接下来干嘛呢,我们刚刚得到的是四个数字组成的区域的轮廓,这个时候我们应该遍历这些区域,得到里面的四个数字的轮廓

同样也是个遍历操作

for (i, (gX, gY, gW, gH)) in enumerate(locs):groupOutput = []# 根据坐标提取每一个组group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5]cv_show('group', group)

会得到四个这样的组

在这里插入图片描述

然后就获取这个组的轮廓,就像第一步骤一样,将数字提取出来就可以了

#计算每一组的轮廓digitCnts, hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)# 从左到右排序digitCnts = myutils.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))# cv_show('roi', roi)# 计算匹配得分scores = []for (digit, digitROI) in digits.items():# 模板匹配result = cv2.matchTemplate(roi, digitROI,cv2.TM_CCOEFF)# print('result',result)# 获取匹配度最高的数值(_, score, _, _) = cv2.minMaxLoc(result)scores.append(score)print("scores",scores)# 得到最合适的数字groupOutput.append(str(np.argmax(scores)))print('groupOutput',groupOutput)

完成上述步骤之后,我们的groupOutput就存放了我们识别出来的银行卡号了,我们只需要在图片上将卡号绘制出来就可以了

  # 画出来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)

最终效果如下

在这里插入图片描述


好了,以上就是此小项目的实现过程

总结:这是我学cv的第一个小实战项目,确实感觉蛮有意思的,学之前觉得这个东西很神奇,学习之后会发现其实一切都是按照逻辑一步步来的,没有那么"高大上",继续努力吧

相关文章:

  • 如何利用Varjo混合现实技术改变飞机维修训练方式
  • 关于RDMA传输的基本流量控制
  • Linux 中常用的设置、工具和操作
  • LeetCode题练习与总结:三角形最小路径和--120
  • 有待挖掘的金矿:大模型的幻觉之境
  • LeetCode ---400周赛
  • 在npm发布自己的组件包
  • 编程规范-代码检测-格式化-规范化提交
  • 安徽京准NTP时钟系统:GPS北斗卫星授时下的生活重塑
  • 2559. 统计范围内的元音字符串数(前缀和) o(n)时间复杂度
  • k-means聚类模型的优缺点
  • 后端 excel的导入
  • 探索k8s集群的配置资源(secret和configmap)
  • 自然语言处理(NLP)—— 主题建模
  • WMS仓储管理系统高效驱动制造企业物料管理
  • JS 中的深拷贝与浅拷贝
  • @angular/forms 源码解析之双向绑定
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • [原]深入对比数据科学工具箱:Python和R 非结构化数据的结构化
  • 【前端学习】-粗谈选择器
  • CSS 专业技巧
  • IDEA常用插件整理
  • Java面向对象及其三大特征
  • JDK9: 集成 Jshell 和 Maven 项目.
  • JS 面试题总结
  • Js基础知识(一) - 变量
  • js中的正则表达式入门
  • LeetCode29.两数相除 JavaScript
  • mongodb--安装和初步使用教程
  • MYSQL 的 IF 函数
  • October CMS - 快速入门 9 Images And Galleries
  • PhantomJS 安装
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • ⭐ Unity + OpenCV 实现实时图像识别与叠加效果
  • 安装python包到指定虚拟环境
  • 简析gRPC client 连接管理
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 理清楚Vue的结构
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 深入浏览器事件循环的本质
  • 我看到的前端
  • 原生JS动态加载JS、CSS文件及代码脚本
  • 在electron中实现跨域请求,无需更改服务器端设置
  • Android开发者必备:推荐一款助力开发的开源APP
  • ​​​【收录 Hello 算法】9.4 小结
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • (+3)1.3敏捷宣言与敏捷过程的特点
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (7)svelte 教程: Props(属性)
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束
  • (备忘)Java Map 遍历
  • (二十三)Flask之高频面试点