1. 项目缘起:为什么非要用FPGA做目标跟踪
先聊点背景。这几年嵌入式的目标跟踪需求越来越多,小到一两个物体移动检测,大到无人机跟拍、AGV小车导航,几乎都能看到一个共同瓶颈:处理器跑软件的模板匹配,帧率上不去。举个我实际调试过的例子,在一块主频600MHz的MCU上跑一个64x64模板、128x128搜索区域的SAD匹配,全图滑动一次要几亿次绝对差运算,单帧耗时300多毫秒,所谓“实时跟踪”其实就是每秒两三次的幻灯片。这种体验放在工业视觉或运动控制场景里,基本不可用。
而FPGA的方案把这些绝对差运算全部拆成硬件流水线,同一个时钟节拍里同时做几十路甚至上百路加法,把计算量从“串行循环”变成“并行电路”。同样一组参数的SAD匹配,FPGA能做到几十微秒一帧,配合上帧间搜索范围预测,稳稳跑出60fps以上的目标跟踪。所以这个项目标题里“FPGA”“SAD”“模板匹配”“目标跟踪”四个词是环环相扣的:SAD是匹配策略,模板匹配是把跟踪问题转成搜索问题,FPGA是把搜索速度拉到实时的关键。
这个项目适合两类人看。一类是刚入门FPGA图像处理、想找一个既有算法深度又有工程壳子的练手项目;另一类是已经在用MCU做视觉、被算力卡住想换架构的工程师。我自己做的时候就强烈感觉到,SAD模板匹配在FPGA上落地,不只是把公式换成硬件代码那么简单,里面牵扯到数据缓存、并行度折中、DDR带宽控制、跟踪稳定性,每一个环节都有坑。这篇文章就把我踩过的坑和验证过的方案都摆出来,尽量按工程实现的思路写清楚。
2. SAD算法的硬件化思路:先搞懂软件在算什么
2.1 SAD的定义和数据流特征
SAD全称Sum of Absolute Differences,中文常叫绝对差值和。公式很直白:把模板图像T和搜索区域中的候选块I做逐像素减法,取绝对值再加总,得到一个代价值。遍历整个搜索区域,代价值最小的那个位置就是匹配结果。
用数学语言表示,对于模板大小为MxN、搜索图像某个候选位置(u,v),SAD值计算公式为:
SAD(u,v) = Σ(i=1 to M) Σ(j=1 to N) |T(i,j) - I(u+i, v+j)|
这个公式在CPU上实现就是一个三重循环——外层遍历搜索位置,内层累加每个像素差。问题也出在这个三重循环上,假设搜索区域是100x100像素,模板是16x16,那意味着要算100x100=10000次候选位置的SAD值,每个候选位置又涉及256次绝对差加累加,总共是256万次运算。如果是30fps的视频流,每秒钟就是7680万次纯运算。对CPU来说,这个量级勉强能跑但已经占了大部分算力,而且随着模板尺寸和搜索范围增长,计算量是指数级膨胀的。
但换一个角度看,SAD算法天生是“数据并行”的——每个候选位置的运算互相独立,不存在树状结构的先后依赖。这正好是FPGA的舒适区:用组合逻辑加流水线把256次绝对差运算展平成硬件电路,再用多路并行单元同时算多个候选位置,计算时间就从“候选位置数乘以模板尺寸”压缩到“模板尺寸加上少量流水线开销”。
2.2 搜索策略的选择:全搜索还是三步搜索
模板匹配的搜索策略直接决定了FPGA资源的用量和实现的复杂程度。全搜索(Exhaustive Search)最直观,在搜索区域内按步长1逐像素滑窗,计算每一个候选位置的SAD值,找全局最小。优点是精度最高、实现简单,缺点是运算量最大。
另一种常见思路是三步搜索(Three-Step Search)或菱形搜索(Diamond Search),属于快速搜索算法。它通过局部的稀疏采样和逐步细化来压缩候选点数量。可是这类算法在FPGA里实现有一个比较恼人的问题:候选点的位置是“动态跳变”的,硬件数据流不好规划,要么做成多轮迭代控制,要么加复杂的取址逻辑。要不了多少资源但时序和面积都不好看。
我最终选择了全搜索,核心原因有两个。第一,FPGA的并行性决定了全搜索的绝对计算量增加并不致命——我只需要把搜索区域的像素行缓存到片内BRAM,然后用多路SAD计算单元把候选位置流水起来。第二,SAD的代价函数在某些纹理环境下会有多个局部极小值,快速搜索容易陷在错误位置上,做目标跟踪最忌讳的就是“跟丢了”,所以宁可多花一点资源换稳定性。
2.3 模板更新的权衡
跟踪场景和图像配准场景有个本质区别:目标外观会变。旋转、尺度变化、遮挡、光照变化都会让固定模板慢慢失效。所以真正的目标跟踪系统里,模板不可能只建一次,还得做在线更新。
但模板更新的实现分寸需要小心。更新频率太高,模板会被错误匹配的噪声污染,出现“模板漂移”问题;更新频率太低,又跟上目标的变化。我最后采用了一个较保守的策略:生成两个模板——初始模板和动态模板。初始模板在第一帧手动锁定目标时提取,全程保持不动;动态模板每隔N帧从当前最佳匹配区域重新截取。最终输出的SAD代价值取两者之中的较小值,相当于同时保留了“最开始的形象”和“最近的样子”,实测下来比单一模板稳很多。
这个策略在FPGA里怎么落?其实不复杂。初始模板存在一个BRAM里,动态模板存在另一个BRAM里,两路SAD计算单元并行跑,最后用比较器取小即可。代价是动态模板需要额外的“写回控制”,但我后面会说,这个写回控制可以复用一个相当透明的DMA通道,并不会把代码复杂度拉高太多。
3. FPGA整体架构设计:从视频输入到跟踪结果输出
3.1 系统级数据流
这个项目的整体数据流我把它画成一条清晰的主干道:
视频源输入 → 帧缓存/行缓存 → 搜索区域裁剪模块 → SAD计算阵列 → 最小代价比较器 → 目标坐标输出 → 跟踪结果显示
块与块之间用FIFO或RAM总线衔接,用一组valid/ready握手信号做跨时钟域处理。我选用的开发板是Xilinx Artix-7系列,自带DDR3和HDMI接口,摄像头走OV5640的DVP接口,输出端接HDMI显示器把搜索框叠加到视频流上。
你可能会问,为什么中间要经过帧缓存,不能直接把摄像头数据实时送到SAD计算模块吗?确实可以,但限制很大:摄像头输出的像素是逐行扫描顺序,而搜索区域的滑动需要随机访问“每一行任意位置”的数据。如果只有一个搜索区域,那可能要等很多帧才能把所有候选位置扫完,得不偿失。所以我的方案是先把完整一帧写到DDR3,再从DDR3读出搜索窗口的数据,这样跑完一帧大约需要两次DDR读写,带宽压力完全可控。
实测情况是,720p分辨率的一帧大约是1280x720像素,灰度图一个字节,整帧才900KB左右。DDR3跑166MHz、32位数据位宽,理论带宽在800MB/s上下,读写两次约1.8MB每帧,算上突发开销也就占用不到百分之十几的带宽。留给后续项目扩展的空间也够,比如同时跑两路视频或加大模板尺寸。
3.2 五大核心模块拆解
整体架构的五个核心模块分别是:
视频输入模块,负责接收OV5640或HDMI传入的像素数据,完成RGB到YUV再到灰度图的转换,输出一个灰度数据流和一个像素valid信号。这里我踩过一个坑:忘记把摄像头的同步信号做跨时钟域处理,导致偶尔出现整帧花屏。最后在输入侧挂了一级异步FIFO才彻底稳定下来。
帧存储与读取模块,负责把灰度帧写入DDR3,同时响应SAD计算模块的读请求,按行突发读出搜索窗口内的数据。这里最关键的参数是burst长度——我设置成64字节的一个burst,这样每次DDR读事务的开销被摊薄,效率会高很多。
搜索区域裁剪模块,根据上一帧的目标坐标为中心,生成一个以该点为中心、大小可配的搜索窗口,并把它当前窗口覆盖的行数据缓存到一组行缓冲BRAM。这部分是整个系统最核心的数据通路,因为SAD计算单元每个时钟都要同时读取模板像素和候选窗口像素,行缓冲的深度需要和模板宽度对齐。
SAD计算阵列,由多个并行SAD核心组成,每个核心负责一个候选位置的全部累加。我用的是16个并行核心,每个核心内部又有4路部分和流水线,整体相当于每时钟周期能输出16个候选位置的等价计算量。生成16x16模板在128x128搜索区域的匹配结果,实测耗时在1.2ms以下,帧率余量很大。
最小代价比较器,把16个SAD核心的结果并行送入一个树形比较器,选出最小值和对应坐标,再交给跟踪控制模块。如果候选位置的最小代价大于某个阈值,说明当前帧的匹配置信度太低——可能是遮挡或剧烈形变,这时跟踪状态机会切换到“重搜索模式”。
3.3 模块间接口与握手设计
FPGA项目的维护难度随着时间的推移通常会变得很大,主要源于模块间接口缺乏统一规范。这个项目里我强制给所有模块定义了一套规则:每个输出端口都带valid信号,说明数据有效;每个输入端口带ready信号,说明接收方当前可以接收数据;当二者同时有效时,表示一拍握手成功。如果有模块暂时没法接收数据,ready拉低之后发送方会自动暂停,不会丢数据。
这套规则最大的好处是,可以在调试时对任意两个模块之间插入一个chipscope观察窗口。比如怀疑SAD计算模块输入速率不够,直接看它的tvalid和tready波形,能立刻判断是上游没送数据还是下游没接收。
接口宽度上我踩过一个选择纠结:灰度像素用8位,SAD阵列内部的累加器用多少位?每个候选窗口累加256个8位绝对值差,理论最大值是255×256=65280,17位就能装下。但考虑到累加器位宽过低可能引入溢出问题,最终我统一用了20位,多出的3位用于中间结果冗余,反正BRAM位宽资源也不差这几比特,换来的是不用操心底层溢出和后续算法扩展。
4. SAD计算阵列的流水线实现细节
4.1 数据缓存与行列寻址控制
在FPGA上,做SAD最难受的不是加法逻辑,而是怎么把像素数据安排得让加法器“吃得饱、不饿着”。模板和候选窗口需要同时读取多个像素,而这些像素在存储介质里往往是不连续的——内存器件最怕随机读。
我的做法是把搜索窗口按“行条带”结构缓存。具体来说,在DDR3的帧缓冲里,搜索窗口覆盖K行,每次把连续的K行全部读到一组行FIFO里。每个新像素进来时,最老的一行被丢弃,新的像素写入当前行。这样行FIFO里任何时候都保存着“以当前行为底的K行数据”,而每个SAD计算核心需要的那K行候选窗口只是这些FIFO行的“滑动子窗口”。读出来的时候再按模板列宽度做一个移位寄存器链,每时钟移出一个窗口列。
如果你对图像处理存储不熟,可以把这个行FIFO想象成一根倾斜的传送带,水果(像素)一个一个排好队往前走,而工人(SAD计算单元)只看眼前固定的一小段带子。带子移动一次,工人就换一批“候选样本”,这就是滑动窗口的硬件版。
模板数据则预先加载到一个双端口BRAM,读端口1供模板块读取,写端口1供跟踪控制模块更新。因为读取模式是固定的顺序遍历,所以BRAM的读时序极其规整,不会出现随机访问性能退化。
4.2 SAD核心的乘法省略与优化
SAD的核心运算是绝对差,但在FPGA里做减法再取绝对值其实要小心仿真和综合工具的处理。如果直接写abs(a-b),不同的综合器可能会生成不同的电路结构,有时候还会嵌入一个条件选择器,面积和延时都很差。我建议手动拆开:
if (pixel_ref > pixel_search) diff = pixel_ref - pixel_search; else diff = pixel_search - pixel_ref;这明明是个简单操作,但手写之后综合工具能识别出一个减法器加一个多路选择器,延时会比隐藏abs好不少。你可以把这个优化理解成“给综合工具一张清晰的地图”,而不是让它自己去猜你这符号什么意思。
每路SAD核心内部,我把模板尺寸16x16分成四个4x16的条带,每个条带用一棵加法树完成累加,最后再把四条支路的和加起来。这样做的目的是压缩关键路径:如果一口气把256个像素差全部串联累加,组合逻辑的延时已经超过时钟周期,时序收敛会非常痛苦。分层之后每个部分拥有单独的处理能力,关键路径基本被限制在一个条带内。
4.3 并行度与帧率、资源利用率的折中
并行度是我在资源约束下不断权衡的一个核心指标。并行度太高了资源爆炸、布线拥塞;太低了帧率上不去。以我的Artix-7 35T为例,片上有大约20K个CLB,等效逻辑单元约33K,DSP48数量90个,BRAM块总共50块,每块36Kb。
SAD计算阵列如果开16路并行核心,每个核心内部4条加法树,总体的加法器硬件规模大约在1000个LUT左右(因为绝对差只需要加法和比较器,不需要乘法器)。再加上行缓存、控制逻辑、视频通路,整体LUT占用率大约在42%,BRAM用到28块左右,DDR控制器的IP也占掉一部分资源。这个资源量不挤,留了余量给调试逻辑和后续加卡尔曼滤波。
如果你手头板子资源很紧张,比如只有几千个LUT的小规模FPGA,那也有办法缩减:把模板缩小到8x8,并行度降到8路。代价是匹配精度下降,对于轻微形变的目标还扛得住,但对严重遮挡和高相似度背景就很难受了。
帧率的理论计算公式:T = (搜索窗口的列数 × 模板列数) / (每时钟能处理的候选位置数) + 流水线延迟。以搜索窗口128x128、模板16x16、每时钟并行处理16个候选位置为例,大约需要1024个时钟完成全窗口扫描。在150MHz的时钟频率下,折算时间约为6.8us,加上行缓存加载和帧间切换,单帧匹配时间大约在1ms左右,跑30fps的视频输入绰绰有余。而如果同一搜索窗口大小改成320x240,单帧匹配时间大约会上升到4ms,这时还能勉强维持实时性,但已经比较吃紧了。所以项目的瓶颈和选题方向一致——瓶颈不在“算得多快”,而是在“喂得够不够快”。
4.4 时序收敛的关键路径优化
SAD阵列的时序收敛是整个工程里最磨人的部分。如果你直接把256路绝对差接成一条加法链子,综合工具给出的最大时钟频率通常只有60~80MHz,完全不够看。我的优化策略分三层:
第一层是上面提到的手动分层的加法树,每层插入一个流水线寄存器,把长链条切断成4~5级,时钟频率可以上到150MHz以上。
第二层是把绝对差模块的输出打一拍,输入端也用寄存器打一拍,让组合逻辑的输入输出都“贴边”,时序裕量会明显改善。这个操作在代码层面其实就是几个always块的事情,但效果立竿见影。
第三层是Vivado的综合和布局策略调整。在implement阶段打开“Performance Explore”的策略,并添加两条时钟约束;跑不出来的时候试一下把SAD阵列的物理位置约束到靠近DDR控制器的地方,减少布线延时。我的经验是,时钟频率目标设在180MHz比较合理,再往上面追要耗费大量时间,收益却不明显。
5. 工程集成与实验验证:从仿真到上板
5.1 与FMC接口通信的接线设计
处理器这边我用了一块STM32H743,通过FMC总线与FPGA通信,效果非常好。也就是热词里常出现的“stm32h743和fpga实现fmc通信”,这个总线结构其实非常适合“ARM当大脑、FPGA当算力”的异构架构——STM32负责协议解析、跟踪结果后处理、通信上报,FPGA专注做图像预处理和SAD匹配。
接线要点先把关键信号列清楚:FMC数据线16根(D[15:0]),地址线若干根,片选、读使能、写使能,再加用于帧同步的中断线。如果后续我选用的板子是7系列FPGA用Bank 500/501接3.3V电平,而STM32的FMC引脚也是3.3V,电平基本兼容。唯一要特别注意的是信号完整性问题:FMC总线速率通常几十兆赫兹,排线过长会带来振铃,我建议PCB走线不要超过5厘米,如果只能用杜邦线,就把FMC时钟降到10MHz以下比较安全。
5.2 FMC寄存器映射与状态机设计
通信的实质是把FPGA内部几个关键寄存器暴露给STM32访问。我的映射表大致如下:
| 地址偏移 | 方向 | 含义 |
|---|---|---|
| 0x00 | 读 | 目标坐标X(16bit) |
| 0x04 | 读 | 目标坐标Y(16bit) |
| 0x08 | 读 | 当前帧SAD最小代价值(20bit) |
| 0x0C | 读 | 系统状态:锁定/重搜/失锁 |
| 0x10 | 写 | 模板更新使能 |
| 0x14 | 写 | 搜索窗口大小配置 |
| 0x18 | 写 | 匹配阈值配置 |
STM32侧的操作流程很简单——每次检测到FPGA的中断信号(一帧匹配完成),就通过FMC发起一次突发读,把0x00到0x0C的连续地址一次性读回来,然后做坐标输出或者卡尔曼滤波预测。FMC读操作本身直接映射到FPGA的寄存器阵列,只需要一个简单的decode逻辑和一组同步寄存器。
5.3 vivado工程实现与板级验证
工程我用了Vivado 2021.2,Verilog为主,中间用了两个Xilinx官方IP:DDR3控制器(MIG)和Video Timing Controller。整个工程的RTL结构在综合后大概有10个模块,代码量约2500行。逻辑设计相对直接,最花时间的地方反而在调试上。
上板验证我分了三步走。第一步是纯输入验证——直接给FPGA喂一条带已知坐标目标的灰度图,DDR里预写入同一帧数据,检查SAD输出坐标是否正确。第二步是动态视频验证——接上OV5640摄像头,目标用手持一个高对比度的玩具,观察搜索框是否跟随。第三步是ARM联动验证——STM32通过FMC读写FPGA寄存器,并在串口打印坐标轨迹。
测试结果做了一张记录表,其中我最满意的一组数据是:720p 30fps情况下,模板尺寸32x32、搜索窗口128x128,FPGA的SAD匹配单帧耗时大约2.1ms,搜索框跟随误差控制在3个像素以内,占用的LUT资源约43%,BRAM 28块。对比纯软件跑同一组参数的耗时,加速比大约在150倍左右。
6. 卡尔曼滤波的引入:坐标平滑与预测
6.1 为什么纯SAD匹配还不够稳
SAD匹配每帧只输出一个坐标,但这个坐标不是绝对平滑的。光照抖动、图像噪声、目标快速运动带来的动态模糊,都会让搜索框在小范围内跳来跳去。如果你在显示端叠加一个搜索框,这种抖动会特别明显,感觉不是跟踪而是“颤抖”。
另外一个更严重的问题是快速运动目标的丢帧问题。当目标在两帧之间的位移大于搜索窗口的覆盖范围时,SAD匹配直接找不到最优了,系统失锁。纯SAD算法无法处理这种情况,必须引入运动预测。
6.2 卡尔曼滤波器在FPGA里的定点化处理
卡尔曼滤波器的经典公式网上到处都有,这里不重复推导,重点讲FPGA定点化实现。卡尔曼滤波器核心涉及矩阵乘法和协方差更新,分别是加法和乘法运算——FPGA有DSP48可用,但在高帧率下做浮点太奢侈。所以我全部做了定点化。
状态量定义为1维位置和1维速度,甚至不用矩阵写法,展开成标量方程即可:
预测: x_pred = x_est + v_est * dt v_pred = v_est P_pred = P_est + Q
更新: K = P_pred / (P_pred + R) x_est = x_pred + K * (z - x_pred) P_est = (1 - K) * P_pred
其中z是当前帧SAD输出的测量坐标。定点化时,位置单位取像素,速度单位取像素/帧,协方差用Q4.12格式,乘法用DSP48处理,除法用移位和查表近似。这套公式在FPGA里实现起来不到五百行代码,却能显著改善跟踪稳定性。
6.3 卡尔曼输出与搜索范围自适应的联动
加入卡尔曼滤波后,SAD搜索窗口不再固定,而是根据预测速度和上一帧位置自动调整。比如预测的目标位置是(x_pred, y_pred),那么搜索窗口中心就设在(x_pred, y_pred),搜索半径根据速度大小动态调整:速度越快半径越大,预留足够的搜索裕量。
这个联动在工程上是一个实质性的性能提升手段,它一方面让搜索区域“跟着目标走”,另一方面通过缩小搜索区域来降低计算量。实测下来,固定搜索窗口128x128时CPU资源占用大约43%,而自适应搜索窗口约80x80,资源占用能降到28%左右,同时失锁率还不到原来的三分之一。
7. 调试踩坑实录:七个高频问题速查
7.1 OV5640配置寄存器反复失效
老生常谈但老有人踩坑:OV5640的初始化序列必须用I2C逐条写入,每条寄存器写入后要插入延时。我一开始图快把几百条配置写到一个连续数组里,频率调很快,摄像头就间歇性不出图。后来在每条写操作之间加了大约100us延时,瞬间稳定。
7.2 灰度转换后的偏色和对比度问题
RGB转灰度我用的是经典公式Y = 0.299R + 0.587G + 0.114B。硬件实现时系数移位近似后会出现轻微偏色,跟踪问题还不大,但叠加显示框时颜色容易和背景融合。解决办法是加法树实现,而不是直接用乘法器,否则综合出的DSP数量会多到肉疼。
7.3 FMC读回来全为零或全为F
FMC通信最常见问题是数据线和地址线的映射错位,多半是原理图上标号和FPGA引脚约束没对齐。检查思路很简单:STM32写一个0x55到某个寄存器,FPGA这边用ILA抓FPGA内部信号,如果FPGA里看到了0x55而不是0x00或0xFF,说明数据线是对的;再写几个不同地址的数据验证地址线。
7.4 FPGA内部寄存器的复位时序问题
FPGA内的寄存器上电默认值不定,而STM32上电后可能立即发起FMC读,导致读回来的状态全是不定值。这个问题的根因是复位电路没有hold住。我加了一个上电复位延时计数器,让FPGA在配置完成后再额外延迟20ms才释放全局复位,状态寄存器全部赋初值。问题立即消失。
7.5 模板更新导致搜索框移偏
动态模板更新如果直接每帧都更新,搜索框会被噪声带跑。这是模板漂移问题。我最后的策略是:只有当当前帧的最小SAD值比历史平均值低20%以上时,才用小块逐步更新动态模板,同时初始模板保持不动。这样既跟上变化又不至于过度污染。
7.6 DDR读写带宽不足的表现
高分辨率下偶尔会出现匹配结果滞后一整帧的错觉,实际上是DDR带宽占满了。排查方法是通过MIG的状态计数器看bandwidth utilization,我实测720p单路灰度视频读写只占不到15%,但如果同时加了OSD叠加和缩放模块,占用率会冲上40%。优化方法是把OSD和缩放合并成一趟读取,不要每加一个模块就多读一遍整帧。
7.7 Vivado综合后时钟频率上不去
这问题基本逃不开“关键路径在哪”的疑问。用report_timing_summary看WNS(最差负时序裕量),WNS为负的那条路径就是瓶颈。常见解法是回读代码检查有没有在组合逻辑里串了太多运算符,尤其是多层嵌套的加法或条件判断。我的SAD累加器第一版就是嵌套了三层条件判断,回读后发现关键路径几乎全在那一小段上,后来改成分级流水线一次解决。
8. 优化扩展方向:从SAD到更高级的跟踪策略
8.1 基于BISS-C接口的高速编码器数据融合
如果你的目标跟踪是在运动平台上做的,比如云台、AGV,那么目标在图像里的位置变化里其实混合了平台自身的姿态变化和目标的真实移动。这种情况下,引入编码器反馈可以大幅提升跟踪稳定性。热词里提到的“FPGA BISS-C”就是指用FPGA的IO高速解码BISS-C协议编码器数据,然后直接和视觉坐标做融合。FPGA在这里的优势是BISS-C时钟频率能跑到10MHz以上,数据延迟极低,能做到真正的实时闭环。
8.2 Zynq软核方案与纯FPGA方案的取舍
部分项目会引入Zynq处理器,在PL端做SAD算法,PS端跑Linux系统和上层视觉库。好处是开发灵活、调试方便,缺点是需要处理PL-PS之间的地址映射和数据搬运逻辑,复杂度会显著增加。如果只是要求“SAD模板匹配+卡尔曼滤波”,我觉得纯FPGA方案更可控,毕竟整个数据流都在硬件里打转,一个时钟一个位置的状态机,不容易出现软件卡死拖后腿的情况。
8.3 面向PCIe的高速视觉采集卡扩展
往更大规模走,这个SAD匹配核完全可以作为一块PCIe视频采集卡上的算子单元。热词里的“FPGA PCIe RC例子”就是指FPGA做Root Complex主动发起DMA写操作,把SAD匹配结果、目标坐标、甚至原始帧数据直接写到上位机内存。我自己在一个工业检测项目里试过这个方向,思路也是大致相同的。上了PCIe之后,跟踪结果可以以极低延迟送入主机,做历史轨迹记录和数据分析,整个系统就从“嵌入式单机跟踪”升级为“机台级智能视觉模块”。
9. 最后分享一点个人经验
做完这个项目再回看,最大的感受是:SAD模板匹配算法本身非常简单,但把一个简单算法在FPGA上做到稳定、高效、可维护,考的是对整个数据流和资源分配的通盘理解。模板匹配只是把候选窗口和模板做减法求和,真正的价值在于你愿意为每一路像素设计多少并行度、为每一帧搜索分配多大搜索窗口,更在于目标会变化时,你用什么策略让匹配结果不漂移、不丢失。
如果你是从零开始起步,我的建议是不要一开始就追求大模板大搜索框。先用16x16模板、64x64搜索区域跑通全流程,把SAD核心做出来,把和STM32的通信链路打通,然后再逐渐加并行度和搜索范围。这个项目真正的门槛不是那一堆加法器,而是你把SAD当成一个“硬件组件”来思考的习惯——它的实时性来自电路级并行,它的稳定性来自状态机控制和预测滤波,不是单纯改几个参数就能解决的。
后续如果你想扩展,路径也很清晰:增加BISS-C接口融合、升级成Zynq软硬协同、或者做PCIe采集卡,都是在现有SAD核心上做的增量开发。但一切扩展都依赖于你的基础框架是否扎实,至少对我来说,把SAD在FPGA上做到这个程度之后,再回去看那些复杂的视频算法,会少很多畏惧感。