返回项目列表

FPGA / LeNet / OV5640 / HDMI / EQ6H103P

基于 FPGA 的实时手写数字识别系统移植与优化

项目基于 LulinChen 的 cnn_open / LeNet 结构 github.com/lulinchen/cnn_open/tree/master 进行工程化移植,将原有串口接收识别模式重构为 OV5640 视频流实时识别系统,并完成 Vivado / Xilinx 工程到 eLinx EQ6H103P 平台的适配。

OV5640 实时摄像头输入,输出像素时钟、同步信号与 RGB 数据
SDRAM 视频帧缓存,衔接采集、显示与识别链路
32x32 标准化与居中后的 LeNet 最终输入尺寸
UART 导出 FPGA 实际 CNN 输入,形成软硬件调试闭环

System Diagram

系统总体框图

重新按实际硬件数据流梳理:摄像头写入 SDRAM,识别链路从 SDRAM 读取 ROI,完成灰度、极性、下采样和居中后写入 src_buf,随后 LeNet 才开始推理。点击任意模块可跳转到下方对应技术细节。

OV5640 -> SDRAM -> ROI / Normalize -> src_buf -> LeNet -> HDMI / UART

主链路和调试闭环分开表达,避免“标准化之后又回到 LeNet 前面”的方向误读。

FPGA Real-time SDRAM Frame Buffer Verilog Python Golden Model
OV5640 采集
SDRAM 帧缓存
ROI / Gray 裁剪与灰度
Normalize 极性 / 下采样 / 居中
src_buf 32x32 输入缓存
LeNet 定点 CNN 推理
HDMI / UART 显示与验证
01

视频采集

Camera Capture

02

帧缓存

Video Buffer

03

图像预处理

Gray / Polarity / Normalize

04

CNN 推理

src_buf / LeNet

05

输出与验证

HDMI / UART / Python

Technical Details

技术细节

视频采集链路

OV5640 负责提供实时图像流,cam_capture 对像素同步、有效区域和数据节拍做整理。采集链路只负责提供稳定图像源,不直接喂给 LeNet。

SDRAM 帧缓存

SDRAM 位于 OV5640 与后续显示 / 识别链路之间。它吸收摄像头写入和 HDMI、ROI 裁剪读取之间的节拍差异,是从静态输入改造为实时视频系统的关键桥梁。

识别框与 ROI 读取

HDMI 链路负责实时显示和识别框绘制。识别区域从 SDRAM 读出的当前帧中截取,随后进入灰度、极性和尺寸标准化,而不是直接进入 CNN。

灰度、极性与对比度

早期识别异常的核心原因之一,是输入图像可能出现白底黑字或黑底白字不统一。系统通过背景估计、阈值去噪和增益拉伸,将最终输入统一为黑底亮字。

64x64 识别框与下采样

直接使用 32x32 识别框时,摄像头画面容易丢失笔画细节。改为 64x64 后先捕获更完整的数字,再用 2x2 平均下采样送入 LeNet,兼顾容错与原模型输入尺寸。

重心居中

LeNet 对输入位置偏移较敏感。mnist_recenter32 在写入 src_buf 前对数字重心做标准化,减少手写位置偏差带来的误判,尤其有助于 6、9、0、8 这类结构相近数字。

src_buf 最终输入缓存

src_buf 是标准化和居中之后的唯一 CNN 输入源。UART dump、Python golden model 和 LeNet 读取路径都围绕这块最终缓存对齐,避免出现“导出图像正确但 CNN 读到另一路 RAM”的问题。

LeNet 定点推理核心

CNN 侧使用 int16 权重、int24 bias 与 Q15 右移近似硬件定点行为。层间启动从简单 ready 信号改为真实 write_done 脉冲推进,保证下一层只在上一层输出写完后启动。

跨时钟域启动

标准化模块完成写入后产生 norm_ready,再通过 go_CDC_go 同步到 CNN 时钟域。这样 CNN 不会在 32x32 图像尚未写完时提前读取 src_buf。

HDMI 与 OSD 输出

HDMI 链路承担实时画面显示,并通过 digit_osd 与 draw_rectangle 叠加识别框、分类结果和调试状态,使工程从单纯推理模块变成完整可交互系统。

UART 调试通道

UART 用于导出 FPGA 实际 CNN 输入图像,配合 PC 端脚本复原 32x32 灰度图。它让调试从猜测分类结果,转为逐帧检查输入质量和硬件路径一致性。

Python Golden Model

Python 版本不是普通浮点网络,而是尽量模拟 FPGA 内部定点计算、截位、卷积与池化流程。它用于区分问题来自图像预处理、层间时序还是 CNN 前向计算本身。

Modules

硬件与工程组成

cam_capture

摄像头输入采集、像素同步与有效区域输出。

sdram_ctrl

视频帧缓存,衔接 OV5640、识别链路与 HDMI。

hdmi_ctrl

视频主控、OSD 叠加、识别框绘制和 UART 调试协调。

capture_lenet / capture_lenet_scaled2x

截取识别区域,并组织 64x64 到 32x32 的 CNN 输入图像。

mnist_recenter32

32x32 图像位置标准化与重心调整。

src_buf

标准化后最终 CNN 输入缓存。

lenet / cnn.v

LeNet 顶层控制、卷积、池化、ReLU、MAC 与层间启动时序。

lenet_roms / ram_blocks

权重 ROM、自定义 RAM 与 eLinx 平台适配。

go_CDC_go

跨时钟域启动脉冲同步。

digit_osd / draw_rectangle

显示识别框与最终分类结果。

Debugging

关键问题与调试闭环

1

灰度极性不统一

通过四角背景估计、自动极性判断、阈值去噪和增益拉伸,将输入统一为黑底亮字。

2

CNN 层间启动过早

把层间推进条件从 iterator.ready 改为真实 write_done 脉冲,避免下一层读取未写完的输出。

3

标准化后启动时机错误

将 CNN 启动源从 capture_ready 改为 norm_ready,并通过 CDC 同步到 CNN 时钟域。

4

src_buf 连接错误

统一串口 dump、Python 验证与 LeNet 实际读取的 RAM,解决图像正常但分类固定的问题。

Optimization

输入标准化与 6 / 9 分析

已验证有效的优化

  • 64x64 识别框 -> 2x2 平均下采样 -> 32x32 CNN 输入。
  • 自动极性判断,统一黑底亮字输入。
  • soft recenter 与目标居中处理。
  • 提高阈值并削弱弱连接对闭环数字的干扰。

6 / 9 仍较困难的原因

  • 结构依赖闭环和竖笔,形态变化更敏感。
  • LeNet 对位置和重心偏移较敏感。
  • MNIST 训练分布与摄像头输入分布存在差异。
  • 闭环过强时容易向 0 或 8 混淆。

Result

项目成果

完成 LeNet 手写数字识别网络到 EQ6H103P FPGA 工程的移植。
以自定义 RAM / ROM 结构替代原工程依赖的特定 IP。
实现 OV5640 -> SDRAM -> 预处理 -> src_buf -> LeNet -> HDMI / UART 的实时链路。
建立 Python 硬件级 CNN 验证工具,形成可复现的调试闭环。
完成 64x64 识别框、32x32 下采样、极性归一化和重心居中优化。
实现实时识别框显示和数字分类结果叠加输出。

Future Work

后续优化方向

当前系统已经具备较完整的实时识别能力,也建立了软硬件对齐的调试基础。后续重点可放在 6 / 9 专项测试、FPGA logits dump、多位置推理投票,以及面向摄像头输入分布的轻量微调。

回到框图