stm32 车牌识别系统

项目课题背景

       在我们的日常生活中,电子科技的应用无处不在,特别是在交通领域。例如,图像处理技术和自动检测技术已经悄然融入我们的生活。在众多道路交通技术中,车牌识别系统尤为关键,它已成为现代交通管理的重要工具。一个高效的车牌识别系统不仅能减少交通事故,还能提高道路通行效率,使我们的出行更加便捷。因此,本文将对车牌识别技术进行深入研究,并探讨其在交通管理中的应用实现。

应用场景

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

系统设计方案

硬件方案

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

软件实现流程

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

 

硬件系统设计

主控STM32

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

摄像头

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

 

 显示屏

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

 

软件设计

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

 

车牌定位

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

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

// 定义函数 ChangePoint_Show_240,用于显示240方向的跳变点
void ChangePoint_Show_240() {
    for(int a = 0; a < 240; a++) { // 遍历所有横向跳变点
        // 在对应点显示红色标记
        LCD_DrawPoint(TableChangePoint_240[a], a, 0xf800); // 显示跳变点,红色标记
        
        // 设置跳变点个数(阈值)设定
        if(TableChangePoint_240[a] >= 15) {
            // 显示达到阈值标准的点,使用绿色
            for(int b = 35; b < 40; b++) {
                LCD_DrawPoint(b, a, 0x6666); // Green
            }
        }
    }
    
    // 建立参考线 10、20、30
    for(int a = 0; a < 240; a++) {
        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显示
    }
}

// 定义函数 ChangePoint_Analysis_240,用于分析240方向的跳变点
void ChangePoint_Analysis_240() {
    Min_ChangePoint_240 = 240;
    Max_ChangePoint_240 = 0;
    
    for(int a = 0; a < 240; a++) { // 扫描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 -= 3;
        // 向下微调2像素
        Max_ChangePoint_240 += 2;
        
        for(int a = 30; a < 280; a++) { // 显示上界限
            LCD_DrawPoint(a, Max_ChangePoint_240, 0x001f);
        }
        for(int a = 30; a < 280; a++) { // 显示下界限
            LCD_DrawPoint(a, Min_ChangePoint_240 + 50, 0xf800); // 显示50参考像素位置
        }
        
        int flag_MaxMinCompare = 1; // 初始化合法性判断标志
        
        // 判断合法性
        if(Min_ChangePoint_240 > Max_ChangePoint_240) {
            flag_MaxMinCompare = 0;
        }
        if(Min_ChangePoint_240 == 240 || Max_ChangePoint_240 == 0) {
            flag_MaxMinCompare = 0;
        }
        if(Max_ChangePoint_240 - Min_ChangePoint_240 < 15) {
            flag_MaxMinCompare = 0;
        }
    }
}

 

字符识别

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

关键代码

// 初始化STM32时钟,设置为16MHz
Stm32_Clock_Init(16);

// 进行LCD颜色变更,用于车牌测定
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; // 定义匹配数量

    // 从begin到end进行模式匹配
    for (a = begin; a < end; a++) { // 循环遍历指定范围内的字符
        num1 = 0; // 初始化当前字符的匹配数量
        
        // 对每个字符模式进行150次比较
        for (b = 0; b < 150; b++) { // 对每个字符进行150次比较
            st1 = table_picture[b]; // 从图片表中取出当前比较位
            st2 = Table[150 * a + b]; // 从模式表中取出相应位置的比较位
            
            // 对每个字节的8位进行比较
            for (e = 0; e < 8; e++) { // 比较每位
                s1 = st1 & (1 << e); // 获取st1的第e位
                s2 = st2 & (1 << e); // 获取st2的第e位
                
                // 如果两位相同,增加匹配数量
                if (s1 == s2) {
                    num1++; // 若相同则增加匹配计数
                }
            }
        }
        
        // 逻辑代码或其他处理
        // 此处缺少对num1的进一步处理或对结果的保存,可能是代码片段不完整
    }

    // 函数应有返回值,此处未给出返回逻辑,代码可能不完整
}

 

字符分割

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

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

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

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

#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;
        }
    }

}

实物测试

 

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值