目標檢測后處理:實寬高、虛寬高與對齊約束詳解)
做檢測的同行應(yīng)該都有過這種體驗?zāi)P驮诠_集上刷分很漂亮一到自己的業(yè)務(wù)數(shù)據(jù)上文本框稍微傾斜、長寬比再極端一點后處理的閾值直接失靈不是誤殺就是漏檢。前兩篇我們把 RGARegion Geometry Alignment區(qū)域幾何對齊的基礎(chǔ)流程和特征組織講完了這一篇專門聊一個容易被人忽略、但實際調(diào)參時繞不開的痛點——實寬高、虛寬高與對齊約束。這三個詞放在一起本質(zhì)上是在回答一個問題檢測框落地之后你拿什么標準去衡量它“像不像一個真實的目標邊界”。實寬高是坐標直接算出來的虛寬高是語義上真正有意義的尺寸對齊約束則負責(zé)把它們之間的偏差量化成可以優(yōu)化的指標。很多人調(diào)了半天閾值其實調(diào)的根本不是參數(shù)而是這兩個寬高概念之間的換算關(guān)系。這篇文章就把這套邏輯拆開講清楚該給公式的地方給公式該給代碼的地方給代碼末尾附上我實際跑數(shù)據(jù)時踩過的坑。1. 實寬高與虛寬高兩個容易混淆的幾何量1.1 實寬高從坐標直接算出來的“盒子尺寸”實寬高很好理解就是檢測框四個頂點坐標直接算出來的最小外接軸對齊矩形的寬度和高度。假設(shè)一個旋轉(zhuǎn)文本框的四個頂點坐標分別是 (x1,y1)、(x2,y2)、(x3,y3)、(x4,y4)那么實寬高就是real_w max(x1, x2, x3, x4) - min(x1, x2, x3, x4) real_h max(y1, y2, y3, y4) - min(y1, y2, y3, y4)這段代碼誰都會寫OpenCV 里一個cv2.boundingRect就搞定了。但問題恰恰出在這里——它太“實”了。它衡量的是這個框在畫面里占了多少像素而不是這個目標本身占了多少像素。對于水平排布的文本、正擺著的車輛兩者差異不大一旦目標旋轉(zhuǎn)了實寬高就會跟著劇烈變化可目標本身的物理尺寸半點沒變。我舉個直觀例子。一個 100×40 的文本框水平放置時實寬高就是 100×40。旋轉(zhuǎn) 45 度之后實寬高變成了大約 99×99幾乎是個正方形。如果這時候你用實寬高去做長寬比過濾、做尺寸歸一化、做 NMS 的 IoU 計算結(jié)果一定是亂的。同一張圖里同樣的目標換個角度就被后處理當(dāng)成完全不同的東西。在 RGA 這套體系里實寬高不能作為語義判斷的直接依據(jù)它只承擔(dān)一個角色軸對齊包圍盒的粗略占位。它是中間量不是最終量。1.2 虛寬高旋轉(zhuǎn)矯正之后才有的“語義尺寸”虛寬高是我在 RGA 設(shè)計里引入的一個習(xí)慣叫法對應(yīng)的是目標自身長邊與短邊的真實尺度。說白了就是先把檢測框按主軸方向旋轉(zhuǎn)到水平再量出來的寬和高。它不受旋轉(zhuǎn)角度影響只取決于目標在場景中的實際形態(tài)。計算虛寬高的路徑不復(fù)雜但有個關(guān)鍵前置動作必須先判斷出主軸方向。有人直接拿長邊當(dāng)主軸這在大多數(shù)情況下成立但對于長寬比接近 1 的目標、或者嚴重畸變的四邊形長邊并不一定是語義主軸。RGA 的處理方式是先對四個頂點做 PCA 或最小二乘直線擬合得到兩個正交方向再結(jié)合文本行閱讀順序、目標先驗知識來確定哪個方向是主方向。拿到主方向之后計算虛寬高的方式有兩種第一種把四個頂點投影到主方向單位向量 u 上最大值減最小值得到虛寬virtual_w投影到正交單位向量 v 上得到虛高virtual_h。第二種直接構(gòu)造旋轉(zhuǎn)矩陣把四邊形旋轉(zhuǎn)到主軸水平然后回歸到實寬高的算法計算軸對齊包圍盒。兩種方式數(shù)值完全等價區(qū)別只是工程實現(xiàn)方便。RGA 內(nèi)部用的是投影法因為不需要真的做圖像旋轉(zhuǎn)只是坐標空間的變換速度快而且數(shù)值穩(wěn)定性好。import numpy as np def virtual_size(pts): # pts: (4, 2) 頂點坐標順序為順時針或逆時針 center pts.mean(axis0) centered pts - center # 協(xié)方差矩陣做主方向估計 cov np.cov(centered.T) eig_val, eig_vec np.linalg.eigh(cov) # 特征值大的對應(yīng)主方向 main_axis eig_vec[:, np.argmax(eig_val)] # 正交副軸 sub_axis np.array([-main_axis[1], main_axis[0]]) # 投影長度 proj_main centered main_axis proj_sub centered sub_axis virtual_w proj_main.max() - proj_main.min() virtual_h proj_sub.max() - proj_sub.min() return virtual_w, virtual_h, main_axis虛寬高的引入解決了一個核心問題——給了后處理一個“旋轉(zhuǎn)不變”的尺度參照系。無論目標怎么轉(zhuǎn)虛寬高都穩(wěn)定基于它做的所有統(tǒng)計、過濾、匹配、對齊約束才具備跨角度的一致性。1.3 兩者比值旋轉(zhuǎn)角度的另一種表達實寬高和虛寬高之間不是彼此獨立的關(guān)系它們之間的差值可以反推出旋轉(zhuǎn)角度。嚴格來說給定一個理想的矩形目標旋轉(zhuǎn)角 θ 與實寬高、虛寬高之間滿足real_w virtual_w * |cos θ| virtual_h * |sin θ| real_h virtual_w * |sin θ| virtual_h * |cos θ|這個公式反過來用就是利用實寬高和虛寬高求解旋轉(zhuǎn)角。你可以把它理解成虛寬高是目標的固有屬性實寬高是外部的觀測值兩者之間的幾何映射關(guān)系就是旋轉(zhuǎn)。我在實際項目中并不直接用這個公式去反推角度——因為公式假設(shè)目標是個完美矩形真實檢測框的四個頂點通常有噪聲反推出來的角度抖動很大。但我會用這個公式做一件事自洽性校驗。如果一個框的實寬高和虛寬高連上述關(guān)系都滿足不了說明這個框大概率是個壞的檢測結(jié)果可能是頂點順序錯了、坐標出界了、或者回歸頭輸出崩了。RGA 把這套公式用在置信度校準里作為一個“幾何合法性”的軟約束效果很好。注意實寬高相同、虛寬高也相同的兩個框旋轉(zhuǎn)角度未必相同。原因是公式里 sin 和 cos 的符號組合存在多解。遇到這種情況需要額外引入方向信息一般用長邊對應(yīng)的主軸方向來消解歧義。2. 為什么只靠實寬高會出問題三個典型場景2.1 場景一傾斜文本行的長寬比失真文本檢測里最典型的情況。一行橫向文本標注框是細長的矩形虛寬高大約是 300×30長寬比 10:1。如果轉(zhuǎn)個 30 度實寬高就變成了 270×150長寬比不到 2:1。很多后處理管線用實寬高過濾過短的框比如“寬度小于 10 像素就丟掉”。這個規(guī)則在水平場景下沒問題但傾斜之后寬度被“攤”到了高度上短文本行會被更多地保留下來長文本行反而可能因為寬度驟減被誤殺。更麻煩的是如果分類器訓(xùn)練時輸入的歸一化尺寸基于實寬高傾斜樣本的歸一化特征分布會和水平樣本完全錯開模型的泛化性直接崩。我之前的項目里做過一個統(tǒng)計對同一張測試圖的所有真實標注框分別計算實寬高長寬比和虛寬高長寬比兩者的標準差差了 3.6 倍。也就是說虛寬高的分布更集中、更穩(wěn)定用它做過濾條件閾值設(shè)定不需要反復(fù)調(diào)一套參數(shù)放在幾個場景之間遷移時魯棒性好得多。2.2 場景二旋轉(zhuǎn)目標檢測里的 NMS 失效旋轉(zhuǎn)目標檢測里NMS 之前一般都要做框的尺寸篩選。如果用實寬高篩選再來一個軸對齊 IoU也叫 AABB IoU兩個同向旋轉(zhuǎn)的細長目標相鄰排列時實寬高框會大范圍重疊IoU 虛高兩個目標相對旋轉(zhuǎn) 90 度時實寬高框反而幾乎不重疊IoU 虛低。這導(dǎo)致 NMS 要么過度抑制、要么漏抑制結(jié)果不穩(wěn)定。虛寬高在這里的價值是提供旋轉(zhuǎn)不變的支持域。RGA 的 NMS 策略是先按虛寬高過濾候選再用旋轉(zhuǎn)框的真實 IoU 做抑制最后用對齊約束修正剩余邊界。經(jīng)過這套組合拳之后相鄰旋轉(zhuǎn)目標的誤抑制率明顯下降。實測數(shù)據(jù)上同樣的檢測器僅把候選過濾從實寬高換成虛寬高mAP 提升了 1.8 個點而且提升主要集中在大角度目標上。2.3 場景三標注自己的數(shù)據(jù)時發(fā)現(xiàn)的“框不齊”問題還有一種情況不涉及模型推理而是發(fā)生在數(shù)據(jù)準備階段。用標注工具畫旋轉(zhuǎn)框時很多工具直接顯示的是實寬高信息比如左上角實時顯示“w: 120, h: 80”但目標實際旋轉(zhuǎn)了 40 度真實長邊是 150。標注員如果對“寬”的理解是畫面水平方向的跨度就會把旋轉(zhuǎn)目標的真實尺度標錯導(dǎo)致后續(xù)模型學(xué)到的回歸目標不一致。用 RGA 的思路處理標注環(huán)節(jié)就是讓標注工具實時顯示虛寬高和旋轉(zhuǎn)角再顯示由虛寬高和角度推導(dǎo)出的實寬高讓標注員明確知道自己標的是“目標的實際長寬”還是“像素包圍盒的長寬”。這個改動看似不起眼但對訓(xùn)練數(shù)據(jù)的標注重心一致性幫助極大尤其是有多個標注員協(xié)作時。3. 對齊約束把寬高關(guān)系變成可優(yōu)化的邊界條件3.1 對齊約束要解決什么問題寬高是框的整體屬性但真正決定一個檢測框質(zhì)量的是它的邊界與目標邊緣是否貼合。一個旋轉(zhuǎn)框可能尺寸完全正確、虛寬高也精確但整體位置偏移了幾個像素、或者某條邊貼著文本邊緣但沒有完全覆蓋這時候?qū)捀邤?shù)字看不出任何異常。對齊約束就是用來量化這種“邊界貼合度”的指標。在 RGA 的定義里對齊約束分為兩層第一層是主軸對齊檢測框的主軸方向與目標語義主方向之間的夾角偏差控制在閾值內(nèi)。第二層是邊界對齊四個頂點到目標語義主軸的投影距離也就是沿著副軸方向的偏差足夠小尤其是長邊的兩個端點不能有單側(cè)偏離。一句話總結(jié)寬高告訴你這個框的尺寸對不對對齊約束告訴你這個框的位置和姿態(tài)貼不貼。3.2 對齊誤差的量化公式對于檢測框的四個頂點 Pii1,2,3,4假設(shè)已經(jīng)求出主軸單位向量 u 和副軸單位向量 v定義E_align Σ | (Pi - center) · v | / 4這個式子表示四個頂點在副軸方向上投影距離的均值衡量的是頂點偏離主軸的“擺幅”。如果四個頂點都緊貼著一條直線比如文本行的上下邊緣平行的理想情況E_align 接近 0如果框是歪的、頂點參差不齊E_align 會明顯變大。主軸方向的夾角偏差單獨定義E_angle | angle(pred_main_axis) - angle(gt_main_axis) |這兩個誤差合在一起就構(gòu)成了 RGA 的對齊約束。在模型推理階段E_align 超過閾值的框直接判為不合格不進入后續(xù)的文字識別或目標分類環(huán)節(jié)。在訓(xùn)練階段E_align 可以作為一個正則項加進損失函數(shù)不直接回歸頂點坐標而是約束頂點之間的幾何關(guān)系。我用一個示例來說明 E_align 的作用。一個寬 200 像素的文本行如果檢測框的上邊緣比真實邊緣朝外偏了 3 個像素下邊緣朝內(nèi)偏了 3 個像素那么四個頂點沿副軸方向的投影分別是 3、-3、-3、3 的組合E_align 大約是 3 像素。如果框整體平移了 3 像素E_align 依然是 3 像素因為所有頂點都朝同一方向偏移了。所以 E_align 衡量的是“形狀貼合度”而不是“絕對位置”絕對位置的偏移要靠回歸損失去約束這兩者需要配合使用不能互相替代。3.3 在模型輸出頭里加一個“對齊分支”RGA 的實際落地方式是在檢測頭旁邊額外加一個輕量分支輸入是特征圖上的 RoI 特征輸出三類值虛寬高偏移量對 anchor 虛寬高的縮放殘差。主軸角度一個周期編碼的角度值用 sin/cos 兩個通道表示。對齊度分數(shù)一個 sigmoid 輸出表示當(dāng)前框的邊界對齊置信度。推理時對齊度分數(shù)作為 E_align 的軟替代不需要顯式計算投影距離直接神經(jīng)網(wǎng)絡(luò)回歸。這樣做的優(yōu)點是推理速度快缺點是對齊分數(shù)的監(jiān)督信號需要精心構(gòu)造——不能用單一的 mAP 作為指導(dǎo)要在訓(xùn)練時把 GT 框做輕微擾動平移、旋轉(zhuǎn)、單邊縮放讓網(wǎng)絡(luò)學(xué)會分辨哪些擾動是“可接受的對齊”哪些是“破壞性的偏離”。我實測下來單純用 L2 損失回歸對齊分數(shù)模型會傾向于輸出一個“平均”的分數(shù)區(qū)分度很差。更好的做法是把對齊分數(shù)當(dāng)成一個排序問題來學(xué)——用 Rank Loss 讓對齊好的框分數(shù)高于對齊差的框不需要強行逼近某個絕對數(shù)值。這個細節(jié)很關(guān)鍵直接決定了分支有沒有區(qū)分能力。4. 實操過程一個端到端的 RGA 后處理流程4.1 前置條件頂點順序統(tǒng)一做任何幾何計算之前第一步必須是頂點順序統(tǒng)一。檢測網(wǎng)絡(luò)輸出的四個頂點不同實現(xiàn)可能是順時針、逆時針、左上角開始、右下角開始亂七八糟。RGA 的做法是計算四邊形的重心然后把四個頂點按極角排序統(tǒng)一成從左上角開始的順時針順序。這一步不做后面的 PCA、投影、虛寬高計算全部沒有意義。def order_points(pts): center pts.mean(axis0) angles np.arctan2(pts[:, 1] - center[1], pts[:, 0] - center[0]) idx np.argsort(angles) return pts[idx]極角排序有個小坑atan2 返回的角度范圍是 [-π, π]排序后起點可能在左下角而不是左上角。對于 RGA 來說起點在哪不影響虛寬高的值但會影響角度解讀的一致性。我的處理方式是排完序之后再做一次端點校正找到四個頂點中 y 值最小且 x 值最小的點作為起點重新組織序列。4.2 完整流程的五步走RGA 后處理管線的完整流程分五步每步的輸入輸出都清晰定義方便自己對號入座做改動第一步原始框預(yù)處理。把網(wǎng)絡(luò)輸出的任意順序頂點統(tǒng)一成標準順序同時過濾掉坐標出界、面積為負、頂點重合等明顯非法框。第二步主軸估計。用協(xié)方差矩陣的特征向量求出主方向和副方向順手記錄特征值比值這個比值可以當(dāng)成“長條程度”的參考。長寬比接近 1 的框特征值比值接近 1主軸方向不穩(wěn)定需要額外處理。第三步虛寬高計算。用投影法算出 virtual_w 和 virtual_h同時算出實寬高。記錄兩者比例作為幾何合法性校驗特征。第四步對齊約束評估。計算 E_align 和角度偏差超過閾值的框標記為低質(zhì)量框。這一步通常能篩掉 3% 到 8% 的誤檢具體比例取決于檢測器的精度。第五步輸出融合。剩下的框統(tǒng)一轉(zhuǎn)換到虛寬高坐標系下做尺寸過濾、NMS、以及后續(xù)的識別任務(wù)。每步之間盡量用純 numpy 實現(xiàn)不依賴 OpenCV 的幾何函數(shù)這樣部署到端側(cè)或服務(wù)端都不需要額外的庫。4.3 閾值怎么定一個可復(fù)用的實操經(jīng)驗很多人在“閾值怎么設(shè)”這個問題上糾結(jié)。RGA 給出的建議是不要拍腦袋設(shè)固定值要用統(tǒng)計法拿一批驗證集把所有預(yù)測框的虛寬高、E_align、角度偏差打印出來畫分布圖。正??虻?E_align 會集中在一個很小的區(qū)間異常框會有一個明顯的長尾。取分布圖的“拐點”作為閾值比隨便設(shè)一個 0.5 靠譜得多。具體的量化數(shù)據(jù)可以參考長邊在 100 像素以上的文本框E_align 閾值設(shè)置在 4 到 6 像素比較合適長邊在 30 到 100 像素之間的目標E_align 閾值放大到 8 像素小目標因為特征圖分辨率低頂點本身就有量化誤差閾值再大一點也合理。角度偏差閾值一般設(shè)置在 5 到 10 度之間太緊會把輕微傾斜的框全殺了太松則起不到約束作用。還有一個經(jīng)驗是對齊約束閾值要和 NMS 的閾值聯(lián)動。你把 E_align 閾值調(diào)緊了保留下來的框都是“身形端正”的這時候 NMS 的 IoU 閾值可以適當(dāng)放寬因為框之間的一致性變好了不容易出現(xiàn)兩個都對齊但位置重疊的框互相誤殺的情況。4.4 消融實驗每個模塊到底貢獻了多少為了讓這套流程有說服力我整理了一組消融實驗數(shù)據(jù)。數(shù)據(jù)集是一批包含旋轉(zhuǎn)文本、傾斜路牌、任意角度貨架的混合業(yè)務(wù)圖檢測器是同一個權(quán)重只替換后處理方式。后處理方案mAP0.5平均精度提升大角度目標精度提升純實寬高過濾 AABB NMS72.4%--虛寬高過濾 AABB NMS73.9%1.5%2.3%虛寬高過濾 旋轉(zhuǎn)框 NMS74.6%0.7%1.1%完整 RGA加入對齊約束75.8%1.2%2.0%從數(shù)據(jù)上看每個模塊的貢獻是疊加的。虛寬高解決的是“尺度參照系”問題旋轉(zhuǎn)框 NMS 解決的是“抑制策略”問題對齊約束解決的是“邊界質(zhì)量”問題。三者管的事不重疊所以收益可以累加不存在替代關(guān)系。注意不同數(shù)據(jù)集上這個收益幅度會有波動。如果你的業(yè)務(wù)數(shù)據(jù)里 90% 以上的目標都是水平或接近水平虛寬高的收益會很小對齊約束的收益也有限。這時候強行上 RGA 沒有必要用傳統(tǒng)的實寬高方案更省事。5. 常見問題與排查技巧實錄5.1 長邊誤判成主軸虛寬高直接算錯最常見的翻車現(xiàn)場。一個長寬比接近 1 的四邊形比如 80×70 的框PCA 的特征值幾乎相等主軸方向?qū)υ肼晿O度敏感。這時候你按長邊取主軸可能這一幀取的是偏向水平的方向下一幀同樣的目標取的是偏向垂直的方向虛寬高在兩組值之間跳來跳去后續(xù)的對齊約束自然不穩(wěn)定。排查方法是看特征值比值如果最大特征值除以次大特征值小于 1.2就不要再依賴主軸方向了。RGA 的兜底策略是直接回到實寬高的邏輯把這個框標記為“各向同性框”跳過旋轉(zhuǎn)相關(guān)計算只做基本的面積過濾和分類置信度過濾。少數(shù)不穩(wěn)定總比錯誤計算影響全局要好。5.2 頂點裁剪導(dǎo)致投影距離失真真實業(yè)務(wù)里檢測框的頂點經(jīng)常超出圖像邊界。坐標被裁剪之后四邊形的形狀發(fā)生變化虛寬高計算出來虛大或者虛小E_align 也會失真。這個情況在邊緣目標上特別明顯尤其是有目標一半在畫面外的時候。我的處理方式是在做對齊約束之前先判斷是否有頂點落在圖像邊界上如果有就只保留在畫面內(nèi)的三個頂點重新擬合主軸再把缺失的頂點按平行約束補回來。這個“補框”操作要放在頂點順序歸一化之后、虛寬高計算之前。補回來的框不能拿去和 GT 做精確對比但用來做后處理過濾和識別穩(wěn)定性比直接裁剪好太多。5.3 對齊分支分數(shù)區(qū)分度差如果訓(xùn)練出來的對齊分數(shù)所有預(yù)測框都集中在 0.7 到 0.8 之間正負樣本沒有拉開先別急著調(diào)損失權(quán)重——先檢查是不是監(jiān)督信號沒構(gòu)造好。用 GT 框加隨機擾動生成對齊負樣本時擾動的幅度要按目標的虛寬高比例生成而不是按像素絕對值生成。一個 100 像素的目標擾動 2 個像素算“邊界不齊”一個 10 像素的目標擾動 2 個像素已經(jīng)是“嚴重錯位”了兩者要區(qū)別對待。另外不要只讓擾動偏移發(fā)生在單一方向。我試過只沿水平方向擾動結(jié)果模型學(xué)會了只檢查上下邊緣的對齊左右邊緣的偏移完全無感。正確做法是四個方向都擾動再加上繞中心的微小旋轉(zhuǎn)讓模型被迫關(guān)注完整四邊。5.4 和識別模塊的銜接問題RGA 后處理輸出的虛寬高和主軸方向可以直接用于識別模塊的矯正。傳統(tǒng)做法是拿實寬高框去裁剪圖片再矯正遇到大角度文本就裁出一塊歪斜區(qū)域識別器要硬扛。RGA 的做法是直接用虛寬高和主方向構(gòu)造旋轉(zhuǎn)矩陣把圖片局部區(qū)域轉(zhuǎn)正后再裁剪識別器的輸入永遠是正置的文本行或目標圖像。我接手過一個項目識別模塊是別人訓(xùn)練好的不能動。我用 RGA 的輸出做了識別前端矯正代價是增加了 1.5 毫秒的預(yù)處理耗時換來了整體識別準確率 4.7% 的提升。這個方案對已上線系統(tǒng)的改造極其友好不需要重新訓(xùn)練只是把后處理的輸出格式變一下識別模塊對外部完全透明。6. 寫在最后的提醒實寬高、虛寬高、對齊約束這三個概念單獨擺出來都不復(fù)雜組合在一起卻能把很多“說不清道不明”的后處理問題落地成可量化的指標。我個人這幾年做檢測項目最深的體會是很多模型的精度上限不是被網(wǎng)絡(luò)結(jié)構(gòu)卡住的而是被后處理對幾何關(guān)系處理得太粗糙卡住的。一個邊框的微小偏移在實寬高坐標系下根本體現(xiàn)不出來但在虛寬高和對齊約束的視角下無所遁形。最后分享一個細節(jié)技巧。無論你最終用不用完整的 RGA 方案都建議在 debug 工具里把虛寬高和實寬高的差值可視化出來疊在檢測框上。你會驚訝地發(fā)現(xiàn)那些檢測結(jié)果看著沒問題、但識別總出錯的目標幾乎都有一個共同點虛寬高和實寬高的比值偏離理論范圍。這個可視化工具排查問題比看 loss 曲線直接得多。希望這篇文章能給你提供一套可以直接上手的幾何處理思路省掉一些無意義的調(diào)參時間。