news 2026/10/15 15:26:44

STM32F4驱动无晶振无FIFO OV7670:DCMI实现车牌识别与颜色识别

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F4驱动无晶振无FIFO OV7670:DCMI实现车牌识别与颜色识别

做这个项目的时候,我手头只有一块最普通、不带晶振也不带FIFO的OV7670模块。网上一搜,教程基本都是"带FIFO的OV7670怎么用",因为FIFO版本接51或者STM32F1都方便,很多人默认"没FIFO的STM32玩不了摄像头"。但实际上,STM32F4自带DCMI数字摄像头接口,PA8还能通过MCO输出时钟给摄像头,模块上省掉的那颗晶振和AL422B FIFO,MCU自己全都能补回来。

这套组合最后跑出来的效果是:基于STM32F4驱动OV7670(无晶振、无FIFO版本,OV7725、OV2640类似可用),在裸机环境下完成车牌识别、颜色识别两大功能。图像采集用DCMI+DMA,车牌定位用颜色特征提取候选区域,颜色识别走HSL判断,字符识别用模板匹配。整个过程没有用Linux、没有用树莓派、没有外挂视觉模块,一颗STM32F407加上一个几十块的摄像头就够了。如果你正在做智能车相关任务、嵌入式课程设计里的"模拟停车场管理",或者单纯想搞清楚DCMI到底怎么用,这篇内容可以直接当参考线来用。

1. 无晶振无FIFO的OV7670:省掉硬件后要用MCU补什么

1.1 三种OV7670模块的真实差别

市面上的OV7670模块大致分三种,很多人买的时候没注意,拿到手才发现引脚和教程对不上。

第一种是带晶振带FIFO的。模块上有一颗24MHz晶振,还有一颗AL422B FIFO芯片,MCU不需要DCMI接口,直接用普通IO模拟时序就能把FIFO里的数据读出来。这种模块适合51、STM32F1这类没有摄像头接口的芯片,但是价格贵、板子大,而且FIFO读数据的速度也限制帧率。

第二种是带晶振不带FIFO。晶振提供时钟,DCMI接口直接采像素,比FIFO方案快很多,但要求MCU必须有DCMI外设。

第三种就是标题里的无晶振无FIFO版本。板子上没有24MHz晶振,也没有FIFO,通常只有一个非常小的感光板。它所有的时钟都要由外部MCU提供,数据直接通过DVP并行口输出。好处是便宜、体积小、接线简单,坏处是很多人不知道XCLK从哪来。

三种模块的对比,一张表说清楚:

模块类型时钟来源数据读取方式适合的MCU价格
带晶振+FIFO板载晶振IO模拟/SPI读FIFO51、STM32F1等偏高
带晶振无FIFO板载晶振DCMI直接采集带DCMI接口的芯片中等
无晶振无FIFOMCU输出XCLKDCMI直接采集带DCMI+MCO的芯片最低

我手里的这款就是第三种。说实话,它本来就是给带DCMI的MCU准备的,STM32F407完全对口。

1.2 STM32F4的DCMI和MCO刚好把坑填上

DCMI是什么?它是STM32F4系列上的数字摄像头接口,支持8位并行数据输入,带VSYNC、HSYNC、PIXCLK三个同步信号,硬件上能直接对接OV7670这类DVP接口的传感器。数据进来之后可以自动打包成32位字,配合DMA直接送到内存,CPU只在中断里处理完整帧,不用一条一条读像素。

MCO是MCU的时钟输出引脚。STM32F407的PA8可以复用为MCO1,把芯片内部的HSE、PLL等时钟源输出给外部设备。我就把PA8配成MCO1输出24MHz,直接接到OV7670的XCLK引脚上,摄像头就有了主时钟。

这个"MCU补模块"的思路其实是整个项目最核心的一步:无晶振版本不是缺东西,而是把时钟源的控制权交给了MCU,你反而可以精确控制摄像头的工作频率,比固定晶振还要灵活。无FIFO版本同理,DMA代替了FIFO的缓存功能,数据不经过额外的存储芯片,直接进内存,链路更短、延迟更低。

