:從運(yùn)動矢量到安全系統(tǒng)異常行為判定)
1. 光流分析到底在解決什么問題第一次看到optical_flow_bicycle.hdev這個例程名的時候我腦子里冒出的第一個念頭是光流這東西不是自動駕駛和視頻壓縮領(lǐng)域的??蛦嵩趺磁艿焦I(yè)視覺的安全系統(tǒng)里來了。后來把例程跑通、又拿自己的幾段現(xiàn)場視頻替換測試之后才明白Halcon 把這個例子歸到“安全系統(tǒng)”分類下是有講究的——它演示的不是“算出一張漂亮的光流圖”這么簡單而是用光流場去判斷畫面中運(yùn)動物體的方向、速度與一致性從而為異常行為判定提供依據(jù)。先把概念說清楚。光流Optical Flow描述的是圖像序列中像素級的運(yùn)動矢量相鄰兩幀之間每個像素從哪兒移動到了哪兒用一個二維向量 (u, v) 表示。把所有像素的向量畫出來就得到一張稠密的運(yùn)動場。它和“目標(biāo)跟蹤”不是一回事——跟蹤關(guān)心的是“這個物體去哪了”光流關(guān)心的是“畫面里每一小塊區(qū)域各自怎么動的”。這個差別決定了光流的強(qiáng)項不需要事先知道目標(biāo)是什么、不需要訓(xùn)練模型、不需要分割輪廓只要畫面有紋理、有相對運(yùn)動就能算出運(yùn)動場。那它為什么能進(jìn)安全系統(tǒng)想象一個場景監(jiān)控畫面里有人群走動正常情況所有人流方向大體一致突然有一小塊區(qū)域的光流方向與周圍明顯相反或者某個區(qū)域光流幅值突然飆升這往往意味著逆行、奔跑、推搡甚至跌倒。這類“運(yùn)動一致性異?!庇霉饬鱽砜坍嫹浅W匀欢矣嬎懔靠煽剡m合在邊緣設(shè)備上做前置篩查。optical_flow_bicycle.hdev這個例程用的是一段自行車運(yùn)動的視頻核心就是演示如何用 Halcon 的光流算子把幀間運(yùn)動場算出來、可視化、并做基本的矢量分析。適合誰看這篇做過一點 Halcon 基礎(chǔ)算子、想往視頻分析方向走的視覺工程師做安防、行為分析、交通監(jiān)控想找一個不依賴深度學(xué)習(xí)的輕量運(yùn)動分析方案的開發(fā)者以及單純想把 Halcon 視頻類例程吃透、搞明白算子參數(shù)怎么調(diào)的人。下面我按“設(shè)計思路—核心算子—實操流程—踩坑排查”的順序把這個例程拆開講中間會補(bǔ)不少例程里沒寫、但實際替換自己視頻時一定會遇到的問題。2. 例程整體設(shè)計與方案選型拆解2.1 為什么選光流而不是幀差或背景建模做運(yùn)動分析最樸素的辦法是幀差法兩幀相減閾值化得到運(yùn)動區(qū)域。它快、簡單但有個致命短板——只知道“這里動了”不知道“往哪個方向動、動多快”。安全系統(tǒng)里“有人經(jīng)過”和“有人逆行”在幀差圖上是完全一樣的這就沒法做方向性判定。背景建模比如高斯混合模型能分離前景但同樣丟失方向信息而且對光照突變、背景抖動很敏感。光流補(bǔ)的正是這塊短板。它輸出的是矢量場方向、幅值都有天然適合做“運(yùn)動一致性”分析。代價是計算量比幀差大而且對紋理有要求——純色墻面、大面積天空這種低紋理區(qū)域光流算出來是噪聲。Halcon 提供的幾種光流算法里例程用的是基于變分的方法optical_flow_mg它在稠密性和魯棒性之間平衡得比較好適合中低分辨率視頻。這里有個選型上的關(guān)鍵判斷我得說清楚如果你的需求只是“檢測有沒有運(yùn)動”別上光流幀差足夠且快十倍只有當(dāng)你要判斷運(yùn)動的方向、速度、一致性時光流才值得這個算力開銷。例程把它放在安全系統(tǒng)分類下前提就是“需要方向性信息”這個場景。2.2 例程的骨架讀幀、算流、可視化、分析把optical_flow_bicycle.hdev從頭到尾讀一遍邏輯其實很線性打開視頻文件讀取第一幀作為參考幀循環(huán)讀取后續(xù)幀對相鄰兩幀調(diào)用光流算子得到矢量場把矢量場轉(zhuǎn)成可視化圖像用顏色編碼方向、亮度編碼幅值對矢量場做統(tǒng)計或區(qū)域分析比如求平均運(yùn)動方向、找異常區(qū)域顯示結(jié)果繼續(xù)下一對幀。這個骨架的價值在于它把“光流計算”和“光流應(yīng)用”分開了。計算部分就是一次算子調(diào)用應(yīng)用部分才是真正體現(xiàn)業(yè)務(wù)邏輯的地方。很多人跑完例程覺得“哦出來一張彩色的圖”然后就卡住了——因為不知道拿這張圖干嘛。我的建議是把可視化那步當(dāng)成調(diào)試手段真正的產(chǎn)出應(yīng)該是矢量場的數(shù)值統(tǒng)計比如全圖平均流向、分區(qū)域流向直方圖、幅值超閾值的像素占比。這些數(shù)值才是能接進(jìn)業(yè)務(wù)判定邏輯的東西。2.3 參數(shù)選型的背后邏輯例程里光流算子有幾個關(guān)鍵參數(shù)我逐個說下它們?yōu)槭裁催@么設(shè)、以及換視頻時該怎么調(diào)。平滑參數(shù)Sigma控制光流場的空間平滑程度。設(shè)太小噪聲全進(jìn)來了矢量場花得沒法看設(shè)太大真實的小尺度運(yùn)動被抹平。例程里給的是一個中等偏大的值因為自行車視頻分辨率不低、紋理豐富需要一定平滑來壓制噪聲。換到低分辨率或高噪聲的監(jiān)控視頻這個值要適當(dāng)加大。迭代次數(shù)變分光流是迭代求解的迭代次數(shù)不夠矢量場沒收斂方向會偏次數(shù)太多耗時線性增長但精度提升有限。經(jīng)驗值是 30 到 100 之間例程取的是中間偏上的值。實測下來超過 100 之后肉眼幾乎看不出差別但耗時明顯上升所以別盲目加大。金字塔層數(shù)這是處理大位移的關(guān)鍵。光流算法假設(shè)幀間位移較小如果物體動得快比如自行車快速穿過畫面單層算不出來需要金字塔——先在低分辨率層算粗位移再逐層細(xì)化。層數(shù)不夠快速運(yùn)動區(qū)域的光流會“斷裂”或算成零。例程里自行車速度中等層數(shù)設(shè)得不算高但如果你換成快速移動的場景這個參數(shù)必須往上加。提示金字塔層數(shù)和最大可檢測位移大致是 2 的冪次關(guān)系。層數(shù)為 L 時理論上能處理約 2^L 像素的幀間位移。算一下你的場景里最快物體每幀移動多少像素反推層數(shù)比瞎調(diào)靠譜。3. 核心算子與實操流程詳解3.1 光流算子的調(diào)用方式與輸入要求Halcon 里做稠密光流的主力算子是optical_flow_mg它的輸入是兩幀灰度圖輸出是一個特殊的“矢量場”對象Vector Field不是普通圖像。這一點新手特別容易懵——拿到輸出想直接dev_display會發(fā)現(xiàn)顯示不出來得先轉(zhuǎn)換。調(diào)用形式大致是這樣基于例程結(jié)構(gòu)整理參數(shù)名以實際版本為準(zhǔn)* 讀取相鄰兩幀轉(zhuǎn)灰度 rgb1_to_gray (Image1, Gray1) rgb2_to_gray (Image2, Gray2) * 計算光流矢量場 optical_flow_mg (Gray1, Gray2, VectorField, fd, default, Sigma, Iterations, ...)算法類型參數(shù)例程里用的是變分類方法決定了求解框架一般不用改。真正要調(diào)的是平滑、迭代、金字塔這幾個數(shù)值參數(shù)。輸入圖像要求是單通道灰度彩色圖必須先轉(zhuǎn)灰度否則算子直接報錯。另外兩幀的尺寸必須完全一致這個看似廢話但如果你是從不同來源拼幀比如兩路攝像頭交替尺寸或?qū)R稍有偏差就會出問題。3.2 矢量場到可視化圖像的轉(zhuǎn)換矢量場對象不能直接顯示例程里用了一組轉(zhuǎn)換算子把它變成可看的圖像。核心思路是把每個矢量的方向映射成色調(diào)Hue幅值映射成飽和度或亮度。這樣一眼就能看出“這塊往右動、那塊往左動、哪里動得快”。轉(zhuǎn)換過程大致分兩步先把矢量場轉(zhuǎn)成實值圖像分別取 u 分量和 v 分量或者直接轉(zhuǎn)成方向幅值的表示再用hsv_to_rgb之類的色彩空間轉(zhuǎn)換拼成彩色圖。例程里這段代碼不長但值得逐行讀——因為它示范了“如何把抽象數(shù)據(jù)變成人能看懂的畫面”這個技巧在別的矢量數(shù)據(jù)可視化里也能復(fù)用。我自己的習(xí)慣是調(diào)試階段用彩色可視化看整體運(yùn)動是否合理正式運(yùn)行時只保留數(shù)值統(tǒng)計可視化圖按需生成比如異常觸發(fā)時才存一張圖留證避免每幀都做色彩轉(zhuǎn)換浪費算力。3.3 從矢量場提取可用統(tǒng)計量這是例程里最容易被忽略、但對業(yè)務(wù)最有價值的部分。拿到矢量場后可以做的統(tǒng)計包括全圖平均流向把所有矢量的角度做圓均值注意角度不能直接算術(shù)平均0 度和 359 度平均出來是 179 度錯的得到畫面整體運(yùn)動主方向分區(qū)域流向把畫面切成網(wǎng)格每格算平均流向比較相鄰格子的差異差異大的地方就是“運(yùn)動不一致”的候選異常區(qū)幅值分布統(tǒng)計幅值的均值、方差、超閾值像素占比用來判斷“運(yùn)動劇烈程度”方向一致性指標(biāo)算所有矢量方向的標(biāo)準(zhǔn)差或圓形方差一致性高說明大家朝一個方向走正常人流一致性低說明運(yùn)動雜亂可能異常。這些統(tǒng)計量用 Halcon 的vector_field_to_real配合常規(guī)的mean_image、deviation_image或者直接取像素值做數(shù)組運(yùn)算都能實現(xiàn)。例程里演示了其中一兩種剩下的需要自己補(bǔ)。我建議至少把“分區(qū)域方向一致性”做出來因為它是安全場景里最直接可用的判據(jù)。3.4 完整實操流程換成自己的視頻怎么跑光看例程不過癮得換成自己的素材驗證。我按實際操作順序列一遍第一步準(zhǔn)備視頻。Halcon 支持讀取常見格式的視頻文件用open_framegrabber或直接read_sequence讀幀序列。建議先把視頻轉(zhuǎn)成幀序列存盤比直接讀視頻文件穩(wěn)定也方便反復(fù)調(diào)試同一段。分辨率別太高720p 足夠1080p 以上光流計算耗時會上一個臺階。第二步確定幀間隔。相鄰幀算光流是最標(biāo)準(zhǔn)的做法但如果你的視頻幀率很高比如 60fps相鄰幀位移太小光流幅值微弱、信噪比低。這時候可以隔幀取每 2 幀或 3 幀算一次人為放大幀間位移。反過來幀率低、物體動得快就要靠金字塔層數(shù)來兜。這個“幀間隔 × 金字塔層數(shù)”的組合是調(diào)參的核心得一起考慮。第三步跑通單對幀。先別急著循環(huán)拿一對幀單獨算把矢量場可視化出來看。如果彩色圖一片混亂說明參數(shù)不對或圖像紋理太差如果運(yùn)動區(qū)域的光流方向和肉眼判斷一致說明參數(shù)基本對了。第四步加統(tǒng)計和判定。在單對幀驗證通過后把統(tǒng)計量算出來觀察正常片段和異常片段的數(shù)值差異定閾值。這一步一定要用真實數(shù)據(jù)定別拍腦袋。第五步循環(huán)處理并記錄。把上述流程包進(jìn)幀循環(huán)每對幀輸出一行統(tǒng)計記錄時間戳、平均流向、一致性指標(biāo)、異常標(biāo)志方便事后回看。* 偽代碼結(jié)構(gòu)示意 for FrameIndex : 1 to NumFrames by FrameStep read_image (Image2, FrameSequence) if (FrameIndex 1) optical_flow_mg (Gray1, Gray2, VectorField, ...) * 統(tǒng)計與判定 vector_field_to_real (VectorField, U, V) * 計算方向一致性、幅值統(tǒng)計... * 記錄或觸發(fā) endif copy_image (Gray2, Gray1) endfor這段結(jié)構(gòu)看著簡單但copy_image那步別漏——參考幀要更新成當(dāng)前幀否則你一直在拿第一幀和后面所有幀比光流會越算越離譜。4. 常見問題與排查技巧實錄4.1 光流圖“花了”或者“全黑”怎么辦這是替換素材后最高頻的問題。表現(xiàn)是可視化圖要么滿屏彩色噪點要么幾乎全黑看不出運(yùn)動。排查順序我總結(jié)成一張表現(xiàn)象可能原因排查與解決滿屏彩色噪點圖像紋理差或噪聲大先做高斯平滑預(yù)處理檢查是否純色背景占比過高幾乎全黑無運(yùn)動幀間位移太小或參數(shù)過大增大幀間隔減小平滑參數(shù)檢查兩幀是否真的不同運(yùn)動區(qū)域光流方向錯亂金字塔層數(shù)不足按最大位移反推層數(shù)適當(dāng)增加邊緣區(qū)域光流異常邊界效應(yīng)分析時裁掉圖像邊緣若干像素計算極慢分辨率過高或迭代過多降分辨率迭代次數(shù)控制在 100 以內(nèi)“全黑”這個現(xiàn)象特別值得說很多人以為是算子壞了其實是相鄰幀幾乎一樣比如視頻里有靜止片段光流自然接近零。先確認(rèn)兩幀確實有運(yùn)動再懷疑參數(shù)。4.2 角度統(tǒng)計算錯的坑前面提過方向是角度不能直接算術(shù)平均。我見過有人把一堆角度加起來除以個數(shù)結(jié)果 350 度和 10 度平均成 180 度方向完全反了。正確做法是轉(zhuǎn)成單位向量再平均把每個角度 θ 轉(zhuǎn)成 (cosθ, sinθ)分別求平均再用atan2轉(zhuǎn)回角度。Halcon 里沒有現(xiàn)成的圓均值算子得自己寫幾行。這個坑不踩一次很難記住但踩過之后就是肌肉記憶。4.3 閾值定不準(zhǔn)、誤報太多光流統(tǒng)計量是連續(xù)值直接卡一個硬閾值環(huán)境一變光照、人流密度就失效。我的經(jīng)驗是用滑動窗口做自適應(yīng)維護(hù)最近 N 幀的統(tǒng)計量均值與標(biāo)準(zhǔn)差當(dāng)前幀偏離均值超過 k 倍標(biāo)準(zhǔn)差才判異常。這樣不用手工調(diào)閾值系統(tǒng)自己適應(yīng)場景基線。k 取 2 到 3 之間太小誤報多太大漏報多。這個思路在例程里沒有但實際部署時幾乎是必需的。注意自適應(yīng)基線在場景切換時比如白天轉(zhuǎn)夜晚、鏡頭被遮擋后恢復(fù)會短暫失效需要加一個“基線重置”機(jī)制檢測到統(tǒng)計量突變持續(xù)若干幀后重新初始化窗口。4.4 性能不夠、跑不到實時光流是稠密計算每個像素都要解分辨率一上去就吃力。幾個實測有效的優(yōu)化手段降分辨率到 640×480 甚至更低安全場景往往不需要高分辨率就能判斷運(yùn)動方向減少迭代次數(shù)到 30 左右只在感興趣區(qū)域ROI內(nèi)算光流比如只處理畫面下半部分的人行區(qū)域隔幀計算用幀間隔換算力。這幾招組合下來普通工控機(jī)上跑到 15 到 25 幀每秒是可行的具體取決于硬件和分辨率得實測。4.5 例程代碼直接復(fù)用到項目里的注意事項例程為了演示清晰很多地方寫得比較“理想化”路徑寫死、沒有異常處理、可視化每幀都做。搬到項目里至少要改三處文件讀取加錯誤處理視頻打不開、幀讀失敗要有兜底可視化改成按需生成統(tǒng)計結(jié)果要落盤或推送到下游而不是只顯示在窗口里。另外例程里的參數(shù)是針對那段自行車視頻調(diào)的換素材必須重新驗證別直接抄參數(shù)上線。5. 這套方案能延展到哪些實際場景把例程吃透之后光流分析的延展性其實比想象中好。除了前面說的人群異常行為篩查還有幾類場景我實際接觸或驗證過思路可行。傳送帶異物與卡料檢測正常運(yùn)行時傳送帶上物料的光流方向一致、速度均勻出現(xiàn)卡料或異物橫插局部光流方向突變。相比傳統(tǒng)的對射傳感器光流能給出“哪個位置、往哪個方向異?!钡男畔⒍ㄎ桓?xì)。液體流動與泄漏初判管道或容器表面的液膜流動光流幅值和方向能反映流速變化泄漏初期往往伴隨局部流向異常。這類場景對紋理要求高需要表面有可追蹤的斑點或反光純透明無紋理的液體不適用。機(jī)械振動與松動監(jiān)測固定相機(jī)拍設(shè)備表面正常振動是高頻小幅值、方向規(guī)律松動或異常抖動會表現(xiàn)為光流方向雜亂、幅值分布改變。這個用法比較小眾但對周期性運(yùn)動的設(shè)備挺有效。交通流量與逆行檢測這個和例程最接近車流方向一致性高逆行車輛的光流方向與主流相反檢測邏輯直接復(fù)用“分區(qū)域方向一致性”那套。需要潑冷水的是光流不是萬能的。它對低紋理、大位移、劇烈光照變化、鏡頭抖動這四種情況都比較敏感。鏡頭抖動尤其致命——整幅畫面一起動光流全是同一個方向的大幅值真實運(yùn)動被淹沒。所以實際部署前要么用防抖支架要么加一步全局運(yùn)動補(bǔ)償先估計背景整體位移再減掉這一步例程里沒有但工程上繞不開。我個人在實際操作中的體會是光流分析這類方案的價值不在于“算法多先進(jìn)”而在于它把“運(yùn)動”這個模糊概念拆成了方向、速度、一致性三個可量化的維度讓安全判定從“感覺這里不對勁”變成“這塊區(qū)域流向偏離基線 2.8 個標(biāo)準(zhǔn)差”。例程只是給了個起點真正花時間的永遠(yuǎn)是拿自己的數(shù)據(jù)把基線摸清楚、把誤報壓下去。最后分享一個小技巧調(diào)試階段把每幀的光流統(tǒng)計量和可視化圖一起存成帶時間戳的文件事后回看異常片段時能快速定位到底是參數(shù)問題還是場景本身就有歧義比盯著實時窗口猜效率高得多。