☰
基于MobileNet的遥感土地利用识别:轻量模型训练与避坑指南
2026/10/1 5:38:47 网站建设 项目流程

简介:这套MobileNet遥感土地利用识别代码包,专为基于深度学习的图像分类场景设计,面向使用Python/PyTorch的中初级学习者。资源不含数据集图片,用户按提示目录自行收集并放置图片后,即可训练并验证土地利用类型识别效果。压缩包共8个文件,涵盖3个Python脚本(训练集路径生成、模型训练、PyQt界面)、3张类别提示图、1份依赖清单和1份说明文档,整体仅209KB,轻量易用。目前已有57人学习下载。脚本逐行注释,配合说明文档能快速理解流程;训练脚本自动适配分类文件夹数量,增加类别无需改代码,训练时展示进度条、每轮准确率与损失值,并自动保存日志和模型权重。配套PyQt界面脚本可直观加载模型查看识别结果,适合作为遥感图像分类入门的可运行参考。

1. 拿到mobilenet土地利用识别压缩包后,先确认三件事再谈训练

很多人下载mobilenet模型基于深度学习AI算法对遥感土地利用类型识别这个压缩包,心思都在“赶紧跑出训练曲线”上,解压后却找不到数据集图片。别慌,这种以代码为核心的压缩包通常只带模型源码、逐行注释和说明文档,图片数据因为体积原因不在包内。也就是说,你拿到手的是一套“模型怎么写、怎么调”的完整解释,而“数据从哪里来、如何组织成训练样本、标签怎么对齐”,是留给你自己补的部分。

这篇笔记围绕这个场景解决三件事:MobileNet做遥感土地利用类型识别时到底在识别什么,没有自带数据怎么把流程跑通,以及哪些坑会让你的正确率看上去很高、真换区域就翻车。适合正在做深度学习毕设、遥感方向课设,或者想用轻量模型先验证高精度遥感识别流程的人。

2. 土地利用类型识别在识别什么:任务边界、MobileNet选型与标签体系

解压之后,我建议你先别碰train.py,先花15分钟把三件事弄清楚:模型输出的是整图类别还是像素类别,为什么要用MobileNet而不是更大模型,以及你最终要分几类。这三件事定了,后面所有参数都有了参照系。

2.1 先分清代码跑的是“整图分类”还是“逐像素分割”

遥感数字图像和普通照片最大的差别在于输出尺度。如果是“整图分类”,输入是一张小图(比如224x224),输出是一个类别标签,模型主干就是一个MobileNet加全连接分类头;如果是“逐像素分割”,输入仍是一张遥感影像,但输出是和输入同尺寸的标签图,MobileNet通常只作为编码器,后面再接解码器。

判断方法看三个地方。第一个是模型定义里的输出维度,out_features等于类别数的通常是分类头,输出形状带H和W的是分割头。第二个是损失函数,分类任务用CrossEntropyLoss,分割任务也常用CrossEntropy,但计算对象是每个像素。第三个是预测脚本,跑完一张测试图,输出是一列类别概率,还是一张单波段tif标签图。

这个判断为什么值得认真做?我见过不少毕设把分类和分割混着写,最后训练时形状对不上,才开始回头改数据。先花10分钟看明白路线,后面能省出一整天。这里要特别提醒:土地利用类型识别不是目标检测,输出的是整片地物的类别覆盖,不是“房子、车辆”那种边界框,所以也别拿遥感图像目标检测的思路去套。

2.2 MobileNet为什么能打:深度可分离卷积、小显存与小数据适应

MobileNet能成为这种压缩包的默认选择,理由很实际。它把标准卷积拆成深度卷积和1x1逐点卷积,参数量和计算量比同深度普通卷积少一个数量级。在遥感任务里,这意味着你可以用一套入门级显卡跑更大的batch size,也能在只有几千张训练小图的情况下更容易收敛,不会像大模型那样动辄过拟合。

我常用的对比口径是MobileNetV2和ResNet18:

