ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLO11积水图像分割检测:从数据集划分到PyQt摄像头界面全流程

YOLO11积水图像分割检测:从数据集划分到PyQt摄像头界面全流程

简介:本资源面向计算机视觉入门与进阶开发者,提供一套基于Python与PyTorch的积水图像分割检测完整方案,核心采用YOLOv11模型,并集成PyQt可视化界面与摄像头实时识别功能,可用于城市排水监测、户外安全预警等场景的二次开发与课程实践。压缩包共1108个文件,约424.92MB,其中442张jpg为积水图像样本,429个txt与214个json为标注及配置数据,另有4个pt权重文件、3个yaml模型配置、3个py脚本及png、csv等辅助文件,覆盖数据集、训练权重与推理代码。使用流程清晰:先运行01划分数据集.py完成训练集与测试集切分,再执行02train.py训练模型,最后通过03pyqt.py启动界面进行摄像头实时检测。目前已有98人学习下载,适合希望快速跑通分割训练、理解YOLOv11工程结构并获取可复用数据集的读者参考。

1. 积水图像分割检测:从数据集到 PyQt 界面的完整落地路径

城市内涝预警、地下车库进水、隧道积水监测,这些场景对“实时识别积水区域”有硬需求。我拿到这个资源包时,第一反应是:它把 YOLO11 的图像分割能力直接绑到了积水检测这个具体任务上,而且给了数据集、训练脚本和 PyQt 摄像头界面,不是那种只丢一个模型权重的“半成品”。整个包基于 Python + PyTorch,核心是 YOLO11 的实例分割分支,能输出积水区域的像素级掩码,而不是只画个框。适合谁?做安防监控二次开发的、搞智慧城市 demo 的、或者想学 YOLO11 分割任务怎么从零跑通的学生。如果你手头正好有积水场景的图片或视频流,这套代码能让你在半天内看到分割效果,不用自己从头搭训练管线。

2. YOLO11 分割任务拆解:为什么选它做积水检测

2.1 从检测框到像素掩码:分割头到底在算什么

YOLO11 的检测分支输出的是每个目标的边界框和类别概率,而分割分支额外挂了一个原型掩码生成网络和一组掩码系数。推理时,每个检测框会对应一组系数,与原型掩码做线性组合,再裁剪到框内,就得到该目标的像素级掩码。积水检测为什么需要这个?因为积水区域形状极不规则,边界框会包含大量非积水像素,比如路面、标线、车辆底盘。用分割掩码才能准确算出积水面积、轮廓,甚至做水位估算。YOLO11 相比前代在分割头里引入了更轻量的原型生成模块,在保持掩码质量的同时把参数量压下来,这对边缘设备部署很关键。

2.2 数据集划分脚本:01划分数据集.py 里藏着的三个参数

资源包里的01划分数据集.py不是简单随机切分。我拆开看,它默认按 8:1:1 分训练、验证、测试,但实际代码里留了train_ratio、val_ratio两个变量,测试集比例是自动补全的。更关键的是,它做了分层抽样——如果数据集中有“无积水”的负样本,脚本会保证每个子集里正负样本比例一致。这个细节很多开源项目会忽略,导致验证集里全是负样本,mAP 虚高。运行前你需要确认数据集目录结构是images/和labels/平行,且标签是 YOLO 分割格式的 txt,每行类别 x1 y1 x2 y2 ...归一化坐标。脚本会生成train.txt、val.txt、test.txt三个索引文件,路径写的是绝对路径,换机器跑要改data_root。

# 01划分数据集.py 核心逻辑节选 import os, random from sklearn.model_selection import train_test_split data_root = r"D:\water_dataset" # 改成你的数据集根目录 img_dir = os.path.join(data_root, "images") label_dir = os.path.join(data_root, "labels") # 收集所有图片文件名(不带扩展名) names = [os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")] # 分层依据:标签文件是否为空(空表示无积水负样本) 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_size=0.2, stratify=labels, random_state=42 ) val_names, test_names = train_test_split( test_names, test_size=0.5, stratify=test_labels, random_state=42 ) # 后续写入 train.txt / val.txt / test.txt,每行一个绝对路径

