STM32F103+OV7670车牌识别:内存受限下的嵌入式图像处理实战
2026/9/1 9:34:46 网站建设 项目流程

简介:面向嵌入式方向毕业设计的一套车牌识别系统方案,采用 STM32F103 微控制器与 OV7670 摄像头模块,完整实现车辆图像采集、车牌定位与字符识别。系统覆盖图像预处理、字符分割、特征提取和模式识别等核心步骤,并针对渝、辽、沪、浙、苏、粤六个省份的车牌样式进行过专项适配,以提高真实场景下的识别准确率;同时提供 AD 格式电路图,展示如何将 OV7670 输出的模拟信号经 ADC 转换后送入主控处理,对理解硬件接口设计很有帮助。压缩包为 7z 格式,大小约 16.54MB,文件总数暂无数据,但结合项目描述可判断内含 STM32 工程源码、接口电路图以及设计说明文档,能够支撑从硬件搭建、外设配置到算法调试的完整毕设流程。目前已有 2193 人学习下载,适合电子、自动化、计算机等专业学生直接参考,或在此基础上进行识别算法优化与功能扩展。 每年毕设季,都会看到一批“基于STM32F103+OV7670车牌识别系统”的题目冒出来。我去年拿到的正是一份这样的工程包,压缩包解出来是几十个源文件加一堆寄存器配置表,第一次编译就报了几十个错误,调了两周才把图像稳定显示出来。做完之后回头看,这个题的含金量并不在于“车牌识别”四个字,而在于它把传感器驱动、外设时序、内存管理和嵌入式图像算法全部缝合在一个72MHz的Cortex-M3芯片上,每一步都是在跟资源限制较劲。这篇文章我会按自己踩坑的顺序拆解整个系统:先讲清楚F103到底适不适合做这件事,再讲OV7670图像怎么进内存,车牌定位分割识别的算法在MCU上怎么做,最后是调试经验和答辩演示注意事项。适合刚拿到类似工程包找不到头绪的人参考,也适合想自己从零搭一套的人做选型依据。

1. 破题:F103做车牌识别,第一道坎不是算法而是内存

1.1 一个容易被忽略的事实:F103没有DCMI摄像头接口

很多初学者拿到OV7670模块后的第一个坑,就是误以为STM32F103自带摄像头接口。实际上,F103系列压根没有DCMI外设,这个数字摄像头接口是F4/F7系列才有的。没有DCMI,意味着OV7670输出的PCLK像素时钟、VSYNC帧同步、HREF行同步信号都无法用硬件自动接收。

有人会想,那我用外部中断去跟PCLK,一位一位读总行吧?实测不行。OV7670在24MHz输入时钟下,PCLK最高可以到24MHz,中断服务程序根本跟不上这个速度,一条GPIO读指令还没执行完下一拍像素就来了。所以市面上的F103+OV7670方案,基本都会选带FIFO的摄像头模块。模块上多一颗AL422B视频FIFO芯片,OV7670负责把像素写入FIFO,F103在VSYNC结束后像读外部SRAM一样把整帧数据读走,完全绕开PCLK速度问题。这是整个硬件设计的前提,理解这一点再看代码就不会懵。

1.2 整帧图像放不进SRAM,必须压缩任务

F103的SRAM只有64KB(ZET6)甚至48KB(RCT6),而一块QVGA分辨率的RGB565图像是320×240×2=153600字节,连最小号的F103都装不下一整帧。所以无论代码怎么优化,都不能抱有“把整帧读进来再慢慢处理”的幻想。

实际可行的思路是读FIFO的时候直接抽点降采样。假设算法只需要160×120的灰度图,读进来的数据量就是19200字节,后续处理也在同一块缓冲区上做,SRAM就吃得下了。这个“读的同时顺便降采样”的思路一定要先想清楚,后面的代码结构都建立在这个基础上。有同学问我能不能用DMA加FSMC去搬运,DMA确实能搬,但它在搬运过程中没法跳点,搬完还得再做一次压缩,内存压力反而更大。CPU循环读看似原始,实际在72MHz下读160×120个像素并没有那么慢,还能顺手完成灰度转换和特征提取,是更实用的选择。