模型参数量量级单张224输入显存占用遥感小数据集上的典型表现
MobileNetV22~4M约1GB级收敛快,流程容易调通
ResNet1810M左右高一档精度略高,但调参更敏感
SegFormer等分割模型依配置明显更大精度上限高,不适合拿来入门跑通

如果你的代码包用的是MobileNetV2,不需要为了“听起来更高级”换成SegFormer或者更大的Transformer。高精度遥感识别靠的是数据、后处理和评价方式,模型本身在这类任务上的边际收益远小于前面那一堆。先把轻量模型的baseline跑出来,后续再考虑用大模型替换编码器,才有对比参照系。

另外,压缩包里的mobilenet可能是V1、V2或V3。V1结构最简单,适合只有CPU的机器;V2在倒残差结构上做过优化,精度和速度均衡,也是torchvision里最常见的版本;V3在小模型上精度更高,但部分实现依赖h-swish这类激活函数,在旧代码里改动成本高。对这类压缩包,“能跑通、能看懂注释”比“用最新版本”重要得多。

2.3 标签体系决定训练成败:先把num_classes和类别顺序对齐

土地利用类型识别最常见的是六类体系:耕地、林地、草地、水体、建设用地、裸地。也有项目会拆出湿地、不透水面、盐碱地等,变成十几类,但每一类都要对应足量标注样本。我的建议是:第一版训练不要贪多,六类起步,先验证流程。

打开train.py,先找模型定义里的num_classes或classes。常见写法类似model.fc = nn.Linear(1280, num_classes)。这里容易犯的错是:PyTorch并不会在训练前校验标签范围,num_classes写成12但数据目录里只有6个文件夹,不会直接报错,只会让loss长期掉不下去、验证指标乱跳。另外一个更隐蔽的问题来自ImageFolder:它按文件夹名字母顺序给类别编号,“耕地”排到第几个取决于文件夹首字母,而不是你心里的顺序。换权重、换数据、做混淆矩阵的时候,一定要以classes列表的顺序为准。

我一般在项目开始就建一张标签记录表:类别名、文件夹名、classes数组下标、标签值四个字段一一对齐。这段记录是后面所有结果不出错的前提,也是你在做遥感图像标注、检查标注质量时最重要的文档。

提示:训练前打印print(train_ds.classes)核对一遍,比看代码里的注释更可靠。注释可能是作者原环境的顺序,不代表你的数据顺序。

3. 没有自带数据集也能跑通:从造训练样本到第一次训练的参数清单

既然压缩包不含数据集图片,跑通流程的第一步其实是“把外界数据转成包能吃的格式”。不管包里写的是ImageFolder还是csv,你都得先准备一批带标签的小图。下面按最常见的情况拆解:RGB三波段tif加整数标注tif,裁成224小图训练。

3.1 先搭深度学习环境:依赖版本与conda命令

拿到压缩包后,先打开说明文档看依赖声明。很多代码的坑不在模型,而在torchvision版本差异。新版本torchvision把pretrained参数改成了weights参数,旧代码在多版本下会直接报TypeError。我一般用conda建独立环境,避免污染系统Python:

bash conda create -n rs_mobilenet python=3.8 -y conda activate rs_mobilenet pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install numpy pillow opencv-python scikit-learn tqdm rasterio

上面这组版本是我自己常用的组合,如果你的说明文档里写了版本号,以文档为准。没有GPU的机器,把cu117对应的命令换成安装CPU版本即可,后面的代码流程完全一样。

这里锁定torch 1.13.1的原因是很多旧代码默认调用pretrained=True,这个版本还能兼容。如果你的环境装的是torch 2.x,模型加载那行就要改成weights=MobileNet_V2_Weights.DEFAULT,其余逻辑不用动。环境这块最耗时间的往往是CUDA和torch版本不匹配,装好后先用python -c "import torch; print(torch.version, torch.cuda.is_available())"确认一遍再继续,避免后面跑出非设备错误。