逻辑说明:stratify=labels保证正负样本在切分后比例一致。random_state=42固定随机种子,方便复现。参数怎么改?如果数据集很小(少于 500 张),把test_size调到 0.1,验证集从训练集里再切,避免测试集样本太少导致指标波动大。注意:标签文件必须和图片同名同目录层级,否则脚本会报FileNotFoundError。

2.3 训练脚本 02train.py:显存不够时先动这三个地方

02train.py用的是 Ultralytics 风格的训练接口,但资源包里把配置写成了 argparse 参数。默认batch=16、imgsz=640、epochs=100。如果你只有 6GB 显存,直接跑会 OOM。我一般按这个顺序降:先把batch降到 8,再把imgsz降到 512,最后才动workers。workers是数据加载线程数,Windows 下设太高反而慢,建议 4 或 8。学习率默认lr0=0.01,对积水分割这种小数据集,我习惯降到 0.001 并加cos_lr=True,让学习率余弦衰减,收敛更稳。还有一个隐藏坑:02train.py里model参数默认加载yolo11n-seg.pt,如果你要更高精度,换成yolo11s-seg.pt或yolo11m-seg.pt,但显存占用会翻倍。

# 训练命令示例(在 02train.py 同级目录下运行) python 02train.py --data data.yaml --weights yolo11n-seg.pt --epochs 100 --batch 8 --imgsz 512 --lr0 0.001 --cos_lr --workers 4

参数说明:--data指向数据集配置文件,里面写train: train.txt、val: val.txt、nc: 1、names: ['water']。--weights是预训练权重,不写则从头训练,但小数据集从头训基本没效果。--cos_lr开启余弦退火。训练日志和权重默认存在runs/segment/train/下,results.csv记录每轮 loss 和 mAP,events.out.tfevents...是 TensorBoard 日志,用tensorboard --logdir runs可以看曲线。

3. PyQt 界面与摄像头识别:03pyqt.py 的线程模型和推理封装

3.1 为什么不能直接在 Qt 主线程里跑模型推理

03pyqt.py用 PyQt5 搭界面,左边是摄像头画面,右边是分割结果和统计信息。如果你把model.predict()直接写在按钮回调里,界面会卡死——因为推理是同步阻塞的,主线程一卡,Qt 事件循环就停转,窗口直接“未响应”。资源包里用了一个QThread子类InferenceThread,把摄像头读取和模型推理都放在子线程,通过信号frame_ready把带掩码的帧发回主线程刷新 QLabel。这个设计是对的,但有个细节:子线程里不能直接操作 UI 组件,必须用信号槽。我见过有人图省事在子线程里self.label.setPixmap(),结果随机崩溃,血泪经验。

# 03pyqt.py 线程类核心结构 from PyQt5.QtCore import QThread, pyqtSignal import cv2 from ultralytics import YOLO class InferenceThread(QThread): frame_ready = pyqtSignal(object) # 发送带掩码的帧 def __init__(self, model_path, cam_id=0): 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, imgsz=640, conf=0.25, verbose=False) annotated = results[0].plot() # 直接画掩码和框 self.frame_ready.emit(annotated) def stop(self): self.running = False self.cap.release() self.wait()

逻辑说明:conf=0.25是置信度阈值,积水检测建议调到 0.3 以上,减少路面反光误检。results[0].plot()返回的是 BGR numpy 数组,可以直接转 QImage。注意verbose=False关掉每帧打印,否则控制台刷屏拖慢速度。cam_id=0是默认摄像头,如果你用 USB 摄像头且电脑有内置摄像头,可能要改成 1 或 2。

3.2 摄像头识别延迟高?先查这三个环节

实时性是这个项目的卖点,但实际跑起来有人反馈“画面延迟两秒”。我排查下来通常是三个原因:第一,imgsz设了 640 但摄像头分辨率是 1080p,每帧都要缩放,改成imgsz=320能快一倍,精度掉得不多;第二,model.predict默认stream=False,每帧都重新初始化推理引擎,改成stream=True用生成器模式,但要注意stream模式下results是迭代器,得用for r in results取;第三,Qt 的QLabel刷新用了setPixmap但没调repaint(),画面堆积。我一般会在frame_ready槽函数里先self.label.clear()再设新图,强制释放旧资源。

