首页/新闻资讯/正文详情

基于CNN的Landsat遥感影像地物分类全流程实战

发布时间:2026/9/23 3:07:38 来源:云帆数科 栏目:资讯中心
基于CNN的Landsat遥感影像地物分类全流程实战
简介这套面向遥感影像地物分类的CNN深度学习Python工程基于PyTorch实现专用于Landsat数据的高效处理与分类建模。工程包含影像切片、模型训练与新增数据预测三个核心Python脚本并附有预训练模型权重.h5、示例遥感影像.tif及其坐标/元数据辅助文件.tfw/.xml以及说明文档可支撑从数据预处理到分类结果输出的完整流程。全部文件共10个压缩包大小14.88MB结构紧凑、目录清晰。资源重点覆盖Landsat多波段数据处理和基于CNN的地物类型识别适合遥感、人工智能、自动化等专业学生作为课程设计、毕业设计或科研入门参考也可用于快速验证深度学习方法在遥感场景下的迁移效果。目前已有87人学习浏览代码经过测试可运行遇到配置问题可联系作者交流。1. CNN深度学习遥感影像地物分类先搞清这个zip到底能帮你解决什么手头攒了一堆Landsat影像想分个地物类型出来结果卡在第一步数据怎么处理、样本怎么标、CNN怎么训、精度怎么验每一步都像隔着一层黑匣子。这个标题给出的方案核心就是一条用Python串起来的完整流水线——从Landsat原始影像开始经过预处理、样本制作、CNN模型训练最后输出一张地物分类图。它不是教你背理论而是把影像数据变成模型能吃的格式再让模型把地物类别吐出来。适合两类人一是遥感专业的本科生、研究生论文里需要一张分类结果图二是自然资源调查、农业估产、生态监测这类岗位的从业者需要把Landsat数据自动化处理成地物分类成果。前提是你会一点Python装过深度学习框架剩下的事情这个方向都能给你一套可以照做的路径。2. 从Landsat到能喂进CNN的数据预处理必须抠的四个环节2.1 数据源选择Collection 2 Level-2的SR产品为什么能直接进模型Landsat数据目前最常用的两个传感器是OLILandsat 8和OLI-2Landsat 9分辨率30米多光谱波段覆盖了可见光到短波红外。下载数据时你会看到两种产品Level-1的原始DN值产品和Level-2的大气校正产品也叫SR产品Surface Reflectance。新手最容易犯的错是下载了Level-1的数据然后花大量时间自己跑大气校正其实USGS已经帮你把这件事做完了。Collection 2 Level-2的SR产品直接提供地表反射率不需要再走一遍辐射定标和大气校正流程省掉的不只是时间还有一堆自己校正带来的误差。我一般会直接下载Collection 2 Level-2的SR产品文件命名里带有“SR”标识波段文件单独存放每个波段一个GeoTIFF。注意Landsat的波段命名规则B2是蓝光、B3是绿光、B4是红光、B5是近红外、B6和B7是短波红外。做地物分类用这几个波段就够了热红外波段B10、B11分辨率是100米重采样到30米对地物分类提升有限一般不放进模型。下载时按行列号Path/Row检索覆盖你研究区的那一景或多景影像如果研究区跨了两景就下载相邻影像后面做镶嵌。2.2 辐射定标与大气校正SR产品为什么不用自己跑很多人一上来就找大气校正的代码其实这是被旧教程带偏了。早期Landsat数据都是Level-1的DN值必须自己做辐射定标转成辐射亮度再做大气校正得到地表反射率。现在的Collection 2 Level-2产品已经把这两步做完了你拿到的就是地表反射率数值范围一般在0到1之间存储的时候乘以10000变成整型。用的时候除以10000还原成反射率这个缩放因子在MTL元数据文件里有明确标注。如果你确实拿到的是Level-1数据比如某些镜像站只提供Level-1那就需要自己处理。常见做法是用ENVI的FLAASH模块或 seis 的开源大气校正但这条路径费时费力精度还不一定比得上官方产品。我的建议很直接优先用Level-2把时间省下来花在样本标注和模型调参上这两个环节对最终精度的影响比大气校正大一个量级。在Python里读取SR产品时不需要管辐射定标系数直接读反射率做归一化或者标准化即可。2.3 波段组合与裁剪别把整景影像直接喂网络Landsat一景影像大约是185公里×180公里直接整景喂给CNN是不可能的显存装不下计算量也不现实。常见的做法是先把影像裁剪成小块patch比如256×256像素或者512×512像素然后按patch来训练和预测。裁剪之前先确定波段组合。一般地物分类用6个多光谱波段B2-B7加上NDVI、MNDWI这类指数波段构成多通道输入。指数波段能增强植被和水体的区分度对分类精度有实际帮助。另一个必须处理的问题是云和云阴影。Landsat的SR产品自带QA波段Quality Assessment波段里面记录了每个像素的云、云阴影、雪等标记。预处理时把这个波段读出来生成一个掩膜把有云和云阴影的像素标记为无效后面做样本标注和精度验证时避开这些区域。很多人在这一步偷懒结果模型学了一堆云的纹理分类图上一片一片的云区被分成了各种奇怪的类别。2.4 批量预处理脚本从原始tif到可直接训练的npy数组预处理我用Python的rasterio库来读写GeoTIFFnumpy做数组运算。核心流程是读取波段→拼接成多通道数组→裁剪成patch→保存为npy或直接在做训练时实时读取。下面是一个最小可用的预处理脚本把一景Landsat影像裁成256×256的patch并保存。import rasterio import numpy as np import os from tqdm import tqdm # 配置参数 tif_path LC08_L2SP_119038_20230601_SR.tif # 原始SR影像多波段合成 out_dir patches patch_size 256 overlap 0 # 裁剪时的重叠像素数 # 读取影像和QA波段 with rasterio.open(tif_path) as src: bands src.read([2, 3, 4, 5, 6, 7]) # B2-B7共6个波段 transform src.transform height, width bands.shape[1], bands.shape[2] profile src.profile # 把DN值转成反射率SR产品缩放因子10000 reflectance bands.astype(np.float32) / 10000.0 # 计算NDVI和MNDWI作为额外特征 np.seterr(divideignore, invalidignore) ndvi (reflectance[3] - reflectance[2]) / (reflectance[3] reflectance[2] 1e-8) mndwi (reflectance[1] - reflectance[4]) / (reflectance[1] reflectance[4] 1e-8) ndvi np.nan_to_num(ndvi, nan0.0, posinf0.0, neginf0.0) mndwi np.nan_to_num(mndwi, nan0.0, posinf0.0, neginf0.0) # 合并所有特征通道6个原始波段 NDVI MNDWI 8通道 features np.concatenate([reflectance, ndvi[None, :, :], mndwi[None, :, :]], axis0) # 按patch_size裁剪保存为npy os.makedirs(out_dir, exist_okTrue) patch_idx 0 for y in range(0, height - patch_size 1, patch_size - overlap): for x in range(0, width - patch_size 1, patch_size - overlap): patch features[:, y:ypatch_size, x:xpatch_size] # 过滤掉全是0值或云掩膜占比过高的patch if np.mean(patch 0) 0.5: continue np.save(f{out_dir}/patch_{patch_idx:06d}.npy, patch) patch_idx 1 print(f共生成 {patch_idx} 个patch每个shape为 {features.shape[0]}x{patch_size}x{patch_size})这段代码把预处理和patch生成合成了一步。逻辑说明先用rasterio读取6个多光谱波段除以10000还原成反射率然后计算NDVI和MNDWI两个指数拼接到特征通道里最后按指定窗口滑动裁剪跳过全是0值的无效区域。参数说明patch_size决定每个样本的空间范围256像素在30米分辨率下对应7.68公里既能覆盖足够的地物上下文又不会让CNN感受野太小overlap控制相邻patch之间的重叠一般设0即可做预测时再设重叠来消除边界拼接痕tif_path指向的是多波段合成文件如果下载的是分波段tif需要用rasterio.merge或GDAL先合成再处理。做完这一步你的数据就从“一景tif”变成了“一堆npy数组”每张npy是一个8通道的patch直接可以作为CNN的输入。第一批patch生成后建议抽查几张用matplotlib把RGB合成波段B4-B3-B2画出来确认波段顺序没搞错、反射率数值范围正常、没有条纹状噪声。这一步花不了五分钟但能避免后面训练时才发现数据有问题白跑好几个小时。3. 样本制作与数据集划分标注决定分类精度上限3.1 每类地物到底要多少样本按类均衡比像素更重要CNN训练需要带标签的patch。很多人问“每类要多少个样本”答案不是固定的但有个经验下限每类至少300个patch也就是300×256×256个像素的标注区域。如果类别不平衡比如水体只标了50个patch农田标了2000个模型会严重偏向农田水体就经常被漏分。常见做法是先做类别统计保证样本量最少的类别不低于最多的类别的三分之一。标注工具怎么选也有讲究。简单的方案是Labelme它画多边形打标签很方便但对遥感大影像不太友好需要先裁剪成patch再标效率不高。更好的方案是在QGIS里直接打开Landsat影像参考更高分辨率的谷歌影像或者Google Earth截图手动矢量化为面然后把矢量栅格化成和影像分辨率一致的标签图再按同样的裁剪逻辑切成patch。栅格化的像素值和类别ID对应起来比如0是背景、1是水体、2是植被、3是农田、4是建筑、5是裸地。类别不要设太多五六类到头了类别越多标注工作量越大类别之间的混淆也越严重。3.2 类别体系怎么定从地表覆盖类型反推标签名称标什么类别首先要看你的应用目标。做土地利用调查就按分类体系走比如GB/T 21010里的耕地、林地、草地、水域、建设用地做生态监测就把重点放在植被覆盖度和水体范围上。Landsat的30米分辨率决定了它区分不了太细的类别比如不同农作物种类但区分水体、植被、农田、建筑、裸地这类一级类效果很好。我一般会建议先做一个六类分类水体、植被、农田、建筑、裸地、其他。类别越多标注难度和分类误差都会显著上升先跑通六类再看需不需要合并或细分。类别合并是另一个容易踩坑的点。比如“植被”这个概念在影像上包括林地、草地、灌丛它们的反射率光谱差异不小如果全标成“植被”会让类内方差过大CNN学到的是一个模糊的“植被”概念。反过来如果你的研究区只有一片林地和一小块草地强行分两类会导致草地样本不足训练时直接崩掉。合理的做法是参考你的研究区实际地物分布结合分类目标的粒度把光谱相似、应用上不需要区分的类别合并。3.3 训练/验证/测试集划分按空间不按像素很多人在这一步犯了一个隐蔽但后果严重的错误随机把patch分到训练集和测试集。如果同一个区域的不同patch同时出现在训练集和测试集里模型实际上已经见过这些像素了测试精度会虚高。遥感影像有很强的空间自相关性相邻patch的纹理、光谱高度相似随机划分等于数据泄漏。正确的做法是按空间区域划分把整个研究区先划分成几个大的子区域比如东西两块一块取训练patch另一块取测试patch保证训练集和测试集在地理位置上完全分开。具体操作上我会先画一个研究区的矢量网格比如每个格网1公里×1公里按格网来采样patch。先把格网按照“训练/验证/测试7/1/2”的比例随机分配到三个集合然后在每个格网里生成patch。这样既保证了空间独立性又让每个集合覆盖整个研究区的地物多样性。验证集用来调超参数测试集只在最后评估一次不要反复拿测试集试否则测试集也会被你“训练”。3.4 标签编码与样本增强从矢量到patch标签的完整代码标注完成后的标签图是单通道的每个像素的值代表类别ID。需要把标签图切成和影像patch完全对应的patch标签组成(x, y)对。这里最关键的是保证裁剪的起始像素坐标完全一致否则影像和标签对不上训练时模型学到的是噪声。import rasterio import numpy as np import os from sklearn.model_selection import train_test_split # 配置 img_patches_dir patches # 预处理阶段生成的影像patch label_tif_path labels.tif # QGIS中栅格化后的标签图 out_train_dir train_data os.makedirs(out_train_dir, exist_okTrue) # 读取标签图 with rasterio.open(label_tif_path) as src: label_img src.read(1).astype(np.int16) # 标签和影像patch的裁剪起始坐标保持一致 patch_size 256 img_files sorted(os.listdir(img_patches_dir)) sample_list [] for fname in img_files: img_path os.path.join(img_patches_dir, fname) img np.load(img_path) h, w img.shape[1], img.shape[2] # 从文件名解析patch的起始坐标在预处理时按规则写入 # 这里约定文件名格式为 patch_yx_{y}_{x}也可以用其他方式记录 parts fname.split(_) y0 int(parts[2]) x0 int(parts[3].split(.)[0]) label_patch label_img[y0:y0h, x0:x0w] # 过滤掉包含大量未标注像素(值为-9999)的patch if np.sum(label_patch 0) (patch_size * patch_size * 0.2): continue # 过滤掉类别单一且背景占比过高的patch unique, counts np.unique(label_patch, return_countsTrue) if len(unique) 2: continue # 保存样本对数据集文件名加前缀x_和y_方便后续读取 sample_id fname.replace(.npy, ) np.save(f{out_train_dir}/x_{sample_id}.npy, img) np.save(f{out_train_dir}/y_{sample_id}.npy, label_patch) sample_list.append(sample_id) # 按空间格网分配训练/验证/测试集这里简化成随机分配实际应按格网 # 实际项目中建议先生成格网ID再按格网ID划分避免空间泄漏 train_ids, temp_ids train_test_split(sample_list, test_size0.3, random_state42) val_ids, test_ids train_test_split(temp_ids, test_size0.33, random_state42) # 保存划分结果 with open(f{out_train_dir}/train_ids.txt, w) as f: f.write(\n.join(train_ids)) with open(f{out_train_dir}/val_ids.txt, w) as f: f.write(\n.join(val_ids)) with open(f{out_train_dir}/test_ids.txt, w) as f: f.write(\n.join(test_ids)) print(ftrain: {len(train_ids)}, val: {len(val_ids)}, test: {len(test_ids)})这段代码的重点在于“坐标对齐”和“样本过滤”。逻辑说明影像patch是从预处理阶段生成的标签图需要按完全相同的起始坐标裁剪才能得到一一对应的标签patch两个过滤条件分别去掉未标注区域过多的patch和只有单一类别的patch后者在训练时无法提供有意义的监督信号。参数说明random_state42固定随机种子保证实验可复现test_size0.3表示先分出30%作为验证测试再从这30%里分出1/3做测试最终比例约为训练70%、验证10%、测试20%。这里演示的是简化版随机划分实际项目里请按照3.3节说的按空间格网划分。样本增强方面遥感patch常用的增强方式包括随机旋转90度的倍数、上下左右翻转、随机亮度扰动、随机裁剪。不要用随机缩放或任意角度旋转因为地物有明确的方向性建筑、道路破坏了方向就改变了语义。可以用torchvision.transforms或albumentations库实现在训练时动态增强增强后的patch不落盘节省存储空间。4. 训练一个CNN地物分类模型模型结构与参数怎么定4.1 为什么选CNN而不是前馈神经网络或Transformer图像处理为啥用CNN不用前馈神经网络这个问题很多入门者都问过。核心原因有两个参数效率和局部性。全连接网络把每个像素当成独立特征256×256×8通道的输入展开成50万个特征第一层全连接的参数量就是天文数字训练不动也严重过拟合。而CNN通过卷积核共享权重只关注局部邻域的像素关系参数数量大幅减少。更关键的是卷积操作天然符合图像信号的特点——相邻像素高度相关远处的像素相关性弱。那和Transformer比呢Transformer靠自注意力机制也能捕捉全局依赖但需要海量数据来训练在遥感小样本场景下很容易欠拟合。CNN的归纳偏置局部连接、权重共享、平移等变性让它在几千个patch的小数据集上就能训练出可用的模型。做Landsat地物分类数据量通常不会超过几万个patchCNN是最稳的选择。等你积累了几百万个patch再考虑Swin Transformer或ConvNeXt这类大模型不迟。4.2 一个能跑通的轻量CNN结构U-Net还是简单分类网络要看输出这里要区分两种任务如果只是给每个像素分类常见做法是用全卷积网络输入patch输出同样尺寸的标签图U-Net是经典选择如果先提取特征再接全连接层做分类输出的是整个patch的类别那是图像分类思路并不适合地物制图。地物分类需要逐像素的预测结果所以用全卷积网络。下面给出一个简化的U-Net结构输入8通道256×256的影像输出6类概率图。import torch import torch.nn as nn import torch.nn.functional as F class SimpleUNet(nn.Module): 轻量级U-Net4次下采样4次上采样适配Landsat多光谱输入 def __init__(self, in_channels8, num_classes6): super().__init__() # 编码器 self.enc1 self.conv_block(in_channels, 32) self.enc2 self.conv_block(32, 64) self.enc3 self.conv_block(64, 128) self.enc4 self.conv_block(128, 256) self.pool nn.MaxPool2d(2) # 瓶颈层 self.bottleneck self.conv_block(256, 512) # 解码器 self.up4 nn.ConvTranspose2d(512, 256, kernel_size2, stride2) self.dec4 self.conv_block(512, 256) self.up3 nn.ConvTranspose2d(256, 128, kernel_size2, stride2) self.dec3 self.conv_block(256, 128) self.up2 nn.ConvTranspose2d(128, 64, kernel_size2, stride2) self.dec2 self.conv_block(128, 64) self.up1 nn.ConvTranspose2d(64, 32, kernel_size2, stride2) self.dec1 self.conv_block(64, 32) self.out_conv nn.Conv2d(32, num_classes, kernel_size1) def conv_block(self, in_c, out_c): return nn.Sequential( nn.Conv2d(in_c, out_c, kernel_size3, padding1), nn.BatchNorm2d(out_c), nn.ReLU(inplaceTrue), nn.Conv2d(out_c, out_c, kernel_size3, padding1), nn.BatchNorm2d(out_c), nn.ReLU(inplaceTrue), ) def forward(self, x): # 编码 e1 self.enc1(x) e2 self.enc2(self.pool(e1)) e3 self.enc3(self.pool(e2)) e4 self.enc4(self.pool(e3)) b self.bottleneck(self.pool(e4)) # 解码 跳跃连接 d4 self.up4(b) d4 self.dec4(torch.cat([d4, e4], dim1)) d3 self.up3(d4) d3 self.dec3(torch.cat([d3, e3], dim1)) d2 self.up2(d3) d2 self.dec2(torch.cat([d2, e2], dim1)) d1 self.up1(d2) d1 self.dec1(torch.cat([d1, e1], dim1)) out self.out_conv(d1) return out这个U-Net把输入从8通道降到32通道起步每下采样一次通道翻倍在256×256输入下显存占用大约4到6GB消费级显卡能跑。逻辑说明编码器逐级提取从细到粗的特征解码器逐级恢复空间分辨率跳跃连接把编码器的细节特征拼到解码器弥补池化损失的空间信息这对地物边界保持很有帮助。参数说明in_channels8要和前面预处理生成的特征通道数一致6个波段NDVIMNDWInum_classes6对应6个地物类别BatchNorm在batch_size较小小于8时效果会变差如果显存不够减小patch_size而不是减batch_size。4.3 训练关键参数lr、batch_size、loss函数怎么设训练参数的选择直接影响收敛速度和最终精度。我常用的初始化配置如下表这些值经过多个遥感数据集验证作为起点基本不会翻车。参数推荐值说明优化器AdamWAdam的改进版权重衰减更干净初始学习率1e-3用余弦退火调度器逐步衰减到1e-5batch_size8256×256×8通道下显存约5GB不够就减到4loss函数CrossEntropyLoss多分类标配配合类别权重处理不平衡epoch数50-80早停机制看验证集loss连续10个epoch不降就停类别不平衡的问题要在loss函数里处理。简单做法是给每个类别一个权重样本少的类别权重高。权重一般取N_total / (N_classes * N_class_i)即总像素数除以该类像素数再除以类别数这样各类的损失贡献基本均衡。PyTorch的CrossEntropyLoss直接支持weight参数传入一个长度为类别数的Tensor即可。训练监控方面每5个epoch在验证集上算一次OA总体精度和Kappa系数这两个指标比loss更直观。loss下降但精度不升一般是过拟合信号需要加大数据增强或降低模型容量loss和精度都停滞不前可能是学习率太小或数据有问题先确认预处理和标签对齐没毛病再动模型。4.4 训练脚本数据加载器、训练循环与模型保存训练循环本身不复杂难的是把数据加载、模型前向、反向传播、验证、保存这几步组织得不出错。下面是一个训练脚本的最小骨架用PyTorch实现读取上一步生成的npy样本对。import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader import numpy as np import os class LandsatDataset(Dataset): def __init__(self, ids_file, data_dir, augmentFalse): with open(ids_file) as f: self.ids f.read().strip().split(\n) self.data_dir data_dir self.augment augment def __len__(self): return len(self.ids) def __getitem__(self, idx): sample_id self.ids[idx] x np.load(f{self.data_dir}/x_{sample_id}.npy).astype(np.float32) y np.load(f{self.data_dir}/y_{sample_id}.npy).astype(np.int64) # 数据增强90度旋转和翻转 if self.augment: k np.random.randint(0, 4) x np.rot90(x, k, axes(1, 2)).copy() y np.rot90(y, k, axes(0, 1)).copy() if np.random.rand() 0.5: x np.flip(x, axis2).copy() y np.flip(y, axis1).copy() # 转成PyTorch张量x归一化到[0,1] x_tensor torch.from_numpy(x) y_tensor torch.from_numpy(y) return x_tensor, y_tensor # 训练配置 device torch.device(cuda if torch.cuda.is_available() else cpu) model SimpleUNet(in_channels8, num_classes6).to(device) train_ds LandsatDataset(train_data/train_ids.txt, train_data, augmentTrue) val_ds LandsatDataset(train_data/val_ids.txt, train_data, augmentFalse) train_dl DataLoader(train_ds, batch_size8, shuffleTrue, num_workers4) val_dl DataLoader(val_ds, batch_size8, shuffleFalse, num_workers4) # 类别权重按像素占比的反比计算 class_weights torch.tensor([1.0, 1.5, 0.8, 1.0, 1.2, 1.5]).to(device) criterion nn.CrossEntropyLoss(weightclass_weights) optimizer torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) for epoch in range(50): model.train() total_loss 0.0 for x_batch, y_batch in train_dl: x_batch x_batch.to(device) y_batch y_batch.to(device) optimizer.zero_grad() out model(x_batch) # (B, 6, H, W) y_batch y_batch.long() loss criterion(out, y_batch) loss.backward() optimizer.step() total_loss loss.item() # 验证 model.eval() correct 0 total 0 with torch.no_grad(): for x_batch, y_batch in val_dl: x_batch x_batch.to(device) y_batch y_batch.to(device) out model(x_batch) pred torch.argmax(out, dim1) # 只统计有效像素标签0 mask y_batch 0 correct (pred[mask] y_batch[mask]).sum().item() total mask.sum().item() val_acc correct / max(total, 1) print(fEpoch {epoch1:02d}, Loss: {total_loss/len(train_dl):.4f}, Val Acc: {val_acc:.4f}) # 保存最佳模型 if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth) scheduler.step()这段代码覆盖了数据加载、增强、训练和验证全流程。逻辑说明数据加载器负责从npy文件读取样本对增强只在训练集启用验证集保持原始数据类别权重让样本少的类别获得更大的损失贡献验证时mask y_batch 0过滤掉无效像素未标注区域标签设为-9999或-1。参数说明num_workers4是数据加载的并行进程数Windows下建议设为0避免多进程报错T_max50要和总epoch数一致否则余弦退火的学习率曲线不完整weight_decay1e-4做权重正则化抑制过拟合。5. 遥感影像分类避坑指南五个让新手翻车的细节5.1 精度明明很高分类图却一片椒盐噪声现象验证集OA跑到90%以上但把整景影像预测出来一看地物边界像是被啃过单个像素孤立地出现错误类别图面非常碎。原因逐像素分类的本质决定了每个像素独立决策没有考虑邻域一致性。模型在特征空间里学到的是光谱模式对单个像素的噪声敏感边界区域更容易出现零星错分。解决两招组合用。第一招是预测时做重叠滑窗与概率平均相邻patch重叠区域多次预测取平均能显著抑制边界噪声第二招是分类后处理用众数滤波或多数投票我这里一般用scipy.ndimage.median_filter或者形态学开闭运算窗口大小3×3或5×5注意不要过度平滑把细小水体抹掉了。5.2 训练集和测试集精度都很高换到新影像上精度暴跌现象在A区域训练的模型测试精度85%直接用到相邻的B区域影像上精度掉到60%以下。原因这是遥感深度学习最常见的泛化问题。不同时相、不同季节的影像地表反射率分布差异很大不同传感器Landsat 8和Landsat 9之间也有细微的光谱响应差异甚至太阳高度角不同阴影区的特征都完全不同。解决没有一劳永逸的办法靠数据策略缓解。一是训练集里加入多时相样本把不同季节、不同年份的patch都放进去增强光谱多样性二是在应用前对目标影像做直方图匹配让它和训练数据的光谱分布对齐三是在训练时做光谱增强对反射率做随机线性变换模拟不同光照条件。5.3 水体和阴影永远分不清现象分类图里的山体阴影区被成片分成水体或者水体被分成阴影两者互相混淆。原因水体和阴影在可见光波段光谱相似都是低反射率从红光到短波红外的曲线形态都很接近。单靠光谱信息区分它们非常困难这也是遥感分类的经典难题。解决增加辅助特征。MNDWI指数对水体非常敏感水体在绿光反射率高、在短波红外反射率低而阴影区域的光谱随下垫面变化没有稳定的指数特征所以在特征通道里保留MNDWI能帮模型区分。另外如果研究区地形起伏大把坡度或山体阴影掩膜作为额外输入通道也有效。从模型角度增加上下文信息用更大的patch_size也能帮助模型学到“旁边的山体导致阴影”这种空间关系。5.4 训练时报错“CUDA out of memory”现象batch_size设成16一跑训练就报显存不足换成batch_size4才勉强跑动。原因256×256×8通道的输入本身就不小U-Net的中间特征图占显存尤其大。很多人误以为是模型参数太多实际上激活值中间特征图占了大头。解决按顺序尝试三个办法。第一减小batch_size到4甚至2配合梯度累积accumulation_steps模拟大batch第二减小patch_size到128或192显存占用按patch_size的平方下降第三用混合精度训练PyTorch的torch.cuda.amp能把显存占用砍半。patch_size不建议小于128否则感受野太小地物上下文信息不足分类精度会下降。5.5 模型训练loss不下降或直接nan现象训练到第10个epochloss一直停留在2.0左右不降或者loss突然变成nan之后所有参数都变成nan。原因loss不降通常是学习率太大或太小或者标签里有未处理好的异常值loss变成nan则几乎可以肯定是数据里有nan或inf比如NDVI计算时除以0或者某个patch全是0值导致BatchNorm收到全零输入。解决先查数据再调参数。把第一个batch的数据打印出来检查是否有nan和inf、反射率是否在合理范围0到1之间、标签ID是否超出num_classes-1。数据没问题再调学习率从1e-3开始如果loss震荡就降到1e-4如果loss不掉就升到3e-3试试。一个实用的做法是写一个torch.autograd.set_detect_anomaly(True)PyTorch会直接告诉你哪个算子导致了nan定位非常快。6. 用训练好的模型做整景预测滑窗推理与精度验证的落地技巧训练完模型最后一步是把它应用到整景Landsat影像上生成分类图。因为模型输入是固定大小的patch所以整景影像需要通过滑窗推理。窗口大小和训练时的patch_size保持一致但步长不能等于patch_size否则相邻预测结果之间会有明显的拼缝。我一般用步长等于patch_size的一半这样每个像素会被多个窗口覆盖最后取多个预测概率的平均值作为最终输出。推理时还有一个细节要注意对于测试集中已经见过的区域不要重复参与精度评估否则结果虚高。正确流程是先用训练好的模型预测整景影像生成分类结果图然后拿测试集里的标签patch和预测结果做逐像素对比计算混淆矩阵、总体精度OA和Kappa系数。Kappa系数能反映分类结果和真实标签的一致性0.8以上说明分类效果优秀0.6到0.8说明可用低于0.6就需要回头找原因。最后我的习惯是把分类结果图用rasterio写成GeoTIFF带上原始影像的地理变换信息和投影坐标这样可以直接在QGIS或ArcGIS里打开叠加分析。写成GeoTIFF的过程不难核心是从原始影像的profile里复制transform和crs信息把预测结果的背景值设为0或者255。一份完整的Landsat地物分类工作流到这里就走完了从数据预处理、样本制作、模型训练到整景预测和精度评估每个环节都有对应的Python实现和参数调试经验。这条路径我走过很多遍踩过的坑基本都在前面几章列全了。按照这个流程做下来的Landsat分类项目哪怕研究区换了、类别换了一两个整体的框架都不用动改改参数和样本就能复现。希望这些从实际项目里攒下来的经验能帮你把第一个遥感地物分类任务顺利跑通。本文还有配套的精品资源点击获取

