1、前言
曾经写过一篇文章,介绍如何利用ESP32获取车牌数据上传至百度云平台识别车牌。不过这种方式既需要无线传输,还需要额外对车牌识别进行缴费。
本期我们探索如何利用STM32进行车牌识别的本地化部署。这里叠个甲,作者做这块纯属好奇,不涉及真正的应。
本期硬件采用STM32F4,TFTLCD以及OV2640显示屏,本来是想用H7的,但是由于不可抗力因素换为使用F4,摄像头之所以用OV2640,主要是比较便宜,其他型号手上暂时也没有。
2、步骤说明
如何实现STM32和摄像头(DCMIPP)的通讯这里不过多赘述,我们主要介绍一下实现车牌识别的步骤,这里我总结为三个部分:
1.车牌区域识别
2.字符分割
3.字符识别(尚未写完)
接下来我将逐一介绍其实现方式,以下是区域识别和字符分割的实现。
目前字符识别还没做完,所有的流程均有不同时间的延时以便展示。
3、区域识别
首先就是要如何识别出车牌的有效区域,我看许多人的做法是进行灰度化之后再进行二值化,再去检测每行中跳变点的个数之类的。
不过我觉得跳变点个数对照片的效果要求太理想了,而且单纯的灰度化的话引入的噪声又比较大。
因此我尝试着使用蓝色阈值+蓝色与红色差异值的方式来进行二值化,总而言之就是尽可能的提取出蓝色特征。
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; // 提取蓝色分量// 根据蓝色和红色、绿色的差异进行二值化处理if (B > (R + G) * 0.8) { // 如果蓝色比红色和绿色的总和大一定比例color = 0xffff; // 白色} else {color = 0x0000; // 黑色}// 更新图像缓冲区rgb_buf[x][y] = color;LCD->LCD_RAM = rgb_buf[x][y];LCD_SetCursor(X_Offset + y, Y_Offset + x);LCD_WriteRAM_Prepare();}}
将RGB三色提取出来后,比较蓝色和红绿值来进行二值化,这样子可以很好的提取出蓝色车牌我们需要的部分。
1
2
不过这个做法也有明显的缺点:就是只能识别蓝色的车牌~~
可以看到二值化后仍然存留着一些噪声,这里我们可以通过滤波降噪。
void MedianFilter(u16 (*input)[RGB_Y], u16 (*output)[RGB_Y]){u16 x, y;u16 median[9]; // 用于存储3x3邻域的像素值u16 temp;// 处理内部像素(非边界像素)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];// 对邻域像素值进行排序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;}}}// 取中值作为当前像素的值output[x][y] = median[4];}}}
采用3x3的中值滤波算法降噪,可以有效的降低噪声。
为了找到车牌区域,我们可以扫描每行白点最多的一行作为基准,自上,自下分别寻找这个最大值80%的行为哪一行,即确定车牌的上下行。
之后在此基础上,对列也进行这种操作,从右至左和从左至右找到车牌的左右区间。
4、字符分割
得到了车牌区域我们就可以考虑如何分割字符了,这里我采用的策略是从右到左去测量空白间隙。
即利用各字符之间的空隙,这里正好也是因为这个川字,所以考虑从右到左,因为这样子可以避免去处理川字中间的几个空隙。
/*寻找分割线*/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)//最后一个字符{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);}}
这样子就可以实现分割字符的目的了。
5、字符识别
字符识别这两天做,大体应该是采用模板识别的策略,利用分割出来的字符和模板的匹配程度实现字符的识别。
这几天有空在钻研一下。
2645