ARTICLE DETAIL

资讯详情

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

Landsat数据喂给CNN:遥感地物分类预处理与训练避坑全指南

Landsat数据喂给CNN:遥感地物分类预处理与训练避坑全指南 简介遥感图像分类是土地覆盖制图与生态监测中的高频需求而Landsat系列卫星提供的多光谱影像正是这类任务的主力数据源。在实际工程中CNN模型结构往往不是瓶颈真正的难点在于如何将Landsat原始数据转换为模型可用的训练张量。文章从Landsat Collection 2的L1与L2数据级别差异出发解释DN值、TOA反射率与地表反射率之间的转换原理并示范基于rasterio的波段堆叠与AOI裁剪流程。针对遥感影像特有的直方图分布进一步介绍百分比裁剪的鲁棒归一化方法并结合PatchCNN的构建、加权交叉熵处理类别不均衡以及训练中常见的坐标系错位、数据泄漏等典型问题给出完整的调参与验证方案。适合从事耕地识别、城市变化检测及土地利用分类的研究与工程人员参考是一份从数据到模型的Landsat深度学习实践指南。1. Landsat地物分类源码不难难的是Landsat数据本身怎么喂给CNN拿到一份CNN深度学习遥感影像地物分类的Landsat数据处理Python源码.zip多数人的第一反应是先把模型跑起来真正动手后会发现卡点全在数据侧Landsat的DN值、TOA反射率、地表反射率到底用哪个波段怎么堆叠AOI边界为什么对不齐归一化用什么范围训练标签和影像为什么总是错位。这些环节里任何一个出了问题CNN训练得再认真落地也是白费功夫。这个标题解决的是遥感地物分类里最典型的一类需求以Landsat 8/9 OLI多光谱影像为输入用深度学习模型把地表像素分类成水体、植被、建设用地、耕地、裸土等类别。适合正在做土地覆盖制图、生态监测、城市变化检测的从业者也适合刚接触Crop Classification、LULC制图的研究生。整套流程的核心不在模型多先进而在于把Landsat的物理量纲问题、样本制作方式和CNN的输入习惯对齐。这篇文章按“数据预处理 → 样本制作 → 模型训练 → 踩坑排查 → 调参验证 → 推理导出”的顺序把这份源码背后真正值得复现的技术点逐项拆开。2. Landsat影像预处理从L1级DN值到可训练反射率张量的完整流程2.1 先分清L1与L2级数据辐射定标与大气校正是否必须做Landsat Collection 2产品分为Level-1和Level-2两级。Level-1产品里的像元值是16位无符号整型DN值反映传感器接收到的辐射强度数值通常在0到30000之间受大气散射、吸收影响很大不能直接作为地物反射特征喂给CNN。Level-2产品是经大气校正后的地表反射率理论上数值范围在0到1附近能真实反映地物光谱反射特性。做地物分类时我几乎总是优先选L2产品省掉自己做大气的坑。判断数据级别只需要看文件名。L2级地表反射率波段文件一般带有SR_B字样例如LC08_L2SP_XXXXXX_SR_B2.TIFL1级辐亮度产品则命名为LC08_L1TP_XXXXXX_B2.TIF。如果你手里只有L1级数据就必须根据MTL文件中的参数把DN值转换成TOA反射率常见做法是读取MTL.txt里的REFLECTANCE_MULT_BAND_x和REFLECTANCE_ADD_BAND_x按线性公式对每个波段计算。注意Landsat 8/9的OLI传感器有11个波段但并非全部适合做地物分类输入我先明确几个常用波段的选择逻辑波段名称空间分辨率在地物分类中的用途B2Blue30m水体、大气散射敏感B3Green30m植被、水体识别B4Red30m植被、建设用地、裸土差异明显B5NIR30m植被健康度、水分探测B6SWIR130m土壤湿度、矿物识别B7SWIR230m地质制图、云检测、干旱监测我一般取B2到B7共6个波段作为CNN输入通道in_channels设为6。B1海岸波段易受大气影响B8全色波段是15m分辨率与多光谱波段分辨率不一致插入输入会增加重采样和配准成本B9卷云波段主要用于云检测分类任务不直接参与。这6个波段的空间分辨率都是30m可以原样堆叠成6通道张量省去很多麻烦。如果拿到的是L1级数据下面的转换函数可以接入你的流水线。它从MTL文件里读取第5波段对应的定标系数然后把整个堆叠波段按波段序号逐一转换到反射率。需要注意系数中还有一个减量REFLECTANCE_ADD_BAND_x不是单纯的乘法。def parse_mtl(mtl_path): import re params {} with open(mtl_path, r, encodingutf-8) as f: for line in f: m re.match(r\s*([A-Z0-9_])\s*\s*([\d\.\-Ee]), line) if m: params[m.group(1)] float(m.group(2)) return params def dn_to_toa(stack, mtl_dict, band_indices(2, 3, 4, 5, 6, 7)): out np.zeros_like(stack, dtypenp.float32) for i, b in enumerate(band_indices): mult mtl_dict[fREFLECTANCE_MULT_BAND_{b}] add mtl_dict[fREFLECTANCE_ADD_BAND_{b}] out[i] stack[i] * mult add return np.clip(out, 0, 1)parse_mtl用正则表达式提取MTL里的键值对实测能应对Collection 2的MTL格式。dn_to_toa里的band_indices要和前面堆叠波段顺序完全对应否则会把B4当成B5参与计算导致植被光谱特征错乱。clip到0和1之间是反射率物理约束如果不加异常像元产生的负值或大于1的值会让归一化阶段出现偏移。这个转换只在L1级产品上执行L2级产品本身就是地表反射率就不再做这一步否则会把反射率二次拉伸训练出来的模型在另一景影像上失效。2.2 用rasterio做波段堆叠、裁剪与投影一张Landsat场景的处理脚本Landsat原始场景范围约185km×180km单景影像约8000×8000像素不可能整景输入CNN。常规做法是先按研究区范围裁剪再做波段堆叠。这里用rasterio库完成两步操作比gdal命令行更可控也方便后续接numpy数组。堆叠阶段按波段列表逐个读取转成float32后沿第一维拼接。import rasterio import numpy as np def stack_landsat_bands(prefix, band_indices(2, 3, 4, 5, 6, 7)): arrays [] profile None transform None crs None for b in band_indices: path f{prefix}_B{b}.TIF with rasterio.open(path) as src: arr src.read(1).astype(np.float32) profile src.profile transform src.transform crs src.crs arrays.append(arr) stack np.stack(arrays, axis0) return stack, profile, transform, crs这段代码的关键是读入第一个波段时保存profile、transform和crs后面所有波段默认与它空间位置一致。Landsat L1和L2产品自带地理定位信息同一景影像各波段之间已经配准不需要额外做影像配准。读成float32而不是保留uint16是为了让后面归一化和模型输入都采用统一精度也避免uint16做除法时被截断成整数。裁剪AOI时我一般把研究区边界写成(west, south, east, north)四个坐标与影像投影坐标系保持一致。如果AOI是WGS84经纬度而影像投影是UTM直接用from_bounds会报错或裁出空白区域必须先对边界做坐标转换。from rasterio.windows import from_bounds def crop_landsat_to_bounds(src_path, out_path, bounds): west, south, east, north bounds with rasterio.open(src_path) as src: window from_bounds(west, south, east, north, src.transform) profile src.profile.copy() profile.update( heightwindow.height, widthwindow.width, transformsrc.window_transform(window) ) data src.read(windowwindow) with rasterio.open(out_path, w, **profile) as dst: dst.write(data)from_bounds的四个参数顺序是west, south, east, north不要写成xmin, xmax, ymin, ymax这种常见顺序。window的高度和宽度由栅格分辨率自动计算30m分辨率下若AOI宽度为3000m得到100个像素。profile.update里必须同步更新transform否则写出的GeoTIFF位置信息仍沿用整景影像的左上角裁剪图在ArcGIS里会叠加到错误位置。如果crop之后还要做波段堆叠建议先对单波段分别裁剪再走stack_landsat_bands流程避免一次读入整景影像占用过多内存。2.3 归一化不用Min-Max而用百分比裁剪遥感影像的直方图特色Landsat地表反射率的理论范围是0到1但实际影像里云层、雪、高反射建筑屋顶会产生极端高值阴影和水体则产生接近0的低值。如果直接用Min-Max归一化2%的极端像元会把有效地物的对比度压缩到很窄的区间CNN很难学到光谱差异。我常用的做法是先取每个波段的2%和98%分位数做一个鲁棒归一化。def robust_normalize(stack, low_percent2, high_percent98): out np.zeros_like(stack, dtypenp.float32) for i in range(stack.shape[0]): band stack[i] vmin np.percentile(band, low_percent) vmax np.percentile(band, high_percent) out[i] np.clip((band - vmin) / (vmax - vmin), 0, 1) return out这里按波段独立计算分位数好处是每个光谱通道的动态范围都被拉伸到0到1波段之间的亮度差异不会主导模型权重。对多云影像2%分位数可能取到云影98%分位数取到云顶导致正常地物压缩建议先做简单云掩膜后再归一化。clip的作用是强制把所有超过分位数范围的像元截断避免训练时出现大梯度。整个张量形状仍是(6, H, W)后面接模型时需要转成(N, C, H, W)的PyTorch格式。3. 样本与PatchCNN模型把Landsat像素变成训练数据的代码路径3.1 训练标签怎么来AOI矢量栅格化与人工标注栅格CNN监督训练需要逐像素的地物类别标签。起步阶段最常见的标签来源是已有的土地覆盖矢量例如从OpenStreetMap提取建设用地和水体或从GlobeLand30等公开土地利用产品重采样到30m分辨率。拿到矢量后先栅格化为与影像完全对齐的标签栅格可以用rasterio.features.rasterize实现关键是fill值设为-1表示不参与训练的像元。from rasterio.features import rasterize def vector_to_label(shapes, out_shape, transform, class_map): label np.full(out_shape, -1, dtypenp.int16) for class_id, geom_list in class_map.items(): mask rasterize( geom_list, out_shapeout_shape, transformtransform, fill0, default_value1, dtypenp.uint8 ) label[mask 1] class_id return labelclass_map是一个字典键为类别编号值为该类别对应的矢量Geometry列表。栅格化时同一像元可能被多个矢量覆盖后写入的类别会覆盖先写入的类别所以类别优先级较高的应放在class_map迭代顺序靠后的位置。fill0表示无矢量覆盖区域先填0后续会被-1整体替换但如果在循环里先写入了某个类别后面又被fill重置为0就比较麻烦。更稳妥的做法是先在一个全零数组上累加每个类别的掩膜最后统一把全零区域置为-1。也可以直接在影像上人工标注训练样本QGIS里新建一个多边形图层按地物类型勾画几个代表性的多边形区域再走同样的栅格化流程。人工标注的优点是类别边界完全由自己控制缺点是费时间。我常用的策略是先栅格化公开矢量得到初版标签再在验证阶段用错分样本反向补标注比一开始就手工勾画整个研究区效率高得多。3.2 PatchCNN结构3层卷积加自适应池化5类地物分类够用当前主流的遥感地物分类已经全面转向U-Net、DeepLab这类语义分割模型但很多老旧源码包和工程流水线仍是Patch-based CNN把每个像元周围的固定尺寸邻域作为输入输出中心像元的地物类别。这个思路对显存占用更低训练数据也更容易制作理解起来更直观。网络结构不必复杂常见做法是3层卷积每层都带ReLU和MaxPooling最后用AdaptiveAvgPool把特征图压缩到1×1再接全连接分类器。import torch import torch.nn as nn class PatchCNN(nn.Module): def __init__(self, in_channels6, num_classes5): super().__init__() self.features nn.Sequential( nn.Conv2d(in_channels, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool2d((1, 1)) ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): x self.features(x) return self.classifier(x)网络输入的patch尺寸一般取32×32或64×64。以Landsat 30m分辨率为例32×32 patch对应960m×960m地表范围足以覆盖一片农田、一个村庄、一段河道的中尺度纹理。输入通道数in_channels必须与预处理阶段堆叠的波段数一致如果用了6波段堆叠这里就是6。把Windows下CNN的卷积核固定为3×3并加padding1特征图尺寸逐层减半最后用AdaptiveAvgPool让网络不限制输入尺寸后续换更大patch时不需要改全连接层。这个结构的参数量大约在百万级单景几百个训练patch就能训练不需要预训练权重。如果发现训练精度上不去优先检查数据归一化和样本质量而不是急着加深网络。深层网络在中小规模遥感样本集上反而容易过拟合。3.3 训练循环与参数加权CrossEntropy处理类别不均衡遥感地物分类的样本天然不均衡。研究区如果有60%是耕地、20%是林地、7%是水体、3%是建设用地、10%是裸土普通交叉熵会把少数类直接忽略。解决办法是给CrossEntropyLoss传入weight参数权重通常按类别频率的倒数计算让模型对低频地物施加更大惩罚。from torch.utils.data import Dataset, DataLoader class LandsatPatchDataset(Dataset): def __init__(self, patches, labels): self.patches torch.tensor(patches, dtypetorch.float32) self.labels torch.tensor(labels, dtypetorch.long) def __len__(self): return len(self.labels) def __getitem__(self, idx): return self.patches[idx], self.labels[idx] def compute_class_weights(labels, num_classes): counts np.bincount(labels, minlengthnum_classes).astype(np.float32) counts[counts 0] 1 total counts.sum() weights total / (num_classes * counts) return torch.tensor(weights, dtypetorch.float32)compute_class_weights里用total除以类别数再除以每个类别的计数得到的权重大致在0.2到5之间。如果某个类别完全没有样本bincount返回0直接除会得到inf这里用counts[counts 0] 1做兜底。注意权重应当在划分训练集和验证集之后按训练集的标签计算不能用全量标签计算否则验证集中的分布差异会被权重放大。Dataset类把numpy数组转成torch.tensor训练时DataLoader会按batch_size自动组装。训练循环本身是标准流程Adam优化器配合1e-3学习率batch_size取64左右。如果patch为64×64单卡8G显存下batch_size 64能跑但超出后优先减小batch_size而不是减小patch。device torch.device(cuda if torch.cuda.is_available() else cpu) model PatchCNN(in_channels6, num_classes5).to(device) criterion nn.CrossEntropyLoss(weightcompute_class_weights(train_labels, 5)) optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(60): model.train() total_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() output model(images) loss criterion(output, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) avg_loss total_loss / len(train_loader.dataset) print(fepoch {epoch 1:03d} loss {avg_loss:.4f})训练时每轮打印平均loss如果loss在3个epoch内没有明显下降先检查数据预处理是否把反射率数值归一化到0到1再检查标签数组是否大部分为-1。验证集指标不要只用整体准确率要按第5章里的混淆矩阵逐类分析否则水体这类小目标被淹没在98%的准确率里模型实际上完全没学到水体特征。4. 复现源码避坑坐标系错位、类别缺失、显存爆炸与数据泄漏4.1 现象Loss一直在掉验证精度纹丝不动Loss曲线正常下降但验证集每个epoch都稳定在同一个低精度值这是很多Landsat分类源码跑起来后最让人烦躁的情况。我遇到过多次原因几乎都是训练样本和标签栅格在空间上错位。Landsat影像经过投影转换、裁剪后如果用同样的transform去栅格化AOI矢量理论上应该对齐但实际操作中研究者经常忘记把矢量重投影到影像的UTM坐标系栅格化出来的标签整体偏移了十几个像元CNN看到的特征和标签不匹配只能学到随机猜测。排查方法很粗暴把一张训练patch和它对应的标签叠加可视化看看水体和植被边界是否吻合。如果不吻合先在QGIS或GDAL里统一投影。开发时不要让模型训练和空间分析混在同一个流程里先单独输出几张patch和标签的对比图确认无误后再启动训练。这一步花10分钟省下来的是一整天无效训练。4.2 现象预测结果整片输出同一个类别模型训练完毕跑完整个研究区的推理输出的分类图几乎全是同一类地物偶尔零星出现其他类别。这类翻车通常是标签类别编号和模型输出维度不一致。标签栅格里的类别是1到5但模型num_classes写成了4或者标签里存在0值但你把它当作有效类别模型把类别0学到了实际预测时遇到任何输入都倾向输出0。另一个隐蔽原因是归一化阶段出了问题。如果百分比裁剪后的影像张量大多是0或1的极端值CNN的卷积输出会被激活函数饱和梯度消失。解决方法是检查输入影像的像素值分布print出训练集patch的最小值、最大值和均值正常情况下均值应该在0.2到0.8之间如果所有波段均值都接近0或接近1回看2.3节的归一化过程重点检查是否在归一化之前做了无效的clip操作。4.3 现象batch一上去就显存溢出显存溢出的直接原因是前向传播过程中中间特征图占用了显存。Landsat是6通道输入比三通道RGB多一倍第一个卷积层输出的特征图数量为32如果patch为128×128batch为32仅第一层激活值就占数百MB。解决优先级从高到低是这样的先把batch_size减半再把patch从128降到64最后把卷积核数量从32降到16。不要一开始就改模型结构训练数据的空间上下文会随patch缩小而改变需要同步验证精度。如果必须保持大patch可以用混合精度训练。PyTorch里torch.cuda.amp.autocast配合GradScaler能减少约40%显存但要注意Landsat影像归一化后的float32精度不高混合精度训练时用小学习率更稳定。显存优化的底线是batch_size不能小于16否则BatchNorm统计量不稳定模型收敛变慢。4.4 现象训练集精度高但验证集崩过拟合还是数据泄漏训练集准确率95%验证集只有60%第一反应是过拟合但Landsat patch分类里更常见的是数据泄漏。如果训练集和验证集是同一景影像上相邻区域随机切出来的patch相邻patch之间重叠严重它们共享了大量相同像元模型在训练时见过的像素以另一种组织形式出现在验证集里验证指标虚高反过来如果训练集和验证集来自不同季节或不同云量影像光谱分布差异又会让模型抓狂。正确切分方法按地理区域划分不要在整景影像上随机抽样。我一般做法是把研究区按东西或南北切成两块东侧全部作训练西侧全部作验证中间留一条宽度为20个像元的缓冲带做隔离。对多时相研究更严格的做法是整个时间序列只抽几景做训练其余景做验证。这条原则比模型选型更影响最终可信度。5. 调参与验证混淆矩阵和后处理让分类结果真正可用5.1 学习率、batch和patch尺寸的联动设置Landsat Patch分类模型的参数设置没有绝对标准但它们之间存在联动关系。patch越大感受野越大模型能看到更多空间上下文但梯度噪音更大学习率要相应调小。我常按以下范围基准启动patch 32×32配1e-3学习率patch 64×64配5e-4到1e-3patch 128×128配1e-4。batch_size影响梯度估计的稳定性batch为32时学习率上限约为1e-3batch降到8时1e-2会导致明显震荡。学习率调度我用过CosineAnnealingWarmRestarts在60到100个epoch的中等规模训练里比等间隔衰减稳定。遥感样本噪声比自然图像大很多学习率在最后20个epoch降到初始值的1/10能明显减少分类图上的椒盐噪声。如果训练集很小只有几千个patchAdam的默认beta20.999可能导致前期震荡改为0.95能改善稳定度。5.2 用混淆矩阵找出被吞掉的类别整体准确率在地物分类里基本是欺骗性指标。一个研究区里耕地70%占主导模型全预测成耕地也能拿到70%准确率现实中这样的模型毫无价值。验证时我必看归一化混淆矩阵它对每一类分别计算召回率能直接暴露出被吞掉的小类别。from sklearn.metrics import confusion_matrix import numpy as np def normalized_confusion(y_true, y_pred, num_classes5): cm confusion_matrix(y_true, y_pred, labelslist(range(num_classes))) cm cm.astype(np.float32) row_sum cm.sum(axis1, keepdimsTrue) row_sum[row_sum 0] 1 return cm / row_sumnormalized_confusion输出按行归一化每一行代表该类别的召回率对角线接近1说明该类识别良好。常见问题在第一类和最后一类上水体类往往落到裸土类因为低反射率水体与阴影中的裸土在光谱上相似。如果某两类混淆严重仅靠加深CNN解决不了需要增加输入波段例如加入NDWI、NDVI等指数特征作为额外通道。这些指数可以在预处理阶段用B3、B5波段计算后直接拼进stack模型输入通道数变为8结构不需要改。混淆矩阵还能帮助发现标签噪声。如果某一行的值分散到三个类别以上说明训练标签里这一类的地物本身就混着其他类需要回看标注精度而不是继续增加训练轮数。我在实际项目中会用验证集错误样本巡检把预测错误和高置信度的patch都导出成图快速定位到底是谁在混淆。5.3 后处理多数滤波与小图斑剔除CNN逐像素分类的常见产物是盐噪声分类图里单个像元或极小斑块频繁跳变。地物分布本身具有空间连续性一栋建筑、一条河流在30m分辨率下至少有几个连续像元因此后处理环节能显著提升制图效果。多数滤波是最简单的方法用scipy的median_filter对每个类别单独滤波会破坏边界我通常对整个标签图按像素邻域取众数。from scipy.ndimage import median_filter def majority_filter(pred_map, size5): classes np.unique(pred_map) smoothed np.zeros_like(pred_map) for c in classes: mask (pred_map c).astype(np.uint8) count median_filter(mask, sizesize, modeconstant) smoothed[count size * size * 0.5] c remaining smoothed 0 smoothed[remaining] pred_map[remaining] return smoothed这里的median_filter用于统计每个类在邻域内出现的次数阈值取邻域面积的0.5意味着过半数。size5对应150m×150m窗口对30m分辨率影像刚好能消除孤立像元又不会抹掉窄河道路网。modeconstant让影像边缘用0填充避免边界出现假类别。多数滤波会略微平滑地物边界如果研究区有大量细碎地块size3更合适。小图斑剔除可以再进一步对连通域小于某个面积的类别斑块用周围最大区域类别替代。用scipy.ndimage.label按4连通或8连通识别连通域然后统计每个连通域的面积。实际项目中我一般只做多数滤波面积阈值剔除在成果制图阶段按具体要求处理。6. 进阶滑窗推理太慢全卷积U-Net才是整景分割的正解PatchCNN的推理模式天然低效。一景裁剪后的影像若为3000×3000像元每预测一个像元就要裁剪一个patch并前传一次网络即使batch推理也要跑900万次窗口提取。我早期跑这种流程时GPU推理几小时CPU裁剪预处理几小时整个预测流程比训练还难熬。如果想继续用这个源码思路做整景分类正确办法是把网络后半段的全连接层替换成1×1卷积让模型接受任意尺寸输入并直接输出整张概率图。class PatchAsFCN(nn.Module): def __init__(self, pretrained_features, num_classes): super().__init__() self.features pretrained_features self.head nn.Conv2d(128, num_classes, kernel_size1) def forward(self, x): return self.head(self.features(x))这里pretrained_features可以直接复用PatchCNN中训练好的features部分head层需要在新数据集上微调因为原来的Linear层权重不能直接搬进1×1卷积。微调时把整个全图切成若干块输入输入尺寸不再固定卷积和池化都能适应。比这更彻底的做法是换用U-Net结构保留编码器部分用转置卷积做上采样恢复分辨率逐像素分类效果通常比PatchFCN更好对边界定位也更准。模型导出和部署值得单独说。训练完成后我把模型转成ONNX格式方便后续用OpenCV或ONNX Runtime做推理。导出时注意dummy input的尺寸必须和最终推理输入尺寸一致或者用dynamic_axes声明动态维度。model.eval() dummy torch.randn(1, 6, 64, 64) torch.onnx.export( model.cpu(), dummy, landsat_cnn.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch, 2: height, 3: width}, output: {0: batch, 2: height, 3: width}}, opset_version13 )导出ONNX后的好习惯是先对比ONNX Runtime和PyTorch的输出确保数值一致再继续接后续流程。输出预测结果写回GeoTIFF时把预处理的transform和crs原样套用否则做出来的分类图没法在GIS软件里叠加。推理阶段我会把整景影像按512×512的重叠块送入网络块与块之间留12.5%的重叠率拼图时只取块中心区域避免边缘伪影传导到最终结果。现在回头看这套Landsat地物分类方案真正花时间的地方始终在数据侧Landsat的物理量纲、归一化策略、样本切分方式、坐标一致性每一环都能让模型效果天翻地覆。我现在的习惯是写训练代码之前先花半小时检查波段顺序、坐标系和标签分布这个习惯帮我避开了大量返工。如果你照这条路把预处理和样本制作做扎实CNN模型本身反而不是瓶颈希望帮到你。本文还有配套的精品资源点击获取
返回列表