news 2026/9/28 17:11:41

CNN遥感地物分类:Landsat影像深度学习实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CNN遥感地物分类:Landsat影像深度学习实战指南

简介:面向遥感与深度学习交叉方向学习者的CNN地物分类实战资源,对应Landsat影像的像素级分类任务,适合计算机、地信、人工智能等专业学生用于课程设计或毕业设计。压缩包共10个文件,14.89MB,包含Python源码、训练好的模型权重(h5)、Landsat示例影像(tif)及配套地理配准文件(tfw/xml),以及项目说明文档。源码按流程拆分:影像切片生成、模型训练、新数据预测三个脚本,可直接对照README运行,从数据准备到结果输出形成完整闭环。已有969人学习下载。通过该资源可掌握基于CNN的遥感影像地物分类基本流程,了解训练数据制作、模型保存与加载、未知影像预测等关键环节,也可作为进一步开展植被/水体/建筑等地物识别实验的起点。

1. 遥感地物分类的现状:为什么用 CNN 成了必然选择

做 Landsat 影像地物分类,很多人第一反应还是最大似然、随机森林这类传统分类器。遇到 30 米分辨率、7 个有效波段的 OLI 数据,植被和农田、裸土和建筑、水体和阴影之间光谱重叠严重,传统方法调参调到头也只能做到 85% 左右的总体精度。换成基于 cnn 卷积神经网络的深度学习方案之后,同样的训练样本,总体精度普遍能拉到 93% 以上,水体和阴影这种常年翻车的类别也有了明显改善。这套「基于CNN深度学习的遥感landsat影像地物分类方法」源码包,解决的就是从 Landsat 原始影像到最终分类成图这一整条链路的问题:多波段数据怎么组织、标签怎么对齐、模型怎么训练、预测结果怎么拼回完整影像。适合手里有 Landsat 数据和一套标签、想把分类精度再往上提一提的从业者和研究生;也适合刚接触遥感深度学习的新手把它作为第一个能完整跑通的项目。值得注意的是,CNN 解决的远不只是「换个更好的分类器」这么简单,它把特征提取和分类决策合并在了一个模型里,这才让那些传统方法完全分不开的地物成为可能。

2. 从影像到样本:Landsat 数据预处理与训练切片

2.1 波段选择:不是所有波段都该进模型

拿到 Landsat 8 OLI 的 Level-1 或者 Level-2 数据,常见的是一个包含 11 个波段的 GeoTIFF,或者分波段存储的一堆 TIF 文件。很多第一次做的人会想「波段越多信息越丰富」,直接全波段往里塞。实际上 B1 海岸带波段气溶胶散射影响大,B9 卷云波段主要是大气校正用的,B10、B11 热红外是 100 米重采样到 30 米的,空间细节本来就丢了。硬塞进去,模型容量被无效信息占掉,训练还变慢。

我一般保留 B2(蓝)、B3(绿)、B4(红)、B5(近红外)、B6(短波红外 1)、B7(短波红外 2)这 6 个 30 米波段。B5 对植被叶绿素含量极其敏感,B6/B7 对土壤湿度、建筑物材质区分度很高,B2-B4 提供真彩色基准。下面是读取和筛波段的代码。

import rasterio import numpy as np def load_landsat_bands(tif_path, bands=[2, 3, 4, 5, 6, 7]): """ 读取 Landsat 8 OLI 多波段影像,band 编号对应文件内的波段序号。 注意:rasterio 读取时下标从 1 开始,与 numpy 下标从 0 开始不同。 """ with rasterio.open(tif_path) as src: # 读取指定波段,src.read 传入的是波段序号列表,返回 shape = (波段数, 高, 宽) data = src.read(bands) profile = src.profile transform = src.transform # 转成 (高, 宽, 波段数) 的排列,方便后续按像素窗口切片 data = np.transpose(data, (1, 2, 0)) return data, profile, transform # 使用示例 image, profile, transform = load_landsat_bands("LC08_L2SP_137039_20230501_20230509_02_T1_SR.TIF") print(image.shape) # 期望输出 (h, w, 6) print(image.dtype) # 期望输出 uint16,Landsat SR 产品通常是 16 位整数

