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