做河道漂浮物识别这件事,很多人一开始就把劲儿使错了地方。模型用得多高级不重要,真正卡住项目进度的,几乎永远是那一句“数据呢?”907张河道口水面漂浮物的labelme格式分割数据集,2个类别,这个体量放在整个深度学习数据集里不算大,但对于一个具体的河道监测场景来说,已经足够让一个分割模型从“能跑”变成“能用”。这篇东西我打算把这907张图背后那些不写在文档里的东西全翻出来:这个数据集到底解决什么问题、labelme格式里藏着哪些坑、标注时怎么描边才不容易返工、训练分割模型时哪些参数值得认真调。不管是做水环境治理的算法工程师,还是想复现一遍完整数据制作流程的学生,应该都能从里面捞到点干货。
我见过不少团队做水面漂浮物识别,第一反应是上目标检测,拿着YOLO一顿框。框完之后发现一个问题:漂浮物这东西形状太不规矩,大片的浮萍、成堆的塑料瓶、半沉在水里的树枝,检测框恨不得把半个河道都框进去,框内背景占比极高,算出来的面积、覆盖率根本没法看。这就是分割数据集的优势所在——直接输出像素级轮廓,能准确算出漂浮物占据的水面面积、飘移速度,甚至可以进一步按形态分类。所以做河道漂浮物监测,分割是比检测更贴合业务需求的方案。而这一切的前提,是有一份像样的分割标注数据集。
1. 为什么要做河道漂浮物分割数据集
1.1 河道监测的真实痛点:人工巡检为什么撑不住
河道漂浮物的清理和考核,一直是水环境治理里非常头疼的一环。传统方式是人工巡河,每天安排保洁船在固定河段巡逻,看到垃圾用网兜捞。这里面有几个绕不开的问题:一是效率低,一条河几公里到几十公里,靠船跑一遍至少大半天,还经常出现刚捞完上游又飘下来一片的情况;二是考核难量化,漂浮物覆盖率、聚集程度、主要类型这些指标全靠人眼估计,不同的人报出来的数可能差得离谱;三是台风暴雨天根本没法出门巡检,而暴雨恰恰是漂浮物暴增的时候。
装视频监控是大家自然会想到的方案,高位 camera 俯拍河道,后台能看到实时画面。但监控装多了之后,新的问题来了——人不可能7乘24小时盯着几十路画面看。这时候AI的作用就体现出来了:让模型在后台实时跑着,发现异常漂浮物聚集就推一张截图给保洁人员。但AI要干活,第一步要解决的就是“认识漂浮物”。
1.2 为什么检测模型不够用,一定要分割
一开始很多项目用的都是目标检测,毕竟检测模型成熟、部署方便、速度也快。但在河道场景里,检测有几个致命短板:
- 漂浮物不规则。一堆聚在一起的白色泡沫、一大片水葫芦,用矩形框框起来,框里可能一半是水面,一半是垃圾。做覆盖率统计的时候,这个“框面积”跟真实漂浮面积差太远。
- 目标重叠密集。水面垃圾经常成堆聚集,检测框之间大量重叠,后处理NMS(非极大值抑制)一不小心就把真实目标给抑掉了。
- 没法算精确面积。要对漂浮物做量化考核,比如“漂浮物覆盖面积不能超过河面面积的5%”,检测给不了答案,只有分割能按像素算出来。
换句话说,分割模型输出的是一张“哪哪些像素属于漂浮物”的像素级掩膜。有了这张掩膜,你可以算出漂浮物面积、占水面比例、聚集位置、漂移速度,甚至可以叠加时间维度判断垃圾带是在扩散还是消散。这些全是检测做不到的。
1.3 907张、2类别这个配置合不合理
先说结论:对于一个固定场景的河道口,完全够用。907张图的分割数据集,如果都是同一个河道口在不同光照、不同水位、不同漂浮物聚集状态下拍的,其实覆盖已经比较全了。我在实际项目中拿几百张这种数据训出来的分割模型,单场景的mIoU能做到0.8以上。
2个类别的设定也很务实。最常规的做法是“背景”和“漂浮物”两类,本质上是前景背景分割。也有人把两类拆成“生活垃圾”和“植物类漂浮物”,这样对下游的清理调度更有意义——生活垃圾靠人工捞,植物类可以等集中打捞。不管是哪种分法,类别数控制在2类是明智的,类别越多,标注成本成倍增加,而且水面上很多漂浮物互相遮挡,细分起来标注员自己都拿不准,标注一致性差,模型训练反而更混乱。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据集格式深入解析:labelme的JSON到底存了什么
2.1 labelme格式的完整字段说明
labelme是标注工具界的老牌选手了,它导出的每张图片对应一个同名JSON文件。很多人第一次打开这个JSON会有点懵,觉得里面结构怎么这么复杂。其实拆开看就几个关键字段:
- version:标注工具的版本号,不用管。
- flags:分类标志位。如果你在标注时给某些图片打了标签(比如“模糊”“有水花遮挡”),会存在这里。
- shapes:核心数组,每一个元素代表一个标注对象。每个对象里最重要的是label(类别名)、points(多边形顶点坐标列表)、shape_type(这里就是polygon)。
- imagePath:对应的图片文件名。
- imageData:这个字段默认情况下是图片的base64编码,体积很大,一个几百KB的图片转成base64能变成几MB。很多人忘了这个是导致数据集体积膨胀的元凶。
理解这个结构是第一步,真正容易踩坑的是后面这些细节。
2.2 关于坐标、分辨率与shape_type的坑
labelme的坐标是像素坐标,直接对应当前图片分辨率下每个多边形顶点的位置。这里有三个容易被忽略的点:
第一,标注时图片分辨率会影响标注精度。如果原图是1920乘1080,标注出来的多边形坐标就是在这个分辨率下的,后面如果做缩放、裁切,必须同步变换坐标,不能拿老坐标硬套新图。
第二,shape_type必须是polygon。labelme也支持rectangle、circle这些形状,但分割数据集基本只用polygon(多边形)。如果你看到数据集里混进了circle,说明标注时误用了工具,训练时格式转换会踩坑。
第三,多边形的点不要过多。有些标注员为了追求精确,把一个塑料瓶描出50个点,密密麻麻的,既费时间又容易引入标注噪声。真实经验是:轮廓关键点控制在8到20个之间,足够刻画形状就行。点太多,模型反而容易过拟合标注里的抖动噪声。
2.3 907张图的文件组织方式
一个规范的数据集不会只有图片和JSON散落在文件夹里,我建议按下面的结构组织:
text复制dataset/
├── images/
│ ├── 001.jpg
│ └── ...
└── annotations/
├── 001.json
└── ...
图片和标注分开放有两个好处:一是做格式转换的时候方便遍历;二是如果你想只拷贝图片给其他协作方,不会把一堆JSON一起带过去。当然,如果你直接从labelme导出,默认是图片和JSON混在一起的,需要自己写个小脚本整理一下目录结构。
另外,907张图的命名建议规范成6位或8位数字,比如000001.jpg这种,避免后期数据混用和拼接时在文件名上出问题。我自己吃过一次亏:某个数据集里既有“photo_1.jpg”又有“photo_01.jpg”,写加载代码时排序错乱,训练集验证集发生了数据泄漏,误判了模型效果。
3. 标注实操流程与质量控制
3.1 标注前必须定好的统一规范
标注这件事,最怕的是两个人用两套标准。开始批量标注之前,必须花半天时间把标注规范定死。拿河道漂浮物来说,要明确这几点:
- 标注目标范围:标注“漂浮物”是指水面上可见的固形物,还是连水花、反光也算?答案应该是不算。如果水花四溅导致目标边界看不清,就按可见轮廓标注,拿不准的边缘宁可少标不能乱标。
- 遮挡处理:漂浮物如果有部分被桥墩、岸边遮挡,标注完整可见部分,不要脑补被遮住的部分。
- 小目标处理:远处小目标,比如只有十几个像素点的泡沫,要不要标?要标的话就按实际轮廓标一个小多边形,不要省略。小目标是分割模型最难学的部分,省了它会加剧漏检。
- 半沉物体:有些垃圾半沉在水里,水面上只露出一点。这种情况按露出的可见轮廓标,别靠想象把水下的部分也画出来,因为你不知道水下到底有多大。
这些规范看着细碎,但直接影响标注一致性。一致性差的数据集,模型训练时loss会异常震荡,验证集mIoU上不去,你还找不出原因——最后排查了半天,发现是标注员把同一种东西标成了两种形状。
3.2 labelme标注的实际操作流程
labelme的安装简单到不需要单独开一节讲,pip install labelme就行。但操作流程里有几个细节值得说说:
- 打开程序后,先打开图片所在的文件夹,然后用“Create Polygons”开始勾边。
- 勾边时先不要急着画很多点,在物体轮廓的关键转折处落点,比如瓶子的瓶口、瓶身、瓶底各两三个点就够了。直线段的地方两点搞定,弧线段的地方多点几个。
- 闭合多边形之后,会弹出让你输入类别名称的对话框,这个名称就是后面shapes里存下来的label值。这里务必保证所有标注员的输入完全一致,哪怕差一个空格、大小写不一样,在模型训练时都会被当成两个类别处理。
- 保存后生成JSON文件,注意labelme在保存时默认把图片base64编码塞进JSON里。这会导致JSON文件特别大,磁盘里907张图能占好几个G。规范做法是保存后在菜单里设置不嵌入图片数据,或者自己写脚本把imageData字段清掉。
3.3 质量校验:返工是常态,别怕返工
标注质量校验是整个流程里最容易被跳过、但又最重要的一步。我的经验是一定要做“双重校验”:标注员A初标,标注员B复检。复检不是看个大概,而是要拿着原图逐张对比标注多边形和实际物体边缘的贴合度。常用的校验指标有两个:
- IoU一致性:让两个标注员独立标注同一个目标,算两个多边形之间的IoU,一般要求至少0.85以上。低于这个值说明标注标准存在分歧,需要重新讨论规范。
- 标签一致性:抽查不同标注员对同一类物体的标注结果,对比类别标签和边界位置是否一致。
实际操作中,907张图全部双重校验可能要额外花两到三天,但值得。因为后期你会发现,模型训练出来的错分割边缘,有很大一部分是原始标注本身就不准导致的。
3.4 标注工作量的经验估算
给想做自建数据集的人一个参考:河道口这类场景,水面目标相对单一,一个熟练标注员标注一张图大约需要10到20分钟。如果是水葫芦成片、边界模糊的图,可能要30分钟以上。907张图,一个人全职干大概需要两到三周。如果赶时间可以考虑用半自动标注工具先跑一轮预标注,人工修正——比如先用一个现成模型跑all图片得到粗糙掩膜,再导入到labelme里面人工调整,能把单张时间压缩到5分钟以内。
4. 从labelme JSON到模型训练:数据转换与训练技巧
4.1 格式转换:JSON转成模型能吃的格式
labelme的JSON不能直接喂给模型训练,中间要做格式转换。目前主流的选择是转成COCO格式或者直接用现成库在训练时读取labelme格式。
COCO格式是一种通用的标注存储方式,用一个大JSON文件保存所有图片的标注信息,PyTorch生态里有大量的库直接支持。转COCO时要重点注意points坐标的存储形式:labelme里points是[[x1, y1], [x2, y2], ...],COCO里则是[x1, y1, x2, y2, ...]一维数组。很多人第一次转格式就漏了这一步,导致训练时坐标全错乱。
还有一种更省事的方案是用一个叫labelme2coco的开源脚本,一行命令搞定转换,但我建议还是自己读一遍转换后的文件,确认几个关键字段没问题再开训练。别问我是怎么知道要检查的——有一次转换遗漏了标注目录里某一张图,硬是让训练和验证的图片数量对不上,排查了半天才发现是文件名编码问题,某张图里带了一个中文名。
4.2 数据增强策略:水面场景不能乱用增强
907张图对分割模型来说不算多,数据增强是必须的。但水面场景有一些独特之处,增强策略要格外小心:
- 镜像翻转:强烈推荐,水平翻转、垂直翻转都安全,因为水面漂浮物的方向和语义无关。
- 随机旋转:建议小角度旋转,比如正负15度,太大的旋转会让漂浮物原本的姿态失真,尤其是垃圾袋这类有明显形状特征的物体,旋转超过90度会让模型学到错误的特征。
- 色彩扰动:要谨慎。水面反光、光照变化虽然真实存在,但如果色彩扰动太狠,会让模型把水的颜色也学进去,反而误把波光粼粼的水面当作目标。
- Mosaic增强:我实测下来是利大于弊,因为它能在一张图里混合多个场景,增加小目标的出现频率。但对分割任务来说,Mosaic让每张图的目标尺度被大幅压缩,某些大尺度目标的细节会丢失,所以要和随机缩放搭配着用。
我的实际配置是:随机水平翻转(概率0.5)、随机缩放(0.5到1.5倍)、正负10度随机旋转、轻微色彩抖动。这个组合在类似项目里效果稳定。
4.3 模型选择与训练参数推荐
907张的核心数据集,模型结构的选择比你在Coco上刷榜要保守得多。我在多个数据集上的经验是,这种规模下最稳的方案是U-Net系或者DeepLabV3+,backbone用ResNet50或者更轻量的MobileNetV3。
具体来说:ResNet50做backbone的DeepLabV3+在907张上训练,单卡大概4到6小时能收敛,mIoU能在0.75到0.85之间。如果追求速度,换MobileNetV3做backbone,推理帧率能到几十FPS,精度损失大概在3到5个百分点。
训练参数推荐这套起步配置:
- 优化器:AdamW,初始学习率1e-4,配合多项式学习率衰减。
- 损失函数:Dice Loss加BCE Loss结合,比单用交叉熵更能应对背景类占比过高的问题。我的经验是配比在0.7比0.3附近。
- 批量大小:8到16之间,取决于显卡显存。分割任务batch太大反而容易过早收敛到局部最优。
- 迭代轮数:40到60个epoch,配合早停。
需要一个细节:预训练权重非常关键。这批907张是特定场景的域内数据,但backbone预训练还是在ImageNet上刷过的更好。直接用随机初始化权重在这点数据上学不出来的,即使损失函数看着在下降,真实分割效果也很拉胯。
4.4 模型评估:不能只看mIoU
训练完要看模型好不好,评估指标得全面一点。mIoU均值交并比是绕不开的核心指标,但我还会额外看这几个:
- Dice系数:和mIoU类似但更强调重叠度,对小目标的敏感度更高,两张图重叠率不过半时,mIoU已经掉到0.3附近,Dice还能有0.5以上,更容易看出问题。
- 像素准确率(PA):这指标在背景占绝大多数的分割场景里很容易虚高。有一种情况是模型把一张图里所有像素全预测成背景,PA还能到95%以上。所以PA只能做参考,不能作为主导指标。
- 类别个别指标的差异:如果“漂浮物”类的IoU比“背景”类低了20个点以上,说明模型对小目标学习不足,要针对性补充该类别的小目标样本。
让我强调一下测试集划分。907张里,我建议按8比1比1切训练、验证、测试。切的时候要保证同一段视频的连续帧尽量落在同一个集里,不然连续帧之间高度相似,验证集和训练集的分布几乎相同,会严重高估真实泛化能力。这也是一个特别容易踩的细节。
5. 常见问题速查与一线排障经验
5.1 训练中loss不下降或者剧烈震荡
这个现象在这类数据上出现得最多。先检查三件事:第一,标注文件里有没有标签名称不一致,比如“garbage”和“garbage ”尾随空格,这种会让类别数凭空多出1类,结构直接乱了;第二,imageData字段是否过大,导致数据加载速度极慢,训练了一个小时其实才跑了几个batch,loss当然看不出下降趋势;第三,损失函数里Dice和BCE的配比是否失衡,Dice在小目标上梯度波动本来就大,如果配比过高,loss就会一直抖。
5.2 模型在水面反光区域大量误检
水面波光粼粼的区域,纹理和漂浮物的边缘在视觉上有相似性,分割模型经常把高亮水纹当成目标。我的排查经验是:先看误检的具体像素区域,是否集中在画面特定位置,如果是,大概率是拍摄机位固定导致该区域长期反光;针对这个问题,在数据增强里加强这一块的亮度扰动,或者干脆在后期加一个水面反光区域掩膜先过滤掉。相比改模型,从数据侧解决更直接。
5.3 边界锯齿严重,轮廓像狗啃的
这通常是两个原因:标注本身边界粗糙,或者上采样方式太朴素。如果是标注问题,回到第3节做复检;如果是模型层面,把Decoder的上采样从双线性插值换成分数步长卷积,边缘能顺滑很多。
5.4 推理速度慢,部署到边缘端不达标
很多河道口的监控设备是边缘算力盒子,跑不动大模型。常规优化路径有三个:一是把backbone换成轻量系列,XX精度掉几个点但速度翻倍;二是导出ONNX后做INT8量化,实测体积能压到原来的四分之一,精度损失可接受;三是裁剪输入分辨率,比如从1280降到960,能将推理时间缩短30%以上。这三个方法组合使用,基本能把真实场景的FPS提到设备要求之上。
5.5 上下游数据格式工具互相不兼容
这个坑我是踩得最深的。标注是labelme,训练用的是COCO,部署又要转成设备厂商自家的格式,每次转换都是一轮debug。现在我的固定打法就是:用labelme作为标注源头,维护一份基础JSON,后续所有格式转换都从这份源数据出发,不让格式互转过程靠近数据本身。每次转换后,跑一个校验脚本,检查图片数量、类别数、坐标范围是否符合预期,确认无误再进入下一步。
6. 持续迭代的建议
907张2类别的labelme格式河道口水面漂浮物数据集,入手之后最简单的一条路径是:熟悉labelme结构、过一遍标注规范、转成训练格式、跑一轮分割模型、用错误case反哺标注和增强策略。这个过程跑通之后,后续再扩充数据,就是在现有流程上滚雪球,不存在推倒重来的问题。
我个人实操的体会是,类似项目最容易拖垮进度的从来不是模型结构选得多妙,而是数据侧的糙活没做细致。标注规范这份“小文档”务必在启动前打磨到位,它可以直接决定数据集下半场的质量与复现效率。另外,把数据增强策略和模型评估指标固化在一个能够反复复用的框架里,会为后续每次迭代省下大量重复劳动。这个方向之后还可以延伸到夜间低照度画面、雨雾天气过滤、不同类型漂浮物细分等领域,每一次数据积累,都会让模型在真实场景里变得更好用。
