
简介面向毕业设计、课程设计及项目开发场景这套基于Python的高光谱遥感影像识别与分类资源提供了完整可运行的源码、配套项目文档与使用教程重点解决高光谱数据维度高、冗余大、样本有限导致分类器性能退化等实际问题。内容围绕三大核心研究点展开基于波段组合(2D)2PCA的高光谱降维方法以抑制休斯现象双通道卷积神经网络综合利用光谱与空间特征进行空谱提取CNN-SVM融合模型提升分类器泛化能力实测基于Indian Pines等标准数据集的完整流程均可复现。资源包共24个文件以13个Python源码脚本为主辅以MAT数据文件、模型权重、PNG图片、使用说明与Markdown文档压缩包大小6.01MB目录划分了数据处理、模型构建、日志记录等模块便于按步骤学习与二次开发。目前已有199人浏览学习源码经过严格测试并附带详细文档既可作为毕业设计、课程设计的完整基线方案也适合开发者在此基础上开展算法对比与功能扩展。1. 高光谱遥感影像识别与分类一条 Python 流水线从数据到分类图的完整落地高光谱遥感影像识别与分类就是把航空或卫星传感器拍下的几十上百个波段光谱数据交给 Python 程序逐像素判断地物类别——这块是玉米、那片是水体、远处是建筑屋顶。近几年这几乎成了毕业设计和课程设计里出镜率最高的遥感题目公开数据集成熟、评价指标明确、分类图一画出来答辩效果很好。但真正上手你会发现从 .mat 数据读取、波段筛选、PCA 降维到 SVM 与 CNN 训练每一步都有隐藏坑。这篇文章把我做过的完整方案拆开讲新手能照着复现熟手可以直接对参数和边界。2. 从原始 HSI 到模型输入数据读取、波段筛选与归一化的实战做法高光谱影像识别与分类的起点不是模型而是数据。这一步没做好后面选什么算法都白搭而且数据问题往往隐蔽——不报错、不崩溃只是在精度数字上悄悄拖后腿。2.1 公开数据集的存储格式与读取以 Indian Pines 为例高光谱分类最常用的公开数据集有三个Indian Pines、Pavia University 和 Salinas。它们由 AVIRIS 或 ROSIS 传感器拍摄常见下载拿到的是 .mat 格式文件。以 Indian Pines 为例影像尺寸 145×145 像素原始 224 个波段公开版本常去除噪声波段后保留 200 个。文件里有两个关键变量影像三维数组以及与影像同尺寸的地物真值标签图标签 0 表示背景。import scipy.io as sio import numpy as np # 读取影像数据和标签图 img sio.loadmat(Indian_pines_corrected.mat)[input] gt sio.loadmat(Indian_pines_gt.mat)[gt] print(影像形状:, img.shape) # (145, 145, 200)高、宽、波段数 print(标签形状:, gt.shape) # (145, 145)逻辑说明loadmat 读入的是 MATLAB 格式字典用变量名作为键直接取值。很多教程写 [0] 取第一个元素在只有两个变量的文件里碰巧能用但文件里多个变量时就会拿错。稳妥做法是先打印所有键名再按名取。拿到影像后第一步是展平成二维样本矩阵——每一行代表一个像素的光谱曲线列是波段。# 展平把 (145, 145, 200) 转为 (21025, 200) H, W, B img.shape X img.reshape(H * W, B) y gt.reshape(H * W) # 剔除背景像素标签为0的样本 mask y ! 0 X_valid, y_valid X[mask], y[mask] print(有效样本数:, X_valid.shape[0], 有效类别数:, len(np.unique(y_valid)))参数说明reshape 的顺序必须和标签图一致高光谱影像一般是 (高, 宽, 波段)展平后第 i 行对应原图第 i 个像素。背景像素占整个 Indian Pines 的六成以上不剔除会让模型第一轮训练就把背景当成最大类。剔除后项目文档里要记录有效样本总数这个数字直接影响后面交代的训练数据规模和模型泛化能力。如果拿到的不是 .mat 而是 ENVI 格式的 .hdr/.dat或遥感软件导出的 GeoTIFF常见做法是用 spectral 或 rasterio 库读取。spectral 库专门处理高光谱数据读写 ENVI 格式很顺手rasterio 更通用适合和 GIS 工作流对接。抓数据时留意一点不同渠道下载的 Indian Pines 版本还原度有差异项目文档必须写明用的是 corrected 版本还是原始版本两者波段数和数值范围不同。记录数据版本这件事能帮你省掉至少一次结果怎么比论文差这么多的排查。2.2 波段子集选择不是波段越多分类越准高光谱波段之间相关性极高。拿 Indian Pines 来说相邻波段相关系数经常超过 0.95水汽吸收波段和噪声波段还会拖累距离计算。所以做分类前通常会先做一次粗筛把方差极低、明显是噪声的波段剔除。# 基于方差筛选波段方差太低的波段基本是常数或噪声 variances np.var(X_valid, axis0) keep variances np.percentile(variances, 10) X_filtered X_valid[:, keep] kept_bands np.where(keep)[0] print(原始波段数:, X_valid.shape[1], 保留波段数:, X_filtered.shape[1])逻辑说明波段筛选的意义不只是减少计算而是让后续 PCA 或模型训练避开低质量输入。np.percentile 取 10% 分位数作为阈值含义是砍掉方差最低的 10% 波段。阈值设置要看数据Indian Pines 用 10%-20% 比较安全数据源噪声大可以提高到 30%但要避免把有判别力的细微光谱差异一并删掉。这段代码有个容易被忽略的产出被保留的波段索引存进了 kept_bands。它有两个用处——后续构造光谱指数时知道哪个索引对应哪个波长范围项目汇报时能说明去除了哪些受水汽影响的波段这是答辩时一个很加分的细节。2.3 归一化先切分数据再 fit这个顺序不能乱高光谱各波段的数值范围差异很大。Indian Pines 有些波段均值只有几十有些达到几百到上千。SVM、KNN 甚至神经网络如果不归一化数值大的波段会在距离计算和梯度更新里占据绝对主导。归一化本身一分钟写完但顺序问题值得单独说必须先切分训练集和测试集再在训练集上 fit 归一化参数。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 分层切分保证少数类在训练和测试中的比例一致 X_train, X_test, y_train, y_test train_test_split( X_filtered, y_valid, test_size0.3, random_state42, stratifyy_valid ) # 在训练集上拟合归一化参数用同一组参数转换测试集 scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) print(训练集形状:, X_train.shape, 测试集形状:, X_test.shape)参数说明test_size0.3 表示 30% 样本做测试。stratifyy_valid 是分层采样它保证每个类别的样本在训练集和测试集里的占比与原数据集一致。这一步在高光谱里必须做——Indian Pines 中个别类别只有 20 个左右样本随机切分有小概率把这些样本全部划入测试集导致训练时完全没见过这个类别。fit_transform 和 transform 的分离是防数据泄漏的关键。scaler 在训练集上计算的均值和标准差是训练阶段的参数测试集必须当作全新数据只做变换不参与参数估计。如果先对全量数据 fit测试集的统计信息混进了归一化参数测试精度会虚高模型真正部署到新影像上时性能立刻露馅。3. 特征降维PCA 和光谱指数在高光谱分类里的实战对比3.1 为什么高光谱必须降维维数灾难与样本量现实高光谱数据和普通图像分类最大的差别是特征维度极高。一张 145×145 的 Indian Pines 影像有效像素约一万出头每个像素却有 200 个波段特征。样本量一万、特征两百乍看不是大问题但地物类内光谱差异大、标注样本稀少实际可用的有效样本经常只有几千。当特征维度接近样本量时欧氏距离在稀疏空间中趋于均匀分类器很难找到可靠的决策边界。这个现象在遥感领域叫 Hughes 现象也叫维数灾难。高光谱分类几乎绕不开它所以降维不是可选优化而是必要步骤。降维路径分两类特征提取把原始波段线性或非线性地组合成新特征和特征选择从原始波段里挑最有判别力的子集。PCA 是特征提取的代表光谱指数是结合先验知识的特征构造两者可以叠加使用。我自己的判断标准是样本量充足、目标类别差异大PCA 就够用类别相近、样本稀缺优先考虑光谱指数加波段选择。3.2 PCA 降维的实现与参数选择PCA 的核心是把原始高维特征投影到一组互相正交的主成分上这些主成分按方差贡献从大到小排列。在高光谱里第一主成分通常对应整体亮度后面的主成分才逐步对应地物之间的光谱差异。from sklearn.decomposition import PCA # 固定保留30个主成分 pca PCA(n_components30) X_train_pca pca.fit_transform(X_train) X_test_pca pca.transform(X_test) # 累计方差贡献率 cum_ratio pca.explained_variance_ratio_.cumsum() print(前30个主成分累计贡献率:, round(float(cum_ratio[-1]), 4))逻辑说明fit_transform 和 transform 分离的原因和第 2 章归一化一样——PCA 计算出的均值向量和特征向量属于训练阶段参数测试集只做投影。n_components 取固定值 30 是高光谱分类里比较稳的起点但我更推荐用累计方差贡献率来反推维度。# 用累计贡献率反推保留维度 pca_full PCA().fit(X_train) cum_ratio_full pca_full.explained_variance_ratio_.cumsum() n_opt int(np.argmax(cum_ratio_full 0.99) 1) print(保留99%方差需要的主成分数:, n_opt)参数说明0.99 是常见阈值设得太低会丢掉细分类别需要的光谱细节设得太高降维意义减弱。Indian Pines 在归一化后通常 20-40 个主成分就能达到 99% 贡献率。Pavia University 的波段结构和噪声分布不同n_opt 可能差十几维所以这个数值应该由程序算出来而不是写死在代码里。PCA 有个容易被忽略的副作用主成分是全局统计量的投影对光照变化和大气条件敏感。用同一模型在不同日期的影像上预测效果常常骤降。这也是为什么有些生产级方案宁可用波段选择加光谱指数也不全依赖 PCA。训练完成后用前两个主成分画一张散点图是快速判断数据可分性的好办法import matplotlib.pyplot as plt plt.figure(figsize(10, 8)) scatter plt.scatter(X_train_pca[:, 0], X_train_pca[:, 1], cy_train, cmaptab20, s8, alpha0.6) plt.colorbar(scatter) plt.xlabel(PC1) plt.ylabel(PC2) plt.savefig(pca_scatter.png, dpi200, bbox_inchestight)参数说明alpha0.6 加透明度类别重叠区域能看出密度s8 控制点大小样本过万时太大容易糊成一团。散点图里如果各类别自然成簇后续分类模型基本不会差如果完全混在一起问题通常出在数据预处理而不是模型选择。3.3 光谱指数用遥感先验知识构造增强特征光谱指数是从物理含义出发用特定波段的组合突出目标地物。最经典的是 NDVI归一化植被指数用近红外与红光波段的差比和突出植被信息。在传统高光谱分类流程里把 NDVI 等指数作为附加特征拼到 PCA 特征后面对植被相关类别有明显提升。# AVIRIS 传感器波段band 50 附近为红波段band 100 附近为近红外 red X_train[:, 50] nir X_train[:, 100] ndvi_train (nir - red) / (nir red 1e-6) # 拼接到 PCA 特征后 X_train_enhanced np.hstack([X_train_pca, ndvi_train.reshape(-1, 1)]) print(增强后特征维度:, X_train_enhanced.shape[1])逻辑说明加 1e-6 是为了防止分母为零在归一化后的数据上尤其必要因为部分波段值可能恰好为 0。波段索引必须对照数据集的波长信息来取——AVIRIS 的通道排列和 Sentinel-2 完全不同不能拿别的传感器的索引硬套。在深度学习方案里光谱指数不一定要人工构造CNN 的第一层卷积理论上能学出类似的波段组合。但小样本条件下显式加入物理先验比让模型自己探索更稳。这也是传统 SVM 加上波段指数往往能和基础 CNN 打平的原因。我做项目时习惯构造两到三个指数覆盖植被的 NDVI、覆盖水体的 NDWI、覆盖建筑的归一化建筑指数三者在城市和农业场景里互补性很强。4. 分类模型选型与训练SVM、随机森林和 1D-CNN4.1 传统方案首选RBF-SVM 的参数网格与调优高光谱分类在深度学习普及之前RBF 核 SVM 是标准答案。它对中等规模样本几千到几万表现稳定决策边界灵活对高维特征有一定鲁棒性。RBF-SVM 有两个关键超参数C 是误分类惩罚系数gamma 是 RBF 核的带宽。from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV param_grid { C: [0.1, 1, 10, 100], gamma: [0.001, 0.01, 0.1, 1], kernel: [rbf] } svm SVC(class_weightbalanced, probabilityTrue) grid_svm GridSearchCV( svm, param_grid, cv5, scoringf1_macro, n_jobs-1, verbose1 ) grid_svm.fit(X_train_pca, y_train) print(最优参数:, grid_svm.best_params_)参数说明C 越小正则化越强越大越努力把训练样本全分对高光谱里 C 取 1 到 100 之间是常见区间。gamma 影响单个样本的影响半径值越大决策边界越曲折过拟合风险越高。class_weightbalanced 自动调整类权重减轻少数类被忽略的问题——Indian Pines 的类别分布极不均匀这个参数通常能带来几个点的少数类 F1 提升。scoringf1_macro 是另一个关键选择。accuracy 会被多数类主导f1_macro 对每个类别等权计算能真实反映少数类表现。高光谱分类论文要求报 Kappa 系数和各类别精度训练时用 f1_macro 做筛选标准和最终评价指标口径一致。SVM 的问题在样本上万后暴露网格搜索要跑很多轮每轮都在做二次规划求解。我一般用两个技巧解决先在 2000 样本的子集上粗调参数锁定 C 和 gamma 的量级再全量跑最终模型或者用特征降维把维度控制在 30-50训练时间能缩短一个量级。4.2 随机森林最划算的基线模型随机森林在高光谱里是性价比最高的基线。它不需要精细调参能处理高维特征训练速度比 SVM 快还能直接输出波段重要性。如果一个新项目没有现成方案我通常会先用随机森林跑一遍拿到的精度可以作为后续所有模型的上车参考线。from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators300, max_depthNone, min_samples_leaf1, class_weightbalanced_subsample, random_state42, n_jobs-1 ) rf.fit(X_train_pca, y_train) # 查看波段重要性的排序 importances rf.feature_importances_ top_indices np.argsort(importances)[::-1][:10] print(前10个重要特征索引:, top_indices)逻辑说明随机森林的特征重要性来自袋外误差变化能告诉你哪些波段或主成分对分类贡献最大。这个信息有两个用途一是对比 PCA 特征和原始波段哪个方案更高效二是直接指导波段筛选把重要性为 0 的波段剔除后重新训练精度通常不变甚至更高推理速度更快。参数说明n_estimators 设 300 左右超过 500 收益很小max_depthNone 让树充分生长靠森林的随机性控制过拟合——这是随机森林区别于决策树的关键class_weightbalanced_subsample 让每棵树在采样时保持类别平衡比全局 balanced 更适合高维噪声数据。随机森林的短板是光谱曲线的连续性建模能力弱。树模型每次只在一个波段上做切分相邻波段之间的结构关联没有被利用。在纯净地物、类别可分度高的场景下随机森林和 RBF-SVM 精度接近但类别相近比如两种长势不同的玉米时SVM 和 CNN 的优势更明显。4.3 1D-CNN把光谱曲线当一维序列建模深度学习方案里高光谱分类的默认入门模型是 1D-CNN。它的输入不是整张影像而是每个像素的光谱曲线——一维向量卷积核沿着波段方向滑动自动学习相邻波段的组合特征。相比 SVM 只做点对点距离计算CNN 能提炼出类似红边斜率这类光谱形态特征。import torch import torch.nn as nn class Spectral1DCNN(nn.Module): def __init__(self, n_bands, n_classes): super().__init__() self.features nn.Sequential( nn.Conv1d(1, 16, kernel_size11, padding5), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.MaxPool1d(2), nn.Conv1d(16, 32, kernel_size7, padding3), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool1d(1) ) self.classifier nn.Linear(32, n_classes) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x)逻辑说明第一个卷积层 kernel_size11覆盖相邻 11 个波段隐藏层 16 个通道相当于让模型学习一组局部光谱模式。第二个卷积层核宽 7在第一个卷积输出上继续组合感受野覆盖更宽的波段范围。BatchNorm 在高光谱里尤其重要——输入波段数值方差差异大BN 能把每层中间输出拉回稳定分布显著加速收敛。AdaptiveAvgPool1d(1) 把每个通道的特征压缩成一个标量替代 Flatten 后接全连接的做法。好处是不管前面特征图长度怎么变分类层输入维度固定模型对输入波段数的容忍度更高。4.4 训练循环与参数batch_size、学习率和早停CNN 训练需要把 numpy 数据包装成 DataLoader。这里有个很多人第一次写会卡住的点PyTorch 的 Conv1d 输入形状是 (batch, channels, length)而 sklearn 体系的样本矩阵是 (样本数, 特征数)中间隔着通道维。from torch.utils.data import TensorDataset, DataLoader X_train_t torch.FloatTensor(X_train_pca).unsqueeze(1) X_test_t torch.FloatTensor(X_test_pca).unsqueeze(1) y_train_t torch.LongTensor(y_train) y_test_t torch.LongTensor(y_test) train_ds TensorDataset(X_train_t, y_train_t) test_ds TensorDataset(X_test_t, y_test_t) train_loader DataLoader(train_ds, batch_size128, shuffleTrue) test_loader DataLoader(test_ds, batch_size256, shuffleFalse)参数说明unsqueeze(1) 就是加通道维从 (N, L) 变成 (N, 1, L)让数据匹配 Conv1d 的输入约定。batch_size 选 128Indian Pines 有效样本约一万一个 epoch 不到一百步训练很快。shuffleTrue 用在训练集打乱样本顺序避免模型学到数据的排列规律测试集保持原序方便后面把预测结果恢复成分类图。def train_1d_cnn(model, train_loader, test_loader, epochs60, lr1e-3): device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) optimizer torch.optim.Adam(model.parameters(), lrlr, weight_decay1e-4) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size20, gamma0.5) criterion nn.CrossEntropyLoss() best_acc 0.0 for epoch in range(epochs): model.train() total_loss 0.0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() out model(xb) loss criterion(out, yb) loss.backward() optimizer.step() total_loss loss.item() # 每5个epoch做一次验证并保存最佳权重 if (epoch 1) % 5 0: model.eval() correct, total 0, 0 with torch.no_grad(): for xb, yb in test_loader: xb, yb xb.to(device), yb.to(device) pred model(xb).argmax(dim1) correct (pred yb).sum().item() total yb.size(0) acc correct / total print(fEpoch {epoch1}/{epochs}, Loss: {total_loss/len(train_loader):.4f}, fTest Acc: {acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_spectral_cnn.pt) print(Best Test Acc:, round(best_acc, 4)) model.load_state_dict(torch.load(best_spectral_cnn.pt))参数说明weight_decay1e-4 是 L2 正则化防止光谱维度过拟合StepLR 每 20 个 epoch 把学习率减半后期用更小步长精细收敛这是我常用的训练策略。保存验证精度最高的权重而不是最后一个 epoch 的权重这个小习惯在高光谱上特别重要——CNN 训练后期测试精度波动明显最后一轮往往不是最优状态。这个后悔药机制比任何学习率衰减技巧都实在。跑 CNN 前先确认机器是否有 CUDA没有就用 CPUIndian Pines 这种规模的数据 CPU 也能在几分钟内完成训练不用为了课程设计去租云 GPU。5. 高光谱分类避坑指南五个高频翻车现场与排查思路5.1 数据泄漏归一化和 PCA 用全局数据拟合现象测试精度高得离谱动不动 99%训练精度接近 100%但拿到新影像上预测效果一塌糊涂。原因在切分训练测试集之前就对全量数据做了 StandardScaler 或 PCA 的 fit测试集的统计信息已经参与训练参数的估计模型偷看了答案。解决把 fit 严格限定在训练集。模板顺序是 train_test_split → scaler.fit(X_train) → scaler.transform(X_train) / scaler.transform(X_test)PCA 同理。检查代码时重点看有没有对全量 X 调用 fit_transform 的痕迹。提示上面五条里数据泄漏和类别不平衡是影响结果真实性的两个关键问题论文里如果这两条没处理好评审很容易质疑结论。5.2 类别不平衡少数类精度为 0整体精度却很高现象整体 accuracy 95% 以上打开分类报告Oats 这个类别的 precision 和 recall 全是 0个别类别只有一两个样本被猜中。原因Indian Pines 的类别样本极度不均Oats 只有 20 个样本Soybeans-notill 超过 900 个。模型把决策边界完全推向多数类accuracy 被多数类拉高。解决切分时 stratifyy模型里加 class_weightbalanced评估用 f1_macro 和 Kappa 而不是 accuracy。如果少数类仍然被忽略可以尝试对少数类过采样但高光谱样本间空间相关性很强SMOTE 插值时要注意别跨越类别边界生成虚假光谱。5.3 内存溢出全量全波段数据直接塞进训练流程现象程序在 PCA fit 或 SVM 网格搜索时报 MemoryError或者 Jupyter 内核直接重启。原因Indian Pines 全量展开后约 21025×200 个 float 值本身不大但 float64 存储、交叉验证的多个副本、网格搜索的并行副本叠加后内存开销成倍增长。解决读取后立刻用 .astype(np.float32) 转换内存直接减半网格搜索先在 2000 样本的子集上粗调PCA 使用 svd_solverrandomized大样本下速度远快于默认的 full SVD。5.4 分类图出现密集噪点和横向条纹现象预测结果还原成 145×145 的分类图后地物边界极不规则分布着大量孤立像素点和实际田块边界对不上。原因逐像素分类只用了光谱信息没有考虑像素周围的空间上下文单独的分类错误在图上表现为椒盐噪点。另一个可能是展平和还原时索引顺序不匹配整张图错位。解决先核对索引对应关系确定没有错位再用多数滤波做可视化后处理——用一个 3×3 或 5×5 的窗口取众数作为中心像素值能有效去除孤立噪点。注意多数滤波只能用在最终出图阶段不能用在测试集评估上否则精度虚高。5.5 分类结果复现困难换了机器跑不出相同数字现象在别人电脑或换一个 Python 环境后同样的代码训练出的精度明显不同或者直接报错。原因sklearn 和 PyTorch 的随机性来源未固定版本差异导致算法行为变化深度学习在 GPU 和 CPU 上的数值运算顺序不同也会造成微小差异。解决固定所有随机种子——random.seed(42)、np.random.seed(42)、torch.manual_seed(42)PyTorch 还要设置 torch.backends.cudnn.deterministicTrue。项目文档里写死 Python 版本和依赖版本并导出 requirements.txt。把代码同步到 GitHub 时README 里写明Python 3.8 scikit-learn 1.0 PyTorch 1.10这种粒度评审老师能在自己电脑上复现你的结果信任度完全不同。6. 分类结果评估与可视化验证混淆矩阵、Kappa 系数与地物分布图项目做到最后判断方案是否值得投入靠的是评估口径和可视化。高光谱分类的答辩和文档里除了 accuracy通常还要报三样东西混淆矩阵、各类别 precision/recall/F1、Kappa 系数。from sklearn.metrics import confusion_matrix, classification_report, cohen_kappa_score y_pred best_svm.predict(X_test_pca) cm confusion_matrix(y_test, y_pred) kappa cohen_kappa_score(y_test, y_pred) print(classification_report(y_test, y_pred, digits3)) print(Kappa:, round(kappa, 4))参数说明classification_report 自带每个类别的精确率、召回率和 F1。Kappa 系数衡量预测与真实标签的一致性剔除了随机猜中的成分类别越不均衡它和 accuracy 的差距越大——所以高光谱论文几乎强制报 Kappa。digits3 保留三位小数方便直接贴进论文表格。分类分布图是项目里最有视觉冲击力的产出。生成时有个关键点训练和测试只覆盖了有效像素而分类图需要全图 145×145 的预测结果背景区域要留为 0 值。import matplotlib.pyplot as plt full_pred np.zeros(H * W, dtypeint) full_pred[valid_indices] best_svm.predict(X_full_pca[valid_indices]) # 背景保持 0 classification_map full_pred.reshape(H, W) plt.figure(figsize(8, 8)) plt.imshow(classification_map, cmaptab20, interpolationnearest) plt.colorbar(labelClass) plt.axis(off) plt.savefig(classification_map.png, dpi200, bbox_inchestight)参数说明valid_indices 是第 2 章剔除背景时记录的有效像素位置X_full_pca 是所有像素的 PCA 投影。只把测试集预测结果填回图里其余位置会变成黑点不能用于答辩展示所以常见做法是对全部有效像素做一次预测背景设为 0。cmaptab20 适合分类标签展示interpolationnearest 防止绘图时把离散类别平滑成渐变。最后一个习惯数据集文件、预处理的 scaler、PCA 模型、训练好的分类器用 joblib.dump 分开保存命名里带版本日期。我第一个高光谱项目就是因为没固定环境和数据版本三个月后自己复现时精度掉了 8 个点排查了一整天发现是换了数据源。后来每次跑通一个环节就导出 requirements.txt数据文件记录来源和预处理方式这个习惯帮我少踩了很多坑。希望帮到你。本文还有配套的精品资源点击获取