議室參會(huì)人數(shù)統(tǒng)計(jì)實(shí)踐與避坑指南)
簡(jiǎn)介基于YOLOv8的智能會(huì)議室參會(huì)人數(shù)統(tǒng)計(jì)項(xiàng)目是一份面向計(jì)算機(jī)相關(guān)專業(yè)學(xué)生與初學(xué)者的完整實(shí)戰(zhàn)資源適合畢業(yè)設(shè)計(jì)、課程設(shè)計(jì)或大作業(yè)場(chǎng)景解決會(huì)議場(chǎng)景下多人目標(biāo)檢測(cè)與人數(shù)統(tǒng)計(jì)的需求。壓縮包共8個(gè)文件以Python腳本、PyTorch模型權(quán)重和說(shuō)明文檔為主整體約15.91MB覆蓋模型訓(xùn)練、視頻檢測(cè)、可視化界面等核心模塊并內(nèi)置訓(xùn)練好的模型權(quán)重下載后按README指引即可快速運(yùn)行。目前已有34人學(xué)習(xí)下載可作為同類目標(biāo)檢測(cè)任務(wù)的參考實(shí)現(xiàn)。項(xiàng)目?jī)?nèi)代碼均經(jīng)過(guò)測(cè)試并成功運(yùn)行可產(chǎn)出核心指標(biāo)曲線圖、混淆矩陣、F1分?jǐn)?shù)曲線、精確率-召回率曲線以及驗(yàn)證集預(yù)測(cè)結(jié)果和標(biāo)簽分布圖方便在答辯或課程展示中直觀驗(yàn)證效果完整數(shù)據(jù)集與部署說(shuō)明進(jìn)一步降低了上手門檻既可直接使用也可在此基礎(chǔ)之上二次開(kāi)發(fā)。1. 為什么會(huì)議室人數(shù)統(tǒng)計(jì)聽(tīng)著簡(jiǎn)單做起來(lái)全是坑會(huì)議室參會(huì)人數(shù)統(tǒng)計(jì)聽(tīng)起來(lái)就是「打開(kāi)攝像頭數(shù)一下有幾個(gè)人頭」但真做起來(lái)會(huì)發(fā)現(xiàn)事情沒(méi)這么順攝像頭通常是俯拍角度后排人頭小到模型認(rèn)不出來(lái)投影儀一亮背景亮斑把人影邊緣整個(gè)吞掉坐著的人只露出半個(gè)頭肩檢測(cè)框在連續(xù)幀里抖來(lái)抖去人數(shù)從 6 跳到 11 再跳回 6?!富赮OLOv8的智能會(huì)議室參會(huì)人數(shù)統(tǒng)計(jì)」這個(gè)項(xiàng)目解決的問(wèn)題不是「能不能檢測(cè)到人」而是「在會(huì)議室這種半靜態(tài)、強(qiáng)遮擋、固定視角的場(chǎng)景下怎么可靠地告訴你現(xiàn)在屋里到底有幾個(gè)人」。它把目標(biāo)檢測(cè)、視頻流處理、人數(shù)去重、可視化界面和部署串成一條完整鏈路適合課程設(shè)計(jì)和畢業(yè)設(shè)計(jì)也適合想快速驗(yàn)證「檢測(cè) 計(jì)數(shù) 界面」這套打法的從業(yè)者。下面按做這個(gè)方案的真實(shí)順序從選型拆到踩坑。2. 方案選型YOLOv8 為什么是會(huì)議室人數(shù)統(tǒng)計(jì)的可靠選擇2.1 先想清楚人數(shù)統(tǒng)計(jì)到底在統(tǒng)計(jì)什么做方案之前先分清楚需求。靜態(tài)圖場(chǎng)景只需要在某一時(shí)刻數(shù)一次人頭比如門禁抓拍會(huì)議室通常不是這個(gè)用法攝像頭常開(kāi)觀眾席上的人一坐就是半小時(shí)系統(tǒng)要持續(xù)給出「當(dāng)前有多少人」的數(shù)字這就需要視頻流處理。視頻流計(jì)數(shù)有兩條技術(shù)路線。第一條是樸素的「每幀檢測(cè)、每幀計(jì)數(shù)」把當(dāng)前幀里 YOLOv8 輸出的 person 框數(shù)量直接當(dāng)成人數(shù)。這條路線實(shí)現(xiàn)成本低但檢測(cè)框在幀間會(huì)有輕微抖動(dòng)漏檢一兩幀人數(shù)馬上跳變演示時(shí)看起來(lái)很業(yè)余。第二條是「檢測(cè) 跟蹤」先檢測(cè)出人再用跟蹤器給每個(gè)人分配一個(gè)穩(wěn)定的 ID按 ID 去重計(jì)數(shù)。會(huì)議室里的人長(zhǎng)時(shí)間保持坐姿ID 一旦建立就很穩(wěn)定即便中間有幾幀漏檢跟蹤器也能靠運(yùn)動(dòng)預(yù)測(cè)補(bǔ)回來(lái)。我做這類項(xiàng)目時(shí)一般建議走第二條路線但不用上 DeepSort 這種重方案。會(huì)議室場(chǎng)景下人的運(yùn)動(dòng)幅度小用輕量跟蹤就能解決問(wèn)題后面第 5 章會(huì)展開(kāi)講。下表的結(jié)論是我多年下來(lái)比較認(rèn)可的選型依據(jù)方案實(shí)現(xiàn)成本人數(shù)輸出穩(wěn)定性適用場(chǎng)景純檢測(cè) 幀內(nèi)計(jì)數(shù)低差檢測(cè)抖動(dòng)直接傳導(dǎo)靜態(tài)抓拍、快速驗(yàn)證檢測(cè) 輕量跟蹤IOU/ByteTrack中好能扛住幀間漏檢會(huì)議室、教室、門店密度估計(jì)CSRNet 類高依賴訓(xùn)練數(shù)據(jù)質(zhì)量密集人群不需要坐標(biāo)框2.2 為什么不是 OpenCV 背景差分、不是人頭密度圖有人會(huì)問(wèn)會(huì)議室背景固定能不能用 OpenCV 背景差分檢測(cè)人背景差分在會(huì)議室場(chǎng)景屬于典型的「實(shí)驗(yàn)室里成立、落地就翻車」人長(zhǎng)時(shí)間坐著不動(dòng)會(huì)逐漸被模型吸收成背景窗簾被風(fēng)吹動(dòng)、投影儀內(nèi)容切換、有人開(kāi)關(guān)燈都會(huì)觸發(fā)大范圍誤報(bào)。會(huì)議室恰恰是「背景在變、人不動(dòng)」的反直覺(jué)組合背景差分幾乎必跪。密度估計(jì)方案如 CSRNet、DM-Count的問(wèn)題在于標(biāo)注成本和輸出形態(tài)。這類模型訓(xùn)練需要逐點(diǎn)標(biāo)注人群密度圖數(shù)據(jù)準(zhǔn)備工作量遠(yuǎn)超畫框而且輸出是一張熱力值圖只能告訴你「這片區(qū)域大概有多少人」給不出每個(gè)人在哪、更做不了 ID 跟蹤會(huì)議室門禁聯(lián)動(dòng)這種需求它接不住。YOLOv8 這類 anchor-free 檢測(cè)器在會(huì)議室這種中等密度場(chǎng)景下是更實(shí)際的選項(xiàng)COCO 預(yù)訓(xùn)練權(quán)重里 person 類已經(jīng)很強(qiáng)不需要從零訓(xùn)練輸出天然帶框和置信度往下接跟蹤、接界面都很順。標(biāo)題里提到「完整數(shù)據(jù)集」實(shí)際意義就是給你一個(gè)在通用 person 模型基礎(chǔ)上做會(huì)議室微調(diào)的起點(diǎn)不是讓你從隨機(jī)權(quán)重開(kāi)始煉。2.3 模型選型n / s / m 三檔怎么選YOLOv8 有 n / s / m / l / x 五檔會(huì)議室人數(shù)統(tǒng)計(jì)用到 l 和 x 屬于浪費(fèi)算力。n 最快但精度相對(duì)低s 在速度和精度之間最均衡m 適合對(duì)精度有執(zhí)念且機(jī)器不太差的場(chǎng)景。選擇依據(jù)主要是部署硬件純 CPU 機(jī)器用 n入門獨(dú)顯用 s嵌入式板子用 n 再導(dǎo)出量化模型。做畢設(shè)演示這件事我的建議是別盲目追高。如果現(xiàn)場(chǎng)機(jī)器是普通筆記本的核顯跑 s 會(huì)卡到影響演示節(jié)奏老老實(shí)實(shí)用 n 加 imgsz640四平八穩(wěn)。如果機(jī)房給的是 GTX 1660Ti 這類 6G 顯存的入門卡那上 s 沒(méi)有任何壓力訓(xùn)練時(shí)間和顯存占用都可控。至于 m適合你打算把模型做成產(chǎn)品原型、后面還要換更大數(shù)據(jù)集的場(chǎng)景。從網(wǎng)絡(luò)結(jié)構(gòu)角度看YOLOv8 的 backbone 用了 C2f 模塊和 SPPF 金字塔池化neck 是 PAN-FPN 雙向融合這些設(shè)計(jì)讓它對(duì)中小尺寸目標(biāo)比較友好。會(huì)議室俯拍畫面里的人頭占比偏小尤其后排可能只有三四十個(gè)像素結(jié)構(gòu)上選對(duì)模型檔次比調(diào)參更重要。2.4 整體流程拆解從視頻幀到界面數(shù)字整個(gè)項(xiàng)目的落地路徑可以拆成一條鏈視頻流或視頻文件 → 丟幀 → letterbox 預(yù)處理 → YOLOv8 推理 → NMS 去重 → 跟蹤 ID 關(guān)聯(lián) → 人數(shù)統(tǒng)計(jì)與平滑 → 可視化界面顯示與記錄。訓(xùn)練環(huán)節(jié)在這條鏈之前數(shù)據(jù)準(zhǔn)備和模型調(diào)優(yōu)的產(chǎn)出是 best.pt 權(quán)重文件推理環(huán)節(jié)加載它。部署教程里做的事情本質(zhì)上就是把這條鏈固定成兩個(gè)入口一條命令行入口負(fù)責(zé)訓(xùn)練和驗(yàn)證一條圖形界面入口負(fù)責(zé)加載模型、讀視頻、顯示結(jié)果。界面是給答辯和演示看的訓(xùn)練和推理是給功能兜底的兩條腿缺一不可。接下來(lái)按這條鏈的順序先講數(shù)據(jù)準(zhǔn)備再講訓(xùn)練調(diào)參然后講最容易翻車的幾個(gè)細(xì)節(jié)最后說(shuō)界面和部署。3. 數(shù)據(jù)集準(zhǔn)備與標(biāo)注把俯拍會(huì)議室畫面變成 YOLO 訓(xùn)練集3.1 采集規(guī)則一個(gè)會(huì)議室該拍哪些畫面數(shù)據(jù)集質(zhì)量直接決定后面所有步驟的成敗。會(huì)議室場(chǎng)景的采集要覆蓋真實(shí)使用中會(huì)出現(xiàn)的組合維度至少包含不同人數(shù)檔位1 人、3 人、5 人、8 人、12 人以上、不同座位區(qū)域分布、開(kāi)燈和關(guān)燈兩種狀態(tài)、投影儀開(kāi)啟時(shí)的強(qiáng)光干擾、窗戶側(cè)光、有人從門口進(jìn)出、個(gè)別單人背對(duì)鏡頭坐著。數(shù)量上按做畢設(shè)和課設(shè)的慣例300 到 800 張的標(biāo)注量已經(jīng)能微調(diào)出可演示的模型。單張圖內(nèi)人數(shù)不用刻意追求很多2 到 15 人的區(qū)間比較合理覆蓋程度比數(shù)量更重要。采集時(shí)盡量保留原始分辨率不要為了省硬盤壓得太狠訓(xùn)練時(shí)會(huì)統(tǒng)一縮放到 imgsz原始大圖是留給自己的一條后悔藥。3.2 標(biāo)注規(guī)范與工具用哪款工具、按什么標(biāo)準(zhǔn)框標(biāo)注工具用 LabelImg 或 X-AnyLabeling 都行。LabelImg 老牌穩(wěn)定導(dǎo)出 VOC XML 格式方便X-AnyLabeling 界面更好用支持自動(dòng)標(biāo)注輔助。對(duì)只畫矩形框的任務(wù)兩者沒(méi)有本質(zhì)差別挑個(gè)安裝順手的就行。環(huán)境上用 Python 3.8 以上的虛擬環(huán)境安裝避免系統(tǒng) Python 環(huán)境被搞亂。標(biāo)注規(guī)范是新手最容易忽略的坑。會(huì)議室的特殊性在于坐姿正面前方的人可以看到上半身俯拍鏡頭下的人可能只露頭肩后排被前排擋住的人可能只露半個(gè)頭。我的做法是統(tǒng)一用 person 這一類坐姿只露頭肩的人框頭肩站姿完整的人框全身遮擋超過(guò)一半的目標(biāo)不標(biāo)或只標(biāo)可見(jiàn)部分。這里的關(guān)鍵不是「框得準(zhǔn)」而是「框得一致」——如果一半標(biāo)注是頭肩、一半是全身模型會(huì)不知道該學(xué)什么。會(huì)議室俯拍畫面還有一個(gè)增強(qiáng)上的特殊性訓(xùn)練時(shí)不要做上下翻轉(zhuǎn)flipud0.0人不會(huì)倒著開(kāi)會(huì)水平翻轉(zhuǎn)fliplr可以保留實(shí)測(cè)對(duì)視角泛化有幫助。3.3 把 VOC 轉(zhuǎn)成 YOLO 格式轉(zhuǎn)換腳本與四個(gè)邊界坑LabelImg 導(dǎo)出的是 VOC 格式 XMLYOLOv8 需要的是每張圖對(duì)應(yīng)一個(gè) txt每一行是「類ID cx cy w h」的歸一化坐標(biāo)。這個(gè)轉(zhuǎn)換腳本幾乎是每個(gè)做檢測(cè)項(xiàng)目的必修課下面給一個(gè)可以直接照著改的版本import xml.etree.ElementTree as ET import os from glob import glob # 類別映射本項(xiàng)目只有 person 一個(gè)類別 CLASSES {person: 0} def convert_voc_to_yolo(xml_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() # 尺寸從 XML 里讀不要自己去讀圖片避免尺寸不一致 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 邊界處理越界坐標(biāo)裁剪到圖像范圍內(nèi) x1, y1 max(x1, 0), max(y1, 0) x2, y2 min(x2, img_w), min(y2, img_h) # 過(guò)濾掉寬或高為 0 的目標(biāo) if x2 x1 or y2 y1: continue cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{CLASSES[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines)) xml_files glob(xmls/*.xml) os.makedirs(labels, exist_okTrue) for xml_path in xml_files: name os.path.basename(xml_path).replace(.xml, .txt) convert_voc_to_yolo(xml_path, os.path.join(labels, name))腳本邏輯不復(fù)雜但有幾個(gè)邊界處必須說(shuō)明。第一圖像尺寸一定從 XML 的 size 節(jié)點(diǎn)讀不要為了省事直接cv2.imread取尺寸LabelImg 偶爾會(huì)記錄到原圖尺寸之外的框兩者對(duì)不上會(huì)產(chǎn)出大于 1 或小于 0 的異常坐標(biāo)。第二越界框要裁剪而不是丟棄很多標(biāo)注工具的框會(huì)自動(dòng)外擴(kuò)幾個(gè)像素直接min/max裁回圖像范圍內(nèi)即可。第三類 ID 從 0 開(kāi)始計(jì)數(shù)YOLOv8 的 names 列表按索引對(duì)應(yīng)這點(diǎn)和 VOC 里字符串類名不同。第四txt 文件名必須和圖片文件名完全一致連后綴都對(duì)齊規(guī)則YOLOv8 訓(xùn)練時(shí)按前綴匹配圖像和標(biāo)簽文件。3.4 數(shù)據(jù)集劃分與 data.yaml 配置訓(xùn)練集、驗(yàn)證集、測(cè)試集的劃分要寫腳本固定隨機(jī)種子不要手動(dòng)挑。會(huì)議室視頻里連續(xù)幀高度相似如果只按順序切分很容易出現(xiàn)訓(xùn)練集和驗(yàn)證集里出現(xiàn)同一鏡頭的情況指標(biāo)虛高。做法是先對(duì)文件列表做 shuffle再按比例切分import os import random from glob import glob random.seed(42) # 固定種子保證每次劃分結(jié)果一致 xmls glob(xmls/*.xml) random.shuffle(xmls) n len(xmls) train xmls[: int(n * 0.8)] val xmls[int(n * 0.8) : int(n * 0.9)] test xmls[int(n * 0.9) :]劃分完的目錄結(jié)構(gòu)要固定成 YOLOv8 約定的樣子images/train、images/val、labels/train、labels/val四個(gè)目錄圖片和 txt 同名。然后寫 data.yamlpath: ./dataset train: images/train val: images/val names: 0: person注意names從 0 開(kāi)始數(shù)量必須比標(biāo)注中的最大類 ID 大 1否則訓(xùn)練報(bào)錯(cuò)。數(shù)據(jù)準(zhǔn)備完成后我不急著訓(xùn)練而是先隨機(jī)挑幾張圖把 YOLO 格式的框畫回原圖看一眼確認(rèn)沒(méi)有坐標(biāo)錯(cuò)位、目標(biāo)遺漏。這一步 10 分鐘能省掉后面定位數(shù)據(jù)問(wèn)題的半天。4. 訓(xùn)練與參數(shù)調(diào)優(yōu)從環(huán)境配置到看懂損失曲線4.1 環(huán)境配置CPU 和 GPU 兩條路都要能走通環(huán)境配置是「ubuntu20.04 搭建 YOLOv8 環(huán)境 CPU 版本」這類熱搜最常見(jiàn)的提問(wèn)來(lái)源。其實(shí)步驟很短創(chuàng)建虛擬環(huán)境、裝 PyTorch、裝 ultralytics。沒(méi)有 N 卡的機(jī)器裝 CPU 版 PyTorch有 N 卡就裝 CUDA 版兩套命令差別只在 torch 的安裝源# 創(chuàng)建虛擬環(huán)境Python 3.8 以上 python -m venv yolo_env source yolo_env/bin/activate # CPU 機(jī)器裝 CPU 版 torch 和 ultralytics pip install torch torchvision pip install ultralytics # GPU 機(jī)器先確認(rèn)驅(qū)動(dòng)和 CUDA再裝 torch nvidia-smi pip install torch torchvision pip install ultralyticstorch 和 torchvision 的版本要匹配這是老生常談的坑。直接用 pip 默認(rèn)源會(huì)拉互相兼容的版本別手動(dòng)指定一堆小版本號(hào)。裝完跑一個(gè)驗(yàn)證命令確認(rèn) torch 能看到 GPUpython -c import torch; print(torch.__version__); print(torch.cuda.is_available())GPU 機(jī)器返回True就說(shuō)明環(huán)境通了。如果nvidia-smi能看到顯卡但torch.cuda.is_available()返回 False一般是 PyTorch 版本和顯卡驅(qū)動(dòng)不匹配換新版本 torch 或升級(jí)驅(qū)動(dòng)二選一。環(huán)境配置這個(gè)環(huán)節(jié)沒(méi)什么玄學(xué)絕大多數(shù)問(wèn)題都是 Python 版本和 torch 版本配對(duì)造成的。4.2 最小訓(xùn)練命令從預(yù)訓(xùn)練權(quán)重到自己的 best.pt環(huán)境就緒后訓(xùn)練命令比大多數(shù)人想象中短。核心就是一條yolo detect train下面這個(gè)是我用過(guò)很多次的配置yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch8 \ device0 \ patience30 \ projectruns/detect \ namemeeting_room \ seed42 \ optimizerauto這條命令里每個(gè)參數(shù)都有實(shí)際意義。modelyolov8n.pt指定預(yù)訓(xùn)練權(quán)重ultralytics 會(huì)在首次運(yùn)行時(shí)自動(dòng)下載這就是很多人問(wèn)的「yolov8 哪里下載」的入口——不需要去別的地方找權(quán)重這條命令自己會(huì)拉。datadata.yaml指向上一章寫好的數(shù)據(jù)集配置。device0表示用第一塊 GPUCPU 機(jī)器改成devicecpu。project和name控制訓(xùn)練輸出目錄訓(xùn)練日志、權(quán)重、曲線圖都會(huì)落在runs/detect/meeting_room下。訓(xùn)練中斷了怎么辦不用從頭再來(lái)resumeTrue參數(shù)就是后悔藥。接上一條訓(xùn)練yolo detect train resumeTrue它會(huì)自動(dòng)從上次的斷點(diǎn)恢復(fù)。注意 resume 依賴訓(xùn)練目錄里的last.pt所以訓(xùn)練沒(méi)跑完別刪目錄。內(nèi)存小的機(jī)器可以把 workers 從默認(rèn)值調(diào)低比如加workers2否則數(shù)據(jù)加載進(jìn)程會(huì)把內(nèi)存吃滿。4.3 imgsz、batch、lr0三個(gè)必調(diào)參數(shù)與它們的邊界訓(xùn)練參數(shù)里最值得花時(shí)間理解的是 imgsz、batch、lr0這三個(gè)直接決定能不能訓(xùn)練出能用的模型對(duì)應(yīng)熱搜詞里「yolov8 模型訓(xùn)練參數(shù)含義」的訴求。下面給一份按會(huì)議室場(chǎng)景整理的參數(shù)表參數(shù)推薦值說(shuō)明imgsz640 或 736會(huì)議室俯拍人頭小640 起步后排密集建議 736batch4~16由顯存決定6G 顯存配 640 分辨率建議 8~16lr00.005數(shù)據(jù)量小于 500 張時(shí)默認(rèn) 0.01 容易震蕩flipud0.0會(huì)議室場(chǎng)景關(guān)閉上下翻轉(zhuǎn)fliplr0.5水平翻轉(zhuǎn)對(duì)視角泛化有幫助mosaic1.0小數(shù)據(jù)集上可關(guān)掉避免目標(biāo)被切碎imgsz 是第一個(gè)要決策的參數(shù)。檢測(cè)器的訓(xùn)練尺寸和推理尺寸必須一致否則訓(xùn)練時(shí)模型學(xué)的是 736 下的目標(biāo)尺度推理時(shí)喂 640 的圖目標(biāo)相對(duì)變小漏檢率直接上升。會(huì)議室俯拍畫面里后排人頭可能只有 30 像素我一般建議用 736代價(jià)是訓(xùn)練時(shí)間和顯存占用上漲。batch 主要受顯存約束。6G 顯存的 GTX 1660Ti 跑 640 分辨率 batch 8 沒(méi)問(wèn)題上 736 就降到 batch 4。CPU 訓(xùn)練內(nèi)存 16G 以上才建議跑 batch 4再小不如換云 GPU。lr0 默認(rèn) 0.01 是 COCO 大規(guī)模數(shù)據(jù)的經(jīng)驗(yàn)值自己只有幾百?gòu)垐D時(shí)學(xué)習(xí)率大會(huì)導(dǎo)致 loss 震蕩降到 0.005 更穩(wěn)妥優(yōu)化器選auto或AdamW都行小數(shù)據(jù)集上 AdamW 收斂更平緩。4.4 訓(xùn)練完看什么從損失曲線到驗(yàn)證指標(biāo)訓(xùn)練結(jié)束后runs/detect/meeting_room目錄下會(huì)自動(dòng)生成results.png這就是「yolov8 畫損失函數(shù)曲線圖」的直接產(chǎn)物圖形包含 train 和 val 的 box loss、cls loss、dfl loss 以及 mAP 曲線。不要只盯 train 曲線重點(diǎn)看 val 側(cè)有沒(méi)有和 train 明顯背離train loss 一路下降、val loss 在某個(gè) epoch 后開(kāi)始回升這是過(guò)擬合的信號(hào)best.pt會(huì)停在回升之前的位置。曲線圖是給人看的做數(shù)據(jù)分析時(shí)我更推薦直接讀results.csvimport pandas as pd df pd.read_csv(runs/detect/meeting_room/results.csv) print(df.columns.tolist()) # 不同版本列名可能有前導(dǎo)空格先打印確認(rèn) mAP_col metrics/mAP50-95(B) best_epoch df[mAP_col].idxmax() print(fbest epoch: {best_epoch 1}, mAP50-95: {df[mAP_col].max():.4f})不同版本 ultralytics 的列名格式不完全一致打印列名再取索引比盲寫安全。訓(xùn)練完成后單獨(dú)跑一次驗(yàn)證命令輸出 precision、recall、mAP50、mAP50-95 四個(gè)指標(biāo)yolo detect val datadata.yaml modelruns/detect/meeting_room/weights/best.pt會(huì)議室任務(wù)的目標(biāo)是「該漏的少漏、該錯(cuò)的不錯(cuò)」mAP50-95 到 0.7 以上、且實(shí)測(cè)場(chǎng)景不漏后排就可以進(jìn)入部署環(huán)節(jié)了。指標(biāo)只是門檻真正的好壞要在真實(shí)場(chǎng)景視頻里跑過(guò)才算數(shù)。5. 避坑與排查會(huì)議室人數(shù)統(tǒng)計(jì)最容易翻車的五個(gè)細(xì)節(jié)5.1 人數(shù)反復(fù)橫跳檢測(cè)框不穩(wěn)是根源現(xiàn)象視頻里人數(shù)從 5 跳到 9 再跳回 5看起來(lái)像系統(tǒng)在亂報(bào)。原因有兩層第一檢測(cè)模型在某些幀置信度不足同一幀里人漏了又補(bǔ)回來(lái)框數(shù)自然抖第二沒(méi)有任何幀間關(guān)聯(lián)機(jī)制每一幀都是獨(dú)立數(shù)數(shù)抖動(dòng)被原樣放大。會(huì)議室的人長(zhǎng)時(shí)間不動(dòng)框閃的原因多半不是人動(dòng)了而是模型在困難的幀上沒(méi)穩(wěn)住。解決分兩步。第一步給檢測(cè)結(jié)果加置信度過(guò)濾conf0.35左右起步把邊緣誤檢壓掉一部分第二步做幀間平滑最簡(jiǎn)單的是 EMA# 幀間指數(shù)平滑平滑系數(shù)越大越遲鈍 ema 0.7 * ema 0.3 * current_count show_count round(ema)更穩(wěn)妥的做法是加跟蹤給每個(gè)人分配 ID連續(xù) N 幀確認(rèn)后才計(jì)入總數(shù)離開(kāi)畫面后延遲若干幀才扣除。會(huì)議室場(chǎng)景不需要復(fù)雜的運(yùn)動(dòng)模型按檢測(cè)框中心做最近鄰匹配的輕量跟蹤就夠用ByteTrack 是效果和實(shí)現(xiàn)成本最平衡的選擇。5.2 后排漏檢俯拍視角的人頭就是小現(xiàn)象前排人數(shù)統(tǒng)計(jì)沒(méi)問(wèn)題后排漏掉兩三成畫面越遠(yuǎn)越嚴(yán)重。原因是目標(biāo)尺寸和訓(xùn)練尺度不匹配640 訓(xùn)練尺寸下后排人頭可能只有二三十像素特征在小尺度分支里沒(méi)有被充分表達(dá)。這不是模型玄學(xué)問(wèn)題是分辨率分配問(wèn)題。解決手段按優(yōu)先級(jí)排一是訓(xùn)練和推理都上 736 或 832二是訓(xùn)練增強(qiáng)里開(kāi) scale 增強(qiáng)讓模型見(jiàn)過(guò)更多尺度變化三是如果攝像頭分辨率很高且 GPU 撐不住高 imgsz就把大幅畫面切塊tile推理每塊單獨(dú)檢測(cè)再合并結(jié)果。另外攝像頭安裝角度也有影響接近正垂直的俯拍角度下目標(biāo)形態(tài)最差稍微傾斜一點(diǎn)反而更容易檢測(cè)。5.3 CPU 部署慢界面卡成 PPT現(xiàn)象檢測(cè)功能一切正常但界面打開(kāi)攝像頭后幀率極低拖動(dòng)窗口都卡頓。原因是推理在界面線程里同步執(zhí)行YOLOv8 的 PyTorch 推理在純 CPU 上每幀要幾百毫秒到一秒多界面刷新自然被堵死。解決分兩個(gè)層面。第一絕對(duì)不能把推理放在 UI 線程里用 QThread 把攝像頭讀取、推理、畫框放在工作線程UI 線程只負(fù)責(zé)接收 QImage 刷新畫面這是治本。第二把推理后端從 PyTorch 換成 ONNX Runtimeyolo export modelruns/detect/meeting_room/weights/best.pt formatonnximport onnxruntime as ort sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider])CPU 上 ONNX Runtime 比 PyTorch 推理快一截因?yàn)槿サ袅藙?dòng)態(tài)圖的調(diào)度開(kāi)銷。注意 ONNX 版的輸入輸出約定和訓(xùn)練時(shí)一致輸入要做 letterbox 歸一化輸出是類似(1, 84, 8400)的格式8400 是各個(gè)尺度特征圖相加的錨點(diǎn)數(shù)具體數(shù)值隨 imgsz 變化解析時(shí)寫通用邏輯不要硬編碼。5.4 標(biāo)注不統(tǒng)一模型學(xué)歪了現(xiàn)象訓(xùn)練指標(biāo)不難看但換一段真實(shí)會(huì)議視頻就漏檢。原因通常不在模型參數(shù)而在標(biāo)注本身每個(gè)人框的粗細(xì)不同有人框頭肩有人框全身后排小目標(biāo)干脆沒(méi)標(biāo)模型學(xué)到的目標(biāo)形態(tài)是撕裂的。解決方法是建立標(biāo)注規(guī)范和自查習(xí)慣。統(tǒng)一頭肩框規(guī)則遮擋目標(biāo)按規(guī)則取舍然后每條數(shù)據(jù)從三個(gè)維度自查有沒(méi)有漏標(biāo)、有沒(méi)有框錯(cuò)、有沒(méi)有越界框。做這個(gè)項(xiàng)目時(shí)我有一個(gè)固定習(xí)慣——每標(biāo)注 100 張就隨機(jī)挑 10 張把框畫回去看一遍肉眼掃完再繼續(xù)后面省下的返工時(shí)間遠(yuǎn)比這 20 分鐘多。5.5 換一間會(huì)議室就失效過(guò)擬合的典型癥狀現(xiàn)象訓(xùn)練用的會(huì)議室跑得很好換隔壁一間燈管位置不同、桌椅顏色不同漏檢率明顯上升。原因很簡(jiǎn)單數(shù)據(jù)集里大量樣本來(lái)自同一個(gè)機(jī)位、同一個(gè)光照環(huán)境模型記住了這個(gè)會(huì)議室的特征而不是通用的人的特征。解決要分兩步想。如果是畢設(shè)演示承認(rèn)邊界用訓(xùn)練場(chǎng)景做主要展示再錄一段目標(biāo)場(chǎng)景的視頻做二次驗(yàn)證。如果是想做成通用產(chǎn)品就必須在數(shù)據(jù)側(cè)下功夫多個(gè)會(huì)議室采集、不同時(shí)間點(diǎn)補(bǔ)拍、加入了光照擾動(dòng)增強(qiáng)。會(huì)議室場(chǎng)景的泛化難度不高人這個(gè)類別在 COCO 里已經(jīng)被學(xué)得很扎實(shí)只要數(shù)據(jù)不是太偏微調(diào)出來(lái)的模型換場(chǎng)地后通常只是精度下降不會(huì)完全失效。6. 可視化界面與部署落地從權(quán)重文件到雙擊運(yùn)行的統(tǒng)計(jì)工具項(xiàng)目最后一個(gè)環(huán)節(jié)是把模型包成能演示的界面。技術(shù)選型上PyQt5 和 Tkinter 之間我建議 PyQt5控件、布局、信號(hào)機(jī)制都更成熟答辯演示也好看。核心思路是工作線程負(fù)責(zé)推理界面線程只顯示結(jié)果兩者用信號(hào)連接。界面里最小可跑的邏輯框架是這樣from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QLabel, QApplication import cv2 from ultralytics import YOLO class Worker(QThread): frame_ready pyqtSignal(QImage, int) # 信號(hào)一幀圖像 當(dāng)前人數(shù) def __init__(self, source): super().__init__() self.model YOLO(best.pt) # 加載訓(xùn)練好的權(quán)重 self.cap cv2.VideoCapture(source) def run(self): while True: ok, frame self.cap.read() if not ok: break results self.model(frame, verboseFalse) count len(results[0].boxes) # 當(dāng)前幀檢測(cè)到的人數(shù) frame results[0].plot() # 畫好框的畫面 rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.frame_ready.emit(qimg, count)代碼里幾個(gè)關(guān)鍵點(diǎn)說(shuō)一下。模型加載放在線程初始化里不要放在 run 里每次循環(huán)加載。results[0].boxes取檢測(cè)框列表len()就是這一幀的檢測(cè)數(shù)配合第 5 章說(shuō)的平滑邏輯用在界面上。plot()畫完的圖要轉(zhuǎn) RGB 再轉(zhuǎn) QImage否則界面上顏色會(huì)偏藍(lán)偏亂。UI 側(cè)只需要在槽函數(shù)里更新 QLabel 的 pixmap 和人數(shù)文本控件所有耗時(shí)推理都在 Worker 線程完成。部署時(shí)的驗(yàn)證方法也很簡(jiǎn)單錄一段 15 個(gè)人進(jìn)進(jìn)出出的視頻跑完后對(duì)比每一幀的統(tǒng)計(jì)人數(shù)和真實(shí)人數(shù)誤差控制在 ±1 以內(nèi)算合格再測(cè)一段單人連續(xù)坐 20 分鐘的錄像看人數(shù)是否保持穩(wěn)定不跳變。驗(yàn)收這一關(guān)在我這里比訓(xùn)練指標(biāo)更重要指標(biāo)是開(kāi)發(fā)者視角實(shí)際視頻里的穩(wěn)定性才是用戶視角。這個(gè)方向做完之后再往上走的路子也清晰導(dǎo)出 ONNX 后可以接嵌入式平臺(tái)跑實(shí)時(shí)推理界面端加歷史曲線和 Excel 導(dǎo)出或者把單攝像頭擴(kuò)展成多會(huì)議室并發(fā)。我做過(guò)很多個(gè)檢測(cè)類小項(xiàng)目一個(gè)習(xí)慣是每次收尾都把「最小可復(fù)現(xiàn)命令」完整寫進(jìn) README——訓(xùn)練命令、驗(yàn)證命令、導(dǎo)出命令、啟動(dòng)界面命令四行跑不通時(shí)先查環(huán)境而不是查模型。希望這個(gè)方案的拆解對(duì)你做畢設(shè)或課程設(shè)計(jì)有實(shí)際幫助。本文還有配套的精品資源點(diǎn)擊獲取