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