这里有两处关键点:第一,src.read()接受的是波段序号列表,开发者容易混淆的是 rasterio 的波段从 1 开始,而 numpy 数组下标从 0 开始,写代码时不要搞混。第二,Landsat Collection 2 的 Surface Reflectance(SR)产品虽然名义上是反射率,但存储的是放大 10000 倍的整型,dtype 是uint16,直接丢进模型之前必须做归一化,这点在后面 2.3 会具体处理。

2.2 影像裁剪与归一化:训练样本到底怎么切

Landsat 单景影像大约是 7800×7800 像素,GPU 显存根本放不下整景。常见做法是把影像和标签一起切成固定大小的 patch,比如 128×128 或者 256×256。patch 太小,模型看不到足够的空间上下文,建筑物阴影和水体更难区分;patch 太大,类别占比失衡严重,一个小村庄可能只占 20×20 像素,其余全是农田。我做过对比,128×128 在 30 米分辨率下对应 3.84 公里见方,既能覆盖中等尺度的地物纹理,又不会让单个类别占比极端化。

归一化不能对整景影像用(x - min) / (max - min),因为单景影像里云、雪、亮色建筑会产生极端值,把水体这种低值区域压到几乎为 0,模型对水体基本学不动。更稳的做法是按百分位截断,比如把每景影像的 2% 和 98% 分位当作最小值、最大值做线性拉伸。

def normalize_percentile(image, lower=2, upper=98): """ 按分位数截断归一化,逐波段独立计算。 输入 image shape = (h, w, bands),dtype 任意数值型。 返回 float32,范围 [0, 1]。 """ h, w, bands = image.shape normalized = np.zeros_like(image, dtype=np.float32) for b in range(bands): band_data = image[:, :, b] p_low = np.percentile(band_data, lower) p_high = np.percentile(band_data, upper) # 防止影像中某波段是常数导致除零 if p_high - p_low < 1e-6: normalized[:, :, b] = 0.0 continue band_norm = (band_data - p_low) / (p_high - p_low) normalized[:, :, b] = np.clip(band_norm, 0, 1) return normalized.astype(np.float32)

归一化时容易出现一个隐藏陷阱:如果训练数据来自多景影像,比如夏天一景、冬天一景,那么分位数应该每景各自计算而不是全放一起算。不同季节的 Landsat 影像辐射差异本来就大,放一起归一化等于把所有影像拉到同一个亮度基准,反而破坏了季节差异带来的分类线索。我自己会在每景影像单独归一化之后,再做一次全体均值和方差的统计,确认各景之间分布没有特别离谱的偏移。

2.3 标签生成与数据集划分:空间自相关是最大隐患

标签通常有两种来源:一种是已有的矢量分类图,比如土地利用调查的 shapefile,需要栅格化成与影像同一个 grid;另一种是人工目视解译画出来的多边形,同样要栅格化。栅格化时最容易出问题的是像元对齐——矢量栅格化用的 transform 和影像的 transform 不一致,导致标签整体偏移几个像素。最稳妥的方式是直接用rasterio.features.rasterize,并且传入影像本身的 transform 和 shape。

import rasterio.features import geopandas as gpd def vector_to_label(shp_path, profile, transform, class_field="class_id"): """ 把矢量标签栅格化为分类标签图。 profile 和 transform 取自对应影像,保证标签与影像严格对齐。 """ gdf = gpd.read_file(shp_path) # 确保矢量与影像坐标系一致,不一致时先重投影 if gdf.crs.to_string() != profile["crs"].to_string(): gdf = gdf.to_crs(profile["crs"]) shapes = [(geom, int(value)) for geom, value in zip(gdf.geometry, gdf[class_field])] label_array = rasterio.features.rasterize( shapes, out_shape=(profile["height"], profile["width"]), transform=transform, fill=0, # 0 作为背景类,不参与训练 dtype=np.uint8 ) return label_array

栅格化之后滑窗切片时,必须保证影像 patch 和标签 patch 在空间位置一一对应,最简单的方式是对影像和标签用同一个窗口起始坐标做切片,不要单独生成各自的窗口。

