尧图精选

FPGA实现SAD模板匹配算法:目标跟踪系统实时性优化与工程实践解析

🕒 发布时间:2026/9/8 5:39:49 📁 来源:尧图网络
说到基于FPGA的SAD模板匹配算法实现目标跟踪很多刚接触图像处理的朋友第一反应是能不能直接在CPU或者ARM上跑答案是可以但在实际项目中你会发现用软件实现模板匹配尤其是全图搜索计算量非常感人。我之前在一个实时视频追踪项目里试过在嵌入式端跑SAD匹配一帧720P的灰度图全图扫一遍需要几百毫秒跟踪框完全跟不上运动目标基本没法用。后来把核心计算搬到FPGA上同一个算法流水线一铺开实时性完全是另一个量级。这篇文章不打算讲太多纯理论重点放在工程实现上。我会把整个项目的设计思路、SAD算法的FPGA并行化方法、搜索策略、时序约束与调试技巧全部分享出来。文章适合两类人看一是准备做FPGA图像处理但不知道怎么下手的初学者二是在做目标跟踪项目、正卡在实时性瓶颈上的工程师。看完之后你会对整个系统怎么搭、代码怎么写、坑在哪里有一个完整的认知。1. 为什么用FPGA做SAD模板匹配算法特性与硬件架构的天然契合1.1 SAD算法原理与计算量分析SADSum of Absolute Differences绝对差值和是模板匹配里最简单直接的一种相似度度量。它的数学含义很朴素拿一个已知的模板图像在一个更大的搜索区域里逐像素滑动每滑动到一个位置就把模板和搜索窗口内对应位置的像素相减取绝对值然后求和。这个和越小说明该位置和模板越相似目标就锁定在SAD值最小的位置。公式写出来很简短但背后藏着巨大的计算量。假设模板尺寸是16x16搜索区域是64x64那么需要在64-16x64-16约2304个候选位置计算一次完整的SAD每个位置要做256次减法、256次取绝对值和255次加法总共上百万次运算。串行CPU做这些操作一次SAD匹配的延迟大概在几十毫秒到几百毫秒之间浮动帧率根本撑不起来。而FPGA的优势在于空间并行和深度流水。SAD计算天然具有数据并行性——模板和搜索窗口的每个像素都是独立的16个像素对可以同时做减法16路绝对差结果可以组成加法树并行求和多个候选位置还可以通过流水线重叠计算。这种“以面积换延迟”的思路正好是FPGA最擅长的事。1.2 为什么不用纯软件方案或GPU做目标跟踪可选的技术路线其实不少OpenCV跑在CPU上、Halcon跑模板匹配、GPU做并行加速当然还有FPGA。我的选择逻辑是这样的如果是PC端项目Halcon或OpenCV确实方便算法迭代快调试工具也成熟但如果是嵌入式设备、工业相机前端、或者对功耗和体积有要求的小型化系统CPU性能往往不够GPU功耗和散热又压不住这时候FPGA就是为数不多的合理选择。另外还有一个很现实的问题很多图像处理任务不是只跑一个算法前面有采集、ISP、缩放后面有串口输出、显示叠加、与主控通信。FPGA可以把采集、预处理、匹配、输出全部整合在同一个器件里避免数据在芯片间来回搬运。相比之下CPU方案至少要外挂一个图像采集芯片GPU方案更是要整块独立显卡系统复杂度和成本都上去了。1.3 整体架构设计与芯片选型思路这个项目的系统架构我在设计时把它拆成了五个模块图像采集、预处理、帧缓存与数据流控制、SAD匹配引擎、结果输出与通信。采集端用的是常见的CMOS传感器我测试时用的OV5640通过DVP接口进FPGA预处理模块做灰度化和可选的3x3均值滤波帧缓存用DDR3存储当前帧图像SAD引擎是整个系统的核心负责完成模板和搜索区域的匹配计算结果输出通过VGA/HDMI叠加显示跟踪框同时用UART或者FMC总线把目标坐标上报给主控。芯片选型上我建议根据资源和成本平衡来定。Xilinx Artix-7系列比如XC7A35T是一个很稳妥的起点逻辑资源够用上手资料也多黑金、正点原子这些开发板上基本都有配套的摄像头和HDMI例程能省不少前期搭建环境的功夫。如果项目后续需要跑Linux系统、做复杂的决策算法直接上Zynq系列会更方便ARM端跑软件决策PL端跑图像预处理和SAD加速分工明确。国产FPGA这两年也做得不错高云、安路的器件在逻辑资源上完全能覆盖这类项目而且价格有优势如果产品要量产可以纳入评估。2. 系统架构拆解从摄像头到坐标输出的完整数据链路2.1 数据流整体设计谁在什么时刻干什么事一个完整的目标跟踪系统数据流必须做到清晰可控。我的设计是CMOS传感器输出YUV422或RGB565格式预处理模块先做成灰度图并降噪写入DDR3帧缓存与此同时SAD引擎从DDR3中读取上一帧的搜索区域和本帧对应的搜索区域执行匹配匹配结果经过决策模块处理后一方面更新目标坐标另一方面用于生成VGA叠加信号把跟踪框画在输出图像上。这里有个关键点SAD引擎当前帧匹配时用到的“模板”实际上来自上一帧锁定目标位置的图像块。这就带来了一个经典的工程问题——模板的获取和复制。我建议在预处理模块后面单独拉一路数据把目标位置的原始像素按地址存入一块专用的模板BRAM而不是直接拿DDR里的数据因为DDR有读写延迟模板数据需要反复读取放在BRAM里才能保证后续计算的时序稳定和带宽充足。2.2 帧缓存设计DDR3读写冲突与仲裁策略这个项目对DDR3的依赖主要集中在帧缓存。一帧720P的灰度图像大约是921600字节分配到DDR3中的连续地址空间VGA输出需要按行读取SAD搜索需要按坐标读取写入模块又不断产生新帧数据所以DDR控制器需要处理三种同时发起的内存访问请求。实测下来如果不做仲裁直接轮流访问画面会明显撕裂SAD匹配也经常读到半帧数据导致结果乱跳。我的解决办法是给DDR控制器加一个简单的优先级仲裁写请求优先级最高因为CMOS传感器的数据如果不及时写入行缓冲会溢出直接丢帧VGA读出是周期性且对实时性敏感排在第二位SAD搜索引擎的读请求对帧率要求没那么苛刻排最后。仲裁状态机的实现不复杂本质就是三个请求源轮流握手但注意burst长度要尽量对齐我是全部设计成128B的burst访问减少DDR刷新和预充电带来的额外开销。另外还有一个低成本方案如果目标分辨率不高比如只有320x240完全可以不挂DDR改用FPGA内部的Block RAM做三帧缓冲节省一层复杂的DDR调试工作。缺点是8位的VGA输出和16位的DDR带宽浪费很明显这一点在选型时要提前考虑清楚。2.3 模板存储与行缓存SAD引擎的“粮仓”SAD引擎运行时的数据访问模式很特殊模板是固定的反复读取搜索窗口是随着扫描位置不断移动的每一拍都要读出一整行像素。如果每次都去DDR中按像素地址读取DDR带宽会被打爆时序也会变得不可控。模板部分我采用双份BRAM存储。一份用于保持上一帧锁定的目标模板另一份用于当前帧匹配过程中更新模板的候选。这样在匹配计算的同时系统还能并行准备下一帧的模板做到无缝衔接。搜索窗口的数据通路我用了行缓存加滑动窗口的方式在FPGA里就是移位寄存器链——每来一个新像素整条链往左移动一拍最右边自然退出窗口。配合双缓存的行缓冲可以在一个时钟周期里拿到搜索窗口中任意N行的数据这样SAD核就能稳定地每个时钟周期消费多个像素计算流水线不会因为数据不足而空转。3. SAD核心计算模块的并行化实现3.1 并行度选型SELL——资源与吞吐率的平衡SAD计算模块的并行度设计是整个项目中最需要权衡的部分。并行度越高单位时间计算的候选位置越多匹配越快但消耗的DSP和LUT也越多。我建议从三个档次中选第一档串行扫描每个时钟周期算一对像素优点是占资源极少缺点是匹配一帧的时间完全不可接受只适合验证逻辑功能不建议在目标跟踪里用。第二档行并行加流水模板16行同时参与计算每行读出一个像素构成16路绝对差计算通道再做加法树累加一个周期能推进一列。这一档的资源消耗适中在Artix-7上占3000多个LUT和几十个DSP已经能把一帧720P图像的匹配压缩到1毫秒以内。第三档多行加宽并行每个周期同时处理16行x8列的像素块资源开销接近翻了8倍性能提升却未必能等比例反映到帧率上因为搜索区域的限制和DDR读带宽很快会成为新瓶颈。我最终选的是第二档16路行并行加4级流水匹配一帧720P图像大约1.2毫秒加上前后处理整帧延迟控制在3毫秒左右无论做实时显示还是后续算法处理都够用。3.2 SAD计算流水线四级流水如何实现SAD计算可以拆成四级流水绝对值差计算、列向求和、行向求和、最小值比较与坐标更新。用Verilog写的时候每一级单独打一拍寄存器保证每个时钟周期都能输入一组新的像素对而不需要等待整个窗口算完。这里我直接给出一个简化版的SAD核心计算框架供参考// SAD流水线第一级绝对差计算 always (posedge clk or negedge rst_n) begin if (!rst_n) begin abs_diff d0; end else begin abs_diff (search_pixel template_pixel) ? (search_pixel - template_pixel) : (template_pixel - search_pixel); end end // SAD流水线第二级列向累加一行中多个通道结果相加 always (posedge clk or negedge rst_n) begin if (!rst_n) begin col_sum d0; end else begin col_sum abs_diff_0 abs_diff_1 abs_diff_2 abs_diff_3; end end // SAD流水线第三级行向累加多行结果相加得到当前候选位置SAD值 always (posedge clk or negedge rst_n) begin if (!rst_n) begin row_sum d0; end else begin row_sum col_sum_0 col_sum_1 col_sum_2 col_sum_3; end end // SAD流水线第四级最小值比较与坐标锁定 always (posedge clk or negedge rst_n) begin if (!rst_n) begin min_sad 32hFFFF_FFFF; best_x d0; best_y d0; end else if (row_sum min_sad) begin min_sad row_sum; best_x current_x; best_y current_y; end end需要注意绝对差计算建议用组合逻辑直接实现不要用if-else判断大于小于直接用两个减法和一个多路选择器逻辑层级更少加法树理论上可以用流水线拆分得更细但在16路输入的情况下四级流水已经能跑到150MHz以上的时钟频率没必要继续加深流水。如果发现时序收敛困难优先检查的是减法和取绝对值这一段组合逻辑是否过深。3.3 资源评估与性能实测在XC7A35T上整个SAD引擎加上预处理和显示输出综合后的资源占用大概是这样LUT约9200触发器约6800DSP48约24个BRAM约18块。这个资源占比大约是35%左右系统的余量还算充足。如果用更小的板卡把分辨率降到640x480资源可以再压缩三分之一。实测帧率方面在100MHz系统时钟下720P灰度图全图搜索模板尺寸16x16固定搜索区域64x64匹配一帧耗时约1.1毫秒。加上预处理和显示输出系统整体跑到60fps毫无压力。如果改用Zynq平台、把搜索区域约束到目标预测位置周围可以轻松跑上100fps以上完全满足高速目标跟踪的场景需求。4. 从“算得出”到“跟得稳”搜索策略与跟踪闭环4.1 搜索区域范围的选择全局搜索不如预测搜索FPGA擅长计算但不代表可以无脑全图搜索。一方面全图搜索的SAD计算量随分辨率指数增长FPGA资源再充足功耗也是问题另一方面目标跟踪场景具有时间连续性上一帧的目标位置和当前帧的目标位置在绝大多数情况下不会跳变因此完全没有必要全图搜索。我的方案是默认搜索区域以上一帧目标位置为中心向外扩展一个固定半径比如横向30像素、纵向30像素。这个范围覆盖了目标最大运动速度同时在计算量上比全图搜索降低了将近90%。如果匹配置信度很低说明目标可能快速运动或丢失此时再触发一次全图搜索重新定位定位成功后又切回局部搜索模式。4.2 卡尔曼滤波在FPGA中的轻量级实现单纯靠SAD最小值得出的目标坐标会有明显的抖动尤其是在光照波动或目标表面纹理复杂的情况下匹配点会在真实位置附近来回跳。我的做法是在FPGA里实现一个一维卡尔曼滤波对目标的X方向和Y方向做平滑预测。卡尔曼滤波在FPGA里的实现并不复杂状态量只有位置和速度方程是线性的核心就是两个乘法累加。我用定点数表示位置量和速度量都用24位定点格式小数部分占12位足够覆盖像素级精度。预测值作为下一帧搜索区域的中心如果SAD匹配结果偏离预测值太远则说明可能是误匹配此时丢弃本次匹配结果直接用预测值更新目标位置。在工程实现时卡尔曼滤波的协方差矩阵可以简化成固定增益逐帧更新即可不必做在线自适应这样既能满足稳定性要求逻辑资源开销也小。4.3 模板更新策略固定模板与动态模板的取舍目标跟踪中一个隐蔽但致命的坑是模板漂移。如果你每一帧都用当前匹配结果的图像块去替换模板那么模板会随着噪声、遮挡和光照变化逐渐“漂移”最后跟踪框慢慢偏到背景上。反过来如果模板一直不变目标自身发生变化比如车辆在运动中的角度变化、行人的肢体伸展匹配会逐步失效。我采用的折中方案是双模板一个固定模板在初始化时从第一帧锁定区域截取长期不变一个动态模板每个N帧我设的5帧用当前最佳匹配区域更新。最终相似度取两个模板中较小的那个SAD值作为判定依据只有当两个模板的SAD都小于阈值时才认为匹配成功。这个策略简单但有效既能适应目标外观的缓慢变化又能防止模板完全漂移到背景上。阈值标定我放在仿真和实测阶段的调试章节细说。5. 时序约束、仿真与调试最容易翻车的地方5.1 时序约束不要让综合工具默认配置害了你FPGA图像处理项目前端采集、DDR、SAD引擎、显示输出各自工作在不同频率域Vivado如果只使用默认时钟约束综合布局布线之后时序报告大概率是红的。做这类项目时钟约束一定要自己写清楚。我是这样约束的摄像头输入时钟24MHz通过MMCM生成三路时钟——像素处理时钟100MHzDDR控制器时钟200MHzVGA输出时钟74.25MHz。对跨时钟域的信号全部用异步FIFO隔离绝不直接打拍采。另外要特别注意SAD引擎和DDR读接口之间的数据有效性这一块如果时序不满足匹配结果会随机出错而且问题非常难查。我建议在综合之后先看时序报告重点检查SAD计算模块的数据通路是否满足建立时间约束不满足就分段插入流水线寄存器宁可多加两级延时不要硬调布局。5.2 仿真阶段怎么验证SAD逻辑正确性写SAD模块的Verilog代码时不要一上来就接整个系统仿真那样调试信息太多根本定位不了问题。我的做法是分三步走。第一步单独对SAD核心模块做单元测试。用Python或者Matlab生成一幅简单的灰度图比如棋盘格背景加一个矩形目标同时生成相应的模板然后给Verilog测试平台灌入像素数据对比硬件输出和软件计算结果。如果数值逐拍对不上优先检查位宽和符号问题——这是最常见的坑灰度像素是8位无符号数做减法取绝对值时如果误用有符号数或位宽不够高位截断会导致负数变正数SAD结果直接错乱。第二部对包含DDR读写和搜索状态机的完整子系统做RTL仿真。仿真时要构造合理的数据依赖关系确保每个地址和数据都准确对应。这一步能发现控制时序和地址计算的问题。第三部上板验证。板级调试我用Vivado的ILA抓取SAD输出和坐标信号实时观察匹配结果的变化配合实际画面很快能确认功能是否正常。5.3 板级调试典型问题记录与解决方案这个项目在调试过程中我遇到并解决了不少典型问题这里整理成一张速查表可以对照排查问题现象可能原因解决方案跟踪框乱跳位置不稳定SAD匹配结果受噪声影响阈值过小增加卡尔曼滤波平滑放宽置信度阈值采用双模板判断画面撕裂跟踪框与图像错位DDR读写仲裁优先级设置不合理调整仲裁优先级写请求优先VGA读请求次之SAD读请求最后匹配位置固定在某一个角模板初始化失败模板区域为全黑或全白检查初始化逻辑确认模板BRAM数据有效打印模板像素值系统时钟上不了150MHzSAD加法树组合逻辑级数过深在加法树中间插入流水线寄存器重构为多级流水上板结果与仿真不一致跨时钟域信号没有同步处理所有跨时钟域信号一律通过异步FIFO或两级同步器目标运动快了就丢失搜索区域太小目标超出搜索范围增加搜索区域半径结合卡尔曼预测值动态调整搜索中心光照变化后跟踪框漂移到背景模板更新策略不合理固定模板被污染改用双模板策略固定模板不参与更新动态模板设定更新间隔这里的每一条都是我实测中踩过的坑。尤其是SAD结果和仿真不一致那条当时排查了整整两天最后发现是DDR读数据跨时钟域时没有同步导致偶尔读出的像素值错位。所以如果你遇到首帧正常、跑着跑着结果突然乱掉的情况优先查跨时钟域和数据对齐而不是怀疑算法本身。5.4 Vivado综合选项与资源优化细节设置综合选项时我建议把状态机的编码方式改为one-hot可以减少组合逻辑存储类的逻辑让工具自动推断为BRAM不要手动实例化RAM原语除非你有特殊时序要求。SAD引擎中大量的加法器Vivado会自动映射到DSP48不需要手动干预但要注意给DSP48输出打寄存器否则频率上限会下降。同时要注意模板BRAM的读端口最好使用寄存器输出模式这个选项在BRAM的IP配置里可以勾选。它虽然会多一拍读出延迟但能改善BRAM输出到SAD加法树之间的时序路径性价比很高。6. 扩展与升级从单目标跟踪到多目标实用系统6.1 如何移植到Zynq平台并与ARM联调这套SAD引擎是纯PL实现逻辑上完全不依赖CPU因此可以非常方便地移植到Zynq平台。在Zynq上PL端负责采集、预处理、SAD匹配和显示输出PS端通过AXI-Lite接口读取目标坐标并运行更高级的决策算法比如轨迹预测、目标分类、行为分析。因为坐标更新率在几百赫兹以上PS端读取的是一个非常平滑的实时数据流可以直接用于控制云台或驱动机械臂反应速度比纯软件方案好很多。如果项目里还需要与STM32H743这类MCU通信我推荐用FMC总线。STM32H743通过FMC并行接口可以非常高速地读取FPGA内部的寄存器时序简单可靠比串口高出不止一个数量级。你可以把FPGA挂在STM32的地址总线上STM32访问FPGA就像访问普通外部SRAM一样简单传输效率非常高而且双方逻辑都不复杂。6.2 多目标跟踪多个SAD引擎的复用策略单目标跟踪扩展到多目标跟踪最直接的做法是例化多个SAD引擎。但这样资源翻倍不是最优解。更经济的方式是做一个“时分复用”的SAD引擎设定多个模板每个模板对应一个目标搜索时按照时间片轮流计算各个模板的SAD值。在100MHz时钟下一个SAD引擎每毫秒可以完成一帧搜索如果做4个目标的时分复用每个目标分配250微秒仍然可以保证250fps的搜索频率完全够用。唯一要注意的是多个目标的搜索区域不能重叠否则一个目标可能匹配到另一个目标的模板区域造成串扰需要在搜索地址生成时添加区域互斥判断。6.3 结合Halcon与PC端仿真的开发流程工程开发时我会先用Halcon或者OpenCV快速验证算法参数。Halcon的模板匹配工具非常成熟可以在PC端确定最佳匹配阈值、搜索范围、模板尺寸等参数。然后把验证好的参数固化到FPGA逻辑中。这个流程能大大缩短FPGA的调试周期因为你不用在硬件上反复调整参数只需要把PC端测试好的参数改写成Verilog参数即可。但要注意Halcon的匹配算法是灰度归一化的和纯SAD在光照鲁棒性上有差异所以FPGA最终参数仍然需要一定的实测微调。7. 写在最后的实操心法做这个项目最大的感受是FPGA图像处理的核心不是把C代码翻译成Verilog而是想办法把算力铺在流水线上。SAD算法在软件里是一个双层循环在FPGA里就要变成一拍一个结果的数据流这中间的思维转换需要刻意练习才能习惯。另外调试阶段遇到问题不要过度依赖“看波形猜原因”而是要有元凶假设的排除思路先查时钟和数据有效性再查控制状态机最后才怀疑算法逻辑。FPGA里很多灵异现象归根结底都是数据没有在正确的时间出现在正确的位置。把数据流画出来对照代码逐级检查比盲目改代码有效得多。如果有条件我强烈建议先在一款资料齐全的开发板上跑通整个流程再决定要不要定制硬件。黑金、正点原子的FPGA开发板对这类项目支持都比较友好配套例程里就有OV5640采集、HDMI显示和DDR读写能省下不少前期环境搭建的功夫。把这套流程完整跑一遍你对FPGA图像处理的理解会上一个台阶。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →