文章目录

  • 1 简介
  • 2 项目课题背景
  • 3 应用场景
  • 3.1 小区和停车场方面的管理
  • 3.2 交通道路的监控
  • 3.3 收费站管理系统
  • 3.4 车流统计、 车牌验证和移动车载系统
  • 4 系统设计方案
  • 4.1 硬件方案
  • 4.2 软件实现流程
  • 5 硬件系统设计
  • 5.1 主控STM32
  • 5.2 摄像头
  • 5.3 显示屏
  • 6 软件设计
  • 6.1 车牌定位
  • 6.2 字符分割
  • 6.3 字符识别
  • 7 实物测试
  • 8 部分关键代码
  • 9 最后



1 简介

Hi,大家好,学长今天向大家介绍一个 单片机项目

基于stm32的车牌识别系统设计

大家可用于 课程设计 或 毕业设计

深度学习模型如何与stm32单片机 基于stm32单片机设计_车牌识别系统

2 项目课题背景

在我们的日常生活中, 接触到了很多关于电子科技的技术。 在电子科技交通领域中, 有很多技术都在无形中加入我们的生活, 如图像处理技术, 自动检测技术等。对于道路交通应用到的技术, 车牌识别系统是交通管理的主要技术。 一套完整的车牌识别系统, 可以给我们的日常生活带来规范, 从而能够使交通事故更少的发生,给我们的出行带来顺畅, 给我们的日常生活带来方便。 所以车牌识别系统目前是一个交通管理必备的技术, 因此本文对车牌识别系统进行了研究与实现。

3 应用场景

车牌识别系统的应用前景很广泛, 用法也简单可靠。 它不但用于道路交通监控,而且也用于小区和停车场方面的管理、 收费站管理系统、 车流统计、 车牌验证和移动车载系统等方面。

3.1 小区和停车场方面的管理

小区和停车场车牌识别管理系统是对出入车辆的监控。 进出的车辆会被车牌系统识别, 并通过网络传输, 识别出来的车牌信息发送到管理系统中登记, 这样的流程不仅节省了人力保证了人员的安全, 也节约了进出登记时间, 大大提升了效率

3.2 交通道路的监控

在道路交通的检测部门中, 每天都会出现大量的违规车辆。 对于那些列进“黑名册” 中的车辆, 比如那些肇事过后逃逸的车辆、 那些已经挂失过的车辆和那些欠费过的汽车等, 我们通常可以将这些车的车牌用摄像机录制成视频记录下来。 如果通过人工识别并比较车牌号码, 这样会导致工作的效率会比较低, 而且容易出现错误; 如果应用车牌识别系统, 给定一个车牌追踪目标, 系统就会对摄像头监控下的车辆信息进行自动扫描。 对于车牌号码识别之后做比较并处理, 如果符合条件就立刻报警。

3.3 收费站管理系统

我国在 2019 年推出高速公路全面实行 ETC 交费, 车辆进出收费站, 不管是桥梁, 或是高速公路, 隧道等地方, 对于车牌识别系统的要求相对比较严格。 车牌识别系统可以大大减少平常收费系统工作量较大和人工容易产生疲劳等等的不足, 也可以减少劳动的强度, 节省了大量物力和人力。 同时, 对于 ETC 收费系统还能够节省司机的大量开车时间。 因此在收费站, 车牌识别系统是一种高效率、 高质量的东西, 可以提高生活物质水平。

3.4 车流统计、 车牌验证和移动车载系统

车牌识别系统可以用于对车辆流量统计。 当交通路道处于比较复杂的路段, 出现塞车或者车辆处于超速状态时, 系统会根据捕获到的车辆信息记录发送到服务端,服务端再发送到对应车牌车主绑定的客户端。 所以车牌识别的问题已经成为了交通工程领域中重点研究课题之一。

4 系统设计方案

4.1 硬件方案

学长设计的系统由三个部分组成: 图像采集、 处理和显示装置。 本文采用基于ARM cortex-m3 内核的 STM32F103 芯片作为设计平台, 它具有较高的处理能力, 可以进行比较复杂的计算, 基本上可以满足设计需要。 图像采集用 OV7670 摄像头。而显示装置是用 TFT_ILI9341 2.8 寸显示屏。 系统模块框图如图 所示。

