基于STM32的车牌图像采集与识别系统设计

0 前言

🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。

为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是

🚩 毕业设计 stm32的摄像头车牌图像采集与识别系统

🥇学长这里给一个题目综合评分(每项满分5分)

  • 难度系数:3分
  • 工作量:3分
  • 创新点:5分
  • 🧿 选题指导, 项目分享:

    https://gitee.com/dancheng-senior/project-sharing-1/blob/master/%E6%AF%95%E8%AE%BE%E6%8C%87%E5%AF%BC/README.md

    1 简介

    使用基于RT-thread操作系统的AB32VG1开发板作为主控,对ov7670摄像头进行图像采集,并使用串口发送图片RGB565格式到PC供opencv进行图像识别。 原项目设想在开发板上进行采集的同时并通过简单的二值算法和插值算法实现车牌号识别,但实践中发现开发板的ram并不够保存采集回来的图像信息,与数据手册中介绍的192k有一定差距,实现用户能使用的ram是70k;同时原设想是带lcd屏幕的,但最后发觉io口数量不够,只能通过串口调试显示,但lcd屏幕的 spi代码仍保留在原码中,可供参考。 目前开发板通过摄像头采集完整数据部分已经完成,并且可以通过串口uart1发送到上位机进行图像显示。识别号牌上位机需要另外再做。

    2 主要器件

  • stm32单片机

  • RT-Thread物联网操作系统

  • OV7670摄像头

  • opencv图像识别

  • LCD屏幕

  • 3 实现效果

    4 设计原理

    4.1 硬件设计

    摄像头ov7670带fifo:采用csi总线的普通30w摄像头。考虑到用模拟读取摄像头,io的反转速度可能不能满足高速采集的需要,因此保险起见,直接使用带fifo的摄像头。sccb总线采用全模拟的方式,跳过了所有中间层,直接操作寄存器,提高了总线的时钟;

    4.2 软件总体过程

    (1)OV7670带FIFO摄像头图像采集,采用GPIO模拟摄像头时序,通过读取FIFO输出值将图像直接显示在LCD屏上
    (2)LCD屏相当于一个图像缓存,同时也做显示,通过读取LCD屏上的像素值进行图像处理;
    (3)车牌定位处理,车牌定位常用二值化分割,腐蚀膨胀处理,连通域计算等操作,显然这些算法在stm32f1上实现是很困难的,且处理速度太慢,因此,采用RGB转HSV颜色空间变换和阈值选择进行车牌定位,然后将车牌定位区域进行二值化处理,不是蓝色车牌的部分就是字符区域;
    (4)车牌字符分割处理,字符分割先采用行统计加列统计的方式,确定每行和每列的有效像素和,进一步确定字符区域;然后进行横向统计分割,通过每一列的像素和阈值判断字符的分界线和个数;
    (5)车牌归一化处理,归一化处理先将每个字符提取出来,然后按照像素值进行横向和纵向压缩,最终处理成模板一样大小的字符;并在液晶屏上保存字符的数据;
    (6)模板匹配,将归一化之后的字符,与模板中的字符通过像素值一一比较,确定相似度最高的字符就是目标值;

    4.3 具体解释

    图像采集
    通过OV7670摄像头进行图像采集,采集的图像大小为320*240像素,像素格式为RGB565。每个像素由两字节组成,第一字节的高五位是Red,第一字节的低三位和第二字节的高三位组成Green,第二字节的低五位是Blue。

    二值化
    二值化就是让图像的像素点矩阵中的每个像素点的灰度值为0(黑色)或者255(白色),让整个图片呈现出只有黑色和白色的效果。二值化后的图像中灰度值范围是0或者255。这时需要设定一个阈值来对像素点进行设置。

    常用二值化方法:

    取中值:设置阈值为127,灰度值小于127的为0,大于127的为255。这样设置计算量小,计算快。缺点也严重:在不同的图像中,颜色分布差别大,处理效果也不会很好。程序开始之前设置R,G,B的阈值,通过阈值判断将像素设置为全黑(0x0000)或者全白(0xFFFF).同时根据色彩的变化记录每一行的颜色跳变点,由此识别出车牌区域。
    取平均值:像素点平均值 = (像素点1灰度值 + 像素点2灰度值 + …… + 像素点n灰度值) / n
    双峰法:此方法适用于具有明显双峰直方图的图像,不适合直方图中双峰差别很大或双峰间的谷比较宽广而平坦的图像。该方法认为图像由前景和背景组成,在灰度直方图上,前景和背景会形成高峰,在双峰之间的最低谷处就是阈值。
    识别车牌区域
    根据上一步的二值化,由于车牌区域跳变点多,由此可以得出车牌区域。分别记录车牌区域的上下高度。然后通过RGB-HSV颜色转换,识别出车牌区域的左右边界。

    字符分割
    我国常见车牌以及排列顺序大部分都是按照如下设计的:汉字、英文字母、点、英文字母、阿拉伯数字、阿拉伯数字、阿拉伯数字、阿拉伯数字。基于这个规律,以及图像采集高度一致,设计了如下的分割方法:

    在内存中开辟七个长为车牌长的七分之一和宽为车牌宽的区域
    从车牌图像长边的巾问向下开始扫描车牌图像,并把扫描到的所有的点灰度值复制到0区域的第四个区域对应位置上。然后再从上向下扫描刚扫描过这一努的左边或右边,直到所扫描的这一峰上的所有点的灰度都是0时为止,并把这一竖认为是字符的分离处。
    切割第五到第七个字符。方法就是,切割完了第四个字符之后,再依次扫描剩下的空间,直到所扫描的这一竖上的所有点的灰度值不全为0时,认为是字符的开始并依次扫描直到所扫描的这一竖上的所有点的灰度值全为0时认为是字符的结束。
    切割第三到第四个字符。这两个字符的切割方式与第五到第七个字符一样。
    切割第一到第二个字符。当第三个字符切割完之后,我们将遇到一个点,我们也把它看作一个字符,只不过这个点扫描之后就不要了。扫描完这个点之后,我们来切割第二个字符,它的切割方式与前面一样。切割完了第二个字符之后,再向左扫描,直到所扫描的这一竖上的所有点的灰度值不全为0时,认为是字符的开始,并依次扫描直到所扫描所有剩下的,并填到相应的位置,直到剩下的空间填满。经过粗分割后,可以得到一些单个字符区域和多余的空间。下一步我们将把这些多余的空间去掉。这将更有利于下一步字符的识别。
    去除图像上多余空间:车牌上的字符经过了粗切割所得到的是一些单的字符,但在分配空间时是按照车牌的宽和长的七分之一来分配的;所以这个空间可能大于字符应该占的空问。所以,要将多余空间去除。对于第一个字符从第一行开始向下扫描,把那些一行中所有的点的灰度值全为0的点去掉,直到扫描到有一行不全为0时为止。然后再从第一列开始向右扫描把那些一列中所有的点的灰度值全为0的点去掉,直到扫描到有一列不全为0时为止。接下来从最后一行开始向上扫描,把那些一行中所有的点的灰度值全为0的点去掉,直到扫描到有一行不全为0时为止。最后从最后一列开始向左扫描把那些一列中所有的点的灰度值全为0的点去掉,直到扫描到有一列不全为0时为止。重复上面的步骤完成剩下字符的切割。
    根据二值化的结果,以及记录的跳变点位置,对字符进行分割,同时记录字符的左右边界。
    字符匹配
    对分割出来的字符进行归一化处理,这里用到图片的扩大算法,扩大之后逐一的去进行字符匹配。字符模板事前通过字模软件转换成二进制数据保存在数组中。最后根据匹配结果相似度最大的做为输出结果。
    归一化图像就是要把原来各不相同的字符统一到同一尺寸。因为扫描进来的图像中字符大小存在较大的差异,而相对来说,统一尺寸的字符识别的标准性更强,准确率自然也更高。具体算法如下:先得到原来字符的高度和宽度,与系统已存字模的数据作比较,得出要变换的系数,然后根据得到的系数按照插值的方法映射到原图像中。

    5 部分核心代码

    /* 摄像头IO口采用直接操作寄存器的方式实现,极大提升io速度 */
    #define BSP_FIFO_RCK_PIN "PA.5"
    #define BSP_FIFO_RCK_SET_LOW (GPIOA_BASE[GPIOx] &= ~(1ul << 5))
    #define BSP_FIFO_RCK_SET_HIGH (GPIOA_BASE[GPIOx] |= (1ul << 5))
    /* sccb总线的初始化并设置ov7670相应寄存器 */
    sccb_init();
    if (sccb_write_reg(0x12, 0x80) == RT_FALSE)
    {
        return RT_FALSE;
    }
    rt_thread_delay(50);
    id1 = sccb_read_reg(0x0b);
    id2 = sccb_read_reg(0x0a);
    rt_kprintf("id1 = 0x%02x, id2 = 0x%02x\n", id1, id2);
    for (rt_uint16_t i = 0; i < sizeof(ov7670_init_reg_tbl) / sizeof(ov7670_init_reg_tbl[0]); i++)
    {
        sccb_write_reg(ov7670_init_reg_tbl[i][0], ov7670_init_reg_tbl[i][1]);
    }
    /* 开启摄像头vsync扫描线程(没有外部中断因此改用轮询的方式实现) */
    rt_thread_t thread;
    /* 查询VSYNC线程 */
    thread = rt_thread_create("ov7670_vsync", ov7670_vsync_thread_entry, RT_NULL, 1024, 5, 100);
    if (thread == RT_NULL)
    {
        rt_kprintf("ov7670_vsync thread create fail!\n");
        return RT_FALSE;
    }
    /* 启动线程 */
    rt_thread_startup(thread);
    /* 提取hal库实现了uart的数据发送函数 */
    void uart1_send(rt_uint8_t *pbuf, rt_uint32_t len)
    {
        for (rt_uint32_t i = 0; i < len; i++)
        {
            hal_uart_clrflag(UART1_BASE, UART_FLAG_TXPND);
            hal_uart_write(UART1_BASE, pbuf[i]);
            while (hal_uart_getflag(UART1_BASE, UART_FLAG_TXPND) == 0)
                ;
        }
    }
    /* LCD底层驱动代码,因为引脚不够,所以无法演示,测试可用,另外程序里也配有寄存器版本的操作代码 */
    static rt_uint32_t spi_bit_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
    {
        struct rt_spi_bit_ops *ops = (struct rt_spi_bit_ops *)device->user_data;
        rt_uint8_t tmp_buf[1024];
        rt_memset(tmp_buf, 0, sizeof(tmp_buf));
        if (message->send_buf == RT_NULL)
        {
            message->send_buf = tmp_buf;
        }
        else if (message->recv_buf == RT_NULL)
        {
            message->recv_buf = tmp_buf;
        }
        else
        {
            return RT_FALSE;
        }
        if (message->cs_take)
        {
            ops->set_cs(ops->data, PIN_LOW);
        }
    #ifdef SPI_DC
        message->length &SPI_DC ? ops->set_dc(ops->data, PIN_HIGH) : ops->set_dc(ops->data, PIN_LOW);
        message->length &= ~SPI_DC; /* 复原消息长度 */
        //    rt_kprintf("message->length = %d\n", message->length);
    #endif
        spi_rw_bytes(device, (rt_uint8_t *)message->send_buf, (rt_uint8_t *)message->recv_buf, message->length);
        if (message->cs_release)
        {
            ops->set_cs(ops->data, PIN_HIGH);
        }
    }
    static const struct rt_spi_ops spi_bit_bus_ops = {
        RT_NULL,
        spi_bit_xfer};
    
    

    6 最后

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于STM32的车牌图像采集与识别系统设计

    发表评论