ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

FPGA实现SAD模板匹配目标跟踪:从算法到RTL的工程实践

FPGA实现SAD模板匹配目标跟踪:从算法到RTL的工程实践 先说结论用FPGA做SAD模板匹配目标跟踪核心不是“会不会写SAD”而是“怎么把SAD算得足够快、足够省资源、时序还能收敛”。我最早是在一个实时视频处理项目里接触到这个需求的当时要在1080p60fps的输入流里框住一个运动目标CPU和GPU的方案要么延迟太高要么功耗和成本压不住最后把目光落在了FPGA上。折腾了小半年从算法仿真到板级调通期间踩了不少坑也总结出一些我觉得比较有价值的设计思路这里完整记录下来。1. SAD算法原理与FPGA方案选型为什么是SAD为什么是FPGA1.1 SAD的数学本质与计算特征SAD的全称是Sum of Absolute Differences即绝对差值和。它的数学表达式非常简洁[ SAD(x, y) \sum_{i0}^{M-1} \sum_{j0}^{N-1} |I(xi, yj) - T(i, j)| ]其中T是M×N大小的模板图像I是搜索区域中同样大小的候选块SAD值越小说明候选块和模板越相似。目标跟踪的任务就是在下一帧图像的搜索区域内遍历所有可能的候选位置找到SAD值最小的那个点作为目标的新位置。这个算法在软件里实现非常直白两层循环遍历搜索区域每个位置再做一次M×N的减法、取绝对值、累加。但正是这种“直白”带来了巨大的计算量。假设模板大小是32×32搜索区域是100×100那意味着要计算(100-321)² ≈ 4761个候选位置每个位置要做1024次减法、1024次取绝对值和1024次累加总计约1500万次操作——这是单帧单目标的计算量。要是目标数量更多、搜索区域更大、帧率要求更高计算量会迅速膨胀到难以接受的程度。所以SAD算法的特征非常鲜明计算密集、数据并行度极高、控制逻辑简单。这恰恰是FPGA最擅长处理的运算类型。FPGA的并行计算能力在这里体现得淋漓尽致——它不像CPU那样一个时钟周期只能执行有限条指令而是可以通过硬件描述语言把成百上千个计算单元同时铺开让所有候选位置的SAD计算同时进行。1.2 FPGA方案与CPU、GPU的边界对比很多人在项目立项时会纠结一个问题现有方案那么多为什么非得用FPGA我自己做过的对比评估大致是这样的维度CPU方案GPU方案FPGA方案峰值算力中低极高中高但高度定制功耗高极高较低延迟毫秒级受OS调度影响毫秒级PCIe传输驱动开销微秒级纯硬件流水开发周期短中等长确定性差受系统负载影响中等极好时钟级确定环境适应性差需要整机差体积大好可单板集成CPU方案的优势在于开发效率OpenCV里一行matchTemplate就能搞定但对实时视频流来说这个“一行代码”背后是每秒上亿次的内存访问和计算帧率一高就吃力。GPU方案算力充足但PCIe传输延迟和数据拷贝开销对“目标跟踪”这种对实时性极其敏感的应用来说有时候反而是致命伤。FPGA方案的突出优势体现在三个方面第一是低延迟数据从传感器进入FPGA后可以直接在流水线上处理不需要经过内存拷贝和操作系统调度延迟能做到微秒级第二是高确定性每个时钟周期做什么都是固定的不存在CPU那种“有时候快有时候慢”的问题第三是低功耗整颗FPGA芯片的功耗通常只有几瓦到几十瓦远低于GPU动辄几百瓦的功耗这在嵌入式场景无人机、机器人、工业检测设备里是硬性指标。1.3 什么样的目标跟踪场景适合用SADFPGASAD算法本身相对“朴素”它没有特征点提取、没有机器学习模型但它有一个突出的优点对纹理清晰、形变小的刚性目标非常有效。比如工业流水线上检测工件位置、无人机锁定地面车辆、机器人视觉伺服追踪标记物等场景目标外观在连续帧之间变化不大SAD算法配合合适的搜索策略完全能胜任。另一个选择FPGASAD组合的重要理由是可定制性和接口灵活性。很多视觉系统不只做目标跟踪还要同时处理多路视频输入、控制云台或机械臂、输出触发信号等。FPGA可以在同一颗芯片里把这些功能全部集成用硬件逻辑实现传感器接口MIPI、LVDS、SDI、图像预处理滤波、增强、SAD计算、结果输出和控制信号生成。这种“一站式”集成能力是纯软件方案很难比的。2. SAD算法硬件化的核心思路流水线设计、并行度规划与资源权衡2.1 从软件到硬件的思维转换把SAD算法从C语言搬到FPGA第一步要完成的是思维模型的转换。软件是“顺序执行”的思维先读数据、再做计算、再写结果每一步都在一个“大循环”里完成。硬件则是“空间展开”的思维数据从一个模块流向另一个模块所有模块同时工作就像工厂里的流水线——不同的零件在不同工位同时被加工而不是一个工人依次加工完所有零件。具体到SAD算法硬件化设计要考虑三个核心问题数据怎么存、计算怎么做、结果怎么比。这三个问题环环相扣任何一个没想清楚后面的综合实现都会出问题。2.2 模板数据的存储与复用策略模板数据是整个SAD计算的基准它在跟踪过程中是基本固定的或周期性更新。我的做法是把模板数据放在FPGA的片上Block RAMBRAM里而不是外部DDR。这个选择的理由很直接BRAM的访问延迟固定一般1-2个时钟周期而且可以做到多端口并行读取DDR虽然容量大但访问延迟高、带宽受总线调度影响大如果模板数据在计算过程中被反复读取DDR会产生大量突发访问严重影响吞吐率。模板大小需要精心权衡。我在项目中用的是24×24的模板BRAM占用大约是24×24×8bit 4608bit约4.5Kb对现代FPGA动辄几Mb的BRAM资源来说微不足道。如果模板切得太大比如64×64一方面BRAM消耗会显著上升另一方面并行计算单元的规模也会膨胀如果模板太小比如8×8跟踪的鲁棒性又会下降容易被噪声干扰或目标局部纹理欺骗。24×24是我在工程实践里认为比较折中的选择。2.3 滑动窗口与像素流式的搜索区域复用SAD计算最直观的实现方式是“整块比较”每算一个候选位置从DDR里读出一个和模板一样大的候选块然后做逐像素比较。但这种方式存在两个问题一是DDR访问量巨大每个候选位置都要读取M×N个像素计算量还乘以了2带宽很快就会耗尽二是数据复用率低相邻候选块之间有大量重叠区域这些像素被反复从DDR读出来非常浪费。更聪明的做法是用滑动窗口的思想配合FPGA的“行缓冲”Line Buffer来构建数据流。核心逻辑是这样的在FPGA内部维护若干行像素缓存行数取决于模板高度数据从传感器或DDR输入后按行逐像素填充这些缓存每当新的像素进入整个窗口就向右滑动一列窗口到达行的末尾时换到下一行继续滑动这样每个输入像素只需要从外部存储读一次但它会被用于计算多个候选位置——被完整地复用了。具体来说我用的搜索区域是64×64模板是24×24那么候选位置总共有(64-241)² 1681个。如果不用滑动窗口每个候选位置都要读576个像素总计约96万次像素读取用滑动窗口后整个搜索区域只需要4096次像素读取64×64数据访问量下降了99%以上。这个差距是决定性的。2.4 并行度规划全并行、行并行还是部分并行接下来是SAD计算阵列的设计。这里有个基本的资源-性能权衡并行度越高、计算速度越快但消耗的DSP数字信号处理单元和LUT查找表资源也越多。我总结下来有三种典型的并行架构第一种是全并行架构。把M×N个绝对差计算单元全部展开一个时钟周期算完一个候选位置的完整SAD值。24×24模板就需要576个减法器、576个绝对值计算单元和一个大型加法树。这种方案延迟最低但资源消耗非常大而且加法树的级数需要11级左右会拉长组合逻辑路径时序收敛比较难。除非模板非常小否则我不建议第一版就这么干。第二种是行并行架构。以模板的一行为单位并行计算一个候选块中每一行的SAD部分和然后用加法树把这M个行和加起来。同样以24×24为例需要24个减法器和24个绝对值单元加法树的规模比全并行小一个数量级。这个方案在资源和速度之间比较平衡适合模板中等的场景。第三种是部分并行架构。只例化K个计算单元每个时钟周期处理K个像素的绝对差通过多周期累加完成整个候选块的计算。比如24×24的模板如果K24就需要24个时钟周期算完一个候选位置。这种方案资源最省但吞吐率最低适合对实时性要求不高的场景。我的做法是选择“行并行”和“部分并行”的组合每个时钟周期并行处理一行24个像素24个时钟周期算完一个候选位置。配合像素流的滑动窗口整个搜索区域的1681个候选位置可以在大约1681×24个时钟周期内扫描完。在200MHz的时钟下这个耗时大约200微秒远低于一帧视频的可用时间预算约16.6ms60fps。2.5 搜索区域的遍历策略与提前终止机制全搜索Full Search是最暴力的做法——所有候选位置都算一遍然后取最小值。它的优点是实现简单、结果最优缺点是计算量最大。在实际项目中我一般会根据移*动目标的运动特性做优化。目标的运动在相邻两帧之间是有连续性的不太可能瞬间跳到很远的位置。所以一个非常实用的优化是搜索中心预测 有限搜索范围用上一帧的目标位置作为中心只在其周围一定范围内比如左右各32像素、上下各32像素计算SAD值而不是在全图范围内遍历。这样既保证了跟踪的实时性又不牺牲过多的准确度。另一个技巧是提前终止Early Termination。在扫描一个候选位置的过程中如果累加的部分SAD值已经超过了当前已经找到的最小SAD值那这个候选位置就不可能是最优解了可以直接终止计算跳到下一个候选位置。这个技巧在跟踪场景下尤其有效因为目标在连续帧中的位置变化通常不大第一个或前几个候选位置往往就能找到不错的SAD值后续大量候选位置在计算过程中就会被提前淘汰。我在实测中发现加上提前终止后搜索区域的平均计算量可以减少30%-50%具体效果取决于目标的运动速度和搜索区域的初始化。3. 核心模块的RTL设计与实现细节3.1 顶层架构与模块划分从顶层来看我把整个系统划分为以下几个模块顶层模块 (sad_tracker_top) ├── 像素输入接口 (pixel_rx) ├── 行缓冲控制器 (line_buffer_ctrl) ├── 模板存储模块 (template_mem) ├── SAD计算阵列 (sad_array) ├── 最小值搜索模块 (min_search) ├── 跟踪状态机 (tracker_fsm) └── 结果输出接口 (result_tx)这里我特别强调一下“模块边界清晰”的重要性。FPGA开发和软件开发的共同点是——模块化设计能显著降低调试难度。每个模块都有明确的输入输出接口信号命名规范统一这对后续的仿真验证和板级调试都很有帮助。3.2 行缓冲控制器的设计要点行缓冲是SAD计算的关键前置模块它的设计质量直接决定数据流的顺畅程度。我的实现思路是// 以24行行缓冲为例每行缓存64个像素搜索区域宽度 // 使用移位寄存器实现每来一个像素所有行的数据同步右移 reg [7:0] line_buf [0:23][0:63]; always (posedge clk) begin if (pixel_valid) begin for (int i 0; i 24; i i 1) begin for (int j 63; j 0; j j - 1) begin line_buf[i][j] line_buf[i][j-1]; end end // 新像素进入第一行 line_buf[0][0] pixel_data; // 其余行从上一行的对应位置移入 for (int i 1; i 24; i i 1) begin line_buf[i][0] line_buf[i-1][63]; // 这里需要仔细设计行间衔接 end end end实际设计时要特别注意行间的衔接逻辑。上面的代码只是一个简化示意真实场景中行缓冲的数据移动需要配合像素坐标和行列计数器精确控制否则会出现错位问题。行缓冲的存储资源消耗也不容忽视。24×64×8bit 12288bit ≈ 12Kb在小规模FPGA上这个是完全可以接受的。如果搜索区域更大比如128×128就需要考虑用BRAM替代寄存器来实现行缓冲以节省寄存器资源。3.3 SAD计算阵列的RTL实现SAD计算阵列是整个设计的心脏。在行并行方案下它的核心逻辑是module sad_row_calc #( parameter TEMPLATE_WIDTH 24, parameter DATA_WIDTH 8 )( input wire clk, input wire rst_n, input wire [DATA_WIDTH-1:0] search_pixels [0:TEMPLATE_WIDTH-1], // 搜索区域当前行的24个像素 input wire [DATA_WIDTH-1:0] template_pixels [0:TEMPLATE_WIDTH-1], // 模板对应行的24个像素 output wire [DATA_WIDTH5:0] sad_row_sum // 13bit24个8bit数累加最大值为24*255 ); // 第一级并行减法并取绝对值 wire [DATA_WIDTH-1:0] abs_diff [0:TEMPLATE_WIDTH-1]; generate genvar i; for (i 0; i TEMPLATE_WIDTH; i i 1) begin : abs_diff_gen assign abs_diff[i] (search_pixels[i] template_pixels[i]) ? (search_pixels[i] - template_pixels[i]) : (template_pixels[i] - search_pixels[i]); end endgenerate // 第二级加法树24输入加法树5级 // 这里使用树形结构逐级两两相加减少组合逻辑深度 // 实际实现时可以用流水线寄存器切分提高时钟频率 reg [DATA_WIDTH5:0] sum_stage1 [0:11]; reg [DATA_WIDTH6:0] sum_stage2 [0:5]; reg [DATA_WIDTH7:0] sum_stage3 [0:2]; reg [DATA_WIDTH8:0] sum_stage4 [0:1]; reg [DATA_WIDTH9:0] sum_stage5; // 每个时钟周期完成一级加法最终结果延迟5个时钟周期 // ... endmodule这段代码里有两个值得展开说的设计决策第一是绝对值计算。FPGA里没有现成的绝对值指令但可以巧妙地利用比较器加减法器来实现。我的写法是先比较两个数的大小然后用大的减小的。这样虽然消耗两个减法器的资源实际只有一个是有效输出但逻辑路径短、时序好。另一种常见的做法是使用补码特性做条件取反但那样会增加额外的异或门开销对比下来并不划算。第二是加法树的流水线切分。24个8bit数求和如果一次性用组合逻辑完成会产生很深的加法链大约4-5级加法的组合延迟在200MHz以上时钟下极难收敛。我的做法是将加法树分5级流水线第一级把24个数分成12对相加第二级把12个结果分成6对相加依此类推最后得到1个结果。每级之间用寄存器切分这样每级组合逻辑只有一条简单加法时钟频率可以显著提高。3.4 最小值搜索模块在线比较与索引追踪最小值搜索模块的功能是在所有候选位置的SAD值中找出最小值并记录其坐标。这个模块和SAD计算阵列直接相连采用了“在线比较”的策略module min_search #( parameter DATA_WIDTH 16, parameter COORD_WIDTH 8 )( input wire clk, input wire rst_n, input wire sad_valid, // SAD结果有效信号 input wire [DATA_WIDTH-1:0] sad_value, input wire [COORD_WIDTH-1:0] pos_x, input wire [COORD_WIDTH-1:0] pos_y, output reg [DATA_WIDTH-1:0] min_sad, output reg [COORD_WIDTH-1:0] min_x, output reg [COORD_WIDTH-1:0] min_y ); always (posedge clk or negedge rst_n) begin if (!rst_n) begin min_sad {DATA_WIDTH{1b1}}; // 初始化为最大值 min_x 0; min_y 0; end else if (sad_valid) begin if (sad_value min_sad) begin min_sad sad_value; min_x pos_x; min_y pos_y; end end end endmodule这里有个容易踩坑的小细节min_sad的初始值必须设为最大值全1而不是0。我见过不少初学者在这里用 0初始化导致最小值搜索永远停留在初始值整个模块输出的目标位置永远是(0,0)。另外一个实用技巧是当多个候选位置的SAD值相同时可以优先保留先出现的那个坐标更小的这样在目标跟踪中能避免目标位置的抖动。这个规则在模块里就体现了——只有严格小于才更新等于时不更新。3.5 跟踪状态机的设计与模板更新策略整个跟踪过程由一个有限状态机FSM控制我的状态划分是这样的IDLE等待启动信号收到后进入TEMPLATE_INIT状态TEMPLATE_INIT从搜索区域中心位置提取模板数据存入模板存储模块SEARCH执行SAD搜索扫描整个搜索区域的所有候选位置RESULT_OUT输出最小SAD值对应的坐标作为目标的当前位置TEMPLATE_UPDATE根据新位置从当前帧提取新的模板更新模板存储模块WAIT_NEXT_FRAME等待下一帧开始信号进入SEARCH状态。其中模板更新策略是目标跟踪效果好坏的关键。如果完全不更新模板目标外观一旦发生变化光照变化、目标旋转、尺度变化跟踪就会逐渐漂移最终丢失如果每帧都全量更新模板又可能出现“模板污染”问题——一旦某一帧跟踪结果有偏差模板会被错误地刷新之后的偏差会越来越大最终导致跟丢。我的做法是条件更新只有当当前帧的最小SAD值低于阈值时才用新的搜索结果更新模板如果SAD值较大说明当前帧的目标匹配置信度不高保留原模板等待后续帧重新确认。阈值的选择需要通过实验标定我建议设为模板总像素数的某个比例比如10%-20%。例如24×24模板SAD总阈值可以设置在5000左右24×24×255×0.1 ≈ 14688实际可以更宽松一些这个数值需要结合具体应用场景调整。4. 时序约束、资源优化与实测结果分析4.1 时序收敛的实战调整加法树、扇出与关键路径FPGA设计从功能仿真通过到板级跑通之间最大的拦路虎几乎都是时序问题。我最初在200MHz时钟下综合时序报告一片通红关键路径集中在SAD计算阵列的加法树上。排查后发现主要问题出在三个方面第一个是加法树的组合逻辑过深。虽然我按5级流水线切分了24输入加法树但每一级的加法器位宽不同从8bit一直扩展到13bit、14bit、15bit级联起来组合延迟依然偏大。我的解决方法是使用Vivado中的retiming选项并手动在加法树的关键节点插入额外的流水线寄存器把加法树拆得更细。第二个是模板数据扇出过大。模板存储模块输出的24个像素需要同时广播到24个SAD计算单元总扇出达到576个负载导致布线拥塞和延迟增加。解决办法是复制模板存储——在FPGA里例化多份相同的模板BRAM每个计算单元只连接其中的一份这样扇出被显著降低。代价是BRAM资源增加但在模板不大时完全划算。第三个是行缓冲的输出使能逻辑复杂。行缓冲模块里为了处理行间衔接生成的控制信号有大量组合逻辑导致路径延迟超标。我后来简化了控制逻辑改用预先计算的地址偏移配合计数器来控制减少了组合逻辑层级时序问题迎刃而解。4.2 资源占用与功耗评估在我的实际项目中使用的FPGA是Xilinx Artix-7系列的XC7A100T最终的资源占用情况如下资源类型使用量总量占用率LUT12,63463,40019.9%Flip-Flop8,209126,8006.5%BRAM(36Kb)121358.9%DSP48E102400%这里有个值得注意的点整个SAD计算阵列没有使用DSP单元。因为绝对差和加法树都可以用LUT和进位链实现对于8bit精度的小规模计算LUT实现反而更灵活不会因为DSP资源布局问题导致布线拥塞。当然如果模板宽度更大、并行度更高用DSP做多路加法也是可行的优化方向。功耗方面在200MHz时钟下整颗FPGA的实测功耗大约是2.3W其中动态功耗约1.8W、静态功耗约0.5W。这个功耗水平在嵌入式视觉系统里非常友好A78的芯片配个普通的散热片就完全够了。4.3 实测性能与跟踪效果整个系统的实测表现是在1080p60fps的视频流中对单个刚性目标的跟踪延迟约230微秒从输入像素到输出目标坐标其中SAD搜索过程占约200微秒其余为流水线延迟和状态机切换开销。在目标以中等速度运动、外观变化不大的场景下跟踪稳定不丢帧。我特别关注的一个指标是“搜索区域的扫描效率”。在加入提前终止机制后实际的平均计算量约为原始全搜索的55%左右搜索性能提升明显。不过在目标快速运动或者被暂时遮挡的场景下提前终止的收益会下降因为最优候选位置可能出现在搜索区域的边缘导致大部分候选位置都需要完整计算。5. 系统调试与验证过程中的关键问题记录5.1 功能仿真正确但板级结果错误的排查方法这是FPGA开发中最让人头疼的问题之一仿真一切正常上了板子就是不对。我在这个项目里遇到了一个典型的案例——输出的目标坐标偶尔会跳变到搜索区域边缘并且SAD值异常大。排查过程是这样的先用ILA逻辑分析仪抓取SAD计算阵列的输出发现某些时钟周期出现了全0的SAD值而正常情况下应该有个非零的基础值。顺着数据流往前查发现行缓冲模块在某些特定坐标时会输出全0数据。最终定位到问题根源是行缓冲的行间数据衔接逻辑。我在处理“新一行的第一个像素进入时上一行的最后一个像素如何移入下一行”这个细节时边界条件写错了导致在第24、48等特定列位置行缓冲内出现了一行全0数据。修改了行间衔接的地址控制逻辑后问题彻底消失。这个经历给我的教训是行缓冲这类与坐标强相关的模块边界条件的仿真用例必须覆盖行首、行尾、首个像素、最后一个像素等极端情况否则仿真很难发现隐患。5.2 模板更新中的“死锁”现象另一个值得一提的问题是模板更新逻辑的一个隐蔽Bug。我在设计模板更新策略时要求“只有当SAD值低于阈值时才更新模板”。这个逻辑本身没有问题但有一个边界情况没考虑到如果目标刚进入画面时搜索区域中心位置并不完全是目标比如目标只占了一半搜索区域初始模板本身就“不干净”之后每帧的SAD值都可能超过阈值导致模板永远不会更新——跟踪器失去了自我修正能力。解决方法是增加了“强制更新”机制如果连续超过30帧SAD值都超过阈值就认为模板严重过时强制用当前帧的最小SAD位置提取新模板。这个机制在很大程度上改善了跟踪的鲁棒性特别是在目标外观随着视角变化而缓慢演变的场景中。5.3 PCIe/HDMI等接口集成时的注意事项在实际部署中FPGA很少单独工作总要接各种接口。我在集成HDMI输入输出时遇到的问题是HDMI的像素时钟和FPGA内部的SAD计算时钟不同域需要做跨时钟域处理。最简单的做法是用FIFO做异步缓冲把HDMI像素流先存入FIFO再用SAD计算时钟域读取。这块属于成熟技术但有一点容易忽略——FIFO的深度一定要按照最大突发长度Burst Length来计算而不是平均速率。我当时按平均速率算的FIFO深度结果在画面快速切换时出现了数据溢出丢像素目标跟踪短暂失效。这个问题最后通过增大FIFO深度解决了另外我还在输入端加了一个像素有效信号de的同步处理彻底消除了亚稳态风险。5.4 三帧对齐与流水线延迟补偿跟踪系统里有个细节容易被软件工程师忽略但硬件工程师必须面对输入数据、处理结果和输出坐标之间是有流水线延迟的。比如我们在第N帧的第1000个像素输入后才开始计算SAD但计算结果可能要到第N帧的第2000个像素输入时才能出来。如果直接把结果用于控制云台或标注显示会出现“控制滞后”现象。我的做法是在跟踪状态机里记录一个“流水线延迟计数器”计算出从搜索区域起始像素到SAD结果输出的精确时钟周期数。在输出目标坐标时把目标位置从“搜索起始时刻的坐标系”换算到“当前时刻的坐标系”也就是加上一个预测位移量。这个位移量可以用目标的历史运动速度来估计即卡尔曼滤波的雏形如果做平滑跟踪可以接一个简单的卡尔曼滤波器做后处理。6. 从工程角度对SAD硬件化的综合评判与优化展望6.1 当前方案的优缺点复盘如果让我用一句话评价FPGASAD做目标跟踪这个技术路线我会说在小规模、高实时性、低功耗的嵌入式视觉场景中它是一个极具竞争力的选择但在模板匹配精度要求极高、目标外观复杂多变的场景中它存在明显的天花板。优点方面不再重复前面已经阐述得比较充分。局限方面主要有两个一是SAD本身是像素级的相似度度量对光照变化、目标形变、尺度变化非常敏感。同一个目标如果角度偏了5度、光照暗了两档SAD值就会急剧上升跟踪稳定性大打折扣。二是FPGA的开发周期和调试成本确实比软件高一个量级。RTL仿真、时序收敛、板级调试每一步都需要专业技能。但一旦架构确定、模块复用起来后期的增量开发效率还是不错的。6.2 多尺度模板匹配与多目标扩展思路针对SAD对尺度变化敏感的问题一个实用的扩展方向是“多尺度模板匹配”在FPGA中例化多套不同尺寸的模板比如16×16、24×24、32×32每套模板并行计算SAD最后在搜索模块里取所有尺度的最小值。这样可以在目标靠近/远离摄像头时保持稳定的跟踪。资源代价是模板存储和计算阵列都翻倍但在中等规模的FPGA上仍然可以接受。多目标跟踪的扩展相对复杂核心瓶颈在于SAD计算阵列需要为每个目标分别分配一套计算资源。如果是两个目标就把计算阵列例化两份如果目标数量动态变化需要引入任务调度的状态机在多个目标之间时分复用计算阵列。后者在逻辑上更复杂但资源效率更高。6.3 结合卡尔曼滤波与光流法的性能增强路径SAD跟踪输出的坐标往往带有一定的噪声和抖动特别是在目标运动不规律或图像有噪声时。我的建议是在SAD结果后续接卡尔曼滤波对目标位置做平滑和预测。卡尔曼滤波器在FPGA上的实现不算复杂——核心就是一个状态预测方程和一个更新方程涉及几次矩阵乘法和除法可以用移位近似替代。加上卡尔曼滤波后跟踪的稳定性会有明显提升实测中目标位置的抖动幅度大约能降低60%-70%。如果还想进一步增强鲁棒性可以考虑在SAD基础上引入“光流法”做局部修正先用SAD锁定粗略位置再用LK光流法在局部窗口内计算目标的精确位移。这种“粗定位精修正”的组合策略在FPGA上是可以流水线实现的但复杂度较高适合在有经验的团队里推进。最后还是分享一个我个人的体会做FPGA图像处理最大的成就感不是看代码综合通过、板子跑通的那一刻而是当你通过硬件架构设计把一个在通用处理器上需要几毫秒才能完成的运算压缩到几百微秒并且功耗只有几瓦的时候你真正感受到了“用空间换时间”这句硬件设计名言的份量。硬件思维和软件思维是很不一样的但一旦跨过那道坎你会发现FPGA世界里的每一个时钟周期都充满确定性每一个逻辑门都在执行明确的任务这种掌控感是其他开发方式很难给的。如果这篇文章能帮你少走几步弯路那这些时间花得就值了。
返回列表