ARTICLE DETAIL

资讯详情

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

基于CNN与PyTorch的遥感滑坡识别全流程解析

基于CNN与PyTorch的遥感滑坡识别全流程解析 简介面向遥感图像滑坡识别任务基于卷积神经网络与PyTorch框架的完整工程包适合深度学习、遥感地灾方向的研发人员与学生使用。该项目以CNN自动提取图像特征实现对滑坡区域的定位与分类省去人工设计特征的繁琐过程。资源共122个文件压缩包大小4.93MB涵盖96个xml标注文件、18个py源码、6个txt配置文件、1个md说明文档及1个ttf字体文件其中py源码涉及数据加载、RPN区域建议网络、ResNet50骨干网络、训练与评估等完整流程xml与txt配合提供带标注的数据集和参数配置md文档则用于快速掌握项目结构。目前已有60人浏览学习可快速部署使用。借助训练好的模型用户无需重新训练即可直接进行滑坡检测同时完整代码与说明也便于二次开发适配其他遥感识别场景为地质灾害监测、城市规划等领域提供实用工具与参考。1. 遥感滑坡识别为什么绕不开CNN与PyTorch拿着高分辨率遥感影像去找滑坡体肉眼能做但换到几百平方公里、几十个时相的影像人工解译的效率就完全跟不上了。基于深度学习CNN网络和PyTorch框架做滑坡识别是目前这类任务里最成熟的一套落地路径CNN负责从影像里自动学滑坡的纹理、色调和地形特征PyTorch负责把训练、验证、推理全流程串起来。这个压缩包里的源码、数据集、训练好的模型和项目说明本质就是一条可复现的工作流。适合刚接触深度学习遥感应用、想在自己机器上跑通一个完整分割项目的从业者也适合需要快速评估滑坡识别效果的地质灾害方向工程师。2. 滑坡识别的模型选型先搞懂CNN在遥感图像里到底看什么2.1 为什么是CNN而不是传统机器学习或Transformer滑坡识别这个任务最朴素的解法是用随机森林或SVM对影像的NDVI、坡度、纹理特征做分类。但传统机器学习有两个硬伤第一特征工程依赖人工经验滑坡在影像上不是单纯的颜色异常——它可能比周围裸土亮也可能比裸土暗还可能被云影盖住第二传统方法逐像素提取特征天然丢失了空间上下文。一个像素是不是滑坡往往要看它周围几十米范围内有没有坡体变形痕迹、有没有植被破坏和堆积区这种“看周围”的能力恰好是CNN卷积核的强项。Transformer模型这两年也在遥感分割里出现像Swin Transformer、SegFormer全局建模能力强。但对滑坡识别来说Transformer需要更多数据才能收敛而且推理速度慢对显存要求高。滑坡训练集通常只有几千张切片CNN用ImageNet预训练权重做迁移学习收敛快、效果稳。不是Transformer不好而是这个任务的数据规模撑不起它的胃口。CNN仍然是遥感滑坡识别性价比最高的起点。PyTorch在这一步的价值在于torchvision里直接提供带预训练权重的backbone比如ResNet34、ResNet50也可以加载DeepLabV3的分割头。不需要自己发明网络结构关键是把输入输出通道、编码器下采样倍数、以及遥感影像的多波段特性处理好。2.2 分割与分类两条技术路线滑坡识别该选哪条做滑坡识别可以有两种建模思路很多人会在这上面走弯路。一种是图像分类把整张影像块判断为“有滑坡/无滑坡”。这类方案看起来简单但实际落地很别扭。因为滑坡边界才是地质灾害评估最关心的东西——滑体范围、堆积区边界、影响宽度。分类模型只能告诉你有滑坡给不出位置。而且一张512×512的影像里滑坡体可能只占几百个像素分类模型会学到“影像块是否包含滑坡”但学不到“每个像素是否是滑坡”。另一种是语义分割对每个像素预测类别输出和输入同尺寸的掩膜滑坡区域标为类别1背景标为类别0。这才是遥感滑坡识别的主流做法。语义分割的结果可以直接矢量化成滑坡边界叠加到地形图上做灾损统计也能按面积筛选误检碎块。所以如果压缩包里给的是分割模型训练好的权重输出的是掩膜说明作者走的是正路。如果给的是分类模型那只能用来做灾害快速筛查不能做边界提取。用的时候一定要先看README里的标注说明或用torchsummary打印模型输出形状确认。2.3 CNN分割骨干网络搭法Encoder-Decoder结构与PyTorch实现片段遥感滑坡识别的CNN分割模型常用结构是Encoder-Decoder。Encoder用CNN逐层下采样提取特征Decoder把特征图逐步恢复到原图分辨率中间用跳跃连接保留空间细节。Unet是这类任务的老典型DeepLabV3是用空洞卷积扩大感受野的改进。下面是一个基于PyTorch、用torchvision预训练backbone搭DeepLabV3的常见写法。注意遥感影像的通道数如果只有RGB三通道可以直接用ImageNet预训练权重如果用了多光谱数据比如R、G、B、NIR四通道就不能直接用预训练权重得自己改第一层。import torch.nn as nn from torchvision.models.segmentation import deeplabv3_resnet50 from torchvision.models.segmentation import DeepLabV3_ResNet50_Weights def build_seg_model(num_classes2, pretrainedTrue): # 使用在ImageNet上预训练过的DeepLabV3编码器是ResNet50 if pretrained: model deeplabv3_resnet50(weightsDeepLabV3_ResNet50_Weights.DEFAULT) else: model deeplabv3_resnet50(weightsNone) # DeepLabV3输出的是一个OrderedDict键名是outout通道数等于类别数 in_channels model.classifier[-1].in_channels model.classifier[-1] nn.Conv2d( in_channels, num_classes, kernel_size1, stride1 ) # 辅助分支aux只在训练时用推理时可以关掉不影响主输出形状 if model.aux_classifier is not None: in_channels_aux model.aux_classifier[-1].in_channels model.aux_classifier[-1] nn.Conv2d( in_channels_aux, num_classes, kernel_size1, stride1 ) return model model build_seg_model(num_classes2, pretrainedTrue) print(model)这段代码的逻辑是先加载候选分割模型然后把分类头替换成自己的类别数。滑坡识别一般分两类背景和滑坡所以num_classes传2。如果数据集里还细分了“滑坡体”“滑坡堆积区”“滑坡后缘裂缝”等就按实际标签数改。注意修改classifier[-1]时in_channels要取旧分类头的输入通道数不能拍脑袋写512或2048否则会出现维度不匹配的报错。pretrained参数在这个任务里非常关键。遥感影像和自然图像有一定差异但ResNet50在ImageNet上学到的边缘、纹理基础特征依然能迁移到滑坡识别上。直接随机初始化CNN从头训练通常需要更多样本来弥补而且收敛速度会慢很多。我一般会在预训练权重的基础上微调初始学习率从1e-4开始比从零训练稳得多。如果输入影像不是RGB而是带了近红外波段的四通道数据就要改写第一层卷积。常见做法是保留预训练权重的均值把RGB三个通道的权重复制一份给第四个通道from torchvision.models import resnet50 def adapt_backbone_to_4channels(backbone): # 取ResNet50的第一层卷积conv1 conv1 backbone.conv1 # 新卷积核通道数是4输出通道数不变 new_conv1 nn.Conv2d( 4, conv1.out_channels, kernel_sizeconv1.kernel_size, strideconv1.stride, paddingconv1.padding, biasconv1.bias is not None ) # 把预训练权重复制过来第四通道用RGB三通道的均值初始化 with torch.no_grad(): new_conv1.weight[:, :3] conv1.weight new_conv1.weight[:, 3] conv1.weight.mean(dim1) return new_conv1这样改的好处是第四个通道初始权重不是随机噪声而是和RGB特征相近模型微调时不会把第一层特征全部打乱。注意如果你用PyTorch相对较新的版本修改backbone.conv1后还要确保forward里输入张量是四通道否则第一层就会报通道数不匹配。3. 滑坡数据集准备与预处理把大尺寸遥感影像切成能喂给CNN的样本3.1 遥感图像标注格式从矢量shp到PNG掩膜遥感滑坡识别项目里的数据集形态上通常分两部分原始影像和标注文件。原始影像常见的是高分二号、WorldView、哨兵二号等卫星影像或者无人机航拍的正射影像DOM。标注文件有两种常见格式一种是矢量格式.shp按地质灾害调查规范把滑坡边界画成多边形另一类是已经栅格化的掩膜每个像素的值是0或10代表非滑坡1代表滑坡。PyTorch的Dataset接口吃的是数组不直接吃shp。所以拿到shp标注后第一步是用GDAL把矢量栅格化成与影像完全对齐的掩膜。栅格化时最容易出错的是坐标系不一致影像坐标系是WGS84 / UTMshp是WGS84 / 经纬度直接用gdal.Rasterize会画错位置。解决思路是先统一坐标系再栅格化最后检查行列数是否和影像一致。下面是一个常见的栅格化流程用osgeo包处理from osgeo import gdal, ogr, osr def rasterize_shp(shapefile_path, reference_tif_path, output_mask_path): # 打开参考影像目的是拿到地理变换信息和尺寸 ref_ds gdal.Open(reference_tif_path) geo_transform ref_ds.GetGeoTransform() x_res ref_ds.RasterXSize y_res ref_ds.RasterYSize ref_ds None # 打开矢量文件 shp_ds ogr.Open(shapefile_path) layer shp_ds.GetLayer() # 如果矢量坐标系和参考影像不一致先做坐标变换 target_srs osr.SpatialReference() target_srs.ImportFromWkt(geo_transform_proj : gdal.Open(reference_tif_path).GetProjection()) source_srs layer.GetSpatialRef() transform osr.CoordinateTransformation(source_srs, target_srs) # 创建掩膜栅格数据类型用UInt8 driver gdal.GetDriverByName(GTiff) out_ds driver.Create(output_mask_path, x_res, y_res, 1, gdal.GDT_Byte) out_ds.SetGeoTransform(geo_transform) out_ds.SetProjection(gdal.Open(reference_tif_path).GetProjection()) # 让目标栅格的波段初始值为0 band out_ds.GetRasterBand(1) band.Fill(0) # 把矢量图层按属性字段class栅格化滑坡类别值为1 gdal.RasterizeLayer(out_ds, [1], layer, options[ATTRIBUTEclass]) out_ds None这段代码注意一个容易踩的坑gdal.RasterizeLayer默认会把图层中的所有要素都画成目标值如果你不指定ATTRIBUTE矢量属性是2的要素也会被画成2导致掩膜里出现除0、1以外的类别。我的习惯是先用ogr检查一下属性字段的取值分布确认“滑坡”的字段值确实是1再决定是否要按属性过滤。3.2 影像切片与数据增强样本数量不够时怎么补遥感影像单景尺寸通常是几千×几千像素直接输入CNN会爆显存所以必须切成patch常见尺寸是256×256或512×512。切片时要注意重叠率我一般设成128像素的重叠也就是步长128。因为滑坡体往往横跨切片边界如果切片不重叠推理时模型可能漏掉切在边上的滑坡碎片重叠切片能缓解这个问题。切片要同时作用于影像和掩膜否则两者错位。用滑动窗口切割时窗口坐标一致裁剪出的掩膜也要同步裁剪。一个偷懒但很常见的错误是只切影像不切掩膜训练时用整幅掩膜索引去取对应像素这在numpy索引不匹配时会产生难以察觉的错位模型在验证集上mIoU很高但推理结果却整体偏移。针对样本数量不足数据增强是遥感项目的常规手段。遥感影像的增强和自然图像增强有区别水平翻转、垂直翻转、随机旋转90度对滑坡识别有效因为滑坡朝向多样不受重力方向限制但随机光照、随机颜色抖动要慎重因为不同时相遥感影像的光照差异本来就大过度颜色增强会让模型学到“颜色奇怪就是滑坡”这种错误特征。推荐的在线增强组合是import random import numpy as np def augment_pair(image, mask): # 对影像和掩膜做同样的随机变换保证像素位置一一对应 if random.random() 0.5: image np.flip(image, axis1) # 水平翻转 mask np.flip(mask, axis1) if random.random() 0.5: image np.flip(image, axis2) # 垂直翻转 mask np.flip(mask, axis2) k random.choice([0, 1, 2, 3]) image np.rot90(image, k, axes(1, 2)) mask np.rot90(mask, k, axes(1, 2)) # 对影像做轻度高斯噪声模拟传感器噪声 if random.random() 0.3: noise np.random.normal(0, 1.5, image.shape).astype(np.float32) image image noise return image, maskplt.imshow画图时可能会发现翻转后影像和掩膜确实同步了但有一类增强不能随便做就是随机缩放和随机裁剪。假设影像本来有地理坐标缩放后滑坡体的像素尺寸变了模型对滑坡尺寸的感知会被打乱推理时对较大滑体的完整性判断反而变差。3.3 数据划分与类别不均衡处理滑坡往往只占几个像素滑坡识别的数据集有一个突出特点正样本像素占比非常低。在一张512×512的切片里滑坡体可能只有几百到几千个像素占比甚至不到5%。如果直接拿原始像素交给模型训练模型会把所有像素都预测成背景因为全猜背景也有95%以上的准确率这种假象在验证时非常坑人。处理类别不均衡方法有三层。第一层是选择损失函数。PyTorch自带CrossEntropyLoss支持类别权重把背景权重设为0.2、滑坡权重设为0.8比默认权重更合理更进阶的方案是DiceLoss或FocalLoss。DiceLoss直接优化分割重叠度对稀疏目标效果好但训练初期容易波动我习惯把CrossEntropyLoss和DiceLoss按0.6和0.4权重混合。第二种是训练时做在线困难样本挖掘把背景像素中模型预测置信度高的样本当作easy sample降权把漏检的滑坡像素放大梯度。第三种是数据层面做负样本过滤。如果一个切片中滑坡像素占比低于0.5%且验证发现模型在这个切片上经常产生误检就把这种纯背景切片从训练集里剔除。注意这个操作只能用于训练集验证集要保持真实分布否则评估结果会虚高。数据划分按场景切分而不是按像素随机切分。这个原则很多从业者会忽略遥感影像相邻切片高度相关如果随机把切片分到train和val同一景影像的切片可能同时出现在两方验证时模型的“记忆”会冒充“泛化”。正确做法是把一景影像完整切出的切片归入同一个集合。比如10景影像按8:2分配一景影像的所有切片要么全在训练集、要么全在验证集。这样才能看出模型对没见过的区域到底行不行。4. 用PyTorch训练CNN滑坡识别模型从零跑通训练脚本4.1 环境配置PyTorchCUDA遥感图像处理库怎么装PyTorch环境的搭建是很多新手卡住的第一步。常见问题不是版本装不上而是CPU版PyTorch和GPU版混装。如果你机器上有NVIDIA显卡先查一下驱动支持的最高CUDA版本然后用conda创建独立环境再安装对应cuda版本的PyTorch。以conda为例一个常见做法是conda create -n landslide python3.9 conda activate landslide # 安装PyTorch。下面这行以CUDA 11.8为例按自己显卡驱动版本调整 conda install pytorch torchvision pytorch-cuda11.8 -c pytorch -c nvidia # 验证GPU可用 python -c import torch; print(torch.cuda.is_available())这段命令里python3.9不是硬性要求但PyTorch官方对Python版本有对应关系3.9到今天依然是兼容性最好的版本之一。torchvision必须和torch一起装不能只装其中一个否则预训练权重下载时会报版本对不上。pytorch-cuda11.8是安装时指定的CUDA运行时版本它不替代驱动驱动版本要在NVIDIA控制面板里确认。安装完成后验证GPU是否可用的print语句会输出True。如果输出False先检查conda环境里是否装了CPU版torch再检查Python进程能否访问显卡。遥感图像处理还需要安装几个非深度学习库rasterio用于读取GeoTIFF和切片geo pandas和osgeo用于处理矢量与栅格对齐。安装命令conda install -c conda-forge rasterio geopandas gdal这些库是纯Python环境下处理遥感数据的基础设施后面写Dataset类时用rasterio读取影像会比OpenCV更顺手因为rasterio直接能读取TIFF的地理信息。4.2 训练脚本核心逻辑Dataset、Loss、优化器怎么配训练脚本的核心可以拆成三个组件Dataset类负责逐个样本返回影像和掩膜Trainer循环负责迭代梯度更新评估函数负责在每个epoch结束后计算mIoU。下面给出一个缩略但完整可运行的训练主循环聚焦在关键逻辑上import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader class LandslideDataset(Dataset): def __init__(self, image_paths, mask_paths, patch_size512, use_augFalse): # 注意image_paths和mask_paths必须一一对应 self.image_paths image_paths self.mask_paths mask_paths self.patch_size patch_size self.use_aug use_aug def __len__(self): return len(self.image_paths) def __getitem__(self, idx): import numpy as np import rasterio # 用rasterio读取GeoTIFF影像保证多波段数据不丢 with rasterio.open(self.image_paths[idx]) as src: image src.read().astype(np.float32) with rasterio.open(self.mask_paths[idx]) as src: mask src.read(1).astype(np.int64) # 影像默认形状是(C, H, W)归一化到0~1之间 image (image - image.min()) / (image.max() - image.min() 1e-8) # 如果开启了增强同步处理影像和掩膜 if self.use_aug: image, mask augment_pair(image, mask) # 转成PyTorch张量掩膜去掉通道维 return torch.from_numpy(image), torch.from_numpy(mask) # 混合损失CrossEntropy Dice class MixedLoss(nn.Module): def __init__(self, weight_bg0.2, weight_fg0.8): super().__init__() self.ce nn.CrossEntropyLoss(weighttorch.tensor([weight_bg, weight_fg])) self.dice DiceLoss(num_classes2) def forward(self, logits, mask): ce_loss self.ce(logits, mask) dice_loss self.dice(logits, mask) return 0.6 * ce_loss 0.4 * dice_loss这段代码里归一化用的是该切片内部的最小最大值。这个做法在切片内部对比明显时效果不错但如果你把归一化放在整个影像上做再切片模型输入分布会更稳定。两种做法各有坑第五部分会细说。训练循环不再完整贴出但核心参数值得强调。优化器选Adam学习率初始为1e-4如果用了带动量的SGD初始学习率可以试1e-3但Adam对滑坡分割更友好因为分割损失曲面不平滑Adam的自适应步长能减少震荡。batch_size设在4到8不等取决于显存L2权重衰减设5e-4防止模型记住纹理噪声。每轮epoch结束后计算验证集的mIoU保存mIoU最高的权重而不是最后一轮权重。4.3 训练过程中的监控loss不下降时要看什么训练跑起来之后典型的监控指标有三个训练loss、验证loss、验证mIoU。诊断顺序很重要。如果训练loss下降但验证mIoU不升大概率是过拟合尤其是滑坡样本少时容易发生如果训练loss和验证loss都降不下去先看归一化是不是把某些切片整体拉到了异常亮度再看学习率是不是太大。有一类情况很隐蔽验证集mIoU一路走高但loss曲线却回头上涨。这是因为DiceLoss在正样本像素极少时会出现大面积梯度消失模型在某一轮突然将所有像素都预测为背景DiceLoss会回到一个较高值此时模型卡在局部最优。我的做法是每轮训练结束后把验证集上预测错误最严重的10个样本单独截图保存配合loss曲线判断是哪种失效模式。如果全是全背景预测就给滑坡类更高的loss权重或者用class_wise的FocalLoss替代普通CrossEntropy。如果GPU显存占用率只有20%说明数据读取速率跟不上GPU计算速率常见原因是rasterio每次读取都要打开文件产生严重的I/O开销。解决方法是在Dataset初始化时就把所有影像一次性读到内存或使用多进程num_workers4及以上。遥感影像单个GeoTIFF可能几百兆一次性读取内存容易爆折中方案是用系统内存缓存或者先离线裁剪成小切片再做Dataset。5. 滑坡识别项目落地避坑数据集、训练、推理三个环节的踩坑记录5.1 坑一标注掩膜与影像不对齐导致mIoU虚高现象训练集和验证集mIoU都能跑到0.7以上但模型在整景影像上推理出的滑坡边界明显飘移和实际滑坡体位置差了几十米。原因标注shp栅格化时参考影像的地面采样距离是1米/像素但shp格式的坐标是从另一套WGS84 / UTM分区投影转换来的如果转换参数写错所有滑坡多边形会整体平移几个像素。模型在训练时看到的影像和掩膜之间存在几个像素的系统偏移但由于CNN对平移敏感度有限验证集同样带有偏移mIoU照样高。解决在栅格化之前把shp和参考影像叠加画到一张图上用QGIS检查边界是否完全套合。更稳妥的做法是不受像素级别的套合直接对比shp多边形的质心和遥感影像上对应地理位置的坐标。先在训练前做一次“影像掩膜叠加图”输出再决定是否要启动训练。这一步能省下后面所有排错时间。5.2 坑二全局归一化把山地阴影变成“伪滑坡”现象训练时loss正常验证mIoU还不错但模型在那种“山体一侧背阴”的影像上大量误检把阴影区域都预测成滑坡。原因遥感影像的动态范围很大同一个数据集中有的影像整体偏亮有的整体偏暗。如果对每张切片做全局最小最大归一化切片内部像元亮度被拉伸原本在暗部纹理和亮部纹理上有区分能力的特征会被抹平。山地阴影的亮度范围和某些滑坡体很接近模型无法用亮度信息区分。解决改用逐影像的百分位归一化而不是全局最小最大缩放。常见做法是对每景影像按2%到98%百分位线性拉伸这样能保住大部分有效信息又能压低极值像元的干扰。具体到代码就是先对整景影像统计np.percentile(image, 2)和np.percentile(image, 98)再做线性映射而不是用image.min()和image.max()。跑完归一化后随机抽几个样本打印归一化后的像素均值、标准差如果不同样本之间均值差异超过0.1说明归一化策略还有问题需要改用更稳健的统计量。5.3 坑三显存不够就降batch_size但别忘同步调学习率现象用batch_size8训练一切正常改成batch_size2之后训练loss波动剧烈验证mIoU下滑。原因batch_size减小后每个batch的梯度估计噪声变大原本适配大batch的Adam学习率此时步长偏大参数在最优值附近来回跳动。很多人只想着“显存小了就降batch_size”却没意识到这会影响优化器的有效学习率。解决一个经验法则是batch_size减半时学习率也减半或降为原来的0.7倍。比如batch_size从8降到4Adam的初始学习率从1e-4降到7e-5或5e-5。同时适当增加梯度累积步数用accumulate_grad_steps2或者4模拟一个更大的batch让梯度更稳定。显存不够时除了降batch_size还可以把输入patch从512改成384分辨率稍微降低对滑坡识别的影响通常有限。5.4 坑四验证集指标好看推理结果却是“椒盐噪声”现象模型在patch级别推理时单张切片掩膜看起来不错但拼回整景影像后结果里出现大量孤立的小碎块有些只有几十个像素像椒盐噪声一样散布在山体上。原因这往往是因为训练切片是固定窗口切出来的模型只学会了在给定上下文内做判断缺少对空间连续性的约束。此外推理时如果滑块重叠但没有做加权融合会产生边界接缝不一致的伪影。解决推理时采用overlap-tile策略把滑窗步长设为patch_size的一半同一个像素会被多次预测取多个预测的平均值或最大值作为最终结果。平均值能有效过滤独立碎块最大值则更容易保留细长的滑坡裂缝。我常用平均值再配合一个后处理用形态学开运算去掉小于一定面积如500平方米的孤立斑块。这里用的后处理库可以是skimage.morphology。5.5 坑五迁移学习时把预训练权重的分类头带进来导致维度报错现象写好了新模型加载torchvision预训练权重时报错提示size mismatch for classifier.4.weight: expected shape [2, 256, 1, 1], found [1000, 256, 1, 1]。原因torchvision里ImageNet预训练模型最后接的是1000类分类头而滑坡识别分割模型只有2类。如果只重新定义了classifier却忘了按新类别数设置权重加载PyTorch会报维度不匹配。这个报错本身是好事可怕的是有些人直接忽略这个warning继续训练结果训练完发现输出类别始终有问题。解决加载权重时用strictFalse跳过分类头。更精确的做法是只加载backbone部分的权重。backbone是指model.backbone它是ResNet50除去最后全连接层的部分分类头不参与加载。用如下方式state_dict torch.load(weight_file.pth, map_locationcpu) # 只取backbone部分忽略classifier和aux_classifier backbone_dict {k: v for k, v in state_dict.items() if k.startswith(backbone)} model.load_state_dict(backbone_dict, strictFalse)如果别人训练好的模型权重里除了backbone还包含完整的classifier但类别数相同可以直接用strictTrue加载。如果类别数不一致那就只能用strictFalse并跳过分类头然后重新训练分类头。注意迁移学习时如果跳过分类头意味着分类头从随机初始化开始训练要给它更大的学习率或者让backbone先冻结几个epoch等分类头学稳再解冻。6. 用训练好的模型做推理与评估从权重文件到成图输出拿到训练好的权重文件后最后一步是把模型输出成可用的滑坡图斑。这个环节最忌讳“直接跑完整张影像”因为大尺寸影像一次性进入模型会爆内存。标准做法是滑窗推理再把切片结果拼回原尺寸。下面给出一个滑窗推理核心逻辑def sliding_window_inference(model, full_image_tensor, patch_size512, stride256, batch_size4): import torch.nn.functional as F model.eval() c, h, w full_image_tensor.shape result torch.zeros((h, w), dtypetorch.float32) count torch.zeros((h, w), dtypetorch.float32) windows [] coords [] for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): # 重叠滑窗确保边界区域也能被预测 windows.append(full_image_tensor[:, y:ypatch_size, x:xpatch_size]) coords.append((y, x)) # 分批次推理避免一次性把所有窗口全部放进显存 with torch.no_grad(): for i in range(0, len(windows), batch_size): batch torch.stack(windows[i:ibatch_size]).cuda() logits model(batch)[out] pred torch.softmax(logits, dim1)[:, 1] # 只取滑坡类概率 for j, (y, x) in enumerate(coords[i:ibatch_size]): result[y:ypatch_size, x:xpatch_size] pred[j].cpu() count[y:ypatch_size, x:xpatch_size] 1 # 重叠区域取平均 result result / count.clamp(min1) return result这段代码里重点是result和count两个同尺寸矩阵重叠区域被多次预测时取平均避免接缝处出现一条条亮线。推理结束后用阈值0.5把概率图二值化再用rasterio写出GeoTIFF保存地理坐标信息就能叠加到GIS里看效果。评估指标不能只看mIoU。滑坡识别里漏检的代价远高于误检因为在灾害应急中错过一个真实滑坡可能影响救援决策。所以我习惯把recall和precision分别打印出来如果recall低于0.8就下调二值化阈值到0.4或者对滑坡类别加大DiceLoss权重。mIoU在类别极度不均衡时会显得虚高哪怕模型把滑坡全部漏掉mIoU也可能有0.9因为背景占了绝大多数。只有单独统计滑坡类别的IoU才能看出模型真正的短板。我自己的习惯是每次训练结束后保留三样东西最佳权重文件、验证集分割结果拼接图、以及一份记录每个epoch指标变化的日志。这让我在三个月后回头分析时能快速判断当前结果是哪一次训练跑出来的。这个习惯帮我省过很多次返工的力气希望你也能用上。希望帮到你。本文还有配套的精品资源点击获取
返回列表