)
1. 大場景多視角跟蹤到底難在哪1.1 從單相機到多相機問題復雜度不是線性增長做過單相機目標跟蹤的人都知道只要目標不長時間被遮擋、外觀變化不太劇烈一個Kalman濾波加匈牙利匹配就能跑出不錯的結(jié)果。但把場景放大到園區(qū)、機場、火車站這類大范圍區(qū)域單相機的視野根本覆蓋不過來必須上多相機。多相機一上來問題就完全變了。首先是視角差異。同一個目標在相機A里是正面在相機B里可能只剩一個后腦勺在相機C里甚至被柱子擋了一半。傳統(tǒng)做法是提取ReID特征做跨相機匹配但ReID特征對視角變化非常敏感正面和背面的特征距離可能比兩個不同的人還大。其次是地面位置的不確定性。單相機里目標在圖像上的位置是確定的但映射到地面平面時由于深度估計誤差一個像素的偏差可能對應地面上好幾米的偏移。大場景下這種誤差會被放大到無法忽略的程度。再就是相機之間的關聯(lián)。多相機跟蹤本質(zhì)上是一個跨視角的數(shù)據(jù)關聯(lián)問題你要判斷相機A里的目標X和相機B里的目標Y是不是同一個人。這個判斷依賴兩個東西外觀相似度和空間位置一致性。外觀不可靠的時候空間位置就是最后的救命稻草。但空間位置又依賴于單相機的定位精度形成了一個循環(huán)依賴。注意很多論文在報告多相機跟蹤性能時只關注MOTA、IDF1這些指標但實際部署時真正要命的是ID Switch。一次錯誤的跨相機匹配會導致目標ID跳變后續(xù)所有關聯(lián)都會連鎖出錯。1.2 現(xiàn)有方案為什么不夠用目前主流的多相機跟蹤方案大致分兩類。一類是先單相機跟蹤再跨相機融合代表方法是基于軌跡拼接的思路。每個相機獨立跑單相機跟蹤器得到局部軌跡然后在某個中心節(jié)點做軌跡匹配。這類方法的問題是誤差會累積單相機的定位誤差直接傳遞到跨相機匹配階段而且軌跡拼接是離線或準離線的實時性差。另一類是聯(lián)合優(yōu)化把所有相機的檢測結(jié)果放在一起做全局關聯(lián)。這類方法理論上更優(yōu)但計算量隨相機數(shù)量指數(shù)增長大場景下根本跑不動。而且很多方法假設相機之間的地面平面是共享的實際場景中地面可能有坡度、有分層這個假設直接不成立。CVPR2026這篇工作的切入點很有意思它沒有去硬解跨相機匹配而是把視角信息和地面幾何顯式地建模成一個交互模塊讓網(wǎng)絡自己去學習“在什么視角下、目標在地面的什么位置、應該和哪個相機里的哪個目標關聯(lián)”。這個思路比強行做特征對齊要聰明得多。1.3 這篇工作的核心貢獻一句話說清用一句話概括通過View-Ground Interaction Transformer把多視角的視覺特征和地面平面的幾何約束做雙向交互讓模型在關聯(lián)目標時同時考慮“看起來像不像”和“站在哪里合不合理”在大場景多視角跟蹤基準上取得了最優(yōu)性能。這個思路的關鍵在于“交互”二字。不是簡單地把地面位置作為一個額外輸入拼接進去而是讓視角特征和地面特征互相查詢、互相修正。視角特征告訴地面模塊“這個目標在這個相機里長這樣”地面模塊告訴視角特征“根據(jù)地面幾何這個目標應該在這個位置”。兩個信息反復迭代最終得到一個既符合外觀又符合幾何的關聯(lián)結(jié)果。2. View-Ground Interaction Transformer的架構(gòu)拆解2.1 整體pipeline從多相機圖像到全局軌跡整個系統(tǒng)的輸入是N個相機的同步視頻流輸出是全局一致的目標軌跡。流程大致分四步單相機特征提取每個相機獨立跑一個檢測器加ReID特征提取器得到每幀的檢測框和對應的外觀特征。檢測器可以用YOLOv11或者DETR系列ReID特征用ResNet50-IBN這類對視角變化魯棒的骨干網(wǎng)絡。地面平面投影利用相機標定參數(shù)把每個檢測框的底部中心點投影到地面平面得到一個地面坐標。這個坐標是后續(xù)幾何約束的基礎。View-Ground Interaction編碼這是核心模塊。把每個相機的視角特征外觀特征加圖像位置和地面特征地面坐標加不確定性估計一起送入Transformer通過cross-attention讓兩者交互。全局關聯(lián)與軌跡管理交互后的特征用于計算跨相機的關聯(lián)代價矩陣用匈牙利算法做匹配匹配結(jié)果送入軌跡管理器更新全局軌跡。這個pipeline看起來不復雜但每個環(huán)節(jié)都有坑。比如地面投影那一步如果相機標定有誤差或者地面不是理想平面投影出來的坐標偏差會很大。論文里用了一個不確定性感知的投影模塊不是直接輸出一個點而是輸出一個概率分布這樣后續(xù)的交互模塊可以知道“這個地面位置有多可信”。2.2 視角特征和地面特征分別怎么表示視角特征的表示比較直接每個檢測框經(jīng)過ReID骨干網(wǎng)絡得到一個256維或512維的向量再加上檢測框在圖像中的歸一化坐標cx, cy, w, h和相機ID的embedding。這里有個細節(jié)相機ID的embedding很重要因為不同相機的成像特性不同網(wǎng)絡需要知道“這個特征來自哪個視角”。地面特征的表示就講究多了。最樸素的做法是把地面坐標(x, y)直接作為一個二維向量。但這樣丟失了不確定性信息。論文的做法是對每個檢測框通過相機投影矩陣和地面平面的交計算出一個地面坐標同時用檢測框的尺寸和位置估計一個協(xié)方差矩陣表示這個地面坐標的不確定性。最終地面特征是一個五維向量(x, y, σx, σy, ρ)其中ρ是x和y的相關系數(shù)。提示這個協(xié)方差矩陣的估計是整篇論文里最容易被忽略但最關鍵的細節(jié)。如果不確定性估計不準后續(xù)的交互模塊會被錯誤的地面位置帶偏。實際實現(xiàn)時可以用檢測框底部兩個角點的投影差異來估計σx和σy比用檢測框整體尺寸更穩(wěn)定。2.3 Cross-Attention怎么讓視角和地面互相修正交互模塊的核心是一個標準的Transformer decoder結(jié)構(gòu)但query和key/value的來源做了精心設計。具體來說視角到地面的查詢每個視角特征作為query去查詢所有地面特征。這一步的目的是根據(jù)目標的外觀找到地面上最可能的位置。比如一個人穿著紅色上衣視角特征會強烈地查詢那些地面位置上也有紅色目標的地面特征。地面到視角的查詢每個地面特征作為query去查詢所有視角特征。這一步的目的是根據(jù)目標的地面位置找到最可能匹配的視角特征。比如兩個目標在地面上距離很近地面特征會去查詢那些外觀相似但可能來自不同相機的視角特征幫助區(qū)分。這兩個方向的查詢交替進行通常做2-3輪。每輪之后視角特征和地面特征都會更新。最終用于關聯(lián)的特征是兩者拼接后的結(jié)果。這里有個工程上的trick位置編碼不能用標準的一維或二維正弦編碼。因為視角特征的位置是圖像坐標地面特征的位置是地面坐標兩者的尺度和語義完全不同。論文用了一個可學習的模態(tài)類型embedding加歸一化的坐標編碼讓網(wǎng)絡自己學會區(qū)分兩種位置信息。2.4 關聯(lián)代價矩陣的計算與匹配交互模塊輸出的是每個檢測框的增強特征。對于跨相機關聯(lián)計算任意兩個來自不同相機的檢測框之間的代價cost(i, j) 1 - cosine_sim(f_i, f_j) λ * geo_dist(g_i, g_j)其中f是增強后的視角特征g是增強后的地面特征geo_dist是地面坐標的歐氏距離除以不確定性。λ是一個平衡超參論文里設為0.3。這個代價矩陣用匈牙利算法求解得到跨相機的匹配。匹配結(jié)果再和上一幀的軌跡做關聯(lián)用的是標準的IoU加外觀特征的聯(lián)合代價。注意匈牙利算法是全局最優(yōu)的但計算量是O(n^3)。大場景下每幀可能有幾百個檢測框直接跑匈牙利會很慢。實際部署時可以用貪婪匹配加局部優(yōu)化或者用Sinkhorn算法做近似速度能快一個數(shù)量級精度損失很小。3. 實操復現(xiàn)從環(huán)境搭建到訓練調(diào)參3.1 環(huán)境準備與依賴安裝復現(xiàn)這篇工作硬件上至少需要一張24G顯存的卡比如3090或4090因為多相機數(shù)據(jù)加上Transformer的attention顯存占用不小。軟件環(huán)境推薦conda create -n vgi_track python3.9 conda activate vgi_track pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install mmcv-full1.7.1 mmdet2.28.2 mmtrack0.14.0 pip install einops timm scipy filterpy數(shù)據(jù)集方面大場景多視角跟蹤常用的有WildTrack、MultiviewX、CityFlow等。WildTrack有7個相機覆蓋一個約12m x 36m的廣場適合做初步驗證。CityFlow場景更大相機更多但標注也更復雜。提示W(wǎng)ildTrack的標注只有地面位置沒有完整的軌跡ID。做跟蹤訓練時需要自己根據(jù)幀間地面距離做軌跡關聯(lián)生成偽軌跡標簽。這個過程噪聲很大建議先用檢測精度驗證pipeline再上跟蹤。3.2 數(shù)據(jù)預處理相機標定與地面平面擬合多相機跟蹤的第一步是標定。每個相機需要內(nèi)參矩陣K和外參矩陣[R|t]。如果數(shù)據(jù)集沒有提供標定參數(shù)可以用棋盤格做離線標定或者用場景中的已知尺寸物體做自標定。地面平面的擬合更關鍵。假設地面是平面方程為n^T * X d其中n是平面法向量d是原點到平面的距離。用至少三個已知地面點的3D坐標可以解出n和d。實際場景中地面可能有輕微起伏可以用RANSAC擬合一個近似平面然后對每個相機單獨微調(diào)。import numpy as np def fit_ground_plane(points_3d): # points_3d: Nx3 array of ground points centroid np.mean(points_3d, axis0) centered points_3d - centroid _, _, Vt np.linalg.svd(centered) normal Vt[-1] d -np.dot(normal, centroid) return normal, d def project_to_ground(bbox_bottom, K, R, t, plane_normal, plane_d): # bbox_bottom: (u, v) bottom center of bbox pixel_homo np.array([bbox_bottom[0], bbox_bottom[1], 1.0]) ray_cam np.linalg.inv(K) pixel_homo ray_world R.T ray_cam cam_center -R.T t denom np.dot(plane_normal, ray_world) if abs(denom) 1e-6: return None s (plane_d - np.dot(plane_normal, cam_center)) / denom ground_point cam_center s * ray_world return ground_point[:2]這段代碼是地面投影的核心。注意denom接近零的情況說明射線和地面平行投影無解這種檢測框要直接丟棄。3.3 模型搭建View-Ground Interaction模塊的代碼實現(xiàn)交互模塊用PyTorch實現(xiàn)核心是一個自定義的Transformer層import torch import torch.nn as nn from einops import rearrange class ViewGroundInteraction(nn.Module): def __init__(self, d_model256, nhead8, num_layers3, dropout0.1): super().__init__() self.view_proj nn.Linear(256, d_model) self.ground_proj nn.Linear(5, d_model) self.modality_embed nn.Embedding(2, d_model) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforwardd_model*4, dropoutdropout, batch_firstTrue ) self.view_encoder nn.TransformerEncoder(encoder_layer, num_layers) self.ground_encoder nn.TransformerEncoder(encoder_layer, num_layers) self.cross_view_to_ground nn.MultiheadAttention(d_model, nhead, batch_firstTrue) self.cross_ground_to_view nn.MultiheadAttention(d_model, nhead, batch_firstTrue) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) def forward(self, view_feats, view_pos, ground_feats, ground_pos): # view_feats: B x Nv x 256 # ground_feats: B x Ng x 5 v self.view_proj(view_feats) self.modality_embed(torch.zeros(1, deviceview_feats.device)) g self.ground_proj(ground_feats) self.modality_embed(torch.ones(1, deviceground_feats.device)) v v view_pos g g ground_pos v self.view_encoder(v) g self.ground_encoder(g) # view queries ground v2g, _ self.cross_view_to_ground(v, g, g) v self.norm1(v v2g) # ground queries view g2v, _ self.cross_ground_to_view(g, v, v) g self.norm2(g g2v) return v, g這個實現(xiàn)里view_pos和ground_pos是位置編碼。視角位置編碼用檢測框的歸一化坐標經(jīng)過一個MLP得到地面位置編碼用地面坐標和不確定性經(jīng)過另一個MLP得到。兩個MLP不共享參數(shù)因為兩種位置的語義完全不同。注意modality_embed的初始化很關鍵。如果隨機初始化訓練初期兩個模態(tài)的特征會混在一起收斂很慢。建議用小的標準差比如0.02初始化并且在前幾個epoch凍結(jié)等特征投影層穩(wěn)定后再解凍。3.4 訓練策略與損失函數(shù)設計訓練分兩個階段。第一階段單獨訓練單相機檢測和ReID特征提取用標準的檢測損失加ReID的triplet loss。第二階段凍結(jié)檢測和ReID部分只訓練View-Ground Interaction模塊和關聯(lián)頭。關聯(lián)頭的損失用對比學習的形式def association_loss(enhanced_feats, gt_matches, temperature0.07): # enhanced_feats: B x N x D # gt_matches: B x N x N binary matrix feats_norm F.normalize(enhanced_feats, dim-1) sim_matrix feats_norm feats_norm.transpose(-2, -1) / temperature pos_mask gt_matches.bool() neg_mask ~pos_mask pos_loss -torch.log(torch.exp(sim_matrix[pos_mask]).sum() / torch.exp(sim_matrix).sum(dim-1, keepdimTrue).expand_as(sim_matrix)[pos_mask]) return pos_loss.mean()這個損失函數(shù)的核心思想是讓匹配的目標對在特征空間里更近不匹配的更遠。temperature設為0.07是經(jīng)驗值太小會導致梯度爆炸太大則對比效果不明顯。訓練時的batch size建議設為4到8個場景每個場景采樣8到16個相機。學習率用1e-4cosine退火訓練50個epoch左右。如果顯存不夠可以用梯度累積。提示第二階段訓練時ReID特征雖然凍結(jié)了但最好還是用EMA的方式維護一個動量更新的特征副本用于計算對比損失。直接用當前batch的特征做對比負樣本不夠豐富容易過擬合。4. 踩坑記錄與性能調(diào)優(yōu)實戰(zhàn)4.1 地面投影誤差導致的關聯(lián)失敗這是最常見的坑。相機標定稍微有點誤差或者地面不是完美平面投影出來的地面坐標就會偏。我試過在WildTrack上直接用論文的標定參數(shù)結(jié)果發(fā)現(xiàn)某些相機的投影誤差超過1米導致跨相機關聯(lián)的geo_dist完全不可用。解決辦法有兩個。一是在線標定微調(diào)用場景中已知長度的物體比如車道線、地磚做參考對每個相機的投影矩陣做一個小的修正。二是不確定性加權(quán)不要直接用歐氏距離而是用馬氏距離把投影誤差納入?yún)f(xié)方差矩陣。論文里用的是第二種實際測試下來馬氏距離比歐氏距離的IDF1高了3個點左右。def mahalanobis_distance(g1, g2, sigma1, sigma2): # g1, g2: (x, y) # sigma1, sigma2: 2x2 covariance matrices diff g1 - g2 sigma sigma1 sigma2 np.eye(2) * 1e-6 inv_sigma np.linalg.inv(sigma) return np.sqrt(diff inv_sigma diff.T)4.2 相機數(shù)量增加時的顯存爆炸View-Ground Interaction的attention復雜度是O(Nv * Ng)Nv是所有相機的檢測框總數(shù)Ng是地面特征總數(shù)。8個相機、每幀20個檢測框Nv就是160attention矩陣是160x160還好。但16個相機、每幀50個檢測框Nv就是800attention矩陣變成800x800顯存直接爆。優(yōu)化手段有幾個。一是分塊計算把相機分成若干組組內(nèi)做full attention組間做稀疏attention。二是降采樣地面特征不是每個檢測框都生成一個地面特征而是把地面劃分成網(wǎng)格每個網(wǎng)格一個特征檢測框通過查詢網(wǎng)格來獲取地面信息。三是用FlashAttentionPyTorch 2.0以上版本自帶能省不少顯存。我實測下來分塊加FlashAttention的組合最穩(wěn)16個相機、每幀50個檢測框的情況下顯存占用從24G降到了11G速度還快了20%。4.3 ID Switch的排查與抑制ID Switch是多相機跟蹤最頭疼的問題。排查的時候我一般按這個順序查排查項檢查方法常見問題檢測質(zhì)量看單相機檢測的漏檢和誤檢漏檢導致軌跡斷裂誤檢導致錯誤關聯(lián)ReID特征可視化特征分布看同類是否聚集視角變化大時特征發(fā)散地面投影檢查投影點的地面誤差標定誤差導致投影偏移關聯(lián)閾值調(diào)整代價矩陣的閾值閾值太松導致誤匹配太緊導致漏匹配軌跡管理檢查軌跡的出生和死亡邏輯軌跡過早死亡導致ID跳變抑制ID Switch的一個實用技巧是軌跡級的外觀記憶。不要只用當前幀的特征做關聯(lián)而是維護每個軌跡的歷史特征池關聯(lián)時用歷史特征的平均或最大相似度。這樣即使當前幀的外觀變化大歷史信息也能兜底。class TrackletMemory: def __init__(self, max_len30): self.features [] self.max_len max_len def update(self, feat): self.features.append(feat) if len(self.features) self.max_len: self.features.pop(0) def similarity(self, feat): if not self.features: return 0.0 sims [F.cosine_similarity(feat, f, dim-1) for f in self.features] return max(sims)4.4 實際部署時的速度優(yōu)化論文里的模型在V100上跑8個相機、每幀30個檢測框大概15FPS。實際部署到邊緣設備上這個速度不夠。優(yōu)化方向檢測器換輕量級YOLOv11-nano比YOLOv11-x快5倍精度掉2個點但跟蹤整體性能只掉0.5個點因為跟蹤對檢測精度的敏感度沒那么高。ReID特征降維256維降到128維速度提升30%精度掉1個點。交互模塊量化用FP16推理速度提升40%精度幾乎無損。異步pipeline檢測和跟蹤異步檢測跑在GPU上跟蹤跑在CPU上用隊列做緩沖。我試過在Jetson Orin上部署8個相機、每幀20個檢測框優(yōu)化后能跑到8FPS基本滿足實時性要求。注意量化的時候要小心LayerNorm層FP16下容易溢出。建議LayerNorm保持FP32其他層用FP16。混合精度訓練時也要注意這個問題用torch.cuda.amp自動處理。5. 這個方向還能怎么玩View-Ground Interaction這個思路其實不限于多相機跟蹤。任何涉及“視覺信息加幾何約束”的任務都可以借鑒。比如多機器人協(xié)同感知每個機器人有自己的視角通過地面位置做關聯(lián)比純視覺關聯(lián)更魯棒。AR/VR中的多用戶定位多個頭顯的視角加地面位置可以做更準確的用戶位置融合。自動駕駛的車路協(xié)同路側(cè)相機和車端相機的融合地面平面是天然的對齊基準。我個人覺得最有意思的擴展是動態(tài)地面平面?,F(xiàn)在的方法假設地面是靜態(tài)平面但實際場景中可能有移動的物體改變地面幾何比如電梯、斜坡。如果能把地面平面也做成可學習的、隨時間變化的應該能進一步提升性能。另外現(xiàn)在的交互模塊只用了外觀和地面位置沒有用時序信息。如果把歷史幀的視角和地面特征也納入交互應該能更好地處理遮擋和短暫消失的情況。這個方向我還在試初步結(jié)果看起來有希望但計算量是個問題。最后分享一個小技巧訓練的時候可以先用單相機數(shù)據(jù)預訓練ReID特征再用多相機數(shù)據(jù)微調(diào)交互模塊。這樣收斂快很多而且對相機數(shù)量變化更魯棒。我試過從4個相機訓練好的模型直接遷移到8個相機只微調(diào)交互模塊性能就能恢復到從頭訓練的95%以上。