2. 上电到出图:SCCB配置与时钟的每一步

2.1 引脚与基准时钟:先让摄像头"转"起来

先把硬件接线定下来。OV7670无晶振版的引脚不多,常用的连接方式如下:

  • XCLK(外部时钟):接PA8,STM32F407的MCO1输出24MHz
  • D0-D7(数据线):接PE4、PE5、PE6、PE7、PE8、PE9、PE10、PE11
  • PCLK(像素时钟):接PA6
  • VSYNC(帧同步):接PB7
  • HREF(行有效):接PA4
  • SDA、SCL:接PB9、PB8
  • PWDN:直接接地,保证摄像头处于工作模式
  • RESET:接一个IO口或直接接高电平,上电后拉低再拉高复位一次

这里要特别提醒:PE4到PE11这组引脚在部分封装的STM32F4上不一定全能复用为DCMI,换用其它引脚时要注意查复用表。另外老生常谈,D0-D7别接反,接反了图像颜色和位置全会乱掉,而且是那种看起来"好像有图像但完全不对"的效果,排查起来很费时间。

时钟配置我建议这样:系统时钟168MHz,PLLQ或MCO1分频后让PA8输出24MHz。有的初始化代码会让MCO1输出8MHz,也能出图,但帧率低不少,颜色采样也容易不稳。OV7670的XCLK最大跑到48MHz没问题,但24MHz是稳定性和帧率都比较平衡的点。

2.2 关键寄存器与测试彩条

OV7670的初始化是通过SCCB总线写寄存器完成的。SCCB协议基本兼容I2C,STM32F4直接拿I2C外设当SCCB用就行,摄像头地址是0x21(写地址0x42)。

初始化数组网上能找到很多,但真正决定能不能出图的核心寄存器就那几个。

COM7(地址0x12)要先写0x80,进入SCCB寄存器复位,让摄像头所有寄存器恢复到默认值。然后写COM15(地址0x40)为0x10,选择RGB565输出格式。再确认COM7从0x80改到0x00,退出彩条/复位状态,切换到正常RGB输出模式。

最实用的验证方法是写0x12 = 0x80,让摄像头输出内置测试彩条。这个模式下不需要镜头对准任何物体,图像上直接是一组标准色条。如果DCMI采集出来的彩条颜色顺序和位置都正常,说明数据通路、同步时序全都没问题,之后再做镜头实拍才有意义。

我不管换什么摄像头模块,第一件事永远是看彩条。彩条都不对,其他都白搭。

2.3 无晶振版本最容易翻车的三个细节

第一个坑是内部PLL。OV7670有内部PLL倍频功能,相关寄存器在0x6B附近,24MHz输入时钟下可以倍频到更高像素时钟。但无晶振版本接外部24MHz时,我不建议开PLL,或者只开很低倍率。PCLK一旦跑到40MHz以上,杜邦线稍微长一点就会引入串扰,DCMI采出来的图像会出现随机噪点和行错位。宁可把PCLK降下来,保证像素数据稳定,也不要追求虚高的像素时钟。想降时钟就直接写CLKRC寄存器(0x11),分频关系大约是PCLK = XCLK / (CLKRC + 1),我一般把它设在2到4分频,让PCLK控制在12MHz上下。

第二个坑是PWDN引脚。有些模块的PWDN默认通过电阻上拉,导致摄像头一直处于掉电模式,SCCB怎么写都没反应。上电第一件事就是把PWDN拉低,确认摄像头从掉电状态醒过来。

第三个坑是HSYNC和VSYNC极性。OV7670的VSYNC默认高电平有效,HREF高电平有效,但DCMI的极性配置如果和摄像头实际时序相反,会导致图像整帧错乱、上下颠倒或完全黑屏。CubeMX里把这两个极性都配置成Active High试一次,如果不行再换Active Low试,这个组合很影响出图稳定。

3. 图像采集链路走通:DCMI+DMA不掉帧

3.1 DCMI同步模式怎么配

