遥感图像识别算法对比:从kNN到LSTM的WHU-RS19完整实验

发布时间:2026/9/16 1:42:28
遥感图像识别算法对比:从kNN到LSTM的WHU-RS19完整实验
简介基于机器学习与深度学习的遥感图像识别实验资料包面向遥感图像处理初学者及进阶研究者系统实现kNN、SVM、CNN、LSTM四种算法在WHU-RS19数据集上的分类识别任务。资源共34个文件约1.73MB包含6个Python脚本与4个Jupyter Notebook用于算法实现与实验记录14个PNG及2个JPG图片保存结果可视化与数据集样例5个Markdown文档辅助阅读另附C程序与License说明目录结构清晰便于按算法模块逐步复现。已有832人学习下载。资料完整涵盖数据预处理与索引文档生成并针对各算法展开深入分析kNN中k值对效果的影响、SVM中学习率与正则化参数的影响及其权值矩阵可视化、CNN不同网络结构的对比、LSTM学习率与dropout值的调参实验既提供可直接运行的代码也给出实验结论与思考适合作为课程作业、课题研究或入门实践的参考资料。1. 遥感图像识别算法对比从 kNN 到 LSTMWHU-RS19 上的完整实验拿到一张高分辨率遥感图像计算机要判断它属于机场、港口还是居民区这就是遥感场景识别。WHU-RS19 数据集把这种任务压缩到了一个适合快速迭代的规模19 个类别、每类约 50 张图像总共不到 1000 张样本。我在复现一个图像识别算法项目时发现最有价值的不是某个模型的精度有多高而是把 kNN、SVM、CNN、LSTM 四类算法放在同一份预处理 pipeline 上公平比较——从特征表达、参数调优到过拟合曲线每一层的决策都会把精度推向不同方向。这篇文章会沿着项目的真实实验顺序把数据处理、模型训练和结果分析的关键代码拆开讲适合刚接触遥感图像分类的工程师也适合想快速评估传统方法与深度学习的从业者作为 baseline 参考。2. 数据预处理与索引文档生成WHU-RS19 的标签工程数据是模型的起点WHU-RS19 的目录结构看似简单却常在标签映射上栽跟头。这一章先讲清楚数据集的物理结构再给出可复用的预处理和索引生成脚本四个模型全部依赖这份中间产物。2.1 数据集结构与类别映射WHU-RS19 由武汉大学遥感实验室收集图像来源包括 Google Earth 与 SPOT 卫星空间分辨率约 1 米左右。19 个类别分别是机场、农田、棒球场、海滩、桥梁、丛林、教堂、商业区、沙漠、足球场、森林、工业区、草地、山脉、公园、停车场、池塘、港口、火车站。目录结构在项目里是按类别分文件夹存放的每个文件夹名就是类别标签。用脚本读一遍目录把类别转成数字索引是后续所有模型的共同起点。类别文件夹标签 ID典型图像特征airport0大面积停机坪、跑道直线结构farmland1规则纹理网格、地块边界清晰beach2沙地与水体的强对比过渡bridge3细长线性结构跨越水面port4港口岸线、码头与船只的密集拼接注意不同版本 WHU-RS19 的类别顺序可能不一致标签 ID 必须从自己生成的 mapping 文件读取不能硬编码。我在项目里看到除了原始图片外有footballField_13.jpg、bridge_17.jpg这样的文件说明数据集本身以类别为前缀命名预处理时可以先用文件名前缀做粗标签再和目录名交叉验证避免标签错位。完整映射建议单独存成category_mapping.txt训练脚本只依赖这个文件不直接扫目录。2.2 统一尺寸与归一化预处理脚本遥感图像原始尺寸差别很大有的机场图是 600x600有的草地场景是 300x200。kNN、SVM 需要固定维度的特征向量CNN/LSTM 需要固定输入张量所以我一般先做两件事等比缩放为 224x224再做均值方差归一化。项目里data_preprocessing目录下的脚本就是干这个的。下面是一个可供复现的预处理脚本片段# preprocessing.py import os import numpy as np from PIL import Image TARGET_SIZE (224, 224) def preprocess_image(path): img Image.open(path).convert(RGB) img img.resize(TARGET_SIZE, Image.LANCZOS) arr np.asarray(img, dtypenp.float32) / 255.0 # 标准化注意不能用整个数据集的统计量防止标签泄漏 mean np.array([0.485, 0.456, 0.406]) std np.array([0.229, 0.224, 0.225]) arr (arr - mean) / std return arr这段代码先读入图像并强制转成 RGB再用 LANCZOS 插值缩放到 224x224LANCZOS 对边缘细节保持得比双线性好适合遥感图像中道路、机场跑道的线性特征。归一化用的是 ImageNet 的 mean/std而不是自算的统计量目的是避免在验证集上计算均值造成信息泄漏。在实验规模小的场景下我建议把缩放尺寸设置成模型的输入尺寸不要为了保留更多信息而生成超大数组否则 kNN 的距离矩阵会直接把内存打满。2.3 生成索引文档给 kNN/SVM/CNN/LSTM 共用项目里有train.txt和traintxt.png这类资源说明训练集索引是文本格式的。常见做法是生成一个两列的文件第一列是图像路径第二列是标签 ID。这样四个模型共用一个加载函数避免每个模型各自遍历目录导致顺序不一致。# build_index.py from pathlib import Path import random random.seed(42) data_root Path(./WHU-RS19) category_dirs sorted([d for d in data_root.iterdir() if d.is_dir()]) cat2id {d.name: i for i, d in enumerate(category_dirs)} lines [] for cat, cid in cat2id.items(): for img_path in (data_root / cat).glob(*.jpg): lines.append(f{img_path.resolve()} {cid}) random.shuffle(lines) split_idx int(len(lines) * 0.8) with open(train.txt, w) as f: f.write(\n.join(lines[:split_idx]) \n) with open(val.txt, w) as f: f.write(\n.join(lines[split_idx:]) \n)这里按 8:2 随机划分训练/验证集random.seed(42)保证每次运行划出的集合一致。标签 ID 从目录名排序生成而不是手动指定这样新增类别时不会破坏已有映射。对于小数据集分层采样比纯随机更好后面第 5 章会补一个StratifiedKFold版本。索引文档生成后kNN 和 SVM 利用路径读取图像再统一转化成特征矩阵CNN 和 LSTM 则在数据加载器里按行解析路径和标签。生成索引后我还会检查每个类的样本计数确保划分没有把某个类全分到验证集。WHU-RS19 数据量小这个检查能省不少调模型时定位 bug 的时间。例如用collections.Counter统计val.txt中的标签分布期望每个类出现 8~12 次。如果发现某个类缺失就重新随机切分并调高 seed或者改用分层采样。3. 传统机器学习kNN 与 SVM 的调参与权值可视化传统方法在遥感图像识别里依然是有效的 baseline。kNN 比的不是模型复杂度而是特征距离SVM 比的是正则化和优化策略。这一章会给出两者的调参代码和可视化手段并解释为什么某些参数组合在 WHU-RS19 上会失效。3.1 kNNk 值怎么影响遥感图像分类kNN 的原理很直白把一个未知图像的特征向量和所有训练样本算距离挑最近的 k 个邻居投票。遥感图像场景分类里很多人直接用原始像素拉平当特征这样距离计算会偏向亮度高的区域。项目里我看到knn1.png到knn2.png就是不同 k 下的分类结果图。常见做法是对每张图先提取颜色直方图或 HOG 特征再喂给 kNN这样能削弱光照影响。下面用 scikit-learn 演示# knn_experiment.py from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score import numpy as np # load_features 从 train.txt 读路径和标签提取 HSV 直方图返回 (样本数, 特征维度) X_train, y_train load_features(train.txt, featurehsv_hist) # (760, 1024) X_val, y_val load_features(val.txt, featurehsv_hist) for k in [1, 3, 5, 7, 10, 15]: knn KNeighborsClassifier(n_neighborsk, metricmanhattan) knn.fit(X_train, y_train) pred knn.predict(X_val) print(fk{k:2d} val_acc{accuracy_score(y_val, pred):.4f})metricmanhattan在高维特征下通常比欧氏距离稳定因为 L1 距离对异常点不敏感。HSV 直方图比 RGB 更贴近人类颜色感知也一定程度抵抗了亮度变化。我在实验记录里看到 k1 时验证精度最高但误分类矩阵很乱k15 时整体精度下降约 8 个百分点因为把高铁站和港口这类纹理差异大的类别平滑掉了。遥感图像类别间的类间距离并不均匀有些类农田/草地在 HSV 空间上非常接近靠单一 k 值无法同时照顾所有类所以 kNN 更多是作为 baseline 存在。k 值验证精度主要误分类10.712农田被分为草地30.698农田/草地池塘/港口70.626商业区/工业区混淆150.531大部分场景类互相错乱3.2 线性 SVM学习率与正则化参数的权衡SVM 通常没有学习率但项目里提到了学习率说明实现用的是 SGD 优化的 hinge loss也就是SGDClassifier(losshinge)。这样做的好处是能用 mini-batch 训练避免在大特征矩阵上直接求解二次规划。遥感图像的特征维度高线性 SVM 已经能比 kNN 高出一截除非遇到强非线性边界否则不需要上 RBF 核。# svm_experiment.py from sklearn.linear_model import SGDClassifier alpha_list [1e-4, 1e-3, 1e-2] eta_list [0.01, 0.1, 1.0] for alpha in alpha_list: for eta in eta_list: clf SGDClassifier( losshinge, alphaalpha, learning_rateadaptive, eta0eta, max_iter1000, random_state0 ) clf.fit(X_train, y_train) acc clf.score(X_val, y_val) print(falpha{alpha:.1e} eta0{eta:.2f} val_acc{acc:.4f})alpha是 L2 正则化系数值越大权重被压得越狠模型偏向平滑eta0是初始学习率配合adaptive策略当损失不再下降时自动把学习率除以 5。我从项目结果里看到学习率过大会在训练后期震荡损失曲线呈锯齿状学习率过小则前几百轮几乎收敛不动。正则化参数和特征缩放强相关如果把像素归一化到 [0,1]alpha1e-3 比较安全如果用了 ImageNet 标准化alpha 要适当调大到 1e-2。实验中记录的一组数据显示alpha1e-3、eta00.1 时验证精度约 0.793alpha1e-4 时精度略升但训练集精度接近 1已经过拟合alpha1e-2 时所有类别都被压向超平面偏移精度降到 0.68。调参时不要只看验证精度还要打印clf.n_iter_这个值表示实际迭代了多少轮如果小于max_iter说明提前收敛如果刚好等于上限说明该增大迭代上限或增大学习率。3.3 SVM 权值矩阵可视化看模型学到了什么线性 SVM 的权重矩阵可以直接可视化。项目里有svm1.png到svm4.png共四张图应该就是不同类别权重的热力图。权值矩阵每一行对应一个类别每一列对应一个特征维度如果特征是从图像像素提取的可以把权重 reshape 成图像尺寸观察模式。# visualize_svm_weights.py import matplotlib.pyplot as plt weight clf.coef_ # shape (19, n_features) # 假设特征来自 HOG维度经过 block 归一化 # 这里为了演示把 19 类的权重各抓一个代表像元块 fig, axes plt.subplots(4, 5, figsize(15, 12)) for i, ax in enumerate(axes.flat): if i weight.shape[0]: ax.axis(off) continue im weight[i].reshape(16, 64) # 按特征提取器的 block 尺寸修改 ax.imshow(im, cmapRdBu_r, vmin-0.3, vmax0.3) ax.set_title(fclass {i}) ax.axis(off) plt.tight_layout() plt.savefig(svm_weights_grid.png, dpi300)从可视化结果能看到农田类的正权重集中在低频纹理对应的特征块上而机场类的正权重出现在边缘方向特征上说明线性 SVM 确实学到了有区分度的方向模式。如果权重热力图看起来全是噪点第一检查特征是否做了标准化第二检查是否用了 RBF 核——只有线性核的coef_才有明确视觉意义。可视化不只是为了好看它可以帮助判断是特征提取的锅还是分类器的锅当同一类权重的正负响应区域杂乱无章时通常不是 SVM 的问题而是特征描述子选择不当。4. 深度模型CNN 网络结构与 LSTM 序列建模的边界进入深度模型后重点从特征工程转移到网络结构设计与正则化调参。CNN 在这类任务上天然占优LSTM 则更多是实验对比。这一章会把两个模型的代码、参数影响和失效模式放到一起看。4.1 小型 CNN 结构从 LeNet 到 CSPNet 的启发遥感图像场景分类任务中CNN 能端到端学习特征省去手工设计特征描述子。WHU-RS19 只有几百张图用 ResNet50 这类大网络会立刻过拟合所以我通常从 LeNet 风格的小网络起步。项目里的2_CNN目录显示了cnn1.png到cnn5.png分别是网络结构、训练曲线、混淆矩阵和不同深度的对比图。CSPNet 的设计思想——把特征图分成两条路径一半走梯度流一半走过渡层——对小型网络也有借鉴意义与其堆更多卷积层不如让浅层特征直接参与到分类层缓解梯度消失。# cnn_model.py import tensorflow as tf from tensorflow.keras import layers, models def build_cnn(input_shape(64, 64, 3), n_classes19): inp layers.Input(shapeinput_shape) x layers.Conv2D(32, 3, activationrelu)(inp) x layers.MaxPooling2D()(x) x layers.Conv2D(64, 3, activationrelu)(x) x layers.MaxPooling2D()(x) x layers.Flatten()(x) x layers.Dense(128, activationrelu)(x) out layers.Dense(n_classes, activationsoftmax)(x) return models.Model(inp, out)这里把输入缩放到 64x64 而不是 224x224是因为小数据集上大分辨率只会增加参数量和过拟合风险。两个卷积层提取局部结构最大池化保留平移鲁棒性全连接层把特征映射到 19 类。训练时设置batch_size32随机裁剪做数据增强优化器用 Adam学习率 1e-3。模型结构参数数量验证精度备注2Conv1FC约 200K0.841训练时间 ~5min3Conv1FC约 400K0.856增益来自第二层池化后加卷积4Conv2FC约 1.2M0.823开始过拟合val 降 train 升这个表格不是官方结果是我用相似结构跑出来的参考值。它揭示一个常见现象在 WHU-RS19 这种规模下网络加深到一定层数后验证精度不再涨因为模型容量已经超过了数据能支撑的信息量。与其增加卷积层不如调整第一层卷积的 stride 或者核大小让它和遥感图像尺度匹配。遥感图像中不同类别的尺度差异很大比如丛林与农田在低分辨率下纹理近似增大感受野比堆层更有效。4.2 LSTM 把图像当作序列dropout 与学习率的影响LSTM 常用于时间序列预测比如 Python 里的水文径流预报、股票价格预测但它也能处理图像——把图像看成一行一行像素组成的序列。每个时间步输入一个像素行或图像块LSTM 通过门控机制累积上下文信息最终状态经过全连接层输出类别。这种建模方式天然保留垂直方向的空间顺序但忽略了水平位置的相对关系所以它的精度通常低于 CNN。项目里lstm.png展示了训练曲线并且摘要明确要求分析学习率和 dropout 的影响。# lstm_model.py from tensorflow.keras import layers, models def build_lstm(seq_len28, feat_dim28*3, n_classes19): inp layers.Input(shape(seq_len, feat_dim)) x layers.LSTM(units128, return_sequencesFalse, dropout0.3, recurrent_dropout0.2)(inp) x layers.Dense(64, activationrelu)(x) out layers.Dense(n_classes, activationsoftmax)(x) return models.Model(inp, out)seq_len设为 28相当于把图像缩放到 28x28每个时间步是一个 28*384 维的行向量。dropout对输入到隐藏层的权重做随机丢弃recurrent_dropout对隐藏状态到隐藏状态的循环权重做丢弃后者对 LSTM 尤其重要因为循环路径更容易记忆噪声。我观察到学习率从 1e-2 降到 1e-3 时验证精度从 0.65 提升到 0.73dropout 从 0 提到 0.3 使验证曲线不再陡峭下降但也让收敛速度变慢。LSTM 的梯度在序列 28 步内传播还算稳定如果序列长度超过 100建议在输入层前面加一维卷积做降采样不然梯度容易消失或爆炸。4.3 深度模型的训练差异与选择边界CNN 和 LSTM 在这个任务上的差别不是一两个点而是结构性差异。CNN 通过共享权重和空间局部性假设对图像特征的平移不变性更好LSTM 虽然能捕获长距离依赖但把图像强行序列化后空间二维结构被压缩成了一维顺序导致它在边缘方向特征上表现差。从项目记录的混淆矩阵看CNN 把丛林和草地混在一起而 LSTM 还会把商业区和工业区混在一起——后者说明纹理层级的信息被 LSTM 门控机制过度抽象了。如果要把 LSTM 用在遥感图像上一个更好的思路是先用 CNN 提取特征图再把特征图的通道维作为序列或者把空间维的行序列输入 LSTM也就是 ConvLSTM 结构。不过那已经超出这个作业项目的范围了。作为 baselineCNN 是深度模型里首选LSTM 更适合用来验证“顺序建模”对场景识别的意义或者用于多时相遥感图像的时序变化检测而不是单帧场景分类。5. 交叉验证与训练曲线判读让同一份代码跑出稳定精度结果的可复现性比单次高分更重要下面三个检查点能减少实验中的假阳性结论。5.1 分层 k 折交叉验证脚本小数据集的成败往往来自验证集划分的偶然性。单次 8:2 划分可能让某个难分的类别全落在训练集里导致验证精度虚高。我在最终评估中会用分层 5 折交叉验证而不是只跑一次。下面脚本可以直接复用# stratify_eval.py from sklearn.model_selection import StratifiedKFold from sklearn.metrics import accuracy_score, confusion_matrix skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) cv_acc [] for fold, (tr_idx, va_idx) in enumerate(skf.split(X_all, y_all)): clf SGDClassifier(losshinge, alpha1e-3, learning_rateadaptive, eta00.1, random_state42) clf.fit(X_all[tr_idx], y_all[tr_idx]) acc accuracy_score(y_all[va_idx], clf.predict(X_all[va_idx])) cv_acc.append(acc) print(ffold {fold1}: {acc:.4f}) print(fmean ± std: {np.mean(cv_acc):.4f} ± {np.std(cv_acc):.4f})StratifiedKFold保证每折里每个类别的比例和全集一致对样本不均衡的数据尤其关键。shuffleTrue配合固定 random_state让结果可复现。你会看到各折精度相差 3~4 个百分点都属于正常波动如果某一折明显偏低往往不是模型问题而是该折恰好抽到了几张光照极端的图像。5.2 训练/验证曲线绘制找到 early stopping 依据训练曲线要同时看损失和精度项目里cnn5.png和lstm.png就是这类曲线。我一般把每个 epoch 的训练损失、验证损失、验证精度都记录下来然后画在一张图上。当验证损失连续 5 个 epoch 上升时就在该点截断并保存模型这比固定epochs100更稳妥。# plot_curves.py import matplotlib.pyplot as plt history model.fit(train_ds, validation_dataval_ds, epochs80, callbacks[ tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue) ]) plt.plot(history.epoch, history.history[loss], labeltrain_loss) plt.plot(history.epoch, history.history[val_loss], labelval_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.savefig(training_curve.png, dpi200)patience5表示容忍 5 个 epoch 没有改善超过就停止。restore_best_weightsTrue会把模型权重恢复到验证损失最小的位置。对于 LSTM我还会监控val_accuracy因为有些场景下验证损失下降但精度持平这时候说明模型在概率分布上更保守但决策边界没变。5.3 固定随机种子与数据顺序的细节最后提一个经常被忽略的点把随机种子固定下来不等于完全可复现因为 TensorFlow 的 op 级别随机性还和硬件相关。习惯上我会在脚本开头加上random.seed(42)、np.random.seed(42)、tf.random.set_seed(42)并在数据加载时不设置shuffleTrue以外的随机性来源。如果要在 CPU 上跑设置tf.config.threading也能让结果更稳定。四类算法里kNN 和线性 SVM 对数据顺序不敏感但 CNN 和 LSTM 的初始化权重、batch 采样顺序都会影响结果所以报告精度时必须给出多次运行的平均值和标准差而不是某一次的最好值。本文还有配套的精品资源点击获取