3.2 自己造训练数据:一张带标注tif裁成训练集的最小脚本

这里先说明白:不带标注的遥感影像不能直接变成监督样本。你需要有一张和影像同尺寸的label栅格,每个像元是类别编码(0耕地、1林地、2草地……),才能自动裁出训练集。下面脚本按256窗口、128步长滑动,把窗口内主类别超过60%的样本写进对应类别文件夹:

python import os import numpy as np import rasterio from PIL import Image

classes = ["耕地", "林地", "草地", "水体", "建设用地", "裸地"] nodata = -9999 # label里的无效值 img_path = "area_img.tif" # RGB三波段 lab_path = "area_label.tif" # 单波段整数标注 out_root = "dataset" win, stride = 256, 128 # 窗口与步长,重叠一半扩大样本量

with rasterio.open(img_path) as src_img, rasterio.open(lab_path) as src_lab: img = src_img.read() # (波段, 高, 宽) lab = src_lab.read(1) img = np.moveaxis(img, 0, -1) # 转成 (高, 宽, 波段)

for r in range(0, img.shape[0] - win + 1, stride): for c in range(0, img.shape[1] - win + 1, stride): sub_lab = lab[r:r+win, c:c+win] if (sub_lab == nodata).any(): continue vals, counts = np.unique(sub_lab, return_counts=True) if counts.max() / counts.sum() < 0.6: continue # 窗口类别太杂,先跳过,后期可以专门做边界难例 cls = int(vals[counts.argmax()]) out_dir = os.path.join(out_root, "train", classes[cls]) os.makedirs(out_dir, exist_ok=True) sub_img = img[r:r+win, c:c+win].astype(np.uint8) Image.fromarray(sub_img).save(os.path.join(out_dir, f"{r}_{c}.jpg"))

这个脚本把“遥感影像下载加人工勾绘”变成了最原始的监督样本生成器。几个参数说明:

  • win要和后面训练代码里的Resize保持一致。常见做法是裁256,训练时再Resize到224,这样带一点尺度扰动,相当于默认增强。
  • stride越小样本越多,但相邻窗口重叠严重,训练集内部极其相似。后面第4章的“假精度”很大程度上就是这里埋下的,建议stride不要小于win/2,宁可样本少一点,也不要让验证集和训练集内容高度重复。
  • 如果你的影像不是RGB三波段而是多光谱(比如Sentinel-2),脚本里img是4个以上波段,astype后PIL无法直接存多通道JPG。常见做法是取真彩色组合波段裁图,或者把所有波段存成npy,留到最后做多光谱输入实验。
  • 如果label是矢量(shp)而不是栅格,需要先在GIS软件里栅格化。这一步别偷懒,矢量直接转监督样本是后期最容易标签错位的环节。

3.3 训练主流程与四个必调参数:batch size、学习率、epoch、num_classes

数据目录结构满足ImageFolder要求后,训练入口其实很短。以MobileNetV2微调为例:

python import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms from torchvision.models import mobilenet_v2, MobileNet_V2_Weights

transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) train_ds = datasets.ImageFolder("dataset/train", transform=transform) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4)

model = mobilenet_v2(weights=MobileNet_V2_Weights.DEFAULT) model.classifier[1] = nn.Linear(model.last_channel, len(train_ds.classes))

criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

for epoch in range(10): model.train() for x, y in train_loader: pred = model(x) loss = criterion(pred, y) optimizer.zero_grad() loss.backward() optimizer.step() print(f"epoch {epoch + 1} loss {loss.item():.4f}")

几个地方按项目实际情况改:

  • batch_size:独立显卡32很稳,显存小降到16;CPU跑建议8,不然一个batch等太长时间。
  • lr:Adam从1e-3开始,loss震荡就降到1e-4重新跑;如果用SGD,建议从1e-2起步并配合momentum=0.9。
  • epoch:小数据集先跑10轮,观察loss是不是稳定向下;10轮不降,不要盲目加到50轮,先检查数据和标签。
  • num_classes:不建议写死。上面代码用len(train_ds.classes)自动读取,文件夹增删类后不用改代码,也能避免类别数和标签顺序不一致的问题。