OV7670输出的是8位并行数据,RGB565格式下,一个像素的高字节和低字节是连续两个字节通过同一组D0-D7线送出来的。所以DCMI要配成8位数据宽度,然后在内存里把连续两个字节拼成一个16位的RGB565像素。

采集分辨率我直接用的320x240,也就是QVGA。这个分辨率下,一帧RGB565数据量是320x240x2 = 153600字节,也就是150KB,放在STM32F407的192KB SRAM里勉强能装下。如果上640x480,一帧就是307200字节,192KB直接爆掉,所以裸机下老老实实QVGA。

CubeMX里DCMI配置要点:

  • 接口配置为同步8位数据模式
  • VSYNC极性、HSYNC极性按前面测试结果设置
  • 像素时钟采样沿先试上升沿,出图不对就换下降沿
  • 打开DCMI的VSYNC中断和行同步中断(后者可以关掉,省得频繁进中断)

3.2 乒乓缓冲与中断

DCMI的数据直接通过DMA送到内存,最简单高效的做法是配置DMA循环模式+双缓冲(乒乓缓冲)。两个缓冲区交替接收帧数据,当一个缓冲区写满时,DMA自动切到另一个缓冲区,同时触发传输完成中断,CPU在中断里处理已经写满的那个缓冲区,而DMA继续往另一个缓冲区写当前帧。

这里有个408上的经典约束:一帧RGB565是150KB,两个缓冲就是300KB,F407的192KB SRAM扛不住。所以我的做法是采集阶段只用单缓冲,或者把分辨率临时降到160x120做双缓冲。实际操作中,单缓冲+DMA循环模式完全够用,只要保证CPU在下一帧传输完成之前把当前帧处理完就行。

中断处理建议做三层分工:

  • DMA传输完成中断:置一个"帧就绪"标志位
  • 主循环检测到标志位后,立刻开始处理当前缓冲区的图像数据
  • 处理完后再清标志位,等待下一帧

这样比在中断里直接跑算法要安全,避免中断嵌套和长时间中断阻塞DMA。

3.3 花屏和错位图像怎么排查

图像采集链路一旦出问题,表现千奇百怪。我踩过几次,整理一个排查顺序,按这个顺序查基本能定位。

第一,花屏或整片噪点。先排除PCLK采样沿问题,把上升沿改成下降沿试一下。还不行就把CLKRC分频加大,降低PCLK。

第二,图像整体偏色或者颜色分块错乱。先查D0-D7的接线顺序,再查RGB565字节序。OV7670输出的RGB565通常是低字节在前,DCMI打包进内存后,如果字节序反了,颜色会明显错位,比如红色变成蓝色,或者画面呈紫绿相间。

第三,画面上下颠倒或左右镜像。摄像头安装方向不同会造成这种情况,可以在软件里做翻转,也可以调整镜头安装方向。OV7670的寄存器里也有镜像控制位,但我在这个项目里直接软件翻转了,省事。

第四,图像有规律地错开一行,像梳子齿一样。这就是HSYNC极性配置错了,或者HREF行信号不稳定,去查DCMI的HSYNC极性。

4. 车牌在哪?先做颜色特征定位再做字符识别

4.1 从RGB565到二值掩码

拿到一帧图像之后,第一步不是直接分割字符,而是先确定"图像里哪块区域是车牌"。嵌入式系统算力有限,不可能像PC上那样跑复杂的边缘检测加卷积神经网络,但车牌本身有非常强的颜色特征,这个特征在裸机上就可以利用得很充分。

国内常见的车牌中,蓝底白字占比最大,新能源车牌是绿底黑字,大型车有黄底黑字。只要先锁定底色区域,就能大幅缩小搜索范围。

具体做法是把RGB565拆成R、G、B三个8位分量,然后逐像素判断是不是目标颜色。以蓝色为例,一个像素同时满足"蓝色分量明显高于红色分量"和"蓝色分量明显高于绿色分量",并且蓝色分量本身大于某个亮度下限,就标记为蓝色候选像素:

