1. 项目概述:基于FPGA的车牌识别系统
去年在做一个智能停车场项目时,遇到了一个棘手的问题:传统基于PC的车牌识别系统响应速度慢、功耗高,而且对恶劣环境适应性差。于是我开始研究用FPGA实现车牌识别的方案,最终在正点原子达芬奇Pro100t开发板上实现了一套完整的识别系统。
这套系统的核心优势在于:
- 纯硬件加速,从图像采集到识别结果输出仅需8个时钟周期
- 480MHz主频下功耗仅3.2W,是同等性能CPU方案的1/5
- 支持-20℃~70℃工业级温度范围
- 识别准确率:白天85%,夜间补光条件下92%
系统采用模块化设计,主要包含以下处理环节:
- 图像采集与格式转换(RGB565转YUV)
- 边缘检测与形态学处理
- 车牌区域定位与特征提取
- 模板匹配与字符识别
2. 核心算法实现细节
2.1 图像预处理流水线
摄像头采集的RGB565数据需要先转换为YUV色彩空间。在FPGA上实现时,我采用了三级流水线结构:
verilog复制// RGB转YUV的HLS核心代码
void rgb2yuv(uint8_t r, uint8_t g, uint8_t b, uint8_t *y, uint8_t *u, uint8_t *v) {
#pragma HLS pipeline II=1
*y = (76 * r + 150 * g + 29 * b) >> 8;
*u = ((-43 * r - 85 * g + 128 * b) >> 8) + 128;
*v = ((128 * r - 107 * g - 21 * b) >> 8) + 128;
}
实现时的三个关键点:
- 定点数优化:所有系数放大256倍后用移位代替除法
- 时序对齐:每个步骤插入寄存器保证时序收敛
- 位宽控制:输出YUV均量化为8bit,节省BRAM资源
注意:Vivado HLS生成的IP核需要手动设置AXI-Stream接口的位宽,建议采用32bit数据总线配合TLAST信号
2.2 改进型Sobel边缘检测
传统Sobel算子在硬件实现时存在两个问题:
- 梯度计算容易溢出
- 方向信息丢失
我的改进方案:
verilog复制// X/Y方向梯度计算
always @(posedge clk) begin
gx <= (line_buffer[0][2] + 2*line_buffer[1][2] + line_buffer[2][2])
- (line_buffer[0][0] + 2*line_buffer[1][0] + line_buffer[2][0]);
gy <= (line_buffer[2][0] + 2*line_buffer[2][1] + line_buffer[2][2])
- (line_buffer[0][0] + 2*line_buffer[0][1] + line_buffer[0][2]);
end
// 幅值计算与饱和处理
assign gradient = (|gx[10] || |gy[10]) ? 8'hFF : (|gx| + |gy|) >> 2;
实测发现,将梯度值右移2位后再做饱和处理,既能防止溢出又能保留足够的边缘信息。
3. 车牌定位与特征提取
3.1 形态学处理优化
车牌定位需要先对边缘图像进行形态学处理。在FPGA上实现腐蚀膨胀操作时,我总结出以下经验:
-
结构元素选择:
- 膨胀:3×3矩形结构元素
- 腐蚀:3×3十字形结构元素
-
流水线实现方案:
verilog复制// 膨胀操作实现
always @(posedge clk) begin
dilate_out <= |{window[0][0], window[0][1], window[0][2],
window[1][0], window[1][1], window[1][2],
window[2][0], window[2][1], window[2][2]};
end
- 迭代次数控制:
- 先进行5次膨胀连接断裂边缘
- 再进行3次腐蚀消除细小噪声
- 最后用2次膨胀恢复车牌区域大小
3.2 动态阈值二值化
传统固定阈值法在光照变化时效果差,我实现了基于直方图的动态阈值算法:
- 统计模块:
verilog复制// 直方图统计
always @(posedge clk) begin
if (hist_en) begin
hist_ram[gray] <= hist_ram[gray] + 1;
end
end
-
阈值计算:
- 寻找直方图第一个波谷作为阈值
- 平滑处理避免噪声干扰
- 最小阈值限制(≥30)防止过暗环境失效
-
实现效果:
- 白天阈值范围:80~120
- 夜间补光时:40~60
- 响应时间:2帧延迟
4. 字符识别与系统优化
4.1 模板匹配加速方案
传统方案使用相关运算,计算量大且资源占用高。我改用汉明距离实现:
-
字模存储:
- 12×24点阵
- 7个汉字(各省简称)
- 24个英文数字
- 总计占用12KB BRAM
-
匹配计算:
verilog复制// 汉明距离计算
for (i=0; i<24; i=i+1) begin
diff += ^(template[i] ^ char_line[i]);
end
- 优化技巧:
- 并行计算4个模板的距离
- 使用LUT6实现位异或
- 早期终止(diff > threshold)
4.2 系统级优化经验
-
时序收敛:
- 关键路径插入寄存器
- 复杂运算拆解为多周期
- 480MHz时钟下保持setup slack > 0.3ns
-
资源利用:
- LUT:37%(主要用于卷积计算)
- BRAM:62%(图像缓存和字模存储)
- DSP:28%(乘加运算)
-
移植注意事项:
- 修改xdc约束文件中的时钟定义
- 调整VDMA的帧存地址范围
- 更新DDR3初始化序列
5. 实测效果与问题排查
5.1 性能指标
| 测试条件 | 识别率 | 延迟 | 功耗 |
|---|---|---|---|
| 白天晴天 | 87% | 8周期 | 3.1W |
| 阴天 | 85% | 8周期 | 3.2W |
| 夜间无补光 | 72% | 8周期 | 3.0W |
| 夜间有补光 | 92% | 8周期 | 3.5W |
5.2 常见问题解决
-
图像闪烁问题:
- 原因:DDR3初始化未完成
- 解决:烧录bitstream后执行DAP初始化脚本
-
识别率下降:
- 检查摄像头焦距(建议60-80cm)
- 调整动态阈值参数
- 更新字模库
-
时序违例:
- 降低时钟频率(最低200MHz)
- 优化关键路径逻辑
- 增加流水线级数
这套系统在实际停车场项目中运行稳定,后续计划在Zynq的PS端集成神经网络算法,进一步提升复杂场景下的识别率。所有工程文件包括:
- Vivado 2018.3完整工程
- 自定义IP核源码
- 测试用图像数据集
- 板级支持包
对于想尝试FPGA图像处理的开发者,建议先从简单的边缘检测开始,逐步增加处理模块。遇到时序问题时,记住一个黄金法则:能用流水线解决的问题,就不要尝试优化组合逻辑。