车牌定位是车牌识别中第一步,也是最重要的一步。
由于中国车牌种类多样,颜色不一, 再加上车牌经常有污损,以及车牌周围干扰因素太多,都成为了车牌定位的难点。
这里首先使用最简单算法来描述车牌定位,以及他的缺陷和改进。
一、投影法
1、车辆图像信息获取
2、HSV颜色转换
把RGB数据转换成HSV空间图像数据
hsvzation(image,hsv,width,height);
3、HSV颜色过滤
设置蓝色车牌底色阈值范围,进行颜色过滤
蓝色车牌
H值范围:190 ~ 245
S值范围: 0.35 ~ 1
V值范围: 0.3 ~ 1
过滤后图像如下:
4、噪声处理
过滤后,一般要进行去噪处理,这里早点不明显,如果车牌周围有蓝色物体,噪点就非常明显了
这里使用平均去噪,一些孤立白点将被去除,效果如下:
5、边缘检测
去噪后,进行边缘检测,边缘检测的目的就是为了突出车牌信息的突变,因为车牌背景和字体颜色区分开了;
这样做的目的也是为了防止周围有和车牌底色相同颜色的物体干扰,尤其是车辆颜色,因为经过边缘检测后车体颜色没有那么多跳变干扰或者与车牌跳变规律不一样,这样就可以滤去车体颜色,去除干扰
6、确定车牌位置
通过水平投影和垂直投影确定车牌位置。
(这里投影方法有很大缺陷,在车牌周围除了车辆还有其他背景信息时尤为明显,这在下面的另一种方法中改善)
7、截取车牌图像
二、投影法定位缺陷示例
1、读取复杂背景的车牌图像
2、HSV滤波
HSV过滤后可以看到明显的干扰信息,车辆后面的栏杆,和车辆同样的颜色
3、均值去噪
去噪后,虽然大部分噪点都去除了,但是栏杆依然清晰
4、边缘检测
边缘检测后的图像,车牌区域的形状特征,给我们解决投影缺陷的一些启示
5,、 投影后错误的定位
6、车牌提取错误
后半块车身,比例也不符合车牌特征
由此看见,在复杂背景的车牌识别中,全局投影就无法抑制干扰,在下面黄色车牌示例中就更加明显;
投影法简单,但只是在只有整体车辆信息,没有复杂背景信息的时候才可以使用。
三、基于候选区域判断方法
这个方法放弃了投影,直接遍历整个图像信息,检查每个联通域的长度和宽度,当符合车牌的宽高比时,才选定为候选区域,由后面处理流程进行处理,来判断是否能够提取正常的字符。
前两个步骤还是同上面一样的。
1、HSV空间转换
2、均值去噪
3、水平膨胀
目的:尽可能的形成连通域
4、边缘检测
也可以不用边缘检测,这里主要考虑到 尽量减少 图像白点 遍历中的运算
5、候选区域筛选
候选区域筛选, 这是区别投影方法的主要部分,
从上图可以看出有几个候选区域,大概有4块, 而中间的车牌有明显矩形特征,符合一定的长宽比例, 其他三块区域不具备这样的特征,在筛选的过程中予以舍弃。
筛选的方法采用 深度优先遍历, 当遇到连通域时,记录他的长度和高度,并设定阈值,当符合长宽比时,才会选中为候选区域,否则予以舍弃,当然还可以添加别的算法,比如检测跳变,毕竟符合这一比率的不一定就是车牌区域。
下图中黄框就是筛选后的区域:
连通域筛选函数如下:
- int find_connected_region_location(struct BMP_img *img, unsigned char *src, int xthreashold, int ythreashold, float rateLow, float rateHigh)
- {
- int i,j;
- int x1, y1, x2, y2;
- int width;
- int height;
- unsigned char *temp;
-
- int head, rear;
- struct XY_Queue *queue;
- static int direction[4][2]={{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
-
- width = img->width;
- height = img->height;
-
- queue = (struct XY_Queue *)malloc(sizeof(struct XY_Queue) * width * height);
-
- temp = (unsigned char *)malloc(width * height * sizeof(unsigned char));
-
- if(temp == NULL)
- {
- printf("find_connected_region_location mem alloc fail\n");
- return -1;
- }
- memcpy(temp, src, width * height);
-
- head = rear = 0;
- img->region_num = 0;
-
- for(i = 0; i < height; i++)
- for(j = 0; j < width; j++)
- {
- if(temp[i * width + j] == 255)
- {
-
- queue[rear].x = j;
- queue[rear].y = i;
- rear ++;
- temp[i * width + j] = 0;
-
- img->pre_region[img->region_num].x1 = j;
- img->pre_region[img->region_num].x2 = j;
- img->pre_region[img->region_num].y1 = i;
- img->pre_region[img->region_num].y2 = i;
-
- if(img->region_num > CAN_REGION_NUM)
- {
- printf("over the CAN_REGION_NUM\n");
- return -1;
- }
-
- while(head < rear)
- {
- x1 = queue[head].x;
- y1 = queue[head].y;
- head ++;
-
- if(x1 < img->pre_region[img->region_num].x1)
- img->pre_region[img->region_num].x1 = x1;
- else if(x1 > img->pre_region[img->region_num].x2)
- img->pre_region[img->region_num].x2 = x1;
- if(y1 < img->pre_region[img->region_num].y1)
- img->pre_region[img->region_num].y1 = y1;
- else if(y1 > img->pre_region[img->region_num].y2)
- img->pre_region[img->region_num].y2 = y1;
-
-
- for(i = 0; i < 4; i++)
- {
- x2 = x1 + direction[i][0];
- y2 = y1 + direction[i][1];
-
- if(x2 > 0 && x2 < width && y2 > 0 && y2 < height && temp[y2 * width + x2])
- {
- temp[y2 * width + x2] = 0;
- queue[rear].x = x2;
- queue[rear].y = y2;
- rear ++;
- }
-
- }
- }
- if((img->pre_region[img->region_num].x2 - img->pre_region[img->region_num].x1 > xthreashold) && (img->pre_region[img->region_num].y2 - img->pre_region[img->region_num].y1 > ythreashold))
- {
-
- img->pre_region[img->region_num].width = img->pre_region[img->region_num].x2 - img->pre_region[img->region_num].x1 + 1;
- img->pre_region[img->region_num].height = img->pre_region[img->region_num].y2 - img->pre_region[img->region_num].y1 + 1;
- img->pre_region[img->region_num].rate = (float)img->pre_region[img->region_num].width/img->pre_region[img->region_num].height;
- if((img->pre_region[img->region_num].width < img->width / 2) && (img->pre_region[img->region_num].height < img->height / 2))
- if((img->pre_region[img->region_num].rate > rateLow) && (img->pre_region[img->region_num].rate < rateHigh))
- {
- if(img->pre_region[img->region_num].x2 + PRE_LOCATION_BIAS > img->width)
- img->pre_region[img->region_num].x2 = img->width;
- else
- img->pre_region[img->region_num].x2 += PRE_LOCATION_BIAS;
- if(img->pre_region[img->region_num].x1 - PRE_LOCATION_BIAS < 0)
- img->pre_region[img->region_num].x1 = 0;
- else
- img->pre_region[img->region_num].x1 -= PRE_LOCATION_BIAS;
- if(img->pre_region[img->region_num].y2 + PRE_LOCATION_BIAS > img->height)
- img->pre_region[img->region_num].y2 = img->height;
- else
- img->pre_region[img->region_num].y2 += PRE_LOCATION_BIAS;
- if(img->pre_region[img->region_num].y1 - PRE_LOCATION_BIAS < 0)
- img->pre_region[img->region_num].y1 = 0;
- else
- img->pre_region[img->region_num].y1 -= PRE_LOCATION_BIAS;
-
- img->pre_region[img->region_num].width = img->pre_region[img->region_num].x2 - img->pre_region[img->region_num].x1 + 1;
- img->pre_region[img->region_num].height = img->pre_region[img->region_num].y2 - img->pre_region[img->region_num].y1 + 1;
-
- img->region_num++;
- }
- }
- }
-
-
- }
- free(temp);
- temp = NULL;
- return 0;
- }
6、截取车牌区域图像
四、黄色车牌检测
1、车辆图像信息
2、HSV过滤分割
由于车牌颜色 与 车辆颜色一直,出现大量噪声信息,全局投影已不可能分割出车牌信息了,
这里只是 利用候选区域长宽比来进行矩形分割,肯定会出现一些符合比例但是不是车牌的区域,必须在后面的处理中加以区分或者添加判断跳变规律的函数
3、去噪
4、 膨胀
6、边缘检测
7、候选区域 连通域筛选
从图中可以看到黄框 部分即是符合候选区域的地方
8、截取候选区域
此候选区域只是符合长宽比,需要另行处理 除去不是车牌的区域
五、小结
车牌定位比较复杂,但对于车牌识别来说,最为重要,我认为它是影响车牌识别最大因素。虽然复杂,但是方法多种多样。
这里仅此个人爱好和研究,希望各位朋友继续提出批评和建议,大家的鼓励给了我坚持下去的勇气。