简介基于PyTorch实现的遥感图像滑坡识别项目面向深度学习研究人员、地质灾害监测工程师及遥感图像处理学习者提供从模型设计到训练评估的完整闭环。项目采用CNN自动提取影像特征免去人工设计复杂特征工程可对滑坡区域进行定位与分类适用于防灾减灾、环境监测等场景。资源共122个文件以Python源码18个py为核心涵盖网络构建、数据加载、训练及验证等环节配套96个xml标注文件用于目标区域标定另有txt配置、md项目说明及字体文件压缩包约4.93MB。已有60人学习浏览。除了完整可运行的源码和训练好的模型外还附带已标注的遥感图像数据集用户可直接调用预训练权重进行推理也可基于项目说明文档理解训练关键步骤并二次开发。整体轻量紧凑适合快速上手和学术研究。1. 遥感滑坡识别这个项目拿到手到底该怎么拆做遥感解译的同行对“滑坡识别”四个字应该不陌生传统方法靠目视解译一个熟练工程师一天能勾几块滑坡体就算高效而基于深度学习CNN的图像分割方法一旦跑通可以把解译速度提升一个量级。这套基于PyTorch框架的滑坡识别源码包包含完整的数据集、训练好的模型权重和项目说明文档目标就是让你跳过从零搭环境的环节直接在遥感图像上把滑坡区域逐像素识别出来。适合的读者很明确准备做地质灾害监测、遥感图像语义分割课题或者想用现成数据集复现一个完整深度学习项目的人。值得注意的是滑坡识别本质上不是图像分类而是逐像素分割问题这也决定了后续所有技术选型的走向它比普通目标检测的应用边界更贴近真实应急场景。2. 数据与模型选型先理解滑坡识别为什么是分割问题再碰代码2.1 滑坡体形态决定了不能用目标检测滑坡区域在遥感影像上的特征非常特殊呈不规则的舌状、圈椅状或长条状边界与山体阴影、裸露岩层交织很多时候滑坡体的一部分被植被覆盖另一部分在色调上和周围坡地几乎没有区别。如果采用目标检测思路用矩形框去圈定滑坡位置会出现两个直接问题矩形框必然把大量非滑坡像素圈入框内导致后续误判率升高小规模滑坡体在框内占比极低检测器很难学习到有效特征。分类网络更不用谈它只能输出“有滑坡”或“无滑坡”的图像级结论在应急场景里没有决策价值。语义分割是正解。它给每一个像素分配一个类别标签滑坡区域和非滑坡区域在边界上可以被精确刻画。编码器-解码器结构是这类任务的主流框架。编码器负责逐级下采样提取语义特征解码器负责逐步恢复分辨率并输出逐像素预测。在这套资源中主力模型是DeepLabV3它以空洞卷积为核心组件在保持特征图分辨率的同时扩大感受野对滑坡这种动辄跨越数十米到数百米的地表目标很有效。备选的U-Net结构轻量得多跳跃连接能把浅层细节直接送到解码器适合在小显存机器上做快速验证。个人在类似项目中一般先用U-Net确认数据和loss曲线正常再切到DeepLabV3做正式训练。2.2 数据集目录结构与标注格式训练前先理清文件关系下载压缩包解压后第一步不是急着配环境跑训练而是把数据目录结构看懂。遥感滑坡数据集的常见组织方式是影像和掩码一一对应存放在两个平行目录中landslide_dataset/ ├── images/ # 遥感影像TIF/PNG多数为RGB三波段 │ ├── slide_001.tif │ ├── slide_002.tif │ └── ... ├── masks/ # 滑坡标注掩码与影像文件名一一对应 │ ├── slide_001_mask.png │ ├── slide_002_mask.png │ └── ... └── train_val_split.txt # 训练/验证划分清单掩码图的像素值一般有两种表示灰度图用0表示背景、255表示滑坡彩色图用黑色背景加红色滑坡区域。训练前务必确认掩码的类别值是否已经归一化。如果读入的掩码是0和255需要在数据加载阶段做一步像素映射把255统一替换为1否则CrossEntropyLoss的类别数会与网络输出维度冲突训练直接报错。另外一个需要检查的点是影像和掩码的尺寸是否一致。遥感TIF经常带着地理坐标信息有的掩码文件尺寸和原图存在细微偏差。传统的检查方式是遍历一遍数据打印出所有图像的shape是否和掩码shape一致不一致的先排除或重采样对齐。这个问题不提前处理训练过程中几乎不会立即报错但会造成图像和标签空间位置错位表现为loss下降异常缓慢验证指标一直上不去排查起来非常浪费时间。2.3 预处理与数据增强遥感影像归一化不能照搬ImageNet遥感影像的像素分布和自然图像差别很大。很多滑坡影像来自卫星或无人机亮度动态范围宽不同传感器之间的色调差异明显。直接套用ImageNet预训练权重对应的mean和std往往不是最优做法因为ImageNet的统计量是为自然照片设计的而遥感图像中植被、裸土、水体、阴影的分布完全不同。我一般会在训练前用训练集自己算一遍均值和标准差把图像归一化到接近标准正态分布再送进网络。如果训练数据和测试数据来自同一传感器这个步骤收益尤其明显。数据增强策略同样需要针对滑坡场景做调整。翻转、旋转、随机裁剪是基础项其中90度旋转对滑坡这种方向性不明显的目标很适用。亮度对比度扰动也有必要因为同一地区多期影像之间的光照差异很大。但增强代码有一个关键约束图像做几何变换的同时掩码必须同步执行完全一样的变换。不少初学项目在这里翻车图像翻转了而掩码没有训练出来模型效果自然不对。import cv2 import numpy as np import random def aug_image_mask(image, mask): # 图像和掩码同步做几何变换 if random.random() 0.5: image cv2.flip(image, 1) # 水平翻转 mask cv2.flip(mask, 1) if random.random() 0.5: image cv2.flip(image, 0) # 垂直翻转 mask cv2.flip(mask, 0) k random.choice([0, 1, 2, 3]) # 随机90度旋转 if k 0: image np.rot90(image, k) mask np.rot90(mask, k) return image, mask这段增强逻辑的核心就是保持图像和掩码的同步变换。如果掩码是0/255二值图旋转翻转没有问题但需要保证后续one-hot编码正确。另一个容易被忽略的细节是dataloader的worker随机种子如果不固定每次取样本的增强顺序都会变化训练结果难以复现。常见的做法是在PyTorch的DataLoader里设置worker_init_fn传入一个固定的numpy种子这样每次跑训练的数据序一致调参才有参照。数据增强对滑坡分割特别重要因为滑坡样本本身就少不做增强很容易过拟合到训练集上。3. PyTorch训练与推理网络结构、混合loss、lr策略与模型保存3.1 网络结构选择DeepLabV3做主力U-Net做快速验证这套资源的模型部分包含了两个训练入口。主力模型是DeepLabV3它基于ResNet做编码器在编码器尾部接ASPP模块通过不同膨胀率的空洞卷积并行抽取多尺度特征再送入解码器恢复细节。ResNet50版本的参数规模在41M左右512x512输入下显存占用约6到8GB适合单卡2060SUPER以上显卡训练。ResNet101版本参数更多精度上限略高但显存占用经常超过10GB普通消费级显卡跑起来局促。U-Net的结构相对朴素编码器逐级下采样解码器逐级上采样跳跃连接把同尺度的编码器特征拼接到解码器。用一个轻量backbone比如ResNet34做编码器时参数量在24M左右显存占用能控制在6GB以内训练速度快不少。U-Net的边界精细度略逊于DeepLabV3但在样本量不大的滑坡场景里二者差距经常没有想象中大。选择依据其实很简单小显存、快速验证、资源紧张时用U-Net先跑通全流程把loss曲线和验证Dice曲线观察清楚显存充足、追求最优效果时用DeepLabV3配合预训练backbone做迁移学习。这里有一个实用的经验第一次运行训练脚本时先把batch size设为2epochs设为5跑通一个迷你版本确认数据流没有问题再去启动长时训练省得跑两小时后才发现dataloader报错。3.2 训练脚本核心混合loss处理滑坡小额占比优化器用AdamW滑坡识别的类别不平衡问题比普通分割任务更严重。一张512x512的遥感图像中滑坡区域往往只占几百到几千像素背景占了九成以上。在这种极度不平衡的状态下单独使用CrossEntropyLoss模型很容易把全部像素预测为背景因为仅凭背景类别就能拿到很低的loss。这个资源里提供的训练脚本用的是DiceLoss与CrossEntropy的加权混合思路是让模型既按像素分类又从区域重叠度上接受监督。import torch import torch.nn as nn import torch.nn.functional as F class DiceLoss(nn.Module): def __init__(self, smooth1.0): super().__init__() self.smooth smooth def forward(self, logits, targets): # logits: [B, C, H, W] 预测分数 # targets: [B, H, W] 类别索引 probs F.softmax(logits, dim1) one_hot F.one_hot(targets, num_classesprobs.shape[1]).permute(0, 3, 1, 2).float() intersection (probs * one_hot).sum(dim(2, 3)) union probs.sum(dim(2, 3)) one_hot.sum(dim(2, 3)) self.smooth dice (2.0 * intersection self.smooth) / union return 1.0 - dice.mean() ce_loss nn.CrossEntropyLoss(weighttorch.tensor([0.2, 1.0])) bce_part ce_loss(logits, targets) dice_part DiceLoss()(logits, targets) final_loss bce_part dice_part这个混合loss的设计逻辑是CrossEntropy部分对每个像素独立计算分类误差DiceLoss部分从“预测区域与真实区域的重叠程度”上施加约束。DiceLoss天然不敏感于类别不平衡因为它在计算时已经把前景和背景按区域占比做了归一化。交叉熵的类别权重在这里不用设置得太悬殊0.2对1.0已经足够因为DiceLoss本身已经在纠正不平衡权重过大会增加假阳性反而把背景像素大面积误判为滑坡。smooth参数起的是平滑和防除零作用一般设为1.0即可。优化器选的是AdamW。AdamW相比原版Adam把权重衰减从梯度更新中解耦实测在分割任务里过拟合现象减轻收敛也更加稳定。weight_decay一般设置在1e-4到5e-4之间学习率初始值推荐1e-4这个值在大多数分割backbone上都适用。学习率调度使用ReduceLROnPlateau监控验证集的Dice指标连续多个epoch不提升则将学习率减半。相比直接从开始就用余弦退火这种方式更容易判断当前训练状态是否正常。训练显存优化方面如果单卡显存只有8GB可以用torch.cuda.amp做混合精度训练前向和反向过程在fp16下进行梯度更新时用fp32显存占用能降低近一半训练速度也有明显提升。3.3 训练循环与模型保存只保存验证集Dice最高的模型训练循环本身不算复杂但有几个工程细节决定成败。模型状态切换需要留意训练时调用model.train()验证时调用model.eval()否则BatchNorm统计量计算混乱。梯度清零要用optimizer.zero_grad()然后前向、计算loss、反向、更新优化器这个顺序不能乱。每个epoch结束后在验证集上计算Dice或mIoU用val_dice驱动学习率调度并决定是否保存模型。from torch.cuda.amp import autocast, GradScaler model DeepLabV3Plus(num_classes2).cuda() optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, modemax, factor0.5, patience10) scaler GradScaler() best_dice 0.0 for epoch in range(50): model.train() for images, masks in train_loader: images images.cuda() masks masks.cuda().long() optimizer.zero_grad() with autocast(): logits model(images) loss ce_loss(logits, masks) DiceLoss()(logits, masks) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() # 验证阶段 val_dice evaluate(model, val_loader) scheduler.step(val_dice) if val_dice best_dice: best_dice val_dice torch.save(model.state_dict(), ./best_slide_model.pth)这段代码里混合精度部分用autocast包裹前向和loss计算scaler负责梯度缩放。validate函数里每个batch都在torch.no_grad()下执行避免验证过程反向传播。scheduler是根据val_dice来调整学习率的这比用训练loss调整更科学因为训练loss持续下降但验证Dice停滞基本可以断定过拟合或者数据划分有问题。保存模型时只保存state_dict而非整个模型对象这样后续推理脚本加载非常方便。训练完成后best_slide_model.pth就是这份资源里那个训练好的模型权重直接用它在新的遥感影像上做推理测试是验证整个项目是否能跑通的快捷方式。3.4 推理脚本与阈值选择0.5阈值容易漏检小滑坡训练好的模型在做推理时有一个很隐蔽的问题是阈值选择。模型输出的滑坡概率图是连续值需要设定一个阈值来二值化。很多人习惯拿0.5当默认阈值但在滑坡识别场景里0.5阈值会明显漏检。原因是滑坡边缘区域的模型响应普遍偏弱概率值在0.4到0.7之间如果卡在0.5一大片边界区域会被强行归为背景漏检率上升。另一个原因是遥感图像上的阴影区域有时候也会有0.3到0.5的响应阈值定低了容易把阴影误判为滑坡。import torch import cv2 import numpy as np model.load_state_dict(torch.load(./best_slide_model.pth, map_locationcpu)) model.eval() with torch.no_grad(): image_tensor torch.from_numpy(img_rgb / 255.0).float().permute(2, 0, 1).unsqueeze(0) logits model(image_tensor.cuda()) prob torch.softmax(logits, dim1)[0, 1].cpu().numpy() # 阈值选择先输出概率图直方图再定阈值 thresh_value 0.65 pred_mask (prob thresh_value).astype(np.uint8) kernel np.ones((5, 5), np.uint8) pred_mask cv2.morphologyEx(pred_mask, cv2.MORPH_OPEN, kernel)这里的预测流程是加载权重、切换eval模式、前向推理、softmax取滑坡类概率。归一化必须和训练时完全一致比如训练时用了除以255再减mean再除std推理时也要执行相同预处理。阈值0.65是我在这个场景下的经验起点适用于大部分中等分辨率遥感影像但对不同传感器数据需要微调。更好的做法是对验证集遍历0.5到0.8的阈值画出Dice曲线取峰值。后处理里加一个开运算能去掉单个孤立像素的假阳性噪声这种零散噪点在滑坡面积统计时会使结果偏大。4. 避坑笔记遥感滑坡识别项目中常见的五个翻车现场4.1 整景大图直接推理导致显存溢出现象把一张5000x5000像素的遥感大图直接送进模型推理进程中途报CUDA out of memory卡死。原因模型输入是固定尺寸patch但你把整张图塞进去实际相当于跑了一个极大batch的forward显存占用与图像面积成正比远超GPU承载能力。解决采用滑窗推理。设定512x512窗口stride取256到384逐个patch推理后把概率图拼回原尺寸重叠区域取平均而非直接覆盖避免拼接缝明显。这个推理方式在代码里需要额外处理边缘区域图像宽高不能被stride整除时最后一列和最后一行的patch要单独补跑一遍。4.2 验证集Dice虚高但新区域效果差现象在划分好的验证集上Dice达到85%以上但换一块新的遥感影像区域测试效果明显变差。原因数据切patch时把同一块滑坡体切成了多个patch部分patch进了训练集、部分进了验证集造成数据泄漏。模型其实在验证集上见到过同一片滑坡的相邻切片指标虚高泛化能力被高估。解决按滑坡对象划分数据而不是按patch划分。先把每个滑坡体看作一个整体同一区域的所有patch要么全进训练集要么全进验证集。实际操作里可以按照坐标范围或文件名前缀分组把数据切分提升到地块粒度。这个教训在滑坡分割里太常见了不少公开数据集都存在这个问题。4.3 训练loss一直在降但预测结果全黑现象loss从0.8降到0.2看着正常收敛但推理出来的掩码全是背景没有任何滑坡像素。原因类别极端不平衡时模型学到了一个局部最优解把所有像素都预测为背景。尤其是交叉熵权重设置不合适时背景类别贡献了绝大多数loss梯度模型很快就陷进这个捷径里。解决直接换成纯DiceLoss跑一轮确认模型能看到滑坡类别再换成混合loss做正式训练。如果还是全黑可以在loss中显式提高滑坡类别的权重或者检查数据增强是否把滑坡区域裁掉了。还有一种可能训练集里有些图像的滑坡区域面积太小切成patch后滑坡部分只有零星几个像素数据增强一翻转可能就翻出图外了需要把这类样本过滤掉。4.4 归一化参数不一致导致推理结果漂移现象训练时指标正常把模型权重换到另一台机器或者另一个预处理脚本里推理概率图整体偏移预测结果完全不可用。原因推理脚本里用了ImageNet默认的mean和std或者不同的代码间预处理逻辑不一致。遥感影像的像素值分布和自然图像差异很大用错归一化参数会把模型输入的分布整体推偏。解决把归一化参数写死在一个配置文件里训练和推理统一读取。更稳妥的办法是改用百分比裁剪的归一化方式比如把影像像素值从2%到98%分位拉伸到0到255不依赖固定的均值方差统计量这样跨数据集的迁移鲁棒性更强。这个方案在遥感项目中越来越常用因为它对不同卫星、不同光照条件下的影像都能自适应。4.5 单波段影像与三通道输入模型不兼容现象部分滑坡影像是单波段灰度TIF训练时报输入通道数不匹配的错误。原因模型第一层卷积输入通道数为3单波段图像读进来只有1个通道维度对不上。解决在数据加载阶段统一判断通道数单波段图像复制三次为三通道四波段及以上的多光谱图像按需取其中三个波段。针对滑坡识别场景优先保留近红外、红光、绿光组合可以更好地刻画植被覆盖和裸土区域。这个转换在训练和推理阶段都必须一致否则部署阶段才暴露问题。这五类坑里4.2和4.3对模型效果的影响最为致命。如果你拿到的训练好的模型在自己数据上效果不理想优先排查这两个问题。5. 进阶滑窗拼接整幅遥感图并估算滑坡面积当模型能稳定输出单张patch的滑坡概率图之后更接近业务需求的下一步是把结果拼回整幅遥感影像并估算滑坡区域的面积。这个过程的工程复杂度其实高于训练本身因为坐标对齐、重叠区域融合、像素面积换算三个环节都可能出问题。滑窗推理的核心是控制stride与窗口大小的关系。 窗口选512x512stride越小重叠区域越多推理结果越平滑但耗时成倍增加。stride为256时每张patch有四分之一区域被重复推理拼接效果比较理想stride为384时速度更快但拼接缝容易露出轻微痕迹。重叠区域融合用累加平均的方式每个像素位置记录累加概率值和累加次数最后相除比直接覆盖后拼出的图更自然。图像边缘需要单独处理超出图像范围的区域用边缘像素值填充避免推理结果出现黑色边框。面积统计的前提是知道影像的空间分辨率。如果原始影像带了地理坐标信息可以通过读取元数据获得分辨率。如果没有需要从项目说明或遥感数据源中确认。比如0.5米分辨率的影像一个像素对应0.25平方米一个典型滑坡如果占据一万个像素对应面积就是2500平方米。计算完成后还应该做一次可视化验证把预测掩码与原图半透明叠加人工确认滑坡边界与真实坡面形态是否一致。import numpy as np def sliding_window_inference(model, full_img, window512, stride384, threshold0.65): h, w full_img.shape[:2] prob_map np.zeros((h, w), dtypenp.float32) count_map np.zeros((h, w), dtypenp.float32) for y in range(0, h - window 1, stride): for x in range(0, w - window 1, stride): patch full_img[y:ywindow, x:xwindow] prob model_predict(model, patch) prob_map[y:ywindow, x:xwindow] prob count_map[y:ywindow, x:xwindow] 1 valid count_map 0 prob_map[valid] / count_map[valid] return prob_map g 0.5 # 单像素地面分辨率单位米 area_per_pixel g * g mask_final prob_map 0.65 landslide_area mask_final.sum() * area_per_pixel print(f估算滑坡面积: {landslide_area:.2f} 平方米)这段代码把滑窗推理和面积统计串在了一条流程里。area_per_pixel的精度直接决定了面积统计的正确性需要特别确认自己数据的空间分辨率。如果预测掩码里有大量孤立噪点建议先做一次形态学开运算再统计面积不然五个孤立像素就能让面积多出好几平方米。输出结果阶段建议把原图、预测掩码、叠加图三张图横向拼成一张对比图既可以用于交付展示也方便人工校验。我曾有一次在面积统计环节把分辨率误写成了1米实际数据是0.5米导致滑坡面积被夸大了四倍排查到最后才发现是单位换算的问题。从那以后我每次推完一幅影像都会强制把预测结果和原图叠加目视检查边界是否吻合、面积数量级是否符合该区域的地质背景确认无误后才敢把数据写进报告。这套从数据集到训练再到推理和面积统计的整体流程希望你也能完整走一遍多数遥感滑坡识别项目真正体现价值的地方恰恰不只是模型精度而是从原始影像到可量化结论的整条链路。希望帮到你。本文还有配套的精品资源点击获取