拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

公路隧道漏水识别分割数据集:27张labelme标注与训练实战

公路隧道漏水识别分割数据集:27张labelme标注与训练实战 1. 为什么27张图的漏水数据集值得单独拿出来说公路隧道漏水识别这个方向听起来冷门但只要你接触过隧道巡检、地铁管养、桥隧结构健康监测这类项目就知道它有多刚需。传统做法是人工拿着手电筒在隧道壁上找渗水痕迹一条几公里的隧道走下来眼睛看花、记录靠拍照、回去还得一张张整理效率低不说漏检率还高。用语义分割模型自动识别漏水区域是这几年比较务实的落地路径而这条路的第一步就是搞到一份标注规范的漏水分割数据集。我这次拿到的是一份labelme格式的公路隧道漏水识别分割数据集总共27张图1个类别。数量确实不多但它的价值在于“干净”——单类别、标注格式统一、场景聚焦在公路隧道内壁。很多人一看到27张就摇头觉得太少没法训。我的实际经验是小样本数据集能不能用关键不在数量而在你打算拿它干什么。如果你是想做原型验证、跑通分割流程、或者给一个更大的自建数据集做标注规范参考27张完全够用如果你想直接训一个能上生产的高精度模型那它只能当种子集后面必须靠扩充。这篇文章我会把这份数据集从里到外拆一遍labelme的标注结构长什么样、单类别分割的标签怎么处理、27张图怎么划分训练验证、小样本下用什么增强策略、以及我在实际跑分割时踩过的那些坑。适合正在做隧道病害检测、结构缺陷分割、或者刚接触labelme语义分割的同行参考。哪怕你手里不是漏水数据是裂缝、剥落、渗水这类同构的缺陷分割任务思路也是通用的。2. 数据集整体设计与labelme标注结构拆解2.1 单类别分割任务的设计逻辑先说说为什么这份数据集只设1个类别。公路隧道漏水识别本质上是一个二分类分割问题像素要么是漏水区域要么是背景隧道壁、管线、灯具、标线等。把漏水单独作为一个前景类背景全部归为一类这是缺陷分割里最常见的简化策略。它的好处很直接标注成本低、类别不平衡问题相对可控、模型输出就是一张二值掩码后处理简单。我见过一些团队一上来就分“渗水、滴水、湿渍、水痕”好几个子类结果标注一致性极差不同标注员对“湿渍”和“水痕”的边界理解完全不一样训出来的模型类别混淆严重。所以单类别不是偷懒而是把问题定义清楚的第一步。等你把漏水这个大类做稳了再考虑细分才有意义。这份数据集的核心字段可以这样理解维度内容说明任务类型语义分割像素级二分类标注工具labelmeJSON格式多边形标注图像数量27张小样本类别数1类漏水区域场景公路隧道内壁光照不均、纹理复杂标注形式polygon多边形可转mask2.2 labelme的JSON结构到底存了什么labelme标注完一张图会生成一个同名的.json文件。很多人只会用工具点来点去却不知道里面存了什么一旦要批量处理就抓瞎。我先把结构讲清楚。一个典型的labelme JSON包含这几个关键字段version记录labelme版本flags是自定义标志位这份数据集基本为空shapes是核心里面每个元素代表一个标注对象包含label类别名、points多边形顶点坐标数组、shape_type这里是polygon、group_id等。再往下是imagePath、imageData有些版本会把图像base64塞进去文件会变得很大、imageHeight、imageWidth。这里有个特别容易踩的坑imageData字段。如果你标注时没注意labelme默认可能把整张图编码进JSON导致单个json文件几MB甚至十几MB。27张图如果都这样光json就上百MB转换mask时读取极慢。我的做法是转换前先批量把imageData置空只保留imagePath这样json瞬间瘦身到几十KB。import json import os def strip_image_data(json_dir): for fname in os.listdir(json_dir): if not fname.endswith(.json): continue path os.path.join(json_dir, fname) with open(path, r, encodingutf-8) as f: data json.load(f) if data.get(imageData) is not None: data[imageData] None with open(path, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse) print(f已清理: {fname})这段脚本我几乎每个labelme项目都会先跑一遍属于标准起手式。2.3 多边形标注对漏水区域的适配性漏水区域有个特点形状极不规则边界模糊经常是渐变的湿痕而不是锐利的边缘。labelme用多边形polygon去框其实是一种折中。对于大面积连续渗水标注员会沿着肉眼可见的湿区外轮廓点一圈对于零散的水滴、水痕就分别点小多边形。这里就引出一个标注质量问题边界模糊区域怎么定我的经验是统一标准——以“肉眼明显可见的颜色/反光差异”为界宁可稍微收紧一点不要把边缘过渡带也算进去。因为过渡带本身语义模糊不同人标出来差异巨大反而污染训练数据。27张图如果标注标准不统一比数量少更致命。另外要注意labelme的points是相对于原图左上角的像素坐标转换mask时直接用即可不需要归一化。但如果你的图在标注后被缩放或裁剪过坐标就对不上了这是新手常犯的错误——先标注后改图等于白标。3. 从labelme到训练mask的完整转换实操3.1 环境准备与labelme安装避坑labelme的安装是第一个拦路虎。网上搜“labelme安装”能搜出一堆报错最常见的就是pyqt5相关的依赖冲突。我实测下来最稳的方式是用conda建独立环境别在base环境里硬装。conda create -n labelme python3.9 -y conda activate labelme pip install labelme -i https://pypi.tuna.tsinghua.edu.cn/simple用清华镜像是因为官方源在国内拉pyqt5经常超时。如果你遇到labelme error pyqt5-sip这类报错八成是sip版本和pyqt5不匹配解决办法是显式指定版本pip install pyqt55.15.9 pyqt5-sip12.12.1 pip install labelme5.8.3我特意锁5.8.3这个版本是因为它在json结构和转换脚本兼容性上比较稳定太新的版本偶尔会改字段名导致老脚本读不了。装完之后命令行敲labelme能弹出界面就说明成了。如果提示找不到命令检查一下是不是环境没激活或者Scripts目录没进PATH。3.2 批量转换JSON为分割掩码labelme自带labelme_json_to_dataset命令但它一次只能转一个27张手动转太蠢。而且它默认输出的是可视化结果不是纯mask。我一般直接用脚本批量转核心逻辑就是读json里的shapes用PIL的ImageDraw把多边形填充到一张全黑图上填充值设为1前景背景保持0。import json import os import numpy as np from PIL import Image, ImageDraw def json_to_mask(json_path, out_path, label_nameleak): with open(json_path, r, encodingutf-8) as f: data json.load(f) h, w data[imageHeight], data[imageWidth] mask Image.new(L, (w, h), 0) draw ImageDraw.Draw(mask) for shape in data[shapes]: if shape[label] ! label_name: continue points [tuple(p) for p in shape[points]] if len(points) 3: draw.polygon(points, fill1) mask.save(out_path) json_dir ./jsons mask_dir ./masks os.makedirs(mask_dir, exist_okTrue) for fname in os.listdir(json_dir): if fname.endswith(.json): name os.path.splitext(fname)[0] json_to_mask( os.path.join(json_dir, fname), os.path.join(mask_dir, name .png) )这里有个细节fill1而不是255。因为后续训练时很多框架会做归一化或者直接当类别索引用用0/1更省事。如果你要可视化检查再乘255就行。另外draw.polygon要求至少3个点少于3个的退化多边形直接跳过避免报错。3.3 转换结果的正确性校验转完mask千万别直接拿去训一定要抽查。我一般做两件事一是把原图和mask叠加显示看多边形有没有偏移二是统计每张mask的前景像素占比。import numpy as np from PIL import Image for fname in os.listdir(mask_dir): m np.array(Image.open(os.path.join(mask_dir, fname))) ratio (m 0).sum() / m.size print(f{fname}: 前景占比 {ratio:.4f})正常情况下漏水区域占比应该在1%到30%之间。如果某张图占比超过50%很可能是标注时把整个画面都框进去了或者多边形自交导致填充异常如果全是0说明label名对不上或者json里根本没shapes。我这份27张的数据集实测前景占比大多在3%到15%之间符合隧道漏水的实际分布——漏水是局部现象不可能占满整个画面。提示如果发现某张mask前景占比异常先回去看原json的shapes别急着改脚本。十有八九是标注本身的问题。4. 27张小样本的训练策略与增强手段4.1 小样本划分别用随机划分27张图如果按8:2随机划分验证集只有5张左右评估结果波动极大今天mIoU 0.6明天可能0.4。我的做法是按场景分层划分先人工看一遍27张图按隧道区段、光照条件、漏水形态大致分成几组保证训练集和验证集里都覆盖到不同形态。比如有逆光的水痕、有顶灯直射下的湿区、有边角零散水滴尽量让验证集不是训练集的简单复制。具体到27张我一般划22张训练、5张验证。如果做交叉验证就5折每折验证5-6张。小样本下交叉验证比单次划分靠谱得多虽然训练次数多但评估更稳。4.2 数据增强小样本的救命稻草27张图直接训模型必然过拟合。增强是必须的但增强策略要贴合隧道漏水场景不能乱用。我常用的组合水平翻转、垂直翻转隧道壁左右上下都有漏水翻转合理。随机旋转90度倍数避免插值带来的模糊90度旋转最干净。亮度/对比度扰动隧道内光照差异大这个增强能提升泛化。随机裁剪从原图裁出子图变相增加样本多样性。高斯噪声模拟传感器噪声和低照度噪点。我不建议用弹性形变elastic transform这类强形变因为漏水区域的形状本身有物理意义过度形变会让模型学到不真实的边界。颜色抖动也要克制隧道壁的色调相对固定抖太狠反而引入噪声。用albumentations可以这样配import albumentations as A train_transform A.Compose([ A.HorizontalFlip(p0.5), A.VerticalFlip(p0.5), A.RandomRotate90(p0.5), A.RandomBrightnessContrast(p0.5), A.RandomCrop(height256, width256, p0.5), A.GaussNoise(p0.3), ])注意mask的插值要用最近邻否则会把0/1标签插出中间值变成多类别假象。albumentations默认对mask用nearest但你自己写pipeline时要确认。4.3 损失函数与评价指标的取舍单类别分割前景占比小典型的类别不平衡。如果直接用交叉熵模型会倾向于全预测背景因为那样loss也能降得很快。我一般用Dice Loss BCE的混合Dice负责拉高前景召回BCE稳定训练。import torch import torch.nn as nn class DiceBCELoss(nn.Module): def __init__(self, weight0.5): super().__init__() self.weight weight self.bce nn.BCEWithLogitsLoss() def forward(self, pred, target): bce_loss self.bce(pred, target) pred_sig torch.sigmoid(pred) intersection (pred_sig * target).sum() dice_loss 1 - (2 * intersection 1e-6) / (pred_sig.sum() target.sum() 1e-6) return self.weight * bce_loss (1 - self.weight) * dice_loss评价指标上小样本别只看mIoU还要看前景IoU和召回率。因为背景占大头mIoU容易被背景拉高看起来很美实际漏水区域可能漏检严重。我这份数据在验证集上前景IoU能到0.55左右就算不错了毕竟只有22张训练图。5. 常见问题与排查技巧实录5.1 labelme使用中的高频故障问题现象可能原因解决办法启动报pyqt5错误依赖版本冲突锁pyqt55.15.9和sip12.12.1标注后json巨大imageData被写入批量置空imageData中文标签乱码编码不一致统一用utf-8读写多边形无法闭合点太少或自交至少3点避免交叉转换mask全黑label名不匹配核对shapes里的label字段5.2 小样本训练中的典型翻车翻车一验证集loss比训练集还低。这通常不是好事而是验证集太简单或者和训练集分布太接近。27张图如果随机划分很容易出现这种情况。解决办法就是前面说的分层划分让验证集有“难度”。翻车二模型把所有湿痕都预测成背景。这是类别不平衡的典型表现。先检查前景占比如果普遍低于5%就要调高Dice Loss权重或者用带权重的BCE给前景像素更高权重。翻车三预测结果边缘毛糙、碎片化。小样本模型容易在边界处不稳定。后处理可以加一步形态学开闭运算去掉孤立小区域填补内部空洞。但别过度否则会把真实的小水滴滤掉。import cv2 import numpy as np def postprocess(mask, kernel_size3): kernel np.ones((kernel_size, kernel_size), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) return mask5.3 我踩过的几个真实坑第一个坑是标注顺序不一致。同一批图有的先标大区域再标小水滴有的反过来导致json里shapes顺序混乱。虽然对mask结果没影响但如果你要做实例级分析就麻烦了。建议标注前约定好顺序。第二个坑是图像和json文件名不匹配。labelme默认json名跟图名一致但如果你中途改过图名json里的imagePath还是旧的转换时找不到图。我的习惯是转换脚本里不依赖imagePath直接用json文件名去对应原图。第三个坑是验证时用了训练时的增强。这个错误很隐蔽验证集如果也做了随机裁剪评估结果就不可比了。验证和测试阶段只做resize和归一化不做随机增强。6. 这份数据集的扩展方向与实用建议27张图作为起点接下来怎么扩我分享几个实际可行的路子。一是同源采集如果还能回到原来的隧道换个时间段、换个天气再拍一批重点补逆光、积水反光、夜间照明这些困难场景。二是半自动标注先用这27张训一个初版模型拿它去预测新图人工只修正错误区域标注效率能提升好几倍。三是跨场景迁移地铁隧道、地下管廊、涵洞的漏水形态和公路隧道有相似性可以拿来补充但要注意域差异最好做域适应或者微调。最后给几条实操建议。第一永远保留原始jsonmask是派生产物标注才是原始资产改转换逻辑时随时能重跑。第二建立标注规范文档哪怕只有一页写清楚什么算漏水、边界怎么定、最小标注面积是多少这对后续扩充至关重要。第三小样本别追求SOTA先把流程跑通、把评估做扎实等数据量上来了再换大模型、调超参。我见过太多人在20张图上死磕精度不如花时间多标200张。这套漏水分割的流程从labelme标注到mask转换再到小样本训练我前后在好几个隧道项目里复用核心逻辑没变过。27张确实少但它足够让你把整条链路验证一遍剩下的就是堆数据和迭代的事了。
返回列表