1. 项目概述与硬件选型
去年在参与一个智能停车场项目时,我首次接触到FPGA车牌识别系统的开发需求。经过多方对比,最终选择了正点原子达芬奇Artix-7开发板作为硬件平台,其搭载的XC7A35T FPGA芯片拥有33,280个逻辑单元和1,800Kb Block RAM,完全能满足实时图像处理的需求。这块开发板最大的优势在于其丰富的外设接口——我们通过板载的DVP接口连接OV5640摄像头,使用RGB接口驱动4.3寸LCD显示屏,整个硬件搭建过程不到半小时。
选择Artix-7系列主要基于三点考量:首先是其28nm工艺带来的功耗优势(实测全速运行功耗仅2.3W),其次是内置的DSP48E1切片能高效处理图像算法,最重要的是正点原子提供的完整外设驱动库极大降低了开发门槛。这里特别提醒新手:XC7A35T的Bank电压有1.8V和3.3V两种配置,连接OV5640时必须确保摄像头接口Bank电压设置为3.3V,否则会出现信号兼容性问题。
2. 图像处理流水线设计
2.1 图像采集模块实现
OV5640摄像头的配置是第一个难点。这款500万像素的摄像头支持多种输出格式,我们选择RGB565模式而非YUV格式,主要考虑三点:一是减少格式转换环节,二是FPGA处理RGB数据更直接,三是LCD显示屏原生支持RGB接口。在Vivado中实现的采集模块核心代码如下:
verilog复制module ov5640_capture(
input pixel_clk, vsync, href,
input [7:0] pixel_data,
output reg [15:0] rgb_data,
output reg data_valid
);
reg [1:0] byte_cnt;
always @(posedge pixel_clk) begin
if(vsync) byte_cnt <= 0;
else if(href) begin
byte_cnt <= byte_cnt + 1;
if(byte_cnt == 0) rgb_data[15:8] <= pixel_data;
else if(byte_cnt == 1) begin
rgb_data[7:0] <= pixel_data;
data_valid <= 1'b1;
end
end else data_valid <= 1'b0;
end
endmodule
实际调试中发现两个关键点:1)必须严格遵循OV5640的I2C配置时序,建议使用正点原子提供的初始化配置文件;2)pixel_clk信号线长超过15cm时会出现信号衰减,需要加缓冲器或缩短走线距离。
2.2 色彩空间转换优化
RGB到YCbCr的转换采用ITU-R BT.601标准,但直接实现浮点运算会消耗大量DSP资源。我们通过定点数优化将公式转换为:
code复制Y = (77 *R + 150*G + 29 *B + 128) >> 8
Cb = (-43*R - 85 *G + 128*B + 128) >> 8
Cr = (128*R - 107*G - 21 *B + 128) >> 8
在Verilog中采用移位相加实现:
verilog复制wire [15:0] y_temp = (R<<6) + (R<<3) + (R<<2) + R // 77*R
+ (G<<7) + (G<<4) + (G<<1) // 150*G
+ (B<<4) + (B<<3) + (B<<2) + B; // 29*B
assign Y = (y_temp + 128) >> 8;
这种实现方式比直接乘法器节省60%的LUT资源。实测在100MHz时钟下,处理一帧800x480图像仅需3.8ms。
3. 车牌识别算法实现
3.1 边缘检测的硬件加速
Sobel算子采用分离式实现以节省资源:先进行3x1垂直卷积,再进行1x3水平卷积。我们设计了一个可配置的线缓冲器,仅存储两行图像数据:
verilog复制reg [7:0] line_buffer[0:1][0:799];
always @(posedge clk) begin
if(data_valid) begin
line_buffer[0][col] <= line_buffer[1][col];
line_buffer[1][col] <= Y_in;
if(row > 0 && col > 0) begin
// 垂直梯度
gy <= line_buffer[0][col] + (line_buffer[1][col]<<1)
+ line_buffer[2][col] - line_buffer[0][col-2]
- (line_buffer[1][col-2]<<1) - line_buffer[2][col-2];
// 水平梯度
gx <= line_buffer[0][col+1] + (line_buffer[0][col]<<1)
+ line_buffer[0][col-1] - line_buffer[2][col+1]
- (line_buffer[2][col]<<1) - line_buffer[2][col-1];
end
end
end
实测显示,这种实现方式比完整3x3卷积节省40%的Block RAM使用量。边缘检测后的二值化阈值建议设置在Y通道的0.3倍最大值附近。
3.2 形态学处理优化
腐蚀和膨胀操作采用3x3结构元素,但通过流水线设计将处理延迟降低到3个时钟周期:
verilog复制module morphology(
input clk, rst,
input [7:0] pixel_in,
output [7:0] pixel_out
);
reg [7:0] window[0:2][0:2];
always @(posedge clk) begin
// 窗口移位寄存器
window[0][2] <= window[0][1]; window[0][1] <= window[0][0];
window[1][2] <= window[1][1]; window[1][1] <= window[1][0];
window[2][2] <= window[2][1]; window[2][1] <= window[2][0];
window[0][0] <= pixel_in;
// 膨胀操作:取3x3窗口最大值
reg [7:0] max_val;
max_val = (window[0][0]>window[0][1]) ? window[0][0] : window[0][1];
// ... 比较所有9个像素
pixel_out <= max_val;
end
endmodule
特别注意:形态学处理会扩大边缘区域,建议先腐蚀后膨胀的组合(开运算)来消除噪声,车牌识别中典型迭代次数为2-3次。
4. 字符识别与系统集成
4.1 特征提取策略
车牌字符识别采用投影特征法,通过以下步骤实现:
- 垂直投影确定字符左右边界
- 水平投影确定字符上下位置
- 提取字符的7段特征(类似数码管显示)
- 与模板库进行汉明距离匹配
verilog复制module char_recognition(
input clk, rst,
input [7:0] bin_img,
output reg [3:0] char_code
);
// 投影统计
reg [10:0] h_proj[0:47], v_proj[0:15];
always @(posedge clk) begin
if(bin_img > threshold) begin
h_proj[row] <= h_proj[row] + 1;
v_proj[col] <= v_proj[col] + 1;
end
end
// 特征匹配
always @(*) begin
case({seg1,seg2,...,seg7})
7'b1110110: char_code = 4'd0; // 数字0
7'b0010010: char_code = 4'd1; // 数字1
// ... 其他字符模板
endcase
end
endmodule
实际测试表明,对于国内蓝牌车辆,这种方法的识别准确率可达92%以上。建议模板匹配时加入字符宽高比约束,能有效过滤非字符区域。
4.2 显示驱动优化
LCD显示驱动采用双缓冲机制以避免画面撕裂:
- 使用Block RAM构建两个480x800的帧缓冲区
- 当一帧图像处理完成时切换写指针
- 显示控制器始终读取非活跃缓冲区
verilog复制reg [18:0] write_addr;
reg [18:0] read_addr;
always @(posedge lcd_clk) begin
if(vsync) begin
buffer_sel <= ~buffer_sel;
write_addr <= 0;
end
if(lcd_de) begin
lcd_data <= (buffer_sel) ? buf1[read_addr] : buf2[read_addr];
read_addr <= read_addr + 1;
end
end
实测显示,这种设计可以将显示延迟控制在1帧以内(约16ms),同时避免读写冲突导致的图像异常。
5. Modelsim仿真技巧
5.1 自动化验证方法
构建分层测试平台:
- 图像源模块:从BMP文件读取测试图像
- 算法验证模块:用MATLAB生成预期结果
- 自动比对模块:统计误识别率
verilog复制initial begin
$readmemh("test_img.hex", img_rom);
for(i=0; i<480; i=i+1) begin
for(j=0; j<800; j=j+1) begin
@(posedge clk);
pixel_in = img_rom[i*800+j];
#10;
if(pixel_out !== matlab_out[i][j])
error_count = error_count + 1;
end
end
$display("Error rate: %f%%", 100.0*error_count/(480*800));
end
建议在仿真时加入随机噪声注入,测试算法的鲁棒性。例如在RGB数据中加入±10%的随机扰动。
5.2 性能分析技巧
通过Modelsim的波形测量功能可以精确统计各模块耗时:
- 使用$time记录关键路径时间戳
- 计算图像处理流水线的吞吐率
- 分析Block RAM的访问冲突情况
verilog复制real start_time, end_time;
initial begin
start_time = $realtime;
@(posedge frame_start);
end_time = $realtime;
$display("Frame process time: %fms", (end_time-start_time)*1000);
end
在优化过程中,我们发现Sobel运算占用了总处理时间的35%,通过增加流水线级数将其耗时降低了40%。
6. 实际部署经验
6.1 电源管理要点
现场部署时遇到的主要问题是电源噪声:
- 使用LT3045线性稳压器为FPGA内核供电(1.0V@3A)
- 每个电源引脚布置10μF钽电容+0.1μF陶瓷电容
- 摄像头单独供电并添加π型滤波电路
实测表明,这种电源方案可以将电源纹波控制在20mV以内,确保图像采集质量。
6.2 环境适应性处理
针对不同光照条件的解决方案:
- 自动曝光控制:通过I2C动态调整OV5640的曝光参数
- 动态阈值:根据图像直方图自动计算二值化阈值
- 多帧平均:在低照度环境下启用3帧平均降噪
verilog复制// 动态阈值计算
always @(posedge frame_done) begin
hist[Y_in] <= hist[Y_in] + 1;
if(pixel_count > 384000*3/4)
threshold <= gray_level;
end
这套系统在实测中实现了白天95%、夜间87%的识别准确率,完全满足停车场等场景的需求。