2. 硬件链路搭建:最小系统、摄像头模块与FSMC读FIFO

2.1 从最小系统到开发板:ZET6还是RCT6

STM32F103ZET6和RCT6都能做这个项目,但优先选ZET6。原因是ZET6带FSMC接口,可以很方便地把AL422B映射成外部SRAM直接读;RCT6没有FSMC,读FIFO要靠GPIO模拟时序,代码复杂,速度也差不少。如果手里只有RCT6的板子也不是不能做,但例程尽量找带FSMC的版本。

供电问题直接影响稳定性。OV7670模块一般自带LDO,对3.3V供电要求不苛刻,但整个系统如果靠USB口供电,插上摄像头和LCD之后电流可能到300mA以上,USB口供电质量差就会出现图像纹波。我实测最稳的方式是用5V适配器从开发板DC座供电,或者给OV7670模块单独用一路LDO供3.3V。ST-LINK下载器的供电只适合调试,不适合长时间跑识别。

这里顺带提醒一下GPIO电平:STM32F103并不是所有引脚都耐受5V,只有标注FT的引脚才能承受5V。OV7670模块的IO大多是3.3V,正常接没问题,别因为看到网上讨论“GPIO能不能承受5V”就给模块供5V逻辑电平,超压大概率烧摄像头。

2.2 带FIFO和不带FIFO的OV7670模块,怎么选

这块不用纠结,直接买带AL422B的。两种模块的差别:

对比项带FIFO模块不带FIFO模块
数据读取方式MCU像读内存一样读FIFO需要跟随PCLK逐像素采集
F103适配难度低,驱动简单高,F103无DCMI,几乎不可行
典型代表带AL422B的OV7670模块裸摄像头小板
价格略贵便宜

带FIFO的模块通常引出SIO_C、SIO_D、VSYNC、RESET以及8位或16位数据线。建议接线方式:SIO_C和SIO_D接GPIO模拟SCCB,VSYNC接一个外部中断引脚,数据线接FSMC的数据总线,读地址通过FSMC片选映射到Bank1的NE1区域。这样F103在VSYNC到来时清FIFO写指针,等VSYNC结束后从固定地址连续读取,就能拿到整帧数据。

3. OV7670寄存器配置与图像采集链路

3.1 SCCB配置:先把输出格式固定好

OV7670用SCCB总线配置寄存器,时序和I2C很像,MCU侧用GPIO模拟即可。OV7670的写地址是0x42,读地址是0x43。初始化代码本质上就是把一串寄存器配置表写进摄像头,关键点有三个:

  • COM7(0x12)要设置为RGB565输出,同时开启QVGA或QQVGA分辨率。
  • COM15(0x40)要配置成RGB565格式,RGB还是BGR的排列方式要根据后续算法确认。
  • 自动曝光和自动白平衡必须关掉。很多花屏和颜色漂移问题并不是硬件坏,而是这两个自动功能在场景变亮变暗时自行调整,导致后续二值化阈值失效、车牌区域被淹没。

这块没有捷径,寄存器值最好先照着一个能正常出图的例程来。自己一点点试寄存器表很容易陷入“图像全黑全绿”的泥潭,我之前就因为一个曝光寄存器没设对,折腾了整整一个晚上。

3.2 AL422B的读时序与CPU读取方式

AL422B的工作流程可以这样理解:OV7670在VSYNC有效期间按PCLK把像素写入FIFO,MCU在VSYNC结束后读取,每读一次数据,FIFO读指针自动加一。所以对MCU来说,AL422B就是一个时钟驱动的串行内存。

用FSMC读时,把FIFO映射到外部SRAM地址,连续读取同一个地址就会被FSMC当成连续读操作,NE和RD信号不断翻转,FIFO输出数据并自动递增。这个“同一个地址连续读”的写法是很多人卡住的地方,注意不要给FIFO分配多个地址,一个片选地址就够了。

读取方式我建议用CPU循环读:每次读16位像素,按目标分辨率抽点存放,同时完成灰度转换。例如目标160×120灰度图,可以在水平和垂直方向每隔一个点取一次,计算量完全能接受。DMA方式更适合整块搬运后再处理,但DMA无法在搬运过程中跳点,内存压力更大,还要处理FIFO读指针和DMA完成中断的配合,复杂度高很多。

