:從取幀鏈路到參數(shù)調(diào)優(yōu))
攝像頭被一塊布蒙住、鏡頭被噴漆、安裝角度被外力掰偏監(jiān)控畫面看起來“沒變化”但后臺值班人員可能幾天都沒發(fā)現(xiàn)反過來畫面里有人走動要不要觸發(fā)告警、要不要聯(lián)動抓拍這是每一臺智能IPC都繞不開的兩個基礎(chǔ)功能。視頻遮擋檢測和運動檢測在MPP多媒體處理平臺開發(fā)里屬于看起來簡單、做起來全是細(xì)節(jié)的模塊。算法層面不復(fù)雜難點在于數(shù)據(jù)鏈路怎么選、參數(shù)怎么配、誤報漏報怎么壓。這篇文章想梳理我在MPP平臺上實現(xiàn)這兩類功能時的完整思路包括取幀鏈路、核心算法邏輯、參數(shù)含義、防誤報設(shè)計以及現(xiàn)場調(diào)試時頻繁踩到的坑。適合正在做智能IPC、攝像頭定制開發(fā)的工程師或者剛接觸平臺SDK、想快速上手遮擋和移動偵測的同學(xué)。內(nèi)容不涉及具體芯片型號只講平臺通用的實現(xiàn)套路你拿到任何帶MPP接口的開發(fā)板都能對照著落。1. 視頻遮擋與運動檢測的實際場景與模塊選型1.1 遮擋檢測到底解決什么問題遮擋檢測的核心不是“認(rèn)出畫面里是什么”而是判斷“畫面本身是否還正?!薄K?wù)的場景非常明確攝像機被惡意遮擋比如用布、紙板、噴漆堵住鏡頭畫面變成一片黑、一片白或一片均勻色。鏡頭因震動或人為破壞發(fā)生視角偏移畫面內(nèi)容對著天空、墻面或地面雖然顏色正常但已經(jīng)失去監(jiān)控價值。鏡頭臟污、起霧、進灰畫面長期模糊。這類問題的共同點是畫面統(tǒng)計特征發(fā)生異常。正常監(jiān)控畫面通常有較豐富的邊緣、紋理亮度分布有層次遮擋后畫面會趨于均勻亮度極端化邊緣密度急劇下降。所以遮擋檢測不需要神經(jīng)網(wǎng)絡(luò)不需要目標(biāo)識別只需要對每幀圖像算幾個統(tǒng)計量再用閾值判斷即可。這個功能的價值在于“監(jiān)控的可靠性”。一臺攝像機如果畫面早就失效了后面接再強的AI分析都是白搭。很多項目把遮擋檢測和“設(shè)備在線狀態(tài)”放在一起看一旦觸發(fā)就派單巡檢或者彈窗告警。1.2 運動檢測在智能設(shè)備上的定位運動檢測解決的又是另一類需求感知場景中的物體移動并基于移動狀態(tài)產(chǎn)生聯(lián)動行為。典型場景包括無人值守區(qū)域有人闖入產(chǎn)生告警并聯(lián)動抓拍、錄像。私有區(qū)域在布防時段出現(xiàn)物體移動通知用戶??土鹘y(tǒng)計、人流方向判斷等前置步驟。運動檢測通常不關(guān)心移動的是什么物體只關(guān)心“有沒有像素塊發(fā)生變化”。它比AI人形檢測粗糙但勝在計算量極低、全天候穩(wěn)定運行適合作為第一層篩選。很多設(shè)備是先跑運動檢測一旦觸發(fā)再調(diào)用更重的檢測模型去判斷是不是人、是不是車這樣能把算力用在刀刃上。在MPP平臺里遮擋檢測和運動檢測經(jīng)常是“共路復(fù)用的”。它們都只需要低分辨率的灰度或YUV數(shù)據(jù)都只需要跑統(tǒng)計或差分邏輯所以可以共用同一路縮放后的視頻幀分時或分線程執(zhí)行。1.3 先用硬件能力還是自己算這是平臺開發(fā)里第一個要拍板的取舍。不同芯片方案的SDK能力差別很大有些平臺提供硬件運動檢測模塊直接在編碼鏈路里輸出運動區(qū)域位圖有些平臺只有VGS視頻圖形子系統(tǒng)縮放能力運動檢測得自己去用戶態(tài)算。我沒有辦法給你一個統(tǒng)一答案只講我自己的判斷標(biāo)準(zhǔn)如果SDK明確提供了硬件MD運動檢測和遮擋檢測通道優(yōu)先用硬件通道。硬件通道不占用CPU檢測效果好缺點是參數(shù)調(diào)節(jié)范圍相對固定底層細(xì)節(jié)看不到。如果SDK沒有硬件檢測或者硬件檢測的ROI、靈敏度不能滿足項目需求就用VGS把YUV縮放到小分辨率再在用戶態(tài)用幀差法做運動檢測、用統(tǒng)計特征做遮擋檢測。我自己更傾向于“用戶態(tài)檢測硬件輔助”的組合。原因是現(xiàn)場調(diào)試時用戶態(tài)代碼可以把統(tǒng)計值、中間結(jié)果全部打出來定位問題非常直觀。硬件模塊往往只能看成敗標(biāo)志一旦誤報很難判斷是閾值問題還是驅(qū)動問題。2. 檢測數(shù)據(jù)鏈路設(shè)計分辨率、幀率與ROI2.1 從哪條鏈路取檢測幀很多第一次做MPP開發(fā)的工程師會直接去取編碼前的原始YUV甚至去讀RAW圖來做運動檢測。這么做最大的問題有兩個一是原始分辨率太大用戶態(tài)計算吃CPU二是取流路徑可能和編碼器搶帶寬影響主碼流幀率。我常用的做法是在編碼前設(shè)置一個VGS通道把輸入幀縮放成一路專門給檢測功能的“小圖”。具體流程如下VI視頻輸入采集到原始幀進入VPSS視頻處理子系統(tǒng)。VPSS根據(jù)需要輸出多路通道一路給編碼器做主碼流一路給編碼器做子碼流一路給VGS做縮放。VGS輸出一個低分辨率NV12或灰度幀供用戶態(tài)讀取。檢測線程通過Frame接口獲取該通道幀執(zhí)行遮擋統(tǒng)計和運動差分。這么做的好處是檢測分辨率固定且很小內(nèi)存帶寬開銷低即使主碼流是4K、1080P檢測鏈路依然可以穩(wěn)定跑在D1甚至CIF級別。而且檢測鏈路獨立于編碼器主碼流編碼出問題、斷幀不會卡死檢測線程。需要注意VGS通道的綁定關(guān)系、是否共享VPSS輸出、是否參與幀率控制這些都要根據(jù)你手里的SDK版本去配。我的經(jīng)驗是如果平臺支持“獨立VGS縮放組”就新建一個分組不要夾在VPSS和編碼器中間去改主鏈路結(jié)構(gòu)否則很容易動一處壞一路。2.2 檢測幀的分辨率與幀率怎么定檢測幀不是越大越好也不是幀率越高越好。這里有一個性價比平衡點。分辨率方面遮擋檢測需要統(tǒng)計邊緣和方差分辨率太低會把細(xì)節(jié)抹掉導(dǎo)致誤判運動檢測需要捕捉移動目標(biāo)分辨率太低會導(dǎo)致小目標(biāo)漏檢。我實測下來的經(jīng)驗區(qū)間場景類型推薦檢測分辨率推薦幀率用戶態(tài)CPU占用約固定攝像頭、室內(nèi)靜態(tài)場景CIF352x2885~10fps可忽略室外周界、行人走動D1704x57610fps3%~5%大范圍區(qū)域、小目標(biāo)檢測720P裁剪區(qū)域12~15fps8%~12%幀率并非越高越好。因為運動檢測用幀差法把連續(xù)兩幀的差分作為“運動能量”來源幀率太高時相鄰幀之間的位移量很小反而對低速運動不敏感幀率太低又會漏掉快速移動的目標(biāo)。5~10fps在實際工程里效果已經(jīng)不錯同時還能兼顧CPU占用。如果場景中對告警延時要求很嚴(yán)比如門禁周界希望200ms內(nèi)出告警那幀率至少得12fps以上同時要把觸發(fā)幀數(shù)從連續(xù)3幀降到2幀。這個聯(lián)動關(guān)系在調(diào)參時要想清楚。2.3 ROI區(qū)域與坐標(biāo)換算遮擋檢測和運動檢測都需要支持區(qū)域配置。用戶配置的ROI通常是在顯示畫面上的絕對像素坐標(biāo)而檢測算法內(nèi)部往往是網(wǎng)格坐標(biāo)或歸一化坐標(biāo)兩者必須做換算。我推薦的一種做法把畫面均勻劃分成M行N列的網(wǎng)格每個網(wǎng)格稱為一個Block。用戶配置矩形區(qū)域時將矩形坐標(biāo)按檢測幀寬高做歸一化left x / frame_widthtop y / frame_height。檢測結(jié)果輸出時再把命中的Block索引換算回顯示坐標(biāo)x block_col * block_widthy block_row * block_height。這里最容易出的問題是用戶配置界面顯示的是OSD疊加后的坐標(biāo)而OSD層可能帶偏移或縮放。如果發(fā)現(xiàn)畫框位置總是偏一點優(yōu)先檢查用戶使用的坐標(biāo)系到底是顯示通道分辨率還是檢測通道分辨率。尤其當(dāng)設(shè)備做了電子放大、畫面裁剪時坐標(biāo)基準(zhǔn)必須換成裁剪后畫面否則區(qū)域會整體偏移。還有一點容易被忽略ROI區(qū)域邊緣的Block要不要參與檢測我的處理方式是不參與。因為部分Block落在ROI外差分統(tǒng)計混入非關(guān)注區(qū)域的運動會干擾事件判斷。網(wǎng)格遍歷時先做一次區(qū)域判定Block中心點不在ROI內(nèi)就跳過。3. 視頻遮擋檢測的原理、參數(shù)與防誤報3.1 遮擋檢測的計算原理遮擋檢測本質(zhì)上是一套圖像質(zhì)量評估指標(biāo)我通常提取三個統(tǒng)計量亮度均值Mean整幀Y分量的平均值。正常畫面均值通常在50~180之間8bit位深遮擋過暗時均值極低過曝遮擋時均值很高。亮度方差Variance反映畫面灰階分布的離散程度。畫面被遮擋后變得均勻方差會明顯變小正常有內(nèi)容畫面的方差通常在數(shù)百到上千。邊緣像素占比EdgeRatio用簡單算子提取邊緣點數(shù)量占總數(shù)比例。遮擋后邊緣點大量減少正常畫面邊緣占比約為5%~15%。檢測邏輯非常簡單每幀計算這三個統(tǒng)計量如果亮度均值超出正常范圍或者方差低于下限或者邊緣占比低于下限則判定為“異常幀”。但單幀判定不可靠必須結(jié)合連續(xù)幀計數(shù)。下面是一個簡化的實現(xiàn)框架我習(xí)慣用C寫便于嵌入到MPP采集線程里typedef struct { int mean; int variance; int edge_ratio; } DetectStat; int check_blocked_frame(DetectStat *stat, BlockThreshold *thr) { if (stat-mean thr-mean_low || stat-mean thr-mean_high) { return 1; } if (stat-variance thr-var_low) { return 1; } if (stat-edge_ratio thr-edge_low) { return 1; } return 0; }實際使用中建議不要只用一個條件因為單一條件誤報率很高。比如夜晚畫面亮度低是正常的只看均值會誤判墻壁反光場景方差也不高只看方差也會誤判。我處理時采用“加權(quán)投票”三個條件命中兩個及以上才認(rèn)為當(dāng)前幀異常這樣一個維度受噪聲影響時其他維度還能拉回來。3.2 關(guān)鍵閾值參數(shù)與靈敏度配置遮擋檢測的閾值參數(shù)通常不直接暴露給用戶而是通過“靈敏度”檔位映射。平臺SDK一般提供低、中、高三檔底層對應(yīng)不同的閾值組合。我整理過一組比較通用的映射關(guān)系可以作為起步值檔位亮度均值下限亮度均值上限方差下限邊緣占比下限適用場景低靈敏度402008008%明亮室內(nèi)、畫面穩(wěn)定中靈敏度302205005%室外多變場景高靈敏度202353003%昏暗環(huán)境、夜視場景這組值不是固定的。我會把閾值全部暴露成調(diào)試參數(shù)通過配置文件下發(fā)這樣現(xiàn)場調(diào)試時不用重新編譯固件直接改配置就能收斂誤報漏報。除了閾值本身“判定窗口”也很關(guān)鍵。我會在連續(xù)5~10幀異常后才上報遮擋事件避免畫面抖動、飛蟲飛過瞬間造成誤報。如果把連續(xù)觸發(fā)幀數(shù)設(shè)成1基本沒法用隨便一個快速變亮的車燈就能觸發(fā)。3.3 光照突變與夜間模式的防誤報策略遮擋檢測最容易誤報的場景不是真遮擋而是光照突變。開門一瞬間強光進入畫面亮度均值從50跳到200方差和邊緣比例短暫失真如果閾值和判定窗口不合理就會上報“遮擋”。還有紅外切換的瞬間畫面可能從彩色變黑白亮度整體變化也容易誤報。我踩過坑之后總結(jié)出三個防誤報手段第一增加遲滯機制。進入遮擋狀態(tài)使用較寬松的閾值A(chǔ)退出遮擋狀態(tài)使用更嚴(yán)苛的閾值B。也就是說同樣一個畫面特征要讓算法“進去容易出來難”。例如進入時要求連續(xù)6幀均值低于30退出時要求連續(xù)10幀均值高于50。這樣做可以避免畫面在黑與稍黑之間反復(fù)橫跳告警狀態(tài)也穩(wěn)定。第二跳過自動曝光收斂期。在檢測線程里加一個時間戳記錄當(dāng)檢測到曝光參數(shù)增益、快門發(fā)生大幅調(diào)整時暫時停止遮擋判定幾百毫秒等畫面重新穩(wěn)定后再繼續(xù)。如果SDK拿不到曝光參數(shù)可以退一步當(dāng)前幀與上一幀的亮度均值差超過30時就跳過本幀判定。這個辦法在實測中非常有用。第三夜間模式單獨匹配?!耙归g畫面亮度低、方差低”是正常狀態(tài)而不是遮擋。所以夜間不能直接套用日天的低閾值。我的做法是當(dāng)檢測到畫面均值整體低于40時自動切換到夜間閾值組把亮度均值下限降到10方差下限降到150邊緣占比下限降到2%同時把判定窗口從5幀加到10幀。4. 運動檢測實現(xiàn)幀差、網(wǎng)格與事件上報4.1 幀間差分與運動能量運動檢測最經(jīng)典、也最實用的辦法是幀間差分。把當(dāng)前幀和上一幀做像素級減法計算差值絕對值差值大的區(qū)域就說明“畫面發(fā)生了變化”。這個變化既可能是物體移動也可能是光照變化、攝像頭抖動所以要靠網(wǎng)格統(tǒng)計和閾值做進一步過濾。實際計算時我不會直接對YUV全幀做逐像素差分而是把檢測幀先轉(zhuǎn)成8bit灰度也就是取Y分量然后按Block為單位統(tǒng)計平均絕對差分值。每個Block的差分能量計算方式diff_energy(block) sum(|current_pixel - reference_pixel|) / block_size這里的reference可以是上一幀也可以是更穩(wěn)定的背景幀。簡單幀差的缺點是運動稍微慢一點、物體停在原地幾秒幀差就歸零所以很多項目里會維護一個長時間平均值作為背景。不過在MPP平臺上跑簡單的背景建模會增加內(nèi)存和CPU開銷而且現(xiàn)場光照適應(yīng)性更差。我的經(jīng)驗是先做上一幀差分配合“運動能量累計”機制效果已經(jīng)能滿足大部分布防需求。運動能量累計的思路是如果某個Block連續(xù)幾幀都有低強度差分視為累積運動如果某幀出現(xiàn)高強度差分視為瞬時運動。兩者通過不同閾值處理。這樣可以兼顧快速闖入和緩慢接近兩種場景。4.2 網(wǎng)格分割、靈敏度與觸發(fā)機制檢測區(qū)域通常劃分為網(wǎng)格而不是逐像素輸出。網(wǎng)格的常見規(guī)格有16x12、16x8、12x8具體選哪種取決于檢測分辨率和最小目標(biāo)尺寸。以D1分辨率704x576為例如果分12x8網(wǎng)格每個Block約為58x72像素。一個成年人站在15米外畫面中大概能占2x3個Block勉強夠檢測如果目標(biāo)是貓、狗這類小動物建議用16x12甚至更細(xì)的網(wǎng)格但代價是CPU占用上升、容易被樹葉晃動干擾。每個Block的差分結(jié)果與兩個閾值比較觸發(fā)閾值Motion Threshold差分能量超過該值才認(rèn)為該Block發(fā)生了明顯變化。去抖閾值Debounce Threshold低于該值則認(rèn)為沒有運動用于濾除噪聲。靈敏度檔位本質(zhì)上就是調(diào)整這兩個閾值。同一段畫面閾值越低越容易觸發(fā)但也更容易把樹葉搖動、光影變化當(dāng)成運動。觸發(fā)機制還必須解決“瞬時噪聲”問題。我用的方案是單個Block連續(xù)2~3幀都超過觸發(fā)閾值才確認(rèn)該Block為激活塊。激活塊的數(shù)量達(dá)到總檢測塊的一定比例比如5%或者激活塊集中在某個ROI區(qū)域里才上報一次運動事件。事件上報后進入靜默期N秒靜默期內(nèi)不再重復(fù)上報避免告警風(fēng)暴。4.3 事件上報、畫框與OSD聯(lián)動運動檢測觸發(fā)后平臺上正常要做兩件事上報事件數(shù)據(jù)、聯(lián)動顯示。事件數(shù)據(jù)最簡單的格式是運動塊位圖bitmap或者矩形區(qū)域結(jié)構(gòu)體。我這里推薦上報“激活Block集合”而不是上報一個合并大矩形。因為上層客戶端拿到Block集合后可以自己決定畫框方式可以把相鄰Block合并成一個外接矩形也可以把每一塊都畫出來。如果只上報一個大矩形多目標(biāo)離散運動時框會非常大用戶體驗很差。合并矩形時建議做一個形態(tài)學(xué)處理把孤立的、面積小于閾值的激活Block過濾掉然后把鄰近Block合并。否則畫面上會出現(xiàn)幾十個碎框看起來像打地鼠。OSD聯(lián)動屬于錦上添花。設(shè)備端可以直接在編碼通道疊加一個半透明遮罩把激活Block對應(yīng)的區(qū)域用色塊標(biāo)注方便現(xiàn)場安裝人員調(diào)試。還有一個很實用的聯(lián)動運動檢測觸發(fā)時在子碼流OSD上顯示最后一張運動觸發(fā)幀的抓拍圖這個功能在門鈴、貓眼類產(chǎn)品里經(jīng)常用。5. 調(diào)試技巧與常見問題排查實錄5.1 抓檢測幀與統(tǒng)計日志分析調(diào)試遮擋和運動檢測最忌諱憑感覺調(diào)參。我強烈建議在檢測線程里加一個“統(tǒng)計值日志”開關(guān)每幀打印亮度均值、方差、邊緣比例、最大塊差分能量、激活塊數(shù)量。通過觀察這些數(shù)值你能很快判斷是閾值問題還是數(shù)據(jù)鏈路問題。例如如果一幀畫面明明是正常的但均值日志突然從100跳到220那就要去查是不是自動曝光亂跳如果均值、方差一直正常但激活塊數(shù)量很多那就要把檢測幀抓下來看看是不是有樹影晃動。抓取檢測幀本身也是一門學(xué)問。MPP平臺一般支持把某個通道的YUV幀轉(zhuǎn)成RAW文件我通常用以下方式直接把VGS縮放后的檢測幀 dump 成YUV文件。用工具把YUV轉(zhuǎn)成PNG看畫面內(nèi)容。同時把對應(yīng)的統(tǒng)計值一起打印和圖像內(nèi)容對照。這么做最大的好處是你能確認(rèn)“算法看到的畫面”和“你腦子里以為的畫面”是不是同一個。我遇到過很多次主機畫面正常但檢測幀因為縮放鏈路配置不對輸出全是黑的算法自然誤報遮擋。5.2 典型問題速查表實際項目里常見問題翻來覆去就那幾個我整理成速查表遇到類似現(xiàn)象可以先對照檢查問題現(xiàn)象可能原因解決思路頻繁上報遮擋閾值過于激進調(diào)整方差下限和邊緣占比下限增加連續(xù)判定幀數(shù)遮擋后很久才上報判定窗口太長或檢測幀率太低縮短連續(xù)幀數(shù)提高檢測幀率到10fps以上遮擋解除后仍處于遮擋狀態(tài)遲滯退出條件過嚴(yán)降低退出閾值縮短退出判定幀數(shù)夜間誤報遮擋直接套用了白天閾值增加夜間模式閾值組降低對亮度和方差的要求運動檢測頻繁誤報網(wǎng)格太細(xì)、閾值太低增大觸發(fā)閾值增加連續(xù)觸發(fā)幀數(shù)調(diào)整靈敏度檔位運動檢測漏報低速目標(biāo)幀率太高導(dǎo)致幀差過小適當(dāng)降低檢測幀率或者使用背景累計法替代幀差法ROI畫框偏移坐標(biāo)基準(zhǔn)使用了編碼分辨率而非檢測分辨率統(tǒng)一換算到檢測幀坐標(biāo)系考慮電子放大和裁剪因素告警風(fēng)暴缺少事件去抖和靜默期增加靜默期合并激活Block按ROI過濾事件CPU占用偏高檢測分辨率過大或逐像素計算量大把檢測幀降到CIF/D1灰度化后計算開啟NEON優(yōu)化5.3 性能優(yōu)化方法如果想在低端平臺上把CPU占用壓到最低有幾個非常實用的辦法。第一個辦法是“降分辨率再做差分”。不要直接拿主碼流的YUV做運動檢測先用VGS把分辨率降到CIF甚至更低。分辨率每降一半像素量降到四分之一計算量急劇減少。實測把1080P降到CIF差分計算耗時可以降到一個數(shù)量級。第二個辦法是“算灰度不做色度”。運動檢測和遮擋檢測都只需要Y分量。從NV12里面取Y平面非常方便因為Y數(shù)據(jù)在內(nèi)存前部連續(xù)存放直接取指針即可不需要單獨做色彩空間轉(zhuǎn)換。如果平臺輸出是YUV420SP正好Y就是那段連續(xù)緩沖。第三個辦法是“分可執(zhí)行塊并行”。檢測幀按Block粒度切分后多核并行計算。C語言里簡單用多線程每個線程處理一行或若干行Block基本上可以吃滿多核。注意線程數(shù)和Block數(shù)要匹配Block太少時拆線程反而增加調(diào)度開銷。第四個辦法是“限制檢測幀率”。遮擋檢測可以每2~3幀跑一次統(tǒng)計運動檢測才需要保持較高幀率。功能分化后CPU占用自然降下來。還有一個小技巧檢測幀的獲取盡量用“同步讀取、拷貝到本地緩存”的方式。不要在檢測線程里直接拿著平臺SDK的緩沖指針跑長計算否則會導(dǎo)致VPSS通道緩沖無法及時歸還嚴(yán)重的會阻塞整個視頻流水線。正確做法是拿到幀后先memcpy到自己的內(nèi)存池然后立刻釋放平臺緩沖。這個細(xì)節(jié)在很多項目里被反復(fù)踩坑值得優(yōu)先注意。寫在最后做這塊功能的時候我最大的體會是遮擋檢測和運動檢測都不是“算法多高深”的活而是“工程細(xì)節(jié)多扎手”的活。平臺數(shù)據(jù)鏈路、坐標(biāo)換算、曝光變化、閾值遲滯每一項都比想象中更能影響最終效果。先把低分辨率檢測幀這一路打通把統(tǒng)計值和差分能量日志打清楚再把靈敏度、判定窗口、去抖靜默這些參數(shù)做成可調(diào)試配置項剩下的事情就是現(xiàn)場微調(diào)了。最后分享一個小技巧驗收測試時不要只在辦公桌前面測幾眼就下結(jié)論。準(zhǔn)備幾段典型場景錄像循環(huán)播放分別覆蓋白天強光、夜間紅外、逆光、鏡頭臟污、高速運動、長時間靜止這些情況然后反復(fù)觀察誤報和漏報。我當(dāng)時就是靠這種“錄像回放參數(shù)實時調(diào)節(jié)”的流程把一次很難纏的現(xiàn)場誤報問題從懷疑算法故障縮小到“自動曝光收斂期間閾值過緊”這一個配置項上。等你把這些基礎(chǔ)功打牢后面再往里疊加人形檢測、越界檢測這類重模型就輕松多了。