• 正文
    • 1、前言
    • 2、步骤说明
    • 3、区域识别
    • 4、字符分割
    • 5、字符识别
  • 相关推荐
申请入驻 产业图谱

基于STM32F4的车牌识别(1)——车牌区域识别和字符分割

04/01 11:15
1471
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

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、字符识别

字符识别这两天做,大体应该是采用模板识别的策略,利用分割出来的字符和模板的匹配程度实现字符的识别。

这几天有空在钻研一下。

点赞
收藏
评论
分享
加入交流群
举报

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录