3.边缘检测
边缘检测有很多办法,比如Sobel,Canny,Laplace等等,我采用的是Sobel算子对图像作水平差分,以求取垂直边缘。因为由于车牌区域的边缘是以垂直边缘为主,一般的背景区域不会有密集的垂直边缘,如果求取全方向上的边缘的话,在背景比较复杂的时候,背景区域也会存在更多边缘,带来干扰。以Canny算子为例,虽然检测效果看起来更好,但是非车牌的背景区域同样检测出了太多无用的边缘,不利于后续处理。
Canny算子检测结果
Sobel算子检测结果
Sobel检测在OpenCV中也很简单,一个函数搞定。在这里我加上了高斯平滑和二值化操作,希望提取出效果更好的边缘。
GaussianBlur(temp, temp, Size(9, 3), 0, 0); //高斯平滑,这里temp代表待处理图像
Sobel(temp, temp, CV_8U, 2, 0, 3); //利用Sobel算子进行二阶水平差分,求取垂直边缘
//Scharr(temp, temp, CV_8U, 1, 0, 1, 0, BORDER_DEFAULT);
threshold(temp, temp, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU);//二值化
在得到边缘图像之后只要再经过一些形态学操作就可以得到候选区域。这里我们可以先进行开运算,开运算是对图像先腐蚀后膨胀,在腐蚀过程中,可以去除背景中一些细小的杂点,以及比较细的边缘,而车牌区域由于边缘密集所以不容易被完全腐蚀掉,再经过膨胀操作就可以恢复出车牌区域。开运算也可以通过OpenCV库函数morphologyEx实现,这里不再赘述。
开运算后的图像
接着可以再对这些区域完成矩形化处理
对于二值图像中的一点P(i,j) :
P(i,j)若 为黑点,当他的4邻域中存在至少2个白点,则将其置为白点
P(i,j)若 为白点,当他的4邻域全为黑点时,将其置为黑点
反复执行以上操作至图像不再变化。不过这不操作略微费时,实际操作时可以先对图像求取水平和垂直投影,然后只对投影值大于一定阈值的区域进行矩形化,这样可以省下部分时间开销。附上结果和部分代码:
候选区域矩形化
//参数src为待处理源图像
//参数dst用于存储处理后的图像
//x,y两个数组存放水平和垂直投影值
void rectanglize(IplImage *src, IplImage * dst,int * x,int * y) //矩形化
{
double temp = 0;
CvScalar white, black;
white.val[0] = 255;
black.val[0] = 0;
for (int i = 1; i < src->height - 1; i++)
{
for (int j = 1; j < src->width - 1; j++)
{
if (y[i]>15 && x[j]>5)
{
temp = cvGet2D(src, i, j - 1).val[0] + cvGet2D
(src, i, j + 1).val[0] + cvGet2D(src, i - 1, j).val[0] + cvGet2D(src, i + 1, j).val[0];
if (255 == cvGet2D(src, i, j).val[0])
{
if (0 == temp)
cvSet2D(dst, i, j, black); //如果该白点周围都是黑点,则设置改点为黑点
else
cvSet2D(dst, i, j, white); //反之则为白点
}
else
{
if (temp >= 255 * 2)
cvSet2D(dst, i, j, white); //如果该黑点周围存在两个及以上的白点,则设置改点为白点
else
cvSet2D(dst, i, j, black); //反之则为黑点
}
}
}
}
//将图像边缘设置为黑色
for (int i = 0; i < src->height; i++)
{
cvSet2D(dst, i, 0, black);
cvSet2D(dst, i, src->width - 1, black);
}
for (int j = 0; j < src->width; j++)
{
cvSet2D(dst, 0, j, black);
cvSet2D(dst, src->height-1, j, black);
}
}