相关推荐

自编码器图像去噪实战:从原理到PyTorch实现与调优
自编码器图像去噪实战:从原理到PyTorch实现与调优

简介:基于Python深度学习的自编码器图像去噪项目,是一套面向毕业设计、期末大作业与课程设计的高分参考实现,围绕图像去噪任务提供DAE、VAE、DCAE三种自编码器变体,适合已有Python基础、希望快速上手深度学习的中级学习者&#xf… · 2026/9/23 3:07:32

Gel 官方 Docker 镜像部署指南:从 docker run 到 Docker Compose 的生产级配置
Gel 官方 Docker 镜像部署指南:从 docker run 到 Docker Compose 的生产级配置

Gel 官方 Docker 镜像部署指南:从 docker run 到 Docker Compose 的生产级配置 【免费下载链接】edgedb Gel supercharges Postgres with a modern data model, graph queries, Auth & AI solutions, and much more. 项目地址: https://gitcode.com/gh_mirror… · 2026/9/23 3:07:32

从跳绳计数实战解析姿态估计与AIoT端侧部署全链路
从跳绳计数实战解析姿态估计与AIoT端侧部署全链路

1. 从一根跳绳说起:算法到底藏在哪儿跳绳这件事,很多人第一反应是体育课,跟算法八竿子打不着。但如果你在科技公司待过,尤其是做过视觉算法或者物联网相关项目,就会知道"跳绳"其实是一个特别经典的测试场景。… · 2026/9/23 3:07:32