数据划分上踩过的大坑是真随机打乱像素。把整景影像的所有 patch 混在一起随机分训练集、验证集,验证集精度能到 97%,但换一景新影像直接掉到 70%。原因在于相邻 patch 之间有大量重叠像素,或者同一块农田被切进两个集合,模型「背」下了空间位置而非泛化能力。正确做法是按空间位置划分,比如把影像从中间劈成两半,一半做训练、一半做验证,或者干脆用另一景完全不相邻的影像做验证。

def split_patches_by_region(images, labels, val_ratio=0.2): """ 按空间区域划分数据,避免相邻 patch 被分到 train 和 val 两侧。 这里简单按行方向分块;更好的做法是按地理坐标或行政区划划分。 """ n_patches = len(images) split_index = int(n_patches * (1 - val_ratio)) train_images, train_labels = images[:split_index], labels[:split_index] val_images, val_labels = images[split_index:], labels[split_index:] return (train_images, train_labels), (val_images, val_labels)

这里的split_by_region看起来简单,实际使用时必须保证传入的 patch 列表本身就是按空间顺序排列的,切片生成顺序如果被打乱过,这个函数就失效了。另一个更可靠的做法是在滑窗生成 patch 时,记录每个 patch 的左上角像素坐标(row, col),然后按坐标范围而不是索引来做划分。

3. 源码里的 CNN 模型:核心结构与参数这样调

3.1 模型骨架:轻量 U-Net 比纯分类网络更适合地物制图

Landsat 地物分类的输出是逐像素的地物类别,所以这里不能用 ImageNet 那种输出单一类别的分类网络,要做语义分割。最常见的骨干结构是 U-Net 及其变体,它的编码器部分逐层下采样提取高层语义特征,解码器部分通过跳连接把低层纹理细节融合回来,最后由 1×1 卷积输出每个像素的类别概率。这个对称的编码器-解码器结构,在遥感影像分割里几乎是默认选择。

源码包里最常见的实现是简化的 U-Net,编码器三层、解码器三层,初始通道 32 或 64。通道数设太大会让参数量爆炸,Landsat 才 6 个输入波段,不需要 ResNet50 级别的容量;设太小又学不动地物的纹理差异。30 米分辨率的地物分类,32 起步、逐层翻倍到 128 就够用了。

import torch import torch.nn as nn class SimpleUNet(nn.Module): """ 轻量 U-Net,面向 Landsat 6 波段输入。 in_channels=6,out_channels=类别数(含背景就多一维)。 """ def __init__(self, in_channels=6, num_classes=6, base_channels=32): super().__init__() # 编码器:两个卷积块 + 一次池化 self.enc1 = self._block(in_channels, base_channels) self.enc2 = self._block(base_channels, base_channels * 2) # 解码器:转置卷积上采样 + 一个卷积块 self.up = nn.ConvTranspose2d(base_channels * 2, base_channels, kernel_size=2, stride=2) self.dec1 = self._block(base_channels * 2, base_channels) # 跳连接拼接后通道翻倍 self.out = nn.Conv2d(base_channels, num_classes, kernel_size=1) def _block(self, in_ch, out_ch): return nn.Sequential( nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), nn.Conv2d(out_ch, out_ch, kernel_size=3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), ) def forward(self, x): e1 = self.enc1(x) e2 = self.enc2(nn.MaxPool2d(2)(e1)) d1 = torch.cat([self.up(e2), e1], dim=1) return self.out(self.dec1(d1))

_block内每个卷积后面都接 BatchNorm,这是遥感数据训练稳定的必要条件。Landsat 不同波段的量纲差异很大,虽然做了归一化,但模型内部特征值仍然会漂移,BatchNorm 能显著缓解这种内部协变量偏移。另外注意跳连接拼接后通道数是原来的两倍,dec1的输入通道要相应改成base_channels * 2,很多人第一次搭 U-Net 都是在这里维度对不上报错。

3.2 训练主循环:损失函数与关键超参数的取舍

地物分类的训练 loss 最常见的方案是CrossEntropyLoss。Landsat 地物类别通常是水体、植被、裸土、建筑、农田这样几类,各类别像素占比极不均衡,水体可能只占 3%,裸土可能占 40%。这时候给CrossEntropyLoss传一个权重向量,让占比小的类别得到更大的惩罚权重,是见效最快的改进。

