1、前言
曾經(jīng)寫過(guò)一篇文章,介紹如何利用ESP32獲取車牌數(shù)據(jù)上傳至百度云平臺(tái)識(shí)別車牌。不過(guò)這種方式既需要無(wú)線傳輸,還需要額外對(duì)車牌識(shí)別進(jìn)行繳費(fèi)。
本期我們探索如何利用STM32進(jìn)行車牌識(shí)別的本地化部署。這里疊個(gè)甲,作者做這塊純屬好奇,不涉及真正的應(yīng)。
本期硬件采用STM32F4,TFTLCD以及OV2640顯示屏,本來(lái)是想用H7的,但是由于不可抗力因素?fù)Q為使用F4,攝像頭之所以用OV2640,主要是比較便宜,其他型號(hào)手上暫時(shí)也沒(méi)有。
2、步驟說(shuō)明
如何實(shí)現(xiàn)STM32和攝像頭(DCMIPP)的通訊這里不過(guò)多贅述,我們主要介紹一下實(shí)現(xiàn)車牌識(shí)別的步驟,這里我總結(jié)為三個(gè)部分:
1.車牌區(qū)域識(shí)別
2.字符分割
3.字符識(shí)別(尚未寫完)
接下來(lái)我將逐一介紹其實(shí)現(xiàn)方式,以下是區(qū)域識(shí)別和字符分割的實(shí)現(xiàn)。
目前字符識(shí)別還沒(méi)做完,所有的流程均有不同時(shí)間的延時(shí)以便展示。
3、區(qū)域識(shí)別
首先就是要如何識(shí)別出車牌的有效區(qū)域,我看許多人的做法是進(jìn)行灰度化之后再進(jìn)行二值化,再去檢測(cè)每行中跳變點(diǎn)的個(gè)數(shù)之類的。
不過(guò)我覺得跳變點(diǎn)個(gè)數(shù)對(duì)照片的效果要求太理想了,而且單純的灰度化的話引入的噪聲又比較大。
因此我嘗試著使用藍(lán)色閾值+藍(lán)色與紅色差異值的方式來(lái)進(jìn)行二值化,總而言之就是盡可能的提取出藍(lán)色特征。
for?(x =?0; x <?RGB_X; x++) {
? ? ? ??for?(y =?0; y <?RGB_Y; y++) {
? ? ? ? ? ? color = rgb_buf[x][y];
? ? ? ? ? ? R = (color >>?11) *?255?/?31; ?// 提取紅色分量
? ? ? ? ? ? G = ((color >>?5) &?0x3f) *?255?/?63; ?// 提取綠色分量
? ? ? ? ? ? B = (color &?0x1f) *?255?/?31; ?// 提取藍(lán)色分量
? ? ? ? ? ??// 根據(jù)藍(lán)色和紅色、綠色的差異進(jìn)行二值化處理
? ? ? ? ? ??if?(B > (R + G) *?0.8) { ?// 如果藍(lán)色比紅色和綠色的總和大一定比例
? ? ? ? ? ? ? ? color =?0xffff; ?// 白色
? ? ? ? ? ? }?else?{
? ? ? ? ? ? ? ? color =?0x0000; ?// 黑色
? ? ? ? ? ? }
? ? ? ? ? ??// 更新圖像緩沖區(qū)
? ? ? ? ? ? rgb_buf[x][y] = color;
? ? ? ? ? ??LCD->LCD_RAM?= rgb_buf[x][y];
? ? ? ? ? ??LCD_SetCursor(X_Offset + y, Y_Offset + x);
? ? ? ? ? ??LCD_WriteRAM_Prepare();
? ? ? ? }
? ? }
將RGB三色提取出來(lái)后,比較藍(lán)色和紅綠值來(lái)進(jìn)行二值化,這樣子可以很好的提取出藍(lán)色車牌我們需要的部分。
1
2
不過(guò)這個(gè)做法也有明顯的缺點(diǎn):就是只能識(shí)別藍(lán)色的車牌~~
可以看到二值化后仍然存留著一些噪聲,這里我們可以通過(guò)濾波降噪。
void?MedianFilter(u16 (*input)[RGB_Y], u16 (*output)[RGB_Y])
{
? ? u16 x, y;
? ? u16 median[9]; ?// 用于存儲(chǔ)3x3鄰域的像素值
? ? u16 temp;
? ??// 處理內(nèi)部像素(非邊界像素)
? ??for?(x =?1; x < RGB_X -?1; x++) {
? ? ? ??for?(y =?1; y < RGB_Y -?1; y++) {
? ? ? ? ? ??// 獲取3x3鄰域的像素值
? ? ? ? ? ? median[0] = input[x-1][y-1];
? ? ? ? ? ? median[1] = input[x-1][y];
? ? ? ? ? ? median[2] = input[x-1][y+1];
? ? ? ? ? ? median[3] = input[x][y-1];
? ? ? ? ? ? median[4] = input[x][y];
? ? ? ? ? ? median[5] = input[x][y+1];
? ? ? ? ? ? median[6] = input[x+1][y-1];
? ? ? ? ? ? median[7] = input[x+1][y];
? ? ? ? ? ? median[8] = input[x+1][y+1];
? ? ? ? ? ??// 對(duì)鄰域像素值進(jìn)行排序
? ? ? ? ? ??for?(u8?i?=?0; i <?9; i++) {
? ? ? ? ? ? ? ??for?(u8?j?=?i +?1; j <?9; j++) {
? ? ? ? ? ? ? ? ? ??if?(median[i] > median[j]) {
? ? ? ? ? ? ? ? ? ? ? ? temp = median[i];
? ? ? ? ? ? ? ? ? ? ? ? median[i] = median[j];
? ? ? ? ? ? ? ? ? ? ? ? median[j] = temp;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ??// 取中值作為當(dāng)前像素的值
? ? ? ? ? ? output[x][y] = median[4];
? ? ? ? }
? ? }
? ? }
采用3x3的中值濾波算法降噪,可以有效的降低噪聲。
為了找到車牌區(qū)域,我們可以掃描每行白點(diǎn)最多的一行作為基準(zhǔn),自上,自下分別尋找這個(gè)最大值80%的行為哪一行,即確定車牌的上下行。
之后在此基礎(chǔ)上,對(duì)列也進(jìn)行這種操作,從右至左和從左至右找到車牌的左右區(qū)間。
4、字符分割
得到了車牌區(qū)域我們就可以考慮如何分割字符了,這里我采用的策略是從右到左去測(cè)量空白間隙。
即利用各字符之間的空隙,這里正好也是因?yàn)檫@個(gè)川字,所以考慮從右到左,因?yàn)檫@樣子可以避免去處理川字中間的幾個(gè)空隙。
/*
? ? ??
? ? ? ? 尋找分割線
? ? ??
? ? ? ? */
? ? ? ? col_threshold = max_col_white *?80?/?100;
? ? ? ??for(int i = right_col;i>left_col;i--)
? ? ? ? {
? ? ? ? ??if(YuzhiFlag)//右邊緣
? ? ? ? ? {
? ? ? ? ? ??if(col_white_counts[i]<max_col_white *?70?/?100)//左邊緣
? ? ? ? ? ? {
? ? ? ? ? ? ? line[number++] = i;
? ? ? ? ? ? ??YuzhiFlag?= !YuzhiFlag;
? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? ??else
? ? ? ? ? {
? ? ? ? ? ??if(col_white_counts[i]>max_col_white *?90?/?100)//最后一個(gè)字符
? ? ? ? ? ? {
? ? ? ? ? ? ? line[number++] = i;
? ? ? ? ? ? ??YuzhiFlag?= !YuzhiFlag;
? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ??
? ? ? ? }
? ? ? ? int zifunumber =?0;
? ? ? ??POINT_COLOR?=?RED;
? ? ? ??for(int i =?0;i<number;i++)
? ? ? ? {
? ? ? ? ??
? ? ? ? ??
? ? ? ? ??if(((line[i]-line[i+1])>(right_col-left_col)*5/100)&&zifunumber<6)
? ? ? ? ? {
? ? ? ? ? ? box.zifu[zifunumber][0] = line[i];
? ? ? ? ? ? box.zifu[zifunumber][1] = line[i+1];
? ? ? ? ? ??
? ? ? ? ? ??LCD_DrawLine(box.zifu[zifunumber][0]+65,top_row+225,box.zifu[zifunumber][0]+65,bottom_row+225);
? ? ? ? ? ??LCD_DrawLine(box.zifu[zifunumber][1]+65,top_row+225,box.zifu[zifunumber][1]+65,bottom_row+225);
? ? ? ? ? ? zifunumber++;
? ? ? ? ? ? i++;
? ? ? ? ? ??delay_ms(1000);
? ? ? ? ? }
? ? ? ? ??else?if(number>=6)
? ? ? ? ? {
? ? ? ? ? ? box.zifu[zifunumber][0] = line[i];
? ? ? ? ? ? box.zifu[zifunumber][1] = left_col+3;
? ? ? ? ? ??
? ? ? ? ? ??LCD_DrawLine(box.zifu[zifunumber][0]+65,top_row+225,box.zifu[zifunumber][0]+65,bottom_row+225);
? ? ? ? ? ??LCD_DrawLine(box.zifu[zifunumber][1]+65,top_row+225,box.zifu[zifunumber][1]+65,bottom_row+225);
? ? ? ? ? ??break;
? ? ? ? ? ??delay_ms(1000);
? ? ? ? ? }
? ? ? ? }
這樣子就可以實(shí)現(xiàn)分割字符的目的了。
5、字符識(shí)別
字符識(shí)別這兩天做,大體應(yīng)該是采用模板識(shí)別的策略,利用分割出來(lái)的字符和模板的匹配程度實(shí)現(xiàn)字符的識(shí)別。
這幾天有空在鉆研一下。