AI日报制作全流程:从信息筛选到深度内容沉淀的实操指南
AI日报制作全流程:从信息筛选到深度内容沉淀的实操指南

1. 一份“AI日报”到底在记录什么做AI日报这件事,我从2023年就开始断断续续地折腾,中间停过几次,又重新捡起来。原因很简单:信息太多,噪音太大,如果不做一层过滤和沉淀,每天刷到的内容就像流水一… · 2026/9/23 3:55:08

百灵斗牛牛实战项目避坑:3步搞定报错崩溃
百灵斗牛牛实战项目避坑:3步搞定报错崩溃

百灵斗牛牛实战项目避坑:3步搞定报错崩溃 报错一堆看不懂 StackTrace? 别慌,这是大多数搞 实战项目 的新人都会遇到的噩梦。特别是当你在处理高并发或者复杂业务逻辑时,那个红色的异常栈就像天书一样,看得人头大。… · 2026/9/23 3:55:02

单田芳评书白眉大侠避坑指南:3个核心原理助你一次通过
单田芳评书白眉大侠避坑指南:3个核心原理助你一次通过

单田芳评书白眉大侠避坑指南:3个核心原理助你一次通过 官方文档堆砌着几十页的参数定义,你翻了两页就头大,根本抓不住重点?别慌,这就是很多初学者在 单田芳评书白眉大侠 相关技术栈里栽跟头的地方。今天我不讲虚的,直接给你一份 避坑指南 。… · 2026/9/23 3:55:02