import torch.optim as optim from torch.utils.data import DataLoader # 类别权重示例:背景0不参与,水体占比小权重给大 # class_weights 长度必须等于模型输出通道数 class_weights = torch.tensor([0.0, 1.2, 0.8, 1.0, 0.7, 3.0], dtype=torch.float32).cuda() criterion = nn.CrossEntropyLoss(weight=class_weights, ignore_index=0) model = SimpleUNet(in_channels=6, num_classes=6).cuda() optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4) scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50) # 训练主循环伪代码(batch 组织略) for epoch in range(50): model.train() running_loss = 0.0 for images, labels in train_dataloader: images, labels = images.cuda(), labels.cuda() optimizer.zero_grad() outputs = model(images) # shape: (B, C, H, W) loss = criterion(outputs, labels) # labels: (B, H, W) 长整型 loss.backward() optimizer.step() running_loss += loss.item() scheduler.step()

优化器选AdamW而不是老式的Adam。地物分类任务里 L2 正则对抑制边界过拟合有效,Adam 的实现里 weight decay 和 L2 正则不是一回事,AdamW 修正了这一点。学习率 1e-3 是很稳的起步值,配合CosineAnnealingLR在 50 个 epoch 内从 1e-3 降到接近 0,基本不需要手调学习率曲线。batch size 在 12GB 显存上设 16,patch 128×128,6 波段输入,刚好能跑;显存小就把 batch 降到 8 或者 patch 降到 96。

3.3 类别不平衡:不只是加权重那么简单

类别不平衡是遥感分类里最容易让模型翻车的问题。水体在大部分区域影像里占比很低,训练时模型发现「预测成植被」的 loss 降低最快,就会倾向于把什么都预测成植被,结果水体像碎片一样被吃掉,这跟现实中专业人员的目视结果完全没法比。

加权重是最简单的处理,但权重怎么给有讲究。直接按「类别像素占比倒数」给权重,往往会让模型对少量类别过度敏感,把阴影、暗色屋顶全部分到水体里。我一般把权重上限压在 3.0,下限拉到 0.7,宁可少数类别漏分,也不要误分一片。另一个补救办法是专门挖「难例」,把训练中 loss 最大的那些 64×64 小块挑选出来追加训练,这些小块通常集中在边界和水体边缘,补一轮之后 IoU 提升非常明显。

如果数据作者有精力做数据增强,旋转、翻转、小角度的随机裁剪对 Landsat 这种平移不变性强的影像帮助很大。但注意不要做强光照增强,Landsat 影像已经是地表反射率,加亮度扰动等于伪造辐射信息,真实应用时不可能有这种变化。

4. 推理与成图:从模型输出到完整分类结果

4.1 滑窗预测与重叠拼接:边缘错位问题的解法

训练时切了 patch,推理时同样要切 patch,但推理的滑窗步长必须小于 patch 尺寸,否则 patch 边界处的预测结果会因为感受野缺失出现明显的块状接缝。常用的做法是步长取 patch 的一半,比如 patch 128、步长 64,推理完每一块之后把中心 64×64 区域作为有效区域写入对应位置,边界部分丢弃。下面是一个完整的滑窗推理示例,输出为整景影像的类别图。

def sliding_window_inference(model, full_image, patch_size=128, stride=64, batch_size=8): """ 全图推理。stride < patch_size 保证每个像素都至少位于一个 patch 的中心区域。 返回 uint8 类型的类别索引图。 """ model.eval() height, width, _ = full_image.shape result = np.zeros((height, width), dtype=np.uint8) count = np.zeros((height, width), dtype=np.float32) # 按步长生成左上角坐标 rows = list(range(0, height - patch_size + 1, stride)) cols = list(range(0, width - patch_size + 1, stride)) if rows[-1] != height - patch_size: rows.append(height - patch_size) if cols[-1] != width - patch_size: cols.append(width - patch_size) with torch.no_grad(): for r in rows: for c in cols: patch = full_image[r:r+patch_size, c:c+patch_size] # (128, 128, 6) patch_tensor = torch.from_numpy(patch).permute(2, 0, 1).unsqueeze(0).float().cuda() logits = model(patch_tensor) # (1, C, 128, 128) pred = torch.argmax(logits, dim=1).squeeze(0).cpu().numpy() # (128, 128) # 只保留中心区域,边缘丢弃 start_r = (patch_size - stride) // 2 start_c = (patch_size - stride) // 2 end_r = start_r + stride end_c = start_c + stride result[r+start_r:r+start_c+stride, c+start_c:c+start_c+stride] = pred[start_r:end_r, start_c:end_c] count[r+start_r:r+end_r, c+start_c:c+end_c] += 1 # 理论上每个像素至少被覆盖一次,count 可能为 0 的点用近邻填充兜底 unfilled = count == 0 if unfilled.any(): print(f"warning: {unfilled.sum()} pixels unfilled, applying nearest fill") # 简化处理:对未填充坐标直接做最近邻插值 from scipy.ndimage import distance_transform_edt idx = distance_transform_edt(unfilled, return_distances=False, return_indices=True) result[unfilled] = result[tuple(idx[i][unfilled] for i in range(2))] return result

推理的显存峰值是训练的一半左右,batch_size 可以适当放大。预测量大的场景下,逐 patch 推理的耗时主要花在 Python 循环和 GPU kernel 启动上,一般的 RTX 3060 跑一景 7800×7800 影像大约需要 15 到 25 分钟,这是正常水平。

4.2 保存成 GeoTIFF:坐标系和元数据别丢

推理结果只是 numpy 数组,要落回地理空间必须把原始影像的 transform、crs 一起写进输出文件。这一步如果漏了,分类结果在 GIS 软件里就是一张没有地理定位的普通图片,完全没法用。

def save_result_tiff(result, profile, output_path): """ 把分类结果保存为 GeoTIFF。 profile 直接复用原始影像的,改 dtype 和 count 即可。 """ out_profile = profile.copy() out_profile.update(dtype=rasterio.uint8, count=1, compress="lzw", nodata=0) with rasterio.open(output_path, "w", **out_profile) as dst: dst.write(result, 1) dst.update_tags(AREA_OR_POINT="Area")

用 LZW 压缩是因为分类结果大量连续区域是同一个值,压缩比很高,文件比原始 uint16 影像小很多。nodata 设成 0 是沿用背景类约定,如果原始影像有真正的无值区域,需要把那些区域在标签里就处理成 255 而不是 0,避免和背景类混淆。

5. 地物分类源码避坑:训练到成图最常见的 5 个坑

5.1 标签和影像错位几个像素,模型精度原地不动

现象:训练 loss 正常下降,验证精度也还行,但把预测结果叠加到影像上肉眼检查,发现地物边界整体偏向一侧,或者边界处是宽窄不一的黑边。

原因:矢量栅格化时没有严格复用影像的 transform,使用了矢量数据自己的分辨率或者坐标范围;另一个常见来源是影像在预处理过程中被重投影过,但标签还是老的坐标系。

解决:标签栅格化前先用gdf.crs.to_string()和profile["crs"].to_string()做比对,不同就直接to_crs(profile["crs"]),栅格化时显式传transform=transform和out_shape=...。更简单的自查方法:把标签数组和影像的近红外波段做成半透明叠加,肉眼看一下河流边界对不对得上。

5.2 验证集精度高,换一景影像全部崩盘

现象:训练集和验证集是在同一景影像上随机采样切 patch 的,训练时验证 mIoU 可达 90% 以上;部署到相邻时相或者相邻轨道的新影像,总体精度掉到 70% 以下,水体区域尤其明显。

原因:空间自相关。同一景影像里相邻像素高度相关,随机划分 patch 导致训练集和验证集互相「泄露」,模型学到的更多是空间位置特征而不是地物光谱特征。出了这景影像,位置特征失效。

解决:数据划分必须以空间区域为单位,而不是以 patch 为单位。最严格的做法是用完全独立的影像做验证;如果数据有限,至少按遥感影像的行方向切分,保证训练和验证之间没有空间相邻的 patch,中间留出至少一个 patch 宽度的缓冲带更稳。

5.3 阴影和暗色屋顶被全部划成水体