另外,验证集要单独建dataset/val目录,每个epoch结束跑一次验证,不要只在训练集上看loss。这个包如果没给验证脚本,你自己也要补上,否则无法判断是否过拟合。

3.4 只有CPU时就靠这三个设置:缩小输入、放宽宽度因子、跑通流程

没有GPU也能跑MobileNet,这块我在不少深度学习环境配置只有CPU的机器上做过验证。第一是输入尺寸,Resize从224降到160,或者裁图时直接用128窗口,速度接近翻倍,精度下降有限;第二是看模型定义里有没有width_mult或width_factor这类宽度因子参数,从1.0改成0.75相当于把每层通道数乘0.75,速度提升明显,精度掉1到2个点,流程验证完全够用;第三是先抽500张小图、跑2个epoch,确认数据读取、标签顺序、损失计算全链路通畅,再换全量数据。

还有个后悔药思路:如果你的目的只是先确认“自建数据集能不能被正确读取和预测”,可以先不训练,直接用torchvision预训练权重对测试图推理一次。这时输出的是ImageNet的1000类名称,不是土地利用类型,但至少能证明预处理、前向推理、概率输出这一段管线没写错。再回头改分类头去微调,会从容很多。

4. 遥感识别避坑指南:四个最容易翻车的现场与排查顺序

模型训练流程跑通之后,真正的血泪经验才开始。遥感土地利用识别这个方向,模型代码本身能提供的帮助其实有限,绝大多数翻车发生在数据组织、类别平衡和评价方式上。下面四条是我在类似项目里反复踩过的坑,按排查顺序写,每一条都可以直接对号入座。

4.1 换区域后精度暴跌:训练验证集“同源”造成的假高分

现象:在同一个区域的影像上随机切训练集和验证集,验证准确率能到90%以上,一旦换到相邻区域重新预测,直接掉到70%甚至更低。很多同学第一反应是模型过拟合,开始调dropout、加正则,但实际原因往往是“空间自相关”。

遥感影像和普通图片数据集最大的不同是:同一片耕地、同一条河道在相邻窗口里高度相似。随机划分训练验证集时,验证集里有很大比例的小图和训练集在空间上重叠。模型根本不需要学会“识别农田”,只需要记住“这一带长这样”就能拿高分。解决方向是划分数据时按空间块,而不是按小图随机分。常见做法是裁图时把影像按行号列号分成若干区块,比如把整幅tif切成4块,3块训练、1块验证,互不相邻;如果你的数据是多个瓦片,按瓦片划分即可。

所以在裁图脚本里,我通常让文件名带上r和c坐标,并预留一个split参数:给定一个区块边界,比如c大于某个值的样本全部进验证集。这样训练验证集在空间上天然分开,得出的指标才接近真实泛化水平。

4.2 小类别永远学不会:类别不均衡与整体准确率陷阱

现象:水体、裸地这类小类别在整幅图里占比很少,裁出来的样本数量只有大类别(耕地、林地)的十分之一。结果训练loss很漂亮,整体准确率也能到80%,但看混淆矩阵会发现水体召回率几乎为0,预测结果里所有像素都被判成耕地。

原因在于普通交叉熵损失被大类别主导,少数类梯度太小;同时“整体准确率”在样本极不均衡时本身就没有参考价值。解决分两步。第一步是给损失函数加权,这也是最省事的方式:

python

weight 顺序必须与 classes 顺序一致,样本越少权重越大

weights = torch.tensor([1.0, 1.0, 1.5, 3.0, 1.0, 5.0]) criterion = nn.CrossEntropyLoss(weight=weights)

第二步是对少数类做加倍采样,把水体、裸地的小图在DataLoader里多放几份,也可以做色彩增强让样本多样化。注意权重不要超过10倍,否则模型会振荡,表现成整体指标变差。判断标准始终是各类别的召回率清单,而不是总体准确率一个数。

