:速度估計與軌跡追蹤代碼落地指南)
簡介這份PDF教程面向智能交通領(lǐng)域開發(fā)者、計算機視覺學(xué)習(xí)者與自動駕駛方向研究人員圍繞YOLOv11在實時車輛速度估計與軌跡追蹤中的實戰(zhàn)應(yīng)用展開幫助讀者掌握從模型訓(xùn)練到系統(tǒng)落地的完整鏈路。資源包內(nèi)含1個PDF文件大小約2.29MB支持目錄章節(jié)跳轉(zhuǎn)與閱讀器左側(cè)大綱快速定位便于按模塊查閱。文檔共45頁內(nèi)容涵蓋YOLOv11基礎(chǔ)原理與安裝配置、系統(tǒng)架構(gòu)設(shè)計、模型訓(xùn)練與優(yōu)化、基于視覺與雷達融合的速度估計算法以及卡爾曼濾波、匈牙利算法、SORT與DeepSORT等軌跡追蹤方案的實現(xiàn)與評估并附有配套代碼。目前已有77人學(xué)習(xí)適合具備一定深度學(xué)習(xí)基礎(chǔ)、希望將目標(biāo)檢測技術(shù)應(yīng)用于交通監(jiān)控與車輛分析的讀者參考可據(jù)此搭建實驗環(huán)境、復(fù)現(xiàn)關(guān)鍵算法并理解多傳感器融合的工程思路。1. 一份 45 頁的 YOLOv11 智能交通實戰(zhàn)文檔到底能不能直接跑起來上周有個做路口電警改造的朋友找我說手上拿到一份《YOLOv11在智能交通-實時車輛速度估計與軌跡追蹤實戰(zhàn)教程附代碼》45 頁目錄從需求分析一路鋪到系統(tǒng)集成測試看著挺全但他翻了兩遍還是不知道從哪下手——因為文檔里講架構(gòu)、講原理、講傳感器選型的篇幅占了七成真正能落地的代碼片段散落在各章且默認(rèn)你已經(jīng)有一套跑通的 YOLOv11 環(huán)境。這份文檔的真實定位是一份「系統(tǒng)設(shè)計說明書 關(guān)鍵算法實現(xiàn)參考」不是一份「從零到跑通」的保姆級教程。它適合誰適合已經(jīng)會用 Ultralytics 系 YOLO 做檢測、現(xiàn)在要把檢測結(jié)果接上速度估計和軌跡追蹤這兩個下游任務(wù)的工程師。如果你連 YOLOv11 權(quán)重文件怎么下載、推理結(jié)果怎么保存都還沒跑通這份文檔會讓你在第三章就卡住。下面我按「這份文檔講了什么 → 怎么把它變成能跑的代碼 → 哪些地方會翻車」的順序把它拆開講清楚。2. 文檔結(jié)構(gòu)拆解45 頁里哪些是干貨哪些是湊頁數(shù)2.1 從目錄看文檔的真實技術(shù)密度把 45 頁目錄過一遍能明顯看出內(nèi)容分三檔。第一檔是真正有實現(xiàn)價值的第三章 YOLOv11 安裝配置、第五章模型訓(xùn)練與優(yōu)化、第六章速度估計算法實現(xiàn)、第七章軌跡追蹤算法實現(xiàn)這四章加起來大約 20 頁是文檔的核心。第二檔是系統(tǒng)設(shè)計層面的描述第四章系統(tǒng)架構(gòu)設(shè)計講了數(shù)據(jù)采集層、處理層、決策層、展示層每層都有傳感器選型和布局建議這部分對做方案匯報有用但對寫代碼幫助有限。第三檔是需求分析和總結(jié)展望第二章、第九章、第十章基本是背景鋪墊可以快速跳過。判斷一份文檔值不值得細讀我一般看它有沒有給出「參數(shù)級」的信息。這份文檔在第六章給了速度估計的具體方法名——SIFT 特征點匹配、ORB、Lucas-Kanade 光流、卡爾曼濾波融合在第七章給了 SORT 和 DeepSORT 的算法原理和評估指標(biāo) MOTA、MOTP、IDSW。這些是能直接對應(yīng)到代碼的關(guān)鍵詞說明作者確實做過一輪技術(shù)調(diào)研不是純拼湊。2.2 速度估計與軌跡追蹤在文檔里的技術(shù)路線文檔給出的技術(shù)路線可以概括為YOLOv11 負(fù)責(zé)逐幀檢測車輛邊界框 → 多目標(biāo)跟蹤算法SORT/DeepSORT負(fù)責(zé)給每個框分配穩(wěn)定 ID 并維護軌跡 → 速度估計算法負(fù)責(zé)把像素位移換算成真實速度。這條路線是當(dāng)前工程上最主流的做法沒有花哨的地方但每一步都有坑。速度估計部分文檔列了兩條路純視覺和視覺雷達融合。純視覺的核心是把連續(xù)幀中同一輛車的像素位移通過標(biāo)定得到的地面像素當(dāng)量每像素對應(yīng)多少米換算成實際位移再除以幀間隔得到速度。雷達融合則是用毫米波雷達直接測速再和視覺結(jié)果做卡爾曼濾波融合。文檔對融合策略的描述停留在「加權(quán)平均法」和「卡爾曼濾波法」兩個名詞上沒有給權(quán)重怎么定、噪聲協(xié)方差怎么設(shè)這部分需要自己補。軌跡追蹤部分文檔把 SORT 和 DeepSORT 都講了。SORT 用卡爾曼濾波預(yù)測下一幀位置用匈牙利算法做檢測框與軌跡的匹配速度快但 ID 切換多DeepSORT 在 SORT 基礎(chǔ)上加了外觀特征ReID 模型提取的 embedding匹配時同時看運動信息和外觀相似度ID 穩(wěn)定性明顯更好代價是多了一個特征提取網(wǎng)絡(luò)的開銷。文檔建議用 DeepSORT這個建議在交通場景下是對的因為車輛被遮擋后重新出現(xiàn)時純運動匹配很容易給錯 ID。2.3 哪些章節(jié)可以跳過哪些必須精讀如果你時間有限我的建議是第一章引言、第二章需求分析、第十章總結(jié)展望這三章加起來大約 8 頁掃一眼標(biāo)題就行。第四章系統(tǒng)架構(gòu)設(shè)計里的傳感器選型和布局如果你不做硬件方案也可以跳過。必須精讀的是第三章 3.4 節(jié)的安裝配置、第五章 5.3 節(jié)的訓(xùn)練命令、第六章 6.2 和 6.3 節(jié)的速度估計算法、第七章 7.3 節(jié)的 SORT/DeepSORT 實現(xiàn)。這四塊是文檔里唯一能直接轉(zhuǎn)化成代碼的部分。提示文檔里給的 GitHub 倉庫地址https://github.com/ultralytics/yolov11需要核實Ultralytics 官方倉庫命名習(xí)慣是ultralyticsYOLOv11 的權(quán)重和代碼通常在該倉庫下。下載前先確認(rèn)倉庫是否存在避免 clone 到一個空倉庫。3. 把文檔里的算法變成可運行代碼環(huán)境、檢測、速度估計三步走3.1 環(huán)境配置別照抄文檔里的 CUDA 版本文檔 3.4.1 節(jié)給的安裝命令里寫了--extra-index-url https://download.pytorch.org/whl/cu113這是 CUDA 11.3 對應(yīng)的 PyTorch 源。這個版本偏舊如果你用的是 30 系或 40 系顯卡建議直接上 CUDA 11.8 或 12.1 對應(yīng)的 PyTorch。照抄 cu113 的后果是新顯卡可能跑不起來或者跑起來但 GPU 利用率上不去。我一般會這樣建環(huán)境# 創(chuàng)建虛擬環(huán)境Python 版本建議 3.9 或 3.10 python3.10 -m venv yolov11_env source yolov11_env/bin/activate # 安裝 PyTorchCUDA 11.8 版本適配大多數(shù) 30/40 系顯卡 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安裝 Ultralytics 包它自帶 YOLOv11 的模型定義和推理接口 pip install ultralytics # 安裝跟蹤和視覺處理相關(guān)依賴 pip install opencv-python numpy scipy filterpy這里的關(guān)鍵參數(shù)是--index-url后面的 CUDA 版本號。cu118對應(yīng) CUDA 11.8cu121對應(yīng) CUDA 12.1。選哪個取決于你驅(qū)動支持的 CUDA 版本用nvidia-smi命令看右上角的 CUDA Version選一個不超過它的版本。filterpy是卡爾曼濾波的 Python 實現(xiàn)庫文檔里講卡爾曼濾波但沒提具體庫實際寫代碼時用 filterpy 比自己手寫矩陣運算省事得多。3.2 用 YOLOv11 做車輛檢測并保存推理結(jié)果文檔 3.4.4 節(jié)的測試代碼用的是from models.yolo import Model這種底層加載方式對新手不友好。Ultralytics 包提供了更簡潔的接口直接調(diào)YOLO類就行。下面這段代碼做三件事加載預(yù)訓(xùn)練權(quán)重、對視頻逐幀檢測、把帶框的結(jié)果保存成視頻。from ultralytics import YOLO import cv2 # 加載 YOLOv11 預(yù)訓(xùn)練權(quán)重n 是 nano 版速度快適合實時 # 如果精度不夠可以換 s/m/l/x 版本模型越大精度越高速度越慢 model YOLO(yolo11n.pt) # 打開視頻文件也可以填 0 調(diào)用攝像頭 cap cv2.VideoCapture(traffic.mp4) fps cap.get(cv2.CAP_PROP_FPS) w int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 視頻寫入器用于保存帶檢測框的結(jié)果 writer cv2.VideoWriter(output.mp4, cv2.VideoWriter_fourcc(*mp4v), fps, (w, h)) while cap.isOpened(): ret, frame cap.read() if not ret: break # 只檢測車輛類別COCO 數(shù)據(jù)集中 car2, motorcycle3, bus5, truck7 results model(frame, classes[2, 3, 5, 7], conf0.4, verboseFalse) # 把檢測結(jié)果畫到幀上 annotated results[0].plot() writer.write(annotated) cap.release() writer.release()這段代碼里三個參數(shù)需要根據(jù)場景調(diào)。classes[2,3,5,7]是 COCO 數(shù)據(jù)集里車輛相關(guān)類別的索引如果你用的是自定義訓(xùn)練的模型類別索引要按你的data.yaml來。conf0.4是置信度閾值交通場景下建議設(shè)在 0.3 到 0.5 之間太低會引入大量誤檢太高會漏掉遠處的小目標(biāo)車輛。verboseFalse只是關(guān)掉每幀的日志輸出不影響結(jié)果。3.3 速度估計從像素位移到真實速度的換算文檔第六章講了光流法和特征點匹配法但沒給完整的換算流程。實際工程里速度估計的核心是一個標(biāo)定參數(shù)地面像素當(dāng)量也就是圖像中一個像素對應(yīng)現(xiàn)實世界多少米。這個參數(shù)怎么來常見做法是在路面上選一段已知長度的參照物比如車道線虛線國標(biāo)是 6 米線加 9 米間隔在圖像里量出它占多少像素除一下就是像素當(dāng)量。import numpy as np # 假設(shè)標(biāo)定結(jié)果路面上 1 像素對應(yīng) 0.05 米 # 這個值必須根據(jù)你的攝像頭安裝高度和角度實測不能拍腦袋 PIXEL_TO_METER 0.05 def estimate_speed(prev_center, curr_center, fps): 根據(jù)前后兩幀的車輛中心點像素坐標(biāo)估計速度 prev_center: 上一幀中心點 (x, y) curr_center: 當(dāng)前幀中心點 (x, y) fps: 視頻幀率 返回速度單位 km/h # 計算像素位移 dx curr_center[0] - prev_center[0] dy curr_center[1] - prev_center[1] pixel_dist np.sqrt(dx**2 dy**2) # 像素位移轉(zhuǎn)實際位移米 real_dist pixel_dist * PIXEL_TO_METER # 位移除以幀間隔得到米每秒 speed_mps real_dist * fps # 換算成 km/h speed_kmh speed_mps * 3.6 return speed_kmh這段代碼的邏輯很直白但有兩個隱藏假設(shè)需要說清楚。第一它假設(shè)車輛在圖像平面上的運動方向就是實際運動方向這在攝像頭正對車道時成立但如果攝像頭斜裝透視畸變會讓像素位移和實際位移不成線性關(guān)系需要先做透視變換把圖像校正成俯視圖。第二PIXEL_TO_METER是一個固定值但實際圖像中不同位置的像素當(dāng)量是不同的——近處一個像素代表的距離比遠處小。要更準(zhǔn)得用透視變換矩陣把整幅圖映射到鳥瞰圖在鳥瞰圖上做位移計算。文檔里提到了透視變換校正但沒有展開這是需要自己補的部分。注意速度估計的誤差主要來源不是算法而是標(biāo)定。我見過太多項目在標(biāo)定環(huán)節(jié)偷懶直接用一個估算的像素當(dāng)量結(jié)果速度誤差超過 30%。標(biāo)定這一步?jīng)]有捷徑必須在實際安裝位置用已知長度的參照物實測。4. 軌跡追蹤落地SORT 與 DeepSORT 的選型、參數(shù)與 ID 穩(wěn)定性4.1 SORT 和 DeepSORT 在交通場景下的實際差異文檔第七章把 SORT 和 DeepSORT 都講了但沒給選型建議。我補一下SORT 的核心是卡爾曼濾波做運動預(yù)測加匈牙利算法做匹配它只看框的位置和大小不看框里面長什么樣。這意味著兩輛車交叉而過時SORT 很容易把 ID 互換。DeepSORT 多了一個外觀特征提取步驟用一個小型 ReID 網(wǎng)絡(luò)把每個檢測框里的車輛圖像轉(zhuǎn)成一個 128 維的特征向量匹配時同時計算運動距離和外觀距離的加權(quán)和。代價是每幀要多跑一次 ReID 網(wǎng)絡(luò)在 1080p 視頻上大約增加 5 到 10 毫秒的延遲。交通場景下我建議直接用 DeepSORT因為車輛被前車遮擋、被樹遮擋、在路口轉(zhuǎn)彎時短暫消失又出現(xiàn)這些情況太常見了SORT 的 ID 切換率會高到?jīng)]法用。文檔里給的評估指標(biāo) IDSW身份切換率就是專門衡量這個的DeepSORT 在 MOTChallenge 上的 IDSW 通常比 SORT 低一半以上。4.2 用 Ultralytics 內(nèi)置跟蹤器快速跑通Ultralytics 包從 8.0 版本開始內(nèi)置了跟蹤功能底層就是 SORT 和 DeepSORT 的變體不需要自己寫匹配邏輯。下面這段代碼在檢測的同時做跟蹤每個框會帶一個穩(wěn)定的 track ID。from ultralytics import YOLO import cv2 model YOLO(yolo11n.pt) cap cv2.VideoCapture(traffic.mp4) fps cap.get(cv2.CAP_PROP_FPS) w int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer cv2.VideoWriter(tracked.mp4, cv2.VideoWriter_fourcc(*mp4v), fps, (w, h)) # 用 track 模式指定跟蹤器配置文件 # botsort.yaml 是 BoT-SORT對 DeepSORT 的改進版 # 也可以換成 bytetrack.yaml速度更快但 ID 穩(wěn)定性稍差 while cap.isOpened(): ret, frame cap.read() if not ret: break results model.track(frame, classes[2, 3, 5, 7], conf0.4, trackerbotsort.yaml, persistTrue, verboseFalse) annotated results[0].plot() writer.write(annotated) cap.release() writer.release()persistTrue這個參數(shù)很關(guān)鍵它讓跟蹤器在幀與幀之間保持狀態(tài)。如果不加每幀都會重新初始化跟蹤器track ID 每幀都變等于沒跟蹤。trackerbotsort.yaml指定用 BoT-SORT這是目前 Ultralytics 里 ID 穩(wěn)定性最好的跟蹤器它結(jié)合了運動信息和外觀特征還加了相機運動補償適合固定攝像頭場景。如果你的場景對速度要求極高、可以接受偶爾的 ID 切換換成bytetrack.yaml能省下 ReID 網(wǎng)絡(luò)的開銷。4.3 從 track ID 到軌跡把每幀的框串成線拿到每幀的 track ID 和邊界框之后軌跡就是同一個 ID 的框中心點按時間順序連成的線。下面這段代碼維護一個字典記錄每個 ID 的歷史中心點。from collections import defaultdict # 用 defaultdict 存每個 track ID 的歷史軌跡點 tracks defaultdict(list) while cap.isOpened(): ret, frame cap.read() if not ret: break results model.track(frame, classes[2, 3, 5, 7], conf0.4, trackerbotsort.yaml, persistTrue, verboseFalse) boxes results[0].boxes if boxes.id is not None: # boxes.id 是當(dāng)前幀所有檢測框的 track ID ids boxes.id.cpu().numpy().astype(int) xywh boxes.xywh.cpu().numpy() for tid, box in zip(ids, xywh): cx, cy box[0], box[1] tracks[tid].append((cx, cy)) # 只保留最近 30 幀的軌跡避免內(nèi)存無限增長 if len(tracks[tid]) 30: tracks[tid].pop(0) # 在幀上畫軌跡線 for tid, points in tracks.items(): for i in range(1, len(points)): cv2.line(frame, (int(points[i-1][0]), int(points[i-1][1])), (int(points[i][0]), int(points[i][1])), (0, 255, 0), 2) writer.write(frame)tracks字典的 key 是 track IDvalue 是中心點列表。len(tracks[tid]) 30這個限制是為了控制內(nèi)存30 幀在 25fps 下大約是 1.2 秒的軌跡足夠畫出直觀的行駛路徑。如果你要做軌跡分析比如判斷變道、計算車道占有率需要保留更長的歷史可以把 30 改成 150 或更大但要注意長時間運行時的內(nèi)存占用。提示軌跡點的坐標(biāo)系是圖像像素坐標(biāo)如果要做跨攝像頭的軌跡拼接需要先把像素坐標(biāo)通過標(biāo)定矩陣轉(zhuǎn)換成世界坐標(biāo)。這一步文檔里沒有涉及屬于進階內(nèi)容。5. 避坑與排查這份文檔沒寫但一定會遇到的五個問題5.1 檢測框抖動導(dǎo)致速度估計跳變現(xiàn)象同一輛車在連續(xù)幀里的速度估計值忽高忽低相鄰幀能差 20 km/h 以上。原因YOLOv11 的檢測框在幀間不是完全穩(wěn)定的邊界框中心點會有幾個像素的隨機抖動這個抖動除以幀間隔后被放大成速度噪聲。解決對速度做滑動平均濾波用最近 5 幀的速度均值作為當(dāng)前速度輸出。如果抖動特別嚴(yán)重檢查是不是conf閾值設(shè)得太低低置信度的框位置通常更不準(zhǔn)。5.2 track ID 在車輛被遮擋后重新分配現(xiàn)象一輛車被公交車擋住 2 秒后重新出現(xiàn)track ID 從 5 變成了 23。原因DeepSORT 的外觀特征在遮擋期間沒有更新重新出現(xiàn)時外觀特征和遮擋前差異較大匹配失敗。解決把botsort.yaml里的track_high_thresh和track_low_thresh調(diào)低讓低置信度的檢測框也參與匹配同時增大max_age參數(shù)讓丟失的軌跡保留更長時間再刪除。這兩個參數(shù)在 Ultralytics 的跟蹤器配置文件里可以改。5.3 像素當(dāng)量標(biāo)定錯誤導(dǎo)致速度系統(tǒng)性偏差現(xiàn)象所有車的速度估計都偏大或偏小誤差方向一致。原因PIXEL_TO_METER這個標(biāo)定值不準(zhǔn)。解決在視頻里找一段已知長度的參照物比如標(biāo)準(zhǔn)車道分界線6 米實線 9 米間隔量出它在圖像中的像素長度反推像素當(dāng)量。如果攝像頭有俯仰角還需要做透視校正否則近處和遠處的像素當(dāng)量不一致會出現(xiàn)近處車速度準(zhǔn)、遠處車速度偏大的情況。5.4 視頻幀率與實際時間戳不匹配現(xiàn)象速度估計值整體偏大或偏小比例大致等于實際幀率與設(shè)定幀率的比值。原因cap.get(cv2.CAP_PROP_FPS)讀到的幀率不一定準(zhǔn)確有些視頻容器里的幀率是近似值。解決不要用視頻自帶的幀率用處理幀數(shù)除以實際耗時來算真實幀率。或者更直接的辦法在視頻里找一個已知速度的參照比如自己開車以 60 km/h 經(jīng)過用它的估計速度反推校正系數(shù)。5.5 多目標(biāo)跟蹤在擁堵場景下算力不足現(xiàn)象車流密集時處理幀率從 25fps 掉到 8fps跟蹤延遲明顯。原因DeepSORT 的 ReID 網(wǎng)絡(luò)對每個檢測框都要跑一次特征提取檢測框數(shù)量從 5 個漲到 30 個時ReID 的計算量線性增長。解決換用bytetrack.yaml它不做外觀特征提取只靠運動匹配速度快很多或者降低輸入分辨率把 1080p 降到 720p檢測和跟蹤的耗時都會下降。6. 進階技巧用標(biāo)定矩陣把像素速度換算成真實速度的完整流程前面 3.3 節(jié)給的速度估計用的是固定像素當(dāng)量這在攝像頭正對車道、車輛在圖像中央?yún)^(qū)域時夠用但一旦車輛偏離圖像中心透視畸變會讓誤差迅速增大。這一章給一個更準(zhǔn)的做法用透視變換把圖像映射成鳥瞰圖在鳥瞰圖上做位移計算這樣整幅圖的像素當(dāng)量是統(tǒng)一的。具體步驟分三步。第一步在視頻里選四個點它們在實際路面上構(gòu)成一個矩形比如一個標(biāo)準(zhǔn)車道的四個角。在圖像里讀出這四個點的像素坐標(biāo)。第二步用cv2.getPerspectiveTransform計算透視變換矩陣把圖像映射成俯視圖。第三步在俯視圖上量出車道寬度對應(yīng)的像素數(shù)除以實際車道寬度國標(biāo) 3.75 米得到鳥瞰圖上的像素當(dāng)量。import cv2 import numpy as np # 圖像中車道四角的像素坐標(biāo)需要手動在視頻幀上讀取 # 順序左上、右上、右下、左下 src_points np.float32([[320, 480], [960, 480], [1280, 720], [0, 720]]) # 鳥瞰圖中對應(yīng)的矩形坐標(biāo)寬度按車道實際寬度比例設(shè)定 # 這里假設(shè)映射到 800x600 的鳥瞰圖 dst_points np.float32([[200, 0], [600, 0], [600, 600], [200, 600]]) # 計算透視變換矩陣 matrix cv2.getPerspectiveTransform(src_points, dst_points) # 對每一幀做變換 def to_bird_eye(frame): return cv2.warpPerspective(frame, matrix, (800, 600)) # 鳥瞰圖上的像素當(dāng)量車道寬 3.75 米對應(yīng) 400 像素 # 所以 1 像素 3.75 / 400 0.009375 米 PIXEL_TO_METER_BE 3.75 / 400這段代碼的關(guān)鍵在src_points的選取。這四個點必須是在實際路面上構(gòu)成矩形的四個點通常選車道線的四個角或者停止線的兩端。選點的時候要在視頻幀上仔細讀坐標(biāo)差幾個像素標(biāo)定結(jié)果就會偏。dst_points是映射后的目標(biāo)矩形我一般設(shè)成 800x600寬度方向?qū)?yīng)實際車道寬度。PIXEL_TO_METER_BE這個值算出來之后在鳥瞰圖上做速度估計就和 3.3 節(jié)的邏輯一樣了但精度會明顯提升因為鳥瞰圖上每個像素代表的實際距離是均勻的。驗證標(biāo)定是否準(zhǔn)確的方法在鳥瞰圖上量車道線的寬度應(yīng)該和dst_points里設(shè)定的寬度一致再找一輛已知速度的車比如自己開車以固定速度經(jīng)過看估計值和實際值的偏差。偏差在 10% 以內(nèi)算合格超過 20% 說明選點或映射有問題需要重新標(biāo)。從那以后我每次做速度估計項目都強制先跑一遍標(biāo)定驗證不看到鳥瞰圖上車道線平行且等寬絕不往下寫速度換算的代碼。這個習(xí)慣幫我省掉了至少三次返工。希望幫到你。本文還有配套的精品資源點擊獲取