3.3 分辨率、帧率和内存预算

推荐直接用QQVGA(160×120)作为算法输入。如果OV7670本身输出320×240,读FIFO时配合抽点,最终消费的数据量是固定的。帧率方面,F103读一帧160×120并完成预处理,实测大约需要几十毫秒到一百多毫秒,加上后续定位识别,整体在每秒1到2帧的水平,做静态车辆识别足够,动态抓拍基本不要指望。

内存预算给一个参考:160×120灰度图19200字节,蓝色特征图用1bit存储约2400字节,二值图直接复用灰度缓冲区,再留几个临时数组,总占用在30KB以内。RCT6的48KB SRAM也能跑,但余量很小,建议全程别开大优化,否则一不留神就把栈撑爆。

4. 车牌定位:把蓝色块变成候选框

4.1 蓝色特征提取,比边缘检测更直接

国内蓝底白字车牌最明显的特征是蓝色。与其费劲做Sobel边缘再加形态学,不如直接在颜色上做文章。每读一个像素,判断是否满足“B分量明显大于R和G,且整体亮度适中”,满足就标记为蓝色像素。因为输入已经是RGB565,判断量极小。

得到蓝色特征图后,可以做连通域分析,找出面积最大的蓝色连通域。这里有两种落地方式:一种是标准的两遍扫描标记,适合追求通用性;一种是只做行列投影的简化版本,效率更高。毕设演示场景下图像干净、车辆正对摄像头,简化版就够用。

4.2 行列投影:简单高效的定位算法

把蓝色特征图按行累加,得到每行的蓝色像素数量。车牌区域会突出一块连续的高值区间,其他蓝色物体也可能形成干扰,但车牌通常更规整。取高值连续区间作为候选行范围,然后在候选行范围内再按列累加,找到列范围,就能得到候选框。

给候选框加约束条件能大幅提高可靠性:宽高比在2.5到4.0之间,面积不小于全图面积的某个比例,不符合直接判定为定位失败。光照变化大时,蓝色阈值可能要动态调整,最简单的办法是按照蓝色分量的统计分布取一个百分位值。

4.3 用边缘投影精修边界

单纯靠蓝色框定位,往往会把上下边框甚至车体的一部分也包括进来。这时候可以在候选框内部做灰度Sobel边缘检测,车牌字符边缘密集、竖边缘和横边缘交替,投影后集中在字符区域,上下边缘就是字符行的上边界和下边界。这个精修步骤能在后续二值化、字符分割时省很多事。

边缘投影时要注意去掉车牌的白色边框,因为白边框和字符一样是高边缘区域。做法是先做水平投影找字符行范围,再在字符行范围内做垂直投影,把上下白边排除掉。

5. 字符分割与识别:模板匹配在MCU上的落地

5.1 局部二值化:大律法只用于车牌区域

得到车牌候选框后,需要把灰度图变成二值图。不建议全图固定阈值,因为室内外光照差异大,车牌区域的亮度和周围场景完全不同。正确做法是只在候选框内用大律法(OTSU)求阈值,按这个阈值把像素分成黑白两类。白色像素对应字符和车牌边框,蓝色底会变成黑色。

OTSU在几十个灰度级上做统计,计算量并不大。处理时用整数累加直方图,避免浮点运算。每次识别前重新计算阈值,能适应环境光的渐变。

5.2 去边框、铆钉、间隔符

二值化后第一件事是去掉四周边框。字符区域外有白色边框和螺丝,这些如果不消除,投影法会把边框当成一个大字符,或者在边界处产生假投影峰值。处理办法:对二值图做水平投影,连续白色行集中在中间区域的就是字符行,把上下杂散行裁掉;再做垂直投影,找到左右有效字符边界。

第二件容易踩坑的是间隔符。国内车牌第二个和第三个字符之间有一个小圆点,垂直投影时可能单独形成一个尖峰,也可能和相邻字符黏在一起。按“宽度小于平均字符宽度三分之一的段,直接丢弃”的原则处理,基本能过滤掉。

5.3 字符归一化与SAD模板匹配