4.3 预测图全是椒盐噪声:逐窗口推理缺少上下文

现象:把大影像裁成小块逐张预测、再把结果拼回去,生成的专题图上布满孤立的碎斑块——建设用地中间冒出一个“水体”点,大块农田内部散落几十个“草地”像素。这是逐窗口分类或分割最常见的结果,也是为什么高精度遥感在实际交付时还需要后处理。

原因是每个窗口独立推理,模型只能看到窗口内信息,没有邻域上下文,窗口越小噪声越明显。解决有两个层次:一是在推理时做重叠滑窗,让每个像素被多个窗口覆盖,最终用多数投票决定类别;二是套一个简单的平滑后处理,比如用多数滤波去掉孤立小斑块。这类操作看起来像玄学,但在土地利用制图里能稳定提升2到5个点,而且不会改变模型本身。具体窗口大小按地物尺度来:建设用地这样的块状地物,用9x9的窗口平滑很安全;公路等线状地物就不要用大窗口,否则会被磨断。

4.4 换了影像分辨率就失灵:GSD与输入尺寸必须先统一

现象:用0.8m无人机影像训练出来的模型,拿到10m分辨率的Sentinel-2影像上预测,结果完全错乱。这不是模型过拟合,而是不同传感器、不同地面采样距离(GSD)下,同一地物的纹理特征完全变了。0.8m影像里的“房子”有清晰的屋脊和阴影纹理,10m影像里“房子”只是一个亮色像素簇。

解决的核心不是调模型,而是先把训练图和推理图统一到同一GSD。常见做法是用重采样工具把影像统一成2m或10m的像元尺寸;如果包的输入尺寸固定224,那么只要GSD一致,Resize不会进一步改变物理尺度关系。另一个要注意的是归一化统计量:换了一组影像后,需要重新统计mean和std,不能继续沿用训练集的值。很多“模型在另一批数据上崩了”的例子,其实就是因为影像数值范围、波段顺序都不一样。

5. 把预测结果交成一张专题图:滑窗多数投票与Kappa验收

模型跑通、指标合格后,最后一步是把一堆小图预测结果拼成一张能交付的专题图。这里给出一个我常用的出图技巧和验收习惯,能够在交付前提前发现大多数隐藏问题。

5.1 多数投票平滑:一个能救回几个点精度的后处理

预测阶段我一般会保存每个窗口的类别概率,而不是只保存最终类别。把所有窗口的类别概率图重叠相加后,用均匀滤波做邻域平均,再取最大概率类别,就是多数投票的软实现:

python def smooth_probs(probs, kernel=9): from scipy.ndimage import uniform_filter smoothed = np.zeros_like(probs) for c in range(probs.shape[0]): smoothed[c] = uniform_filter(probs[c], size=kernel) return smoothed.argmax(axis=0)

这个函数配合重叠推理使用,能把逐小图的“马赛克”结果变成连续的土地利用专题图。如果代码里不方便保存概率,至少先把重叠预测的多数投票结果试出来。

5.2 用混淆矩阵与Kappa验收,别让一张整体准确率骗了你

出图前我会用sklearn跑一次分类报告,过程很简单,但价值很高:

python from sklearn.metrics import classification_report, cohen_kappa_score print(classification_report(y_true, y_pred)) kappa = cohen_kappa_score(y_true, y_pred)

这里y_true和y_pred是验证集所有样本的类别编码。Kappa低于0.6说明模型一致性不合格,即使整体准确率到80%也不能交付。我的经验是把各类别召回率单独打印出来逐一核对,尤其是小类别。

我做遥感识别项目吃亏最多的一次,就是只报整体准确率,结果专题图上水体类别整片丢失,被对方一眼看出来。从那以后,只要换数据、换模型、调参数,我都会回到第4章那四个问题里重新过一遍:空间划分、类别均衡、平滑后处理、GSD统一。这套流程走下来,模型本身反而不是最容易出问题的地方。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询