3.3 分割结果后处理:从掩码到积水面积估算

results[0].masks里存的是原始掩码数据,results[0].masks.data是 tensor,形状[n, h, w],每个通道对应一个检测实例。如果你想算积水面积占比,可以这样:先取masks.data[0].cpu().numpy(),二值化后统计非零像素数,除以总像素数。但注意,这个掩码是模型输入尺寸(比如 640x640)下的,要映射回原始帧尺寸才能算真实面积。资源包里没做这个映射,我补了一段:

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() # 取第一个实例 mask = cv2.resize(mask, (orig_shape[1], orig_shape[0]), interpolation=cv2.INTER_NEAREST) return np.count_nonzero(mask) / (orig_shape[0] * orig_shape[1])

参数说明:orig_shape是原始帧的(height, width)。INTER_NEAREST保持二值性,别用线性插值。这个比例可以显示在 PyQt 界面上,超过阈值就弹预警。

4. 避坑与排查:跑通这套代码前必须知道的五件事

4.1 现象:运行 02train.py 报 “No labels found”

原因:data.yaml里的train和val路径写的是相对路径,但脚本工作目录不对。或者01划分数据集.py生成的 txt 里是绝对路径,换机器后路径失效。解决:打开train.txt检查第一行路径是否存在,统一改成相对于data.yaml的路径,比如images/xxx.jpg,并在data.yaml里写path: D:/water_dataset。

4.2 现象:PyQt 界面能开,但摄像头画面全黑

原因:OpenCV 的VideoCapture在 Windows 下默认用 MSMF 后端,某些摄像头不兼容。解决:在cv2.VideoCapture(cam_id)后面加cv2.CAP_DSHOW,即cv2.VideoCapture(cam_id, cv2.CAP_DSHOW)。如果还不行,换cv2.CAP_MSMF试试。Linux 下一般不用改。

4.3 现象:训练 loss 降到 0.1 以下但验证集 mAP 只有 0.2

原因:过拟合。积水数据集如果背景单一(比如全是灰色路面),模型会记住背景而不是积水特征。解决:在02train.py里加数据增强参数--degrees 10 --translate 0.1 --scale 0.5 --fliplr 0.5,并开启--mosaic 1.0。另外把dropout调到 0.1(YOLO11 分割头支持)。

4.4 现象:03pyqt.py 运行报 “No module named ‘PyQt5.sip’”

原因:PyQt5 安装不完整,或者 Python 版本和 PyQt5 轮子不匹配。解决:先pip uninstall PyQt5 PyQt5-sip,再pip install PyQt5==5.15.9 PyQt5-sip==12.12.2。别用pip install pyqt5-tools,那个会带一堆没用的依赖。

4.5 现象:推理结果里积水掩码边缘锯齿严重

原因:模型输出掩码分辨率低,plot()直接放大导致锯齿。解决:在predict里加retina_masks=True,让掩码在原图分辨率上生成,代价是推理慢 10% 左右。或者后处理时用cv2.morphologyEx做开运算平滑边缘。

5. 进阶技巧:用 TensorBoard 盯住分割指标,别只看 loss

训练跑起来后,results.csv里有一列metrics/mAP50-95(M),这是分割掩码的平均精度,比检测框的 mAP 更能反映积水区域贴合度。我习惯在训练中途用 TensorBoard 看train/seg_loss和val/seg_loss的 divergence,如果验证 loss 连续 10 轮不降,就手动停掉,别等 100 轮跑完。另外,events.out.tfevents...文件可以直接拖进 TensorBoard 的--logdir目录,不用改代码。还有一个技巧:把03pyqt.py里的model.predict换成model.track,加上persist=True,就能对视频流里的积水区域做 ID 跟踪,适合做积水扩散趋势分析。最后说个我自己的习惯:每次改完data.yaml或训练参数,先跑python 02train.py --epochs 1做冒烟测试,确认数据加载和模型前向不报错,再开长训练。从那以后我每次动数据集划分脚本,都强制先跑一遍01划分数据集.py并检查三个 txt 的行数比例,避免训练到一半发现验证集是空的。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进