cv区域变黑 python_python识别银行卡号

滑稽研究所 14ebf1bf43433dd6e4e9109770e5bf26.gif

python识别银行卡号

哈喽,大家好呀,我是滑稽君。本期我们利用opencv来识别银行卡号,除了之前已经学习过的基础的图像处理方法之外,本次要用到一个新的方法——cv2.matchTemplate()函数,它的作用是模板匹配。我们把它放在最后讲解。

6889dee3328821c9aaa91e9dd013404d.gif

视频讲解:

我们的数字模板如下:

7ed352dc598a5c42958b1bfb9a25c671.png

模板的数字是等距的,我们需要把每个数字单独的裁出来,然后人工加上编号。使用enumerate()方法即可实现。

简单粗暴一点,上代码,我们跟着代码每个阶段的运行结果来理一遍思路。

源代码:

import cv2 as cvfrom imutils import contoursimport matplotlib as pltimport numpy as np#对模板图像做预处理img=cv.imread("images/ocr_a_reference.png")gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)ref=cv.threshold(gray,10,255,cv.THRESH_BINARY_INV)[1]#二值化refCnts=cv.findContours(ref.copy(),cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)[0]#后面还要继续使用ref,因此需使用ref.copy(),否则会对原图做出改变;第二个参数为指定检测外轮廓;第三个参数为轮廓逼近的一种方法cv.drawContours(img,refCnts,-1,(0,0,255),3)#-1表示绘制所有轮廓,当指定为其他值时,只在图像中选择一个绘制单个轮廓cv.imshow("m", img)refCnts=contours.sort_contours(refCnts,method="left-to-right")[0]#返回排序完的轮廓digits={}#建立一个字典类型,i是轮廓索引,c是轮廓----字典类型:每个索引号对应一个索引值for(i,c)in enumerate(refCnts):#i是轮廓索引,c是对应轮廓,则完成了对检测出来的轮廓进行了排序    (x,y,w,h)=cv.boundingRect(c)#得到每一个外接矩形的左上坐标点以及长度、宽度    roi=ref[y:y+h,x:x+w]#每个数字的外接矩形的尺寸    roi=cv.resize(roi,(57,88))#重置外接矩形的尺寸至合适大小    digits[i]=roi#每个数字对应一个模板#展示模板cv.imshow("7", digits[7])#对待检测图像做预处理recKernel=cv.getStructuringElement(cv.MORPH_RECT,(10,3))#为保证检测信息准确,需去除银行卡页面杂乱信sqKernel=cv.getStructuringElement(cv.MORPH_RECT,(2,2))#因此需要对图像做形态学操作,故在此设立卷积核image=cv.imread("images/credit_card_01.png")image=cv.resize(image,(250,200))gray=cv.cvtColor(image,cv.COLOR_BGR2GRAY)tophat=cv.morphologyEx(gray,cv.MORPH_TOPHAT,recKernel)#根据字体的大小来选定合适的核;顶帽操作来突出明亮的区域#此处为x方向gradx=cv.Sobel(tophat,ddepth=cv.CV_32F,dx=1,dy=0,ksize=3)#对X还是对Y需要或者同时需要根据实际需要来设定,图像梯度gradx=np.absolute(gradx)#取绝对值(minVal,maxVal)=(np.min(gradx),np.max(gradx))#归一化#8位无符号数,取值范围为0~255,超出范围则会被截断(截断指的是,当数值大于255保留为255,当数值小于0保留为0,其余不变)gradx=(255*((gradx-minVal)/(maxVal-minVal)))gradx=gradx.astype("uint8")cv.imshow("2", gradx)gradx=cv.morphologyEx(gradx,cv.MORPH_CLOSE,recKernel)#执行闭操作,使图像上的内容成块出现cv.imshow("8", gradx)ret,thresh=cv.threshold(gradx,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)#低阈值之所以设为0,是因为后面的方法选用了OTSU自动设定阈值,适合双峰的图像操作thresh=cv.morphologyEx(thresh,cv.MORPH_CLOSE,sqKernel)#本次闭操作是为了填补二值化图像中块中的不完整小块cv.imshow("9", thresh)cv.imshow("4", thresh)Cnts=cv.findContours(thresh.copy(),cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)[0]cnts=CntscurImage=image.copy()cv.drawContours(curImage,cnts,-1,(0,0,255),3)#此处轮廓不是原图像的轮廓,而是经历了一些列运算之后的图像的轮廓cv.imshow("5", curImage)locs=[]for (i,c)in enumerate(cnts):    (x,y,w,h)=cv.boundingRect(c)#做出每个轮廓的外接矩形    ar=w/float(h)#根据外接矩形的长宽比来筛选有用的矩形,并将其添加到元组中    print("rate",ar)    print("w",w,"h",h)    if ar>2.4 and ar<4.0:        if(w>35 and w<55)and(h>10 and h<20):            locs.append((x,y,w,h))locs=sorted(locs,key=lambda x:x[0])#经筛选之后的轮廓print("this:",locs)output=[]#我们之前筛选出了4个轮廓,每个轮廓又等分成4份,分别与模板对比for (i,(gx,gy,gw,gh))in enumerate(locs):#遍历每一块中的每一个数字    groupOutput=[]    group=gray[gy-2:gy+gh+2,gx-2:gx+gw+2]#取轮廓及其周围的区域    cv.imshow("group",group)    group=cv.threshold(group,0,255,cv.THRESH_BINARY|cv.THRESH_OTSU)[1]#后面的[]要加,否则会报错元组类型不能copy,下面再对每个块进行轮廓检测、绘制    digitCnts,hierarchy=cv.findContours(group.copy(),cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)#每一个group再进行轮廓检测、绘制    digitCnts=contours.sort_contours(digitCnts,method="left-to-right")[0]    for c in digitCnts:#计算每一组数中的每一个数值        (x,y,w,h)=cv.boundingRect(c)        roi=group[y:y+h,x:x+w]        roi=cv.resize(roi,(57,88))#尺寸需与模板的尺寸对应,得到每一个数字所在的区域        scores=[]#新建一个空列表,用来存储检测到的数字        for (digit,digitROI)in digits.items():#在模板预处理中建立了数值的字典类型,一个为索引、一个为值            result=cv.matchTemplate(roi,digitROI,cv.TM_CCOEFF)#匹配,返回与之匹配度最高的数值            #cv.TM_CCOEFF方法越匹配,返回数值越高,数值越低则代表匹配结果越不理想。所以我们下面需要取最大值            (_,score,_,_)=cv.minMaxLoc(result)#做10次匹配,取最大值(注意:取最大值还是最小值跟选取的模板匹配方法有关)            #4个值分别为 min_val,max_val,min_indx,max_indx   最大值和索引            scores.append(score)        groupOutput.append(str(np.argmax(scores)))    cv.rectangle(image,(gx-5,gy-5),(gx+gw+5,gy+gh+5),(0,0,255),1)#第一组的矩形框    cv.putText(image,"".join(groupOutput),(gx-8,gy-15),cv.FONT_HERSHEY_SIMPLEX,0.65,(0,0,255),2)    output.extend(groupOutput)print("Credit Card #: {}".format("".join(output)))cv.imshow("Image",image)cv.waitKey(0)cv.destroyAllWindows()

