1. 项目概述
最近在调试一个基于FPGA的车牌识别系统,用的是正点原子的达芬奇Artix-7开发板(FPGA型号XC7A35T),配合OV5640摄像头和4.3寸RGB LCD屏。整个项目从图像采集到最终结果显示,完整实现了车牌识别的全流程处理。这里分享下我的实现思路和踩过的坑,希望能给想做类似项目的朋友一些参考。
这个系统主要包含以下几个关键模块:
- 图像采集(OV5640摄像头驱动)
- RGB转Ycbcr色彩空间转换
- Sobel边缘检测
- 形态学处理(腐蚀膨胀)
- 特征提取与模板匹配
- LCD显示驱动
2. 硬件平台搭建
2.1 开发板选型考量
选择正点原子达芬奇Artix-7开发板主要基于以下几点考虑:
- XC7A35T芯片具有34K逻辑单元,足够处理800x480分辨率的图像
- 板载丰富的外设接口,包括摄像头接口和RGB LCD接口
- 正点原子提供了完善的例程和文档支持
- 性价比高,适合学习和项目开发
注意:实际使用时发现开发板的电源设计对摄像头供电不足,需要外接5V电源给OV5640供电,否则图像会出现噪点。
2.2 外设连接要点
摄像头和LCD的连接有几个关键点需要注意:
- OV5640的I2C配置接口要接开发板的PL端GPIO
- 像素时钟(PCLK)信号线要尽量短,避免信号完整性问题
- LCD的RGB接口时序要严格匹配显示屏规格书要求
- 所有高速信号线建议使用屏蔽线,减少电磁干扰
3. 图像处理流水线设计
3.1 图像采集模块实现
OV5640摄像头的驱动主要包含两部分:
- I2C配置:设置分辨率、帧率、曝光等参数
- 像素数据采集:处理VSYNC、HREF、PCLK等同步信号
verilog复制module ov5640_interface(
input wire clk_50m,
input wire rst_n,
// 摄像头接口
input wire cam_vsync,
input wire cam_href,
input wire cam_pclk,
input wire [7:0] cam_data,
// 图像输出
output reg [23:0] rgb_data,
output reg data_valid
);
// 状态机定义
localparam IDLE = 2'b00;
localparam WAIT_LINE = 2'b01;
localparam CAPTURE = 2'b10;
reg [1:0] state;
reg [10:0] pixel_cnt;
always @(posedge cam_pclk or negedge rst_n) begin
if(!rst_n) begin
state <= IDLE;
pixel_cnt <= 0;
rgb_data <= 24'd0;
data_valid <= 1'b0;
end else begin
case(state)
IDLE: begin
if(cam_vsync) state <= WAIT_LINE;
end
WAIT_LINE: begin
if(cam_href) begin
state <= CAPTURE;
pixel_cnt <= 0;
end
end
CAPTURE: begin
if(!cam_href) begin
state <= WAIT_LINE;
end else begin
// 拼接RGB565数据为RGB888
if(pixel_cnt[0])
rgb_data[15:8] <= cam_data;
else
rgb_data[7:0] <= cam_data;
if(pixel_cnt == 11'd1)
rgb_data[23:16] <= cam_data;
data_valid <= (pixel_cnt >= 2);
pixel_cnt <= pixel_cnt + 1;
end
end
endcase
end
end
endmodule
3.2 色彩空间转换优化
RGB转Ycbcr的定点数实现有几个优化点:
- 使用移位代替除法:>>8代替/256
- 预计算系数乘积,减少实时计算量
- 添加流水线寄存器提高时序性能
verilog复制module rgb2ycbcr_pipelined(
input wire clk,
input wire [23:0] rgb,
output reg [7:0] y,
output reg [7:0] cb,
output reg [7:0] cr
);
// 第一级流水:计算乘积项
reg [15:0] r_coeff, g_coeff, b_coeff;
always @(posedge clk) begin
r_coeff <= 16'd66 * rgb[23:16]; // 0.257
g_coeff <= 16'd129 * rgb[15:8]; // 0.504
b_coeff <= 16'd25 * rgb[7:0]; // 0.098
end
// 第二级流水:求和并偏移
reg [15:0] y_sum;
always @(posedge clk) begin
y_sum <= r_coeff + g_coeff + b_coeff + 16'd128;
y <= (y_sum >> 8) + 8'd16; // 最终Y值
end
// 类似实现Cb和Cr通道...
endmodule
4. 关键算法实现细节
4.1 Sobel边缘检测优化
传统Sobel算子需要两个3x3卷积核,在FPGA中实现时有几个优化技巧:
- 行缓冲设计:使用两个行缓冲寄存器存储前两行像素
- 并行计算:同时计算Gx和Gy梯度
- 近似计算:用绝对值之和代替平方和开方
verilog复制module sobel_edge(
input wire clk,
input wire [7:0] pixel_in,
output reg [7:0] edge_out
);
// 行缓冲
reg [7:0] line_buffer[0:1][0:1023];
reg [7:0] window[0:2][0:2];
always @(posedge clk) begin
// 更新行缓冲
line_buffer[0] <= pixel_in;
line_buffer[1] <= line_buffer[0];
// 构建3x3窗口
for(int i=0; i<2; i++) begin
for(int j=0; j<2; j++) begin
window[i][j] <= window[i][j+1];
end
window[i][2] <= line_buffer[i];
end
window[2][2] <= pixel_in;
// 计算梯度
reg [10:0] gx = (window[0][0] + 2*window[0][1] + window[0][2]) -
(window[2][0] + 2*window[2][1] + window[2][2]);
reg [10:0] gy = (window[0][0] + 2*window[1][0] + window[2][0]) -
(window[0][2] + 2*window[1][2] + window[2][2]);
// 近似梯度幅值
edge_out <= (gx[10] ? -gx : gx) + (gy[10] ? -gy : gy);
end
endmodule
4.2 形态学处理实现
腐蚀和膨胀操作采用3x3结构元素,通过查找极值实现:
verilog复制module morph_filter(
input wire clk,
input wire [7:0] pixel_in,
input wire mode, // 0:腐蚀 1:膨胀
output reg [7:0] pixel_out
);
reg [7:0] window[0:2][0:2];
always @(posedge clk) begin
// 更新3x3窗口(类似Sobel模块)
// ...
// 查找极值
reg [7:0] ext_val = window[1][1];
for(int i=0; i<3; i++) begin
for(int j=0; j<3; j++) begin
if(mode ? (window[i][j] > ext_val) : (window[i][j] < ext_val))
ext_val = window[i][j];
end
end
pixel_out <= ext_val;
end
endmodule
5. 字符识别方案
5.1 特征提取设计
采用垂直投影法提取字符特征:
- 二值化处理后的图像进行垂直投影
- 通过投影直方图分割字符
- 提取每个字符的网格特征
verilog复制module char_feature(
input wire clk,
input wire bin_img, // 二值图像
output reg [63:0] feature // 8x8特征向量
);
// 投影计数器
reg [15:0] h_proj[0:79]; // 假设字符宽度80像素
reg [7:0] x_cnt;
// 网格特征提取
always @(posedge clk) begin
if(bin_img) h_proj[x_cnt] <= h_proj[x_cnt] + 1;
x_cnt <= (x_cnt == 8'd79) ? 0 : x_cnt + 1;
// 每行结束时更新特征
if(x_cnt == 0) begin
for(int i=0; i<8; i++) begin
for(int j=0; j<8; j++) begin
feature[i*8+j] = |h_proj[i*10 +: 10]; // 每10像素为一个网格
end
end
end
end
endmodule
5.2 模板匹配优化
使用预存储的字符模板进行匹配:
- 模板采用8x8二值特征
- 使用异或运算计算相似度
- 并行比较多个模板提高速度
verilog复制module template_match(
input wire clk,
input wire [63:0] feature,
output reg [3:0] char_code
);
// 预存储模板 (示例只存储0-9)
parameter [63:0] TEMPLATES [0:9] = '{...};
// 相似度计数器
reg [5:0] match_score[0:9];
reg [3:0] best_match;
reg [5:0] max_score;
always @(posedge clk) begin
max_score <= 0;
best_match <= 0;
for(int i=0; i<10; i++) begin
match_score[i] <= 64 - $countones(feature ^ TEMPLATES[i]);
if(match_score[i] > max_score) begin
max_score <= match_score[i];
best_match <= i;
end
end
char_code <= (max_score > 48) ? best_match : 4'hF; // 阈值判断
end
endmodule
6. 系统集成与调试
6.1 流水线时序控制
整个图像处理流水线的时钟域控制很关键:
- 使用全局时钟和使能信号同步各模块
- 每个模块输出添加valid信号
- 合理插入流水线寄存器平衡时序
verilog复制module pipeline_ctrl(
input wire clk_100m,
input wire rst_n,
// 各模块接口
...
);
// 生成像素使能信号
reg [15:0] px_cnt;
reg px_valid;
always @(posedge clk_100m) begin
px_cnt <= (px_cnt == 800*480-1) ? 0 : px_cnt + 1;
px_valid <= (px_cnt < 800*480);
end
// 各模块实例化
ov5640_interface cam(.clk(clk_100m), .en(px_valid), ...);
rgb2ycbcr color(.clk(clk_100m), .en(cam_valid), ...);
// ...
endmodule
6.2 Modelsim仿真技巧
仿真时需要注意的几个要点:
- 使用$readmemh读取测试图像
- 对摄像头接口信号建模
- 添加关键信号检查点
verilog复制module tb_lpr;
// 测试图像存储
reg [23:0] test_img [0:800*480-1];
initial $readmemh("test_img.hex", test_img);
// 时钟生成
reg clk = 0;
always #5 clk = ~clk;
// 摄像头信号模拟
reg [9:0] px_idx = 0;
always @(posedge clk) begin
if(px_idx < 800*480) begin
cam_data <= test_img[px_idx][7:0];
px_idx <= px_idx + 1;
end
end
// 实例化待测设计
lpr_top dut(.*);
// 自动检查
always @(posedge dut.lcd_valid) begin
if(dut.lcd_data != expected_char)
$error("识别错误!");
end
endmodule
7. 性能优化经验
7.1 资源利用优化
- 使用DSP块实现乘法运算
- 合理选择流水线级数
- 共享行缓冲减少BRAM使用
实际项目中,通过优化Sobel模块的行缓冲共享,节省了30%的BRAM资源。
7.2 时序收敛技巧
- 关键路径添加寄存器
- 使用跨时钟域同步器
- 合理设置时序约束
tcl复制# XDC约束示例
create_clock -period 10 [get_ports clk_100m]
set_input_delay 2 -clock clk_100m [get_ports cam_data]
set_multicycle_path 2 -setup -from [get_clocks cam_pclk] -to [get_clocks clk_100m]
8. 常见问题排查
8.1 图像采集问题
- 画面出现条纹:检查PCLK信号完整性,添加终端电阻
- 颜色异常:确认RGB数据拼接顺序是否正确
- 图像错位:调整VSYNC和HREF的采样边沿
8.2 识别率问题
- 字符分割不准:调整投影分割阈值
- 误识别率高:增加模板数量和特征维度
- 速度不达标:优化流水线结构,提高并行度
9. 项目扩展方向
- 支持多车牌识别:增加图像ROI检测
- 添加神经网络加速:利用FPGA实现CNN
- 增加通信接口:通过UART或以太网输出识别结果
经过实测,当前系统在800x480分辨率下能达到15fps的处理速度,车牌字符识别准确率约85%。后续通过算法优化和硬件加速,还有很大的提升空间。
