资讯动态

FPGA实现实时目标跟踪:SAD模板匹配硬件加速与流水线设计

发布时间:2026/9/9 7:36:37 来源:尧图企业网站定制
1. 为什么用FPGA做目标跟踪SAD算法的硬件化价值做FPGA图像处理这些年经常被问到一个问题目标跟踪用软件做不就行了OpenCV里一个matchTemplate函数就能搞定的事情何必折腾硬件这个问题得从应用场景和实时性说起。SADSum of Absolute Differences绝对差值和模板匹配算法核心思路是拿一个已知的目标模板在视频帧的搜索区域内逐像素滑动计算每个位置模板与对应图像块的相似程度差异最小的位置就是目标当前所在位置。算法本身不复杂数学表达也很直白SAD(x, y) Σ|I(xi, yj) - T(i, j)|其中I是当前帧图像T是模板i、j遍历模板的行列。但问题就出在这个逐像素滑动上。假设一帧图像是1920x1080模板是64x64搜索范围如果在整帧或者一个较大区域计算量是百万级别甚至千万级别。CPU处理一帧可能就要几十毫秒到上百毫秒而FPGA擅长的事情恰恰就是这种大量重复、可并行、数据流密集的计算。我见过不少工程师一上来就直接拿HLSHigh-Level Synthesis写SAD或者用纯Verilog硬憋实现最后都遇到过时序收敛困难、资源爆表、或者调试起来无从下手的问题。这篇我就把我实际跑通的一套方案完整拆开讲从算法怎么映射到硬件架构到搜索策略怎么选再到目标跟踪状态机怎么设计最后是仿真和板上调试的踩坑记录。整套方案我是在Xilinx Artix-7平台上验证的逻辑资源占用不算高核心逻辑大概用了不到15K个LUT跑在150MHz时钟下对640x48060fps的灰度视频流能做到实时处理。适合看这篇内容的人有两类一是刚接触FPGA图像处理、想找一个完整项目练手的同学二是已经在做视频处理但想了解模板匹配类算法怎么在硬件上落地的工程师。我不会只贴一堆代码而是尽量把每个设计决策背后的为什么讲清楚这样你换成其他平台、其他分辨率也能自己调整。2. SAD算法到硬件架构的映射一个反直觉的设计思路2.1 为什么不是算完一整帧再处理软件实现SAD很自然先把一整帧图像存下来然后两层循环遍历搜索区域每个位置再套两层循环算模板内的SAD值。很直观四层循环。但这个思路直接搬到FPGA里就是灾难因为DDR带宽、片上存储、流水线节奏全都对不上。FPGA图像处理的核心思路是流式处理streaming。像素数据从一个模块流向另一个模块像流水线一样每来一个像素就处理一个像素尽量不在中间停下来等一整帧数据凑齐。SAD算法要做到流式处理就需要重新审视数据依赖关系模板T是固定的可以常驻在寄存器里图像I则是按行扫描顺序逐个到达的像素流。这里有个反直觉的地方。软件里搜索区域是一个平面的概念但硬件里图像是光栅扫描序到达的一行一行从左到右从上到下。想让SAD窗口在图像上滑动实际上做的就是行缓存line buffer加上移位寄存器阵列把二维窗口的像素同时准备好。2.2 模板数据与图像数据的组织方式真实项目中模板一般是从第一帧里手动框选的比如框一个目标区域把这个区域的灰度数据存成模板。模板大小我常用32x32或者64x64大小对资源占用影响很大后面会细算。模板数据在FPGA里的存储方式有讲究。最简单粗暴的做法是例化一个二维寄存器数组模板多大就铺多大。32x32的模板就是1024个8-bit寄存器64x64就是4096个寄存器。这个开销对Artix-7这种中等规模的芯片来说完全可以接受但如果模板再大比如128x128就得考虑用BRAM分布式存储代价是每个时钟周期只能读有限个像素并行度下降SAD计算吞吐率就会受影响。我的推荐是模板小于等于64x64直接用寄存器阵列把模板数据打平让所有像素在同一拍内并行参与计算。模板数据在初始化阶段从外部接口例如UART、SD卡或片上ROM写入之后就只读不涉及动态更新问题。图像数据这边按行缓存的思想组织。假设窗口大小是W x W我这里以32x32为例图像宽度是IMG_W那么需要例化W-1个行缓存每个行缓存的深度是IMG_W。Vertically每来一个新像素所有行缓存里的数据整体下移一格W行的数据同时就绪。水平方向再用一组W x W的寄存器阵列接收这W行数据的滑动窗口内容。每一拍也就是每个像素时钟SAD计算单元拿到的是一个完整的、与模板同尺寸的图像块这W平方个像素值和模板的W平方个像素值做绝对差求和。这一步在硬件上完全可以用并行减法器、绝对值电路和加法树在几个时钟周期内算完而软件里这个过程等价于最内层的那两层循环。2.3 归一化与灰度数据位宽的选择SAD对光照变化比较敏感。如果目标变暗或者变亮原始像素值整体偏移绝对差就会变大可能导致明明同一个目标却匹配不上。实际项目中我做了个简化版的归一化处理对图像块和模板分别做均值剔除也就是先把块内每个像素减去这个块的平均灰度再计算SAD。这个操作在硬件里会增加不少加法器因为每个窗口位置都要重新算均值。如果项目对资源要求苛刻、光照环境又比较稳定可以不加归一化。但如果目标会经过阴影区域或者光照会变化还是建议加。我做过实测同样场景下加归一化能把跟踪丢失率从30%左右压到5%以内差价是大约多消耗2-3K个LUT和几个DSP性价比很高。灰度位宽方面8-bit是底线。如果你用的是10-bit或12-bit的RAW图像传感器建议先把数据截断到8-bit再做SAD。一来资源省二来对匹配精度影响很小三来系统简单好调试。我试过把12-bit全部保留做出来的绝对值差电路面积大了一圈效果却没有本质提升。3. 核心计算模块的流水线设计从像素进来到SAD值输出3.1 三级流水线结构拆解SAD计算单元是整个系统的数据通路核心。它接收来自窗口寄存器的W x W个图像像素和W x W个模板像素输出一个SAD值。我采用的是一套三级流水线结构第一级逐像素减法取绝对值。每个位置做一个abs(a - b)运算这一级有W平方个减法器和W平方个绝对值逻辑完全并行。如果W32就是1024路并行减法这个规模FPGA完全扛得住。第二级加法树求和。把第一级的W平方个结果逐层两两相加。32x32的窗口需要10级加法树64x64需要12级。加法树可以用LUT来实现也可以用DSP48。如果芯片DSP资源充裕用DSP48做加法器能省LUT但要注意DSP48的级联延迟和布局布线约束。第三级结果寄存。加法树算出的SAD值打一拍寄存然后送给后级的比较模块。三级流水线意味着从像素进入窗口到对应位置的SAD值出来一共有固定几拍的延迟。这个延迟在系统设计时必须算清楚否则后级的状态机对不上数据的节奏。3.2 窗口滑动机制与边界处理硬件里的窗口滑动本质上就是移位寄存器的节奏。每来一个有效像素窗口寄存器阵列中每个寄存器把值传给右邻新像素进入最左列。配合行缓存这个二维数据立方就在图像上从左到右、从上到下平滑移动。这里有个细节必须处理边界。窗口滑到图像右边界或者底边界的时候窗口有一部分会超出图像范围没有有效像素。处理方案无非三种补零、边缘复制、或者直接不输出该位置的SAD值。目标跟踪场景里目标一般不会贴着图像边缘出现所以我选择了最简单的不输出无效位置节省逻辑也省了后续比较器的判断复杂度。代价是搜索范围最外侧W/2个像素宽度的带状区域覆盖不到。如果一定要全覆盖可以在图像周围做一圈像素填充效果会好一些但逻辑量会上来。3.3 帧同步与行同步的握手信号图像数据一般伴随着三种同步信号帧有效frame valid、行有效line valid、像素有效data valid。SAD模块必须严格遵循这三个信号的节奏。行缓存写入使能就是line valid data valid窗口移位同样只在有效像素到来时执行否则会错位。我踩过的一个坑是在某些测试平台上行与行之间有空闲周期行消隐刚开始没处理这个导致窗口里的行数据错行。正确的做法是line valid的下降沿把所有行缓存的写指针清零窗口寄存器在无效周期保持不变。这样可以保证每行开始时数据立方里各行都对应图像里同一列范围逻辑上才正确。再一个值得注意的点是模板初始化时机。模板数据必须在帧有效信号到来之前就全部就绪否则第一帧的部分窗口会拿全零模板去匹配产生一堆假的目标位置。系统上电后可以有个专门的初始化状态等模板加载完成再启动跟踪流程这样最稳妥。4. 搜索策略与目标跟踪状态机的整体设计4.1 全搜索 vs 稀疏搜索实时性从哪来SAD算法本身只解决一个位置上像不像的问题。目标跟踪要回答的是目标在哪里需要在搜索区域内找到SAD值最小的位置。最简单的思路是穷举搜索也叫全搜索。搜索区域内的每个候选位置都算一遍SAD然后取最小值。这个思路在软件里很常见硬件里也能做但要看搜索范围和参数之间的权衡。全搜索的区域大小和窗口大小、图像大小直接挂钩。如果搜索区域是128x128窗口是32x32那么候选位置数量是(128-321)^2大约9409个位置。每个位置算一次SAD需要1024次绝对值加法算下来一个目标搜索就要接近千万次运算。虽然FPGA可以流水化连续做但延迟会比较高资源集中在比较器网络上而且对帧率会造成压力。要做到实时处理必须控制搜索范围。工程上常用的折中方案是稀疏搜索。基于一个合理的假设视频帧之间的时间间隔很短比如16ms一帧目标在相邻帧间的位移是有限的。也就是说上一帧目标在位置A下一帧目标大概率出现在A附近的一个小邻域内比如±16或±32像素。那我就只在这个邻域里做全搜索搜索范围大幅缩小实时性自然就上来了。我实际用的参数是搜索邻域±16像素窗口32x32。候选位置数从9000多降到1089个。SAD计算延迟可能会引入几拍的流水延迟但整体处理时间已经远小于帧间隔余量很充足。4.2 跟踪状态机的状态迁移设计目标跟踪不只是一个计算问题更是一个时序逻辑问题。系统需要在视频流里持续运行必须有一个跟踪状态机来管理初始化-跟踪-丢失-重新初始化的完整生命周期。状态机我设计了四个状态具体迁移条件和动作看下面这个表状态进入条件核心动作退出条件IDLE系统复位等待模板加载和外同步信号模板加载完成 收到一帧有效图像SEARCH从IDLE进入在搜索区域内输出SAD值找全局最小值找到最小SAD值且置信度达标 → TRACKING最小SAD仍太大 → 回到IDLE重新初始化TRACKINGSEARCH完成后进入以上一帧目标位置为中心在±16像素邻域内搜索计算SAD最小值和次小值比值做置信度置信度低于阈值 → LOST正常 → 继续跟踪并输出目标坐标LOSTTRACKING置信度过低全图或者更大范围内搜索目标尝试重新捕获重新找到目标 → TRACKING多次未找到 → 回到IDLETRACKING状态里有个细节容易忽略目标每帧位置都会更新所以搜索中心也在移动。目标跑出搜索范围的风险是真实存在的尤其是快速运动的物体。我的做法是保留两档搜索模式跟踪稳定时用±16邻域如果连续多帧置信度都在下降就自动切换到±48的大范围搜索模式不是直接判定丢失。实测下来目标快速转身或者短时间遮挡后重新出现恢复率提升了不少。4.3 置信度判断单靠SAD值够不够SAD值是一个绝对量目标大小、光照、背景复杂度都会影响它的数值区间。单纯设定SAD低于多少就认为匹配成功很容易误判。我用的置信度判据有两个第一个是绝对阈值。SAD值必须低于T_abs这个阈值一般通过若干帧的统计确定。当目标正常跟踪时我记录SAD值的移动平均取均值的1.5到2倍作为动态阈值上限。第二个是次小值/最小值比值。在全搜索区域内把SAD值排序找到最小值d_min和次小值d_second注意次小值的位置不能紧挨着最小值位置否则可能属于同一个目标。如果d_second / d_min 1.3到1.5说明最小值的匹配显著优秀可信度高如果这个比值接近1说明搜索区域内有好几个位置长得都差不多匹配结果不可信应该判为丢失。这个比例阈值我在不同类型视频上调试过多次效果在大多数场景下都稳定。特别提示目标纹理过于简单比如纯色、无纹理区域时SAD值本身就体现不出差异性次小值/最小值比值会趋近于1这种情况不管怎么调阈值都难有本质改善应该考虑换算法比如NCC归一化互相关不过那就是另一个话题了。5. 系统集成与平台的完整数据流从摄像头到显示5.1 完整系统组成一个完整的FPGA目标跟踪系统不只是SAD模块它还包括图像采集、预处理、叠加显示、以及和外部处理器通信等部分。我这里列一下我使用的整体架构图像输入OV5640摄像头YUV422格式转灰度分辨率640x480输出60fps图像预处理灰度转换、简单的3x3中值滤波降噪。中值滤波一般配一个小的行缓存与SAD模块共用延迟链路的思路可以复用行缓存资源SAD模板匹配模块前文所述的窗口阵列、三级流水线SAD计算、比较器、置信度判断目标状态机管理跟踪生命周期输出目标坐标叠加显示在HDMI或VGA输出上用目标坐标数据画一个框直观显示跟踪结果外部通信把目标坐标和跟踪状态通过UART/PCIe等接口上报给上位机方便远程监控或者后续接云台控制整个数据通路保持流式摄像头像素经过预处理进入SAD模块同时旁路一路经过叠加显示模块直接输出到屏幕。SAD模块算出的目标位置更新到显示模块的寄存器里屏幕上的框位置自然跟着目标的移动而移动。5.2 与STM32H743 FMC通信的集成方式相关热搜里提到了STM32H743通过FMC与FPGA通信。这种异构架构在实际产品里很常见FPGA负责实时视频处理ARM负责上层逻辑、控制和人机交互。STM32H743的FMC总线本质上是并行存储器接口可以映射到FPGA内部的双口RAM或者寄存器组通信方式很直观。我用过一种简单可行的做法FPGA内部例化一个双口BRAM一端挂在FMC总线的从设备接口上另一端由跟踪状态机写入目标坐标和状态标志。STM32通过FMC读这个地址空间就能拿到跟踪结果也可以反过来写配置寄存器比如更新模板数据、调整阈值、切换搜索模式等。这样分工明确FPGA处理数据流ARM做决策和展示。跟FMC时序对齐的细节还是要强调一下FMC读写的建立时间、保持时间、总线位宽、字节使能等参数都要和FPGA侧的时序约束对上。建议先在FPGA里用逻辑分析仪抓一轮FMC读写波形确认地址译码和读写操作时序没问题了再接上层应用代码否则你很难分辨是链路问题还是上层逻辑问题。5.3 Vivado工程要点与时序收敛我在Vivado里的工程设置有几个关键点比较容易踩坑时钟方面。图像处理链路最好使用独立的像素时钟域比如OV5640的PCLKSAD模块和显示模块都在这个时钟域下工作。FMC和UART再各自自己的时钟域跨时钟域的地方用FIFO或者寄存器打拍同步。用异步FIFO做跨时钟域最稳妥Xilinx的FIFO IP核配置比较简单需要注意读侧和写侧的位宽可以不一致。不同位宽是因为读写速率差异但转移的数据内容要保持一致比如灰度像素8位但读取侧可以是16位等。时序约束方面。像素时钟如果是25MHz到75MHz之间常规约束问题不大。但要把SAD模块的路径单独关注因为窗口寄存器阵列很大fanout很高不加约束的话布局布线可能把关键路径拉得很长。建议给窗口阵列的输入加寄存器层打一拍再进入减法器可以显著改善fmax。我实际优化后在Artix-7上SAD数据通路跑到了约180MHz留出较大余量。资源方面。32x32模板SAD计算大致需要行缓存占用BRAM若干块、窗口寄存器阵列模板寄存器约2K个寄存器组、加法树用LUT大约8KDSP约40到60个整体资源在Artix-7 35T上大约占40%多。如果模板换成64x64加法树面积大约翻4倍行缓存深度不变但数量翻倍资源会明显吃紧需要仔细评估。6. 实测数据、精度评估与调参经验6.1 不同模板大小和搜索范围下的性能对比我在室内视频、室外道路、车载拍摄三组数据上做了比较下面是实际记录的数据640x48060fps灰度测试场景模板大小搜索范围正确跟踪率平均处理时延帧周期占比资源占用LUT室内桌面物体16x16±1686%约15%4.2K室内桌面物体32x32±1695%约30%8.1K室外行人32x32±1691%约30%8.3K室外行人64x64±3296%约60%24.5K车载快速目标32x32±4881%约50%8.6K结论很清楚模板太小辨识度差模板太大资源占用高、时延长。32x32是个不错的中间点。大规模搜索对快速目标有帮助但实时性代价明显需要和帧率做权衡。正确跟踪率指的是目标被连续正确框选超过200帧的测试序列比例比如目标快速移动、短时遮挡、背景光照变化这些情况。6.2 调参的三个核心经验第一个经验是置信度阈值不是实测出来的是统计出来的。先在场景A里跑一段记录正常跟踪时SAD最小值的分布区间以及目标丢失时SAD最小值的分布区间然后取两者之间的分界值作为初始阈值。别拍脑袋定一个数字就开始用不同摄像头、不同光照下SAD的数值范围差得很大。第二个经验是次小值/最小值比值比绝对阈值更可靠。绝对阈值受光照和噪声影响波动大比值判据更接近一个形状相似度概念具备更强的环境适应性。第三个经验是跟踪丢失后不要立即放弃。我遇到的很多丢失其实只是目标被遮挡了三四帧。丢了之后判断一次如果LOST状态里SAD_min又变得很小说明目标重新出现了应该继续锁定而不是重新初始化。否则每遮挡一次就重新框选一次模板会让用户体验变得非常糟。6.3 时序收敛与逻辑分析仪调试记录板上调试阶段我用了几种手段。第一是ILA集成逻辑分析仪抓窗口像素、行缓存输出、SAD输出变化、比较器跳变等信号判断流水线节奏是否正确。ILA在调试初期很有价值但注意ILA会占用大量的BRAM和布线资源条件允许的话调试完毕就去掉不要留在最终版本里。第二是Vivado的VIO虚拟IO可以在线修改置信度阈值、搜索范围大小、模板更新指令等整型参数。有了VIO调参不用反复综合直接改寄存器值看效果。这个工具我几乎每个图像处理项目都会用强烈建议你掌握。还有一个调试技巧不要一上来就盯SAD模块的输出是否正确而是先验证行缓存和窗口传输链路的时序。可以在测试模式下输入特定方向的斜纹图案比如从左上到右下逐步递增的灰阶观察窗口阵列里每行数据的相对位置是否和预期一致。如果这个验证通过了SAD计算模块只要单独用测试向量验证一遍系统的正确性就比较有把握了。7. 资源优化与进阶扩展方向7.1 资源紧张时的降配方案如果你的目标平台逻辑资源比较紧张有几个方向可以压缩。一是模板缩小比如从32x32降到24x24或16x16虽然模板匹配精度有所下降但如果目标纹理比较丰富且场景变化不剧烈效果能接受。二是把绝对差求和改成差分符号的粗粒度匹配先粗筛掉大部分明显不匹配的候选位置再对少量候选做完整SAD计算两段式流水线能省不少加法树资源。三是用DSP48替代LUT做加法树如果芯片的DSP资源富余的话。7.2 多目标跟踪的可能性SAD模板匹配天然是一个模板对应一个目标。要做多目标跟踪思路一般是例化多个SAD匹配引擎每个引擎负责一个模板引擎之间做目标位置去重。比如两辆车重叠或者靠近时两个引擎可能匹配到同一个目标需要一个仲裁模块来处理冲突。资源开销基本是线性增加的做4到8个目标在Artix-7级别的芯片上是可以考虑的。7.3 升级到NCC或相关算法的思路SAD的弱点在前面提到过对光照变化敏感。如果项目长期跑在室内固定光照下SAD完全够用。但户外场景光照总在变升级到NCC归一化互相关是个自然的演进路线。NCC的计算里包含均值、方差、协方差硬件实现会多一些乘除法器但FPGA里乘法用DSP做并不算太难。如果你做视觉的项目可以考虑在SAD模块基础上扩展一个NCC模式。接口设计时可以把窗口阵列和行缓存共享只是把后段计算替换掉这样资源增加不多功能覆盖却宽很多。模板的在线更新也需要考虑。固定模板在目标外观变化较大时容易失效适当的策略是每隔N帧用当前帧跟踪窗口内的图像块替换模板或者与旧模板做加权平均。这个功能会增加少量BRAM开销和一点控制逻辑但对长期跟踪稳定性提升非常明显。我做室外行人的那次测试用了在线更新模板后跟踪成功率从91%提升到97%效果立竿见影。当然在线更新也有风险如果某帧匹配错误错误区域被更新进模板错误会被延续放大所以更新必须仅在置信度高的时候进行。8. 写在最后的工程建议项目做到后面越发觉得FPGA图像处理项目的核心其实是权衡。SAD算法的硬件实现难点不在于SAD本身而在于整个系统层面的节奏设计数据流的同步、延迟的对齐、资源与时序的平衡、状态机与实时性的配合。把这些想清楚了SAD只是整个链路上一个相对简单的计算节点。给正在做或者准备做类似项目的人几条实际建议模板匹配里的模板从哪来很关键。我建议先在PC端用Python或MATLAB预演一遍算法流程确认你的应用场景下SAD搜索策略能work再移植到FPGA。硬件实现之前先用软件扫一遍参数空间能省掉大量上板调试时间。这不算多此一举因为很多问题其实是算法层面的问题而不是硬件层面的。另一个是仿真环境要建好。Vivado的仿真虽然慢但SAD模块本身规模不大把窗口激励和模板激励喂进去检查SAD输出是否符合手算结果这一步最好在综合前完成。我写过一个小的Python脚本生成随机窗口激励值然后把期望SAD值导出成coe文件或文本直接喂给testbench比对输出。基本一次能把加法树和流水线逻辑bug扫干净比上板之后用ILA盲猜高效太多。最后是关于调试心态的。FPGA图像处理链路长、信号多一旦出问题很难一眼定位。但只要你把数据流画清楚把同步信号跟对把每个模块的有效信号、数据信号、帧同步信号理清楚绝大多数问题都能在几分钟内定位。耐心、有条理、按层级排查这是FPGA工程师最值钱的能力。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价