素材:

7cbe7ced2ae48c6911e1eecefd1b5e77.png

处理数字模板:

0f677501062abcfc64c0ea6e66b8946c.png

裁剪的7的区域:

04b9d8d3fe0d986f586f197e3ca994eb.png

如上图,我们遍历数字的轮廓,这意味着我们可以得到它的最小正外接矩形,坐标也就得到了。那么等值裁切就很简单了。带入左上角坐标加上宽高遍历即可。

顶帽操作:

8b2265fe109cd0682b5cd602eee81716.png

571180000ab4983c5e59d6a3deaa7fb1.png

上图一经过二值化之后,又进行了一次顶帽操作(原图像-开运算)和cv2.Sobel()的横向边缘计算,即x方向的计算。第二张图为y方向的计算。很显然我们应该选择第一张。

容易粘连的地方:

6437838d5d85b00369d430f99fc674bc.png

e43e4f213fce1c8e5ef3858821ef8b81.png

就我个人理解上,这样的好处除了使我们数字轮廓更加明显,另一点就是防止后续进行闭操作时,卡号和左下方的数字距离太近导致粘连。这会使我们的模板无法匹配。同理,若干扰项与我们需要的区域左右相邻,是否可以采用y方向上的运算?

两次闭操作之后:

2550c94ca167d5d4d92b03f1f6a05b43.png

600606994ded2d4f5873c5ec42770b38.png

此时已经具备了获取轮廓的条件。我们画出轮廓。红色区域即是我们捕捉到的轮廓,之前我们一直使用面积去排除干扰项,很显然对本次的情况来说不适用。卡号区域均为四个数字一组,那么我们可以通过计算长宽比的方法来筛选我们的卡号区域。方法还是通过添加轮廓的最小外接正矩形。阈值应根据实际情况来调整。

在获取坐标之后,我们使用sort()对x坐标来排序,完成轮廓从左到右的排序。这是为了确保我们与模板对比时顺序正确。

最终结果:

2f81b8de5d8d8eba0eb591b1f53b882e.png

在我们筛选出区域之后,4个数字为一组,同样是遍历裁剪出单独的区域,前文我们说过,在获取轮廓的最小外接正矩形之后,得到坐标和宽高,等值裁剪就是一件十分容易的事情。之后就是顺序遍历模板进行对比。对比的具体方法,写在了注释里。对比方法有3种,我们按需选择,调整后续的筛选方法。

最后enumerate()方法可能不常用但不复杂,我们对模板进行处理的时候,经过从左到右的排序之后,还需要人工赋值。这个方法可以很方便的帮我们完成。

我们需要注意的就是对如何让轮廓按照我们的需要排序。从左到右?按照坐标从大到小?之前我们对轮廓的顺序排列没有要求,这次我们用到了应认真学习理解。我认为在以后会经常用到。不要觉得知识点零碎,修改参数重复实验繁琐。只有深入理解它们才能面对各自需求灵活使用。

其他素材:

f3ea34c406a749b6c1b8a60712b0df82.png

f6e43854ec8b780d8c6ea8349e6c5c10.png

看下面这个例子:

refCnts = ["x","y","z","m","n"]for(i,c)in enumerate(refCnts):    print(i,c)#其实就是加上了索引>>>0 x1 y2 z3 m4 n#我们通过这个方法完成了对轮廓从左到右的排序。#当然还要其他排序的方法,大家百度自行了解哈。digitCnts=contours.sort_contours(digitCnts,method="left-to-right")[0]
本期素材来源:https://blog.csdn.net/weixin_44823151/article/details/100540174
177adbec31d1709de213e3ed5f5edf25.gif ❂ END     enumerate()方法与我们的数字模板很契合,是我们很轻松的完成了人工标注。如果模板内容包括字母呢?让你识别数字和字母混合的验证码应该怎么办? 691e0841569035a946839c6c919efd0c.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值