车牌定位是车牌识别中第一步,也是最重要的一步。
由于中国车牌种类多样,颜色不一, 再加上车牌常常有污损,以及车牌周围干扰因素太多,都成为了车牌定位的难点。
这里首先使用最简单算法来描写叙述车牌定位,以及他的缺陷和改进。
一、投影法
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空间转换
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 queue_count;
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
");
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
");
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过滤切割
由于车牌颜色 与 车辆颜色一直,出现大量噪声信息,全局投影已不可能切割出车牌信息了,
这里仅仅是 利用候选区域长宽比来进行矩形切割,肯定会出现一些符合比例可是不是车牌的区域,必须在后面的处理中加以区分或者加入推断跳变规律的函数
4、 膨胀
6、边缘检測
7、候选区域 连通域筛选
从图中能够看到黄框 部分即是符合候选区域的地方
8、截取候选区域
此候选区域仅仅是符合长宽比。须要另行处理 除去不是车牌的区域
五、小结
车牌定位比較复杂。但对于车牌识别来说。最为重要。我觉得它是影响车牌识别最大因素。
尽管复杂,可是方法多种多样。
这里仅此个人爱好和研究,希望各位朋友继续提出批评和建议。大家的鼓舞给了我坚持下去的勇气。
更希望能够一起去讨论:
QQ群:105060236105060236