十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

高光谱影像分类实战:从数据读取到SVM与CNN模型调优

高光谱影像分类实战:从数据读取到SVM与CNN模型调优 简介这套基于Python实现的高光谱遥感影像识别与分类项目内含完整源码、项目文档与使用教程面向需要完成毕业设计、课程设计或相关课题开发的学生和研究人员。项目针对高光谱数据维度高、易导致分类性能下降的休斯现象提出基于波段组合(2D)2PCA的降维方法以降低数据冗余并设计双通道卷积神经网络综合提取光谱与空间特征再融合SVM分类器增强泛化能力完整覆盖了从数据降维、特征提取到分类识别的典型流程。压缩包内共24个文件包括13个Python源码、2个MAT数据文件以及PNG图片、项目文档、使用教程、txt配置文件等整体约6.01MB结构清晰文档详述了原理与运行步骤即便是入门级开发者也能参照说明快速跑通并复现结果。目前已有199人学习适合作为毕业设计、课程设计或项目开发的基础框架可在此基础上进行算法对比与二次扩展。1. 高光谱影像分类,毕业设计选它的三个理由一张 400x400 像素的遥感影像,每个像素不是 RGB 三个通道,而是 224 个连续波段构成的一条光谱曲线。这正是高光谱遥感影像识别与分类和普通图像分类最本质的区别:数据不是“三通道大图”,而是“带深度的高维立方体”。很多同学拿到课题后第一步就卡在数据读取上,后面又卡在特征维度爆炸、训练集太小、精度上不去这几个环节上。这个方向适合作为毕业设计或课程设计的原因很直接:它同时覆盖了 Python 数据处理、机器学习/深度学习建模、遥感领域知识三条线,工作量展示充分,而且每一步都有清晰的验证指标。更实际的好处是,公开数据集足够多,不需要自己采集影像;代码写完后用分类图和混淆矩阵就能直观呈现结果。本文将沿着数据读取、降维、分类器选型、评估与调优这条完整链路,给出可以直接复用的实现方案和参数选择思路。2. 先理顺数据:高光谱影像的读取、预处理与标注2.1 读取 ENVI 格式高光谱影像的两种 Python 姿势高光谱遥感数据最常见的分发格式是 ENVI 的.hdr.dat组合,或者直接是多波段 GeoTIFF。第一次接触时最容易困惑:.dat文件没有后缀名提示,打开全是乱码,因为它是按波段顺序存储的原始二进制。读取这类数据,主流的 Python 库有两个:spectral:专门为高光谱设计,自带envi.open()接口,还内置了 Indian Pines、Salinas 等经典数据集的加载函数;rasterio:通用遥感栅格库,处理 GeoTIFF 更方便,也能读 ENVI 格式。个人经验是:如果只需要取数据做分类,spectral更顺手;如果后续还要做地理配准、坐标写入或裁剪,rasterio更合适。下面给出两种读取方式:# 方式一:使用 spectral 库读取 ENVI 格式 from spectral.io import envi img envi.open(indian_pines.hdr) # 自动关联同目录下的 .dat 文件 data img.load() # 方式二:使用 rasterio 读取 GeoTIFF 或多波段栅格 import rasterio import numpy as np with rasterio.open(paviaU.tif) as src: data src.read() # 形状: (波段数, 行, 列) meta src.profile # 保留元数据,用于后续写出分类结果 data np.transpose(data, (1, 2, 0)) # 转为 (行, 列, 波段数)需要特别留意spectral加载后返回的是MsrcFile对象,直接print只能看到形状信息,必须先转换为 NumPy 数组才能进行后续的切片和计算。而rasterio读取出的形状是(波段数, 行, 列),很多图像处理函数默认输入是(行, 列, 通道)或(样本数, 通道),所以转置这一步不能省。2.2 坏波段剔除与归一化:影响分类精度的第一道坎高光谱数据不是拿来就能喂给模型的。成像光谱仪在某些波段上受水汽吸收影响严重,信号基本是噪声;再加上传感器标定问题,部分波段的数值整体漂移。这些“坏波段”会直接拉低分类器的表现,而且很难通过后续的模型调优弥补。处理方式是先看每个波段的统计特征,再决定保留哪些。水汽吸收波段通常在 1400nm 和 1900nm 附近,可以先用人工经验剔除;更稳妥的做法是计算每个波段的方差,方差接近 0 说明该波段信息量极低,直接删除。# 计算每个波段的均值与方差,辅助坏波段剔除 band_means np.mean(data, axis(0, 1)) band_vars np.var(data, axis(0, 1)) # 找出方差过小的波段索引 low_var_bands np.where(band_vars np.percentile(band_vars, 2))[0] print(低方差波段:, low_var_bands) # 剔除坏波段,保留有效波段 valid_bands [b for b in range(data.shape[2]) if b not in low_var_bands] data_clean data[:, :, valid_bands]做完坏波段剔除后,还需要做归一化。高光谱各波段的辐射值量级差异很大,如果不归一化,SVM 等距离度量模型会偏向数值大的波段。推荐的做法是按波段做 Min-Max 缩放,保留每个波段内部的相对关系:from sklearn.preprocessing import MinMaxScaler # 将 3D 数据展平为二维: (像素数, 波段数) rows, cols, bands data_clean.shape data_2d data_clean.reshape(rows * cols, bands) # 按波段做归一化 scaler MinMaxScaler() data_norm scaler.fit_transform(data_2d) # 还原为 3D 结构,方便后面按像素取样本 data_norm data_norm.reshape(rows, cols, bands)MinMaxScaler的feature_range参数默认是 (0, 1),如果后续使用 ReLU 类激活函数,这个范围比较合适。若使用以 0 为中心的激活函数,比如 tanh,可以考虑把参数改为(-1, 1)。这是论文里不会写但实际调参时非常常见的操作。2.3 训练与测试集划分:空间维度上的留出法高光谱分类任务常用公开数据集做精度对比,公开数据集的信息对选题也很有价值:数据集传感器空间尺寸波段数类别数特点Indian PinesAVIRIS145x145200(去噪后)16样本不均衡严重,经典基准Pavia UniversityROSIS610x3401039空间分辨率高,城市场景Salinas ValleyAVIRIS512x21720416农业场景,类别边界较规整数据划分方式会直接影响最终精度的可信度。很多初学者直接用train_test_split对全体像素随机划分,这在高光谱任务里有一个明显问题:同一地物区域内的相邻像素高度相似,随机划分会导致训练集和测试集中出现近似重复的像素点,测得的高精度有虚高嫌疑。更严格的做法是按空间区域划分,即先把影像分成若干不重叠的图像块,再按块划分训练与测试。实际做毕业设计时,如果追求效率可以先用随机划分作为“开发集”调试代码,最终报精度时建议改成按块划分并说明。按块划分的代码不复杂,可以借助 scikit-learn 的BlockShuffleSplit实现,或者自己写一个滑动窗口切块函数,按块索引采样。测试集类别分布与训练集差距过大时,后续的混淆矩阵分析会非常痛苦,这一点要在数据准备阶段就处理好。3. 降维与特征提取:PCA 和波段选择谁更适合高光谱3.1 Hughes 现象:为什么波段越多分类越差高光谱数据动辄上百个波段,但这不代表分类器能利用全部信息。随着特征维度增加,需要的训练样本数量呈指数增长;在样本量固定的情况下,分类精度会先升后降,这就是 Hughes 现象。Indian Pines 数据集只有 10249 个标注像素,却要处理 200 维的光谱特征,特征维度与样本量的矛盾非常突出。因此,降维不是“锦上添花”,而是高光谱分类流程里不可跳过的一步。降维在主流做法里分两类:特征提取和波段选择。特征提取通过线性或非线性变换将原始波段映射到新特征空间,典型代表是 PCA;波段选择则从原始波段中挑出信息量最大的子集,保留物理含义。两者在高光谱任务里都有大量应用,但适用场景不同。3.2 用 PCA 对光谱曲线做压缩的完整流程PCA 在高光谱任务里是最常用的线性降维方法,核心思想是找到方差最大的正交方向,把原始波段映射为一组互不相关的主成分。实际操作时需要注意一个维度陷阱:sklearn.decomposition.PCA默认输入是(样本数, 特征数),所以需要先把高光谱立方体还原成(像素数, 波段数)的形式。from sklearn.decomposition import PCA import numpy as np # data_norm 形状: (rows, cols, bands),先转为二维 rows, cols, bands data_norm.shape pixels data_norm.reshape(rows * cols, bands) # 只对训练集中有标注的像素做 PCA 拟合,避免引入无标注像素的分布 labeled_mask (y_true ! 0).flatten() # 假设 0 为背景类 train_pixels pixels[labeled_mask] pca PCA(n_components30, whitenTrue) pca.fit(train_pixels) # 查看累计方差解释率,决定最终保留的主成分数量 cum_ratio np.cumsum(pca.explained_variance_ratio_) n_keep np.searchsorted(cum_ratio, 0.99) 1 print(f保留 99% 方差需要 {n_keep} 个主成分) # 对所有像素做变换 data_pca pca.transform(pixels) data_pca_3d data_pca.reshape(rows, cols, n_keep)n_components的选择比模型本身对精度的影响更大。常见的做法有两种:固定值(30 或 50)适合快速跑通代码;按累计方差解释率达到 0.99 动态选择主成分数,则更严谨。whitenTrue的作用是对主成分做归一化,让每个成分方差为 1,这能避免某些成分因方差过大主导距离计算。注意,whiten在后续用 CNN 训练时会更好收敛,但用 SVM 时是否开启对结果影响不显著,可以对比实验。3.3 波段选择:不改变物理含义的替代方案PCA 的问题在于投影后的主成分失去了物理含义,论文中解释类别特征时会比较困难。波段选择保留原始波段的物理意义,更契合遥感领域的地物光谱分析习惯。最简单的方案是基于方差的波段选择:保留方差最大的 K 个波段,因为方差反映了该波段在不同地物间的区分度。更精细的做法是考虑波段之间的相关性,避免选出一堆高度冗余的相邻波段。对比维度PCA 特征提取波段选择维度压缩能力强,几十维可解释大部分方差弱,通常需要保留较多波段物理可解释性低,主成分无物理含义高,保留原始波段计算开销拟合耗时 O(n^3) 量级低,只需统计计算适用模型CNN、SVM 均适用更适合 SVM、随机森林对噪声的敏感性对坏波段敏感,需先预处理可以通过选波段规避噪声实现波段选择比 PCA 更轻量,适合作为对比实验的一部分:# 基于方差 相关性约束的波段选择 from scipy.stats import pearsonr def select_bands(data_2d, k30, corr_threshold0.9): variances np.var(data_2d, axis0) selected [] # 按方差从大到小依次尝试加入 for idx in np.argsort(variances)[::-1]: if len(selected) k: break # 与已选波段的相关性不能超过阈值 if all(pearsonr(data_2d[:, idx], data_2d[:, s]).statistic corr_threshold for s in selected): selected.append(idx) return selected selected_bands select_bands(pixels[labeled_mask], k30) data_sel pixels[:, selected_bands]选择和调参的经验:如果你最终分类器选 SVM,波段选择往往比 PCA 效果更稳定,因为 SVM 在高维稀疏特征上本身就比较鲁棒,保留原始波段能让核函数的距离计算更可解释;如果你最终走深度学习路线,建议用 PCA 或后续的 1x1 卷积做特征压缩,效果比手工选波段好,PCA 的强压缩能力对收敛速度帮助明显。4. 分类器落地:SVM 与 CNN 在高光谱数据上的取舍4.1 SVM 是默认起点:小样本下的稳定表现高光谱分类领域,SVM 是绕不开的基线方法。它的优势在少量训练样本下非常突出:高光谱标注成本高,每个类别往往只有几十个到几百个像素,而 SVM 基于结构风险最小化,在小样本下不容易过拟合。加上高光谱数据在原始波段空间通常非线性可分,采用 RBF 核能映射到高维空间处理非线性边界。用 scikit-learn 实现 SVM 分类的代码非常直接:from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler # 准备训练数据:取有标注的像素 train_data data_pca_3d[labeled_mask] # 使用 PCA 后的特征 train_labels y_true[labeled_mask] # 高光谱数据 PCA 后仍建议做标准化,消除量纲差异 scaler StandardScaler() train_data_scaled scaler.fit_transform(train_data) # RBF 核 SVM,C 和 gamma 先给一个经验值 svm SVC(kernelrbf, C100, gamma0.1, class_weightbalanced) svm.fit(train_data_scaled, train_labels) # 全图预测并重塑 test_data data_pca_3d.reshape(-1, data_pca_3d.shape[2]) pred svm.predict(scaler.transform(test_data)) pred_map pred.reshape(rows, cols)两个参数直接影响精度:C是误分类惩罚系数,C 越大对训练集拟合越严格,但也更容易过拟合;gamma是 RBF 核的宽度参数,gamma 越小决策边界越平滑,gamma 越大模型越复杂。对高光谱数据,C在 10-200 之间、gamma在 0.01-0.5 之间是需要重点搜索的范围。class_weightbalanced对 Indian Pines 这种类别极不均衡的数据集几乎是必须的,否则多数类会主导训练,少数类精度惨不忍睹。4.2 一维卷积网络:把光谱曲线当作序列数据SVM 不够的地方在于它只能处理“一个像素一条输入”,完全忽略像素周围空间上下文。高光谱影像相邻像素属于同一地物的概率极高,空间信息对分类有很大帮助。另一方面,SVM 做全图预测时,几十万像素逐个推理耗时明显,这在课程设计的答辩演示上会有些尴尬。想同时引入空间上下文并提升预测效率,CNN 是更合适的选择。高光谱的 CNN 有两条技术路线:用 1D-CNN 对每个像素的光谱曲线做卷积,或者用 3D-CNN 同时处理空间与光谱维度。3D-CNN 效果更好但参数量大,在只有几千训练样本的毕业设计场景下容易过拟合。更稳妥的折中是 1D-CNN 提取光谱特征,再接一个简单的空间平滑后处理。下面给出一个结构精简的 1D-CNN:import torch import torch.nn as nn class SpectralCNN(nn.Module): def __init__(self, n_bands30, n_classes16): super().__init__() self.features nn.Sequential( # 1D 卷积,沿光谱维度提取局部特征 nn.Conv1d(1, 16, kernel_size5, padding2), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.Conv1d(16, 32, kernel_size3, padding1), nn.BatchNorm1d(32), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool1d(1) ) self.classifier nn.Linear(32, n_classes) def forward(self, x): # 输入形状: (batch, n_bands) x x.unsqueeze(1) # (batch, 1, n_bands) feat self.features(x).squeeze(-1) return self.classifier(feat)训练时重点注意两点。第一,kernel_size5适合光谱维度 30 左右的输入,卷积核过大容易跨多个不相关波段,过小则提取不到平滑特征;输入波段数较多时(如 100 以上),堆叠两层 3x3 卷积比单层大卷积核效果更好。第二,AdaptiveAvgPool1d(1)的作用是把任意长度的序列压成固定长度,这比手动计算池化尺寸更省心,尤其当主成分数动态变化时。训练轮次设置在 50-100 之间,配合 Adam 优化器,学习率从 0.001 开始,loss 下降变平后手动降为 0.0001。4.3 混淆矩阵、分类精度和分类图输出精度评估阶段,毕业设计里极少只报一个 overall accuracy。高光谱遥感领域更看重的是一致性系数 Kappa 和各类别的分类精度,混淆矩阵是必须展示的材料。同时,分类结果图的可视化往往决定答辩印象分。from sklearn.metrics import confusion_matrix, accuracy_score, cohen_kappa_score, classification_report import matplotlib.pyplot as plt import seaborn as sns cm confusion_matrix(train_labels, svm_pred_train) kappa cohen_kappa_score(test_labels, svm_pred_test) oa accuracy_score(test_labels, svm_pred_test) print(fOverall Accuracy: {oa:.4f}) print(fKappa: {kappa:.4f}) print(classification_report(test_labels, svm_pred_test)) # 画出混淆矩阵 plt.figure(figsize(10, 8)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues) plt.xlabel(Predicted) plt.ylabel(True) plt.show() # 输出分类结果图(背景像素设为黑色) plt.figure(figsize(8, 8)) plt.imshow(pred_map, cmapjet) plt.axis(off) plt.savefig(classification_map.png, dpi300, bbox_inchestight)分类图输出之后,可以叠加一个简单的中值滤波来消除“椒盐”噪声——那些在均匀地物区域里零星出现的错误像素点。这个操作在遥感分类论文里几乎都能看到:用scipy.ndimage.median_filter对预测图做 3x3 滤波,能把孤立误分类像素降下来,视觉效果好很多。但要向导师说明,本质是后处理平滑,原始逐像素精度数据仍是论文中引用的值,否则会有虚报精度的嫌疑。5. 把精度和代码一起打磨的实战技巧5.1 用分层 K 折交叉验证评估真实精度单次划分训练集和测试集得到的结果受随机因素影响太大,尤其 Indian Pines 这类样本不均衡的数据集,换一次随机种子精度可以波动好几个百分点。因此,到了调优阶段,把评估标准从train_test_split切换为分层 K 折交叉验证更科学。高光谱数据量大,跑 10 折会非常慢,5 折是时间与稳定性的折中选择。from sklearn.model_selection import StratifiedKFold skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) oa_scores [] for fold, (train_idx, test_idx) in enumerate(skf(train_data, train_labels)): svm.fit(train_data[train_idx], train_labels[train_idx]) acc accuracy_score(train_labels[test_idx], svm.predict(train_data[test_idx])) oa_scores.append(acc) print(fFold {fold1}: OA {acc:.4f}) print(f5-Fold Mean OA: {np.mean(oa_scores):.4f} ± {np.std(oa_scores):.4f})交叉验证的结果能真实反映模型稳定性,论文里写“平均精度 ± 标准差”比只写单次精度更有说服力。5.2 几个提升精度的细节第一个细节是每个类别的训练样本数量。Indian Pines 中部分类别标注像素不足 50,这类别单独报告精度没有统计意义。常见做法是在数据分析阶段就把样本过少的类别剔除,或者做数据增强——对光谱曲线加入少量高斯噪声,生成多个变体,让 CNN 有足够多样化的训练数据。第二个细节是 PCA 主成分数量对最终精度的影响。很多人习惯固定n_components30,但在实验阶段可以从小到大扫描 15、30、50、80,画出“主成分数-精度”曲线,能直观看到是否存在过拟合拐点,这个图放论文里也很加分。第三个容易被忽视的坑是标准化范围不一致:如果 SVM 的StandardScaler只在训练集像素上拟合,预测时直接用于全图数据,理论上没问题;但如果先在全图上做了 Min-Max 再划分数据集,测试集的信息已经泄漏到训练过程中,测出的精度会偏高。严格的做法是先用训练集拟合 scaler,再应用到测试集。5.3 源码组织与文档写法答辩提交时的源码结构用一份简洁的 README 说清楚目录就够了。个人推荐按功能拆分:data(原始数据与预处理脚本)、features(PCA/波段选择)、models(SVM/CNN 定义)、evaluation(混淆矩阵与图表生成)、main.py(运行入口)。main.py里用 argparse 接收--model、--n_components、--device几个必要参数,方便复现不同实验设置。项目文档里的环境依赖部分,建议明确写清 Python 版本(3.9 或 3.10)、PyTorch 版本、scikit-learn 版本,先让环境能复现,再讲方法细节。GitHub 上同步维护时,.gitignore里千万要把*.dat、*.hdr这类大文件加进去,多波段数据动辄几百 MB,直接推仓库既不合适也容易超限。把这些细节做完,整个课题从“跑通代码”到“能复现、好展示”就齐全了。本文还有配套的精品资源点击获取
返回列表