實戰(zhàn):YOLO跟蹤計數(shù)與部署避坑指南)
簡介這是一份基于深度學(xué)習(xí)的人流量檢測系統(tǒng)畢業(yè)設(shè)計項目包含 Python 源碼與項目說明文檔面向計算機相關(guān)專業(yè)正在準備畢業(yè)設(shè)計的學(xué)生也適合需要項目實戰(zhàn)練習(xí)的學(xué)習(xí)者可用于課程設(shè)計或期末大作業(yè)。項目經(jīng)嚴格調(diào)試后可穩(wěn)定運行作為導(dǎo)師認可的高分畢業(yè)設(shè)計從模型搭建、訓(xùn)練到前端檢測展示均有完整代碼目錄結(jié)構(gòu)清晰便于對照說明理解整套人流量檢測流程。資源包共 1235 個文件約 61.54MB其中 76 個 Python 文件構(gòu)成核心檢測邏輯配合 382 個 HTML 頁面、194 個 JS 腳本、51 個 CSS 樣式搭建可視化界面208 張 PNG 圖片與 186 張 GIF 動圖用于界面元素和過程演示另有 ipynb 筆記、配置文件與模型資源方便復(fù)現(xiàn)訓(xùn)練與推理過程。該資源已有 264 人學(xué)習(xí)使用適合在已有項目上快速上手、二次開發(fā)也可用于學(xué)習(xí)深度學(xué)習(xí)檢測項目的工程組織方式。1. 人流量檢測別急著訓(xùn)模型拿到這個標題你第一反應(yīng)多半是找個人體檢測模型數(shù)畫面里有幾個人。但我做下來的感受是人流量檢測真正難的不是檢測而是數(shù)得穩(wěn)——跨攝像頭、跨時段、有遮擋、有透視畸變時同一個人在不同幀里既可能被漏檢也可能被重復(fù)計數(shù)準確率浮動非常大。這個畢業(yè)設(shè)計項目的核心并不在于用深度學(xué)習(xí)框出人而在于把檢測結(jié)果、追蹤邏輯和業(yè)務(wù)口徑什么才算通過一次拼成一套能自洽的計數(shù)系統(tǒng)。適合正在做畢設(shè)或課設(shè)、想拿一個完整可演示項目的同學(xué)也適合剛?cè)腴T目標檢測、想看看工程約束怎么倒逼模型選型的開發(fā)者。下面我把整套方案的選型、數(shù)據(jù)準備、源碼結(jié)構(gòu)和踩坑點按實際開發(fā)順序拆給你。2. 檢測 vs 計數(shù)兩條技術(shù)路線與選型判斷2.1 為什么單張圖檢測不夠用計數(shù)口徑才是系統(tǒng)的靈魂人流量檢測系統(tǒng)如果只做每幀輸出若干框站在答辯或驗收視角是站不住的——演示時隨便有人交錯走過人數(shù)就亂了。真正的流量指標是單位時間內(nèi)有多少人穿過某個斷面這要求系統(tǒng)能區(qū)分這是剛才那個人走過去了還是新來一個人。常見做法是引入跟蹤Track來給檢測框分配 ID再配合一條虛擬計數(shù)線或區(qū)域判斷來完成計數(shù)。也就是說模型只是上游Tracking 和計數(shù)邏輯才是中游和下游三者缺一不可。我一般會把技術(shù)選型分成兩類一類是檢測類方案即 YOLO 系模型檢出人框再配 IoU 匹配做跟蹤另一類是密度回歸類方案即 CSRNet 這類網(wǎng)絡(luò)直接回歸人群密度圖再積分求人數(shù)。前者對個體定位清晰、可解釋性強、單人場景精度高但遮擋嚴重時漏檢率會陡升后者在密集人群場景更有優(yōu)勢但無法給出每個人在哪的框而且訓(xùn)練密度圖標注成本極高。2.2 畢設(shè)場景選 YOLO 系模型的四個理由如果你的場景是教室門口、走廊、超市出入口這類半密集場景我會優(yōu)先選 YOLO 系檢測模型理由很直接預(yù)訓(xùn)練權(quán)重成熟公開數(shù)據(jù)集多換到自己場景只需要微調(diào)推理速度快普通 GPU 上輕松跑實時可視化效果好畫框標 ID 一眼就能看懂答辯時演示性強生態(tài)完善從標注格式轉(zhuǎn)換到部署都有大量現(xiàn)成代碼可參考。相比之下CSRNet 這類密度回歸模型在 50 人以上的密集場景確實更強但標注密度圖是逐點標注人頭的工作量比畫框還大而且視覺反饋弱演示效果不容易討喜。選型還要考慮運行環(huán)境。大部分畢設(shè)項目會在自己的筆記本上演示因此我建議優(yōu)先考慮 YOLOv5s 或 YOLOv8n 這類小體量版本而不是 YOLOv8x 這種重模型。同樣的訓(xùn)練設(shè)備和推理設(shè)備n/s 級別模型能保證在 CPU 上也有可接受的推理幀率這對演示現(xiàn)場沒有 GPU 的情況非常關(guān)鍵。如果你的機器配置好再考慮大模型。2.3 完整系統(tǒng)的最小功能骨架一個能交付的人流量檢測系統(tǒng)至少要包含五個模塊目標檢測模塊負責(zé)從畫面中檢出人跟蹤模塊負責(zé)分配并維護 ID計數(shù)邏輯模塊負責(zé)定義穿越規(guī)則并累計結(jié)果可視化模塊負責(zé)實時畫框、畫計數(shù)線和顯示人數(shù)曲線數(shù)據(jù)記錄模塊負責(zé)把計數(shù)結(jié)果落到 CSV 或數(shù)據(jù)庫里供后續(xù)分析或答辯展示。標題里源碼項目說明正常情況下就應(yīng)包含這些模塊對應(yīng)的代碼文件和說明文檔。項目結(jié)構(gòu)我一般會這樣組織people_flow/ ├── checkpoints/ # 模型權(quán)重存放目錄 ├── configs/ # 配置文件路徑、閾值、計數(shù)線位置等 ├── data/ │ ├── dataset/ # 標注數(shù)據(jù)與圖片 │ ├── processed/ # 轉(zhuǎn)換后的 YOLO 格式標注 │ └── videos/ # 測試視頻 ├── scripts/ │ ├── convert_labels.py # 標注格式轉(zhuǎn)換腳本 │ └── train.py # 訓(xùn)練入口 ├── src/ │ ├── detector.py # 檢測封裝 │ ├── tracker.py # 跟蹤封裝 │ ├── counter.py # 計數(shù)邏輯 │ ├── visualizer.py # 可視化 │ └── main.py # 主入口 └── requirements.txt為什么要拆這么細因為畢設(shè)答辯會問你每個文件是干什么的如果全部邏輯堆在一個 main.py 里提問時很難講清楚模塊劃分。按功能拆出獨立模塊既能體現(xiàn)工程能力也方便單獨測試某一個環(huán)節(jié)比如單獨跑 detector 看檢測效果單獨跑 counter 看計數(shù)邏輯。3. 訓(xùn)練數(shù)據(jù)準備從 VOC 到 YOLO 格式的轉(zhuǎn)換腳本與四個邊界坑3.1 優(yōu)先用公開數(shù)據(jù)集做遷移學(xué)習(xí)自定義數(shù)據(jù)只做微調(diào)流量檢測的數(shù)據(jù)集準備有一個原則不要從零標注幾千張圖。公開數(shù)據(jù)集里COCO 的 person 類別、CrowdHuman、MOT Challenge 系列都可以直接用來做預(yù)訓(xùn)練或微調(diào)。其中 CrowdHuman 對人群遮擋場景標注比較充分MOT16/17 帶跟蹤標注適合順帶做跟蹤評估。畢設(shè)項目一般不需要自己從零采集大量數(shù)據(jù)常見做法是先用公開數(shù)據(jù)訓(xùn)練一個可用的檢測器再采集一小部分目標場景數(shù)據(jù)比如自己教室門口、實驗室走廊的視頻做微調(diào)讓模型適應(yīng)當前攝像頭的視角和光照。這樣能明顯減少標注工作量訓(xùn)練數(shù)據(jù)量在幾百張級別就夠了。需要提醒的是即便用了遷移學(xué)習(xí)標注格式依然是最容易被絆倒的環(huán)節(jié)。假如你找到的公開數(shù)據(jù)集是 COCO 格式JSON而訓(xùn)練代碼用的是 YOLO 格式TXT中間必須寫轉(zhuǎn)換腳本。很多學(xué)生在網(wǎng)上找的代碼只支持一種格式數(shù)據(jù)喂進去報錯半天才發(fā)現(xiàn)是格式問題這個我深有體會。因此第一步先確認你的訓(xùn)練代碼吃的是什么格式再寫轉(zhuǎn)換腳本而不是先轉(zhuǎn)完再回頭看代碼。3.2 標注格式轉(zhuǎn)換腳本XML/TXT 與 JSON 的互相轉(zhuǎn)換這里給出一個從 COCO JSON 轉(zhuǎn) YOLO TXT 的腳本。它是整套數(shù)據(jù)流程中最常被復(fù)用的工具后續(xù)你換數(shù)據(jù)集、換場景大概率還要改它來適配。import json import os # COCO 數(shù)據(jù)集的 person 類別 id 通常是 1需按實際數(shù)據(jù)集確認 PERSON_CATEGORY_ID 1 def convert_coco_json_to_yolo_txt(coco_json_path, output_dir): with open(coco_json_path, r, encodingutf-8) as f: coco json.load(f) # 建立 image_id - 圖片文件名的映射 images {img[id]: img for img in coco[images]} os.makedirs(output_dir, exist_okTrue) for ann in coco[annotations]: if ann[category_id] ! PERSON_CATEGORY_ID: continue image_info images[ann[image_id]] img_w, img_h image_info[width], image_info[height] # COCO 的 bbox 是 [x, y, width, height]YOLO 需要的是歸一化中心點和寬高 x, y, w, h ann[bbox] cx (x w / 2) / img_w cy (y h / 2) / img_h w_norm w / img_w h_norm h / img_h # 一個圖片對應(yīng)一個 txt 文件多個標注就追加多行 txt_path os.path.join( output_dir, os.path.splitext(image_info[file_name])[0] .txt ) with open(txt_path, a, encodingutf-8) as out: out.write(f0 {cx:.6f} {cy:.6f} {w_norm:.6f} {h_norm:.6f}\n) print(f轉(zhuǎn)換完成標注文件輸出到: {output_dir})這段腳本的邏輯不復(fù)雜對每一條 category 為 person 的標注取出 bbox將左上角坐標格式轉(zhuǎn)成歸一化的中心點加寬高格式寫入同名 TXT。注意它是按行追加寫入的所以同一個圖片有多個人時會產(chǎn)生多行標注。如果你拿到的數(shù)據(jù)本身就是 VOC 的 XML 格式也是同樣的邏輯只是解析標簽從 JSON 字段換成 XML 的 object/bndbox 節(jié)點。3.3 四個邊界坑坐標裁剪、空標注、類別編號、數(shù)據(jù)集劃分第一個坑是 bbox 出界。攝像頭畫面邊緣的人往往只露出一半身體標注框的坐標可能落在圖像邊界之外。YOLO 訓(xùn)練時若不對坐標做 clip裁剪會算出負的寬高或超出 1 的歸一化值輕則警告重則訓(xùn)練 loss 變成 NaN。因此轉(zhuǎn)換時務(wù)必加一步x max(0, x)、x w min(img_w, x w)之類的邊界處理。第二個坑是空標注文件。如果某張圖只標了 person 類而實際畫面有大量人沒被標注轉(zhuǎn)換后會生成空 TXT。把這些空文件直接丟給訓(xùn)練腳本有些框架會報no labels錯。我的建議是轉(zhuǎn)換腳本里統(tǒng)計每個 TXT 的行數(shù)自動列出空文件讓你人工確認是漏標還是確實無人。第三個坑是類別編號一致性。YOLO 訓(xùn)練配置里 class 編號從 0 開始很多人從網(wǎng)上下載的標注里 person 編號是 0但換一個數(shù)據(jù)集可能變成 1 或 15訓(xùn)練時如果不改配置文件模型會把人學(xué)成背景精度一塌糊涂。第四個坑是數(shù)據(jù)集劃分。訓(xùn)練、驗證、測試三個集合之間不能有圖片重疊而且同一個人在不同幀里的畫面不要被分到訓(xùn)練和驗證兩個集合里否則驗證指標虛高。這一點在按幀抽幀做人流數(shù)據(jù)時特別容易犯——視頻連續(xù)幀高度相似隨機劃分會導(dǎo)致驗證集泄露。提示轉(zhuǎn)換腳本里建議把類別編號做成可配置參數(shù)不要寫死。你在換數(shù)據(jù)集時只需改一個變量的值而不是去代碼里到處找數(shù)字。3.4 數(shù)據(jù)增強怎么做才不偏離流量場景數(shù)據(jù)增強方面人流場景我最常用的組合是HSV 色域擾動、隨機平移與縮放、馬賽克增強。HSV 擾動可以提高不同光照下的泛化能力因為你訓(xùn)練的可能是白天采集的視頻而實際部署可能要面對傍晚光線馬賽克增強則把四張圖拼成一張?zhí)嵘四P驮谌巳赫趽鯃鼍跋碌聂敯粜?。需要謹慎的是大角度旋轉(zhuǎn)和上下翻轉(zhuǎn)。上下翻轉(zhuǎn)在人流場景是禁忌因為監(jiān)控攝像頭一般是固定的不可能出現(xiàn)倒立的人大角度旋轉(zhuǎn)也會產(chǎn)生大量現(xiàn)實中不存在的姿態(tài)。合理的增強策略應(yīng)該是模擬攝像頭可能遇到的真實變化而不是無腦堆疊所有增強手段。4. 源碼拆解與二次開發(fā)模型定義、推理主循環(huán)和評估指標4.1 模型結(jié)構(gòu)選擇與參數(shù)設(shè)定從 YOLOv5s 到 YOLOv8n 的取舍到了源碼層面首先面對的是模型怎么定義。如果你拿到的項目說明基于 YOLOv5那它大概率用 yaml 文件描述網(wǎng)絡(luò)結(jié)構(gòu)如果是 YOLOv8結(jié)構(gòu)定義則內(nèi)聚在 Python 類中。兩種寫法本質(zhì)一樣都包含 Backbone、Neck、Head 三部分。畢設(shè)項目里我建議優(yōu)先讀 YOLOv8n 的源碼因為它的結(jié)構(gòu)更清晰、接口更簡潔而且文檔和示例更豐富。# yolo_v8_n.yaml 的核心結(jié)構(gòu)說明用實際文件由 ultralytics 包自動加載 backbone: - [-1, 1, Conv, [64, 3, 2]] # 下采樣輸出 1/2 分辨率特征圖 - [-1, 1, Conv, [128, 3, 2]] # 下采樣輸出 1/4 分辨率特征圖 - [-1, 3, C2f, [128, True]] # C2f 模塊特征提取 - [-1, 1, Conv, [256, 3, 2]] # 下采樣輸出 1/8 分辨率特征圖 - [-1, 6, C2f, [256, True]] # ... 后續(xù)為 1/16、1/32 分辨率層級 head: - [-1, 1, Detect, [nc]] # nc 是類別數(shù)這里固定為 1person這段描述里每一行的[-1, 1, Conv, [64, 3, 2]]含義是從上一層輸入-1 表示上一層、1 個該模塊、Conv 卷積模塊、輸出通道 64、卷積核 3x3、步長 2。C2f 是 YOLOv8 的核心殘差模塊兼顧速度和精度。修改結(jié)構(gòu)時你只需要關(guān)注兩個參數(shù)depth_multiple 和 width_multiple分別控制網(wǎng)絡(luò)深度和寬度調(diào)小它們就能得到更輕量的模型調(diào)大則更準但更慢。4.2 推理主循環(huán)幀讀取、檢測、跟蹤、計數(shù)如何串起來在線推理主循環(huán)是系統(tǒng)運行的核心。下面給出一個簡化但完整的推理循環(huán)骨架展示了檢測與跟蹤是如何銜接的import cv2 import torch from ultralytics import YOLO model YOLO(checkpoints/yolov8n_person.pt) tracker cv2.legacy.TrackerKCF_create() # 用 KCF 做簡單兜底跟蹤 count_line_y 240 # 計數(shù)線位置需按實際畫面標定 up_count 0 down_count 0 tracked_positions {} cap cv2.VideoCapture(data/videos/test.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # 檢測conf 閾值控制誤檢與漏檢的平衡 results model(frame, conf0.4, iou0.5, classes[0]) boxes results[0].boxes.xyxy.cpu().numpy() for box in boxes: x1, y1, x2, y2 box[:4].astype(int) cx, cy (x1 x2) // 2, (y1 y2) // 2 # 框底部中心點更穩(wěn)定 # 判斷是否穿越計數(shù)線 if cy count_line_y: down_count 1 else: up_count 1 # 可視化 frame cv2.line(frame, (0, count_line_y), (frame.shape[1], count_line_y), (0, 255, 0), 2) cv2.imshow(people_flow, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()這段代碼的邏輯是每讀一幀送進 YOLO 檢測人框取框的底邊中心點作為人的落腳點當落腳點跨過計數(shù)線時上/下行計數(shù)加一。為什么用底邊中心而不是框中心因為框中心會隨人體姿態(tài)變化而上下浮動在彎腰、坐下等動作下容易誤判跨越方向而底邊中心貼近地面幾何上更穩(wěn)定。注意上面這個循環(huán)為了展示骨架省略了跟蹤模塊。實際項目中直接用逐幀檢測做計數(shù)會產(chǎn)生嚴重重復(fù)計數(shù)——同一個停留的人會被反復(fù)累加。至少需要加一層簡單的目標 ID 去重判斷當前檢測框是否已關(guān)聯(lián)到某個已有 ID如果關(guān)聯(lián)到則只更新位置、不重復(fù)計數(shù)。4.3 項目說明通常包含哪些內(nèi)容評估指標與可視化輸出項目說明在畢設(shè)項目里通常不只是代碼注釋它要包含數(shù)據(jù)集介紹、模型結(jié)構(gòu)說明、訓(xùn)練參數(shù)表、評估指標和運行步驟。評估指標方面人流檢測系統(tǒng)建議至少報告三個檢測精度的 mAP0.5計數(shù)精度的 MAE平均絕對誤差和 RMSE均方根誤差。MAE 的計算很簡單逐幀把預(yù)測人數(shù)與人工標注人數(shù)做差的絕對值再取平均。在答辯時給出 mAP 只能證明你檢測得準給出 MAE 才能證明你數(shù)得準后者才是人流量檢測系統(tǒng)區(qū)別于普通目標檢測項目的核心。def compute_mae(pred_counts, gt_counts): assert len(pred_counts) len(gt_counts) errors [abs(p - g) for p, g in zip(pred_counts, gt_counts)] return sum(errors) / len(errors) pred [3, 4, 5, 6] # 模型每幀計數(shù) gt [3, 5, 4, 6] # 人工標注 print(compute_mae(pred, gt)) # 輸出 0.75這個函數(shù)看起來簡單但在畢設(shè)項目中幾乎是必不可少的。因為人流量檢測的項目說明文檔里需要你給出本系統(tǒng)計數(shù)精度達到 MAE0.75這樣的結(jié)論性數(shù)據(jù)而不是只寫模型效果不錯。我建議你在項目說明中附上逐幀的預(yù)測值和真實值對比表格再畫出變化曲線圖這種量化的呈現(xiàn)方式在答辯時非常加分。5. 部署避坑白天黑夜漂移卡頓和這些常見問題5.1 模型在白天效果好、傍晚效果差的玄學(xué)多時段數(shù)據(jù)微調(diào)我做流量檢測遇到的第一個大坑就是白天能用傍晚翻車。原因不復(fù)雜訓(xùn)練數(shù)據(jù)基本來自白天光線充足的場景模型學(xué)到的是亮環(huán)境下的行人外觀。到了傍晚暗光加路燈的混合光源下人臉和衣服的顏色細節(jié)都變了檢測置信度直線下降漏檢率飆升。解決思路不是加多少數(shù)據(jù)增強就能搞定的最有效的做法是采集目標場景下多個時段早、中、晚各 10~20 分鐘視頻抽幀后做半自動標注再加入訓(xùn)練集微調(diào)。注意微調(diào)時不要只用傍晚數(shù)據(jù)要混合原有數(shù)據(jù)否則模型會對傍晚過擬合、白天精度反而下降。5.2 視頻里人一多就掉幀推理速度優(yōu)化三件套人流檢測最容易在演示時暴露的問題是卡頓——人一多幀率降到 15 以下畫面肉眼可見地不流暢。性能瓶頸通常在檢測模型而不在跟蹤和計數(shù)邏輯。優(yōu)化手段按性價比排序第一換小模型YOLOv8x 換成 YOLOv8n精度可能只降 2~3 個點速度翻倍第二輸入分辨率從 1280 降到 640這是最直接的加速方式第三打開 TensorRT 或 ONNX 導(dǎo)出做推理加速用 GPU 的話能再快不少。在這三招之后才考慮改跟蹤策略、跳幀檢測這類工程技巧因為跳幀檢測會犧牲計數(shù)實時性很容易被答辯老師看穿。# 用半精度推理加速GPU 可用時能明顯提升幀率 model YOLO(checkpoints/yolov8n_person.pt) results model(frame, conf0.4, imgsz640, halfTrue)這段代碼的關(guān)鍵參數(shù)有兩個imgsz640直接降低輸入分辨率halfTrue啟用 FP16 半精度推理。如果目標機器是 GPU這兩個參數(shù)疊加通常能把 1080p 視頻的推理幀率拉到可用水平。CPU 機器上則不建議開 half部分 CPU 對 FP16 支持不好反而更慢。5.3 重復(fù)計數(shù)沒有跟蹤模塊時的經(jīng)典翻車現(xiàn)場重復(fù)計數(shù)是人流量檢測里最經(jīng)典也最丟分的 bug。現(xiàn)象是一個人站在鏡頭前不動計數(shù)數(shù)字卻不斷上漲。原因前面提過——逐幀檢測沒有去重。解決思路是引入跟蹤給每個檢測框分配穩(wěn)定的 ID。實現(xiàn)方案很多OpenCV 自帶 sort 或 BoT-SORT 都是可以選的前者輕量、適合畢設(shè)后者精度更穩(wěn)、但依賴更多。# 一個極簡的基于位置去重方案當前框的中心點 # 若與上一幀某個已跟蹤目標距離小于閾值則視為同一人 import math tracked_targets {} # id - (cx, cy) NEXT_ID 0 DIST_THRESH 50 # 單位像素需按實際畫面尺寸調(diào)整 for box in boxes: x1, y1, x2, y2 box[:4].astype(int) cx, cy (x1 x2) // 2, (y1 y2) // 2 matched_id None for tid, (tx, ty) in tracked_targets.items(): if math.hypot(cx - tx, cy - ty) DIST_THRESH: matched_id tid break if matched_id is None: tracked_targets[NEXT_ID] (cx, cy) NEXT_ID 1 else: tracked_targets[matched_id] (cx, cy)這個方案只能算最簡單的距離匹配真實項目中它扛不住目標交錯和短暫遮擋。但作為畢設(shè)項目的起步它能立刻解決重復(fù)計數(shù)問題而且代碼量極小、容易講清楚。DIST_THRESH 這個參數(shù)需要按畫面里人的移動速度調(diào)如果人走得快、兩幀之間位移大閾值小了就會把同一人判成不同人如果畫面高糊、噪聲多閾值大了又容易把兩個人合并成一個 ID。5.4 計數(shù)線位置和方向設(shè)定幾何標定的細節(jié)計數(shù)線的位置看起來是隨手畫一條線實際調(diào)參時很講究。如果線畫得太靠近畫面上邊緣人在遠處尺寸很小、檢測框不穩(wěn)定容易來回橫跳導(dǎo)致重復(fù)計數(shù)如果畫得太靠下人已經(jīng)走到鏡頭正下方框的底邊中點受遮擋影響大。常見做法是選擇畫面中行人走動的必經(jīng)斷面且在檢測框高度約為整幅畫面高度 1/4 的位置。線方向的選擇也影響左右計數(shù)映射要明確好畫面上方向下走對應(yīng)的是進入還是離開一旦映射反了統(tǒng)計出的進出人數(shù)就是反的。這個問題在評估時很容易被忽略等看數(shù)據(jù)才發(fā)現(xiàn)進的人數(shù)比出的還少。5.5 模型權(quán)重文件的坑訓(xùn)練了一個類別的模型部署時類別過濾最后一條常見的坑是類別編號在部署時不匹配。訓(xùn)練時候如果你改成了nc1只有 person 一類那么模型的輸出類別編號就是 0推理時直接全部框都可以接受。但如果你的權(quán)重是從 YOLOv8 官方 COCO 預(yù)訓(xùn)練權(quán)重繼續(xù)微調(diào)的它的類別編號 0 仍然是 person那就沒問題。最怕的是從別人項目里拿的權(quán)重文件類別列表可能被改過部署時沒有檢查 classes 參數(shù)結(jié)果模型把貓、狗、車都當成目標框出來計數(shù)直接崩。解決方案只有一個拿到任何權(quán)重文件第一件事是用一張包含行人和非行人的測試圖跑一次推理查看輸出的類別 ID 和置信度確認類別含義再投入使用。6. 讓準確率再進一步透視校正、區(qū)域計數(shù)與卡爾曼濾波的配合6.1 為什么要做透視校正等大的人在畫面遠處更小流量檢測系統(tǒng)到了能用階段你還是會發(fā)現(xiàn)一個視覺上很明顯、數(shù)據(jù)上也很明顯的問題同一段真實人流在畫面遠處檢測到的框小、漏檢多近處的框大、誤檢也多。這是因為攝像頭是透視成像遠處的人所占像素面積本來就小小目標檢測能力就成了瓶頸。一個務(wù)實的做法是引入透視校正在畫面中選幾個參考點標出實際距離的比例關(guān)系將遠處目標按比例放大后再送進檢測器。這樣可以讓整幅畫面中人的尺寸大致與真實物理尺寸成比例提升遠處小目標的召回率。import numpy as np # 在畫面中選擇 4 個點對應(yīng)于地面一個矩形的 4 個角 # src 是畫面中的像素坐標dst 是物理平面上的坐標映射 src np.array([[200, 300], [600, 300], [900, 450], [100, 450]], dtypenp.float32) dst np.array([[0, 0], [400, 0], [400, 300], [0, 300]], dtypenp.float32) matrix cv2.getPerspectiveTransform(src, dst) warped cv2.warpPerspective(frame, matrix, (400, 300))透視校正的本質(zhì)是把斜視角的監(jiān)控畫面攤平成俯視視角。應(yīng)用這個變換后再對 warped 圖像做檢測遠處小目標的像素尺寸會被放大檢測置信度會明顯提升。這個方案有兩個限制一是只能校正地面平面人站立時的高度信息無法恢復(fù)二是變換后畫面四周會產(chǎn)生黑邊或拉伸失真需要裁剪。因此實際部署中我一般只對計數(shù)線附近區(qū)域做透視校正而不是整幅畫面。6.2 區(qū)域計數(shù)進入/離開判定比線計數(shù)更穩(wěn)用一條虛擬計數(shù)線做穿越判定雖然直觀但在多人并肩行走、來回走動頻繁的場景里誤差很大。更穩(wěn)的做法是用區(qū)域計數(shù)劃定兩個相鄰的區(qū)域比如 A 區(qū)和 B 區(qū)當某個目標的軌跡從 A 區(qū)中心進入 B 區(qū)中心時才判定為一次穿越。區(qū)域計數(shù)天然能過濾掉只在計數(shù)線附近來回試探的情況因為你需要的是完整的跨區(qū)軌跡而不是某一幀的跨越。實現(xiàn)上區(qū)域計數(shù)的核心是一個狀態(tài)機對每個跟蹤 ID記錄它上一次所在的區(qū)域當檢測到區(qū)域從 A 變?yōu)?B就認為發(fā)生了一次 A→B 的穿越。這樣對同一個人往返走也能正確記錄為兩次穿越。相比單線計數(shù)區(qū)域計數(shù)的優(yōu)點是對檢測框抖動不那么敏感缺點是邏輯稍復(fù)雜、需要維護每個 ID 的區(qū)域狀態(tài)。6.3 卡爾曼濾波讓軌跡平滑計數(shù)更準確最后一個提升準確率的常用技巧是卡爾曼濾波。它解決的是檢測框抖動問題YOLO 在連續(xù)幀中對同一個人的框會有幾個像素的無規(guī)律抖動這種抖動疊加到計數(shù)線上就會造成重復(fù)跨越。卡爾曼濾波的基本思想是用上一幀的預(yù)測位置和當前幀的觀測位置做一個加權(quán)融合讓軌跡更平滑。OpenCV 中可以用cv2.KalmanFilter實現(xiàn)。kf cv2.KalmanFilter(4, 2) # 狀態(tài) [x, y, vx, vy]觀測 [x, y] kf.measurementMatrix np.array([[1, 0, 0, 0], [0, 1, 0, 0]], dtypenp.float32) kf.transitionMatrix np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]], dtypenp.float32) def filter_update(cx, cy): prediction kf.predict() measured np.array([[np.float32(cx)], [np.float32(cy)]]) filtered kf.correct(measured) return filtered[0, 0], filtered[1, 0]卡爾曼濾波在這里不是必須的如果用了區(qū)域計數(shù)通常不需要它。但如果你做的是單線計數(shù)且畫面有輕微抖動加上它會讓計數(shù)曲線平滑很多。一個多年調(diào)參的教訓(xùn)是卡爾曼濾波的噪聲參數(shù)measurementNoiseCov、processNoiseCov對結(jié)果影響極大噪聲協(xié)方差設(shè)得太大濾波結(jié)果基本等于觀測值失去濾波意義設(shè)得太小軌跡會變得非常鈍、跟不上快速移動的人。我通常從measurementNoiseCov1e-1、processNoiseCov1e-3開始調(diào)再根據(jù)畫面中人的移動速度做微調(diào)。有時候現(xiàn)場演示時畫面上有樹葉搖晃、燈光閃爍這些干擾濾波器反而可以幫你把誤檢帶來的跳變吸收掉。這個技巧在你做項目效果對比時很有用——同一段視頻加不加濾波計數(shù)曲線會明顯差一個級別拿來作為優(yōu)化前后對比展示在項目說明里說服力很強。希望這些踩過的坑和調(diào)過的參數(shù)能幫你在畢設(shè)或項目里少走一段彎路。本文還有配套的精品資源點擊獲取