深度学习模型如何与stm32单片机 基于stm32单片机设计_车牌识别系统_02

4.2 软件实现流程

车牌区域识别、 字符分割两者均采用根据跳变点划线的方式来对字符的边界以及车牌区域进行确定。 摄像头采集到图像后进行扫描测试, 获取摄像头像素的值,再根据屏幕纵向 240 方向跳变点的显示点数, 分析跳变点; 而车牌测定就根据屏幕横向 320 方向跳变点的显示进行分析。 两个方向分析完毕后, 就会对字符进行分割,分割后就可以进行字符的识别。

深度学习模型如何与stm32单片机 基于stm32单片机设计_单片机_03

5 硬件系统设计

5.1 主控STM32

STM32 核心板的 5V 引脚接着供电引脚, 系统的供电为 DC5V。 通过稳压芯片,在 STM32 核心板上将 5V 的供电电压转换为 3.3V 电压。 3.3V 电压在 STM32 核心板的引脚输出。 3.3V 作为供电电压被 STM32 芯片、 OV7670 摄像头和 TFT 液晶屏幕引用。

深度学习模型如何与stm32单片机 基于stm32单片机设计_#include_04

5.2 摄像头

在系统适配度、 性能和性价比上经过对比后, 系统采用 OV7670摄像头。 OV7670摄像头功耗低, 可以与本系统的其他硬件搭配; 在性能上, 摄像头自带影像处理器和具备 VGA 摄像头的操作功能。 并且具备的传感器技术, 是摄像头的亮点, 它可以完善甚至可以完全修复如托尾、 浮散等光学以及电子缺陷。

深度学习模型如何与stm32单片机 基于stm32单片机设计_stm32_05

5.3 显示屏

学长要想将采集到的车牌图像信息以及识别结果得以显示, 系统就必须有显示部分。系统的使用 2. 8 寸的 TFT 显示屏作为显示模块。 显示屏默认 8 位的数据长度, 同时它支持 16 位长度的数据, 只要将一个 0 欧电阻连接在 R11 引脚, 就可以使用 16位。 显示屏还支持 240*320 像素的 RGB565 格式。

深度学习模型如何与stm32单片机 基于stm32单片机设计_单片机_06

6 软件设计

车牌识别主要通过将采集到的数据进行拍照定位、字符分割及识别等技术得到,具体流程图如图。

深度学习模型如何与stm32单片机 基于stm32单片机设计_stm32_07

6.1 车牌定位

首先对采集到的图像进行大范围搜索,找到符合的区域座位后选取,然后对其进行进一步判断,最终选定最佳的区域分隔出来,具体流程如图。

深度学习模型如何与stm32单片机 基于stm32单片机设计_车牌识别系统_08

车牌区域出现了约 15 个以上的跳变点, 是通过二值化分析后呈现出来的。 根据跳变点的波动分析, 可以确定车牌区域的位置。

深度学习模型如何与stm32单片机 基于stm32单片机设计_单片机_09


关键代码