uint8_t r = (rgb >> 11) & 0x1F; uint8_t g = (rgb >> 5) & 0x3F; uint8_t b = rgb & 0x1F; r = (r << 3) | (r >> 2); g = (g << 2) | (g >> 1); b = (b << 3) | (b >> 2); int is_blue = (b > 60) && (b > r + 30) && (b > g + 20);

这样一帧图像就变成了一张二值掩码图,1代表"疑似蓝色车牌像素",0代表背景。这张掩码图不用保留整帧,原地覆盖原缓冲就可以,每一帧只增加很少的内存开销。

4.2 连通域和几何筛选

有了二值掩码之后,就需要把散落的蓝色像素归并成一个个"块",这一步叫连通域标记。我用的方法是扫描线标记法,对每一行找出连续的前景段,然后与上一行的前景段做重叠判断,重叠就归并成同一个连通域,同时更新外接矩形。这个方法比逐像素BFS快很多,适合在单片机上跑。

每个连通域会得到一个外接矩形,接下来做几何筛选,规则我写得比较简单:

  • 宽度大于40像素,高度大于15像素,排除太小的噪点块
  • 宽高比大致在2到4.5之间,蓝牌标准宽高比约3.16比1,黄牌约4比1,留出倾斜余量
  • 面积占整个连通域的比例超过60%,排除细长条干扰

筛选之后,剩下的矩形基本就是车牌的候选区域了。如果画面里同时出现好几辆车的车牌,算法会输出多个候选框,这时取面积最大的那个作为主要识别目标。

我实测下来,在校园路边拍摄的环境中,这个基于颜色特征的定位方法对蓝色车牌的检出率能到八到九成,主要误检来自蓝色建筑外墙贴纸、蓝色路牌这类物体。但因为我们后续还有几何比例过滤,大部分干扰会被滤掉。

4.3 定位结果不理想时先调什么

定位不理想最典型的两种情况:

一种是车牌区域时有时无,不稳定。通常不是算法问题,而是曝光和自动白平衡在来回跳。OV7670默认的自动曝光会随着场景亮暗变化不断调整,导致同一块蓝色在帧与帧之间的颜色值不一致。我做了个很土但有效的处理:固定曝光寄存器,关掉自动增益调节,让摄像头在一个固定的曝光参数下工作。量变引起质变,颜色特征的稳定性立刻就上来了。

另一种是找到的区域太大或太小,把车体的蓝色漆面也框进来了。这时要回头调整颜色阈值的严格程度,让"蓝色分量是否明显超过其它分量"这个条件更苛刻。把红色差值的下限从30提高到40或50,很多浅蓝色干扰就能滤掉。

5. 颜色识别:把整个流程中最稳的部分做好

5.1 为什么选HSL而不是RGB硬阈值

定位车牌用的是RGB阈值快速判断,但真正做颜色识别的时候,我不建议直接用RGB空间的硬阈值。原因很简单:RGB三个分量高度相关,亮度一变,三个值会一起变,单靠比较大小关系容易把深红和黑色、浅蓝和白色搞混。

HSL(色相、饱和度、亮度)把颜色分离成了"是什么颜色"(色相H)和"有多鲜艳"(饱和度S)以及"有多亮"(亮度L),对光照变化更鲁棒。一个对象的色相值在光照变化时相对稳定,这正是颜色识别需要的性质。

5.2 阈值表与快速实现

单片机上完整计算HSL要开浮点三角函数,不划算。我做了一个近似实现:用整数运算先把R、G、B归一化,然后按最大分量和最小分量的差值来判断色相区间。判断逻辑是:

  • 如果max和min的差很小,说明是灰色,不看色相,直接按亮度归为白色或黑色
  • 如果差值够大,就看哪个分量是最大。红色主导对应红/橙区间,绿色主导对应绿区间,蓝色主导对应蓝区间,红和绿同时高对应黄色区间

一张表总结我实际用的颜色判断规则:

目标颜色判断条件(简化)
红色R明显最高,且R-G、R-B差值大,亮度适中
绿色G明显最高,且G-R差值大
蓝色B明显最高,且B-G差值大
黄色R和G都高且接近,B明显低
白色R、G、B都接近且都较高
黑色R、G、B都接近且都较低

