基于opencv-python模板匹配的银行卡号识别(附源码)

news2024/11/20 11:39:28

目录

介绍

数字模板处理

银行卡图片处理 

导入数字模板

模板匹配及结果


介绍

我们有若干个银行卡图片和一个数字模板图片,如下图

我们的目的就是通过对银行卡图片进行一系列图像操作使得我们可以用这个数字模板检测出银行卡号。

数字模板处理

首先我们先对数字模板进行处理,处理的目的是将数字模板中的每个数字分割开来。

先导入需要用到的包

import cv2
import os
import numpy as np
import matplotlib.pyplot as plt

然后再定义一个修改图片尺寸的函数

#修改尺寸
def img_resize(img, hight):
    (h, w) = img.shape[0], img.shape[1]
    r = h / hight
    width = w / r
    img = cv2.resize(img, (int(width), int(hight)))    
    return img

接下来,我们读入数字模板图片并对其进行灰度化,二值化和轮廓检测

#读入总模板
img = cv2.imread('images/ocr_a_reference.png')
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ref,thresh= cv2.threshold(ref, 127, 255, cv2.THRESH_BINARY_INV)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

由于模板比较简单,故用这些操作即可分割出来数字模板中的每个数字,我们可以看一下操作完后的结果

for i in range(len(contours)):
    x, y, w, h = cv2.boundingRect(contours[i])
    plt.subplot(3, 4, i + 1)
    plt.imshow(thresh[y:y + h, x:x + w], cmap=plt.cm.gray)
    plt.xticks([])
    plt.yticks([])

plt.show()

接下来,我们将各个模板保存起来,以便于后期读取使用

#保存模板
if not os.path.exists('data'):
    os.mkdir('data')

for i in range(len(contours)):
    x, y, w, h = cv2.boundingRect(contours[i])
    cv2.imwrite(os.path.join('data', str(9-i)+'.jpg'), thresh[y:y+h, x:x+w])

保存完后会生成一个data文件夹,可以看到每个数字都已经单独分割保存为单张图片了

到这里,数字模板处理就完成了

银行卡图片处理 

我们是要基于模板匹配去识别具体的银行卡号,而且我们在上述操作中已经得到了每个数字的模板,所以我们现在只需要从银行卡里面切割处理每个银行卡号,就可以进行模板匹配,那么怎么切割出银行卡里的每个号码呢,这里小编尝试过直接用图像处理技术进行单个切割,但发现效果并不好。此时我们发现银行卡号共有16位,其中每4位离的都比较近,那我们可不可以先画出整体四个,然后再对四个进行单独切割呢,显然,这样做的效果是比较好的。

 我们首先读入银行卡图片并修改尺寸和做灰度化处理

#灰度化
img = cv2.imread('images/credit_card_01.png')
img = img_resize(img, 200)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.imshow(gray, cmap=plt.cm.gray)

 然后对灰度图进行礼貌操作,用来突出银行卡中的数字

#礼貌操作
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 5))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
tophat = cv2.morphologyEx(gray,cv2.MORPH_TOPHAT,rectKernel)
plt.imshow(tophat, cmap=plt.cm.gray)

然后利用sobel算子增强图片的边缘信息,即增强数字信息

#sobel边缘检测
sobel = cv2.Sobel(tophat, cv2.CV_64F,dx=1, dy=0, ksize=3)
sobel = cv2.convertScaleAbs(sobel)
minval, maxval = np.min(sobel), np.max(sobel)
sobel = (255 * ((sobel - minval) / (maxval - minval)))
sobex = sobel.astype('uint8')
plt.imshow(sobex, cmap=plt.cm.gray)

 再对图像进行膨胀和腐蚀的操作,使得每四个数字连接在一起

#膨胀腐蚀
dilate = cv2.dilate(sobel, rectKernel, 10)
erosion = cv2.erode(dilate, rectKernel, 10)
plt.imshow(erosion, cmap=plt.cm.gray)

此时发现图像上有些噪声,所以我们对图像进行二值化操作,以去除这些白点

