简介:面向遥感图像分类与深度学习入门人群的WHU-RS19数据集,提供19种土地利用类型的已标注卫星图像,涵盖机场、海滩、桥梁、商业区、沙漠、农田等常用类别,可直接用于图像分类模型训练与精度评估,也可作为基准数据集检验不同网络的迁移效果。包内按训练集、测试集分别存放同类图片,并附show脚本一键可视化样本,便于检查标签质量与数据分布;json文件列明全部19个类别名称,方便自动化加载与评估,尤其适合课程设计、毕业设计及算法对比实验。资源共1016个文件,主体为1006张jpg图像,其余包括运行生成的db缓存、Python脚本和类别映射json,压缩包总体约100.06MB。已有199人学习下载,结合作者配套的CNN分类、图像分割及YOLOv5改进系列项目,可借鉴从数据准备、网络结构设计到训练调优的完整流程,为后续扩展研究提供可复用的实验基石。
1. 为什么拿 WHU-RS 当遥感分类入门数据集:先把这 1,000 张图盘明白
做过遥感图像分类的人都有个体会:公开数据集要么太大(动辄几十万张,没等下载完硬盘先报警),要么标签质量参差,跑完模型对着混淆矩阵一头雾水。WHU-RS19 这个数据集恰恰卡在一个舒服的中间位置——约 1,000 张已标注遥感影像,覆盖 19 种土地利用类型,每类 50 张左右。这个规模对新手来说,能在半天内完成数据加载、模型训练和结果分析整套流程;对熟手来说,它又是一个检验迁移学习、数据增强策略是否有效的低成本试验场。
这个数据集解决的核心问题很简单:当你想验证一个分类网络在遥感影像上的表现,或者想理解不同地物类别(机场、操场、港口、居民区等)在视觉特征上的差异,不需要一上来就啃上百 GB 的卫星影像。WHU-RS19 的标注是图像级别的,即每张图对应一个土地利用类别标签,而不是像素级分割掩码,所以它天然适合做图像分类任务,也方便做消融实验和基线对比。适合人群包括刚转遥感方向的学生、做算法选型验证的工程师,以及需要快速评估某个预训练模型在遥感场景下是否值得继续投入的团队。
但别被"简单"两个字骗了。这 1,000 张图的类别不均衡、图像分辨率不统一、某些类别之间视觉混淆严重,这些坑会在后面的章节一个个展开。先把数据集本身的结构和标注规范搞清楚,后面踩坑时才有据可查。
2. WHU-RS19 数据集结构与类别分布:先搞清楚你手里有什么牌
2.1 文件组织与标注格式:不是所有"标好"都是你想要的格式
拿到 WHU-RS19 压缩包解压后,常见的目录组织方式是每种类别一个文件夹,文件夹名即类别标签,例如 airport、beach、forest、residential 等。每张图是典型的 RGB 三通道遥感影像,格式多为 JPG 或 PNG,尺寸不一——这一点很关键,因为遥感影像的原始分辨率受成像平台影响,有的图是 600×600,有的可能是 800×600,不会像 ImageNet 那样统一成正方形。
标注格式是典型的"隐式标签":类别信息藏在文件夹路径里,而不是像 COCO 那样有独立的 JSON 标注文件。这是很多第一次接触这个数据集的同学会忽略的点——直接用 torchvision.datasets.ImageFolder 加载时,它会按照一级子目录名自动生成类别索引,顺序按文件夹名字母排序。如果你依赖这个顺序去对应自己定义的类别名称,一定要先打印 class_to_idx 看看对应关系,否则后面画混淆矩阵时类别轴全是乱的。
文件清单这个概念在 WHU-RS19 里并不复杂,常见做法是解压后先统计一下每个文件夹下的文件数量,确认是否符合约 50 张/类的预期。我用一个简单的 Python 脚本做这一步:
import os dataset_root = "WHU-RS19" class_counts = {} for cls_name in sorted(os.listdir(dataset_root)): cls_path = os.path.join(dataset_root, cls_name) if os.path.isdir(cls_path): file_count = len([f for f in os.listdir(cls_path) if f.lower().endswith((".jpg", ".png", ".jpeg"))]) class_counts[cls_name] = file_count for cls, cnt in class_counts.items(): print(f"{cls}: {cnt}") print(f"总类别数: {len(class_counts)}") print(f"总样本数: {sum(class_counts.values())}")这段代码的逻辑很简单:遍历 WHU-RS19 根目录下的一级子目录,统计每个目录下图像文件的数量,然后打印类别名、对应数量和总和。参数说明:endswith接受的元组里按需加上你实际遇到的图片扩展名,有的版本可能包含.tif或.bmp;dataset_root指向你解压后的实际路径。跑完你就能确认手里的数据是否完整。
2.2 19 个类别都有谁:从地物类型看模型要面对什么
WHU-RS19 的 19 个类别覆盖了常见的高分辨率遥感场景,包括:飞机场(airport)、海滩(beach)、立交桥(overpass)、商业区(commercial)、农田(agriculture)、森林(forest)、工业区(industrial)、草地(meadow)、港口(harbor)、停车场(parking)、居民区(residential)、河流(river)、体育场(stadium)、池塘(pond)、高架桥(viaduct)、火车站(railway station)、网状道路(road)、广场(square)等。具体类别拼写以你解压后的文件夹名为准,不同发布渠道可能有细微差别。
这些类别在视觉特征上差异巨大,但有两组特别容易混淆:一是forest和meadow,都是大片绿色,只是纹理密度不同;二是railway station和harbor,都有人造建筑和水域或铁轨的混合结构。这种类别间相似性对分类模型是天然压力测试,反过来也意味着你在这个数据集上刷到的准确率,需要按类别拆分看才有参考价值。
另一个值得注意的点是:每类约 50 张,这个数量级对于端到端训练一个深层网络是明显不足的。直接从头训练 ResNet 这类模型,大概率在验证集上波动很大。所以围绕这个数据集的主流用法是迁移学习——用 ImageNet 预训练权重初始化骨干网络,然后微调全连接层或部分卷积层。这也是后面章节要详细展开的内容。
2.3 数据划分策略:别让测试集泄漏进训练集
由于 WHU-RS19 没有官方规定的训练/验证/测试划分,你自己动手切分数据时就要格外小心。最常见且可靠的做法是按类别分层采样,保证每个类别在训练集、验证集、测试集中的比例一致。例如按 6:2:2 划分,每类 50 张就是 30 张训练、10 张验证、10 张测试。
这里有一个隐藏的坑:如果解压后的文件顺序本身是按照某种拍摄时间或地点排列的,直接按顺序切片会导致同一个区域的图像都进了训练集或测试集,造成空间相关性泄漏。遥感图像的相邻帧往往高度相似,这种泄漏会让验证准确率虚高,换个区域的数据马上现原形。所以我一般会先对每个类别的文件名做一次随机打乱,固定随机种子后再切分。
用 scikit-learn 的train_test_split加分层参数可以一步到位:
import os import random from shutil import copy2 from sklearn.model_selection import train_test_split random.seed(42) paths = [] labels = [] for cls_name in sorted(os.listdir("WHU-RS19")): cls_dir = os.path.join("WHU-RS19", cls_name) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): if fname.lower().endswith((".jpg", ".png", ".jpeg")): paths.append(os.path.join(cls_dir, fname)) labels.append(cls_name) train_paths, temp_paths, train_labels, temp_labels = train_test_split( paths, labels, test_size=0.4, stratify=labels, random_state=42 ) val_paths, test_paths, val_labels, test_labels = train_test_split( temp_paths, temp_labels, test_size=0.5, stratify=temp_labels, random_state=42 )这段代码先把所有图像路径和对应标签读进内存,然后按 test_size=0.4 分出 40% 作为临时集,再把临时集二等分成验证集和测试集各 20%。stratify=labels是分层的关键参数,保证每个类别在不同集合中的比例与原数据集一致。random_state=42固定随机过程,方便他人复现。切分完成后,可以进一步检查每个集合的类别分布是否均衡。
3. 把 WHU-RS19 变成模型能吃的格式:读取、增强与批处理
3.1 用 ImageFolder 还是自定义 Dataset:看你要不要做复杂变换
如果只是跑一个标准分类流程,torchvision.datasets.ImageFolder是最省事的基线选择。它要求目录结构恰好是"根目录/类别/图像",WHU-RS19 解压后天然满足这个条件,所以直接用就行。但 ImageFolder 在读取时会自动把图像缩放到你指定的尺寸,这个缩放方式是最近邻插值还是双线性,会直接影响遥感图像的纹理信息保留程度。
考虑到遥感图像里的地物纹理(比如农田的条带、港口的水陆边界)是分类的关键线索,我一般不太放心用默认的Resize加CenterCrop一套组合拳直接丢给模型。更稳妥的做法是自定义一个 Dataset 类,在初始化时读取所有样本路径和标签,在__getitem__里控制图像读入后的预处理顺序。这样既能统一处理不同尺寸的原始图,又能在训练和验证阶段使用不同的变换策略。
下面是一个自定义 Dataset 的参考实现:
import torch from torch.utils.data import Dataset from PIL import Image import torchvision.transforms as T class WHURS19Dataset(Dataset): def __init__(self, file_paths, labels, train=True, target_size=(224, 224)): self.file_paths = file_paths self.labels = labels self.train = train self.target_size = target_size self.transform = self._build_transform() def _build_transform(self): base = [ T.Resize(self.target_size, interpolation=T.InterpolationMode.BILINEAR), ] if self.train: base += [ T.RandomHorizontalFlip(p=0.5), T.RandomRotation(degrees=15), T.ColorJitter(brightness=0.2, contrast=0.2), ] base += [ T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ] return T.Compose(base) def __len__(self): return len(self.file_paths) def __getitem__(self, idx): img = Image.open(self.file_paths[idx]).convert("RGB") label = self.labels[idx] img = self.transform(img) return img, label这个类的设计逻辑是:训练阶段做随机水平翻转、随机旋转和颜色抖动,模拟遥感影像在不同成像角度和时间下的变化,提升模型泛化;验证和测试阶段只做 Resize 和标准化,保证评估结果稳定。参数说明:target_size按你的骨干网络输入要求来定,ResNet 系列常用 224×224,EfficientNet 系列可能是 240 或 300,需查阅对应模型配置;normalize的均值和标准差用的是 ImageNet 统计量,如果后续要训练的是自监督预训练模型,这里需要替换成遥感数据自己的统计量,否则预训练权重的作用会被削弱。
3.2 数据增强的尺度:在遥感图上别乱用随机裁剪
图像分类里的常规增强套路是 RandomResizedCrop,它随机裁剪一块区域再缩放到固定尺寸。这套思路在 ImageNet 上有效,因为物体通常占据画面中心,裁剪掉一部分不影响语义。但在 WHU-RS19 里,一张 600×600 的遥感图上可能包含了多个地物成分,比如"居民区"类别里会混有道路和绿地,"港口"类别里同时存在水面、船舶和建筑。随机裁剪如果裁掉了核心判断区域,标签虽然没有变,但视觉语义已经漂移了,模型学到的关联会产生噪声。
所以我更倾向用固定尺寸的 Resize 加随机翻转和旋转,最多加一点轻微的颜色扰动。遥感图像是从上往下看的正射视角,翻转和旋转(90度的整数倍)不会改变地物语义,水平翻转和 15 度内的随机旋转在实践中也比较安全。注意不要用太大的旋转角度,因为旋转会引入黑边或裁切损失,而且会让规则的建筑群产生不真实的斜向排列。
3.3 加载与批处理:DataLoader 参数怎么设不算浪费 GPU
WHU-RS19 总样本只有约 1,000 张,单张图几百 KB,整个数据集加载进内存都可行。所以在 DataLoader 里不需要纠结 IO 瓶颈,更多的注意力应该放在 batch size 和 num_workers 的设置上。如果你的显卡显存是 8GB 左右,输入 224×224、ResNet50 骨干、批量大小 32 大概要吃 6~7GB,留给训练过程比较紧张;批量大小降到 16 更稳妥。
num_workers 的作用是提前异步读取下一批数据,因为图像读取和 Resize 是 CPU 操作,不占用 GPU 计算。我一般设置在 4 到 8 之间,取决于你的 CPU 核数和系统是否在同时跑其他任务。设太大会造成进程切换开销,设太小会让 GPU 在每轮迭代开始时空等。
from torch.utils.data import DataLoader train_dataset = WHURS19Dataset(train_paths, train_labels, train=True) val_dataset = WHURS19Dataset(val_paths, val_labels, train=False) train_loader = DataLoader( train_dataset, batch_size=32, shuffle=True, num_workers=4, pin_memory=True ) val_loader = DataLoader( val_dataset, batch_size=32, shuffle=False, num_workers=4, pin_memory=True )shuffle=True在训练阶段打乱样本顺序,避免模型学到批次内的顺序依赖;验证集不需要打乱,因为只是前向传播。pin_memory=True适用于 GPU 训练,它把数据固定在 CPU 的内存锁页区,能减少 CPU 到 GPU 的传输延迟。如果你的机器内存够大,这个参数开启后训练速度会有可感知的提升。
4. WHU-RS19 分类实践避坑:这 5 个坑我替你踩过了
4.1 坑一:类别标签顺序对不上,混淆矩阵全乱
现象:训练完模型后画混淆矩阵,发现对角线上的数字对不上,明明准确率挺高,但某个类别的预测结果总是"移位"到相邻类别上。
原因:WHU-RS19 没有标准标签文件,ImageFolder 按文件夹名的字母排序生成class_to_idx,比如agriculture是 0,airport是 1,而你自己手写标签字典时是按中文习惯排列的,两套顺序不一致,导致模型输出的 logits 索引与真实标签解析对不上。
解决:在数据加载后第一时间打印并保存dataset.class_to_idx,把这个映射作为后续所有评估的唯一标准。自定义 Dataset 时,用sorted(os.listdir(...))生成标签列表,保证生成索引的方式和 ImageFolder 一致。写评估代码时,先从 DataSet 里取 label 索引,再映射回类别名称,不要在推理阶段重新排序。
4.2 坑二:单类 50 张导致过拟合,验证曲线剧烈震荡
现象:训练 loss 一路下降,但验证准确率忽高忽低,有时 80%,有时 95%,换个随机种子结果就差很多。
原因:每类只有约 50 张样本,训练集按 6:2:2 划分后每类仅 30 张。模型容量过高时,它会直接记住训练图里特有的颜色和纹理组合,验证集上稍有变化就判断失误,方差大是必然的。
解决:两个手段配合使用。一是降低模型容量,把 ResNet50 换成 ResNet18,或者把预训练模型的最后全连接层隐藏层大小从 2048 降到 512;二是加强数据增强,尤其是翻转、旋转、颜色抖动这些对遥感图友好的操作。更激进的做法是做 5 折交叉验证,用平均准确率和标准差来评估模型稳定性,不要只依赖一次划分。
4.3 坑三:迁移学习时冻结全部骨干层,效果还不如从零训练
现象:用 ImageNet 预训练权重初始化 ResNet,把所有卷积层参数冻结,只训练最后全连接层,验证准确率只有 60% 左右,比从零训练还差。
原因:ImageNet 预训练权重是在自然图像上学习的,自然图像关注的是物体轮廓和局部纹理,而遥感图像的判别特征往往是尺度性的纹理模式和空间排布,比如农田的条纹、居民区的屋顶排列。这些特征在骨干网络的浅层就存在差异,全部冻结等于放弃了遥感图特有的特征提取能力。
解决:最常见的做法是只冻结 Backbone 的前几层,后几层参与微调。一个有效策略是按批次解冻:先用冻结全部骨干的方式训练若干个 epoch 让分类头收敛,然后解冻最后几个 Bottleneck,用较低的学习率继续训练。学习率设置为初始训练学习率的十分之一到五分之一,防止预训练权重被破坏。
4.4 坑四:忽略原始图像尺寸信息,Resize 后丢失细粒度纹理
现象:模型训练正常,但拿到一张 600×600 的港口图,预测结果把港口误判成河流,检查代码发现 Resize 强制缩放到 224×224 时把码头和船舶细节全压缩没了。
原因:遥感影像中地物判别常常依赖小目标和细纹理。当原始图像宽高比接近 1:1 时,直接 Resize 到 224×224 是可行的;但部分样本可能是长条形或包含大量背景区域,无脑 Resize 会破坏关键目标的比例关系。
解决:一个可靠做法是加入 SquarePadding,先将原始图像等比缩放到短边等于目标尺寸,然后在长边两端填充灰度值,最后再 Resize。填充色可以选择该类地物的平均像素值(如农田用绿色调,水体用深色调),但实践表明用 128 灰色填充对分类结果影响很小,因为模型看到的是完整地物分布。如果你希望保留更多高频信息,也可以在 Resize 时使用InterpolationMode.LANCZOS,它的插值结果比双线性更锐利,但计算开销稍大。
4.5 坑五:用全局平均池化后的特征直接做分类,忽略空间分布
现象:有人把 ResNet 的 pool 层输出(2048 维向量)直接输入 SVM 或逻辑回归,发现不同类别之间的可分性并没有想象中那么好。
原因:全局平均池化把整张图的特征压缩成一个向量,丢失了地物的相对位置信息。遥感分类中,"港口"和"居民区"可能包含相似的颜色块,但空间分布完全不同——港口的水体集中在图像边缘或按区块分布,居民区的建筑呈棋盘式排列。仅靠全局统计特征很难区分这类空间模式。
解决:如果要在特征层面做实验,建议结合特征金字塔或直接使用分类网络的最终特征图做全局池化之前,叠加一层卷积注意力模块(如 CBAM),让模型关注空间上的判别区域。或者更简单一点,使用 ResNet 的 layer3 输出特征图(空间尺寸 14×14),做全局平均池化后拼接不同尺度的池化结果,能保留更多空间线索。
5. 把 WHU-RS19 用出价值:微调参数建议与验证技巧
5.1 一个适合小样本的微调策略:分阶段学习率
基于我在这类千张级遥感数据上的经验,最稳的训练流程分三个阶段:第一阶段冻结骨干,只训练分类头,学习率 1e-3,跑 10 个 epoch;第二阶段解冻最后两个残差块,学习率降到 1e-4,跑 20 个 epoch;第三阶段整个网络参与训练,学习率 5e-5,跑 20 个 epoch。用 AdamW 优化器,weight decay 设 0.01,batch size 16。
import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR model = torchvision.models.resnet18(weights=torchvision.models.ResNet18_Weights.IMAGENET1K_V1) model.fc = torch.nn.Linear(model.fc.in_features, 19) # 阶段一:冻结骨干 for param in model.parameters(): param.requires_grad = False for param in model.fc.parameters(): param.requires_grad = True optimizer = optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3, weight_decay=0.01) scheduler = CosineAnnealingLR(optimizer, T_max=10)这段代码中,filter(lambda p: p.requires_grad, ...)只传入需要梯度的参数(这里是分类头),避免冻结层产生无意义的梯度更新。CosineAnnealingLR 让学习率从 1e-3 余弦下降到接近 0,适合小数据集的收敛特性。每个阶段结束后,用验证集评估,如果准确率不再提升,果断停止该阶段,提前进入下一阶段。
5.2 验证模型的正确姿势:按类别的召回率比总准确率重要
WHU-RS19 的总样本少,平均准确率高不代表所有类别都可靠。我通常会在训练过程中记录每个类别的精度、召回率和 F1,训练结束后输出分类报告。你会发现,forest和meadow的召回率通常垫底,而airport和stadium这种结构特征明显、色调统一的类别准确率很高。这个结果符合遥感影像的认知规律——结构性地物有强几何特征,纹理性地物更容易混淆。
一个专门针对 WHU-RS19 的验证技巧是检查模型的激活区域。用 Grad-CAM 对验证集里预测正确的样本和预测错误的样本分别可视化,你会发现预测错误的样本往往在空间上只关注了局部背景区域,而没有覆盖真正的地物核心。这是在 Datasets 之外帮你快速定位模型失效模式的低成本手段。
5.3 最后一里路:用测试集做置信度校准
很多人训练完直接拿测试集算个准确率就结束了。但小数据集的测试集只有每类 10 张,一次预测的偶然性很大。我的习惯是记录每个测试样本的 softmax 概率,并计算所有错误样本的平均置信度。如果模型在错误样本上的置信度高于 0.8,说明模型过于自信,需要考虑在训练时加入 Label Smoothing 或更强的正则化;如果错误样本置信度较低,说明模型学习不充分,可以尝试增加训练轮数或调高 backbone 微调层数。
另外,由于 WHU-RS19 是遥感领域少有的包含常见土地利用类别的小规模数据集,它特别适合做多模型对比实验。同一个预训练权重,分别用 ResNet18、ResNet34 和 EfficientNet-B0 做同样的微调流程,比较它们在每类召回率和模型大小上的差异。这类对比结果写进技术方案或者论文基线,比单纯报一个总准确率更有说服力。
做遥感分类有一段时间后,我最大的感受是:数据集越小,越容易让人轻视里面的门道。WHU-RS19 让我花了不少时间在排查那些看似"不该出错"的地方,但也正是这些排查过程,把对数据组织、迁移学习边界和评估规范的敏感度练出来了。希望你跑通这套流程后,也能带着这种敏感度去面对下一个更大也更复杂的数据集。希望帮到你。
本文还有配套的精品资源,点击获取