管理者高效汇报的7大场景与5大陷阱
管理者高效汇报的7大场景与5大陷阱

1. 从执行者到管理者的思维转变刚晋升为经理的前三个月,是我职业生涯中最痛苦的适应期。记得第一次参加部门周会时,我花了20分钟详细汇报了自己写的代码和调试过程,却发现总监的眼神越来越飘忽。会后,我的直属上司拍了拍我肩膀&am… · 2026/9/23 3:54:44

OpenCV+Mediapipe手势识别毕设源码:关键点提取与音乐触发实战
OpenCV+Mediapipe手势识别毕设源码:关键点提取与音乐触发实战

简介:这是一套面向计算机相关专业学生与项目实战学习者的手势识别系统源码,基于Python与OpenCV实现,适合用作毕业设计、课程大作业或技能练习的参考方案。项目经导师指导并通过评审,难度适中,源码均经本地编译调试&… · 2026/9/23 3:54:44

3招搞定刘伯温四不像图,避坑高频面试题
3招搞定刘伯温四不像图,避坑高频面试题

3招搞定刘伯温四不像图,避坑高频面试题 复制来的代码跑不通,报错信息满屏飞,新手最容易在这里卡死。 别慌,这种“刘伯温四不像图”式的逻辑陷阱,也是 高频面试题 里的常客。 今天不整虚的,直接拆解底层逻辑,教你怎么把死代码变活。… · 2026/9/23 3:54:44

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码