1. 项目概述
最近在做一个基于FPGA的车牌识别系统,用的是正点原子达芬奇Artix-7开发板(FPGA芯片XC7A35T),配合OV5640摄像头和4.3寸LCD显示屏。整个项目从图像采集到最终结果显示都跑在FPGA上,同时还做了Modelsim仿真验证。这个项目特别适合想学习FPGA图像处理的同学,下面我就把整个实现过程详细分享给大家。
2. 硬件平台搭建
2.1 开发板选型与配置
我选的是正点原子达芬奇Artix-7开发板,核心是Xilinx的XC7A35T FPGA芯片。这块板子有几个优势:
- 35K逻辑单元足够处理图像算法
- 内置DSP模块适合做卷积运算
- 板载的PSRAM可以缓存图像数据
- 自带摄像头和LCD接口,省去了很多外设电路设计
硬件连接很简单:
- 用排线连接OV5640摄像头到板子的CAMERA接口
- 连接LCD显示屏到RGB接口
- 接上12V电源和下载器
注意:OV5640需要单独供电,开发板的3.3V输出可能不够,建议用外部5V电源。
2.2 摄像头参数配置
OV5640是一款500万像素的摄像头,但车牌识别不需要这么高分辨率。我在初始化时将其配置为:
- 分辨率:640x480(VGA)
- 输出格式:RGB565
- 帧率:30fps
- 自动曝光:开启
配置是通过I2C接口完成的,具体寄存器设置可以参考OV5640的数据手册。这里有个小技巧:先用现成的配置工具生成初始化序列,再移植到Verilog代码中。
3. 图像处理流水线设计
3.1 整体架构
整个处理流程采用流水线设计,各模块通过FIFO连接:
code复制摄像头采集 → RGB转Ycbcr → Sobel边缘检测 → 腐蚀膨胀 → 车牌定位 → 字符分割 → 模板匹配 → LCD显示
每个模块都设计为独立时钟域,用异步FIFO做数据缓冲,这样能提高系统吞吐量。
3.2 图像采集模块实现
图像采集模块要处理OV5640的时序,关键信号有:
- pixel_clk:像素时钟(约25MHz)
- vsync:帧同步信号
- href:行同步信号
- data[7:0]:像素数据
Verilog实现要点:
verilog复制module image_capture (
input pixel_clk, vsync, href,
input [7:0] cam_data,
output reg [15:0] rgb_data,
output reg data_valid
);
reg [1:0] byte_cnt; // RGB565每像素2字节
always @(posedge pixel_clk) begin
if (~vsync) begin // 帧消隐期复位
byte_cnt <= 0;
data_valid <= 0;
end else if (href) begin
byte_cnt <= byte_cnt + 1;
if (byte_cnt == 0) rgb_data[15:8] <= cam_data;
else begin
rgb_data[7:0] <= cam_data;
data_valid <= 1; // 完整像素有效
end
end else data_valid <= 0;
end
endmodule
调试心得:OV5640的时序要严格遵循手册要求,特别是vsync和href的极性。我最初因为没注意这个导致采集的图像错位。
3.3 RGB转Ycbcr优化
标准的RGB转Ycbcr公式有浮点运算,在FPGA上实现效率低。我改用了整数运算的近似公式:
verilog复制// 优化后的转换公式(移位代替除法)
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;
这样实现只需要乘法器和加法器,不用DSP资源。实测在100MHz时钟下,处理640x480图像能达到60fps。
4. 车牌识别核心算法
4.1 Sobel边缘检测优化
传统Sobel要计算x和y两个方向的梯度,我做了两点优化:
- 改用绝对值近似:
verilog复制G = |Gx| + |Gy| // 代替平方开方运算
- 采用3行缓存实现滑动窗口:
verilog复制reg [7:0] line_buf[0:2][0:639]; // 3行缓存
always @(posedge clk) begin
// 滑动更新缓存
for (int i=0; i<640; i++) begin
line_buf[0][i] <= line_buf[1][i];
line_buf[1][i] <= line_buf[2][i];
line_buf[2][i] <= new_pixel;
end
// 计算梯度
Gx = (line_buf[0][i+1] + 2*line_buf[1][i+1] + line_buf[2][i+1]) -
(line_buf[0][i-1] + 2*line_buf[1][i-1] + line_buf[2][i-1]);
Gy = (line_buf[2][i-1] + 2*line_buf[2][i] + line_buf[2][i+1]) -
(line_buf[0][i-1] + 2*line_buf[0][i] + line_buf[0][i+1]);
end
4.2 车牌定位算法
车牌定位是识别成功的关键,我采用的方法:
- 对边缘图像做水平投影,找到可能的行区域
- 在候选行内做垂直投影,确定左右边界
- 根据长宽比(中国车牌约3:1)过滤误检
verilog复制module plate_locator (
input clk, rst,
input [7:0] edge_data,
input data_valid,
output reg [10:0] x1, y1, x2, y2, // 车牌边界
output reg found
);
// 水平投影计数
reg [15:0] h_proj[0:479];
// 垂直投影计数
reg [15:0] v_proj[0:639];
always @(posedge clk) begin
if (data_valid) begin
// 更新投影计数
if (edge_data > THRESHOLD) begin
h_proj[y] <= h_proj[y] + 1;
v_proj[x] <= v_proj[x] + 1;
end
end
end
endmodule
5. Modelsim仿真验证
5.1 测试平台搭建
仿真分两个层次:
- 模块级仿真:验证单个模块功能
- 系统级仿真:验证整个流水线
以RGB转Ycbcr模块为例的测试平台:
verilog复制`timescale 1ns/1ps
module tb_rgb2ycbcr;
reg [23:0] rgb;
wire [7:0] y, cb, cr;
rgb_to_ycbcr uut (.*);
initial begin
// 测试用例
rgb = 24'hFF0000; #10; // 红色
$display("Red: Y=%d, Cb=%d, Cr=%d", y, cb, cr);
rgb = 24'h00FF00; #10; // 绿色
$display("Green: Y=%d, Cb=%d, Cr=%d", y, cb, cr);
rgb = 24'h0000FF; #10; // 蓝色
$display("Blue: Y=%d, Cb=%d, Cr=%d", y, cb, cr);
$stop;
end
endmodule
5.2 波形调试技巧
在仿真中我发现几个常见问题及解决方法:
- 数据不同步:添加足够的时钟延迟
- 边界条件错误:测试各种极端输入
- 时序违规:检查setup/hold时间
经验:仿真时把关键信号(如数据有效信号)加到波形窗口,可以快速定位问题。
6. 实际效果与优化
6.1 资源占用情况
在XC7A35T上的资源使用:
- LUT:约12K(35%)
- FF:约8K(23%)
- DSP:10个(20%)
- Block RAM:15(30%)
6.2 识别率优化
通过以下方法提升识别率:
- 多帧融合:对连续3帧结果投票
- 字符增强:对分割后的字符做二值化和缩放
- 模板更新:动态调整模板匹配阈值
实测在白天光照下,车牌识别率能达到85%以上。
7. 常见问题解决
7.1 图像采集不稳定
现象:采集的图像有随机噪点或错位
解决方法:
- 检查摄像头供电是否充足
- 降低像素时钟频率
- 在代码中添加输入同步寄存器
7.2 识别率低
现象:车牌定位不准或字符识别错误
解决方法:
- 调整边缘检测阈值
- 优化投影算法的窗口大小
- 增加车牌颜色检测(蓝底白字)
8. 项目扩展方向
这个基础框架还可以进一步扩展:
- 添加车牌颜色识别
- 支持多车牌同时检测
- 增加网络传输功能
- 移植到其他FPGA平台
整个项目代码已经开源,需要的朋友可以联系我获取。在实际部署时要注意环境光照的影响,夜间可能需要补光。