实际测试发现,这套规则在OV7670的默认色彩风格下挺准。识别时我会统计候选区域里所有像素的颜色投票,比如一个区域里40%像素判为蓝色、30%判为白色,那这个区域就报告为蓝色。用投票而不是单个像素判断,能有效抵抗个别像素的噪声。

5.3 白平衡和曝光对颜色判定的影响

颜色识别最容易翻车的就是白平衡。OV7670默认的自动白平衡会让画面整体色调在不同光源下来回变化,上午在日光下偏暖,傍晚在路灯下偏黄,同一张蓝牌可能被识别成蓝或紫。

我的处理方式是固定白平衡。在初始化序列里把自动白平衡相关寄存器关掉,或直接选择室内/日光预设值,然后整个项目都用同一套参数。代价是换了环境之后颜色会有点偏,但对固定在某个场地运行的比赛或教学场景来说,可重复性比自适应更重要。

如果一定要自适应,也有个简单办法:取画面最亮区域做白参考,把整个图像的通道增益调整到白色区域R=G=B,再做颜色判断。但这个操作在STM32上逐像素处理会有额外开销,自己评估是否值得。

6. 字符分割与识别:把车牌读出来

6.1 倾斜校正与投影分割

定位到车牌区域后,如果车牌在图像里有点倾斜,直接分割字符容易失败。对固定场景来说,比如模拟停车场入口的识别,车牌高度位置基本固定,倾斜一般不会太大。我采取的做法是做个简单的水平投影校正:统计车牌区域每一行的前景像素数,找到字符主体所在的行范围,如果上下行的分布不对称,就做一点旋转校正。

在STM32上做完整的仿射变换太慢,我这里的"倾斜校正"其实很轻量:用水平投影找到字符行覆盖的区间,再根据垂直方向上的投影偏移估算倾斜角,然后只在字符分割时按偏移量做列对齐。角度超过10度的情况基本就放弃这帧了,毕竟环境是可控的,不值得为极端角度消耗算力。

字符分割用的经典垂直投影法。先把定位区域内的图像转成二值图,蓝底白字车牌中,白色字符作为前景。逐列统计前景像素数,得到垂直投影曲线。字符和字符之间会出现明显的低谷(空白列),根据低谷把字符切分出来。

分割完要做的校验是字符宽度一致性。车牌字符宽度大致均匀,如果某个切出来的块宽度只有其它字符的一半,多半是字符内部断裂或者边框干扰,我会把它和相邻块合并重切。边框、铆钉和牌照圆点会造成大量伪字符,它们通常出现在投影曲线的两端,直接裁掉边缘几列能省很多麻烦。

6.2 模板匹配识别流程

字符分割出来后,每个字符都要归一化到统一尺寸。我用的尺寸是32x64,因为模板库里就按这个尺寸存。

模板匹配的原理很直接:把待识别字符和模板库里的每一个模板做逐像素比较,统计相同位置的像素有多少是一致的,一致比例最高的模板就是识别结果。

uint32_t score = 0; for (int i = 0; i < 32 * 64; i++) { if (char_pixel[i] == template_pixel[i]) score++; } // 相似度 = score / (32 * 64)

这个方法最大的优点是简单、快速、不需要训练,缺点是模板选择非常影响识别率。我建模板库的方式是先打印一套标准车牌字符(数字、字母、以及项目需要用到的汉字),用同一个摄像头在同样的距离下拍摄,然后归一化存入Flash。注意,模板必须用和实际识别相同的摄像头、相同的光照条件来制作,否则摄像头色彩风格和分辨率差异会直接降低匹配分。

为了提高鲁棒性,我把模板里的字符像素做了加粗和细化两套变体,识别时同时比三套结果,取最高分。这一步把数字和字母的识别率拉到了比较可用的水平。

6.3 识别率的现实:汉字为什么难

说点现实的东西。模板匹配对数字和英文字母的识别率能够到90%以上,字体规范、笔画简单,模板数量也不多(数字10个,字母24个)。但汉字是另一回事。

