前情提要
上期我们提到了一个做抠车牌的案例,得到了一幅这样的图。
但我们需要做进一步的细处理,让我们得到比较好的白色的车牌区域掩码。
本期内容
本期就是要处理这个问题,先来仔细分析这个问题。我们想要得到的区域是“辽H....”这一块白色的区域,这个区域有什么特点呢?第一、连贯,不像其他地方很多小小的白点;第二、整个区域近似一个矩形,不像车窗前那些区域,虽然连贯,但是形状比较蛇皮。这两个特点可以作为筛选的突破口,我们将介绍一些手段能够根据这些特点进行处理。
一、腐蚀膨胀
腐蚀顾名思义,是把每一个白色区域都往内缩,有些小的噪点就会缩没了。膨胀则相反。可以看看这里的图感受一下。腐蚀的原理是:对于一个点,观察它的周围(邻域)的像素点哪个值最小,就把当前点的值替换成最小值;膨胀则替换成最大值。
比如当前要处理的点是P点,邻域大小是3x3,也就是我们要看看P点周围的8个点和它本身,共9个点,如果是腐蚀操作则把P点的值换成最小值,P点周围有黑色的点,值为0,则P点会变成黑色;如果是膨胀操作P点不发生改变。
对于车牌的掩码图上的噪声,我们就可以用腐蚀操作去掉一些,但车牌区域也会受到影响,所以腐蚀完以后还要膨胀回去,但是噪声已经消失了,所以噪声不会被膨胀回来。先腐蚀后膨胀,这个叫开运算,如果是先膨胀再腐蚀就叫闭运算,是用来填充白色区域内的黑洞的。下面是这个操作的代码。
# 腐蚀
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # 矩形结构
mask = cv2.erode(mask, kernel)
# 膨胀
mask = cv2.dilate(mask, kernel)
kernel是一个3x3的矩阵,值全为1,这个叫矩形核,cv2.getStructuringElement的第一个参数正是指定用哪种形状的核,除了矩形核,还有十字架核cv2.MORPH_CROSS和椭圆核cv2.MORPH_ELLIPSE。
效果如下:
3x3的十字架核是这样的:
不同形状的核的区别是:腐蚀膨胀的最小最大值是从核的值为1的地方找的,值为0的地方则不理会。
二、滤波
滤波是信号处理中减少噪声的方法,图像滤波的方法有很多种:中值滤波、高斯滤波、均值滤波等等,不一而足。
个人常用的是中值滤波,下面介绍这个。
中值是中位数,有序数列中位于中间位置的值,比如[1,2,3,3,6]里的中位数就是3。跟腐蚀膨胀类似,我们的处理也是基于一个核,对于每个像素的处理都是把这个核的中心挪到这个像素的位置再进行处理,这是图像处理中很常见的操作,叫图像卷积。
对于一个3x3的图,中间有一个白色噪点的情况,使用中值滤波则会把这个白点涂黑,从而达到去除噪声的目的。下面是一个示例代码:
mask = cv2.medianBlur(mask, 7)
用7x7的核对图像做中值滤波,效果如下:
其他类型的滤波器基本上只是核里面填充的值不一样,对图像卷积后就产生了不同的作用,如索贝尔算子卷积后能得到图像的梯度。这些滤波器的值都是经过了人为设计,而近几年很火的深度学习技术中有一种卷积神经网络(CNN),它的原理就是各种滤波器的叠加,这些滤波器的值是靠它自己根据训练数据学习后自己填充的。
PS: 经朋友问到,核的大小可以是偶数吗? 建议,最好不要用偶数的核大小,因为中间点不知道在哪,是个浮点的位置,可能会让它四舍五入,也可能会插值,我也搞不清 ,大家可以自己试一下。
算法加速
图像卷积操作的耗时与核的大小成正相关,假如图像大小是NxN,核大小是KxK,则时间复杂度接近O(N*N*K*K)。
我了解到在用十字架核对图像做膨胀操作时有一种快速的方法,复杂度是O(N*N)。具体请参考这里。大概原理是先求每一个点离最近的白色点的曼哈顿距离,然后再遍历一次存放距离的图,把小于K的地方都设为白色。我在MacBook Pro的i5-5257U CPU上测试了11x11的十字架核在640x360的图上用OpenCV处理的用时是20-30ms,但使用上面的方法用时与核大小无关,用时稳定在2-3ms。
对于有些滤波器还可以采用一种叫可分离卷积的方法加速。数学上的原理可以参考维基,本质是利用了线性操作的结合性,卷积是线性操作,矩阵乘法也是,因此可以把某些可拆分成两个向量相乘的滤波器从原来的直接卷积变成两个向量分别卷积。以下是索贝尔算子求x方向梯度拆分的样子。
复杂度从O(N*N*K*K)变成O(N*N*K)。