域電子圍欄:從訓練到部署全流程)
簡介基于YOLOv8的實驗室危險區(qū)域電子圍欄項目面向計算機相關專業(yè)學生與畢業(yè)設計/課程設計場景集成目標檢測模型、可視化界面、完整數(shù)據(jù)集與部署教程。資源包含訓練腳本、視頻檢測腳本、可視化頁面代碼、YOLO系列權重yolov8n.pt、best.pt等及說明文檔共8個文件壓縮包約15.91MB結構精煉按README指引即可快速運行。模型訓練后自動生成核心指標曲線、混淆矩陣、F1分數(shù)曲線、精確率-召回率曲線、驗證集預測結果與標簽分布圖既支撐算法效果驗證也方便答辯展示。目前已有70人瀏覽學習適合需要完整可運行項目參考的初學者或用于課程設計、大作業(yè)起步在此基礎上可繼續(xù)修改擴展功能。1. 基于YOLOv8的實驗室危險區(qū)域電子圍欄這套方案值不值得做實驗室里貼著黃黑膠帶的安全區(qū)域經(jīng)常被人誤闖光靠攝像頭肉眼盯根本盯不過來。把YOLOv8跑起來用檢測框鎖定畫面中的人再拿一個多邊形把危險區(qū)圈出來人一旦進入就觸發(fā)報警——這就是基于YOLOv8的實驗室危險區(qū)域電子圍欄最樸素的實現(xiàn)思路。這類項目包通常會把目標檢測、圖像標注、坐標算法、桌面可視化界面都串在一起正好覆蓋了畢業(yè)設計和課程設計的全部得分點。你不需要從頭搭環(huán)境拿到來直接用唯一要花時間的是理解每一段代碼在干什么、參數(shù)怎么調、自己的數(shù)據(jù)怎么替換。2. 數(shù)據(jù)準備與標注先把要檢測的“人”和“區(qū)域”定義清楚2.1 電子圍欄場景要哪些類別實驗室電子圍欄的目標很簡單檢測到“人”再判斷這個人的位置和危險區(qū)域的關系。不要一上來就把類別拆成“戴安全帽的人”“穿白大褂的人”這些對你最終判定越界沒有幫助反而讓模型變得更難訓練。我建議單類別“person”既能復用COCO預訓練權重里的特征又讓數(shù)據(jù)量門檻低很多。如果你用的是已經(jīng)打包好的項目里面多半已經(jīng)有一份組織好的數(shù)據(jù)集。你要做的是先檢查它的目錄結構再決定要不要用自己的數(shù)據(jù)重新訓練。常見的結構是dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── data.yamldata.yaml 內容是類別定義和路徑path: /path/to/dataset train: images/train val: images/val names: 0: person請留意 path 這一段很多人訓練時報錯“dataset not found”最后查下來是絕對路徑寫死了別人電腦上的地址。建議改成相對路徑或者訓練前用 pwd 確認當前目錄把 path 設成數(shù)據(jù)集相對于當前目錄的位置。2.2 用 LabelMe 標注并轉成 YOLO 格式如果你要用自己的實驗室內景圖片訓練標注工具用 LabelMe 就夠了。LabelMe 保存的是 JSON 文件而 YOLOv8 需要的是每個圖片對應一個 .txt 標簽每行寫 class_id 和歸一化后的中心坐標、寬高。轉換腳本是這類項目里必帶的核心代碼邏輯并不復雜但要考慮多邊形的特殊情況。下面是 JSON 轉 YOLO 格式的腳本能處理單個多邊形直接轉矩形框的情況import json import os from glob import glob def convert_labelme_json(json_path, class_map, output_dir): json_path: LabelMe 生成的 JSON 文件路徑 class_map: 類別名到 id 的映射例如 {person: 0} output_dir: 輸出的 .txt 標簽存放目錄 with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w, img_h data[imageWidth], data[imageHeight] if img_w 0 or img_h 0: return lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue points shape[points] # 多邊形頂點列表 # 取多邊形的外接矩形轉成 YOLO 的 xywh 格式 xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) box_w x_max - x_min box_h y_max - y_min x_center (x_min x_max) / 2.0 y_center (y_min y_max) / 2.0 # 歸一化到 0~1避免越界 x_center / img_w y_center / img_h box_w / img_w box_h / img_h lines.append(f{class_map[label]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) base_name os.path.splitext(os.path.basename(json_path))[0] label_path os.path.join(output_dir, base_name .txt) with open(label_path, w, encodingutf-8) as f: f.write(\n.join(lines)) # 批量轉換示例 class_map {person: 0} os.makedirs(labels, exist_okTrue) for json_file in glob(labelme_json/*.json): convert_labelme_json(json_file, class_map, labels)這段代碼用外接矩形把多邊形圍起來優(yōu)點是穩(wěn)定、不會丟目標缺點是框里會塞進一些背景。對于“人”這種接近直立的物體外接矩形的損失很小可以接受。如果你后續(xù)想跑實例分割才需要保留多邊形頂點坐標那就是另外一套格式了。2.3 數(shù)據(jù)劃分與分布檢查訓練集和驗證集的劃分建議 9:1不要用 7:3目標檢測的數(shù)據(jù)量本身就顯得少驗證集砍太多會影響模型泛化能力評估。劃分時還要注意同一個場景的連續(xù)幀不能同時出現(xiàn)在訓練集和驗證集里否則會造成“數(shù)據(jù)泄漏”驗證集分數(shù)虛高。按視頻幀抽取時每隔幾幀抽一張或者按目錄分開放都能避免這個問題。劃分完以后把每類的圖片數(shù)量、標注框數(shù)量統(tǒng)計出來。如果 person 這類的標注框少于 500 個訓練效果會比較勉強。這時優(yōu)先考慮用一個通用人形檢測權重做初始化而不是從零開始訓練。3. YOLOv8 訓練過程環(huán)境配置、超參數(shù)與訓練日志3.1 環(huán)境搭建的兩種路徑CPU 版本的 YOLOv8 是可以跑的只是速度慢得讓人心慌。我用 GTX 1660 Ti 跑過一次 100 epoch 的 person 檢測每輪大約 40 秒同配置換純 CPU 會慢十倍不止。所以第一步先判斷手頭機器有沒有可用 GPU沒有的話直接用 CPU 版環(huán)境把訓練跑通也算一種落地方式。Windows 上推薦用 Python 3.9 PyTorch 2.x 的組合conda create -n yolov8 python3.9 conda activate yolov8 pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simpleUbuntu 20.04 下如果只是 CPU 推理這幾行就夠pip install ultralytics python -c import torch; print(torch.cuda.is_available())安裝完成后先跑這一條驗證環(huán)境不要直接開訓練。torch.cuda.is_available() 返回 False 的話后續(xù)所有 GPU 訓練命令都會靜默回退到 CPU很多人跑了一整天才發(fā)現(xiàn)模型根本沒上 GPU。還有一套關聯(lián)問題是 CUDA 版本與 PyTorch 不匹配報錯信息里帶 “torch._C” 或者找不到 DLL 時回到 PyTorch 官網(wǎng)選對應 CUDA 版本的安裝命令重裝。3.2 訓練命令與每個參數(shù)的實際含義訓練入口非常簡單關鍵是參數(shù)含義要清楚。拿一張 640x640 的輸入圖模型按結構復雜度分 yolov8n/s/m/l/x 五檔實驗室電子圍欄這種單類別任務yolov8n 或 yolov8s 就夠了。更小的模型意味著更快的推理速度對實時報警更有利。yolo train datadata.yaml modelyolov8s.pt epochs100 batch16 imgsz640 device0epochs 設為 100 是常見初值不是越大越好。我一般先用 50 快速跑通確認 loss 在下降后再加大到 100 或 150。batch 受顯存限制16G 顯存跑 yolov8s 用 16 沒有問題顯存不夠就降到 8。device0 表示第一張 GPU沒有 GPU 時寫 devicecpu速度慢但能驗流程。遷移學習默認是自動的modelyolov8s.pt 會下載 COCO 預訓練權重其中包含 person 類的特征這對小數(shù)據(jù)集幫助很大。如果你的數(shù)據(jù)集里人形姿態(tài)和實驗室場景差異特別大可以試一下凍結前幾層特征只是電子圍欄項目很少需要做到這一步。3.3 損失曲線與訓練日志怎么看訓練過程會在 runs/detect/train 目錄下生成多次實驗結果包括混淆矩陣、損失曲線和驗證集圖片。損失曲線分三部分box_loss 是框回歸損失cls_loss 是分類損失dfl_loss 是分布損失。電子圍欄項目里最優(yōu)先看 box_loss因為越界判定完全依賴檢測框位置的穩(wěn)定性。曲線持續(xù)下降說明模型在學習如果到第 20 輪以后還在高位震蕩優(yōu)先檢查數(shù)據(jù)標注質量不要急著加訓練輪次。還有個常見誤區(qū)是只盯訓練集 loss忽略 val 的 mAP。訓練集 loss 降但驗證集 mAP 沒變化說明模型在過擬合訓練集的背景紋理。這時可以加數(shù)據(jù)增強參數(shù)比如yolo train datadata.yaml modelyolov8s.pt epochs100 batch16 imgsz640 hsv_h0.02 hsv_s0.5 hsv_v0.4hsv_h 是色調變化幅度0.02 表示在原來基礎上微調改到 0.05 以上顏色畸變明顯。電子圍欄場景里光照變化比較頻繁適當?shù)?hsv_s 飽和度抖動能提升夜間和反光場景的魯棒性。4. 電子圍欄判定邏輯從檢測框坐標到越界報警4.1 危險區(qū)域怎么標定電子圍欄的“圍欄”在代碼里通常是一個或多個多邊形。你可以用鼠標在視頻畫面上點擊頂點也可以寫在配置文件里按像素坐標定義頂點。這里有一個坐標系陷阱畫面如果是 1280x720推理時縮放成 640x640檢測框坐標是相對于縮放后圖像的。拿這個框和標定好的多邊形比較必須先把兩邊統(tǒng)一到同一個坐標系。我一般按原始分辨率標定多邊形然后把模型推理出的框按比例放大回原始分辨率再判斷。具體縮放比例scale_x frame_width / model_width scale_y frame_height / model_height4.2 用 pointPolygonTest 判斷檢測框與多邊形的關系判斷一個點是否在多邊形內OpenCV 的 pointPolygonTest 是現(xiàn)成的方案。默認判斷檢測框中心點太粗暴人站在邊界外身體探進危險區(qū)就漏判。更穩(wěn)的做法是取檢測框底邊中心點因為人腳踩的位置最能代表“人實際站在那里”。import cv2 danger_zone np.array([[100, 200], [300, 150], [400, 400], [150, 450]], dtypenp.float32) def is_point_in_zone(point, zone): point: (x, y) 底邊中心點 zone: 多邊形頂點數(shù)組 返回 True 表示點在區(qū)域內或邊界上 result cv2.pointPolygonTest(zone, point, measureDistFalse) return result 0person 檢測框的底邊中心點結合了點特征和幾何特征比單純用中心點更符合人類對“站在某處”的直覺。如果你的檢測框特別不穩(wěn)定一幀大一幀小底邊坐標會上下抖動這時退一步用中心點和底邊中心點的折中位置犧牲一點靈敏度換穩(wěn)定性。4.3 報警觸發(fā)與去抖檢測偶爾會自動消失一幀就立刻停止報警會讓整個系統(tǒng)非常聒噪。至少連續(xù) 3 到 5 幀判定“人在區(qū)域內”才觸發(fā)報警這樣誤報會少很多。這個邏輯用一個計數(shù)器就能實現(xiàn)。同樣報警發(fā)生后人移出區(qū)域也建議連續(xù)幾幀判定安全才復位報警狀態(tài)避免邊界反復橫跳時蜂鳴器響個不停。status_buffer [] ALARM_FRAME_COUNT 5 def update_alarm_state(in_zone): in_zone: 當前幀是否在區(qū)域內 返回當前應不應該觸發(fā)報警 status_buffer.append(in_zone) if len(status_buffer) ALARM_FRAME_COUNT: status_buffer.pop(0) if all(status_buffer): return True return Falsestatus_buffer 維持一個長度為 5 的緩沖隊列全部為 True 才報警。這個參數(shù)配合推理幀率調整如果你每秒只處理 5 幀隊列全 True 相當于人已經(jīng)在區(qū)域里待了一秒每秒處理 20 幀的話就相當于 0.25 秒。拿去抖設定的幀數(shù)要匹配你的推理速度否則報警延遲會變成一大截。5. 可視化界面與部署五個容易翻車的排查點5.1 PyQt 與 OpenCV 視頻流的集成交互可視化界面最常用的組合是 PyQt 做窗口和控件OpenCV 負責取流和顯示幀。把推理循環(huán)放進界面線程會直接卡死窗口因為模型推理一次可能耗時幾十毫秒畫面就在那卡著不動。常見做法是單獨開一個 QThread 跑視頻循環(huán)然后用信號把處理好的幀發(fā)給主界面刷新。from PyQt5.QtCore import QThread, pyqtSignal import cv2 class VideoThread(QThread): frame_signal pyqtSignal(object) def __init__(self, model, source0): super().__init__() self.model model self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running: ret, frame cap.read() if not ret: break results self.model(frame, conf0.45, imgsz640) # 這里把檢測框和圍欄繪制到 frame 上 annotated draw_danger_zone(frame, danger_zone) self.frame_signal.emit(annotated) cap.release()信號傳圖像對象比傳路徑更直接刷新頻率上限取決于推理速度。如果你的界面顯示跟不上可以在 signal.emit 前加一個簡單的幀間隔控制用 time.sleep 限流。5.2 部署排查一模型路徑與權重不匹配現(xiàn)象訓練完跑 demo.py程序報錯 “cannot find model”或者加載后檢測結果全為空。原因代碼里默認加載 runs/detect/train/weights/best.pt但你把項目目錄移動過或者復制了項目給同學絕對路徑已經(jīng)失效。另一個關聯(lián)原因是 torch.load 加載到 CPU 機器上但權重是在 GPU 上訓練的也會出現(xiàn)加載報錯。解決先確認 best.pt 存在并且用絕對路徑試一次加載python -c from ultralytics import YOLO; m YOLO(/abs/path/best.pt); print(ok)輸出 ok 再排查界面代碼里的相對路徑寫法。5.3 部署排查二視頻流卡死和內存增長現(xiàn)象程序運行幾分鐘后畫面開始卡頓最后直接無響應內存占用一路飆升。原因預覽窗口在持續(xù)累積新的 QPixmap 對象舊的沒有被釋放?;蛘呤峭评砭€程把結果發(fā)到主線程的速度超過界面刷新速度積壓的信號把事件隊列堆滿了。第二個原因是做電子圍欄最容易踩的坑視頻幀處理完后沒能正確釋放 OpenCV 的 Mat 對象。解決不要在 while 循環(huán)里反復設置整個 QGraphicsScene 的內容改用 QLabel.setPixmap 直接刷新并確保 frame_signal 連接的是同一個刷新槽函數(shù)。最簡單的驗證方法是打印線程 id看推理線程和 UI 線程是不是同一個同一個就是線程模型寫錯了。5.4 部署排查三內置攝像頭編號不對現(xiàn)象界面能打開但預覽畫面是黑屏或報 “Unable to open camera”。原因cv2.VideoCapture(0) 的 0 不一定是你的 USB 攝像頭筆記本自帶攝像頭和 USB 外接攝像頭的編號在不同驅動下順序不同。還有一類原因是攝像頭被其他軟件占用OpenCV 拿不到句柄。解決先單獨測一次python -c import cv2; cap cv2.VideoCapture(0); print(cap.isOpened())如果返回 False換 VideoCapture(1) 再試。不要直接在項目代碼里面試編號那個過程很折磨人。5.5 部署排查四CPU 機器推理速度過慢現(xiàn)象畫面能跑起來但一幀要兩秒多報警延遲完全不可接受。原因在純 CPU 機器上跑了默認的 yolov8s 模型且沒有限制輸入分辨率。640 輸入圖在 CPU 上推理一次大約幾百毫秒性能較差的 CPU 到一秒以上很正常。解決把模型換成 yolov8n把推理尺寸從 640 降到 480再把推理線程的輸出限在一個合理幀率例如每 0.2 秒最多推理一幀。最后的畫面流暢度由顯示線程負責報警判定基于推理結果所以降低推理頻率不會讓報警邏輯出錯只是讓邊界判斷更新變慢而已。6. 進階導出 ONNX 做加速附帶一個幀率優(yōu)化思路訓練好的 best.pt 在推理腳本里跑沒問題但如果你想把核心檢測和圍欄邏輯移到板端比如 RK3588就得先導出 ONNX。導出指令很簡單yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640 simplifyTruesimplifyTrue 會做計算圖融合減少部分算子方便后續(xù)轉換 RKNN 格式。導出后用 onnxruntime 驗證一遍輸入輸出尺寸確保和 .pt 權重結果一致常見的坑發(fā)生在動態(tài)尺寸問題上。導出時沒有指定固定尺寸推理時輸入維度是動態(tài)的有些部署框架不支持動態(tài)輸入建議 export 時加上 dynamicFalse 定死尺寸。如果你只在本地做幀率優(yōu)化一個成本很低的做法是分開縮放把 640 輸入圖的檢測結果映射回預覽畫面時預覽畫面用 960x540 顯示。這樣推理開銷降下來視覺上清晰度也沒有明顯下降。再配合跳幀推理每隔一幀跑一次模型中間一幀沿用上一幀的檢測結果報警抖動的感知幾乎沒有差別。跑完這些以后你會在 best.pt 的 val 分數(shù)、導出模型的一致性和報警延遲上花最多的調試時間。我曾經(jīng)在一個項目里為了把報警延遲從一秒壓到半秒最后發(fā)現(xiàn)瓶頸在攝像頭自身的 30 幀緩沖而不是模型推理。先測量瓶頸再決定優(yōu)化位置這是我從這個方案里學到的最大教訓希望幫到你。本文還有配套的精品資源點擊獲取