现象:模型在水体和阴影上反复横跳,明明是建筑物阴影,预测结果清一色是水体;或者反过来,水体边缘大量归属为阴影类。

原因:水体在可见光波段是低反射率,阴影区域同样低反射率,两者光谱曲线在 B2-B4 上高度相似;CNN 虽然能用纹理区分,但光谱相似的 patch 占比一大,模型偏向用光谱捷径决策。

解决:第一,尽量在 Landsat 影像的 B5 近红外波段上验证,水体近红外吸收很强、反射极低,阴影地表的近红外反射通常比纯水高,这个波段是区分水体和阴影的关键;第二,给水体类别加更高的 loss 权重是双刃剑,权重过高会引发误分,建议同时做难例挖掘;第三,如果阴影太多,考虑在预处理阶段加一个基于 B5/B2 比值的阴影掩膜,把高置信阴影区域在 loss 计算时置为 ignore。

5.4 直接对 16 位整数影像做训练,loss 曲线乱跳

现象:数据加载后没有归一化就直接进模型,训练 loss 前几个 epoch 降不下来,甚至出现 NaN;或者把 uint16 影像直接除以 255,所有波段被压到 0 到 60 的窄区间,模型特征提取失效。

原因:Landsat SR 产品是 uint16,数值范围 0-65535,除以 255 得到的不是 0-1 的反射率,而是 0-257 的错误区间。模型对输入量纲极其敏感,BatchNorm 虽然能在一定程度上兜底,但这种量级错误会让第一层卷积的梯度极不稳定。

解决:统一走 2.2 的分位数归一化流程,或者用官方公式reflectance = DN / 10000缩放到真实反射率范围,然后再做标准化。至少确保模型输入的数值范围在 0-1 左右,RGB 波段均值在 0.05-0.3 之间是健康的状态。

5.5 训练速度慢、显存溢出,不是模型的锅

现象:一开训练,12GB 显存的卡直接 OOM;勉强跑起来,一个 epoch 要 20 分钟,50 个 epoch 得十几个小时。

原因:patch 开到 256×256、batch 开到 8 或 16,再加上全 11 个波段输入,显存需求指数上涨。很多人以为 patch 越大效果越好,Landsat 30 米分辨率下 128×128 的感受野已经足够覆盖大型地物,256 带来的提升微乎其微,显存和耗时代价却很大。

解决:显存优化的优先级是:先把输入波段砍到 6 个 → patch 降到 96 或 128 → batch size 降到 4 或 8 → 开启 PyTorch 的torch.cuda.amp.autocast混合精度训练,显存直接减半。混合精度在 V100/Turing 架构之后的显卡上对精度影响很小,遥感地物分类这种任务可以放心用。

6. 精度验证与扩展:看完这些指标再决定要不要深挖

训练完成的模型不能只看 loss 曲线,要产出一份可追踪的验证报告。针对保留的独立验证影像,跑完推理后计算混淆矩阵、总体精度 OA、Kappa 系数和各类别的 IoU。其中 IoU 比 OA 更有参考价值,因为 OA 会被植被这种占比大的类别拉高,水体 IoU 即使只有 0.3,OA 也可能显示 90%。

from sklearn.metrics import confusion_matrix, cohen_kappa_score def evaluate_prediction(label_true, label_pred, class_names): """ label_true/label_pred: 展平后的 1D 数组,仅包含参与评估的类别。 """ cm = confusion_matrix(label_true, label_pred) oa = cm.diagonal().sum() / cm.sum() iou_per_class = [] for i in range(cm.shape[0]): intersect = cm[i, i] union = cm[i, :].sum() + cm[:, i].sum() - intersect iou_per_class.append(intersect / union if union > 0 else 0.0) kappa = cohen_kappa_score(label_true, label_pred) # 逐类别 IoU 打印,方便定位是哪一类在拖后腿 for name, iou in zip(class_names, iou_per_class): print(f"{name}: IoU={iou:.4f}") print(f"OA={oa:.4f}, Kappa={kappa:.4f}") return cm, oa, kappa, iou_per_class

如果验证结果中某一类 IoU 明显低于其他类,不要急着调模型结构,先回去看这个类别的训练样本数和样本纯净度。我遇到过裸土 IoU 一直上不去的 case,最后发现是标签里把收割后的农田错分成了裸土,标签噪声导致模型学到的是农田纹理,验证时却要求它输出裸土。先清洗标签,再谈调参,常常比改模型更有效。

模型跑通之后,源码包的扩展空间主要在三个方向。其一是输入从单时相改成多时相,把不同月份的两景影像按波段维拼接,输入通道变成 12,可以让模型学会「同一块地在不同季节的光谱变化规律」,对农作物分类精度提升显著。其二是把 U-Net 编码器替换成预训练的 EfficientNet 或 ResNet 骨干,利用 ImageNet 预训练权重做迁移学习,在训练样本少于 5000 个 patch 的场景下收益明显。其三是完全跳开逐像素分类,尝试基于 Transformer 的 SegFormer 模型,对地物边界细节的保持更好,代价是显存占用更高、推理更慢。我自己的经验是:先跑通当前这套 U-Net 流程,把数据、标签、验证体系都理顺,再考虑上述升级,否则一步跳到新模型只会让问题排查变得复杂。希望这套基于 cnn 卷积神经网络做 landsat 影像地物分类的流程能帮你在自己的数据上少踩几个坑。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 17:11:31

Superpowers:让Codex从代码生成器变成工程助手

如果你用过Codex这类AI编程助手&#xff0c;多少会有一种“它能写代码&#xff0c;但写不出我要的工程”的憋屈感。代码片段倒是一套一套的&#xff0c;可真放进项目里&#xff0c;命名规范不统一、缺少异常处理、不考虑历史包袱&#xff0c;改起来比手写还累。这就像发动机给你…

作者头像 李华
网站建设 2026/9/28 17:11:12

AI论文写作工具测评:导师严选9款软件与避坑指南

写论文也用上了AI&#xff0c;这话搁三年前我肯定不信。但2025年带了几轮毕业设计之后&#xff0c;我彻底改观了——不是AI写论文这回事变靠谱了&#xff0c;而是“会拿AI写论文的人”变靠谱了。手头这十几篇自考学生的论文&#xff0c;从选题到初稿再到改格式&#xff0c;全程…

作者头像 李华
网站建设 2026/9/28 17:10:39

STM32驱动气泵与电磁阀的MOS管控制方案详解

如果你做过基于STM32的小型气动设备&#xff0c;多半遇到过类似尴尬&#xff1a;GPIO引脚在数据手册上写得清清楚楚&#xff0c;输出电流顶天20mA上下&#xff0c;可气泵和电磁阀一上来就要几百毫安甚至好几安培。用继电器硬顶&#xff0c;体积大、噪音刺耳、触点烧蚀&#xff…

作者头像 李华
网站建设 2026/9/28 17:10:24

ax:面向AI Agent的轻量级Kubernetes调度基座

1. “ax”不是缩写&#xff0c;而是一个正在成型的开源调度基座项目最近在几个技术社区和内部分享会上&#xff0c;陆续看到有人提到“ax”&#xff0c;不是那个老牌的AX系列硬件驱动&#xff0c;也不是某个小众框架的代号&#xff0c;而是指代一个正在快速演进的、面向现代云原…

作者头像 李华
网站建设 2026/9/28 17:09:54

agent-native架构实战:从AI增强到自主代理系统

第一次听到 agent-native 这个词的时候&#xff0c;我的第一反应是&#xff1a;这不就是把 AI 代理用得好一点吗&#xff0c;至于发明一个新词&#xff1f;但当我真的把一个业务系统从“AI 增强”改成 agent-native 架构之后&#xff0c;我才发现这个前缀背后并不是营销话术&am…

作者头像 李华
网站建设 2026/9/28 17:09:54

自研轻量级调度内核:时间轮与最小堆混合实现延迟任务调度

1. 先从整体上把 ax 调度拆开&#xff1a;它到底解决什么问题前几天整理线上后台服务的任务体系时&#xff0c;发现团队里各种"定时任务"实现得七零八落&#xff1a;订单超时靠每一分钟扫一次表&#xff0c;优惠券过期提醒用 Thread.sleep 硬顶&#xff0c;报表生成直…

作者头像 李华