毕业设计 您所在的位置:网站首页 车牌识别能识别照片吗 毕业设计

毕业设计

2024-07-06 10:30| 来源: 网络整理| 查看: 265

书接上文:https://blog.csdn.net/m0_59113542/article/details/123592010?spm=1001.2014.3001.5502

本篇文章主要介绍如何对采集到的图像处理,进行车牌号的识别。

本车牌识别系统包括以下:图像采集→二值化灰度处理→识别车牌区域→字符分割→字符识别。

一、效果展示:

有图有真相

图片比任何语言都有说服力,话不多说直接上图。

 二、视频演示:

基于stm32的车牌识别系统

三、原理讲解:

图像采集已在上一篇文章讲过,有需要的可以取去看一下。

1、二值化:

        二值二值,字面意思就是转变成两个数值,就是将整个图像的每个像素都转变成0(黑)或255(白)这两个值,非黑即白,这样处理起来就很方便了。那么问题又来了,0~255之间应该如何转换?是随便转的吗?  

当然不是,简单的办法就是取一个合适的阈值,比如说127吧,小于127的转为0,大于127的转为1,因此找合适阈值就变得尤为重要,可以取其中值,也可以取各个像素的平均值。

2、识别车牌区域:

        如何在一张图片中精准的定位车牌所在的位置,这个就很有意思了!!!

上边界和下边界:在上一步二值化处理时,已经将整张图片转换成0(黑色)或者255(白色),车牌是蓝底白字,二值化后蓝色的低会变成黑色,上面的字依旧是白色,一行一行的看,  这一行中灰度值会多次发生跳变,也就是会有很多从0变成255,我们恰恰利用这一点,也就是统计跳变点的个数,当跳变点个数超过一定的阈值时,就认为该行是边界。

空说太无力了,还是举个例子:

 绿线代表一行,沿着这一行看,当到达白字时,其灰度数值会发生跳变从 0(黑色)跳变成255(白色),记为一个跳变点,这一行的跳变点还有很多,我们就根据跳变点的个数来判断上下边界。

左右边界:介于我们上下边界已经找好,所以我们可以在这两边界之间的区域找左右边界,从而缩小工作量,我们依旧可以按跳变点的算法来做,也可以用另一种方法,RGB转变成HSV,根据其色调、饱和度、明度来判断。

 也就是这样。

3、字符分割

        区域也定好,我们想要识别字母,首先得先提取出来啊,一一识别,因此就需要字符分割了。如何分割呢。

先上个图便于大家理解。

如图,红线代表着我们上方确定好的边界,我们可以看到两个字母之间二值化处理后全是黑色,唉~我们就可以根据这一特性看,竖着看如果某一列全为黑色也就是0,并且旁边也全都是黑色,就可以判断为空隙,这样就能截取到了各个字母,用蓝线表示字母的边界。

4、字符识别

经过前面的努力,我们已经提取到了各个字符,下面就进行识别呗。

为了增加识别的准确度呢,我们将提取到的字符进行放大,先横向放大然后纵向放大,以提高准确度。下图:

 怎么识别呢,我们通过取模软件,取到多个多个字模,比如说,粤、苏、辽、A~F、1~9,将其存放在数组中,然后让我们提取到的字符的数组去和他们一一比较。

比如说我们现在提取到“苏”,让它的数组去比较,肯定会找到一个标准数组和提取数组一样,但是呢,就像世界上没有两片相同的叶子一样,他们或多或少会有出处,因此我们设定当他们的相似度超过一定的阈值时,就认为二者相同。

最下方就是我们识别到的车牌号:

 当然啦,用f103系列的单片机去做机器视觉,多少有点为难它了,毕竟它不是专门做这个的。所以有时会出现一些个别字符识别错误,尤其是“E”与“F”,这是正常现象。