每个字符候选段裁出来后,先按高度统一缩放到目标尺寸,宽度等比例缩放,尽量不要拉伸变形。缩放后的字符和模板库做SAD(绝对差值和),取最小差值对应的模板作为识别结果。

模板库建议从标准车牌字体图中生成,每个字符归一化到16×32或者24×48。31个省市简称汉字加24个字母加10个数字,一共65个模板,按24×48计算总大小约75KB,Flash完全放得下。模板生成和匹配都用整数运算,F103不擅长浮点SAD。

5.4 识别结果的时间平滑

单帧识别偶尔会跳字,尤其汉字里“京”和“津”、“鲁”和“晋”这种轮廓接近的。我后来加了一个简单的时间平滑:连续5帧识别结果中,对每一位字符做多数投票,只在投票结果明显占优时才更新显示。这个改进对答辩演示特别有用,静止车辆停在那里,识别结果不会频繁跳动,观感好了很多。

6. 跑通工程和调试点:从花屏到识别率上不去

6.1 拿到工程包后先做的三件事

第一,把工程在Keil里编译,先把编译器版本和Include路径的报错清掉。第二,确认接线和代码里的引脚定义一致,很多工程包换了一块板子之后引脚就不对。第三,把所有外设的初始化顺序理一遍:先LCD、再摄像头、后算法,任何一个顺序乱了就会出现“屏幕亮但没图像”或者“识别永远失败”。

我个人的习惯是先用一个最简单的功能测试:上电只初始化LCD和OV7670,把摄像头图像直接显示在LCD上,确认颜色正常、没有花屏,再开始写算法。这能帮你把“图像采集链路”和“算法逻辑”两个问题域隔离开,排查问题时不会两头猜。

6.2 图像花屏、颜色不对的排查顺序

花屏通常不是算法问题,而是硬件链路问题。按这个顺序排查:

  • 供电是否稳定:USB供电时图像有没有水波纹,有就换外部供电。
  • OV7670寄存器配置是否成功:读回COM7,确认是RGB565模式。
  • 数据位顺序:FSMC 16位读FIFO时高低字节是否反了,RGB565和BGR565排列是否反了。
  • 帧同步时序:VSYNC中断是否真的来了,FIFO写指针有没有在帧开始时清零。

颜色偏绿或偏紫,九成是RGB通道顺序问题,换个排列就好。图像看起来有拖影,通常是对FIFO写指针清零的时机不对,必须在帧开始前清零,否则读到的是上一帧和下一帧混在一起的数据。

6.3 识别率上不去的真实原因

如果定位准了、分割也能切出7个框,但识别结果不对,问题多半在模板。车牌字符字体有行业标准,但不同批次生产的车牌字体细节有差异,模板最好从实拍的高清车牌中截取,而不是从网图里随手扣。另一个常见原因是字符归一化时宽高比被破坏,导致“京”和“津”这类字形混淆。

光照是最大的不稳定因素。建议答辩演示时固定室内灯光,或者用一个小型补光灯从侧上方打光。摄像头一定要正对车牌,俯仰角太大时车牌在成像中有明显透视变形,后面的定位和分割都会跟着崩。

6.4 从毕设到可扩展的方向

这套系统的架构其实可以往下延伸不少。想上实时操作系统的,可以把采集、识别、显示拆成三个FreeRTOS任务,F103跑FreeRTOS没有压力,但要注意临界区保护FIFO读取和显示缓冲区。想做道闸联动的,可以用TIM2的PWM输出驱动舵机,占空比更新用DMA,PA1和PA3正好是TIM2的通道。想记录历史数据的,识别结果可以用内部Flash模拟EEPROM,掉电保存最近几十组车牌。低功耗场景下,识别完一帧后可以进入停机模式,靠外部中断唤醒再采下一帧。这些方向都能作为毕设亮点写进论文。

最后提醒一句,毕设演示一定要准备一个固定脚本:灯光、距离、车牌朝向全部固定。图像类项目最怕现场环境变化,同一套代码在调试台上识别率90%,到了答辩教室打光一变可能只剩50%。先把最稳定的主场景调试到完美,再考虑适应不同环境,这是我做完这个项目最深刻的体会。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询