:從數(shù)據(jù)集劃分到PyQt攝像頭界面全流程)
簡(jiǎn)介本資源面向計(jì)算機(jī)視覺入門與進(jìn)階開發(fā)者提供一套基于Python與PyTorch的積水圖像分割檢測(cè)完整方案核心采用YOLOv11模型并集成PyQt可視化界面與攝像頭實(shí)時(shí)識(shí)別功能可用于城市排水監(jiān)測(cè)、戶外安全預(yù)警等場(chǎng)景的二次開發(fā)與課程實(shí)踐。壓縮包共1108個(gè)文件約424.92MB其中442張jpg為積水圖像樣本429個(gè)txt與214個(gè)json為標(biāo)注及配置數(shù)據(jù)另有4個(gè)pt權(quán)重文件、3個(gè)yaml模型配置、3個(gè)py腳本及png、csv等輔助文件覆蓋數(shù)據(jù)集、訓(xùn)練權(quán)重與推理代碼。使用流程清晰先運(yùn)行01劃分?jǐn)?shù)據(jù)集.py完成訓(xùn)練集與測(cè)試集切分再執(zhí)行02train.py訓(xùn)練模型最后通過03pyqt.py啟動(dòng)界面進(jìn)行攝像頭實(shí)時(shí)檢測(cè)。目前已有98人學(xué)習(xí)下載適合希望快速跑通分割訓(xùn)練、理解YOLOv11工程結(jié)構(gòu)并獲取可復(fù)用數(shù)據(jù)集的讀者參考。1. 積水圖像分割檢測(cè)從數(shù)據(jù)集到 PyQt 界面的完整落地路徑城市內(nèi)澇預(yù)警、地下車庫進(jìn)水、隧道積水監(jiān)測(cè)這些場(chǎng)景對(duì)“實(shí)時(shí)識(shí)別積水區(qū)域”有硬需求。我拿到這個(gè)資源包時(shí)第一反應(yīng)是它把 YOLO11 的圖像分割能力直接綁到了積水檢測(cè)這個(gè)具體任務(wù)上而且給了數(shù)據(jù)集、訓(xùn)練腳本和 PyQt 攝像頭界面不是那種只丟一個(gè)模型權(quán)重的“半成品”。整個(gè)包基于 Python PyTorch核心是 YOLO11 的實(shí)例分割分支能輸出積水區(qū)域的像素級(jí)掩碼而不是只畫個(gè)框。適合誰做安防監(jiān)控二次開發(fā)的、搞智慧城市 demo 的、或者想學(xué) YOLO11 分割任務(wù)怎么從零跑通的學(xué)生。如果你手頭正好有積水場(chǎng)景的圖片或視頻流這套代碼能讓你在半天內(nèi)看到分割效果不用自己從頭搭訓(xùn)練管線。2. YOLO11 分割任務(wù)拆解為什么選它做積水檢測(cè)2.1 從檢測(cè)框到像素掩碼分割頭到底在算什么YOLO11 的檢測(cè)分支輸出的是每個(gè)目標(biāo)的邊界框和類別概率而分割分支額外掛了一個(gè)原型掩碼生成網(wǎng)絡(luò)和一組掩碼系數(shù)。推理時(shí)每個(gè)檢測(cè)框會(huì)對(duì)應(yīng)一組系數(shù)與原型掩碼做線性組合再裁剪到框內(nèi)就得到該目標(biāo)的像素級(jí)掩碼。積水檢測(cè)為什么需要這個(gè)因?yàn)榉e水區(qū)域形狀極不規(guī)則邊界框會(huì)包含大量非積水像素比如路面、標(biāo)線、車輛底盤。用分割掩碼才能準(zhǔn)確算出積水面積、輪廓甚至做水位估算。YOLO11 相比前代在分割頭里引入了更輕量的原型生成模塊在保持掩碼質(zhì)量的同時(shí)把參數(shù)量壓下來這對(duì)邊緣設(shè)備部署很關(guān)鍵。2.2 數(shù)據(jù)集劃分腳本01劃分?jǐn)?shù)據(jù)集.py 里藏著的三個(gè)參數(shù)資源包里的01劃分?jǐn)?shù)據(jù)集.py不是簡(jiǎn)單隨機(jī)切分。我拆開看它默認(rèn)按 8:1:1 分訓(xùn)練、驗(yàn)證、測(cè)試但實(shí)際代碼里留了train_ratio、val_ratio兩個(gè)變量測(cè)試集比例是自動(dòng)補(bǔ)全的。更關(guān)鍵的是它做了分層抽樣——如果數(shù)據(jù)集中有“無積水”的負(fù)樣本腳本會(huì)保證每個(gè)子集里正負(fù)樣本比例一致。這個(gè)細(xì)節(jié)很多開源項(xiàng)目會(huì)忽略導(dǎo)致驗(yàn)證集里全是負(fù)樣本mAP 虛高。運(yùn)行前你需要確認(rèn)數(shù)據(jù)集目錄結(jié)構(gòu)是images/和labels/平行且標(biāo)簽是 YOLO 分割格式的 txt每行類別 x1 y1 x2 y2 ...歸一化坐標(biāo)。腳本會(huì)生成train.txt、val.txt、test.txt三個(gè)索引文件路徑寫的是絕對(duì)路徑換機(jī)器跑要改data_root。# 01劃分?jǐn)?shù)據(jù)集.py 核心邏輯節(jié)選 import os, random from sklearn.model_selection import train_test_split data_root rD:\water_dataset # 改成你的數(shù)據(jù)集根目錄 img_dir os.path.join(data_root, images) label_dir os.path.join(data_root, labels) # 收集所有圖片文件名不帶擴(kuò)展名 names [os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)] # 分層依據(jù)標(biāo)簽文件是否為空空表示無積水負(fù)樣本 labels [1 if os.path.getsize(os.path.join(label_dir, n .txt)) 0 else 0 for n in names] train_names, test_names, train_labels, test_labels train_test_split( names, labels, test_size0.2, stratifylabels, random_state42 ) val_names, test_names train_test_split( test_names, test_size0.5, stratifytest_labels, random_state42 ) # 后續(xù)寫入 train.txt / val.txt / test.txt每行一個(gè)絕對(duì)路徑邏輯說明stratifylabels保證正負(fù)樣本在切分后比例一致。random_state42固定隨機(jī)種子方便復(fù)現(xiàn)。參數(shù)怎么改如果數(shù)據(jù)集很小少于 500 張把test_size調(diào)到 0.1驗(yàn)證集從訓(xùn)練集里再切避免測(cè)試集樣本太少導(dǎo)致指標(biāo)波動(dòng)大。注意標(biāo)簽文件必須和圖片同名同目錄層級(jí)否則腳本會(huì)報(bào)FileNotFoundError。2.3 訓(xùn)練腳本 02train.py顯存不夠時(shí)先動(dòng)這三個(gè)地方02train.py用的是 Ultralytics 風(fēng)格的訓(xùn)練接口但資源包里把配置寫成了 argparse 參數(shù)。默認(rèn)batch16、imgsz640、epochs100。如果你只有 6GB 顯存直接跑會(huì) OOM。我一般按這個(gè)順序降先把batch降到 8再把imgsz降到 512最后才動(dòng)workers。workers是數(shù)據(jù)加載線程數(shù)Windows 下設(shè)太高反而慢建議 4 或 8。學(xué)習(xí)率默認(rèn)lr00.01對(duì)積水分割這種小數(shù)據(jù)集我習(xí)慣降到 0.001 并加cos_lrTrue讓學(xué)習(xí)率余弦衰減收斂更穩(wěn)。還有一個(gè)隱藏坑02train.py里model參數(shù)默認(rèn)加載yolo11n-seg.pt如果你要更高精度換成yolo11s-seg.pt或yolo11m-seg.pt但顯存占用會(huì)翻倍。# 訓(xùn)練命令示例在 02train.py 同級(jí)目錄下運(yùn)行 python 02train.py --data data.yaml --weights yolo11n-seg.pt --epochs 100 --batch 8 --imgsz 512 --lr0 0.001 --cos_lr --workers 4參數(shù)說明--data指向數(shù)據(jù)集配置文件里面寫train: train.txt、val: val.txt、nc: 1、names: [water]。--weights是預(yù)訓(xùn)練權(quán)重不寫則從頭訓(xùn)練但小數(shù)據(jù)集從頭訓(xùn)基本沒效果。--cos_lr開啟余弦退火。訓(xùn)練日志和權(quán)重默認(rèn)存在runs/segment/train/下results.csv記錄每輪 loss 和 mAPevents.out.tfevents...是 TensorBoard 日志用tensorboard --logdir runs可以看曲線。3. PyQt 界面與攝像頭識(shí)別03pyqt.py 的線程模型和推理封裝3.1 為什么不能直接在 Qt 主線程里跑模型推理03pyqt.py用 PyQt5 搭界面左邊是攝像頭畫面右邊是分割結(jié)果和統(tǒng)計(jì)信息。如果你把model.predict()直接寫在按鈕回調(diào)里界面會(huì)卡死——因?yàn)橥评硎峭阶枞闹骶€程一卡Qt 事件循環(huán)就停轉(zhuǎn)窗口直接“未響應(yīng)”。資源包里用了一個(gè)QThread子類InferenceThread把攝像頭讀取和模型推理都放在子線程通過信號(hào)frame_ready把帶掩碼的幀發(fā)回主線程刷新 QLabel。這個(gè)設(shè)計(jì)是對(duì)的但有個(gè)細(xì)節(jié)子線程里不能直接操作 UI 組件必須用信號(hào)槽。我見過有人圖省事在子線程里self.label.setPixmap()結(jié)果隨機(jī)崩潰血淚經(jīng)驗(yàn)。# 03pyqt.py 線程類核心結(jié)構(gòu) from PyQt5.QtCore import QThread, pyqtSignal import cv2 from ultralytics import YOLO class InferenceThread(QThread): frame_ready pyqtSignal(object) # 發(fā)送帶掩碼的幀 def __init__(self, model_path, cam_id0): super().__init__() self.model YOLO(model_path) self.cap cv2.VideoCapture(cam_id) self.running True def run(self): while self.running: ret, frame self.cap.read() if not ret: continue results self.model.predict(frame, imgsz640, conf0.25, verboseFalse) annotated results[0].plot() # 直接畫掩碼和框 self.frame_ready.emit(annotated) def stop(self): self.running False self.cap.release() self.wait()邏輯說明conf0.25是置信度閾值積水檢測(cè)建議調(diào)到 0.3 以上減少路面反光誤檢。results[0].plot()返回的是 BGR numpy 數(shù)組可以直接轉(zhuǎn) QImage。注意verboseFalse關(guān)掉每幀打印否則控制臺(tái)刷屏拖慢速度。cam_id0是默認(rèn)攝像頭如果你用 USB 攝像頭且電腦有內(nèi)置攝像頭可能要改成 1 或 2。3.2 攝像頭識(shí)別延遲高先查這三個(gè)環(huán)節(jié)實(shí)時(shí)性是這個(gè)項(xiàng)目的賣點(diǎn)但實(shí)際跑起來有人反饋“畫面延遲兩秒”。我排查下來通常是三個(gè)原因第一imgsz設(shè)了 640 但攝像頭分辨率是 1080p每幀都要縮放改成imgsz320能快一倍精度掉得不多第二model.predict默認(rèn)streamFalse每幀都重新初始化推理引擎改成streamTrue用生成器模式但要注意stream模式下results是迭代器得用for r in results取第三Qt 的QLabel刷新用了setPixmap但沒調(diào)repaint()畫面堆積。我一般會(huì)在frame_ready槽函數(shù)里先self.label.clear()再設(shè)新圖強(qiáng)制釋放舊資源。3.3 分割結(jié)果后處理從掩碼到積水面積估算results[0].masks里存的是原始掩碼數(shù)據(jù)results[0].masks.data是 tensor形狀[n, h, w]每個(gè)通道對(duì)應(yīng)一個(gè)檢測(cè)實(shí)例。如果你想算積水面積占比可以這樣先取masks.data[0].cpu().numpy()二值化后統(tǒng)計(jì)非零像素?cái)?shù)除以總像素?cái)?shù)。但注意這個(gè)掩碼是模型輸入尺寸比如 640x640下的要映射回原始幀尺寸才能算真實(shí)面積。資源包里沒做這個(gè)映射我補(bǔ)了一段import numpy as np def calc_water_ratio(result, orig_shape): if result.masks is None: return 0.0 mask result.masks.data[0].cpu().numpy() # 取第一個(gè)實(shí)例 mask cv2.resize(mask, (orig_shape[1], orig_shape[0]), interpolationcv2.INTER_NEAREST) return np.count_nonzero(mask) / (orig_shape[0] * orig_shape[1])參數(shù)說明orig_shape是原始幀的(height, width)。INTER_NEAREST保持二值性別用線性插值。這個(gè)比例可以顯示在 PyQt 界面上超過閾值就彈預(yù)警。4. 避坑與排查跑通這套代碼前必須知道的五件事4.1 現(xiàn)象運(yùn)行 02train.py 報(bào) “No labels found”原因data.yaml里的train和val路徑寫的是相對(duì)路徑但腳本工作目錄不對(duì)?;蛘?1劃分?jǐn)?shù)據(jù)集.py生成的 txt 里是絕對(duì)路徑換機(jī)器后路徑失效。解決打開train.txt檢查第一行路徑是否存在統(tǒng)一改成相對(duì)于data.yaml的路徑比如images/xxx.jpg并在data.yaml里寫path: D:/water_dataset。4.2 現(xiàn)象PyQt 界面能開但攝像頭畫面全黑原因OpenCV 的VideoCapture在 Windows 下默認(rèn)用 MSMF 后端某些攝像頭不兼容。解決在cv2.VideoCapture(cam_id)后面加cv2.CAP_DSHOW即cv2.VideoCapture(cam_id, cv2.CAP_DSHOW)。如果還不行換cv2.CAP_MSMF試試。Linux 下一般不用改。4.3 現(xiàn)象訓(xùn)練 loss 降到 0.1 以下但驗(yàn)證集 mAP 只有 0.2原因過擬合。積水?dāng)?shù)據(jù)集如果背景單一比如全是灰色路面模型會(huì)記住背景而不是積水特征。解決在02train.py里加數(shù)據(jù)增強(qiáng)參數(shù)--degrees 10 --translate 0.1 --scale 0.5 --fliplr 0.5并開啟--mosaic 1.0。另外把dropout調(diào)到 0.1YOLO11 分割頭支持。4.4 現(xiàn)象03pyqt.py 運(yùn)行報(bào) “No module named ‘PyQt5.sip’”原因PyQt5 安裝不完整或者 Python 版本和 PyQt5 輪子不匹配。解決先pip uninstall PyQt5 PyQt5-sip再pip install PyQt55.15.9 PyQt5-sip12.12.2。別用pip install pyqt5-tools那個(gè)會(huì)帶一堆沒用的依賴。4.5 現(xiàn)象推理結(jié)果里積水掩碼邊緣鋸齒嚴(yán)重原因模型輸出掩碼分辨率低plot()直接放大導(dǎo)致鋸齒。解決在predict里加retina_masksTrue讓掩碼在原圖分辨率上生成代價(jià)是推理慢 10% 左右?;蛘吆筇幚頃r(shí)用cv2.morphologyEx做開運(yùn)算平滑邊緣。5. 進(jìn)階技巧用 TensorBoard 盯住分割指標(biāo)別只看 loss訓(xùn)練跑起來后results.csv里有一列metrics/mAP50-95(M)這是分割掩碼的平均精度比檢測(cè)框的 mAP 更能反映積水區(qū)域貼合度。我習(xí)慣在訓(xùn)練中途用 TensorBoard 看train/seg_loss和val/seg_loss的 divergence如果驗(yàn)證 loss 連續(xù) 10 輪不降就手動(dòng)停掉別等 100 輪跑完。另外events.out.tfevents...文件可以直接拖進(jìn) TensorBoard 的--logdir目錄不用改代碼。還有一個(gè)技巧把03pyqt.py里的model.predict換成model.track加上persistTrue就能對(duì)視頻流里的積水區(qū)域做 ID 跟蹤適合做積水?dāng)U散趨勢(shì)分析。最后說個(gè)我自己的習(xí)慣每次改完data.yaml或訓(xùn)練參數(shù)先跑python 02train.py --epochs 1做冒煙測(cè)試確認(rèn)數(shù)據(jù)加載和模型前向不報(bào)錯(cuò)再開長(zhǎng)訓(xùn)練。從那以后我每次動(dòng)數(shù)據(jù)集劃分腳本都強(qiáng)制先跑一遍01劃分?jǐn)?shù)據(jù)集.py并檢查三個(gè) txt 的行數(shù)比例避免訓(xùn)練到一半發(fā)現(xiàn)驗(yàn)證集是空的。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取