實(shí)戰(zhàn):從數(shù)據(jù)集標(biāo)注到PyQt界面與邊緣部署)
簡(jiǎn)介本資源面向計(jì)算機(jī)視覺學(xué)習(xí)者與安防場(chǎng)景開發(fā)者提供一套可直接落地的打架行為檢測(cè)方案包含YOLOv5訓(xùn)練好的權(quán)重、一萬(wàn)余張標(biāo)注數(shù)據(jù)集以及PyQt可視化界面適合課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或行為識(shí)別項(xiàng)目快速驗(yàn)證。壓縮包共2000個(gè)文件以1994個(gè)XML標(biāo)注文件為主另含3個(gè)Python腳本與3份環(huán)境配置及PyQt使用說明PDF整體約508MB數(shù)據(jù)集已按train、val、test劃分并附data.yaml類別為normal與fight兩類txt格式標(biāo)簽可直接供YOLOv5、YOLOv7、YOLOv8、YOLOv9等算法訓(xùn)練。已有160人學(xué)習(xí)下載。讀者可借助預(yù)訓(xùn)練權(quán)重直接推理檢測(cè)也可基于完整目錄結(jié)構(gòu)復(fù)現(xiàn)訓(xùn)練流程配合說明文檔完成環(huán)境配置與界面調(diào)用省去數(shù)據(jù)采集與標(biāo)注成本。1. 從一張監(jiān)控截圖說起YOLOv5打架行為檢測(cè)到底在做什么凌晨?jī)牲c(diǎn)值班室的屏幕上彈出一段自動(dòng)截取的視頻片段畫面里兩個(gè)人從推搡升級(jí)到揮拳系統(tǒng)在第三秒就框出了兩個(gè)交疊的人體框并打上“fight”標(biāo)簽。這不是什么高精尖的實(shí)驗(yàn)室demo而是一套用 YOLOv5 訓(xùn)練出來的打架行為檢測(cè)模型在跑。很多人第一次聽到“打架行為檢測(cè)”腦子里浮現(xiàn)的是動(dòng)作識(shí)別、時(shí)序建模、光流法那一套重裝備但真正落地到園區(qū)、工地、校園這些場(chǎng)景時(shí)最穩(wěn)、最快、最容易維護(hù)的方案反而是把問題降維成目標(biāo)檢測(cè)——用 YOLOv5 直接檢測(cè)畫面里“正在打架的人”這個(gè)類別。它不追求理解整段動(dòng)作的語(yǔ)義只回答一個(gè)問題這一幀里有沒有人正在做出打架姿態(tài)。配合 PyQt 界面值班人員不需要懂命令行打開軟件、選視頻、點(diǎn)開始結(jié)果就實(shí)時(shí)畫在畫面上。這套組合適合誰(shuí)適合手里有幾百到幾千張標(biāo)注圖、想在一周內(nèi)跑出一個(gè)能演示能迭代的原型的工程師也適合已經(jīng)用過 YOLOv5 做安全帽、煙火檢測(cè)、想再擴(kuò)一個(gè)行為類別的熟手。它不適合追求學(xué)術(shù)級(jí)動(dòng)作識(shí)別精度的團(tuán)隊(duì)但對(duì)絕大多數(shù)工程落地場(chǎng)景夠用而且快。2. 數(shù)據(jù)集怎么攢從公開數(shù)據(jù)到自標(biāo)注的取舍2.1 打架行為檢測(cè)的數(shù)據(jù)集長(zhǎng)什么樣打架檢測(cè)的數(shù)據(jù)集和普通目標(biāo)檢測(cè)有個(gè)本質(zhì)區(qū)別它的正樣本極度依賴“姿態(tài)”而不是“物體”。一個(gè)人站著是 person揮拳也是 person區(qū)別在肢體關(guān)系。所以標(biāo)注時(shí)不能只框人要框“正在發(fā)生沖突的兩個(gè)人或一群人”通常做法是用一個(gè)較大的框把參與沖突的人整體包住或者對(duì)每個(gè)參與人單獨(dú)框但統(tǒng)一打上 fight 標(biāo)簽。常見做法是后者因?yàn)?YOLOv5 本身是多框檢測(cè)單框包兩人容易在密集場(chǎng)景下漏檢。數(shù)據(jù)集來源一般三條路一是公開的行為識(shí)別數(shù)據(jù)集里抽幀比如 RWF-2000、Movies Fight 這類抽幀后人工篩掉模糊和誤標(biāo)二是自己從監(jiān)控錄像里截取這是最貼合落地場(chǎng)景的但要注意隱私合規(guī)只保留人體區(qū)域、不涉及可識(shí)別身份信息三是用現(xiàn)有 person 檢測(cè)模型先跑一遍把所有人框出來再人工把其中打架的框改標(biāo)簽這樣能省一半標(biāo)注時(shí)間。我一般會(huì)建議至少準(zhǔn)備 1500 張圖其中正樣本含打架不低于 500 張負(fù)樣本里要有大量“兩個(gè)人靠得近但沒打架”的干擾圖比如握手、遞東西、排隊(duì)否則模型會(huì)把“靠近”學(xué)成“打架”。2.2 標(biāo)注格式轉(zhuǎn)換與目錄結(jié)構(gòu)YOLOv5 要的是 YOLO 格式的 txt每行class x_center y_center width height全部歸一化到 0~1。如果你用 LabelImg 或 CVAT 標(biāo)的是 VOC 的 xml需要轉(zhuǎn)一道。下面這個(gè)腳本是我常用的轉(zhuǎn)換邏輯處理了邊界裁剪和空文件跳過import os import xml.etree.ElementTree as ET # 輸入VOC xml目錄輸出YOLO txt目錄 voc_dir annotations_xml out_dir labels_yolo classes [person, fight] # 類別順序要和訓(xùn)練時(shí)一致 os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(voc_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, xml_file)) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 裁剪到圖像邊界內(nèi)防止標(biāo)注越界導(dǎo)致歸一化出錯(cuò) x1 max(0, min(x1, w)) y1 max(0, min(y1, h)) x2 max(0, min(x2, w)) y2 max(0, min(y2, h)) if x2 x1 or y2 y1: continue # 跳過無效框 xc (x1 x2) / 2.0 / w yc (y1 y2) / 2.0 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) if lines: txt_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines))邏輯說明先讀圖像寬高做歸一化分母再對(duì)每個(gè) object 取類別名映射到 id坐標(biāo)裁剪防止越界最后按 YOLO 格式寫入。參數(shù)上classes列表的順序必須和后面訓(xùn)練時(shí) data.yaml 里的 names 完全一致否則標(biāo)簽全錯(cuò)。空文件不寫因?yàn)?YOLOv5 訓(xùn)練時(shí)遇到空 txt 會(huì)當(dāng)作純背景圖這是有用的但如果你不希望背景圖參與就在生成后手動(dòng)刪掉。目錄結(jié)構(gòu)按 YOLOv5 官方推薦dataset/ images/ train/ val/ labels/ train/ val/train 和 val 按 8:2 分注意同一個(gè)視頻抽出來的幀不能同時(shí)出現(xiàn)在 train 和 val否則驗(yàn)證指標(biāo)會(huì)虛高這是血淚經(jīng)驗(yàn)。2.3 數(shù)據(jù)增強(qiáng)里哪些該開哪些不該開YOLOv5 自帶 mosaic、mixup、HSV 增強(qiáng)、隨機(jī)翻轉(zhuǎn)。打架檢測(cè)里mosaic 和 mixup 要謹(jǐn)慎mosaic 把四張圖拼一起容易讓兩個(gè)不相關(guān)的人被拼成“近距離接觸”模型可能學(xué)到假的沖突關(guān)系。我一般會(huì)把 mosaic 關(guān)掉或把概率降到 0.3 以下mixup 直接關(guān)。HSV 增強(qiáng)可以開因?yàn)楸O(jiān)控畫面色偏常見。隨機(jī)翻轉(zhuǎn)要分場(chǎng)景如果打架動(dòng)作有方向性比如右勾拳水平翻轉(zhuǎn)會(huì)造出不符合人體工學(xué)的樣本但實(shí)際監(jiān)控里左右都有所以水平翻轉(zhuǎn)可以開垂直翻轉(zhuǎn)絕對(duì)不要開人不會(huì)倒著打架。這些參數(shù)在hyp.scratch-low.yaml里改訓(xùn)練時(shí)用--hyp指定。3. 訓(xùn)練自己的打架檢測(cè)模型超參數(shù)怎么設(shè)才不翻車3.1 從預(yù)訓(xùn)練權(quán)重起步還是從頭訓(xùn)除非你有十萬(wàn)張以上的標(biāo)注圖否則一定從預(yù)訓(xùn)練權(quán)重起步。YOLOv5 在 COCO 上學(xué)到的人體特征對(duì)打架檢測(cè)非常有用因?yàn)榇蚣苁紫鹊檬侨?。常見做法是下載yolov5s.pt或yolov5m.pts 版本推理快適合 PyQt 實(shí)時(shí)顯示m 版本精度高一點(diǎn)但幀率會(huì)掉。我一般先用 s 跑通全流程如果驗(yàn)證集漏檢嚴(yán)重再換 m。命令如下python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data/fight.yaml \ --weights yolov5s.pt \ --hyp data/hyp.fight.yaml \ --project runs/fight \ --name exp1參數(shù)說明--img 640是輸入分辨率監(jiān)控畫面里人通常占畫面比例不大640 夠用如果小目標(biāo)多可以上 1280 但顯存翻倍--batch 16在 8G 顯存上比較穩(wěn)爆顯存就降到 8--epochs 100配合早停實(shí)際可能 60 輪就收斂--data指向你的 yaml里面寫 train/val 路徑和類別名--hyp指向自定義增強(qiáng)配置。訓(xùn)練過程中重點(diǎn)看mAP0.5和val/box_loss如果 box_loss 震蕩不降多半是學(xué)習(xí)率太大或標(biāo)注框有大量越界。3.2 打架檢測(cè)的錨框需不需要重新聚類YOLOv5 默認(rèn)錨框是基于 COCO 的COCO 里人體框的寬高比和監(jiān)控里打架的人體框有差異——打架時(shí)人可能彎腰、伸展框更扁或更方。所以建議用自己的數(shù)據(jù)跑一遍 k-means 聚類。YOLOv5 倉(cāng)庫(kù)里有utils/autoanchor.py訓(xùn)練時(shí)加--noautoanchor關(guān)閉自動(dòng)錨框然后手動(dòng)跑python utils/autoanchor.py --data data/fight.yaml --weights yolov5s.pt它會(huì)輸出建議的 anchors 并計(jì)算 BPRbest possible recall。如果 BPR 低于 0.98說明默認(rèn)錨框確實(shí)不匹配把輸出的 anchors 填到模型 yaml 里。這一步很多人跳過結(jié)果就是小目標(biāo)打架框回歸不準(zhǔn)mAP 卡在 0.5 上不去。注意聚類用的圖應(yīng)該是訓(xùn)練集子集別用驗(yàn)證集否則等于偷看答案。3.3 訓(xùn)練中必須盯的三個(gè)指標(biāo)第一個(gè)是metrics/mAP_0.5這是最直觀的但別只看它因?yàn)榇蚣軝z測(cè)里負(fù)樣本多mAP 高不代表誤報(bào)低。第二個(gè)是val/obj_loss如果它一直降但 mAP 不漲說明模型在學(xué)背景可能是正樣本太少。第三個(gè)是混淆矩陣訓(xùn)練完在runs/fight/exp1/下會(huì)生成confusion_matrix.png重點(diǎn)看 fight 被誤判成 person 的比例如果很高說明模型分不清“人”和“打架的人”這時(shí)候要么加更多打架正樣本要么把 person 類去掉只留 fight 和 background。我一般會(huì)保留 person 類因?yàn)?PyQt 界面上同時(shí)顯示人和打架框值班人員能看清上下文。4. PyQt 界面怎么接從推理到可視化4.1 界面線程與推理線程必須分開PyQt 最大的坑就是卡界面。如果你在按鈕點(diǎn)擊槽函數(shù)里直接跑 YOLOv5 推理視頻一播放主線程就凍結(jié)窗口直接“未響應(yīng)”。正確做法是用 QThread 把推理循環(huán)扔到子線程通過信號(hào)槽把帶框的圖像傳回主線程顯示。下面是最小可用的結(jié)構(gòu)from PyQt5.QtCore import QThread, pyqtSignal import cv2 import torch class DetectThread(QThread): frame_signal pyqtSignal(object) # 傳numpy圖像 def __init__(self, model, source): super().__init__() self.model model self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ret, frame cap.read() if not ret: break # 推理model是加載好的YOLOv5 results self.model(frame, size640) annotated results.render()[0] # 畫框后的圖 self.frame_signal.emit(annotated) cap.release() def stop(self): self.running False self.wait()邏輯說明frame_signal用 object 類型傳 numpy 數(shù)組主線程收到后轉(zhuǎn)成 QImage 再塞給 QLabel。self.running控制循環(huán)退出窗口關(guān)閉時(shí)調(diào)用stop()避免線程泄漏。參數(shù)上size640要和訓(xùn)練時(shí)一致不一致精度會(huì)掉。注意results.render()返回的是列表取第 0 個(gè)。這個(gè)結(jié)構(gòu)跑 1080p 視頻在普通 i5 上能到 15~20 FPS夠值班看。4.2 檢測(cè)結(jié)果怎么畫才不糊YOLOv5 自帶的 render 畫的是細(xì)線框在監(jiān)控大屏上遠(yuǎn)看還行但 PyQt 窗口里如果縮放顯示線會(huì)糊。我一般自己寫繪制用 OpenCV 的rectangle加putText框線寬按圖像寬度動(dòng)態(tài)算比如thickness max(2, int(frame.shape[1] / 640))。顏色上 fight 用紅色person 用綠色這樣值班人員一眼能區(qū)分。文字背景加個(gè)實(shí)心矩形不然白字在亮背景上看不清。還有一點(diǎn)如果連續(xù)多幀都檢測(cè)到 fight不要每幀都彈報(bào)警加一個(gè)簡(jiǎn)單的計(jì)數(shù)器連續(xù) 5 幀命中才觸發(fā)聲音或日志否則單幀誤檢會(huì)把人煩死。4.3 模型加載與界面初始化的順序PyQt 啟動(dòng)時(shí)加載 YOLOv5 模型要幾百毫秒到幾秒如果放在主窗口構(gòu)造函數(shù)里界面會(huì)白屏一會(huì)兒。常見做法是先把界面顯示出來再在子線程里加載模型加載完發(fā)信號(hào)啟用“開始檢測(cè)”按鈕。模型加載用torch.hub.load或直接DetectMultiBackend注意指定device為cpu或cuda:0如果機(jī)器沒顯卡就老老實(shí)實(shí) cpu別硬等 cuda 報(bào)錯(cuò)。還有模型路徑別寫死絕對(duì)路徑用os.path.join(os.path.dirname(__file__), weights/fight.pt)不然換臺(tái)機(jī)器就翻車。5. 避坑與排查打架檢測(cè)落地時(shí)最容易翻車的五件事5.1 現(xiàn)象模型把兩個(gè)人靠近就判成打架原因訓(xùn)練集里負(fù)樣本缺少“近距離但非打架”的圖模型把空間距離當(dāng)成了沖突特征。解決專門收集握手、遞煙、排隊(duì)、攙扶這類圖標(biāo)成 person 或 background數(shù)量至少和打架正樣本持平重新訓(xùn)練。如果已經(jīng)訓(xùn)完不想重來可以在推理后加一個(gè)簡(jiǎn)單規(guī)則兩個(gè) fight 框的 IoU 超過閾值且持續(xù)多幀才報(bào)警但這只是補(bǔ)救。5.2 現(xiàn)象PyQt 界面播放視頻越來越卡內(nèi)存一直漲原因每幀都創(chuàng)建新的 QImage 和 QPixmap舊對(duì)象沒釋放Python 垃圾回收跟不上。解決在子線程里復(fù)用 numpy 緩沖主線程顯示時(shí)用QLabel.setPixmap后手動(dòng)del舊 pixmap或者用QImage直接構(gòu)造不經(jīng)過QPixmap。更徹底的做法是限制顯示幀率比如每?jī)蓭@示一次人眼看起來一樣流暢。5.3 現(xiàn)象訓(xùn)練 loss 正常下降但驗(yàn)證 mAP 一直是 0原因data.yaml 里的val路徑寫錯(cuò)或者 val 的 labels 目錄為空YOLOv5 找不到驗(yàn)證集就跳過評(píng)估。解決檢查data/fight.yaml里 train 和 val 的路徑是相對(duì)于數(shù)據(jù)集根目錄還是絕對(duì)路徑建議統(tǒng)一用絕對(duì)路徑。再確認(rèn) val 的 images 和 labels 文件名一一對(duì)應(yīng)缺一個(gè)都會(huì)導(dǎo)致該圖被忽略。5.4 現(xiàn)象換一臺(tái)機(jī)器推理檢測(cè)框全偏了原因訓(xùn)練時(shí)圖像做了 letterbox 填充推理時(shí)如果直接 resize 不填充坐標(biāo)映射會(huì)錯(cuò)。YOLOv5 的DetectMultiBackend默認(rèn)會(huì)做 letterbox但如果你自己寫預(yù)處理就容易漏。解決要么直接用官方推理接口要么自己寫時(shí)嚴(yán)格按scale min(640/w, 640/h)算縮放和 padding再把框坐標(biāo)反算回去。這個(gè)坑我踩過兩次框整體偏移幾十像素查了一晚上。5.5 現(xiàn)象PyQt 打包成 exe 后模型加載失敗原因PyInstaller 沒把weights和models目錄打進(jìn)去或者torch.hub.load在打包環(huán)境里找不到緩存路徑。解決用--add-data把權(quán)重和模型配置目錄加進(jìn)去代碼里用sys._MEIPASS拼路徑。另外 torch 打包體積巨大可以用--exclude-module去掉不用的模塊但別刪 torch 核心否則推理直接崩。6. 進(jìn)階技巧把打架檢測(cè)模型塞進(jìn) RK3568 或樹莓派4B如果你已經(jīng)跑通了 PC 上的 YOLOv5 PyQt下一步大概率是想把它挪到邊緣設(shè)備。RK3568 和樹莓派4B 是熱搜里出現(xiàn)最多的兩個(gè)平臺(tái)但它們的算力差一個(gè)量級(jí)。RK3568 有 NPU支持 INT8 量化YOLOv5s 量化后能跑到 15 FPS 以上樹莓派4B 只有 CPU跑原版 YOLOv5s 大概 2~3 FPS基本沒法實(shí)時(shí)看視頻只能做定時(shí)抓拍。所以選型上要實(shí)時(shí)就上 RK3568要低成本演示就樹莓派加低分辨率。RK3568 的路徑是PyTorch 訓(xùn)練 → 導(dǎo)出 ONNX → 用 RKNN-Toolkit2 轉(zhuǎn)成 rknn 模型 → 板端用 rknn_api 推理。導(dǎo)出 ONNX 時(shí)注意把--img固定成 640動(dòng)態(tài)軸關(guān)掉否則 RKNN 轉(zhuǎn)換會(huì)報(bào)錯(cuò)。量化需要準(zhǔn)備一批校準(zhǔn)圖一般從訓(xùn)練集里抽 200 張就夠校準(zhǔn)圖要覆蓋白天、夜晚、室內(nèi)、室外不然量化后夜間誤檢會(huì)飆升。下面是一個(gè)導(dǎo)出 ONNX 的命令python export.py \ --weights runs/fight/exp1/weights/best.pt \ --include onnx \ --img 640 640 \ --batch 1 \ --simplify--simplify會(huì)調(diào)用 onnx-simplifier 去掉冗余節(jié)點(diǎn)RKNN 轉(zhuǎn)換時(shí)更順。轉(zhuǎn) RKNN 的腳本在 RKNN-Toolkit2 的 examples 里改重點(diǎn)配置mean_values和std_values要和訓(xùn)練時(shí)的歸一化一致YOLOv5 默認(rèn)是 0~1 歸一化所以 mean 填 0,0,0std 填 255,255,255。板端推理后處理要自己寫 NMS因?yàn)?RKNN 輸出的是原始特征圖別指望它幫你解碼。樹莓派4B 上如果非要跑建議用 YOLOv5n 或者把輸入降到 320然后開 OpenVINO 或 NCNN 推理框架比原版 PyTorch 快 2~3 倍。但說實(shí)話樹莓派做實(shí)時(shí)打架檢測(cè)體驗(yàn)很差我一般只拿它做離線視頻分析一晚上跑完一天的錄像第二天看報(bào)警片段。最后說個(gè)習(xí)慣不管上哪個(gè)邊緣設(shè)備先在 PC 上用同一段視頻對(duì)比量化前后的檢測(cè)結(jié)果如果量化后 mAP 掉超過 5 個(gè)點(diǎn)就回去檢查校準(zhǔn)集是不是太單一。這個(gè)對(duì)比步驟能幫你省下大量在板子上反復(fù)燒錄的時(shí)間。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取