Quick Start
本指南将引导你从零开始,在Xilinx Zynq-7020平台上搭建一个基于FPGA的实时目标跟踪系统。系统采用光流法(Lucas-Kanade)算法,通过摄像头采集视频流,经FPGA硬件加速处理,实时跟踪选定目标并在显示器上高亮显示。按照以下步骤,你可以在约2小时内完成硬件搭建、代码部署与基本功能验证。
前置条件
- 硬件平台:Xilinx Zynq-7020 SoC开发板(如Zedboard或PYNQ-Z2),配备OV5640摄像头模块(或兼容MIPI接口的摄像头)与HDMI显示器。
- 开发工具:Vivado 2020.1及以上版本(含Vivado HLx),以及Xilinx SDK(用于PS端软件编写)。
- 基础知识:熟悉Verilog/VHDL硬件描述语言,了解FPGA开发流程(综合、实现、生成比特流),具备基本的数字图像处理概念(如灰度转换、梯度计算)。
- 软件依赖:Python 3.8+(用于仿真脚本与测试数据分析),以及MATLAB(可选,用于算法原型验证)。
目标与验收标准
- 功能目标:系统上电后,HDMI显示器实时显示摄像头画面;通过串口设置初始目标框(矩形区域),系统自动跟踪该区域内的移动物体,并在画面上用绿色框高亮显示跟踪结果。
- 性能验收:在640×480分辨率下,帧率达到30fps;跟踪延迟(从物体移动到框更新)小于50ms;目标丢失后(如物体被遮挡),重捕获时间小于1秒。
- 资源验收:LUT使用率不超过50%,BRAM使用率不超过80%,DSP使用率不超过40%,最高工作频率不低于100MHz。
实施步骤
步骤1:搭建硬件平台与摄像头接口
连接摄像头模块至开发板的FMC或PMOD接口,确保I2C配置引脚(SDA、SCL)与数据引脚(D0-D9)正确映射。在Vivado中创建Block Design,添加Zynq PS核,配置MIO用于串口(UART)与I2C控制器。添加摄像头接口IP核(如OV5640驱动),该IP核负责通过I2C配置摄像头寄存器(设置分辨率640×480、帧率30fps、输出格式RGB565),并采集并行数据。将摄像头数据输出连接到VDMA(Video Direct Memory Access)的输入,VDMA将帧数据写入DDR3内存。时钟域划分:摄像头时钟域(25MHz)用于摄像头数据采集,系统时钟域(100MHz)用于VDMA与后续处理逻辑,HDMI时钟域(74.25MHz)用于显示输出。跨时钟域同步使用异步FIFO,在VDMA内部自动处理。
步骤2:实现图像预处理模块
在FPGA逻辑中实现RGB转灰度模块:采用加权平均法(Gray = 0.299R + 0.587G + 0.114B),使用3个乘法器与1个加法器,流水线设计,每个时钟周期输出一个灰度像素。接着实现高斯滤波模块:使用3×3窗口,系数为[1,2,1;2,4,2;1,2,1]/16,通过移位寄存器实现行缓存(Line Buffer),使用BRAM存储两行数据,当前行与上下行数据并行计算。滤波后的灰度图像写入另一个VDMA通道,供后续光流计算使用。注意:预处理模块工作在系统时钟域(100MHz),通过异步FIFO从摄像头VDMA读取数据。
步骤3:实现跟踪核心模块(光流法)
跟踪核心模块包含三个子模块:Harris角点检测、Sobel梯度计算、光流方程求解。
- Harris角点检测:在灰度图像上计算每个像素的角点响应值R = det(M) - k * trace(M)^2,其中M为结构张量矩阵。使用3×3窗口计算梯度Ix、Iy,然后累加窗口内Ix^2、Iy^2、Ix*Iy。设置阈值,提取R值大于阈值的像素作为特征点。硬件实现时,使用BRAM缓存窗口数据,通过流水线计算,每个时钟周期输出一个像素的角点响应。
- Sobel梯度计算:使用3×3 Sobel算子(水平Gx、垂直Gy),通过移位寄存器与加法器实现,输出梯度幅值与方向。梯度数据与角点检测结果同步,用于后续光流方程。
- 光流方程求解:基于Lucas-Kanade方法,在特征点邻域内求解线性方程组。使用9个DSP48单元并行计算矩阵乘法与求逆,实现3×3窗口内的最小二乘解。输出光流向量(u, v),更新目标框中心位置。注意:为降低资源消耗,仅对特征点区域进行计算,非特征点区域跳过。
跟踪核心模块工作在系统时钟域,通过AXI-Stream接口与预处理模块连接。模块内部使用状态机控制帧同步:每一帧开始时,读取上一帧的目标框位置,在当前帧中搜索特征点并计算光流,更新目标框。框更新逻辑:根据光流向量平移框中心,保持框大小不变(未来可扩展为自适应大小)。
步骤4:实现HDMI输出模块
HDMI输出模块负责生成显示时序(640×480 @60Hz,像素时钟74.25MHz),并从VDMA读取原始RGB图像数据,叠加跟踪框(绿色矩形)。框叠加逻辑:在像素坐标位于目标框范围内时,将RGB值替换为绿色(0x00FF00)。使用Xilinx HDMI TX IP核(或自定义编码器)将RGB数据转换为TMDS差分信号输出。注意:HDMI时钟域与系统时钟域之间使用异步FIFO同步,FIFO深度至少为2行像素(1280像素)以避免溢出。
步骤5:集成与综合
在Vivado中连接所有模块:摄像头VDMA输出到预处理模块,预处理模块输出到跟踪核心模块,跟踪核心模块输出框坐标到HDMI模块,HDMI模块从原始图像VDMA读取数据。添加AXI互联模块,用于PS与PL之间的通信(如串口设置初始框)。运行综合与实现,检查时序约束是否满足(时钟周期10ns对应100MHz)。资源占用目标:LUT使用率45%,BRAM使用率72%,DSP使用率35%。若资源超标,可优化窗口大小(如从3×3改为2×2)或减少并行度。
步骤6:PS端软件编写
在Xilinx SDK中创建C语言工程,实现以下功能:初始化串口(波特率115200),通过UART接收用户输入的目标框坐标(格式:x y width height);通过AXI-Lite接口将坐标写入PL寄存器(地址映射在系统内存空间);启动VDMA传输,循环读取帧数据。软件流程:上电后,等待用户输入初始框;输入后,设置PL寄存器并启动跟踪;每隔100ms读取PL返回的当前框坐标,通过串口打印,用于调试。
验证结果
验证分为仿真与上板测试两个阶段。
- 仿真验证:编写testbench,模拟摄像头输出连续帧(每帧640×480像素,包含一个移动的白色方块)。检查跟踪核心模块输出的框坐标是否与模拟物体的实际位置一致(误差小于2像素)。使用Vivado仿真器运行100帧,记录每帧的坐标误差,绘制曲线,确保误差不累积。
- 上板测试:将比特流下载到开发板,连接摄像头与显示器。上电后,显示器显示实时画面。通过串口终端(如Putty)发送初始框坐标(例如:"320 240 100 100"),观察绿色框是否稳定跟随移动物体(如手或玩具车)。测试场景:物体以约0.5m/s速度移动,框跟踪延迟目测小于50ms;物体被遮挡3秒后移出,框在1秒内重新捕获。记录实际帧率(通过串口打印帧计数)与资源占用(通过Vivado报告),与目标对比。
排障指南
- 问题:显示器无画面。检查摄像头I2C配置是否成功(通过Vivado ILA抓取I2C波形);检查VDMA是否启动(PS端软件中VDMA控制寄存器是否设置正确);检查HDMI时钟是否稳定(使用示波器测量TMDS时钟引脚)。
- 问题:跟踪框不移动或抖动。可能原因:Harris角点检测阈值过高(无特征点)或过低(噪声点过多);光流方程求解中矩阵奇异(邻域内梯度变化太小)。调整阈值参数(在PL代码中修改寄存器值),或增加高斯滤波的平滑强度。
- 问题:帧率低于30fps。检查摄像头是否配置为30fps(I2C寄存器);检查VDMA带宽是否足够(DDR3带宽应大于640×480×30×2 ≈ 18.4 MB/s);检查PL逻辑中是否有长路径导致时钟频率降低(查看时序报告,优化关键路径)。
扩展方向
- 参数化设计:将跟踪窗口大小、高斯滤波系数、Harris阈值等参数通过AXI-Lite接口暴露给PS,实现运行时动态调整,适应不同场景(如快速运动物体需增大窗口)。
- 分辨率升级:升级摄像头接口(如使用MIPI CSI-2)支持1080p@60fps,同时优化BRAM使用(如采用行缓存压缩或外部DDR帧缓存),以应对更高数据吞吐量。
- 算法增强:集成卡尔曼滤波或粒子滤波,对光流结果进行平滑与预测,提高在遮挡或快速运动下的鲁棒性。可在PS端软件实现滤波,通过AXI-Lite接口与PL交换数据。
- 平台移植:将设计移植到其他FPGA平台(如Intel Cyclone V或Lattice ECP5),需替换Xilinx专用IP核(如VDMA、HDMI TX)为通用接口,并调整时钟管理单元(MMCM/PLL)配置。
参考资源
- Xilinx UG585: Zynq-7000 Technical Reference Manual
- Lucas, B. D., & Kanade, T. (1981). An iterative image registration technique with an application to stereo vision. IJCAI.
- Harris, C., & Stephens, M. (1988). A combined corner and edge detector. Alvey Vision Conference.
- Xilinx PG020: Video Direct Memory Access v6.3 Product Guide
附录:关键代码片段
RGB转灰度模块(Verilog)
module rgb2gray (
input clk,
input [7:0] r, g, b,
output reg [7:0] gray
);
wire [15:0] sum = r*8'd76 + g*8'd150 + b*8'd29; // 0.299*256, 0.587*256, 0.114*256
always @(posedge clk) gray <= sum[15:8];
endmodule光流方程求解(伪代码)
// 在特征点邻域内,计算A = [sum(Ix^2), sum(Ix*Iy); sum(Ix*Iy), sum(Iy^2)]
// 计算b = [-sum(Ix*It), -sum(Iy*It)]
// 求解 A * [u; v] = b,使用Cramer法则
det = A[0][0]*A[1][1] - A[0][1]*A[1][0];
if (det > threshold) begin
u = (b[0]*A[1][1] - A[0][1]*b[1]) / det;
v = (A[0][0]*b[1] - b[0]*A[1][0]) / det;
end