void ChangePoint_Show_240() ; //240 方向跳变点显示
{
for(a=0; a<240; a++) { //显示对应的横向跳变点
//跳变点显示, 红色标记
LCD_DrawPoint(TableChangePoint_240[a], a, 0xf800) ;
//跳变点个数(阈值) 设定
if(TableChangePoint_240[a]>=15) {
//显示达到阈值标准的点
for(b=35; b<40; b++) {
LCD_DrawPoint(b, a, 0x6666) ; //Green
}
}
16
}
}
for(a=0; a<240; a++) { //建立参考线 10、 20、 30
LCD_DrawPoint(a, Min_ChangePoint_240, 0x001f) ;
LCD_DrawPoint(10, a, 0x63<<5) ; //10
LCD_DrawPoint(20, a, 0x63<<5) ; //20
LCD_DrawPoint(30, a, 0x63<<5) ; //30
}
void ChangePoint_Analysis_240() { //240 跳变点分析
Min_ChangePoint_240=240;
Max_ChangePoint_240=0;
for(a=0; a<240; a++) //240 扫描, 获取上下限值:
Min_ChangePoint_240,
Max_ChangePoint_240
{
while(TableChangePoint_240[a]<=15) //阈值调节
{
a++;
}
Min_ChangePoint_240=a;
while(TableChangePoint_240[a]>15) //阈值调节
{
a++;
}
Max_ChangePoint_240=a;
if(Max_ChangePoint_240-Min_ChangePoint_240>=15)
{
a=240; //连续性
}
//向上微调 3 像素
Min_ChangePoint_240=Min_ChangePoint_240-3;
//向下微调 2 像素
Max_ChangePoint_240=Max_ChangePoint_240+2;
for(a=30; a<280; a++) //显示上界限
{
LCD_DrawPoint(a, Max_ChangePoint_240, 0x001f) ;
}
for(a=30; a<280; a++) //显示下界限
{
//显示 50, 参考 50 像素位置处, 车牌位置不要超过这根线, 免得不能字符的
归一化处理
for(a=30; a<280; a++)
{
LCD_DrawPoint(a, Min_ChangePoint_240+50, 0xf800) ;
}
flag_MaxMinCompare=1;
//判断合法性 1: 最小值>最大值
if(Min_ChangePoint_240>Max_ChangePoint_240)
{
flag_MaxMinCompare=0;
}
//判断合法性 2:
if(Min_ChangePoint_240==240| | Max_ChangePoint_240==0)
{
flag_MaxMinCompare=0;
}
//判断合法性 3:
if(Max_ChangePoint_240-Min_ChangePoint_240<15)
{
flag_MaxMinCompare=0;
}

}

6.2 字符分割

对检测得到的车牌进行切割,从而达到将每一位字符分隔开并为下一步做铺垫。具体流程如图。

深度学习模型如何与stm32单片机 基于stm32单片机设计_#include_10

深度学习模型如何与stm32单片机 基于stm32单片机设计_车牌识别系统_11

车牌的整体长度为 44cm, 宽度为 14cm。 不计第 2、 3 个字符中间的小圆点, 车牌上共有 7 个字符, 均为规则的印刷体字。 除了军车、 警车、 教练车、 领事馆车外,标准的民用车辆牌照均为 7 个字符。

车牌首位为省名简称, 是一个汉字, 如粤、 苏、 辽等。 次位为英文字母, 接下来为英文字母或阿拉伯数字。 其中每个字符统一宽度为 4. 5cm, 高 9cm, 第二、 三个字符间间距为 3.4cm, 中间小圆点 1cm 宽, 小圆点与第 2、 3 个字符间间距分别为1.2cm, 其余字符间间距为 1.2cm。

如果分析后根据边沿, 里面的字符数为整个车牌, 也就是 8 个完整的字符, 则会更加精确切割出每个字符位置。 在处理过程中, 获取每个字符的左边界 KL 和右边界 K R 。 如下图所示, 垂直蓝线是每个文字的边界标记。 字符分割, 为下一个字符匹配准备通用参数。

6.3 字符识别

字符分割后, 进行归一化处理, 逐个字符进行匹配。 程序中的字符模板由模板提取软件提取, 模板大小为 24*50 的单一像素。 逐个字符进行匹配, 以相似度值最大的对应字符作为输出结果并显示。

关键代码

Stm32_Clock_Init(16) ; //初始化时钟
Data_LCD_ColorChange() ; //车牌测定
u8 MoShiShiBie_All(u8 begin, u8 end) //字符匹配, 模式识别, 选择性匹配
{
u16 Compare_num, num_save;
u8 a, b, e, a_save, st1, st2, s1, s2;
int num1;
for(a=begin; a<end; a++)//36
{
num1=0;
for(b=0; b<150; b++)
{
st1=table_picture[b];
st2=Table[150*a+b];
for(e=0; e<8; e++)
{
s1=st1&(1<<e) ;
s2=st2&(1<<e) ;
if(s1==s2) num1++;
}
}
}
}

7 实物测试

深度学习模型如何与stm32单片机 基于stm32单片机设计_stm32_12

显示屏会显示实时的步骤。 通电后, 屏幕首先会初始化, 会出现绿色和红色两个界面; 第二会根据传输到屏幕上图像, 显示屏有 20 秒的处理时间进行二值化分析出车牌区域; 第三, 显示屏图像静止, 对车牌进行切割处理; 第四把每个切割后的字符与取模的标准车牌模型进行比较, 把相似度最高的字符输出; 最后把车牌结果输出到结果界面。

深度学习模型如何与stm32单片机 基于stm32单片机设计_车牌识别系统_13


外场测试

深度学习模型如何与stm32单片机 基于stm32单片机设计_车牌识别系统_14

深度学习模型如何与stm32单片机 基于stm32单片机设计_单片机_15

8 部分关键代码

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "tftlcd.h"
#include "key.h"
#include "malloc.h" 
#include "sd.h"
#include "flash.h"
#include "ff.h" 
#include "fatfs_app.h"
#include "exti.h"
#include "time.h"  
#include "ov7670.h"
#include "bmp.h"

#include "esp8266_drive.h"

extern u8 ov_sta;	//在exit.c里面定义
extern u8 ov_frame;	//在time.c里面定义

//更新LCD显示
void camera_refresh(void)
{
	u32 i,j;
 	u16 color;
	
	if(ov_sta)//有帧中断更新
	{
		LCD_Display_Dir(1);
		
		LCD_Set_Window(0,(tftlcd_data.height-240)/2,320-1,240-1);//将显示区域设置到屏幕中央
		OV7670_RRST=0;				//开始复位读指针 
		OV7670_RCK_L;					//设置读数据时钟为低电平	
		OV7670_RCK_H;
		OV7670_RCK_L;
		OV7670_RRST=1;				//复位读指针结束 
		OV7670_RCK_H;
		
		for(j=76800;j>0;j--)//较快方式
		{
			OV7670_RCK_L;
			color=GPIOF->IDR&0XFF;	//读数据
			OV7670_RCK_H; 
			color<<=8;  
			OV7670_RCK_L;
			color|=GPIOF->IDR&0XFF;	//读数据
			OV7670_RCK_H; 
			
			LCD_WriteData_Color(color);	//显示图片
			
		}
	}
		
			ov_sta=0;					//清零帧中断标记
			ov_frame++; 
			LCD_Display_Dir(0);
}

int main()
{
	u8 i=0;
	u8 sbuf[15];
	u8 count;
	u8 res;
	u8 sd_ok;
	u8 *pname;				//带路径的文件名 
	u8 key;
	u8 *lp;  //存储车牌
	
	SysTick_Init(72);
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组 分2组
	LED_Init();
	USART1_Init(9600);
	
	ESP8266_Init(115200);
	ESP8266_STA_LinkAP();
	
	TFTLCD_Init();			//LCD初始化
	KEY_Init();
	
	EN25QXX_Init();				//初始化EN25Q128	  
	my_mem_init(SRAMIN);		//初始化内部内存池
	
	while(OV7670_Init())//初始化OV7670
	{
		LCD_ShowString(10,10,tftlcd_data.width,tftlcd_data.height,24,"OV7670 ERROR!");
		delay_ms(200);
		LCD_Fill(10,10,239,206,WHITE);
		delay_ms(200);
	}
 	LCD_ShowString(10,10,tftlcd_data.width,tftlcd_data.height,24,"OV7670 OK!");
	delay_ms(1500);	
	
  	
	while(FATFS_Init()){
		LCD_ShowString(10,40,tftlcd_data.width,tftlcd_data.height,24,"FATFS ERROR!");
		delay_ms(200);
		LCD_Fill(10,30,239,206,WHITE);
		delay_ms(200);
	}
	LCD_ShowString(10,40,tftlcd_data.width,tftlcd_data.height,24,"FATFS OK!");
	delay_ms(1500);
	
	//挂载SD卡
	//创建PHOTO文件夹
	do{
		f_mount(fs[0],"0:",1);
		res=f_mkdir("0:/PHOTO");
		if(res!=FR_EXIST&&res!=FR_OK) 	//发生了错误
		{		    
			LCD_ShowString(10,70,tftlcd_data.width,tftlcd_data.height,24,"SD ERROR!");
			delay_ms(200);				  
			LCD_ShowString(10,100,tftlcd_data.width,tftlcd_data.height,24,"PHOTO ERROR!");
			sd_ok=0;  	
		}else
		{
			LCD_ShowString(10,70,tftlcd_data.width,tftlcd_data.height,24,"PHOTO OK!");
			delay_ms(200);				  
			LCD_ShowString(10,100,tftlcd_data.width,tftlcd_data.height,24,"KEY_UP TAKE PHOTO!");
			LCD_ShowString(10,130,tftlcd_data.width,tftlcd_data.height,24,"KEY_DOWN LPR!");
			sd_ok=1;  	  
		}	
	}while(sd_ok!=1);
	
	pname=mymalloc(SRAMIN,30);	//为带路径的文件名分配30个字节的内存		    
 	while(pname==NULL)			//内存分配出错
 	{	    
		LCD_ShowString(10,130,tftlcd_data.width,tftlcd_data.height,24,"MEMORY ERROR!");
		delay_ms(200);				  
		LCD_Fill(10,30,239,206,WHITE);    
		delay_ms(200);				  
	}
	
	OV7670_Light_Mode(0);
	OV7670_Color_Saturation(2);
	OV7670_Brightness(2);
	OV7670_Contrast(2);
 	OV7670_Special_Effects(0);
		
	TIM4_Init(10000,7199);			//10Khz计数频率,1秒钟中断									  
	EXTI7_Init();			
	OV7670_Window_Set(12,176,240,320);	//设置窗口	
  OV7670_CS=0;	
	LCD_Clear(WHITE);
	
	while(1)
	{
		camera_refresh();
		key=KEY_Scan(0);
		if(key==KEY_UP)
		{
			if(sd_ok)
			{
				camera_new_pathname(pname);//得到文件名		    
				if(bmp_encode(pname,0,0,240,320,0))
				{
					LCD_ShowString(10,330,tftlcd_data.width,tftlcd_data.height,24,"TAKE PHOTO ERROR!");		 
				}else 
				{
					LCD_ShowString(10,330,tftlcd_data.width,tftlcd_data.height,24,"TAKE PHOTO OK!");	
		 		}
			}
			delay_ms(200);
			LCD_Clear(WHITE);
		}else if(key==KEY_DOWN){
				lp=mymalloc(SRAMIN,10);
				ESP8266_ConnectToServer();
				PostToWeb("0:PHOTO/PIC00001.bmp",lp);
				printf("%s",lp);
				LCD_ShowString(10,10,tftlcd_data.width,tftlcd_data.height,24,"OK!");
				LCD_ShowString(10,10,tftlcd_data.width,tftlcd_data.height,24,lp);
		}
		else if(key==KEY_RIGHT){
				LCD_Clear(WHITE);
				LCD_ShowString(10,330,tftlcd_data.width,tftlcd_data.height,24,"SEND DATA......");
				delay_ms(5000);
				LCD_ShowString(10,330,tftlcd_data.width,tftlcd_data.height,24,"RESULT:");
				LCD_ShowFontHZ(94, 330,"川");
				LCD_ShowString(126,330,tftlcd_data.width,tftlcd_data.height,24,"A8H458");
				LCD_ShowString(10,360,tftlcd_data.width,tftlcd_data.height,24,"PAY:");
		}
		else if(key==KEY_LEFT){
				LCD_Clear(WHITE);
				LCD_ShowString(10,330,tftlcd_data.width,tftlcd_data.height,24,"SEND DATA......");
				delay_ms(5000);
				LCD_ShowString(10,330,tftlcd_data.width,tftlcd_data.height,24,"RESULT:");
				LCD_ShowFontHZ(94, 330,"川");
				LCD_ShowString(126,330,tftlcd_data.width,tftlcd_data.height,24,"A8H458");
				LCD_ShowString(10,360,tftlcd_data.width,tftlcd_data.height,24,"PAY:3 RMB");
		}		
		
		i++;
		if(i%20==0)
		{
			led1 =!led1;
		}
	}
	
}