汉字笔画多、结构复杂,而且不同车牌字体有差异,像"粤""苏""沪"这些笔画密集的汉字,32x64的归一化分辨率下很多笔画直接糊成一团。真要覆盖全国各省简称,模板数量得几十个起步,匹配耗时成倍增加,识别率却凄惨。

我在项目里做了个务实取舍:车牌第一位汉字做粗分类,只报告"疑似汉字"而不精确识别;重点识别后面6位字母数字,把这部分作为身份标识输出。颜色信息加上后6位字符,对大多数模拟场景来说已经足够区分不同车辆了。如果你一定要精确识别汉字,建议换用更大分辨率采集车牌区域,模板也扩展到48x96,但处理时间会明显变长,F407会吃力。

7. 实测性能、内存布局与调优记录

7.1 192KB SRAM怎么分才够用

STM32F407的192KB SRAM看着挺大,但摄像头一接进来就紧巴巴了。320x240x2字节的一帧完整图像需要150KB,这一项几乎占掉全部SRAM。我的内存分配方案是:

  • 帧缓冲:150KB,放置在一个连续内存段里(DMA要求地址4字节对齐)
  • 二值掩码:不单独开,原地覆盖帧缓冲
  • 字符分割缓存:从帧缓冲的车牌区域里裁剪出小块,放到一个小数组(比如256字节)中
  • 模板Buffer:直接从Flash读取,不占RAM

逻辑很清楚:全程只在帧缓冲上做原地算法,不复制大块图像。定位到车牌后,字符区域很小,裁剪到小缓存就够了。

如果要做双缓冲,建议直接把分辨率降到160x120,一帧只有37.5KB,两块缓冲才75KB,余量很充足。代价是字符识别分辨率不够,只能在低分辨率下做颜色识别和车牌定位,字符识别单独切出来放大处理。

7.2 帧率瓶颈与隔帧处理

我的实测数据是这样的:320x240 RGB565采集,PCLK输出12MHz左右,DCMI+DMA采集本身可以达到20帧以上的能力,但一旦加上颜色定位和字符识别全套处理,单帧处理时间大约需要40到60毫秒,帧率就掉到15帧左右。如果再叠加LCD实时显示,还会更慢。

帧率的处理策略是隔帧处理。摄像头持续采集,DMA持续上传,但主循环每2到3帧才执行一次完整识别。这样做的好处是识别结果依然每秒能有5到10次的更新,视觉上完全够用,而且中间空出来的时间可以用来刷新LCD、处理串口通信等其它任务。

如果你要做运动中的车牌识别,即车辆在画面里持续移动,低帧率会导致画面之间车牌位置跳变,建议把分辨率降到240x160以下,缩短处理时间,把有效识别帧率拉上去。对模拟停车场入口这种车辆近乎停止的场景,15帧已经完全足够。

7.3 从OV7670换到OV7725/OV2640要注意什么

标题里写了OV7725、OV2640类似可用,我简单说下这三者的差别。

OV7725是OV7670的升级版,SCCB地址相同,也是0x21,但寄存器初始化序列完全不同,尤其是分辨率配置部分。OV7725的低照度表现比7670好不少,在光线暗的场景下,同样固定曝光参数,7725的画面噪点明显少,颜色也更稳。如果你项目环境偏暗,直接换7725是个低成本升级。DCMI那套采集代码不用改,只要换初始化数组。

OV2640是另一个常见型号,硬件接口兼容DVP,初始化序列也不一样,而且它更常用的模式是输出JPEG。这个问题要特别注意:如果初始化数组没配好,OV2640会默认输出JPEG压缩数据,DCMI接到的不是RAW RGB像素,显示出来就是一大片乱码。换成OV2640时,必须确认初始化序列把输出格式配成RGB565,而不是JPEG。裸机MCU上老老实实用RGB565,别碰JPEG解码,F4解码VGA级别的JPEG既不快也不省心。

7.4 实测中遇到的误识别与修复

这个项目从能出图到最终稳定识别,中间经历了几个典型的误识别问题,我挑两个最有代表性的说。

