基于單片機stm32的車牌識別系統(tǒng) -物聯(lián)網(wǎng) 嵌入式 單片機(含代碼)
- 難度系數(shù):4分
- 工作量:4分
- 創(chuàng)新點:3分
1 簡介
本系統(tǒng)利用STM32單片機驅(qū)動攝像頭采集圖像,通過模式識別,匹配車牌的識別結(jié)果,并在屏幕上顯示。
2 主要器件
- STM32F103RCT6 主控芯片
- TFT液晶屏
- OV7670攝像頭
- 蜂鳴器
- LED電路
3 實現(xiàn)效果
識別效果
攝像頭模塊
4 設(shè)計原理
4.1軟件總體過程:
(1)OV7670帶FIFO攝像頭圖像采集,采用GPIO模擬攝像頭時序,通過讀取FIFO輸出值將圖像直接顯示在LCD屏上
(2)LCD屏相當于一個圖像緩存,同時也做顯示,通過讀取LCD屏上的像素值進行圖像處理;
(3)車牌定位處理,車牌定位常用二值化分割,腐蝕膨脹處理,連通域計算等操作,顯然這些算法在stm32f1上實現(xiàn)是很困難的,且處理速度太慢,因此,采用RGB轉(zhuǎn)HSV顏色空間變換和閾值選擇進行車牌定位,然后將車牌定位區(qū)域進行二值化處理,不是藍色車牌的部分就是字符區(qū)域;
(4)車牌字符分割處理,字符分割先采用行統(tǒng)計加列統(tǒng)計的方式,確定每行和每列的有效像素和,進一步確定字符區(qū)域;然后進行橫向統(tǒng)計分割,通過每一列的像素和閾值判斷字符的分界線和個數(shù);
(5)車牌歸一化處理,歸一化處理先將每個字符提取出來,然后按照像素值進行橫向和縱向壓縮,最終處理成模板一樣大小的字符;并在液晶屏上保存字符的數(shù)據(jù);
(6)模板匹配,將歸一化之后的字符,與模板中的字符通過像素值一一比較,確定相似度最高的字符就是目標值;
4.2 具體解釋
圖像采集
通過OV7670攝像頭進行圖像采集,采集的圖像大小為320*240像素,像素格式為RGB565。每個像素由兩字節(jié)組成,第一字節(jié)的高五位是Red,第一字節(jié)的低三位和第二字節(jié)的高三位組成Green,第二字節(jié)的低五位是Blue。
二值化
二值化就是讓圖像的像素點矩陣中的每個像素點的灰度值為0(黑色)或者255(白色),讓整個圖片呈現(xiàn)出只有黑色和白色的效果。二值化后的圖像中灰度值范圍是0或者255。這時需要設(shè)定一個閾值來對像素點進行設(shè)置。
常用二值化方法:
- 取中值:設(shè)置閾值為127,灰度值小于127的為0,大于127的為255。這樣設(shè)置計算量小,計算快。缺點也嚴重:在不同的圖像中,顏色分布差別大,處理效果也不會很好。程序開始之前設(shè)置R,G,B的閾值,通過閾值判斷將像素設(shè)置為全黑(0x0000)或者全白(0xFFFF).同時根據(jù)色彩的變化記錄每一行的顏色跳變點,由此識別出車牌區(qū)域。
- 取平均值:像素點平均值 = (像素點1灰度值 + 像素點2灰度值 + …… + 像素點n灰度值) / n
- 雙峰法:此方法適用于具有明顯雙峰直方圖的圖像,不適合直方圖中雙峰差別很大或雙峰間的谷比較寬廣而平坦的圖像。該方法認為圖像由前景和背景組成,在灰度直方圖上,前景和背景會形成高峰,在雙峰之間的最低谷處就是閾值。
識別車牌區(qū)域
根據(jù)上一步的二值化,由于車牌區(qū)域跳變點多,由此可以得出車牌區(qū)域。分別記錄車牌區(qū)域的上下高度。然后通過RGB-HSV顏色轉(zhuǎn)換,識別出車牌區(qū)域的左右邊界。
字符分割
我國常見車牌以及排列順序大部分都是按照如下設(shè)計的:漢字、英文字母、點、英文字母、阿拉伯數(shù)字、阿拉伯數(shù)字、阿拉伯數(shù)字、阿拉伯數(shù)字。基于這個規(guī)律,以及圖像采集高度一致,設(shè)計了如下的分割方法:
- 在內(nèi)存中開辟七個長為車牌長的七分之一和寬為車牌寬的區(qū)域
- 從車牌圖像長邊的巾問向下開始掃描車牌圖像,并把掃描到的所有的點灰度值復制到0區(qū)域的第四個區(qū)域?qū)?yīng)位置上。然后再從上向下掃描剛掃描過這一努的左邊或右邊,直到所掃描的這一峰上的所有點的灰度都是0時為止,并把這一豎認為是字符的分離處。
- 切割第五到第七個字符。方法就是,切割完了第四個字符之后,再依次掃描剩下的空間,直到所掃描的這一豎上的所有點的灰度值不全為0時,認為是字符的開始并依次掃描直到所掃描的這一豎上的所有點的灰度值全為0時認為是字符的結(jié)束。
- 切割第三到第四個字符。這兩個字符的切割方式與第五到第七個字符一樣。
- 切割第一到第二個字符。當?shù)谌齻€字符切割完之后,我們將遇到一個點,我們也把它看作一個字符,只不過這個點掃描之后就不要了。掃描完這個點之后,我們來切割第二個字符,它的切割方式與前面一樣。切割完了第二個字符之后,再向左掃描,直到所掃描的這一豎上的所有點的灰度值不全為0時,認為是字符的開始,并依次掃描直到所掃描所有剩下的,并填到相應(yīng)的位置,直到剩下的空間填滿。經(jīng)過粗分割后,可以得到一些單個字符區(qū)域和多余的空間。下一步我們將把這些多余的空間去掉。這將更有利于下一步字符的識別。
- 去除圖像上多余空間:車牌上的字符經(jīng)過了粗切割所得到的是一些單的字符,但在分配空間時是按照車牌的寬和長的七分之一來分配的;所以這個空間可能大于字符應(yīng)該占的空問。所以,要將多余空間去除。對于第一個字符從第一行開始向下掃描,把那些一行中所有的點的灰度值全為0的點去掉,直到掃描到有一行不全為0時為止。然后再從第一列開始向右掃描把那些一列中所有的點的灰度值全為0的點去掉,直到掃描到有一列不全為0時為止。接下來從最后一行開始向上掃描,把那些一行中所有的點的灰度值全為0的點去掉,直到掃描到有一行不全為0時為止。最后從最后一列開始向左掃描把那些一列中所有的點的灰度值全為0的點去掉,直到掃描到有一列不全為0時為止。重復上面的步驟完成剩下字符的切割。
- 根據(jù)二值化的結(jié)果,以及記錄的跳變點位置,對字符進行分割,同時記錄字符的左右邊界。
字符匹配
對分割出來的字符進行歸一化處理,這里用到圖片的擴大算法,擴大之后逐一的去進行字符匹配。字符模板事前通過字模軟件轉(zhuǎn)換成二進制數(shù)據(jù)保存在數(shù)組中。最后根據(jù)匹配結(jié)果相似度最大的做為輸出結(jié)果。
歸一化圖像就是要把原來各不相同的字符統(tǒng)一到同一尺寸。因為掃描進來的圖像中字符大小存在較大的差異,而相對來說,統(tǒng)一尺寸的字符識別的標準性更強,準確率自然也更高。具體算法如下:先得到原來字符的高度和寬度,與系統(tǒng)已存字模的數(shù)據(jù)作比較,得出要變換的系數(shù),然后根據(jù)得到的系數(shù)按照插值的方法映射到原圖像中。
5 部分核心代碼
#define COLOR_RGB565_TO_R8(pixel) \ ({ \ __typeof__ (pixel) __pixel = (pixel); \ __pixel = (__pixel >> 8) & 0xF8; \ __pixel | (__pixel >> 5); \ }) #define COLOR_RGB565_TO_G8(pixel) \ ({ \ __typeof__ (pixel) __pixel = (pixel); \ __pixel = (__pixel >> 3) & 0xFC; \ __pixel | (__pixel >> 6); \ }) #define COLOR_RGB565_TO_B8(pixel) \ ({ \ __typeof__ (pixel) __pixel = (pixel); \ __pixel = (__pixel << 3) & 0xF8; \ __pixel | (__pixel >> 5); \ }) int8_t imlib_rgb565_to_l(uint16_t pixel) { float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)]; float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f); y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); return fast_floorf(116 * y) - 16; } int8_t imlib_rgb565_to_a(uint16_t pixel) { float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)]; float x = ((r_lin * 0.4124f) + (g_lin * 0.3576f) + (b_lin * 0.1805f)) * (1.0f / 095.047f); float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f); x = (x>0.008856f) ? fast_cbrtf(x) : ((x * 7.787037f) + 0.137931f); y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); return fast_floorf(500 * (x-y)); } int8_t imlib_rgb565_to_b(uint16_t pixel) { float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)]; float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f); float z = ((r_lin * 0.0193f) + (g_lin * 0.1192f) + (b_lin * 0.9505f)) * (1.0f / 108.883f); y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); z = (z>0.008856f) ? fast_cbrtf(z) : ((z * 7.787037f) + 0.137931f); return fast_floorf(200 * (y-z)); }
模板匹配算法
/**
* @function 歐幾里得距離計算,用于圖片相似度計算
* @param[in] src1和src2,必須是相同大小灰度圖片
* @param[out] 歐幾里得距離
* @retval ERROR -1 錯誤
* @par 2021年5月28日 zhengmf
*/ float Euclidean_Distance(unsigned char *Src1,unsigned char *Src2,int length,float Euclideandis) { if(Src1==NULL||Src2==NULL)
{ return -1;
} int sum=0; int i=0; for(i=0;i<length;i++)
{
sum+=(int)pow((*Src1-*Src2),2);
Src1++;
Src2++;
}
Euclideandis=(float)sqrt(sum); return Euclideandis;
} /**
* @function 余弦相似度計算,用于圖片相似度計算
* @param[in] src1和src2,必須是相同大小灰度圖片
* @param[out] 余弦相似度
* @retval 0 相似度小于0
* @retval ERROR -1 錯誤
* @retval CosineSimilar
* @par 2021年5月28日 zhengmf
*/ float Cosine_Similarity(unsigned char *Src1,unsigned char *Src2,int length,float CosineSimilar) { if(Src1==NULL||Src2==NULL)
{ return -1;
} int sum=0,sum1=0,sum2=0; float temp0=0,temp1=0; int i=0; for(i=0;i<length;i++)
{
sum+=(int)(*Src1)*(*Src2);
sum1+=(int)pow((*Src1),2);
sum2+=(int)pow((*Src2),2);
Src1++;
Src2++;
} if(sum<=0)
{ return 0;
}
temp0=(float)(sqrt(sum1));
temp1=(float)(sqrt(sum2));
CosineSimilar=(float)((sum/temp0)/temp1); return CosineSimilar;
} /**
* @function 皮爾遜相似度計算,用于圖片相似度計算
* @param[in] src1和src2,必須是相同大小灰度圖片
* @param[out] 皮爾遜相似度
* @retval ERROR -1 錯誤
* @par 2021年5月28日 zhengmf
*/ float Pearson_Correlation(unsigned char *Src1,unsigned char *Src2,int length,float PearsonSimilar) { if(Src1==NULL||Src2==NULL)
{ return -1;
} unsigned char aver1=0,aver2=0; int sum=0,sum1=0,sum2=0; float temp0=0,temp1=0; int i=0; for(i=0;i<length;i++)
{
sum1+=*Src1;
sum2+=*Src2;
Src1++;
Src2++;
}
aver1=(unsigned char)(sum1/length);
aver2=(unsigned char)(sum2/length);
sum1=0;
sum2=0; for(i=0;i<length;i++)
{
sum+=(int)(*Src1-aver1)*(*Src2-aver2);
sum1+=(int)pow((*Src1-aver1),2);
sum2+=(int)pow((*Src2-aver2),2);
Src1++;
Src2++;
} if(sum<=0)
{ return 0;
}
temp0=(float)(sqrt(sum1));
temp1=(float)(sqrt(sum2));
PearsonSimilar=(float)((sum/temp0)/temp1); return PearsonSimilar;
}
6 最后
嵌入式物聯(lián)網(wǎng)的學習之路非常漫長,不少人因為學習路線不對或者學習內(nèi)容不夠?qū)I(yè)而錯失高薪offer。不過別擔心,我為大家整理了一份150多G的學習資源,基本上涵蓋了嵌入式物聯(lián)網(wǎng)學習的所有內(nèi)容。點擊下方鏈接,0元領(lǐng)取學習資源,讓你的學習之路更加順暢!記得點贊、關(guān)注、收藏、轉(zhuǎn)發(fā)哦!
點擊這里找小助理0元領(lǐng)取:掃碼進群領(lǐng)資料
- 贊