去年接了一个智慧工地视觉项目客户提的需求很直接指定两三个点位摄像头对着施工区白天黑夜只要起尘就得识别出来最好能把尘土的轮廓抠出来别只给一个框。当时第一反应就是上实例分割模型则锁定了刚刚出了正式版的 YOLOv11。这篇就围绕这套“基于 YOLOv11的施工尘土实例分割系统”把数据整理、标注格式、训练调参、推理部署以及踩坑记录全流程拆开聊一遍。项目核心关键词是深度学习、YOLOv11、实例分割、数据集和智慧工地适合手里有工地监控项目、需要做扬尘识别的同学参考也适合刚入门想做分割实战的人照着手册跑一遍。1. 先搞清楚这件事的本质不是“检测框”而是“灰尘轮廓”1.1 扬尘管控在智慧工地里的位置先说背景。工地的扬尘噪音监测通常分为两套体系一套是空气质量在线监测靠传感器测 PM2.5、PM10、TSP另外一套就是视频巡查靠人工盯屏幕或者事后回放抓违规。传统方案最大的问题在“发现滞后”——传感器只能告诉你这片区域浓度超了却不知道是哪台挖掘机、哪辆渣土车在什么位置引起的人工巡查又根本盯不住多路画面。智慧工地这两年强调“闭环管理”核心逻辑就是发现污染行为 → 定位源头 → 联动降尘设备 → 记录留痕。这里需要的不是“有没有灰”这个布尔值而是“灰在哪里、范围多大、从哪儿来的”所以要做分割要把尘土的地盘在图像里精确地圈出来。另外别忽略一个痛点工地尘土本身是半透明的、颜色和地面土壤高度接近传统目标检测框住一片区域其实很不准。框大了会把正常的作业机械、堆料甚至工人框进去误报率直接劝退客户框小了又会漏掉弥散在外围的薄尘。实例分割能够输出像素级掩码配合面积计算、位置判断可以做很多检测框做不到的事情比如判断尘土是否越过围挡边界、计算扬尘覆盖面积占画面比例、判别是车辆作业扬尘还是风吹积尘引起的二次起尘。这些细化能力才是甲方愿意买单的核心。1.2 实例分割与目标检测、语义分割的差异工程上最容易混淆的是目标检测、语义分割、实例分割三者。目标检测给的是“框”语义分割给的是“所有像素都打上类别标签但不区分类内个体”实例分割则是“既要像素级别又要区分每一个个体”。拿工地场景举例如果画面里有三辆渣土车同时在卸土目标检测只告诉你有几辆车、在什么位置语义分割把画面里所有“土”都标成一个类别三辆车的尘土连成一片也没关系实例分割则必须把三坨扬尘分开每坨都有自己的掩码和编号。为什么要区分类内个体因为后续要按“单个污染源”去做责任追溯和报警联动三辆车同时起尘和一辆车起尘处置策略完全不同报警记录也要对应到具体的触发点。实际开发里还有一个容易被忽略的点YOLOv11 的实例分割结果和检测结果之间不是二选一的关系。它本质上还是先做目标检测然后在每个候选框内部做掩码分支所以每个分割目标天然带着 bbox、置信度和掩码。这个结构带来一个工程便利——你可以在项目里同时拿到框和轮廓框做跟踪、做区域过滤掩码做面积和形态分析两者并行各干各的活。我在做抓拍快照和视频回放时就是靠这一层数据打的配合。1.3 技术选型评估为什么锁定 YOLOv11-seg这个项目选型时对比过几类方案。一类是基于Transformer的分割模型比如 Mask2Former 这类精度确实不错但推理速度在工地常见的那种 i5 工控机上有压力部署成本也高甲方预算不允许动不动上 4090。第二类是纯语义分割网络像 DeepLabV3、U-Net速度和显存满足要求但没法做实例区分业务流程上直接不满足需求。第三类是两阶段的 Mask R-CNN精度稳但速度偏慢而且在对小目标和低对比度尘土的场景里表现并不比新版的 YOLO 系列占优。最后选定 YOLOv11-seg理由有四点。第一Ultralytics 在 YOLOv8 之后已经把分割任务整合得相当成熟到了 v11 版本C3k2 模块和 C2PSA 注意力机制在特征提取上对细小的尘土边缘更友好第二推理速度在实测中能把 640×640 输入跑到 40 FPS 以上这里指在 RTX 3060 上放在工地场景绰绰有余第三模型权重和生态工具链完善训练、导出 ONNX、TensorRT 部署一条龙省去大量工程适配时间第四v11 自带多个尺寸的预训练权重从 n 到 x 应有尽有可以根据现场算力灵活选。选型阶段我也看到不少人争论“YOLOv11 到底比 v8 强多少”实话讲单看精度数字提升没有想象中那么夸张真正香的是整体的模块化能力和工程友好度。既然项目目标是快速落地而不是跑榜刷分选它性价比最高。2. 数据集是真正的重头戏工地尘土分割数据怎么来的2.1 数据采集不是拍几张照片就能用的很多朋友拿到这个项目第一个想象是“从网上下个现成的尘土数据集”但现实很骨感公开可用的工地扬尘分割数据集极少即便有场景也和你的项目点位对不上。工地之间差异极大——北方扬尘是干土颗粒南方工地更多是泥浆和湿润粉尘基坑阶段的尘土和主体阶段的尘土颜色、形态都不一样白天强光下的尘土几乎看不见傍晚逆光时尘土又会泛金光。所以最靠谱的路子是“自采 半自动标注”这也是本项目最有工作量、也最有价值的部分。采集时要注意点位覆盖的逻辑。摄像头装在围挡立杆和塔吊上的视角和人工手持相机拍出来的画面差异很大后者视角低、距离近前者是高远俯视尘土通常出现在画面中部或下半部。数据集必须覆盖这样的俯视角度。时间维度上尽量分早、中、晚三个时段采样光线角度、阴影长度都不相同至少凑够一个月的素材才能把晴天、阴天、雨后地面潮湿、夜间灯光这些情况都纳入。天气维度上大风天气里尘土会被拉成明显的羽流状这类形态对实例分割挑战最大需要专门多采集一些。数据量本身不是越多越好。实测下来一个点位 15002500 张有效图像就能训出可以商用的效果关键在于多样性和标注质量而不是单纯堆张数。图像分辨率建议至少 1080p后续训练统一缩放到 640 或 1280高分辨率原图能保住尘土边缘的细节缩放后不至于糊成一片。2.2 标注格式YOLO 分割不能用矩形框思维这是全项目最容易翻车的地方。YOLOv11 的实例分割标注不是 COCO 那种 JSON也不需要 RLE而是最简单的 txt 文件每一行对应一个实例格式为class_id x1 y1 x2 y2 ... xn yn后面的点是多边形顶点坐标除以图片宽高做了归一化。也就是说每张图的标注结果是同名的 txt放在labels目录下。标注尘土和标注车、人不一样。尘土边缘是渐变的一个肉眼可见的“尘团”边界往往要放大到 200% 才能判断哪里是边缘。我们定了一套自己的标注规范只标视觉上明显成团、有浓度的尘土淡淡的、像雾一样的背景薄尘不标因为后期训练容易把模型带偏被建筑物遮挡了一半的尘团只标可见部分如果两团尘土在图像里连在一起但明显来自不同车也要分成两个实例。这套规范保证标注员工不凭感觉画减少标注不一致。工具上我试过 LabelMe、X-AnyLabeling、Roboflow 三家。LabelMe 老牌但批量处理能力弱Roboflow 在线标注方便但数据上传下载速度和隐私总有顾虑最后主力用的是 X-AnyLabeling因为它内置了 YOLOv8 的预训练模型可以做半自动标注先用模型在画面上抽框再手动调整多边形顶点速度能提升 3 到 5 倍。标注完成后直接导出 YOLO 格式一个按钮搞定。2.3 原始数据集与增强别让模型只认识“晴天下午”做数据增强之前要先搞清楚工地场景的典型干扰。我们项目里模型一开始在测试集上掉点是因为训练数据里全是白天晴天真实场景里出现了三种意外情况傍晚逆光导致尘土呈金色且对比度低夜间路灯下尘土在暖光里浑浊不清下雨后地面反射灯光尘土和地面反光混在一起。这些都要靠针对性的数据增强或者补充实拍解决。YOLOv11 训练时我启用了这几个增强参数效果立竿见影hsv_h色调偏移轻微调节模拟不同色温scale和translate模拟不同距离和摄像头抖动fliplr水平翻转工地摄像头左右朝向都有mosaic和mixup增加背景多样性。但有两个参数必须保持默认或关掉degrees旋转建议不调太大因为俯视画面的地平线基本不会倾斜shear剪切变换也不要给太多会破坏尘土原本的形态比例。另外我还做了一个分级补充把已有的分割掩码通过随机仿射变换模仿风的方向让尘团的形态从“一团”变成“一条”专门补大风场景。做法很简单用 OpenCV 对标注掩码做透视变形生成新图像和新的多边形标注。这里要提醒一句对掩码做变换必须和图像用同一个变换矩阵否则标注就废了我习惯用cv2.warpPerspective时把变换矩阵存下来统一应用到两张图上。2.4 数据集目录组织与验证集划分YOLOv11 的目录结构不复杂但第一次上手经常因为路径写错浪费时间这里给出一个可以直接复制的标准布局dust_dataset/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml里写三类信息训练验证路径、类别编号、类别名。注意类别名只能是英文字母和下划线中文名会在后续导出时出乱码。我们项目里只定义了一个类别dust如果你想细分可以拆成load_dust装卸起尘、vehicle_dust车辆行驶扬尘、wind_dust风吹扬尘但类目越多标注成本越高且类间形态重叠大实际训练容易互相混淆单类起步是最稳的。验证集划分建议按“时间段”而不是“随机”来切。我们的坑是随机划分后训练集和验证集里往往包含同一段视频的连续帧模型近乎“开卷考试”验证分数虚高。后来改成按视频片段切分同一天同一机位的素材整体放进训练集或者验证集验证结果才真实反映模型在陌生场景上的表现。划分比例我用了 85% 训练、15% 验证数据量不大时这个比例比 8:2 更稳妥。3. 环境搭建与模型训练从零把 YOLOv11seg 跑起来3.1 环境配置Conda PyTorch Ultralytics 一条龙环境配置完全可以照着官方文档走但有几个版本坑我在项目里替大家踩平了。Python 建议 3.10PyTorch 用 2.1 或 2.2CUDA 11.8 或 12.1Ultralytics 包版本必须 ≥8.3.0 才有完整的 YOLOv11 支持。核心安装命令conda create -n yolov11 python3.10 -y conda activate yolov11 pip install torch2.2.0 torchvision0.17.0 --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics # 检查 YOLOv11 是否可用 python -c from ultralytics import YOLO; m YOLO(yolo11s-seg.pt); print(m.model.__class__.__name__)特别提醒不要用 pip 默认源装 PyTorch默认源的 CPU 版会让你后续训练慢到怀疑人生。另外 ultralytics 会自动下载预训练权重第一次运行需要联网内网环境就提前手动下载yolo11s-seg.pt放到项目目录里。我这次因为客户网络受限走了手动下载权重的路建议你不管有没有网络都先手动把权重包放在工作目录省得卡在最开始的下载环节。3.2 训练参数选型与起步配置训练命令的真身长这样yolo train \ modelyolo11m-seg.pt \ datadust_dataset/data.yaml \ epochs150 \ imgsz640 \ batch16 \ device0 \ workers8 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ close_mosaic10每个参数都是我调过的。imgsz用 640 还是 1280这里有个精度和速度的平衡问题。工地尘土是低对比度小目标我用 1280 训练过一版掩膜边缘确实更细腻但显存直接从 8G 涨到 16G推理速度也降了一截。后来实测 640 训练加 1280 推理测试阶段用imgsz1280提升精度是一个不错的折中方案训练时不至于爆显存部署时还能按现场性能灵活选分辨率。batch的设置原则是“吃满显存但不爆”。显存 24G 的卡可以 batch 32 左右12G 的卡老老实实 168G 就 812。close_mosaic10是很关键的一手架构里最后 10 个 epoch 关闭马赛克增强让模型在正常图像分布上收敛防止增强带来的分布偏差这是验证集分数能明显提升的一招。开始训练前建议先用少量迭代跑一个 10 epoch 的 smoke test确认数据路径、类别数、loss 曲线正常再跑完整训练。否则等 150 个 epoch 跑完才发现问题几小时甚至一天的算力就白烧了。3.3 训练过程监控指标不要光看 loss 数字训练过程中最容易被误解的是 mask loss。YOLOv11 的训练日志会输出box_loss、cls_loss、dfl_loss分割任务还会附加seg_loss。这几项 loss 都是越低越好但单一指标下降不能说明分割质量好关键看验证集上的mAP50-95和mask_mAP。我训练时的经验是如果cls_loss稳定下降但mask_mAP50一直卡在 0.8 上不去大概率是掩码分支的标注边缘噪声太大模型被多边形锯齿干扰了这时候回头检查标注而不是一味地加 epoch。训练过程中我会开一个终端跑 TensorBoard 或者直接看runs/segment/train/目录下的results.png曲线。这里有个细节v11 的训练过程会将验证集的预测掩码可视化直接保存在val_batch*_pred.jpg里每 10 个 epoch 就能看到模型预测的尘团长什么样。我几乎每天都会翻这组图光看曲线数字发现不了“模型把塔吊阴影当成尘土”这种问题但可视化图一眼就能看出来。3.4 验证评估与模型选择训练结束以后不要直接用最后一个权重做部署。Ultralytics 会在训练中保存最佳权重best.pt和最后一个last.pt部署一律用best.pt这个文件是按照验证集综合指标选出来的比训练终点更可靠。如果后面想进一步提点可以用yolo val对每一个候选模型做一次独立验证yolo val modelruns/segment/train/weights/best.pt datadust_dataset/data.yaml imgsz1280注意这里imgsz用 1280 会得到一个比训练时640更高的精度指标。这就带来一个良心建议训练 640 验证 640 与训练 640 验证 1280 两组指标分别对应“快速部署”和“精部署”两种场景。我给客户交付时同一个模型配两个推理分辨率档位白天光线好、画面简单时用 640 跑满帧率晚上或画面复杂时切到 1280 保精度灵活度很高。4. 推理部署与业务联动把模型变成工地可用的功能4.1 推理脚本与预测结果保存模型训练完就要把它接进业务系统。最基础的需求是跑一张图片输出所有尘团的掩码并把掩码面积超过阈值的预测结果截图存到本地同时记录时间戳和置信度。这里直接给出可复用的核心推理代码from ultralytics import YOLO import cv2 import numpy as np model YOLO(runs/segment/train/weights/best.pt) image cv2.imread(site_pic.jpg) results model.predict(image, imgsz640, conf0.35, iou0.45, saveTrue) for r in results: if r.masks is None: continue masks r.masks.data.cpu().numpy() # shape: (num_objects, H, W) boxes r.boxes.xyxy.cpu().numpy() for i, mask in enumerate(masks): area mask.sum() * (image.shape[1] / mask.shape[1]) ** 2 # 粗略像素面积换算 if area 500: # 大于阈值的才报警 cv2.imwrite(falert_{i}_{area}.jpg, image)这里有两个细节需要提醒。第一conf0.35是尘土场景比默认的 0.25 更合适的一个阈值因为尘土预测天然容易出低置信度的误报提高置信度阈值能过滤掉大量背景噪声第二iou保持 0.45 就行低于这个值同一个尘团会被切成多个碎片影响面积统计。如果你发现同一个尘团被反复报警问题多半出在 IOU 阈值太低而不是模型没训好。4.2 导成 ONNX 之后部署到边缘端训练用的 PyTorch 模型不能直接上生产环境的工控机。标准动作是先导出 ONNX再用 ONNX Runtime 或者 TensorRT 推理。导出命令一行搞定yolo export modelruns/segment/train/weights/best.pt formatonnx imgsz640 opset12导出后务必检查 ONNX 文件里的输出节点。YOLOv11-seg 的 ONNX 输出一般有两到三组bbox 输出、类别输出、以及掩码系数prototype masks。用 ONNX Runtime 推理时要拿掩码系数和原型掩码做矩阵乘法再用 sigmoid 得到最终掩码做阈值化。这个流程很容易踩坑所以我习惯导出后先用官方 Python 接口验证一遍 ONNX 和 PyTorch 的预测结果一致性确认无误再交给部署组。工地现场的工控机大多是 Windows i5/i7 集显CUDA 都没装ONNX Runtime CPU 推理在 640 分辨率下也得几十毫秒一帧实测完全够用。4.3 实时视频流接入与报警联动到了业务集成阶段需要把模型接到视频流而不是单张图片。我们用的是 RTSP 流接入拉流帧率不需要太高工地监控 5 FPS 足以捕捉扬尘过程。实时处理的逻辑是一个消费者线程从队列里取帧 → 缩放 → 推理 → 掩码处理 → 触发联动。这里最大的工程问题是延迟如果每个模块串行处理网络拉流延迟、推理延迟、喷淋系统 PLC 响应延迟叠加从“起尘”到“喷淋”可能超过 5 秒。所以我们的做法是推理线程缓存最近一帧联动逻辑直接读缓存帧的掩码结果不等待新的视频帧。业务联动方面分两级预警级检测到尘土面积超过画面 5%推送告警截图到管理后台人工复核。处置级尘土面积超过 15% 且持续 3 秒以上自动触发喷淋降尘设备同时记录报警前后 30 秒的视频片段用于回溯。注意阈值和延迟参数必须在现场用真实场景调不能拍脑袋。有一次我把持续 3 秒的判定设得太严导致尘团刚出现就被掐断喷淋永远触发不了后来改成“3 秒内出现 5 帧以上报警”的滑窗逻辑才稳定。4.4 关于遮挡与跟踪的进阶处理工地上尘土经常被车辆、围挡、塔吊遮挡导致同一个尘团在时序上时隐时现。如果只做单帧检测报警记录会一会儿有、一会儿没后台统计就很尴尬。进阶做法是用 ByteTrack 这类轻量跟踪器把相邻帧的尘团掩码中心点关联起来形成一条“尘团轨迹”。我在项目里用的是掩码中心点作为跟踪特征比用 bbox 中心点更稳定因为尘团形状不规则时 bbox 中心会有明显偏移。跟踪的好处是报警记录可以按轨迹汇总——某个时间段内一条轨迹出现了面积怎么变化源头大致在哪辆车数据完整度马上不一样。5. 常见问题与排查技巧实录5.1 掩码断裂或变成锯齿状这是分割训练最常见的问题。表现为同一个尘团预测出几个碎块或者掩码边缘明显锯齿化。排查思路按三步走先检查标注多边形是否沿着尘团边缘精细描绘标注如果太粗糙模型学到的是锯齿的规律而不是尘团平滑的过渡其次看训练分辨率640 训练下小尘团可能只占十几个像素很容易被模型忽略可以针对性给这类样本做上采样增强最后把mask_ratio参数从默认的 4 调成 2让掩码分支保留更高分辨率的特征图代价是训练速度变慢但边缘质量改善明显。5.2 尘土和背景混淆误把阴影/围挡/黑色车辆当尘土尘土和阴影在灰度上高度相似尤其是下午光线斜射时塔吊影子正好投在作业面上模型经常把阴影边缘当成尘团。我的解决思路分三层第一在标注阶段就明确规范“阴影不标只有物体运动带起的微粒集合才算尘土”避免标注了好几个“假尘土”喂给模型第二在图像预处理中对对比度做拉伸让尘土和背景的灰度差更明显抑制阴影干扰第三推理阶段业务侧加区域过滤比如只对围挡内作业面区域做检测围挡外车辆行驶区域单独用目标检测判断是否违规。注意靠加大数据集让模型自己区分阴影和尘土代价太高不如在业务规则上做兜底。5.3 显存不足训练到一半 OOMOOM 在分割任务上是家常便饭因为掩码分支额外消费大量显存。我的处理顺序先降 batch从 32 降到 16 再不行降到 8总能跑起来然后再开amp混合精度默认开启的如果关了就重新打开最后如果还不行把imgsz从 640 降到 512。这里要提醒的是imgsz降到 512 会明显损失小尘团精度所以尽量优先动 batch。另外多卡训练时显存分配不均也常见可以尝试device0,1并配合batch按卡数整除数据集不大的项目不用上多卡。5.4 夜间与强光场景的泛化失效晚上路灯下的尘土在红外摄像头上呈现灰白色可见光下呈现黄褐色形态差异很大。如果在数据集中夜间样本占比太低模型的夜间预测能力会断崖式下跌。解决思路有两个方向一是采集夜间数据时人工补齐把夜间样本比例提升到总量的 30% 左右这是最可靠的做法二是做“红外-可见光双向增强”将夜间样本统一做灰度化再训练一个分支工地很多摄像头其实自带红外模式可以让模型在不同色彩域上学习尘土的形态特征而不是依赖颜色。实测下来两者结合效果最好。5.5 施工阶段不同带来的数据漂移工地从基坑、主体到装修阶段扬尘形态完全不同。基坑阶段是大量黄土颜色接近地面主体阶段是混凝土和木屑混合灰尘颜色偏灰装修阶段又是粉尘较细、扬起高度低。这个坑我吃了大亏第一版模型只在基坑阶段的数据上训练客户把点位移到主体阶段后精度直接掉到没法用。后来把数据集按施工阶段做分层采样并在每个阶段采集完成后做增量训练微调。部署上也要预留“模型热更新”的通道工控机上保留最近两个版本权重现场验证新版本没问题再切换。最后再分享一个实在的经验这套系统从数据采集到上线大概花了两个月其中精力分配大概是这样采集和标注占了七成训练调参不到两成部署集成占一成。很多做人工智能项目的朋友容易把注意力放在“训练模型”本身实际上工地尘土这种场景瓶颈从来不在模型结构而在数据质量和业务规则的打磨。YOLOv11 很强强在它把检测和分割做成了一套标准工具链但你如果喂给它的标注是一团乱麻它回给你的就是一团乱麻。我个人在实际操作中体会最深的一件事不要追求“一次性训出完美模型”而是把项目做成“数据反馈闭环”。模型上线后把每天漏报、误报的样本自动截取回来每周补充一次标注滚动训练一个增量版本两周迭代一轮。这样模型会慢慢适应这个工地特有的光线、背景和扬尘规律越跑越准。这也是为什么我这边的最终交付物从来不是一个模型文件而是一整套数据和模型的持续迭代机制。如果你手头也有类似的工地扬尘分割需求我建议先把“能跑通”的目标定低一点第一步用 YOLOv11s-seg 搭配 1000 张标注数据验证流程完整再换大模型加数据提精度。这条路是最稳的也是我反复踩坑之后得出的最优路径。