四、程序实现: algorithm.c:上面这些操作的各个算法 #include "algorithm.h" #include "delay.h" #include "lcd.h" #include "ShowChar.h" #include "string.h" #include void ChangePoint_Show_240(void) { //240方向跳变点显示 vu16 a = 0,b = 0; for(a = 0; a < 240; a++) { //建立参考线10、20、30(左侧纵向三条绿色线) LCD_DrawPoint(10,a,0x635)&0x3f)*255/63;b=(num&0x1f)*255/31; max = r; min = r; if(g >= max)max = g; if(b >= max)max = b; if(g 15 && 100 > V && V > 45) { // 和 蓝色HSV 阈值比较 if(b < min_320) { //获得横轴的Min和Max值,即蓝色车牌的左右边界 min_320 = b; //得到左边界 } if(b > max_320) { max_320 = b; //得到右边界 } } } } Min_blue = min_320; //获取各行的最大值//修正一点 Max_blue = max_320 - 5; //获取各行的最小值//修正一点 for(a = Min_ChangePoint_240; a < Max_ChangePoint_240; a++) { //显示左界限 蓝线 LCD_DrawPoint(Min_blue, a, 0xf8); //LCD_DrawPoint(Min_blue,a,0xf800); } for(a = Min_ChangePoint_240; a < Max_ChangePoint_240; a++) { //显示右界限 红线 LCD_DrawPoint(Max_blue, a, 0xf800); } } void ChangePoint_Analysis_320(void) { //蓝色区域中,320跳变点分析,获得TableChangePoint_320[b]结果 //(先二值化,再判断白点个数,=0则是分割线) vu16 a = 0, b = 0,num_color = 0; vu8 R1 = 0, G1 = 0, B1 = 0; vu8 Mid_ChangePoint_240 = 0; vu8 max_R = 0, max_G = 0, max_B = 0, min_R = 0, min_G = 0, min_B = 0; vu8 mid_R = 0, mid_G = 0, mid_B = 0; max_R = 0; max_G = 0; max_B = 0; min_R = 30; min_G = 60; min_B = 30; Mid_ChangePoint_240 = (Min_ChangePoint_240 + Max_ChangePoint_240) / 2; //中间位置 for(b = Min_blue; b < Max_blue; b++) { //左右边界 num_color = LCD_ReadPoint(b, Mid_ChangePoint_240); //(取车牌中间那一行的颜色为参考量)读取像素,代码优化速度有待提升 ?扫描方法也可优化,以提升速度 R1 = num_color >> 11; G1 = (num_color >> 5) & 0x3F; B1 = num_color & 0x1F; if((R1 > 10) && (G1 > 25) && (B1 > 15) && (R1 B1) min_B = B1; } } mid_R = (max_R + min_R) / 2; //获得分割阈值 mid_G = (max_G + min_G) / 2; mid_B = (max_B + min_B) / 2; for(b = 0; b < 320; b++) { //各行跳变点计数,数组清零 TableChangePoint_320[b] = 0; } for(a = Min_ChangePoint_240;a < Max_ChangePoint_240; a++) { for(b = Min_blue + 1; b < Max_blue; b++) { num_color = LCD_ReadPoint(b, a); //读取像素,代码优化速度有待提升 ?扫描方法也可优化,以提升速度 R1 = num_color >> 11; G1 = (num_color >> 5) & 0x3F; B1 = num_color & 0x1F; if((R1 >= mid_R) && (G1 >= mid_G) && (B1 >= mid_B)) //二值化,高阈值:25.55.25,较合适阈值(21,47,21) { LCD_DrawPoint(b, a, 0xffff); //白色 TableChangePoint_320[b]++; //白色,跳变点+1(该列跳变点个数==该列白色像素点个数) } else { LCD_DrawPoint(b, a, 0x0000); //黑色 } } } } void ChangePoint_Show_320(void) //320方向跳变点显示 { vu16 a = 0,b = 0; for(a = 0;a < 320; a++) //显示对应的横向跳变点 { if(TableChangePoint_320[a] == 0) { LCD_DrawPoint(a, 0, 0x001F); //跳变点显示,红色标记 } else { LCD_DrawPoint(a, TableChangePoint_320[a], 0xf800); //跳变点显示,红色标记 } } } //字符分割,返回分割的字符个数,用于判断合法性 vu8 SegmentationChar(void) { vu16 a = 0, b = 0; vu8 i = 0; //统计分割的字符个数,不为9说明分割有误 for(b = Max_blue; b > Min_blue; b--) // 左右界线的扫描 { if(TableChangePoint_320[b] == 0) //间隙分割 根据HSV比较 跳变点为0 代表空隙 { for(a = Min_ChangePoint_240; a < Max_ChangePoint_240 ; a++) //画线--调试用 车牌高度一样的线 { LCD_DrawPoint(b,a+1,0x001f); //仔细观察b的数值,在这里只画空隙的右边界线 } i++; //右边界线个数 b--; //下一列有跳变点才是分割界 while(TableChangePoint_320[b] == 0) //画过线后,找到跳变点不为0的地方 { b--; if(b 0) b--; character_boundary_left_3 = b; if((character_boundary_right_3 - character_boundary_left_3) < 4) { //省略低于3个像素的位置 while(TableChangePoint_320[b] == 0) b--; // 重新找第3个字符左右边界 character_boundary_right_3 = b+1; while(TableChangePoint_320[b] > 0) b--; character_boundary_left_3 = b; } while(TableChangePoint_320[b] == 0) b--; //取第4个字符 character_boundary_right_4 = b + 1; while(TableChangePoint_320[b] > 0) b--; character_boundary_left_4 = b; if((character_boundary_right_4 - character_boundary_left_4) < 4) { //省略低于3个像素的位置 while(TableChangePoint_320[b] == 0) b--; // 重新找第4个字符左右边界 character_boundary_right_4 = b + 1; while(TableChangePoint_320[b] > 0) b--; character_boundary_left_4 = b; } while(TableChangePoint_320[b] == 0) b--; //取第5个字符 character_boundary_right_5 = b + 1; while(TableChangePoint_320[b] > 0) b--; character_boundary_left_5 = b; if((character_boundary_right_5 - character_boundary_left_5) < 4) { //省略低于3个像素的位置 while(TableChangePoint_320[b] == 0) b--; // 重新找第5个字符左右边界 character_boundary_right_5 = b + 1; while(TableChangePoint_320[b] > 0) b--; character_boundary_left_5 = b; } while(TableChangePoint_320[b] == 0) b--; //取第6个字符 character_boundary_right_6 = b + 1; while(TableChangePoint_320[b]>0) b--; character_boundary_left_6 = b; while(TableChangePoint_320[b] == 0) b--; //取第7个字符 character_boundary_right_7 = b+1;//+1 while(TableChangePoint_320[b] > 0) b--; character_boundary_left_7 = b; if((character_boundary_right_7 - character_boundary_left_7) < 4) { //省略低于3个像素的位置 while(TableChangePoint_320[b] == 0) b--; // 重新找第7个字符左右边界 character_boundary_right_7 = b + 1; while(TableChangePoint_320[b] > 0) b--; character_boundary_left_7 = b; } while(TableChangePoint_320[b] == 0) b--; //取第8个字符 character_boundary_right_8 = b + 1; while(TableChangePoint_320[b] > 0) { if(b 数组 Result = MoShiShiBie_All(0, 36); //字符匹配,模式识别,返回a, 0数组 Result = MoShiShiBie_All(0, 36); //字符匹配,模式识别 if(Result < 10) { LCD_ShowNum(220,220,table_char[Result], 1); } else { LCD_ShowChar(220, 220, table_char[Result], 0); } table_cardMeasure[4] = Result; //保存识别的车牌结果 //第4个字符 Normalized(character_boundary_right_4, character_boundary_left_4); //归一化 25*50 PictureToString(); //图片->数组 Result = MoShiShiBie_All(0, 36); //字符匹配,模式识别 if(Result < 10) { LCD_ShowNum(210,220,table_char[Result], 1); } else { LCD_ShowChar(210,220,table_char[Result], 0); } table_cardMeasure[3] = Result; //保存识别的车牌结果 //第5个字符 Normalized(character_boundary_right_5, character_boundary_left_5); //归一化 25*50 PictureToString(); //图片->数组 Result = MoShiShiBie_All(0, 36); //字符匹配,模式识别 if(Result < 10) { LCD_ShowNum(200,220,table_char[Result],1); } else { LCD_ShowChar(200, 220, table_char[Result], 0); } table_cardMeasure[2] = Result; //保存识别的车牌结果 //“。” LCD_ShowChar(190, 220, '.', 0); //第6个字符 Normalized(character_boundary_right_7, character_boundary_left_7); //归一化 25*50 PictureToString(); //图片->数组 Result = MoShiShiBie_All(10, 36); //字符匹配,模式识别,只匹配字母 if(Result < 10) { LCD_ShowNum(180, 220, table_char[Result], 1); } else { LCD_ShowChar(180, 220, table_char[Result], 0); } table_cardMeasure[1] = Result; //保存识别的车牌结果 //汉字 Normalized(character_boundary_right_8, character_boundary_left_8); //归一化 25*50 最后一个汉字 PictureToString(); //图片->数组 Result = MoShiShiBie_All(36, 42); //字符匹配,匹配汉字 WordShow(Result - 35, 160, 220); //显示汉字 table_cardMeasure[0] = Result - 35; //保存识别的车牌结果 //识别结束 //串口发送车牌信息 if(Result == 36) { sprintf((char*)temp, "识别结果:渝"); } else if(Result == 37) { sprintf((char*)temp, "识别结果:闽"); } else if(Result == 38) { sprintf((char*)temp, "识别结果:沪"); } else if(Result == 39) { sprintf((char*)temp, "识别结果:浙"); } else if(Result == 40) { sprintf((char*)temp, "识别结果:苏"); } else if(Result == 41) { sprintf((char*)temp, "识别结果:粤"); } sprintf((char*)temp1, "%c.%c%c%c%c%c\r\n" , table_char_char[table_cardMeasure[1]], table_char_char[table_cardMeasure[2]], table_char_char[table_cardMeasure[3]], table_char_char[table_cardMeasure[4]], table_char_char[table_cardMeasure[5]], table_char_char[table_cardMeasure[6]]); //时间价格界面 //先匹配已保存的车牌号 for(u = 0; u < 5; u++) { for(i = 0; i < 7; i++) { if(table_card[u][i] != table_cardMeasure[i]) i = 8; //退出第一个车牌信息for循环 } if(i == 7) //匹配成功 { LCD_Fill(0x00); //黑屏 Show_Title(); //显示标题 Show_Card(u); //显示第几组车牌 u = 5; while(GPIO_ReadInputDataBit(KEY1_PORT, KEY2_PIN) == 1); //等待k2按下 } } if(i == 9) //无匹配车牌,则保存车牌 { i = 0; while(1) { if(GPIO_ReadInputDataBit(KEY1_PORT,KEY2_PIN) == 0) break; LCD_ShowNum(30,220, i / 100, 2); if(i == 300) { //保存数据 for(u = 0; u < 5; u++) { if(table_card[u][0] == 0) { for(i = 0;i < 7; i++) { table_card[u][i] = table_cardMeasure[i]; } u = 5; //退出循环 } } LCD_Fill(0x00); //黑屏 Show_Title(); //显示标题 Show_Card(0); //显示第几组车牌 Show_Card(1); Show_Card(2); Show_Card(3); Show_Card(4); while(GPIO_ReadInputDataBit(KEY1_PORT,KEY2_PIN)==1); //等待K2按下 break; } delay_ms(1); i++; } } } algorithm.h #ifndef __ALGORITHM_H #define __ALGORITHM_H #include "stm32f10x.h" #include "value.h" void ChangePoint_Show_240(void); void ChangePoint_Analysis_240(void); void ChangePoint_Analysis_Blue(void); void ChangePoint_Analysis_320(void); void ChangePoint_Show_320(void); vu8 SegmentationChar(void); void CharacterRecognition(void); #endif  main.c #include "stm32f10x.h" #include "stm32f10x_it.h" #include "key.h" #include "usart.h" #include "delay.h" #include "lcd.h" #include "ov7670.h" #include "rcc.h" #include "ShowChar.h" #include "discern.h" extern vu8 Red_Vlaue, Green_Value ,Blue_Value;//阈值 int main(void) { unsigned int scan_time = 0; STM32_Clock_Init(16); //初始化时钟 LCD_Init(); Key_Init(); //初始化 KEY1 PA8 OV7670_GPIO_Init(); //OV7670引脚初始化,放在串口初始化前面 //USART1_init(); //初始化串口 TIM3_Configuration(); //10Khz的计数频率,计数到5000为500ms LCD_Fill(0x6666); //初始化刷绿色屏 while(!Sensor_Init()); //摄像头芯片初始化 LCD_Fill(0xF800); //成功后刷红色屏 delayms(100); scan_time = 2; //二值化阈值 Red_Vlaue = 24; Green_Value = 53; Blue_Value = 24; while(1) { if(scan_time 1) { CameraScan(); //摄像头扫描测试 LCD_ShowNum(30,220,21 - scan_time, 2); while(GPIO_ReadInputDataBit(KEY1_PORT,KEY1_PIN)==0) //K1按键按下 跳出停留时间与收费情况 { LCD_Fill(0x00); //黑屏 Show_Title(); //显示标题 Show_Card(0); //显示第几组车牌 Show_Card(1); Show_Card(2); Show_Card(3); Show_Card(4); delay_ms(5000); } } if(scan_time == 20) { scan_time = 0; } scan_time++; } }

大家如果想省事的话也可直接下载全部工程资料。

完整工程链接:https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-24450682672.4.6c772c42AqQnna&id=677048652126

  欢迎大家指正交流,有空可以一起讨论代码啊。

制作不易,感谢大家支持,感谢!!!!!!

  --------------一个正在努力的人  

感觉未分享清楚、有疑惑 咨询问题、了解具体内容、需要帮助者 可私信联系


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有