尧图精选

基于MobileNet的遥感土地利用分类:轻量卷积网络训练与推理全流程

🕒 发布时间:2026/10/1 5:38:55 📁 来源:尧图网络
简介基于PyTorch与MobileNet的遥感土地利用类型识别工程面向python图像识别学习者及遥感应用开发者聚焦城市、森林、农业等用地场景的分类需求。压缩包内含8个文件体量约209KB以3个Python脚本为核心01生成txt.py负责遍历数据集目录并生成带标签的txt自动划分训练集与验证集02CNN训练数据集.py能够自适应分类文件夹数量无需修改代码即可训练实时输出进度条、准确率与损失值并保存训练日志03pyqt界面.py则提供可视化交互入口便于测试与应用。辅助文件包括3张提示图、requirements.txt和Word说明文档提示图帮助用户了解图片应放置的位置说明文档提供环境配置与运行参考代码本身带有逐行注释方便阅读和二次开发。需要说明的是资源不含数据集图片用户需自行收集对应类别的图片放入文件夹后再训练。目前已有57人学习下载适合希望快速跑通MobileNet图像分类流程的中级开发者也可作为遥感图像识别入门或课程设计的参考工程。1. 先说结论轻量模型才是遥感分类的稳健起点土地利用变化监测是自然资源、农业农村、生态评估里每年都要做的例行工作。遥感影像拿到手之后第一件事往往是把地物分成耕地、林地、草地、水域、建设用地、裸地这些大类再做面积统计和变化检测。传统随机森林、支持向量机在特征工程上能顶一阵但一到高分影像、复杂纹理精度就上不去于是深度学习成了绕不开的方向。mobilenet模型-基于深度学习AI算法对遥感土地利用类型识别-不含数据集图片-含逐行注释和说明文档.zip这个标题指向的就是这样一套完整的落地工程用 MobileNet 系列轻量卷积网络做遥感土地利用分类压缩包不含数据集图片自带逐行注释的代码和说明文档适合直接照着改、照着训。这类方案最大的价值在于选型思路。遥感分类的第一反应通常是上 ResNet、甚至直接上分割网络但实际做下来会发现当训练样本只有几百到几千张、类别只有六到十几类时重网络不仅训练慢还极易过拟合而 MobileNet 这类轻量模型反而更稳。它不是“阉割版”而是用深度可分离卷积换了一个更合适的精度—速度—数据需求平衡点。下面按选型原理、数据准备、训练实现、调参评估、避坑排查、进阶验证的顺序把这套方案完整拆开讲。2. 为什么遥感土地利用识别适合用 MobileNet选型逻辑与输入预处理2.1 MobileNet 的核心机制深度可分离卷积节省了什么MobileNet 系列的核心是深度可分离卷积depthwise separable convolution它把标准卷积拆成两步第一步是 depthwise 卷积每个输入通道用独立的卷积核做空间滤波不跨通道第二步是 pointwise 卷积用 1×1 卷积核把通道信息融合起来。同样一个 3×3 卷积操作参数量和计算量大约降到标准卷积的 1/9 左右。这个压缩不是没有代价的。深度可分离卷积的感受野组合方式比标准卷积弱一些对细碎纹理的建模能力有上限。但遥感土地利用分类恰恰是一个“大局优先”的任务——耕地、林地、水域这类地物在影像上通常是大面积均质区域类别数少、类间差异大MobileNet 的表达能力正好够用。相比之下如果用 ResNet-50 或者更深的分割网络在小样本条件下很容易记住训练集的纹理细节验证集虚高换一块区域的影像立刻崩掉。这不是玄学是模型容量和样本量不匹配的必然结果。2.2 MobileNet v1 / v2 / v3 怎么选三个版本的取舍标题只写了 mobilenet没有标注具体版本。三个版本在遥感分类上的表现和适用场景差异明显实测下来我的选择逻辑如下。MobileNet v1 结构最简单就是深度可分离卷积堆叠最后接全局平均池化和全连接层。它在 ImageNet 上有预训练权重迁移到遥感影像上能跑但精度属于“能交差”级别适合快速验证流程。MobileNet v2 引入了线性瓶颈linear bottleneck和反残差结构inverted residual先升维再降维信息流动更充分在保持小参数量的同时精度明显好于 v1是目前小样本遥感分类里用得最多的版本。MobileNet v3 加入了神经架构搜索和 SE 注意力机制理论精度更高但它在 ImageNet 上的预训练分布和遥感影像差异较大迁移效果不一定比 v2 好而且对数据增强和微调策略更敏感。版本参数规模遥感分类实测表现适用场景v1约 4.2M收敛快精度偏低流程验证、资源极紧张v2约 3.5M精度/速度平衡最好小样本土地利用分类首选v3约 5.4M依赖预训练权重分布漂移敏感数据量大、类别细碎时再考虑我一般默认选 MobileNet v2把最后一层全连接改成目标类别数其余结构不动。别自己乱改前面的卷积层预训练权重会失效等于从头训练。2.3 遥感影像输入预处理波段、归一化、裁剪遥感影像和自然图像最大的区别在波段和数值范围。自然图像的 RGB 三个通道从 JPEG 读出来就是 0~255 的整数而遥感影像可能是 16 位深度的单波段或多波段数据数值范围可能到 0~65535甚至还有负值。不做处理直接丢进 MobileNet 肯定翻车。预处理的第一步是统一波段。模型输入是三通道手上如果是多光谱影像比如蓝、绿、红、近红外四波段必须自己选三个通道合成 RGB 或伪彩色。常见做法是直接用红、绿、蓝三波段合成真彩色如果只有近红外和红边可以取近红外、红、绿做标准假彩色合成植被会呈现红色地物区分度反而更高。这步没有标准答案取决于你的影像来源和地物类型我在做林地识别时用假彩色比真彩色高了两三个点准确率。第二步是归一化。不要简单除以 255因为 16 位影像的值域不是 0~255。正确做法是统计全影像的均值和方法差做标准化或者按百分比截断到 2%~98% 分位数再映射到 0~1。后者对云影、高反差区域更稳。第三步是裁剪。MobileNet 的标准输入是 224×224但遥感地物往往需要更大的上下文才能判断我一般用 256×256 或 320×320 裁剪同时配合滑动窗口推理后面会细说。提示如果影像带了地理坐标和投影信息裁剪时顺手记录每个样本对应到原图的行列号。后期做逐像素分类结果矢量化或面积统计时没有这个映射关系会非常被动。3. 从零跑通训练流程数据组织、Dataset 实现与训练脚本3.1 数据组织方式目录结构就是你的标签体系压缩包不含数据集图片所以数据要自己准备。这里最省事也最不容易出错的结构是“按类别分文件夹”PyTorch 自带的ImageFolder能直接读不需要手写标签文件。data/ train/ cropland/ # 耕地 001.jpg 002.jpg forest/ # 林地 water/ # 水域 builtup/ # 建设用地 bareland/ # 裸地 val/ cropland/ forest/ ... test/ ...类别体系一定要事先定死。国内的常规做法是参考《土地利用现状分类》GB/T 21010 的二级类按项目需求合并成大类。比如把水田、水浇地、旱地合并成“耕地”把有林地、灌木林地、其他林地合并成“林地”。合并的逻辑要写进说明文档里不然后期核查的人不知道你为什么把果园归到了林地而不是草地这类隐患在项目验收时非常容易炸雷。3.2 自定义 Dataset 类的完整实现如果影像格式比较杂或者要做复杂的通道选择和归一化建议还是自己写 Dataset。下面这段代码是标准实现文件里有逐行注释的习惯也推荐保持。# dataset.py import os import torch from torch.utils.data import Dataset from torchvision import transforms from PIL import Image class LandUseDataset(Dataset): 土地利用分类数据集目录结构为 root/类别名/图片文件 def __init__(self, root, transformNone): self.root root self.transform transform self.classes sorted(os.listdir(root)) # 类别名列表按字典序排序 self.class_to_idx {cls: i for i, cls in enumerate(self.classes)} self.samples [] for cls in self.classes: cls_dir os.path.join(root, cls) for fname in os.listdir(cls_dir): if fname.lower().endswith((.jpg, .jpeg, .png, .tif)): path os.path.join(cls_dir, fname) self.samples.append((path, self.class_to_idx[cls])) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] # 遥感tif可能是多波段或16位PIL无法直接处理需要按3.1的前置步骤先转成8位RGB img Image.open(path).convert(RGB) if self.transform: img self.transform(img) return img, label这里的核心是__getitem__里先convert(RGB)把可能存在的灰度图、带透明通道的 PNG 统一成三通道。如果源文件是 16 位 TIFFPIL 打开后convert(RGB)会直接按 16 位数据做截断效果往往很差所以预处理要在入库前完成不要在 Dataset 里临时处理。数据增强下面这套配置是我在遥感分类里常用的一组 baseline先跑通再逐步收紧# augment.py from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomVerticalFlip(p0.5), # 遥感影像是顶视图垂直翻转合理 transforms.RandomRotation(degrees30), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])注意RandomResizedCrop的scale我设了 0.7~1.0没有用默认的 0.08~1.0。遥感地物裁剪过狠会让样本失去空间上下文一小块土壤纹理很难判断是耕地还是裸地。Normalize的均值和标准差是 ImageNet 的统计值遥感影像分布和它差异大但实测中迁移预训练模型时保留这个归一化反而更稳不要自己重算重算的统计值会和预训练权重的分布打架。3.3 训练脚本加载预训练 MobileNet v2 与训练循环训练脚本是整套工程的中枢直接给出完整可运行的版本。# train.py import torch import torch.nn as nn from torch.optim.lr_scheduler import CosineAnnealingLR from torch.utils.data import DataLoader from torchvision import models from dataset import LandUseDataset from augment import train_transform, val_transform num_classes 6 batch_size 32 epochs 40 device torch.device(cuda if torch.cuda.is_available() else cpu) # 加载ImageNet预训练权重替换分类头 model models.mobilenet_v2(weightsmodels.MobileNet_V2_Weights.IMAGENET1K_V1) model.classifier[1] nn.Linear(model.last_channel, num_classes) model model.to(device) train_ds LandUseDataset(data/train, transformtrain_transform) val_ds LandUseDataset(data/val, transformval_transform) train_loader DataLoader(train_ds, batch_sizebatch_size, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_sizebatch_size, shuffleFalse, num_workers4) # 类别权重按样本数倒数归一化缓解样本不平衡 from collections import Counter counts Counter(label for _, label in train_ds.samples) total sum(counts.values()) class_weights torch.tensor([total / counts[i] for i in range(num_classes)], dtypetorch.float32).to(device) criterion nn.CrossEntropyLoss(weightclass_weights) optimizer torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_maxepochs) best_acc 0.0 for epoch in range(epochs): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * images.size(0) model.eval() correct, total 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) val_acc correct / total scheduler.step() print(fEpoch {epoch1}/{epochs} loss{running_loss/len(train_ds):.4f} val_acc{val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), fbest_mobilenet_v2_{num_classes}cls.pth) # 每个epoch保存一次用于后期做模型对比分析 torch.save(model.state_dict(), fcheckpoint_epoch{epoch1}.pth)两个参数值得专门说明。AdamW的初始学习率 1e-3 对 MobileNet 微调来说是安全的起点权重衰减weight_decay1e-4是防止小样本过拟合的关键。CosineAnnealingLR配合 40 轮训练学习率从 1e-3 平滑降到接近 0相比固定步长下降在遥感分类这种类别数少、但类内差异大的任务上收敛更稳定。追求极限精度时可以换 SGD momentum0.9初始学习率 0.01但必须配合 warmup冷启动直接 0.01 大概率前期 loss 震荡。4. 参数调优与评估方法跑出结果只是开始4.1 一份可复用的参数基线表训练跑通之后接下来就是调参。下面这张表是我在多个土地利用分类项目里沉淀下来的 baseline新手可以直接抄熟手可以在此基础上做正交实验。参数推荐值调整方向输入尺寸224×224 起步数据量够可试 320×320小地物多就加大但显存占用和训练时间同步上升批大小32batch 太小 BN 统计不稳定太大在类别不平衡时更容易忽略小类优化器AdamWlr1e-3weight_decay1e-4验证集震荡时降 lr 到 3e-4不要直接重训学习率调度CosineAnnealingT_maxepochs前期收敛偏慢就加 5 轮 warmup冻结策略前 5 层冻结只训分类头和后半段数据不足 300 张时强烈建议冻结防止预训练特征被破坏类别不平衡CrossEntropy 的 weight 参数按样本倒数效果不够再换 Focal Lossalpha0.25、gamma2.0嵌入数据集图片的模型训练时每轮只保存最优模型就够了。但这个项目里的路径是“每轮都存”因为小样本训练时验证集波动很大最优模型可能出在第 23 轮而不是最后一轮每轮都存等于买了后悔药后期对比分析时能回头看模型在哪一轮开始过拟合。4.2 评估指标不要只看准确率土地利用分类的准确率有一个很大的陷阱——类别不平衡。假设某个区域 70% 是耕地模型全预测成耕地准确率也有 70%看起来不错实际上一无是处。所以我一般看三个指标总体精度OA、Kappa 系数、每类 F1-score。OA 就是所有预测正确的样本除以总样本数作为参考可以但不能当唯一标准。Kappa 系数剔除了随机一致性的影响数值在 0~1超过 0.8 才算“高度一致”。F1-score 是每类精确率和召回率的调和平均重点关注样本少的类别。输出这些指标只需要在验证时多写几行代码# evaluate.py 计算每类precision/recall/F1和混淆矩阵 from sklearn.metrics import classification_report, confusion_matrix import numpy as np def evaluate(model, loader, class_names, device): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for images, labels in loader: images images.to(device) outputs model(images) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, target_namesclass_names, digits3)) cm confusion_matrix(all_labels, all_preds) return cm如果每一类 F1 都在 85 以上这个模型基本可以进入下一步的整景影像推理如果某类只有 60 多不要急着调模型结构先去查原始训练样本里那一类的图片是不是混入了错误标注。标注噪声在农忙季节的影像里非常多耕地和裸地的边界在只有两三成植被覆盖时人眼都未必分得清。4.3 迁移学习里最容易忽视的一个参数冻结策略遥感影像和 ImageNet 的分布差异很大一个常见的误区是“预训练权重反正不匹配遥感域干脆全量从头训”。数据充足时这样没问题但这个小样本场景下从头训练 MobileNet v2 在 40 轮内很难收敛到可用精度而加载预训练权重后只微调后几层反而稳定得多。具体操作是遍历模型参数前若干层设置requires_grad False# 冻结模型前5层其余层参与训练 freeze_layers 5 for idx, (name, param) in enumerate(model.features.named_parameters()): if idx freeze_layers: param.requires_grad False这个策略的核心逻辑是预训练模型的前几层学到的是边缘、纹理、颜色块这些基础视觉特征这对遥感影像同样有效而后面几层学到的是 ImageNet 特有的物体语义和土地利用类别完全对不上需要重新训练。冻结前几层既减少了显存占用又让梯度集中到需要学习的深层参数上收敛快得多。5. 遥感土地利用识别避坑指南5 条血泪经验5.1 通道不匹配多光谱影像直接训崩现象训练 loss 降不下去验证准确率在随机水平附近震荡换了模型也一样。 原因遥感影像源是四通道B、G、R、NIR或单波段灰度图直接读入后要么多通道被压缩、要么值域异常模型看到的数据分布完全乱了。 解决入库前统一转成 8 位 RGB 三通道。近红外波段对植被识别帮助大可以合成假彩色NIR、R、G 分别映射到 R、G、B 通道但合成规则要在训练和推理时保持一致。一个批次的数据生成里训练集用假彩色、测试集用真彩色是这类问题最容易翻车的地方。5.2 类别不平衡把小类吞掉全预测成大类现象混淆矩阵里水域和裸地的召回率低于 50%几乎所有样本都被分成了耕地或建设用地。 原因数据集里耕地样本占了一半以上模型发现全预测成耕地就能把 loss 压得很低没必要费力学小类特征。 解决最少三步一起做。第一步是类别权重第 3 章代码里已经实现了按样本数倒数给 loss 加权。第二步是做 TTA测试时增强对小类样本做水平翻转、垂直翻转、旋转 90 度后再预测取平均概率这个技巧能稳定提小类 2~3 个百分点。第三步是如果小类样本本身不到 50 张直接放弃训练改为从大影像里手动裁剪补充任何 loss 技巧都救不了过少的样本。5.3 模型记住了影像纹理而非地物语义现象验证集指标很高但换到另一块区域的影像上准确率断崖式下跌尤其是跨季节影像。 原因训练样本里耕地全是“带垄沟纹理的褐色区域”模型学会的是纹理匹配而不是光谱语义。遥感影像上同一类地物在不同季节、不同天气下的纹理差异极大这是最容易被忽略的泛化陷阱。 解决数据增强里加入高斯模糊、随机擦除、色彩抖动破坏掉过于稳定的纹理信号样本来源尽量覆盖不同季节、不同成像角度、不同云影遮挡情况。我个人的习惯是宁可让训练集整体“噪声大一点”也不追求完美干净的样本泛化能力比训练集上的漂亮指标重要得多。5.4 整景影像直接缩放输入小地物全部消失现象单张裁剪样本预测效果不错但把整景遥感影像缩放到 224×224 后直接喂给模型输出结果完全不可用小地块像被橡皮擦过一样。 原因整景影像可能是 10000×10000 像素缩放到 224 时一个农田地块只剩几个像素MobileNet 的卷积核根本看不到足够上下文。 解决整景影像必须用滑动窗口推理窗口 256×256步长小于窗口比如 192相邻窗口重叠预测后取投票或平均概率。这个方案第 6 章会给出完整代码。千万别做整图缩放这条是遥感分类落地失败的最常见原因。5.5 类别体系定义含糊标注前后矛盾现象验证集上误分样本集中在耕地和草地之间、林地和园地之间肉眼看起来两类的影像确实很像。 原因标注规范没定死不同标注员对“低覆盖草地算草地还是裸地”“疏林地算林地还是草地”理解不一致导致大量标签噪声模型学到的边界是混乱的。 解决开工前先写一份一页纸的标注规则每种地物给 3~5 个典型样例和 2~3 个边界样例规定好在分辨率达不到时采用“取主要地物覆盖面积超过 50%”的判据。规则文本存放在说明文档里和训练代码放在一起。这个看似和算法无关的动作对最终精度的帮助往往比换大模型还明显。6. 从单图分类到整景制图滑窗推理与结果矢量化模型在裁剪样本上验证通过后最终要面对的是整景遥感影像。滑窗推理的代码并不复杂但几个参数直接影响出图质量。# predict_full_image.py import numpy as np import torch import torch.nn.functional as F from torchvision import transforms from PIL import Image class FullImagePredictor: def __init__(self, model_path, num_classes6, window_size256, stride192, devicecpu): self.device device self.window_size window_size self.stride stride self.num_classes num_classes self.model torch.load(model_path, map_locationdevice) if isinstance(self.model, dict): from torchvision import models self.model models.mobilenet_v2(num_classesnum_classes) self.model.load_state_dict(torch.load(model_path, map_locationdevice)) self.model.to(device).eval() self.transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def predict(self, image): h, w image.shape[:2] # 每类一张概率图最终图大小与原图一致 prob_map np.zeros((self.num_classes, h, w), dtypenp.float32) count_map np.zeros((h, w), dtypenp.uint8) for y in range(0, h - self.window_size 1, self.stride): for x in range(0, w - self.window_size 1, self.stride): crop image[y:yself.window_size, x:xself.window_size] crop_pil Image.fromarray(crop) tensor self.transform(crop_pil).unsqueeze(0).to(self.device) with torch.no_grad(): logits self.model(tensor) prob F.softmax(logits, dim1).squeeze(0).cpu().numpy() prob_map[:, y:yself.window_size, x:xself.window_size] prob count_map[y:yself.window_size, x:xself.window_size] 1 # 除以计数得到平均概率再取argmax count_map np.maximum(count_map, 1) prob_map / count_map[None, :, :] return np.argmax(prob_map, axis0)stride192意味着窗口有 64 像素的重叠重叠区域被多次预测后取平均概率能显著减少地块边缘的椒盐噪声。如果重叠太密推理时间几乎翻倍但不重叠的sstride256会在地块边界出现大量的棋盘格伪影两种做法之间我更倾向于牺牲一些推理速度换边缘平滑。最终生成的分类栅格可以直接转成 Shp 矢量用 GDAL 的Polygonize做栅格矢量化后按类别统计面积。注意边缘碎图斑要按最小成图面积阈值过滤一般 0.01 平方公里以下的碎块合并到邻近大类否则制图结果满屏小碎块验收时大概率被打回。这个阈值写在说明文档里作为制图后处理的标准参数。配套的工程习惯是模型训练、滑窗推理、矢量化、面积统计的每个步骤对应的命令行全部写成一个run_all.sh保证换台机器、换个人也能完整复现。本人早期在这上面栽过跟头——模型调好之后同事复现时少跑了一步归一化出来的分类图错到没法看。后来所有预处理步骤固化成脚本输入原图直接输出矢量结果中间过程全部存档这个问题才彻底终结。希望你不用经历这个就能少踩一个坑希望这篇笔记帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →