基于OpenCV的银行卡号识别系统设计与实现
通过输入银行卡号进行绑定是电子货币的互通的基础,但在越来越讲究效率的现在,如何快速的输入卡号成为人们的关注点。因而,本设计设计了一个银行卡号识别系统,该系统完成了银行卡号的识别。该系统是建立在OpenCV基础上的,对拍摄图片进行许多图像处理后得到银行卡号位置,然后切割图片得到该区域,最后采用深度学习的识别模型对其识别,得到识别结果并进行输出。通过多次识别结果对比,证明该系统具有较快的识别速度和较高
系统简介
通过输入银行卡号进行绑定是电子货币的互通的基础,但在越来越讲究效率的现在,如何快速的输入卡号成为人们的关注点。因而,本设计设计了一个银行卡号识别系统,该系统完成了银行卡号的识别。该系统是建立在OpenCV基础上的,对拍摄图片进行许多图像处理后得到银行卡号位置,然后切割图片得到该区域,最后采用深度学习的识别模型对其识别,得到识别结果并进行输出。通过多次识别结果对比,证明该系统具有较快的识别速度和较高的准确率,同时识别结果相对稳定。
关键词 银行卡卡号 图像处理 深度学习
引言
银行卡的识别涉及到多个方面,其中最主要的是图像处理和图像识别这两大方向。随着国民经济的高速发展,在线支付已经成为当前经济的主流支付手段。而在线支付的金钱主要来自银行,所以在线支付平台与银行之间的互通显得尤为重要。而这种互通需要得到银行客户的账号加以绑定,所以,对银行卡号进行识别对银行卡服务业有着重要意义。现代发展越发讲究时间效率,所以对于银行卡的识别需求也变得越发强烈。本系统通过对拍摄的银行卡图片进行处理进行识别,也可以在相关数据参数修改后实现其他物体的识别。
目前使用的图像字符识别包含神经网络,模板匹配,差别不等式等多种识别方式,在这些识别方式里面,以神经网络为基础的识别技术是建立在深度学习飞快发展下实现的,由于一个个优秀的神经网络模型的提出,对于图像的识别有了具体化的搭建,因而成为现在最为广泛的识别方法。通过模拟实现人脑神经元的处理方式对图像进行多种操作得到特征用于识别,但因为处理能力很强,一般需要大量的数据集作为实现基础。模板匹配则是由于基于单一模板,不适应多种版本的卡号识别,无法做到兼容性。差别不等式是建立在图片的差别点与合适的阈值上的,而这些需要大量的对比实验以及计算进行确定,也需要了解相关的特征原理,
本设计通过银行卡号字符的分类提取,设计并完成基于OpenCV的银行卡号识别系统,通过大量识别表明,该系统能够稳定的识别银行卡号。本设计通过对拍摄的银行卡图片进行图像处理,然后使用神经网络搭建的识别模型对处理完成的图片进行识别,借此实现卡号识别,可以为进行与银行卡相关的绑定提供方便。此外,该技术不单单可以应用于银行卡号识别,通过修改不同物体的特征参数,可以进行其他物体的识别。
银行卡号的识别系统流程图如下所示:
图 1 银行卡识别流程图
图像处理的功能是将输入的原始图像处理为适合后续部分处理所要求的图像, 然后对的图像进行卡号字符的查找与保存。其中卡号的查找与提取是该部分的主要的处理目的,卡号查找的结果将会直接影响到后面部分的识别结果。通过轮廓检测得到卡号位置后进行保存并输入输入模型识别部分。
模型识别的主要目的是将截取好的卡号图像导入建立的卡号识别模型中进行识别,得到识别结果并输出。
1 图像处理技术
1.1图片整体提取
图像的整体提取处理是在对灰度图像进行线段检测获得线段数据的基础上实现的,对于银行卡的整体提取需要得到对应的线段数据,数据需要在灰度图上进行线段检测得到,思想是通过算子进行边缘检测,随后进行算法变换,得到线段的数据。
本设计采用的直线检测算法是霍夫变换算法,该算法不仅可以查找图像中的直线,也可以通过调整用来查找图像中相对低级的线结构。通过霍夫变换算法得到的输出的是以浮点数坐标形式的数组,数组中的数值表示检测到的直线的起点与重点坐标。
通过霍夫变换得到了包含图中所有被检测到的线段起点与终点,根据得到的数据选取最小与最大的x,y确定银行卡的整体范围,可以截取到银行卡的具体区域,去除多余的背景。
效果如下:
图 2 原图
图 3 图像整体提取
1.2图像数据处理
本设计的使用的图像数据处理主要为灰度化、浮雕化、二值化等。
1.2.1灰度化
灰度化是相对于彩图而言的,一张基本的彩色图片包含了多个通道,最常见的是RGB,而灰色图像则是多个通道分量相等后的产物。灰度化的好处在于是图像只具备亮度而没有色彩信息,这意味着可以减少计算值。本程序一开始输入的图片便是彩色图片,其中包含了大量不需要用到的色彩信息,如果不去除这些色彩信息,不仅影响处理结果,还会占用数据存储空间。所以对原始图片擦次用灰度化操作不仅可以去除多余的色彩信息,也可以节约存储空间,有利于程序的整体运行。通过OpenCV提供的cv2.cvtColor()函数完成该效果。
效果如下:
图 4灰度化效果
1.3图像检测
1.3.1边缘检测
本设计使用算子对图像数据进行边缘检测,查找图像数据的边缘,边缘是指图像数据中像素灰度值变化最大的部分,因为在边缘两边不是一类内容,会造成较大的灰度值差。能够得到图像数据的一阶梯度。主要用于计算图像的亮度灰度值的近似,算子通过使用两组的卷积滤波,实现了对不同方向上的边缘检测。使用精确数值的方式取代开平方,可以提高程序的运算效率。算子需要具体的阈值,边缘的检测是通过的对比判断得到的,使用像素点的灰度值,与具体的阈值进行对比。边缘是灰度值变化较大的部分,当灰度值比具体阈值高的时候,就将该灰度值对应的像素点判定为边缘点。算子通过加权来减少位置要素的影响,使边缘更清晰,准确性也得以提升。OpenCV提供cv2.Sobel()函数实现边缘检测。
效果如下:
图 7 边缘检测效果图
1.3.2轮廓检测
现在图片的黑白区别已经十分明显了,接下来寻找银行卡卡号的位置,从二值化的结果中可以明显地看出银行卡号的具体位置,但那是以肉眼所见的,如何实现让计算机找到银行卡的卡号需要使用轮廓检测。
轮廓,如其字面意思,物体边缘上一系列的连续点组成的一个空洞图形,
目前的轮廓检测分为两类,一种是在传统的边缘检测基础上进行轮廓检测,而另一种是通过人类的视觉系统提取对应的反应结构建立有指向性的轮廓检测模型。本文采用的轮廓检测方式是第一种,通过边缘检测来实现轮廓检测的方式是一种低位视觉检测,依赖于图像的颜色、亮度等变化显著的特征来确定边缘,得到边缘点,即轮廓点,进而整合在一起,形成对应的轮廓。因此,该方法的缺点在于难以形成完整而封闭的轮廓,而且受噪声的影响很大,检测的图像需要进行降噪处理。同时,边缘检测是将图像与微分算子卷积,这借助于各类算子实现,本文使用的算子为算子。
通过调整边缘检测的阈值与二值化图像可以提高轮廓检测的准确性,通过调用OpenCV提供的cv2.findContours()函数可以实现轮廓检测,通过函数返回的轮廓点在原图上绘出轮廓可以得到轮廓检测的效果图
效果如下:
图 8 轮廓检测效果图
1.3图像截取
从轮廓检测的效果图看出,银行卡的卡号高度在一定范围以内,对得到的轮廓结合进行if判断,若轮廓高度在银行卡号的范围内保存至另一列表中,然后对得到数据进行排序,得出银行卡的大致范围,进而对原图进行适当的扩大范围截取,得到如下的区域:
图 9 卡号截取效果图
2 基于深度学习的银行卡号自动识别算法
2.1深度学习
模型识别是现在的主流识别方式,原理是建立在机器学习中深度学习的基础上的进行特征提取来进行识别。机器学习是机器通过数据进行总结进而得到对应规律来建立模型,这种方式使机器能处理各种实际生活的问题。深度学习是机器学习的重要分支与延伸,它的结构是以人脑处理基本单位神经元为模仿对象的神经元多层神经网络。
深度学习的出现是因为传统的机器学习算法无法解决大量数据带来的特征提取,在互联网高速发展现在,每天都会产生堪称海量的数据,人们正在进入大数据时代。深度学习通过赋予计算机人脑般的处理方式加上计算机自身的高速计算能力对海量数据进行抽象的高层突出属性特征,即所谓的特征提取。
深度学习对数据的处理主要体现在池化层与卷积层这两层上,卷积层的原理来源与人脑对图像的识别方式,当人脑识别图片时,并不是从整个图片整体得到认知的,而是对于图片中的局部进行认知,然后对认知返回的信息进行反馈,直到得到全图的信息。所以,卷积层的作用就是对输入图像进行局部卷积,计算局部的通道值,然后进行合并成为一层卷积层。池化层则是对数据的压缩,体现在对特征降维,减少过拟合发生的概率,让模型的兼容性得以提升。
2.2模型结构
识别网络模型使用的是CRNN文本识别网络模型。目前市场上银行卡的种类很多,而且银行卡的卡号长度不是固定的,因此识别过程中会涉及到不定长字符的预测。一般的识别模型思路是对数字进行切割,在对单字符进行识别,这样可以避免不定长字符的识别,但这样一方面隐形识别效率,另一方面受众多因素影响,字符的切割并不是很准确,经常导致切割出错。为此,本文采用CNN与RNN相结合的识别模型进行识别。其中CNN用于图像的特征提取,RNN进行对不定长字符的处理。具体的采用模型是CNN 的VGG模型与RNN的LSTM模型结合。
整个网络模型如下表所示:
2.3具体模型
2.3.1 CNN模型
本设计选用CNN的VGG16层模型实现图片的特征提取,VGG16层模型主要包括三个部分,第一个是对输入图像进行卷积的卷积层,第二个是对卷积完成的图像数据进行减参的池化层,最后是起到分类器作用的全连接层。
其中卷积层是将得到的三维数据用卷积核处理,将三维图片的三个通道值累加起来,得到一个一维矩阵。一个卷积层的卷积核数不是单个的,很多时候是使用者自己决定的,但因为总数是13层,所以进行分配时需要注意总数。通过卷积核的滑动处理后的数据在激活函数的激活下就成为了一个卷积层的输出。
池化层是对卷积层的输出结果进行信息的简化处理,因为卷积处理完的数据带有大量的相似数据,这些数据在一起会显得冗杂,进而影响对应的特征处理。所以需要对数据进行降维操作,对一定区域内的数据采用最大或最小数据代替该区域,借此实现数据的简化与调整,降低计算难度。
全连接层是整个网络的输出开关,通过对输入的图片数据图像特征进行分类。以不同的数字具有不同的特征这一观点进行分类处理,通过找到对应的特征激活对应神经元,然后将其组合起来,将可能性最大的分类输出。
VGG模型的优点在于结构简单,层次清晰,模型中使用的卷积核尺寸与最大池化尺寸是相同的,而且采用的均为较小的尺寸。通过几个小型的滤波器的组合实用性比一个大的滤波器要好。但缺点在于占用较多计算资源,导致内存占用较大,使得训练速度缓慢。
CNN部分网络模型搭建如下图所示:
图10 CNN模型部分结构
2.3.2 RNN模型
本设计采用RNN的LSTM模型进行不同长度的银行卡卡号处理,RNN本身是循环神经网络,通过循环,将上一层的信息传递到下层,具体的结构如下所示;
图 11 RNN结构示意图
由于RNN的信息只能往后续的层次传递,所以当输出与其相关的输入信息距离近的时候,一般的RNN可以处理。而当这个距离较长时,理论上的RNN 是能够处理的,但由于梯度爆炸的存在使得实现变得困难。因而提出,即LSTM,如名字一样,主要用于处理间隔较长的记忆问题。
下图为LSTM网络模型结构如:
图 12 LSTM模型结构图
遗忘门控制细胞状态的数据删除,该门决定删除多少数据。该门决定删除的程度由一个0到1之间的数值决定,这个数字是前面的结果与现在的输入通过激励函数的处理得到,该数值的大小决定了数据保留的百分比。
输入门控制细胞状态的新数据添加,该门决定添加多少数据。该门决定谈价的程度由一个0到1之间的数值决定,这个数字是前面的结果与现在的输入通过激励函数的处理得到,同时,tanh使用前面的结果与现在的输入来得到可能添加的数据,将其状态设置为准备放入的状态。将准备放入的数据按激活函数得到数值百分比进行提取,就能得到当前门结构真正要添加的新数据量。
输出门控制着细胞状态的结果导出操作。与另外两个门类似,会有一个由激励函数处理得到的数值。细胞状态的数据在与该数值相乘时先要经过一个tanh层进行激活。最后得到就是最终的输出数据。
3 实验结果与分析
3.1数据集准备
深度学习是建立在大量的数据基础上的,所以本文的模型使用了多类型银行卡字体数字进行拼接产生的数据进行训练。除了拼接外,还对数据集进行了其他方面的扩充,比如改变图片的大小,对图片进行部分位移,增加高斯噪声,对图像进行模糊处理等,借此得到增强后的数据集,部分处理效果如下:
图 17 数据集准备示意图
为此准备了1060张不同数位的卡号片段拼接,每一张片段经过处理后得到80张不同处理下的卡号图片,因此得到近9万张供模型训练的数据集图片。
3.2实验过程
模型搭建完毕后,制作数据生成器,使用DataGenerator类用于声明网络输入数据制作器,从train.txt以及val.txt中获取数据集图片,以及对应标签。
训练的部分过程如下图所示:
图 18 部分训练示意图
绘制模型训练的Loss值曲线图,观察Loss值的变化。
图 19 Loss值变化图
4 结束语
本文通过对图像识别与机器学习中存在的处理算法建立与使用,参考一部分成熟的识别操作,完成了银行卡号的识别。同时,该系统不局限在识别银行卡号上,其中涉及到的相关概念与操作思想可以为车牌号码、发票、身份证号等其他有关数字识别的图像识别提供思路。在以后的优化调整中,我认为有以下几个方面可以对该系统进行改进。
首先,图像处理部分可以使用更为精准的阈值进行检测,这个需要多次实验得到通用性较强的数据。其次,可以优化神经网络的结构,并采取新的模型进行使用。最后,应该适当扩大数字模板库,使模型识别范围变得更宽,让程序可以识别更多的银行卡字体类别。
在设计并实现这个系统时,存在着部分麻烦。首先,在图像处理调用opencv的轮廓查找函数来对银行卡卡号进行定位时,使用OpenCV的cv2.FindContours查找轮廓时,时常无法到完整轮廓,查找轮廓得到的边界往往因为尺寸以及版式原因使得银行卡的位置难以确定,最后再多次尝试后基本确定了银行卡卡号的高度大小数值。 其次,模型的数据集的准备困难,因为模型识别是建立在大数据之上的,而我也不知道去哪里获得大量的数据。后来在进行卡号截取时,得到卡号,于是打算使用大量的卡号实现数据集。通过百度获得了大量不同类型的银行卡图片,通过截取获得了对应的卡号,再对它们多种处理,得到了总数达到8万的数据集。最后是选择神经网络进行建模识别的过程中,面对如同人脑一般复杂的神经网络结构,一度不知道从何下手,后来从简单二分类识别模型进行学习,大致明白了的深度学习识别的原理。
致 谢
时光匆匆,如同白驹过隙一般,就要毕业了。回首昨日,高考仿佛还在眼前,转眼之间,四年的大学生活即将画上一个句号,但对于接下来的人生,这只是一个开头。四年的大学,收获了很多东西,从对计算机内部世界的茫然无知到现在的实现机器视觉与机器学习的相关论文,这是一条艰难的路。没有人能够永远顺风顺水,努力也有力所不及的地方。这一路,吃了多少苦,受了多少罪,没有人比自己更清楚。但令人高兴的是这条路我不是一个人在走,不管是前面的行走的前辈留下的足迹,还是同辈人的支持与帮助,都是我不曾放弃的理由。宝剑锋从磨砺出,梅花香自苦寒来,有努力的人终究不会后悔,也终会有苦尽甘来的那一天。
在此感谢在本论文撰写期间给予过我帮助的同事、同学、朋友、导师、父母,感谢我的同事跟我讲述了哪些东西是我实现论文所需要的;感谢我的朋友与同学时常与我分享他们的心得与发现;感谢我的导师指出我的不足并给予我改进的建议;感谢父母在平日里为我这个做儿子的默默付出。没有以上人士的帮助与鼓励,我想我很难完成我的论文,因而,我很庆幸拥有着他们的帮助,礼尚往来,我在实现论文的同时,也努力地为他人提供我自己的见解与意见。
最后,再次郑重感谢我的指导老师魏翠萍教授!魏翠萍教授为论文研究思路的设计和文献资料的选择提供了有益的指导,使得论文能在成文阶段减少很多不必要的错误。同时,魏翠萍教授孜孜以求、严谨治学的作风也令我受益良多。在此论文完成之际,谨向魏翠萍老师致以衷心的感谢!
附 录
Image.py
import cv2
import numpy as np
# 银行卡图像处理
class Image:
HEIGHT = 550
WIDTH = 350
img = 0
remove_back_img = 0
number_area = 0
def __init__(self, img):
self.img = img
H, W, _ = img.shape
if H / W > 0.6 and H / W < 0.69:
self.remove_back_img = cv2.resize(self.img, (self.HEIGHT, self.WIDTH))
else:
self.remove_back_img = self.removeBackground()
self.number_area = self.position(self.remove_back_img)
self.pos_img = self.getNumberArea()
def getNumberArea(self):
num_img = self.number_area
h, w, _ = num_img.shape
gray_img = cv2.cvtColor(num_img, cv2.COLOR_BGR2GRAY)
dilate_img = self.embossment(gray_img)
embo_img = cv2.medianBlur(dilate_img, 3)
_, thresh_img = cv2.threshold(embo_img, 155, 255, cv2.THRESH_BINARY)
thresh_img = cv2.medianBlur(thresh_img, 3)
thresh_img = cv2.GaussianBlur(thresh_img, (3, 3), 0)
thresh_img = cv2.dilate(thresh_img, None, iterations=10)
_, thresh_img = cv2.threshold(thresh_img, 220, 255, cv2.THRESH_BINARY)
a = np.zeros(w, np.uint8)
for j in range(0, w):
for i in range(0, h):
if thresh_img[i, j] == 0:
a[j] += 1
a = a[::-1]
length = int(0.75 * w)
min_ = sum(a)
start = 0
for i in range(len(a)):
if a[i] < 15:
a[i] = 0
else:
a[i] = 35
for i in range(w - length):
end = i + length
mean_ = a[i:end].mean()
if (min_ > mean_ and i < 50):
min_ = mean_
start = i
end = w - start
a = a[::-1]
min_ = sum(a)
start = 0
for i in range(130):
mean_ = a[i:end].mean()
if min_ > mean_:
min_ = mean_
start = i
self.W_end = end + 20
self.W_start = start + 25
return num_img[:, start + 5:end]
def removeBackground(self):
resize_img = cv2.resize(self.img, (self.HEIGHT, self.WIDTH), 0, 0, cv2.INTER_NEAREST)
self.img = resize_img
gray_img = cv2.cvtColor(resize_img, cv2.COLOR_BGR2GRAY)
blur_img = cv2.medianBlur(gray_img, 9)
x = cv2.Sobel(blur_img, cv2.CV_32F, 1, 0, 3)
y = cv2.Sobel(blur_img, cv2.CV_32F, 0, 1, 3)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
sobel_img = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
thresh_img = cv2.adaptiveThreshold(sobel_img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 3,
0)
tmp = cv2.findContours(thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(tmp) == 2:
cnts, _ = tmp
else:
_, cnts, _ = tmp
temp = 0
W = 0
H = 0
X = 0
Y = 0
for i in range(0, len(cnts)):
x, y, w, h = cv2.boundingRect(cnts[i])
if (temp < w + h):
temp = w + h
W = w
H = h
X = x
Y = y
remove_back_img = resize_img[Y:Y + H, X:X + W]
return cv2.resize(remove_back_img, (self.HEIGHT, self.WIDTH), cv2.INTER_NEAREST)
def embossment(self, img):
H, W = img.shape
dst = np.zeros((H, W), np.uint8)
for i in range(0, H):
for j in range(0, W - 1):
grayP0 = int(img[i, j])
grayP1 = int(img[i, j + 1])
newP = grayP0 - grayP1 + 150
if newP > 255:
newP = 255
if newP < 0:
newP = 0
dst[i, j] = newP
return dst
def horizontal(self, img):
H, W = img.shape
hor_array = np.zeros(H, np.int32)
for j in range(0, H):
for i in range(0, W):
if img[j, i] == 0:
hor_array[j] += 1
return hor_array
def getArea(self, array):
H = len(array)
label_H = int(H / 10)
min_ = sum(array)
ans = 0
for i in range(int(1 / 2 * H) - label_H):
a = int(2 / 5 * H) + i
b = int(2 / 5 * H) + i + label_H
mean = array[a:b].mean()
if mean < min_:
ans = a
min_ = mean
if a > 0.6 * H:
return ans, ans + label_H
def position(self, img):
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_img = cv2.dilate(gray_img, None, iterations=2)
gray_img = cv2.erode(gray_img, None, iterations=2)
emboss_img = self.embossment(gray_img)
sobel_x = cv2.Sobel(emboss_img, cv2.CV_32F, 1, 0, 3)
sobel_y = cv2.Sobel(emboss_img, cv2.CV_32F, 0, 1, 3)
absX = cv2.convertScaleAbs(sobel_x)
absY = cv2.convertScaleAbs(sobel_y)
sobel_img = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
sobel_img = cv2.medianBlur(sobel_img, 11)
sobel_img = cv2.dilate(sobel_img, None, iterations=2)
_, threshold = cv2.threshold(sobel_img, 10, 255, cv2.THRESH_BINARY)
threshold = cv2.GaussianBlur(threshold, (9, 9), 0)
pixel_array = self.horizontal(threshold)
start, end = self.getArea(pixel_array)
self.H_start = start
self.H_end = end
res = img[start:end, 20:]
return res
Model.py
import keras
from keras.layers import Lambda, Dense, Bidirectional, GRU, Flatten, TimeDistributed, Permute, Activation, Input
from keras.layers import LSTM, Reshape, Conv2D, MaxPooling2D, BatchNormalization, ZeroPadding2D
from keras import backend as K
import numpy as np
import os
import tensorflow as tf
def ctc_loss_layer(args):
y_true, y_pred, pred_length, label_length = args
batch_cost = K.ctc_batch_cost(y_true, y_pred, pred_length, label_length)
return batch_cost
def model(is_training=True, img_shape=(32, 256, 1), num_classes=11, max_label_length=26):
initializer = keras.initializers.he_normal()
picture_height,picture_width, picture_channel = img_shape
# CNN part
inputs = Input(shape=(picture_height, picture_width, picture_channel), name='pic_inputs')
x = Conv2D(64, (3, 3), strides=(1, 1), padding="same", kernel_initializer=initializer, use_bias=True,
name='conv2d_1')(inputs)
x = BatchNormalization(name="BN_1")(x)
x = Activation("relu", name="relu_1")(x)
x = MaxPooling2D(pool_size=(2, 2), strides=2, padding='valid', name='maxpl_1')(x)
x = Conv2D(128, (3, 3), strides=(1, 1), padding="same", kernel_initializer=initializer, use_bias=True,
name='conv2d_2')(x)
x = BatchNormalization(name="BN_2")(x)
x = Activation("relu", name="relu_2")(x)
x = MaxPooling2D(pool_size=(2, 2), strides=2, padding='valid', name='maxpl_2')(x)
x = Conv2D(256, (3, 3), strides=(1, 1), padding="same", kernel_initializer=initializer, use_bias=True,
name='conv2d_3')(x)
x = BatchNormalization(name="BN_3")(x)
x = Activation("relu", name="relu_3")(x)
x = Conv2D(256, (3, 3), strides=(1, 1), padding="same", kernel_initializer=initializer, use_bias=True,
name='conv2d_4')(x)
x = BatchNormalization(name="BN_4")(x)
x = Activation("relu", name="relu_4")(x)
x = MaxPooling2D(pool_size=(2, 1), strides=(2, 1), name='maxpl_3')(x)
x = Conv2D(512, (3, 3), strides=(1, 1), padding="same", kernel_initializer=initializer, use_bias=True,
name='conv2d_5')(x)
x = BatchNormalization(axis=-1, name='BN_5')(x)
x = Activation("relu", name='relu_5')(x)
x = Conv2D(512, (3, 3), strides=(1, 1), padding="same", kernel_initializer=initializer, use_bias=True,
name='conv2d_6')(x)
x = BatchNormalization(axis=-1, name='BN_6')(x)
x = Activation("relu", name='relu_6')(x)
x = MaxPooling2D(pool_size=(2, 1), strides=(2, 1), name='maxpl_4')(x)
x = Conv2D(512, (2, 2), strides=(1, 1), padding='same', activation='relu', kernel_initializer=initializer,
use_bias=True, name='conv2d_7')(x)
x = BatchNormalization(name="BN_7")(x)
x = Activation("relu", name="relu_7")(x)
conv_otput = MaxPooling2D(pool_size=(2, 1), name="conv_output")(x)
x = Permute((2, 3, 1), name='permute')(conv_otput)
rnn_input = TimeDistributed(Flatten(), name='for_flatten_by_time')(x)
# RNN part
y = Bidirectional(LSTM(256, kernel_initializer=initializer, return_sequences=True), merge_mode='sum',
name='LSTM_1')(rnn_input)
y = BatchNormalization(name='BN_8')(y)
y = Bidirectional(LSTM(256, kernel_initializer=initializer, return_sequences=True), name='LSTM_2')(y)
y_pred = Dense(num_classes, activation='softmax', name='y_pred')(y)
base_model = keras.models.Model(inputs=inputs, outputs=y_pred)
y_true = Input(shape=[max_label_length], name='y_true')
y_pred_length = Input(shape=[1], name='y_pred_length')
y_true_length = Input(shape=[1], name='y_true_length')
ctc_loss_output = Lambda(ctc_loss_layer, output_shape=(1,), name='ctc_loss_output')(
[y_true, y_pred, y_pred_length, y_true_length])
model = keras.models.Model(inputs=[y_true, inputs, y_pred_length, y_true_length], outputs=ctc_loss_output)
if is_training:
return model
else:
return base_model
App.py
# -*- coding: utf-8 -*-
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMessageBox,QDesktopWidget,QGraphicsPixmapItem,QFileDialog,QGraphicsScene,QApplication
from PyQt5.QtGui import QPixmap,QImage
from PyQt5.QtCore import Qt
from graphics import GraphicsView,GraphicsPixmapItem
import cv2
import numpy as np
from Image import Image
from predict import single_recognition
model_dir='./trainning/crnn.h5'
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.setWindowTitle("银行卡卡号识别")
MainWindow.setFixedSize(825,570)
center = QDesktopWidget().screenGeometry()
MainWindow.move((center.width()-825)/2,(center.height()-570)/2)
MainWindow.resize(825, 617)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayoutWidget_2 = QtWidgets.QWidget(self.centralwidget)
self.verticalLayoutWidget_2.setGeometry(QtCore.QRect(670, 190, 151, 341)) self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_2)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.pushButton_3 = QtWidgets.QPushButton(self.verticalLayoutWidget_2)
self.pushButton_3.setObjectName("pushButton_3")
self.verticalLayout_2.addWidget(self.pushButton_3)
self.pushButton_2 = QtWidgets.QPushButton(self.verticalLayoutWidget_2)
self.pushButton_2.setObjectName("pushButton_2")
self.verticalLayout_2.addWidget(self.pushButton_2)
self.pushButton_4 = QtWidgets.QPushButton(self.verticalLayoutWidget_2)
self.pushButton_4.setObjectName("pushButton_4")
self.verticalLayout_2.addWidget(self.pushButton_4)
self.pushButton = QtWidgets.QPushButton(self.verticalLayoutWidget_2)
self.pushButton.setObjectName("pushButton")
self.verticalLayout_2.addWidget(self.pushButton)
self.graphicsView = GraphicsView(self.centralwidget)
self.graphicsView.setEnabled(True)
self.graphicsView.setGeometry(QtCore.QRect(10, 180, 552, 352))
self.graphicsView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.graphicsView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.graphicsView.setObjectName("graphicsView")
self.graphicsView_3 = QtWidgets.QGraphicsView(self.centralwidget)
self.graphicsView_3.setGeometry(QtCore.QRect(10, 30, 502, 52))
self.graphicsView_3.setObjectName("graphicsView_3")
self.label = QtWidgets.QTextEdit(self.centralwidget)
self.label.setGeometry(QtCore.QRect(10, 100, 502, 52))
self.label.setObjectName("label") self.label.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.radioButton = QtWidgets.QRadioButton(self.centralwidget)
self.radioButton.setGeometry(QtCore.QRect(550, 150, 112, 23))
self.radioButton.setObjectName("radioButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 825, 28))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.action = QtWidgets.QAction(MainWindow)
self.action.setObjectName("action")
self.action_2 = QtWidgets.QAction(MainWindow)
self.action_2.setObjectName("action_2")
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
MainWindow.setStyleSheet("QMainWindow{background:#66B3FF}")
self.graphicsView.setStyleSheet("QGraphicsView{background:#66B3FF}") self.graphicsView_3.setStyleSheet("QGraphicsView{background:#66B3FF}")
self.graphicsView.setLineWidth(3)
self.graphicsView.setMidLineWidth(5)
self.graphicsView_3.setLineWidth(3)
self.graphicsView_3.setMidLineWidth(5)
self.pushButton_3.setText(_translate("MainWindow", "打开图片"))
self.pushButton_3.setIcon(QtGui.QIcon("icons/open.svg"))
self.pushButton_3.setIconSize(QtCore.QSize(20,20))
self.pushButton_3.setStyleSheet("QPushButton{background:#16A085;border:none;color:#000000;font-size:15px;}"
"QPushButton:hover{background-color:#008080;}")
self.pushButton_2.setText(_translate("MainWindow", "卡号定位"))
self.pushButton_2.setIcon(QtGui.QIcon("icons/locate.svg"))
self.pushButton_2.setIconSize(QtCore.QSize(20,20)) self.pushButton_2.setStyleSheet("QPushButton{background:#FFA500;border:none;color:#000000;font-size:15px;}"
"QPushButton:hover{background-color:#D26900;}")
self.pushButton_4.setText(_translate("MainWindow", "卡号识别"))
self.pushButton_4.setIcon(QtGui.QIcon("icons/bank_card.svg"))
self.pushButton_4.setIconSize(QtCore.QSize(20,20)) self.pushButton_4.setStyleSheet("QPushButton{background:#9F35FF;border:none;color:#000000;font-size:15px}"
"QPushButton:hover{background-color:#9932CC;}")
self.pushButton.setText(_translate("MainWindow", "退出"))
self.pushButton.setIcon(QtGui.QIcon("icons/close_.svg"))
self.pushButton.setIconSize(QtCore.QSize(20,20)) self.pushButton.setStyleSheet("QPushButton{background:#CE0000;border:none;color:#000000;font-size:15px;}"
"QPushButton:hover{background-color:#8B0000;}")
self.label.setText("银行卡卡号")
self.label.setFrameShape(QtWidgets.QFrame.Box)
self.label.setLineWidth(3)
self.label.setMidLineWidth(5)
font = QtGui.QFont()
font.setBold(True)
font.setPointSize(13)
font.setWeight(75)
self.label.setFont(font)
self.label.setAlignment(Qt.AlignCenter)
self.radioButton.setText(_translate("MainWindow", "手动定位"))
self.pushButton.clicked.connect(self.close)
self.pushButton_3.clicked.connect(self.clickOpen)
self.pushButton_2.clicked.connect(self.clickLocation)
self.pushButton_4.clicked.connect(self.recognition)
self.radioButton.setCheckable(True)
self.radioButton.toggled.connect(self.checkbox)
self.messageBox = QMessageBox() self.messageBox.setStyleSheet("QMessageBox{background-color:#CE0000;border:none;color:#000000;font-size:15px;}")
def close(self):
reply = self.messageBox.question(None,"Quit","确定要关闭该程序?",QMessageBox.Yes | QMessageBox.No,QMessageBox.No)
if reply == QMessageBox.Yes:
sys.exit()
def clickOpen(self):
imgName,imgType = QFileDialog.getOpenFileName(None,"打开图片","","*.jpg;;*.png;;*.jpeg;;All Files(*)")
img = cv2.imread(imgName)
self.image = Image(img)
self.img = self.image.pos_img
H,W,C = self.image.img.shape
P = 3 * W
qimage = QImage(self.image.img.data,W,H,P,QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(qimage)
self.graphicsView.setSceneRect(0,0,550,350)
self.graphicsView.setItem(pixmap)
self.graphicsView.Scene() self.graphicsView.setStyleSheet("QGraphicsView{background-color:#66B3FF}")
def clickLocation(self):
if self.radioButton.isChecked() == False:
self.graphicsView.viewport().setProperty("cursor", QtGui.QCursor(QtCore.Qt.ArrowCursor))
Img = self.image.pos_img
self.img = Img
Img = cv2.resize(Img,(500,50),cv2.INTER_NEAREST)
H,W,_ = Img.shape
P = 3 * W
Img = QImage(Img.data,W,H,P,QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(Img)
Item = QGraphicsPixmapItem(pixmap)
self.graphicsView_3Scene = QGraphicsScene()
self.graphicsView_3Scene.addItem(Item)
self.graphicsView_3.setSceneRect(0,0,500,50) self.graphicsView_3.setStyleSheet("QGraphicsView{background-color:#66B3FF}")
self.graphicsView_3.setScene(self.graphicsView_3Scene)
backImg = self.image.remove_back_img.copy() cv2.rectangle(backImg,(self.image.W_start,self.image.H_start),(self.image.W_end,self.image.H_end),(0,0,255),2)
backImg = cv2.resize(backImg,(550,350),cv2.INTER_NEAREST)
H,W,_ = backImg.shape
P = 3 * W
backImg = QImage(backImg.data,W,H,P,QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(backImg)
self.graphicsView.setItem(pixmap)
self.graphicsView.Scene()
else:
backImg = self.image.remove_back_img.copy()
Img = backImg[int(self.graphicsView.image_item.start_point.y()):int(self.graphicsView.image_item.end_point.y()),
int(self.graphicsView.image_item.start_point.x()):int(self.graphicsView.image_item.end_point.x())]
Img = cv2.resize(Img,(500,50),cv2.INTER_NEAREST)
self.img = Img
Img = QImage(Img.data,500,50,1500,QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(Img)
Item = QGraphicsPixmapItem(pixmap)
self.graphicsView_3Scene = QGraphicsScene()
self.graphicsView_3Scene.addItem(Item)
self.graphicsView_3.setSceneRect(0,0,500,50)
self.graphicsView_3.setStyleSheet("QGraphicsView{background-color:#66B3FF}")
self.graphicsView_3.setScene(self.graphicsView_3Scene)
def checkbox(self):
if self.radioButton.isChecked() == True:
self.graphicsView.viewport().setProperty("cursor", QtGui.QCursor(QtCore.Qt.CrossCursor))
backImg = self.image.remove_back_img
backImg = cv2.resize(backImg,(550,350),cv2.INTER_NEAREST)
H,W,_ = backImg.shape
P = 3 * W
backImg = QImage(backImg.data,W,H,P,QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(backImg)
self.graphicsView.setItem(pixmap)
self.graphicsView.Scene()
self.graphicsView.image_item.setStart(True)
print(self.graphicsView.image_item.isStart)
elif self.radioButton.isChecked() == False:
self.graphicsView.image_item.setStart(False)
self.graphicsView.viewport().setProperty("cursor", QtGui.QCursor(QtCore.Qt.ArrowCursor))
def recognition(self):
img = self.img
res = single_recognition(img,model_dir)
self.label.setText(res)
self.label.setAlignment(Qt.AlignCenter)
Run.py
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from app import *
if __name__ == '__main__':
app = QApplication(sys.argv)
MainWindow = QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.setWindowTitle("银行卡卡号识别")
MainWindow.show()
sys.exit(app.exec_())
更多推荐


所有评论(0)