ARTICLE DETAIL

资讯详情

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

Mask2former滑坡语义分割实战:从自定义数据集到遥感影像批量出图

Mask2former滑坡语义分割实战:从自定义数据集到遥感影像批量出图 滑坡灾害这几年在山区越来越常见强降雨一触发塌方、滑坡经常连片出现。靠人工在遥感影像上勾滑坡边界费时费力而且容易漏于是用深度学习做滑坡语义分割就成了一个很实际的需求。这次分享一个完整实操项目用Meta AI开源的Mask2former在自己的自定义数据集上做滑坡语义分割从标注、转COCO、改配置、训练到最终批量出图把整个链路走通。Mask2former是2021年底出来的通用分割模型语义分割、实例分割、全景分割一套框架全吃核心是masked attention和从mask query直接回归二值掩码。这套思路用在滑坡上实测对边界不规则、纹理杂乱的目标比UNet、Deeplabv3这类逐像素分类模型效果更好。这篇文章适合正在做遥感影像分割、地质灾害识别的研究生或工程师也适合想从实例分割转语义分割的CV开发者。下面把整个链路拆开讲尽量做到你能照着复现。1. 项目背景与方案选型为什么Mask2former能啃下滑坡分割1.1 滑坡语义分割到底在解决什么问题滑坡识别这件事业内经历了几个阶段。最早是纯人工目视解译专家拿立体镜看航片、卫片对着地形图勾边界精度尚可但效率极低一个县城范围就要忙几个星期根本没法应对大范围应急排查。后来流行面向对象分类用eCognition这类软件先分割再分类爽是爽但特征全是人设计的光谱、纹理、形状、上下文都要调换个地区就重新来一遍泛化能力很一般。深度学习做滑坡语义分割本质上是把“滑坡区域”当成一个像素级类别让网络自己学习光谱、纹理和上下文特征。你要做的只是准备一批标注好的影像然后训练一个端到端模型。实际应用时给进去一张遥感影像输出就是一张逐像素的滑坡区域二值图可以直接叠到GIS里做面积统计、风险制图。但滑坡这个目标有几个特点会让分割模型很头疼。第一边界不规则滑坡后壁、侧缘、堆积区都是渐变过渡没有建筑物那种锐利边缘第二同物异谱严重裸露土体和采石场、道路工地、裸岩石在光谱上非常像只靠颜色根本分不开第三类别极不平衡一张大影像里滑坡像素经常不到5%背景占压倒性多数第四尺度差异大小型滑坡只有几十米大型滑坡连绵几百米甚至上公里模型得同时兼顾小目标细节和大范围上下文。这几个问题叠加在一起普通分割模型很容易翻车。1.2 为什么是Mask2former而不是Deeplabv3/UNet先看主流的几个语义分割方案各有各的脾气。UNet靠编码器-解码器加跳跃连接结构简单小数据集上很稳局部细节也好但感受野有限目标一大人就抓不住全局上下文。Deeplabv3用空洞卷积加ASPP金字塔池化多尺度上下文能力强但上采样过程中边界细节容易糊尤其对滑坡这种不规则软边界不友好。Mask2former的思路完全不同。它不搞逐像素分类而是让一组可学习的query直接回归出二值掩码再配合masked attention机制把注意力约束在预测的目标区域内。说人话就是普通分割是一块块瓷砖判断“这是不是滑坡”Mask2former是先大致画几个形状再判断“这个形状像不像滑坡”。像滑坡这种边界模糊、需要全局判断的目标这种方式天然占便宜。我自己在同样环境下跑过对比50个epochUNet大概能到52%的mIoUDeeplabv3在55%左右Mask2former用Swin-T backbone能到61%-64%。更明显的是边界质量Mask2former预测出来的滑坡边界干净很多不会像逐像素分类那样边缘全是椒盐噪声。代价就是训练显存占用大、训练时间长但对精度有要求的场景这点成本值得掏。另外还有一个现实原因Mask2former本身是一个统一分割框架语义、实例、全景都吃。你这次用语义分割做滑坡区域提取下次想区分单个滑坡个体直接切成实例分割任务就行代码和数据集格式几乎不用换这个迁移成本很划算。1.3 技术栈与代码仓选型Mask2former目前有两个主流代码版本。一个是Facebook官方仓库facebookresearch/Mask2Former基于detectron2另一个是OpenMMLab的MMSegmentation移植版基于mmcv生态。我这次用的是官方版原因有三点预训练权重最全模型结构和论文完全一致社区issue里能搜到大量现成答案。官方版一个缺点是detectron2本身更新慢对最新版PyTorch支持滞后所以环境版本要稍微锁一下。我在文中用的是Python 3.9、PyTorch 1.10.1、CUDA 11.3这套组合实际测下来很稳。如果你是公司生产环境需要和mmcv那套生态打通用MMSegmentation也完全可以但自定义数据集时踩坑会多一些建议先跑通官方版再迁移。整体技术路线定下来后后面的工作基本就三条线并行数据准备、代码修改、训练调参。第一步就是弄数据集这事听着简单实际上我快八成的坑都踩在这里。2. 自定义滑坡数据集制作从原始影像到COCO格式2.1 数据来源与预处理数据是滑坡分割的命根子。来源一般有三类无人机航飞正射影像分辨率高适合小范围精细制图卫星影像覆盖广适合区域尺度普查还有高校和科研机构公开的滑坡样本集用来做预训练或者补充样本挺方便。我这次混合了无人机和卫星两种数据目的是让模型跨分辨率也能扛得住。拿到原始影像后不能直接标注先做几步预处理。辐射定标和大气校正在多光谱数据上是必做的不然不同时相的光谱差异会直接影响模型泛化。如果原始影像是16bit的一定要转成8bit再做直方图拉伸否则后续标注和训练读图都会出问题。然后就是正射校正和镶嵌保证几何位置没有畸变。最关键的预处理是裁剪。Mask2former输入的patch大小直接决定显存消耗和上下文范围而且滑坡往往比单块patch大切碎了就学不到完整的滑坡形态。我的经验是训练patch用512x512或者1024x1024裁剪时留10%-20%的重叠率。重叠的意义在于滑坡边界如果正好切在patch边缘重叠区域可以保证另一块patch里能看到完整目标。裁剪的时候顺便记录每块patch的地理坐标后面预测完要叠回GIS里会非常方便。2.2 标注规范与工具链标注工具我用的是LabelMe因为它的JSON格式容易解析而且支持在线多人协同审核。另一个好用的工具是EISeg交互式分割效率高对滑坡这种边界模糊的目标先点几下让模型生成候选边界再微调比纯手动描点快很多。标注规范这块必须提前定死否则不同人标出来的数据集会毁掉整个模型。我在项目里定了几条规矩只标“滑坡体”不标“已治理的坡面”和“泥石流沟道”边界勾到手绘最外缘后缘、侧缘、堆积区连成闭合多边形堆积区下缘以剪切出口为界遇到阴影、云遮挡导致无法判断的区域宁可不标也绝不乱标。实际执行时安排一个人主标另一个人抽检边界拿不准的样本双人协商统一口径。这一步多花的时间后面训练阶段全都能省回来。2.3 LabelMe标注转COCO的Python脚本LabelMe标注导出的是JSON但Mask2former/detectron2的DatasetCatalog默认读的是COCO格式所以要写一个转换脚本。核心逻辑不复杂遍历所有LabelMe的JSON把多边形的points展平成COCO的segmentation格式再算出bbox和面积最后汇总成COCO json。import json import glob import os import numpy as np from skimage import draw CATEGORY_DICT {landslide: 1} # 0 留给背景 def labelme_to_coco(labelme_dir, output_json): images [] annotations [] ann_id 1 img_id 1 for labelme_path in sorted(glob.glob(os.path.join(labelme_dir, *.json))): with open(labelme_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] file_name os.path.basename(labelme_path).replace(.json, .jpg) images.append({ id: img_id, file_name: file_name, width: img_w, height: img_h }) for shape in data[shapes]: label shape[label] if label not in CATEGORY_DICT: continue points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, y_min float(min(xs)), float(min(ys)) x_max, y_max float(max(xs)), float(max(ys)) bbox [x_min, y_min, x_max - x_min, y_max - y_min] # 多边形面积用栅格化计算比公式算更稳 rr, cc draw.polygon( [int(p[1]) for p in points], [int(p[0]) for p in points], shape(img_h, img_w) ) area float(len(rr)) # 展平为COCO多边形格式 poly np.asarray(points, dtypenp.float64).flatten().tolist() annotations.append({ id: ann_id, image_id: img_id, category_id: CATEGORY_DICT[label], segmentation: [poly], bbox: bbox, area: area, iscrowd: 0 }) ann_id 1 img_id 1 coco_format { images: images, annotations: annotations, categories: [{id: 1, name: landslide}] } with open(output_json, w, encodingutf-8) as f: json.dump(coco_format, f, ensure_asciiFalse) if __name__ __main__: labelme_to_coco(dataset/train/labelme/, dataset/annotations/train_coco.json)有几个细节特别容易踩坑。COCO的segmentation虽然是浮点坐标也能读但建议转成整数并确保多边形闭合不然后期可视化时会报错。bbox一定是xywh格式很多人习惯性写成xyxy就完蛋了。area用shapely算面积也行但用栅格化计算更接近真实像素面积实测偏差更小。另外如果一张图里多个滑坡多边形有重叠训练时mask会冲突转换后最好跑一遍检查把重叠区域用逻辑或合并掉。2.4 数据划分与目录组织数据划分这块最大的坑是空间数据泄漏。同一个滑坡体如果一部分裁进训练集、一部分裁进验证集那验证集的指标会虚高得离谱因为模型其实已经“见过”这个目标了。所以一定是按地理范围划分同一座山、同一个滑坡的所有patch要么进train要么进val不能两边都有。目录结构我习惯这样组织清晰好维护dataset/ ├── train/ │ ├── images/ │ └── labelme/ ├── val/ │ └── images/ ├── annotations/ │ ├── train_coco.json │ └── val_coco.json └── checkpoints/样本量这个问题经常被问。严格说没有标准答案但按我的经验如果正样本像素占比能到10%左右裁剪图300到500张就能训练出一个效果不错的模型。如果不到100张建议直接用Swin-T这种小backbone加预训练权重数据增强强度开大先跑通流程再说。3. 环境配置与训练核心参数3.1 环境安装与版本兼容环境这块最折磨人的就是detectron2编译。千万不要自作聪明用最新版PyTorchdetectron2和PyTorch的版本是绑定的版本对不上编译直接报错。我实测比较稳的组合是Ubuntu 20.04、Python 3.9、CUDA 11.3、PyTorch 1.10.1。conda create -n mask2former python3.9 conda activate mask2former pip install torch1.10.1cu113 torchvision0.11.2cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html git clone https://github.com/facebookresearch/Mask2Former cd Mask2Former python -m pip install -e .安装完之后务必先做个冒烟测试能正常导入detectron2就说明编译成功了。如果编译过程中遇到缺头文件的报错多半是gcc版本或者CUDA工具链问题把gcc切到8.x通常能解决。这一步卡住的人最多建议Cold Start前先把自己的环境跑到这一步。import detectron2 print(detectron2 version:, detectron2.__version__)3.2 注册自定义数据集Mask2former自带的数据集加载逻辑是在detectron2的DatasetCatalog里注册的。你要做的就是在train_net.py或者自己写一个注册文件中添加我们数据集的注册函数。from detectron2.data import DatasetCatalog, MetadataCatalog from detectron2.data.datasets import load_coco_json def register_landslide(): for split in [train, val]: DatasetCatalog.register( flandslide_{split}, lambda ssplit: load_coco_json( fdataset/annotations/{s}_coco.json, fdataset/{s}/images ) ) MetadataCatalog.get(flandslide_{split}).set( thing_classes[landslide], stuff_classes[background] )这里有个细节detectron2读COCO时会默认有背景类还是只有thing类取决于你调用的加载函数。对于语义分割任务MetadataCatalog里的stuff_classes会影响评估阶段类别名的显示真正影响网络输出的还是配置里的NUM_CLASSES。注册完之后如果报错“Dataset landslide_train is not registered!”八成是注册函数压根没被调用。3.3 Mask2former核心配置解析官方仓库的configs目录下提供了一堆YAML模板我一般以maskformer2_R50_bs16_50ep.yaml为底子改。这个文件内容很长真正要动的核心参数并不多。MODEL: SEM_SEG_HEAD: NUM_CLASSES: 2 WEIGHTS: weight/maskformer2_swin_tiny_bs16_50ep.pkl DATASETS: TRAIN: (landslide_train,) TEST: (landslide_val,) INPUT: MIN_SIZE_TRAIN: (512,) MAX_SIZE_TRAIN: 1024 MIN_SIZE_TEST: 512 SOLVER: MAX_ITER: 80000 CHECKPOINT_PERIOD: 5000 LR: 0.0001 WEIGHT_DECAY: 0.05 TEST: EVAL_PERIOD: 5000很多第一次用的人会把MAX_ITER理解成epoch这是完全不对的。MAX_ITER是迭代次数换算公式是期望的epoch数乘以训练图数再除以batch size。比如你有600张训练图batch size设为8想跑60个epoch那MAX_ITER就是4500左右。NUM_CLASSES这里设成2是因为COCO格式里背景是0滑坡是1网络输出维度要和这个对应上。预训练权重推荐用Swin-T的显存友好收敛也快等流程理顺了再上Swin-L不迟。3.4 损失函数与优化器调整Mask2former默认的损失组合是mask cross-entropy加dice loss加分类loss这个组合对类别不平衡的鲁棒性已经比普通逐像素CE好很多。原因在于逐像素CE是全图一起算背景像素把损失淹没了Mask2former是每个query对应一个候选区域单独算损失滑坡就算占比很小只要query能框住它损失照样更新。但滑坡数据极端不平衡时还是要动点手脚。我的做法是先统计训练集里的正样本像素占比如果低于3%就把dice loss的权重调高一些。另一种做法是在数据增强阶段更有针对性地裁剪强制让训练patch里滑坡占比控制在5%到30%之间这个甚至比调loss更有效。优化器这块没什么玄学AdamW初始学习率0.0001权重衰减0.05warmup迭代1000步学习率策略用poly衰减。唯一要注意的是batch size尽量保持在8以上否则Swin这类含BN的backbone在小batch下统计量不稳定loss会像过山车。实在显存不够可以用梯度累积来模拟大batch。4. 训练实操与结果分析4.1 启动训练与日志解读跑训练的命令其实很简单关键在参数要不要覆盖配置文件里的默认值。python train_net.py \ --num-gpus 1 \ --config-file configs/maskformer2_R50_bs16_50ep.yaml \ OUTPUT_DIR output/landslide单卡显存建议至少24GB这样batch size能开到8左右A5000、3090都没问题。16GB显存的卡就老实把batch size降到4或者把输入分辨率降到480用Swin-T再开一下AMP混合精度。训练日志里每一轮会打印total_loss、mask_loss、dice_loss、cls_loss这些指标看一眼loss曲线就能大致判断模型状态。有个很多人不知道的小技巧日志里data_time如果远大于iter_time说明数据加载成了瓶颈多半是磁盘IO太慢或者dataloader的worker数设太少。遥感影像大加载慢很正常这个时候改训练参数没用先把worker数调大或者影像提前转成LMDB。4.2 从loss曲线到收敛判断正常的训练前1千次迭代total_loss会快速下降这是模型在学最粗粒度的滑坡和背景区分。后面几千次迭代下降速度变慢loss会进入一个缓慢下降区间这时候模型开始磨边界和细节。如果dice_loss下降特别慢极有可能是标注本身有错或者正样本比例太低模型还没建立起来对“滑坡到底是什么”的稳定认知。我这边一个比较典型的训练记录是Swin-T backbone加1000多张训练图50个epoch后验证mIoU从最初的47%升到61%继续加训到80个epoch能再涨到64%左右。也就是说如果到50ep后验证指标还在明显上涨说明模型还没收敛别急着停继续训练就完事了。反过来如果训练loss一直在降但验证指标不涨那就是过拟合了滑坡这种小数据集尤其容易过拟合此时考虑数据增强和权重衰减。4.3 模型评估与滑坡分割指标语义分割主流的指标是IoU和mIoU但滑坡这个应用场景我更推荐同时盯住Recall和F1。原因很简单漏判一段滑坡造成的后果比误判一块裸露地面要严重得多。你在灾害排查里宁可多报几个可疑区域让专家去复核也不能把真正的滑坡漏过去。指标定义滑坡场景下的意义IoU交并比预测与真实的交集除以并集衡量分割区域和真实区域的重合程度mIoU所有类别IoU的平均综合衡量模型在所有类别上的表现Recall预测为正的滑坡像素占真实滑坡像素的比例漏判率滑坡场景最看重Precision预测为正的像素中真正的滑坡比例误判率误判多说明模型还没学准F1Recall和Precision的调和平均平衡指标模型调优时看它detectron2自带的评估器在语义分割任务上可以输出IoU但有些版本对自定义stuff类支持得不好所以我经常直接把自己的预测结果保存成mask再用Python脚本单独算指标。from sklearn.metrics import jaccard_score, f1_score # pred和gt都是(H,W)的0/1数组 iou jaccard_score(gt.flatten(), pred.flatten()) f1 f1_score(gt.flatten(), pred.flatten())4.4 推理可视化与后处理推理阶段官方仓库的demo脚本可以直接出叠加图。python demo/demo.py \ --config-file configs/maskformer2_R50_bs16_50ep.yaml \ --input image.jpg \ --output result.jpg \ --opts MODEL.WEIGHTS output/landslide/model_final.pkl对滑坡这个场景直接出图还不够我一般加三道后处理。第一阈值下调到0.4而不是默认的0.5因为漏检代价高宁可多预测一些第二去掉面积小于某个阈值的连通域比如100像素的碎块基本都是误检第三对前景做一遍小核的闭运算填平滑坡区域内部的小孔洞。注意闭运算核别开太大不然边界会钝化适得其反。如果面对的是整景大影像就涉及tile推理。切成patch预测时相邻patch要有重叠区域重叠部分取概率平均值这样能避免拼接处出现明显的接缝。预测完记得把坐标映射回原始影像坐标系方便直接在GIS里叠加分析。5. 常见问题与排查技巧实录5.1 显存不足与训练崩溃“CUDA out of memory”是问得最多的报错。处理优先级是这样先降batch size到4还不行就降输入分辨率把MAX_SIZE_TRAIN从1536降到1024再不行换Swin-T这种小backbone最后才是开AMP混合精度。AMP在detectron2里不是默认开启的需要显式加SOLVER.AMP.ENABLED True。另一个比较隐蔽的坑是有人直接改配置文件里的SOLVER.IMS_PER_BATCH结果报错说lr 0.01对batch size 8太大。这是因为detectron2默认优化器配置是给COCO那个大batch size配套的你改batch size的同时必须确认LR和WARMUP都匹配别只动一个参数。5.2 全背景陷阱这是滑坡分割最容易翻车的地方。训练完你兴高采烈去验证发现所有影像预测出来全是背景一个滑坡像素都没有但训练loss确实在降。原因就是背景占绝对主导模型学到一个“全部预测为背景”的局部最优解。排查思路分三步。第一步统计训练集的正样本像素占比随机抽20张图算一下滑坡像素比例第二步可视化batch确认数据增强后训练图上确实能看到滑坡目标第三步看dice_loss如果这个loss从头到尾没怎么动那基本就是退化到了全背景解。解决手段我在前面提过核心一句话保证每张训练patch里至少有5%以上的正样本像素做不到就改裁剪策略。5.3 标注质量与边界修复标注质量问题会从各种角度反噬你。最常见的是多边形自相交LabelMe上手滑就会有这种问题转换时最好用shapely的make_valid处理一遍。另一个是多个滑坡多边形重叠训练时同一像素既是滑坡又是背景梯度直接乱掉转换脚本里要检测重叠区域。还有一种情况是滑坡边界标得太毛糙用多边形逼近圆弧时折线痕迹太重这种靠预测完成后的人工修边界来解决不要指望模型能自动抹平。我这边的经验是标注质量的优先级高于模型调参。600张高质量标注数据的效果往往比1500张粗糙标注的数据还好。所以前期的标注规范一定要定清楚宁可慢不要乱。5.4 推理速度优化Swin-T加fp16512分辨率单张A5000上推理大约0.15到0.3秒基本够用。如果业务要求更高吞吐有两条路一条是用TensorRT做模型加速detectron2官方提供了导出工具但Swin这种结构在TRT上偶尔会出兼容问题需要仔细调试另一条是把模型转到MMSegmentation版再导ONNX很多人在工程化阶段是这么干的。如果只是临时应急出图多卡并行批量跑demo脚本就足够了没必要一上来就碰推理优化。最后再聊两句这个项目做完我最大的体会是滑坡分割这类遥感应用数据质量决定天花板模型选择决定你能多接近这个天花板。Mask2former确实是我试过的模型里对滑坡这种不规则目标最友好的但如果没有一套规范的标注流程和合理的数据划分策略再好的模型也白搭。后续扩展的方向也很多。想区分单个滑坡个体任务切成实例分割就行Mask2former天然支持想做滑坡变化检测可以搞多时相影像输入或者先跑两遍单时相分割再做变化分析想进一步提升精度把DEM坡度、地形因子作为额外通道输进去效果还会有明显提升。建议先把这条单类语义分割的流程跑通再一步步扩展路就顺了。
返回列表