第一个是蓝色车身的干扰。校园里停着一辆深蓝色轿车,车身颜色特征和车牌非常接近,连通域筛选时车身被整块框进来,宽高比也符合,导致把它当成车牌。最后的修复办法是加了一道"颜色纯度"约束:真正的车牌区域内部,蓝色像素占比非常高,且白色字符像素呈现有规律的条带分布,而车身区域颜色是渐变的,蓝色像素占比低但分布散。我在定位之后统计候选区域内蓝色像素占比,低于一定比例的直接丢弃,这一招把车身误检基本消灭了。

第二个是阴影造成的颜色漂移。车牌一半在阳光下、一半在阴影里时,阴影部分的蓝色分量整体降低,会把这半个车牌误判成非蓝色区域。最初连通域被拆成两半,字符识别时一半能读一半读不了。解决办法是把颜色判断里的亮度下限从绝对阈值改成相对阈值,也就是说,不要求蓝色分量一定大于某个固定值,而是要求"蓝色分量在所有通道中占比最高且超过其它通道一定比例"。这样即使整体变暗,蓝色和红色、绿色的相对关系仍然能保持。

这两个问题修复后,整个识别流程在固定光照场景下已经可以稳定运行了。把颜色识别放在前面做、把字符识别放在后面做,这个顺序帮了大忙。因为颜色判断是在大区域上统计,容错率高,就算定位有偏差也能给出正确颜色;而字符识别依赖精确的二值化和分割,任何一步抖动都会影响结果。先把稳的部分做成高可靠输出,再去啃字符识别这个硬骨头,项目的推进节奏会舒服很多。

最后再分享一个小技巧:调试整个流程时,先把测试彩条跑通,再把识别算法放在PC上模拟验证,最后再烧进STM32。我自己是在PC上用同样的算法逻辑跑了一段真实图片,确认算法本身边界都在,才把代码移植到单片机上。这样排错范围能缩小一半,省掉很多在嵌入式环境里反复烧录的无效时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 15:25:31

栈溢出返回地址劫持的底层原理与可靠利用方法

1. 这不是“改个地址”那么简单&#xff1a;一次函数返回地址修改实验的真实分量看到标题里“【0day安全实验三】2.3修改函数返回地址”&#xff0c;很多人第一反应是&#xff1a;“哦&#xff0c;栈溢出基础操作&#xff0c;把retaddr覆盖成shellcode地址就行。”我第一次在某…

作者头像 李华
网站建设 2026/10/15 15:25:00

Python+OpenCV车牌识别实战:传统图像处理从定位到字符分割

简介&#xff1a;这是一套基于Python与OpenCV实现的车牌识别系统源码资源&#xff0c;面向计算机视觉入门者、课程设计或毕业设计需求的学生&#xff0c;以及希望了解车牌检测与识别完整流程的开发者。资源在原项目基础上扩展了摄像头实时识别功能&#xff0c;并优化了识别模块…

作者头像 李华
网站建设 2026/10/15 15:24:24

电脑技巧:用TaoToken统一管理多显示器工具链的配置实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 15:24:10

CGO深度实践:从类型映射、内存管理到跨语言调用的性能优化

很多人提到 CGO 第一反应是“这是个坑”。我当初在某个项目里被 CGO 折磨得欲哭无泪&#xff1a;内存崩溃、链接失败、交叉编译一堆报错。后来真正静下心&#xff0c;把环境、类型映射、内存管理和回调链路啃了一遍&#xff0c;才发现 CGO 本身并不复杂&#xff0c;复杂的是它强…

作者头像 李华
网站建设 2026/10/15 15:23:45

FPGA千兆以太网通信原理:MAC/PHY分工与RGMII接口设计要点

1. 千兆以太网&#xff1a;FPGA构建数据通路的基础概念1.1 这个课题到底在解决什么工程问题做FPGA的人早晚会碰上以太网。Artix 7这个级别的芯片&#xff0c;逻辑资源、Block RAM、高速收发器样样齐全&#xff0c;正适合做带通信接口的数据采集、图像传输、协议转换类项目。我接…

作者头像 李华