#二值化
erosion = cv2.convertScaleAbs(erosion)
ret, thresh = cv2.threshold(erosion, 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
plt.imshow(thresh, cmap=plt.cm.gray)

 

进行完二值化操作后,再进行一次膨胀腐蚀操作,加深数字区域信息

#膨胀腐蚀
dilate = cv2.dilate(thresh, sqKernel, 10)
erosion = cv2.erode(dilate, sqKernel, 10)
plt.imshow(dilate, cmap=plt.cm.gray)

现在效果就比较好了,我们就可以在此图像上画轮廓了

#画轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cur_img = img.copy()
cur_img = cv2.cvtColor(cur_img, cv2.COLOR_BGR2RGB)
cv2.drawContours(cur_img,contours,-1,(0,0,255),3)
plt.imshow(cur_img)

 

但是我们发现,这个轮廓不仅廓住了数字区域,还廓住了其他区域,此时我们将数字区域轮廓过滤出来,并画出来数字区域显示一下(数字是棕色的是因为此时显示的BGR图像)

#过滤轮廓
locs = []
for(i,c) in enumerate(contours):
    (x,y,w,h) = cv2.boundingRect(c)
    ar = w/float(h)
    if ar>2.5 and ar<4.0:
        if(w>40 and w<60) and (h>10 and h<20):
            locs.append((x,y,w,h))
print(len(locs))

for i in range(len(locs)):
    x,y,w,h = locs[3-i]
    contour = img[y:y+h, x:x+w,:]
    plt.subplot(2, 2, i+1)
    plt.imshow(contour)
    plt.xticks([])
    plt.yticks([])
        
plt.show()

 

此时没有银行卡上其他信息的干扰,我们可以很简单的使用灰度化,二值化和轮廓检测来廓住每个单独的数字

#进行最后的处理
results = []
for i in range(len(locs)):
    x,y,w,h = locs[3-i]
    img_new = img[y:y+h, x:x+w,:]
    gray = cv2.cvtColor(img_new, cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    res = img_new.copy()
    for j in range(len(contours)):
        x, y, w, h = cv2.boundingRect(contours[3-j])
        res = cv2.rectangle(res, (x, y), (x+w, y+h), (255, 0, 0), 1)
        results.append(thresh[y:y+h, x:x+w])
    
    plt.subplot(2, 2, i+1)
    plt.imshow(res, cmap=plt.cm.gray)
    plt.xticks([])
    plt.yticks([])
        
plt.show()

最后我们就可以得到银行卡中的每个单独号码

#可以看一下results
for i in range(16):
    results[i] = cv2.resize(results[i], (10, 15))
    plt.subplot(2, 8, i+1)
    plt.imshow(results[i], cmap=plt.cm.gray)
    plt.xticks([])
    plt.yticks([])
        
plt.show()

 

导入数字模板

在处理完银行卡后,我们导入我们一开始获得的数字模板,进行最后的模板匹配

#引入模板
digits = {}
for i in range(10):
    digits[i] = cv2.resize(cv2.imread('data/{}.jpg'.format(i)), (10, 15))
    digits[i] = cv2.cvtColor(digits[i], cv2.COLOR_BGR2GRAY)
    ref, digits[i] = cv2.threshold(digits[i], 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)

for i in range(10):
    plt.subplot(2, 5, i+1)
    plt.imshow(digits[i], cmap=plt.cm.gray)
    plt.xticks([])
    plt.yticks([])
        
plt.show()

模板匹配及结果

导入数字模板后,就可以进行模板匹配得到结果了

#模板匹配得出结果
res = ''
for i in results:
    scores = []
    for j in range(10):
        result = cv2.matchTemplate(i, digits[j], cv2.TM_CCOEFF)  # result为一个输出矩阵
        (_, score, _, _) = cv2.minMaxLoc(result)  # 这个方法会返回最小值,最大值,最小值位置和最大值位置
        scores.append(score)
    res = res + str(np.argmax(scores))
print(res)

plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.show

我们也可以看一下其他银行卡的匹配结果 

 

其中有一张银行卡号的识别好像因为环境等因素出了点问题,其他的识别都是没问题的,大体来说结果还算可以

源码及文件请查看:https://github.com/jvyou/Bank-card-number-identification

效果演示请查看:https://www.bilibili.com/video/BV1hK421C7Bk/?spm_id_from=333.999.0.0&vd_source=ea64b940c4e46744da2aa737dca8e183

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

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

相关文章

2024Node.js零基础教程(小白友好型),nodejs新手到高手,(六)NodeJS入门——http模块

047_http模块_获取请求行和请求头 hello&#xff0c;大家好&#xff0c;那第二节我们来介绍一下如何在这个服务当中来提取 HTT 请求报文的相关内容。首先先说一下关于报文的提取的方法&#xff0c;我在这个文档当中都已经记录好了&#xff0c;方便大家后续做一个快速的查阅。 …

Python速成篇(基础语法)下(新年快乐♥)

引言 一天不学编程手就痒&#xff0c;今天是除夕&#xff0c;学C艹vector的话就没时间出去玩了&#xff0c;所以就写写博客。今天要讲的内容是关于&#xff0c;list&#xff08;列表&#xff09;&#xff0c;tuple&#xff08;元组&#xff09;&#xff0c;字典&#xff08;di…

C#,巴都万数列(Padonve Number)的算法与源代码

1 巴都万数列&#xff08;Padovan Sequence&#xff09; 巴都万数列&#xff08;Padovan Sequence&#xff09;是一个整数数列。 首数个值为1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12, 16, 21, 28, 37 ... 此数列以建筑师理察巴都万命名&#xff0c;他的论文Dom&#xff08;1994年&a…

Select 选择器 el-option 回显错误 value

离谱 回显的内容不是 label 而是 value 的值 返回官方看说明&#xff1a; v-model的值为当前被选中的el-option的 value 属性值 value / v-model 绑定值有3种类型 boolean / string / number 根据自身代码猜测是&#xff1a;tableData.bookId 与 item.id 类型不一致导致 &…

C++入门学习(二十六)for循环

for (初始化; 条件; 递增/递减) { // 代码块 } 打印1~10&#xff1a; #include <iostream> using namespace std; int main() { for (int i 1; i < 10; i) { cout <<i<<endl; } return 0; } 打印九九乘法表&#xff1a; #include <iostream…

大模型基础架构的变革:剖析Transformer的挑战者(下)

上一篇文章中&#xff0c;我们介绍了UniRepLKNet、StripedHyena、PanGu-π等有可能会替代Transformer的模型架构&#xff0c;这一篇文章我们将要介绍另外三个有可能会替代Transformer的模型架构&#xff0c;它们分别是StreamingLLM、SeTformer、Lightning Attention-2&#xff…

1、学习 Eureka 注册中心

学习 Eureka 注册中心 一、创建 Eureka 微服务0、SpringBoot 和 SpringCloud 版本1、引入 Eureka 服务端依赖2、启动类加 EnableEurekaServer 注解3、配置 yaml 文件&#xff0c;把 Eureka 服务注册到 Eureka 注册中心4、访问 Eureka 服务端&#xff0c;查看注册中心的服务列表…

Blazor Wasm Gitee 码云登录

目录: OpenID 与 OAuth2 基础知识Blazor wasm Google 登录Blazor wasm Gitee 码云登录Blazor SSR/WASM IDS/OIDC 单点登录授权实例1-建立和配置IDS身份验证服务Blazor SSR/WASM IDS/OIDC 单点登录授权实例2-登录信息组件wasmBlazor SSR/WASM IDS/OIDC 单点登录授权实例3-服务端…

[神奇代码岛】皮肤功能使用

前言 最近有很多人在制作地图的时候&#xff0c;因该会用到皮肤的功能&#xff0c;但是皮肤操作只知道UI操作&#xff0c;但缺点是&#xff0c;只能设置地图默认皮肤&#xff0c;根本都做不到想要的什么皮肤购买功能&#xff0c;自主穿戴功能&#xff0c;而API官方又放在非常隐…

Blazor SSR/WASM IDS/OIDC 单点登录授权实例2-登录信息组件wasm

目录: OpenID 与 OAuth2 基础知识Blazor wasm Google 登录Blazor wasm Gitee 码云登录Blazor SSR/WASM IDS/OIDC 单点登录授权实例1-建立和配置IDS身份验证服务Blazor SSR/WASM IDS/OIDC 单点登录授权实例2-登录信息组件wasmBlazor SSR/WASM IDS/OIDC 单点登录授权实例3-服务端…

SpringCloud-Ribbon实现负载均衡

在微服务架构中&#xff0c;负载均衡是一项关键的技术&#xff0c;它可以确保各个服务节点间的负载分布均匀&#xff0c;提高整个系统的稳定性和性能。Spring Cloud 中的 Ribbon 就是一种负载均衡的解决方案&#xff0c;本文将深入探讨 Ribbon 的原理和在微服务中的应用。 一、…

12 ABC串口接收原理与思路

1. 串口接收原理 基本原理&#xff1a;通过数据起始位判断要是否要开始接收的数据&#xff0c;通过采样的方式确定每一位数据是0还是1。 如何判断数据起始位到来&#xff1a;通过边沿检测电路检测起始信号的下降沿 如何采样&#xff1a;一位数据采多次&#xff0c;统计得到高…

大水仙花数求解

输入位数&#xff0c;求解水仙花数。暴力求解&#xff0c;位数如果太多&#xff0c;会超时。 思路&#xff1a; &#xff08;1&#xff09;11333355和33331155看上去是不一样的两个数&#xff0c;但是它们又一样&#xff0c;因为相同数字出现的次数一样。 &#xff08;2&…

【Makefile】入门

一、编译工具及构建工具介绍 二、Makefile的简单讲解 1. 编译的四个阶段2. Makefile的规则 a. 基本规则b. 伪目标c. 变量赋值和预定义变量d.注释和换行符e. 变量的替换引用f.常见函数 一、编译工具及构建工具介绍 在之前的课程&#xff0c;都是直接使用gcc对代码进行编译&a…

JAVA设计模式之代理模式详解

代理模式 1 代理模式介绍 在软件开发中,由于一些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为"代理"的第三者来实现间接访问.该方案对应的设计模式被称为代理模式. 代理模式(Proxy Design Pattern ) 原始定义是&#xff1a;让你能够提供对象的替代…

Linux运用fork函数创建进程

fork函数&#xff1a; 函数原型&#xff1a; pid_t fork(void); 父进程调用fork函数创建一个子进程&#xff0c;子进程的用户区父进程的用户区完全一样&#xff0c;但是内核区不完全一样&#xff1b;如父进程的PID和子进程的PID不一样。 返回值&#xff1a; RETURN VALUEO…

ncstisc-2018-babydriver:UAF

启动脚本 #! /bin/sh qemu-system-x86_64 \-initrd rootfs.cpio \-kernel bzImage \-append consolettyS0 root/dev/ram oopspanic panic1 /dev/null \-m 64M --nographic \-smp cores1,threads1 -cpu kvm64,smep只可开启了smep保护 题目信息 babyopen 每次open(驱动名称)&…

VitePress-12-markdown中使用vue的语法

前言 VitePress 中&#xff0c;markdown文档最终都会转换成为 html文件&#xff0c;我们在访问的时候&#xff0c;也是直接访问的 xxx.html 文件。而且&#xff0c;markdown文档会被作为 [vue单文件] 进行处理&#xff0c;因此&#xff0c;我们我们可以在文档中使用 vue 语法&…

力扣231. 2 的幂(数学,二分查找,位运算)

Problem: 231. 2 的幂 文章目录 题目描述思路即解法复杂度Code 题目描述 思路即解法 思路1&#xff1a;位运算 1.易验证2的幂为正数&#xff1b; 2.易得2的幂用二进制表示只能有一个位为数字1 3.即将其转换为二进制统计其二进制1的个数 思路2&#xff1a;数学 当给定数n大于1时…

Java并发基础:LinkedTransferQueue全面解析!

内容概要 LinkedTransferQueue类实现了高效的线程间数据传递&#xff0c;支持等待匹配的生产者-消费者模式&#xff0c;基于链表的无界设计使其在高并发场景下表现卓越&#xff0c;且无需担心队列溢出&#xff0c;丰富的方法和良好的可扩展性满足了各种复杂应用场景的需求。 …