目录
1 项目介绍
2 代码实现
2.1 导入库
2.2 设置参数
2.3 定义银行卡信息
2.4 定义显示图片函数
2.5 处理模板
2.5.1 读入图片
2.5.2 灰度处理
2.5.3 二值处理
2.5.4 轮廓检测
2.5.5 轮廓排序
2.5.6 把模板中每一个数字分别切出来
2.6 处理图像
2.6.1 初始化卷积核
2.6.2 读取图像
2.6.3 调整图像尺寸
2.6.4 使图像变为灰度图
2.6.5 礼帽操作
2.6.6 计算横向梯度
2.6.7 闭运算
2.6.8 二值处理
2.6.9 再次进行闭操作
2.6.10 计算轮廓
2.6.11 过滤轮廓
2.6.12 轮廓排序
2.6.13 对轮廓中的内容再进行轮廓检测
我们现在有一张银行卡
我们还有一个模板
效果是这样的
我们切出银行卡上的每一个数字的区域,然后与我的模板进行匹配,匹配之后会得到与模板最像的区域,模板不同的区域就代表着不同的数字,这样就识别出来了
图像处理的流程不唯一,我们当遇到其他项目问题的时候应该对着之前讲过的方法看一下,看看哪一种最合适
并不是所有银行卡都能通用的,如果要找通用的还是要使用人工智能算法
首先我们导入库
此处导入的myutils并不是网上pip install的包,而是一个文件,文件内容如下
import cv2
def sort_contours(cnts, method="left-to-right"):
reverse = False
i = 0
if method == "right-to-left" or method == "bottom-to-top":
reverse = True
if method == "top-to-bottom" or method == "bottom-to-top":
i = 1
boundingBoxes = [cv2.boundingRect(c) for c in cnts] #用一个最小的矩形,把找到的形状包起来x,y,h,w
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
key=lambda b: b[1][i], reverse=reverse))
return cnts, boundingBoxes
def 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 image
if 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)
return resized
内容是基于opencv的两个方法,我们把以上代码存储为一个py文件,在主文件中就可以导入了,后面用的时候会提到
我们有两个参数 -i 是我们要识别的图像,-t是我们要使用的模板
前面的-i,--image是要在控制台中输入字符后,然后输入变量,就像这样
或者这样
这样我们就能使用这个变量了,help是当用户在使用时输入-h或--help可以查看的字符串
required如果为True,那么代码将只能在终端加入变量且无法使用默认值,当我们调试的时候每次都打开终端是比较麻烦的,所以是需要有默认值的,我们先给它置为False,然后给两个参数分别添加默认值
最下面的args = vars(ap.parse_args()是把获得的参数整合到一起,有两种写法,例子中是第一种,我们看一下打印出来会是什么东西
是根据--后的值创建的键值对
我们再看一下第二种,我把参数名称换一下以印证我刚刚的说法
他们的调用方式不一样
我们在案例中为与视频保持一致所以选用带vars的方法
创建一个键值对,键值对的内容是每一种银行卡
定义一个显示图片的函数,在过程测试中会多次显示图片,我们使用定义的函数会减少很多代码量
我们先看一下模板
确认是这张图后对其进行灰度处理
虽然看着差不多,但是确实通道变化了
小于10的值我们改为255,大于10的值我们改为0,由于图像中只有黑与白,所以我们相当于对图像做了一个反色
后面的[1]是这样的,我们之前是这样调用的
现在我们只把结果赋值给了一个变量,所以赋值的变量就会是一个元组,我们取元组的第一位展示出来
检测之后把所有轮廓画出来
我们查看一个轮廓总数
发现总数是10个,之后我们使用myutils对这十个轮廓从左到右进行排序
我们现在进入myutils看一下这个排序是怎么做的
首先看参数,cnts轮廓集合,method方法,这个是排序的方向,恶意是左到右,右到左,下到上,上到下
之后定义变量reverse,起始值为False,也就是不反向,定义变量i,起始值为1
之后对方法种类进行判定
我们使用的是左到右,所以不走这两个判定,所以我们当前的reverse为False,i为0,然后我们继续看中括号中的内容
遍历每个边界,然后对每一个边界执行boundingRect()
,boundingRect()之前介绍过,是轮廓的外接矩形,它会返回一个元组,元组中有x,y,w,h四个变量
之后我们再看这一整句,这样写的意思是将每一个轮廓返回来的每一个元组作为元素,形成一个列表,然后赋值给boundingBoxes
也就是说现在 boundingBoxes 是一个有轮廓外接矩形信息的元组列表
现在只剩最后一句了,我们拆解来看,先看紫色框的zip(cnt,boundingBoxes),这个是意思是将所有轮廓与(x,y,w,h)打包在一起,由于我们之前每动过顺序,所以cnts与(x,y,w,h)是一一对应的
那么执行完上面一句,我元组应该是这样的(cnt,x,y,w,h),其中x,y,w,h是浮点型数据,cnt是每一个轮廓,我们打印cnts中的第0位出来看一下,
我们可以看到cnt也是一个列表,所以我们的zip(cnts,boundingBoes)实际上是这样的 ([cnt],(x,y,w,h))
我们再看第二个小括号也就是蓝色的区域,使用sorted对刚刚压缩的东西进行排序,然后排序方式(key)定义为b,冒号后面没有语句,直接进行选择,这个代表b就是要排序对象本身,启用本身的1号为元素(x,y,w,h)中的第i(0)个元素(x)进行排序,不进行反向
这样我们就得到了以x值为顺序的一组(([cnt],(x,y,w,h)),([cnt1],(x1,y1,w1,h1)),...,([cnt10],(x10,y10,w10,h10))
最后一步,使用匹配压缩,再给cnts,和boundingBoxes整成一堆
我们可以看一下这个
zip(*)会抽取每一个元组中的第一个值然后捏在一起,然后抽第二个值然后捏在一起,直到抽完,我们例子中是先抽第一个值(cnt),然后捏在一起(cnts),再抽第二个值(x,y,w,h),然后捏在一起boundingBoxes
捏在一起的意义在于后面就可以根据索引调用了,而且现在的第0位就是0的轮廓,第一位就是1的轮廓
然后我们会到我们的主函数中来
执行完后,发现我们取第0号元素,也就是排好序的cnts
首先定义一个空的字典digits,这个一会儿要用
之后遍历轮廓,此处我的c是单一的轮廓,i是c这个轮廓对应的索引,现在我们排序完毕了,那么0的轮廓就是0号索引
进入循环后,找到每一个轮廓的外接矩形,然后把该矩形中的模板图片提取出来,ref是我们二值后的模板图片
我们以第一遍的遍历距离,遍历到0了,把0的图片提取出来,此时的roi
然后我们将索引与图像放到digits中,也就是我现在的digits是这样的
我就不一一展示了,一直到9,一共十个,至此我们对处理完了模板
由于卡上不只有数字,所以我们要处理图像排除其他东西的影响
cv2,getStructuringElement是cv2中定义核的一个函数,下面我说一下这个函数的几个参数
此处我们使用myutils中的第二个方法resize
首先定义dim为None,然后获取图片的高与宽,我们是给定width为300的,所以我们直接进入else,定义r = width/原宽度,这个r就是现有图与原有图的横向比例,然后定义dim = (现有图宽度,现有图高度(我们使用原有图高度*横向比例),这个dim是做了一个等比例缩放,之后使用cv2.resize,resize中interpolation可以选择不同的插值方法,有五种插值方法,如下所示
关于插值如果想详细的了解可以看一下这个计算机视觉基础-图像插值算法_CleMints的博客-CSDN博客
我们当前的inter是cv2.INTER_AREA
使用的是我们定义的(9,3)的矩形核
ksize = -1标识采用默认值,默认值为(3,3)
cv2.THRESH_OTSU的意思是,opencv会自动判断一个合适值来代替0,因为我现在也不知道这个像灰色的颜色的值是多少,所以就交给计算器去自动判断
目的是把区域中这些黑色填充成白色
这次使用的是之前定义的sqKernel核,视频中没有写迭代次数,我在这迭代两次,感觉效果要更好些
使用上面的结果找到轮廓后画在原本的图上,之后显示出来
我们实际就是要白色区域内的轮廓,其余轮廓全都不要
这四个轮廓我们可以通过边界矩形的长宽比进行判断
首先创建一个空列表locs,然后获取所有的轮廓外接矩形,之后对每一个矩形计算长宽比,我当前这四个轮廓的长宽比区间在2.5与4之间,过滤掉一部分之后,我们在进行一次过滤筛掉与这四个相似的,我们定义宽度在40-55之间,高度在10-20之间的为我们想要的轮廓
显示出来发现有四个合适的轮廓
之后我们再利用(x,y,w,h)中的x这个值进行排序
我们先定义一个空列表output,这个后面会用
然后遍历这四个轮廓,之后再定义一个空列表groupOutput,之后有用,之后提取原始灰度图中的外接轮廓位置图像
发现没有什么问题,之后对这四张图进行二值处理
然后对这四张照片检测轮廓,之后排序
这样我们就得到了最内层数字的轮廓,比如4000中的4,这个时候我们提取轮廓矩形,然后从上面四张图中切下来
第一组
第二组
第三组
第四组
之后进行模板匹配
roi是被检测图像这种图
digitROI是模板种的这种图
我们只对最大值(最像的值)感兴趣,其余变量我不需要,所以用_直接替代掉,它原本的四个变量应该是这样
然后我们把最大的值放入列表scores中
此时我们的scores是这样的
由于我再每次大循环中都会重新将scores赋值为空,所以我们只能看到四个,我们看到的这四个是最后一组的情况,它使用图中的roi去核digitROI对10次,每一次都会出现一个值,一次类推,第一个会出现10个值,第二个也会出现10个值
我们现在取每10个值中最大的一个值的索引放入groupOutPut中
然后我们画上框,写上字
putText中的0.65是字号,2是线条宽度
我们可以把每一轮的索引搞出来,最后打印出来
我们最后再做一些处理,我们可以通过output的第0位判断银行卡是何种类型4是viso,然后我们在末尾将结果显示出来