現(xiàn)SAD模板匹配:從算法到Verilog的實(shí)時(shí)目標(biāo)跟蹤實(shí)戰(zhàn))
1. 從圖像數(shù)據(jù)流到目標(biāo)坐標(biāo)SAD算法的硬件友好性拆解做FPGA圖像處理這幾年我最大的體會(huì)是很多在軟件里隨手就能寫的算法搬到硬件上完全是另一回事。SAD模板匹配恰好是少有的天生適合FPGA的算法之一這也是為什么很多實(shí)時(shí)目標(biāo)跟蹤項(xiàng)目的第一版原型都會(huì)選它。SADSum of Absolute Differences絕對(duì)差值和的原理簡單到一句話就能說清拿一個(gè)已知的目標(biāo)模板在視頻幀的搜索區(qū)域內(nèi)逐個(gè)位置滑動(dòng)計(jì)算模板與對(duì)應(yīng)圖像塊的像素差值絕對(duì)值之和和最小的位置就是目標(biāo)最可能出現(xiàn)的地方。聽起來就是一個(gè)雙重循環(huán)加累加器的事在CPU上寫起來確實(shí)也就十幾行代碼。但放到實(shí)時(shí)視頻處理場景里事情就沒那么輕松了。以1080p60fps的輸入為例一幀圖像大約有200萬個(gè)像素留給每幀的處理時(shí)間只有16.7毫秒。如果搜索區(qū)域是100×100像素模板是32×32像素暴力遍歷需要計(jì)算的候選位置接近一萬個(gè)每個(gè)位置要做1024次減法、取絕對(duì)值和累加。這個(gè)計(jì)算量在通用處理器上做軟件優(yōu)化能跑到實(shí)時(shí)但CPU占用率基本就滿了。而在FPGA上SAD的計(jì)算結(jié)構(gòu)可以被完全并行化和流水線化同樣的工作量占用資源很少還能把整條圖像處理鏈路的時(shí)間壓到微秒級(jí)。不過這里要提醒一句SAD在FPGA上好實(shí)現(xiàn)不等于隨便寫寫就能跑。實(shí)際工程里最麻煩的往往不是算法本身而是數(shù)據(jù)怎么喂進(jìn)來、結(jié)果怎么送出去、時(shí)序怎么收斂。所以這篇文章我會(huì)從算法公式出發(fā)一路講到具體的Verilog代碼結(jié)構(gòu)、乒乓緩存設(shè)計(jì)、時(shí)序約束技巧最后聊一聊實(shí)測性能和踩過的坑。先看SAD的數(shù)學(xué)表達(dá)。假設(shè)模板T的大小是M×N搜索圖像I中的候選塊與模板的SAD值為SAD(x, y) Σ(i0 to M-1) Σ(j0 to N-1) |I(xi, yj) - T(i, j)|其中(x, y)是候選塊左上角在搜索區(qū)域中的坐標(biāo)。遍歷整個(gè)搜索區(qū)域后取SAD值最小的位置作為跟蹤結(jié)果。這個(gè)公式?jīng)]有任何復(fù)雜的非線性運(yùn)算只有減法、絕對(duì)值、加法三種操作映射到FPGA上就是減法器、絕對(duì)值邏輯和加法樹。整個(gè)計(jì)算鏈路的延遲只有幾個(gè)時(shí)鐘周期吞吐量極高。1.1 為什么模板匹配在target跟蹤場景仍不過時(shí)現(xiàn)在深度學(xué)習(xí)目標(biāo)檢測很火動(dòng)不動(dòng)就是YOLO、Transformer似乎傳統(tǒng)方法已經(jīng)被淘汰了。但實(shí)際做工程項(xiàng)目你會(huì)發(fā)現(xiàn)很多場景根本用不上那么重的方案。第一FPGA上跑神經(jīng)網(wǎng)絡(luò)推理尤其是視頻流實(shí)時(shí)處理資源開銷非常大。一個(gè)稍微像樣點(diǎn)的卷積網(wǎng)絡(luò)即使做了量化也要消耗大量的DSP和BRAM資源邏輯資源和功耗也跟著上去。而SAD模板匹配只用到加法器和比較器幾乎不碰DSP資源邏輯資源消耗也很小在入門級(jí)芯片上就能跑得很輕松。第二當(dāng)目標(biāo)是預(yù)先知道的小尺寸剛體比如零件、標(biāo)記點(diǎn)、特定紋理區(qū)域模板匹配的精度和穩(wěn)定性完全夠用。目標(biāo)跟蹤不等于一定要做語義級(jí)別的理解很多工業(yè)場景只需要知道目標(biāo)在畫面里的像素坐標(biāo)就夠了。第三模板匹配算法是確定性的行為完全可預(yù)測這對(duì)通過安全認(rèn)證或功能驗(yàn)證非常有幫助。而深度學(xué)習(xí)模型的行為很難做到逐幀可解釋。我做過一個(gè)高速運(yùn)動(dòng)目標(biāo)跟蹤的Demo目標(biāo)是一個(gè)快速移動(dòng)的小球背景相對(duì)干凈。用SAD模板匹配在100MHz時(shí)鐘下處理720p視頻單幀匹配耗時(shí)不到1毫秒跟蹤穩(wěn)定沒有任何卡頓感。這要是用軟件跑單幀耗時(shí)至少翻十倍還得面臨操作系統(tǒng)調(diào)度帶來的延遲抖動(dòng)。2. 硬件架構(gòu)設(shè)計(jì)從頂層框圖到數(shù)據(jù)通路FPGA工程和軟件工程最大的區(qū)別在于你得先把數(shù)據(jù)的流動(dòng)路徑想清楚再談?dòng)?jì)算邏輯。SAD模板匹配的整體架構(gòu)我習(xí)慣這樣劃分圖像緩存與數(shù)據(jù)流控制模塊、SAD計(jì)算陣列并行比較器陣列、最小值和坐標(biāo)提取模塊、以及輸出與顯示控制接口。每個(gè)模塊各司其職組合成一條完整的流水線。頂層數(shù)據(jù)通路的邏輯是這樣的視頻輸入可以是攝像頭接口也可以是測試用的測試圖案發(fā)生器先把像素?cái)?shù)據(jù)寫入行緩存行緩存將數(shù)據(jù)組織成模板大小的圖像塊然后送入SAD計(jì)算單元。計(jì)算單元對(duì)搜索區(qū)域內(nèi)的所有候選位置并行計(jì)算SAD值或者按行流水計(jì)算得到一個(gè)SAD值序列送入最小SAD追蹤模塊。該模塊不斷比較當(dāng)前SAD值和歷史最小值最終輸出最小SAD值對(duì)應(yīng)的坐標(biāo)。2.1 搜索窗口的行緩存設(shè)計(jì)別小看這塊BufferSAD計(jì)算需要同時(shí)訪問模板和圖像中的鄰域像素。如果直接從外部存儲(chǔ)器讀取每個(gè)候選塊的像素訪問次數(shù)會(huì)爆炸。以32×32模板、100×100搜索區(qū)域?yàn)槔┝ψx取的像素?cái)?shù)大約是32×32×100×100 ≈ 1000萬次這在實(shí)時(shí)系統(tǒng)里是不可接受的。正確的做法是用行緩存Line Buffer在FPGA內(nèi)部構(gòu)建一個(gè)滑動(dòng)窗口。經(jīng)典方案是利用FPGA內(nèi)部的BRAM實(shí)現(xiàn)N行緩存N等于模板高度比如32行每來一個(gè)新像素窗口整體右移一格當(dāng)窗口到達(dá)行尾時(shí)換到下一行。這樣任意時(shí)刻窗口內(nèi)都保存了當(dāng)前位置周圍32×32的圖像塊數(shù)據(jù)復(fù)用率接近100%。行緩存的實(shí)現(xiàn)有一些細(xì)節(jié)值得注意用移位寄存器還是用BRAM模板小的話比如8×8可以用移位寄存器實(shí)現(xiàn)延遲小、邏輯簡單模板大的話16×16以上建議用BRAM實(shí)現(xiàn)多行循環(huán)緩存節(jié)省寄存器資源。建議用兩級(jí)緩存第一級(jí)緩存原始圖像的行數(shù)據(jù)第二級(jí)緩存窗口內(nèi)的數(shù)據(jù)塊。這樣可以把圖像輸入速率和計(jì)算速率解耦。行緩存深度要留余量。如果圖像寬度是1920緩存深度至少是1920但實(shí)際工程中我習(xí)慣留到2048因?yàn)橛行┮曨l源會(huì)有行消隱和同步信號(hào)控制邏輯更省心。實(shí)際代碼里行緩存的經(jīng)典寫法是用一個(gè)二維數(shù)組 寫指針循環(huán)覆蓋。以16行緩存、每行1280像素、8bit灰度為例reg [7:0] line_buffer [15:0][1279:0]; reg [9:0] write_addr; reg [3:0] write_line; always (posedge clk) begin if (data_valid) begin line_buffer[write_line][write_addr] pixel_in; if (write_addr 1279) begin write_addr 0; write_line write_line 1b1; end else begin write_addr write_addr 1b1; end end end這段代碼維護(hù)了16行、每行1280像素的滑動(dòng)窗口。窗口的讀地址根據(jù)當(dāng)前像素坐標(biāo)動(dòng)態(tài)生成取出的16×16數(shù)據(jù)塊直接對(duì)接后續(xù)的SAD計(jì)算陣列。2.2 SAD計(jì)算陣列并行度與資源的天平SAD計(jì)算陣列是整個(gè)設(shè)計(jì)的核心。一次完整的SAD計(jì)算要對(duì)模板和圖像塊的每一個(gè)像素做減法、取絕對(duì)值、累加。在FPGA上這個(gè)操作可以按像素級(jí)并行展開。以16×16模板為例理想情況下我可以生成256個(gè)減法器和256個(gè)絕對(duì)值電路然后通過一個(gè)加法樹把256個(gè)結(jié)果累加起來。這樣只用一個(gè)時(shí)鐘周期就能完成一個(gè)候選位置的SAD計(jì)算。但256路并行意味著大約256個(gè)DSP48E1或大量的LUT對(duì)資源緊張的芯片不現(xiàn)實(shí)。實(shí)際工程中需要在并行度和資源占用之間取平衡。我常用的設(shè)計(jì)是把一行的16個(gè)像素做并行計(jì)算然后按行累加16個(gè)像素并行做減法、絕對(duì)值、部分和消耗16個(gè)減法器和16個(gè)加法器每一行的部分和在一個(gè)時(shí)鐘周期內(nèi)完成16行的部分和再串行累加得到完整的SAD值。這樣只需要大約16路的并行計(jì)算資源代價(jià)是單個(gè)候選位置需要16個(gè)時(shí)鐘周期才能出結(jié)果。不過這個(gè)速度對(duì)大多數(shù)跟蹤場景已經(jīng)夠用了。下面給出16×16模板、行并行實(shí)現(xiàn)的核心Verilog框架// 假設(shè)win_data和template_data是16 bit位寬的16個(gè)像素 // 實(shí)際使用中應(yīng)展開為16路減法器 wire [8:0] diff [0:15]; wire [9:0] abs_diff [0:15]; genvar i; generate for (i 0; i 16; i i 1) begin: sad_cell assign diff[i] {1b0, win_data[i]} - {1b0, template_data[i]}; assign abs_diff[i] diff[i][8] ? (~diff[i] 1b1) : diff[i]; end endgenerate wire [13:0] row_partial_sum; assign row_partial_sum abs_diff[0] abs_diff[1] ... abs_diff[15];每一行得到一個(gè)14bit的部分和之后在16行的尺度上做累加。這個(gè)加法樹可以做成pipeline結(jié)構(gòu)每級(jí)插一拍寄存器避免組合邏輯過長導(dǎo)致時(shí)序不收斂。我實(shí)測過一個(gè)8×8模板、64路像素級(jí)并行加法樹的設(shè)計(jì)在Artix-7系列上跑到200MHz沒有任何問題。如果做16×16模板全并行256路頻率會(huì)降到150MHz左右但對(duì)720p視頻來說仍然綽綽有余。2.3 最小SAD追蹤比大小也有講究SAD計(jì)算陣列不斷產(chǎn)生候選位置的SAD值目標(biāo)跟蹤需要從中選出最小的那個(gè)以及對(duì)應(yīng)的坐標(biāo)。這個(gè)模塊看似簡單其實(shí)有一個(gè)容易忽略的性能瓶頸。如果你在每個(gè)時(shí)鐘周期都做一個(gè)當(dāng)前SAD 歷史最小SAD的比較那整個(gè)比較鏈會(huì)變得很長。比較器是有延遲的尤其是當(dāng)歷史最小值需要反饋到下一拍時(shí)組合邏輯回路很可能成為時(shí)序瓶頸。更好的做法是流水線化比較過程把SAD值和坐標(biāo)寫進(jìn)一個(gè)FIFO由專門的最小值搜索狀態(tài)機(jī)來處理。狀態(tài)機(jī)每次從FIFO取一個(gè)SAD值和當(dāng)前最小值比較同時(shí)記錄坐標(biāo)。FIFO可以平滑計(jì)算陣列的輸出節(jié)奏讓最小值搜索不阻塞SAD計(jì)算。另一個(gè)工程細(xì)節(jié)是不要只記最小SAD值還得把對(duì)應(yīng)的搜索位置坐標(biāo)存下來。坐標(biāo)需要跟著SAD值一起進(jìn)FIFO或者通過一個(gè)與SAD計(jì)算陣列同步的計(jì)數(shù)器來生成。我習(xí)慣用后者搜索位置由行列計(jì)數(shù)器直接編碼當(dāng)SAD值被判定為新的最小值時(shí)把當(dāng)前計(jì)數(shù)器值鎖存到輸出寄存器。下面是這個(gè)核心比較邏輯的示意always (posedge clk or negedge rst_n) begin if (!rst_n) begin min_sad 16hFFFF; best_x 0; best_y 0; end else if (sad_valid) begin if (sad_value min_sad) begin min_sad sad_value; best_x curr_x; best_y curr_y; end end end注意一個(gè)坑如果搜索區(qū)域里有多個(gè)候選位置SAD值相同到底取哪一個(gè)這在模板匹配里是個(gè)真實(shí)存在的問題尤其當(dāng)背景有大片平坦區(qū)域時(shí)。我的做法是取先出現(xiàn)的即只在嚴(yán)格小于時(shí)更新這樣實(shí)現(xiàn)最簡單行為也確定。如果你需要取中心位置之類的策略就得再加一個(gè)計(jì)數(shù)器記錄候選位置與搜索區(qū)域中心的距離并在SAD值相等時(shí)做二次比較。3. 模板更新機(jī)制與多目標(biāo)擴(kuò)展從Demo到工程化的距離能跑通單目標(biāo)跟蹤只是第一步。真正落地的目標(biāo)跟蹤系統(tǒng)幾乎都會(huì)遇到兩個(gè)問題目標(biāo)外觀變化了怎么辦畫面里有多個(gè)目標(biāo)怎么辦先說模板更新。固定模板在目標(biāo)發(fā)生尺度變化、旋轉(zhuǎn)、光照變化時(shí)匹配效果會(huì)迅速惡化。比如跟蹤一輛正在轉(zhuǎn)彎的車模板還是直著時(shí)的正面照轉(zhuǎn)過30度角之后SAD值就會(huì)暴漲跟蹤框很容易飄走。最樸素也最有效的方案是每N幀用當(dāng)前跟蹤結(jié)果區(qū)域的圖像更新模板。這樣做的好處是能持續(xù)跟隨目標(biāo)外觀變化壞處是跟蹤錯(cuò)誤一旦發(fā)生錯(cuò)誤會(huì)被累積放大也就是所謂的模板漂移。為了解決漂移問題工程上常用的折中方案是保留初始模板第一幀手工框選或檢測得到的目標(biāo)每次匹配時(shí)同時(shí)計(jì)算當(dāng)前模板和初始模板的SAD當(dāng)當(dāng)前模板的SAD迅速變差時(shí)回退到初始模板重新匹配。這在FPGA上的實(shí)現(xiàn)成本很低只需要雙端口RAM存兩份模板比較邏輯稍作修改即可。而它帶來的穩(wěn)定性提升非常明顯尤其適合目標(biāo)被短暫遮擋或姿態(tài)突變的場景。如果你用的是Zynq這類SoC FPGA更聰明的做法是把模板更新放到ARM核上做比如每50幀用當(dāng)前跟蹤框的圖像重新計(jì)算一次模板做一些簡單的濾波比如新舊模板加權(quán)平均然后通過AXI-Lite接口把新模板寫入FPGA側(cè)的模板RAM。FPGA只管高速匹配ARM管決策和更新分工非常合理。再說多目標(biāo)擴(kuò)展。SAD算法天然支持多目標(biāo)擴(kuò)展思路有兩種時(shí)分復(fù)用一個(gè)SAD計(jì)算陣列輪流加載多個(gè)目標(biāo)的模板輸出多個(gè)最小SAD值和坐標(biāo)。缺點(diǎn)是當(dāng)目標(biāo)數(shù)量增多時(shí)整體幀率會(huì)線性下降??臻g并行復(fù)制多份SAD計(jì)算陣列每份配一個(gè)模板同時(shí)計(jì)算多個(gè)目標(biāo)的位置。缺點(diǎn)是資源占用會(huì)成倍增加。如果做4個(gè)目標(biāo)的跟蹤4套16×16模板的SAD陣列中等規(guī)模的FPGA芯片能扛住。我實(shí)際做過的項(xiàng)目里用空間并行方案在一塊中等規(guī)模的Kintex-7上同時(shí)跟蹤4個(gè)目標(biāo)處理1080p60fps輸入資源占用大約在45%左右時(shí)序收斂良好約180MHz。如果目標(biāo)數(shù)量再多建議還是考慮用DSP軟件方案來卸載部分計(jì)算。畢竟FPGA也不是萬能的關(guān)鍵還是選對(duì)計(jì)算引擎。4. 系統(tǒng)集成與實(shí)時(shí)性分析當(dāng)SAD匹配撞上視頻流算法模塊單獨(dú)驗(yàn)證沒問題不代表放到整個(gè)視頻鏈路里也能跑。SAD模板匹配的目標(biāo)跟蹤一般嵌在視頻采集 → 預(yù)處理 → 目標(biāo)匹配 → 結(jié)果疊加 → 顯示輸出的完整系統(tǒng)中。這里每個(gè)環(huán)節(jié)都會(huì)影響SAD模塊的輸入數(shù)據(jù)質(zhì)量以及整個(gè)系統(tǒng)的實(shí)時(shí)性。4.1 輸入預(yù)處理灰度轉(zhuǎn)換與降噪SAD算法本身是基于灰度圖像的所以彩色視頻進(jìn)來必須先做RGB轉(zhuǎn)灰度。如果攝像頭輸出的是RAW Bayer格式那還得先做去馬賽克再轉(zhuǎn)灰度。這些預(yù)處理在FPGA上做很快但要注意預(yù)處理延遲要計(jì)算進(jìn)整個(gè)流水線——如果你的預(yù)處理要緩存好幾行才能出結(jié)果那SAD模塊收到第一幀完整數(shù)據(jù)的時(shí)間會(huì)往后拖。降噪方面如果畫面噪聲較大SAD匹配的穩(wěn)定性會(huì)很差。我的經(jīng)驗(yàn)是加一個(gè)簡單的3×3中值濾波或者高斯濾波作為預(yù)處理。中值濾波在FPGA上的經(jīng)典實(shí)現(xiàn)是行緩存 排序網(wǎng)絡(luò)3×3窗口的9個(gè)像素排序需要3個(gè)比較器級(jí)聯(lián)資源開銷不大效果立竿見影。但注意濾波會(huì)使邊緣略微模糊如果目標(biāo)本身是很精細(xì)的紋理結(jié)構(gòu)模板匹配精度可能會(huì)輕微下降這一點(diǎn)要在實(shí)際項(xiàng)目中權(quán)衡。4.2 幀存調(diào)度DDR3/DDR4帶寬與時(shí)序跟蹤是在視頻幀上撒搜索窗口可能涉及對(duì)整幀圖像的隨機(jī)訪問。如果要把搜索區(qū)域擴(kuò)展到全圖或者需要大范圍的搜索比如目標(biāo)快速運(yùn)動(dòng)就需要把整幀圖像存入外部DDR內(nèi)存。這時(shí)DDR讀寫帶寬就成為主要瓶頸。以1080p、8bit灰度、60fps為例寫入幀存帶寬約124MB/s讀取搜索窗口數(shù)據(jù)的帶寬取決于搜索策略。如果搜索區(qū)域覆蓋整個(gè)畫面每個(gè)候選位置都要從DDR讀一個(gè)模板大小的圖像塊帶寬需求會(huì)爆炸根本跑不動(dòng)。所以全圖搜索的SAD跟蹤在實(shí)際工程中很少見大多數(shù)情況是基于上一幀目標(biāo)位置的小范圍搜索——比如只搜上一幀位置周圍64×64的區(qū)域。這樣DDR讀取帶寬可以控制在一個(gè)可接受的范圍。我做過一個(gè)測試上一幀位置周圍64×64搜索、16×16模板720p60fps輸入DDR3-1600單片16bitDDR帶寬占用大約25%余量充足。如果把搜索范圍擴(kuò)大到128×128帶寬占用直接飆到80%以上系統(tǒng)的其他部分比如顯示疊加就會(huì)開始卡頓。4.3 流水線設(shè)計(jì)的時(shí)序約束要點(diǎn)FPGA設(shè)計(jì)跑到系統(tǒng)集成階段最讓人頭疼的就是時(shí)序收斂。SAD計(jì)算陣列動(dòng)輒幾百級(jí)的組合邏輯如果不加流水線寄存器時(shí)序必然出問題。我的做法是在每個(gè)SAD計(jì)算級(jí)之間插入一級(jí)寄存器形成3~5級(jí)流水線減法和絕對(duì)值合并在同一級(jí)流水內(nèi)完成減少組合邏輯深度加法樹每級(jí)加法插一拍寄存器用乒乓寄存器加法器結(jié)構(gòu)實(shí)現(xiàn)。時(shí)序約束文件XDC里建議給SAD計(jì)算陣列單獨(dú)定義時(shí)鐘組并對(duì)跨時(shí)鐘域信號(hào)做異步FIFO隔離。視頻輸入時(shí)鐘和系統(tǒng)時(shí)鐘不一致是很常見的情況比如攝像頭給一個(gè)24MHz像素時(shí)鐘系統(tǒng)主頻跑100MHz中間必須通過異步FIFO轉(zhuǎn)換。我實(shí)測過一個(gè)設(shè)計(jì)因?yàn)楹雎粤丝鐣r(shí)鐘域約束導(dǎo)致跟蹤坐標(biāo)偶爾跳變排查了半天才發(fā)現(xiàn)是亞穩(wěn)態(tài)問題。下面給出一段簡化版的XDC約束示例create_clock -period 10.000 -name sys_clk [get_ports clk] create_clock -period 41.667 -name pix_clk [get_ports pix_clk] set_clock_groups -asynchronous -group [get_clocks sys_clk] -group [get_clocks pix_clk]5. 實(shí)測調(diào)優(yōu)SAD閾值、搜索策略與性能數(shù)據(jù)設(shè)計(jì)做完之后真正決定能不能用的是調(diào)優(yōu)階段。SAD模板匹配有非常多的旋鈕可以調(diào)不同場景下的最優(yōu)參數(shù)差異很大。分享幾個(gè)我實(shí)際調(diào)過的經(jīng)驗(yàn)。5.1 SAD閾值與遮擋判定的配合SAD最小值本身不能直接說明匹配有多可靠。如果目標(biāo)跑出畫面外或者被完全遮擋SAD計(jì)算還是會(huì)給你一個(gè)最小的數(shù)值但那個(gè)數(shù)值通常比正常跟蹤時(shí)高不少。所以工程上必須設(shè)置一個(gè)SAD閾值當(dāng)最小SAD值超過閾值時(shí)判定為跟蹤丟失或目標(biāo)被遮擋此時(shí)可以切換到等待模式或者啟動(dòng)搜索模式擴(kuò)大搜索區(qū)域重新尋找。閾值的標(biāo)定方法我建議這樣先讓系統(tǒng)對(duì)著目標(biāo)正常跟蹤50~100幀記錄SAD值的統(tǒng)計(jì)分布最大值、均值、標(biāo)準(zhǔn)差然后設(shè)定閾值為均值加3~4倍標(biāo)準(zhǔn)差。在FPGA上做這個(gè)統(tǒng)計(jì)不方便一般是在仿真階段用軟件算好再固化到寄存器里。如果想做自適應(yīng)閾值可以把統(tǒng)計(jì)邏輯放在Zynq的ARM端每N幀回調(diào)一次更新閾值寄存器。5.2 多尺度搜索大目標(biāo)變小目標(biāo)怎么辦SAD模板匹配對(duì)尺度變化非常敏感。目標(biāo)在畫面里變大變小SAD值都會(huì)迅速增大。解決這個(gè)問題的一個(gè)實(shí)用方案是多尺度模板池預(yù)先生成目標(biāo)在不同尺度下的多個(gè)模板比如1.0倍、0.85倍、0.7倍每一幀對(duì)每個(gè)尺度都做一次SAD匹配取所有尺度中最小的SAD值及其坐標(biāo)作為輸出。這個(gè)方案在FPGA上的實(shí)現(xiàn)開銷是乘法器資源BRAM容量。如果模板數(shù)量不多3~5個(gè)尺度資源成本還能接受。我的實(shí)測感受是多尺度匹配對(duì)跟蹤穩(wěn)定性的提升是肉眼可見的尤其是在俯視相機(jī)比如無人機(jī)視角場景中目標(biāo)大小隨高度變化非常明顯。不過要注意多尺度匹配的計(jì)算時(shí)間會(huì)成倍增加。對(duì)于實(shí)時(shí)系統(tǒng)建議優(yōu)先做時(shí)間維度的并行優(yōu)化也就是把不同尺度的匹配任務(wù)分配到不同的計(jì)算陣列上而不是串行執(zhí)行。5.3 實(shí)測性能數(shù)據(jù)參考最后給出一組典型的實(shí)測數(shù)據(jù)硬件平臺(tái)是Xilinx Artix-7 XC7A200T工作時(shí)鐘150MHz圖像分辨率1280×720灰度8bit參數(shù)配置數(shù)值模板大小32 × 32搜索區(qū)域64 × 64以上一幀目標(biāo)位置為中心候選位置數(shù)1089單候選位置SAD計(jì)算周期32個(gè)時(shí)鐘周期單幀匹配總耗時(shí)約232微秒資源占用LUT/FF/BRAM/DSP12500/9800/20/0整體幀率60fps232微秒的匹配耗時(shí)意味著即使搜索區(qū)域進(jìn)一步擴(kuò)大依然有充足的性能余量。如果你用更高端的芯片比如Kintex-7或Zynq UltraScale跑更大的模板和更大的搜索區(qū)域也毫無壓力。所以SAD模板匹配在FPGA上做目標(biāo)跟蹤最大的優(yōu)勢不是能不能做而是做了之后還剩多少資源給其他功能。這在實(shí)際產(chǎn)品設(shè)計(jì)中往往比單純的算法效果更具決策價(jià)值。6. 踩坑實(shí)錄跨時(shí)鐘域、資源競爭與坐標(biāo)抖動(dòng)調(diào)試階段總是會(huì)遇到一些讓人抓狂的問題。分享幾個(gè)我踩過的坑希望能幫你少走彎路。6.1 異步FIFO沒處理好坐標(biāo)偶爾跳變的癥狀是跟蹤基本正常但每隔幾秒鐘目標(biāo)坐標(biāo)會(huì)突然跳出去幾十個(gè)像素然后下一幀又跳回來。一開始懷疑是算法問題查了半天邏輯仿真完全正常。后來用ILA抓實(shí)時(shí)信號(hào)發(fā)現(xiàn)是像素時(shí)鐘域和系統(tǒng)時(shí)鐘域之間的異步FIFO讀數(shù)據(jù)時(shí)出現(xiàn)了亞穩(wěn)態(tài)導(dǎo)致偶爾讀到錯(cuò)誤的像素值SAD計(jì)算結(jié)果當(dāng)然就錯(cuò)了。解決辦法是給異步FIFO加上擺同步gray code指針同步并且預(yù)留兩級(jí)寄存器同步后再判斷FIFO空滿狀態(tài)。這個(gè)坑提醒我FPGA里最貴的時(shí)序成本往往不是算法本身而是接口轉(zhuǎn)換。6.2 模板RAM和圖像緩存的BRAM資源打架之前在一個(gè)設(shè)計(jì)中模板最多支持64個(gè)預(yù)留給多目標(biāo)每個(gè)模板32×32×8bit 8KB總共512KB直接吃了半片Artix-7的BRAM。結(jié)果SAD計(jì)算陣列里頭行緩存已經(jīng)用了不少BRAM布局布線時(shí)資源告警甚至出現(xiàn)時(shí)序違例。解決方案有兩個(gè)方向把模板存儲(chǔ)在DDR里運(yùn)行時(shí)通過AXI DMA按需讀取到FPGA的BRAM緩存中。這樣BRAM只需要保存當(dāng)前用的幾個(gè)模板即可壓縮模板位寬?;叶?bit降到6bit或者5bitSAD精度略降但對(duì)跟蹤穩(wěn)定性影響不大。曾做過一次測試5bit量化模板下SAD值整體偏小但最小坐標(biāo)基本不變。6.3 目標(biāo)高速運(yùn)動(dòng)時(shí)跟蹤框滯后目標(biāo)移動(dòng)速度太快搜索窗口跟不上目標(biāo)直接跑出搜索范圍。癥狀是跟蹤框一開始還行目標(biāo)加速后就丟掉然后到處亂跑。這不是算法算得慢而是搜索策略的問題。解決思路是引入運(yùn)動(dòng)預(yù)測。最簡單的預(yù)測方法是記錄前兩幀目標(biāo)坐標(biāo)用差分近似速度預(yù)測當(dāng)前幀的搜索中心。公式上就是predict_x last_x (last_x - prev_x)predict_y last_y (last_y - prev_y)然后把搜索窗口中心放在predict坐標(biāo)上而不是上一幀坐標(biāo)。這個(gè)改動(dòng)在FPGA上幾乎沒有成本兩個(gè)減法器加兩個(gè)加法器。但效果非常顯著目標(biāo)以高速直線運(yùn)動(dòng)時(shí)跟蹤成功率幾乎達(dá)到100%。如果還想更精細(xì)一些可以上卡爾曼濾波不過卡爾曼在FPGA上實(shí)現(xiàn)涉及矩陣運(yùn)算資源消耗大不少。我的建議是先用差分預(yù)測效果不夠再考慮卡爾曼。很多場景下差分預(yù)測已經(jīng)足夠。另外有一個(gè)容易忽略的細(xì)節(jié)預(yù)測坐標(biāo)不要直接作為跟蹤輸出而是作為搜索中心。最終輸出仍然用SAD匹配到的坐標(biāo)。這樣即使預(yù)測偏了只要目標(biāo)還在搜索窗口內(nèi)依然能正確捕獲。7. 用FPGA做SAD跟蹤的幾點(diǎn)經(jīng)驗(yàn)總結(jié)如果讓我用一個(gè)詞概括FPGA上的SAD模板匹配目標(biāo)跟蹤我會(huì)選劃算。算法邏輯簡單、資源消耗可控、實(shí)時(shí)性極強(qiáng)是FPGA圖像處理入門到進(jìn)階之間非常優(yōu)質(zhì)的一個(gè)練習(xí)項(xiàng)目。它可以鍛煉行緩存設(shè)計(jì)、流水線架構(gòu)、并行計(jì)算陣列、跨時(shí)鐘域處理這些基本功而這些能力在更復(fù)雜的FPGA視頻處理項(xiàng)目里都是通用的。再說幾個(gè)個(gè)人體會(huì)最深刻的點(diǎn)。其一不要一上來就追求全并行。SAD的并行度可以根據(jù)芯片資源靈活調(diào)整先用行并行、串行累加的方式跑通整個(gè)鏈路再去優(yōu)化并行度是更穩(wěn)妥的路徑。全并行方案在資源利用率上并不一定更優(yōu)反而會(huì)引入更復(fù)雜的布線擁塞和時(shí)序收斂問題調(diào)試成本陡增。其二數(shù)據(jù)流的組織比計(jì)算本身更重要。很多FPGA初學(xué)者把精力都花在SAD計(jì)算邏輯上卻忽略了行緩存、FIFO、坐標(biāo)計(jì)數(shù)器這些數(shù)據(jù)管道。實(shí)際上視頻處理系統(tǒng)的性能瓶頸十有八九出在數(shù)據(jù)搬運(yùn)上而不是計(jì)算上。行緩存設(shè)計(jì)是否合理、跨時(shí)鐘域FIFO是否可靠直接決定了整個(gè)系統(tǒng)的穩(wěn)定性。其三善用Zynq這種SoC平臺(tái)做任務(wù)分工。單純的FPGA實(shí)現(xiàn)適合追求極致實(shí)時(shí)性和確定性要求的場景但一旦涉及復(fù)雜的模板更新策略、多目標(biāo)管理、甚至和上層控制系統(tǒng)交互ARM核的加入會(huì)讓系統(tǒng)架構(gòu)清爽很多。SAD計(jì)算在PL側(cè)做管理和決策在PS側(cè)做這是Zynq平臺(tái)上目標(biāo)跟蹤項(xiàng)目最經(jīng)典的架構(gòu)之一。最后留一個(gè)可以繼續(xù)深入的優(yōu)化方向當(dāng)前做的是灰度圖像的SAD匹配如果把RGB三通道的SAD分別計(jì)算再加權(quán)合并可以在彩色特征明顯的場景下大幅提升匹配精度。實(shí)現(xiàn)上只需把單通道SAD陣列復(fù)制三份或者做時(shí)分復(fù)用資源增加約2倍但對(duì)特定目標(biāo)的跟蹤效果提升明顯。以后有空我會(huì)把這個(gè)擴(kuò)展的實(shí)現(xiàn)細(xì)節(jié)單獨(dú)整理出來這次就先聊到這里。