車牌定位是車牌識別中第一步,也是最重要的一步。
由於中國車牌種類多樣,顏色不一, 再加上車牌經常有污損,以及車牌周圍干擾因素太多,都成爲了車牌定位的難點。
這裏首先使用最簡單算法來描述車牌定位,以及他的缺陷和改進。
一、投影法
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\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過濾分割
由於車牌顏色 與 車輛顏色一直,出現大量噪聲信息,全局投影已不可能分割出車牌信息了,
這裏只是 利用候選區域長寬比來進行矩形分割,肯定會出現一些符合比例但是不是車牌的區域,必須在後面的處理中加以區分或者添加判斷跳變規律的函數
4、 膨脹
6、邊緣檢測
7、候選區域 連通域篩選
從圖中可以看到黃框 部分即是符合候選區域的地方
8、截取候選區域
此候選區域只是符合長寬比,需要另行處理 除去不是車牌的區域
五、小結
車牌定位比較複雜,但對於車牌識別來說,最爲重要,我認爲它是影響車牌識別最大因素。雖然複雜,但是方法多種多樣。
這裏僅此個人愛好和研究,希望各位朋友繼續提出批評和建議,大家的鼓勵給了我堅持下去的勇氣。
更希望能夠一起去討論:
QQ羣:105060236105060236