2PCA与双通道CNN-SVM)
简介这是一份基于Python实现的高光谱遥感影像识别与分类完整项目面向毕业设计、课程设计及项目开发场景。项目针对高光谱数据维数灾难导致的休斯现象提出基于波段组合(2D)2PCA的高光谱降维方法降低数据冗余并提升后续处理效率同时构建双通道卷积神经网络空谱特征提取模型并融合SVM分类器形成CNN-SVM识别模型兼顾特征提取能力与泛化性能有效提升分类精度。资源共24个文件含13个Python源码文件、2个MAT数据文件、PNG示意图、pyc编译文件、Ubuntu环境配置及md文档与txt说明压缩包大小约6.01MB目录含data、model、logs等模块方便按流程复现实验。项目源码经过严格测试并附带项目文档与使用教程可帮助读者快速理解算法原理、运行环境搭建及模型调优思路。目前已有199人浏览学习适合需要完整案例参考或在此基础上拓展研究的Python与遥感方向学习者。1. 高光谱遥感影像分类的三大障碍与HSI-SVM项目设计思路第一次拿Indian Pines跑高光谱分类八成你会在休斯现象上翻车两百个波段挤在145×145像素的小图上标记样本少则几十个分类精度不升反降。基于Python实现的HSI-SVM项目就是针对这个痛点来的用(2D)2PCA做波段组合降维用双通道CNN同时提取光谱和空间特征再把特征交给SVM做最终分类。源码按Part_1到Part_4组织覆盖预处理、降维、训练和评估README里有完整教程。毕业设计、课程设计可以拿它当基线项目开发里这种CNN特征SVM融合的思路对少样本高维数据也很有参考价值。下面我按数据准备、降维、双通道CNN、CNN-SVM融合和复现细节这条线展开。2. Indian Pines数据集与(2D)2PCA降维从光谱维数灾难到波段组合2.1 Indian Pines数据格式与加载方式Indian Pines是AVIRIS传感器采集的高光谱影像尺寸145×145原始波段224个去掉噪声和吸水波段后常用200个波段。每个像素是一条光谱曲线地面覆盖包括玉米、大豆、小麦、草地、树木共16类。由于扫描区域中部分像素没有标注实际分类实验只使用带标签的约1万多个像素。属性值影像尺寸145 × 145 像素原始波段数224常用波段数200去掉吸水/噪声波段类别数161~160为背景标记像素总数约10249项目源码里的data目录一般放两种格式一种是.mat文件如Indian_pines_corrected.mat另一种已转成numpy的.npy或CSV。加载.mat时要注意文件版本MATLAB R7.3的HDF5格式需要h5py旧版本用scipy.io.loadmat。import h5py import numpy as np from scipy import io # 处理 MATLAB v7.3 格式 def load_hsi(path): with h5py.File(path, r) as f: img f[paviaU][:] # 实际键名以文件为准 return img # 处理传统 .mat 格式 data io.loadmat(Indian_pines_corrected.mat)[indian_pines_corrected]代码里我按常见命名写了paviaU和indian_pines_corrected两个键实际使用前要先打开mat文件看keys避免KeyError。加载后数据维度是(M,N,B)即行、列、波段。Indian Pines的标签矩阵在同目录单独的mat文件里形状也是145×145像素值为0~16其中0表示未标注。2.2 休斯现象为什么波段越多精度越低休斯现象Hughes Phenomenon的定义当训练样本数量固定时分类精度随特征维数先上升、到达临界点后开始下降。原因是高维空间中样本分布变得稀疏估计协方差矩阵需要的样本量随维度平方级增长而高光谱的标注样本本来就稀缺。直接对所有波段做分类SVM、KNN这些传统分类器都会遇到这个问题。降维不是简单丢掉波段而是要保留可分性。常见做法是PCA、MNF但PCA把光谱向量当成一维序列没有利用像素的二维空间结构。(2D)2PCA的思路是先把相邻波段组合成二维图像再对图像矩阵同时做行方向和列方向的投影这样降维后的特征既压缩了波段冗余又保留了空间纹理。对Indian Pines这种地物边界明显的影像这种预处理比全局PCA更稳。2.3 (2D)2PCA的数学表达与numpy实现设第i个训练样本的图像块为 $A_i \in R^{h \times w}$这里h、w是空间尺寸每个波段作为图像的一层。(2D)2PCA先构造行方向的总离散度矩阵$$G_r \frac{1}{n}\sum_{i1}^n (A_i - \bar{A})^T(A_i - \bar{A})$$求G_r的前d_r个最大特征值对应特征向量得到投影矩阵U接着把图像投影为$B_i A_i U$再对B_i做列方向投影矩阵V最终得到降维结果$C_i V^T A_i U$。下面是一个可直接运行的简化实现import numpy as np from numpy.linalg import eigh def twod2pca(gray_images, d_row, d_col): gray_images: list of 2D image matrices (h, w) d_row, d_col: 保留的行/列主成分数量 n len(gray_images) h, w gray_images[0].shape mean_img np.zeros((h, w)) for img in gray_images: mean_img img mean_img / n # 行方向协方差矩阵 G_r (w x w) G_r np.zeros((w, w)) for img in gray_images: diff img - mean_img G_r diff.T diff G_r / n eigvals_r, eigvecs_r eigh(G_r) idx_r np.argsort(eigvals_r)[::-1][:d_row] U eigvecs_r[:, idx_r] # 投影矩阵 # 第一次投影 Y [(img - mean_img) U for img in gray_images] # 列方向协方差矩阵 G_c (h x h) G_c np.zeros((h, h)) for proj in Y: G_c proj proj.T G_c / n eigvals_c, eigvecs_c eigh(G_c) idx_c np.argsort(eigvals_c)[::-1][:d_col] V eigvecs_c[:, idx_c] # 最终降维特征 Z [V.T proj for proj in Y] return Z, U, V实现里我做了两步投影先沿行方向压缩到d_row维再沿列方向压缩到d_col维。eigh返回的特征值升序所以用argsort()[::-1]逆序取前k个。特征矩阵U的列是标准正交基投影后保留了方差最大方向上的信息。这里要注意输入图像要先做归一化或者至少减均值否则投影方向会被光照强度主导。对于高光谱数据不会把200个波段一次性做成一个2D图像。项目里“波段组合”的做法是先选出信息最丰富的若干波段比如基于信息熵或PCA贡献率把选出的波段组合成多通道图像再对每个通道或对组合后的张量执行(2D)2PCA。常见做法是先对所有波段做一次快速PCA保留前3~10个主成分作为图像通道再在这个低通道图像上计算(2D)2PCA。这样既缓解了休斯现象计算量也可控。2.4 数据标准化与训练集划分降维之后特征值尺度可能差异很大。如果后面接CNN通常对每个波段做z-score标准化如果接SVM标准化甚至比降维更关键。标准化参数必须在训练集上计算不然测试集信息被泄漏。def standardize(train_feat, test_feat): mean train_feat.mean(axis0) std train_feat.std(axis0) train_norm (train_feat - mean) / (std 1e-6) test_norm (test_feat - mean) / (std 1e-6) return train_norm, test_norm训练集划分有两种主流策略随机采样像素划分和按连续地块划分。前者简单但同一块地相邻像素可能同时出现在训练和测试集里导致分数虚高后者更接近真实遥感分类场景但样本少时方差大。项目代码里一般用random seed控制的可复现随机抽样。如果是毕业论文建议两种都做并在论文里说明差距。3. 双通道CNN空谱特征提取光谱序列与空间邻域并行建模3.1 为什么需要双通道光谱与空间互补高光谱影像里同一种地物的光谱曲线形状有相似性但相邻像素的地物往往也相同这叫空间相关性。单通道CNN只能处理一种输入1D CNN把每个像素的光谱向量视为长度为B的序列能捕捉谱段之间的依赖关系但完全忽略邻域信息。2D CNN取像元周围的邻域块作为输入能提取纹理和边缘特征但把波段当通道不强调光谱曲线的连续性。双通道CNN让两个分支并行提取特征最后在某个层拼接。光谱分支输入形状是(B,)空间分支输入形状是(h_window, w_window, B)。这种设计在分类精度上比单分支高尤其对植被、建筑物这种纹理差异大的类别。3.2 双通道模型结构设计下面是我在HSI项目里常用的配置表分支层核/步长输出尺寸光谱Conv1D 13×1 / 164光谱Conv1D 23×1 / 1128光谱MaxPool264光谱Flatten-64×(B/2?) 实际计算空间Conv2D 13×3 / 132空间Conv2D 23×3 / 164空间MaxPool2×232空间Flatten-32×(h_pool×w_pool)融合Concat FC1-256分类FC2 Softmax-num_classes这个表是示意具体输出尺寸取决于输入窗口大小。一般空间邻域窗口取11×11或13×13太大包含过多异类像素太小空间上下文不足。3.3 PyTorch实现双通道CNNimport torch import torch.nn as nn class DualChannelCNN(nn.Module): def __init__(self, n_bands, window_size, num_classes): super().__init__() # 光谱分支输入 (B, 1, n_bands) self.spectral_branch nn.Sequential( nn.Conv1d(1, 32, kernel_size3, padding1), nn.BatchNorm1d(32), nn.ReLU(), nn.Conv1d(32, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(), nn.AdaptiveAvgPool1d(16) ) # 空间分支输入 (B, n_bands, window, window) self.spatial_branch nn.Sequential( nn.Conv2d(n_bands, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(), nn.AdaptiveAvgPool2d((4, 4)) ) self.fusion nn.Sequential( nn.Flatten(), nn.Linear(64*16 64*4*4, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x_spectral, x_spatial): f_s self.spectral_branch(x_spectral) f_p self.spatial_branch(x_spatial) feat torch.cat([f_s.flatten(1), f_p.flatten(1)], dim1) return self.fusion(feat)思路说明光谱分支用1D卷积处理归一化后的光谱曲线自适应池化固定输出长度空间分支直接用整个波段立方体作为多通道图像输入用2D卷积提取局部纹理。融合层把两个分支的特征向量拼接后送全连接。AdaptiveAvgPool的好处是输入窗口尺寸变化时不需要改全连接维度。实际训练时输入要扩展维度光谱分支需要reshape为(B,1,B)空间分支保持(B,Bands,H,W)。注意BatchNorm1d和BatchNorm2d在batch size较小时统计量不稳定。如果显存允许batch size尽量不低于32否则训练和推理时BN的均方差不一致会拉低精度。3.4 从分类网络到特征提取器CNN-SVM融合的关键是让CNN只做特征提取把最后一层Softmax去掉。在模型里定义extract_features方法返回全连接层之前的256维向量def extract_features(self, x_spectral, x_spatial): f_s self.spectral_branch(x_spectral) f_p self.spatial_branch(x_spatial) feat torch.cat([f_s.flatten(1), f_p.flatten(1)], dim1) return feat这样训练时用整个网络测试时先用该方法抽出特征再输入SVM。需要提醒的是spectral_branch和spatial_branch的batch norm在eval模式下的行为与训练不同所以提取特征前务必调用model.eval()否则特征分布不稳定。4. CNN-SVM融合分类让卷积特征适配最大间隔分类器4.1 为什么CNN后面还要接SVM深度学习分类头通常用Softmax交叉熵它学到的决策边界是光滑的但在小样本、特征重叠大的场景下Softmax边界不一定是最优的。SVM以最大化间隔为优化目标对高维稀疏特征有更强的鲁棒性。CNN负责非线性特征提取SVM负责分类决策两者天然互补。项目里“双通道CNN-SVM融合”的实现思路是先把双通道CNN在有标签样本上训练到收敛然后截断分类层把训练集和测试集都映射到256维特征空间最后用RBF核SVM作为分类器。这样既获得了CNN提取的抽象特征又用SVM避免了Softmax在小数据集上的过拟合。分类头决策边界小样本表现实现复杂度Softmax概率平滑容易过拟合特征直接替换最后一层RBF SVM最大间隔鲁棒性更好需要单独训练和调参4.2 训练与特征提取的标准流程具体流程分成五步加载降维后的Indian Pines数据构造光谱序列样本和空间邻域块样本。初始化双通道CNN用交叉熵损失训练记录最佳验证精度。加载最优权重调用extract_features提取全连接层前的特征向量。对特征做标准化用网格搜索或启发式方法确定SVM的C和gamma。在测试集上计算混淆矩阵、总体精度、平均精度和Kappa系数。第4步的SVM训练代码import numpy as np from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV from sklearn.preprocessing import StandardScaler def train_svm(features, labels): # 先标准化再进SVM避免大数值维度主导边界 scaler StandardScaler() X_scaled scaler.fit_transform(features) params { C: [0.1, 1, 10, 100], gamma: [scale, 0.01, 0.001] } grid GridSearchCV( SVC(kernelrbf, class_weightbalanced), params, cv3, scoringaccuracy, n_jobs-1 ) grid.fit(X_scaled, labels) return grid.best_estimator_, scalerC控制误分类惩罚越大越容易过拟合gamma控制RBF半径越小决策边界越平滑。class_weightbalanced在Indian Pines这类类别不平衡数据上很重要否则少数类如Oats、Grass-trees容易被吞。网格搜索的cv不要太大3折即可否则耗时很长。4.3 提升SVM精度的特征处理细节在训练SVM之前要考虑两个细节特征是否要归一化CNN输出特征虽然经过了BN但不同维度的数值范围仍有差异。SVM基于距离计算不归一化会导致变化大的维度主导边界。是否要降低SVM输入的维度256维对于几千样本的Indian Pines已经够用但如果特征维度过高可以先做PCA压缩到50维再喂SVM。项目里的(2D)2PCA已经做过一次降维所以这一步通常省略。下面是完整的评估代码顺便给出OA、AA、Kappa三个指标from sklearn.metrics import accuracy_score, cohen_kappa_score, confusion_matrix y_pred best_svm.predict(scaler.transform(test_features)) y_true test_labels oa accuracy_score(y_true, y_pred) aa np.mean([accuracy_score(y_true[y_truec], y_pred[y_truec]) for c in np.unique(y_true)]) kappa cohen_kappa_score(y_true, y_pred) cm confusion_matrix(y_true, y_pred)AA是每个类别精度的算术平均不考虑类别样本数OA直接看全局正确比例。Kappa排除了随机一致性的影响遥感论文里最看重它。打印混淆矩阵时把每类的召回率单独算出来可用于分析哪些类别被混淆。4.4 与纯CNN的对比实验设计要证明CNN-SVM融合有效需控制变量相同通道结构、相同训练数据下只替换分类头。常见做法是训练两个模型一个用Softmax分类一个把特征提取后接SVM。在Indian Pines测试集上融合模型的OA通常会比Softmax高一些但这种差距在大样本场景下会缩小。因此毕业论文里做这个对比时要固定随机种子多跑几次取均值。项目源码里的Part_3/model保存了训练好的权重Part_4/logs保存了每次运行日志可以用TensorBoard或matplotlib画训练曲线。如果复现时发现SVM结果不如Softmax优先检查数据划分是否泄漏、特征提取时是否忘记model.eval()、SVM参数搜索范围是否过窄。5. 复现实验参数与验证技巧划分策略、Kappa系数和三个常见坑5.1 运行环境与目录映射我复现这个项目时环境是Python 3.10、PyTorch 2.0、scikit-learn 1.3。如果从零开始先装好Python环境再按README安装依赖。注意data目录里原始的mat文件如果是HDF5格式scipy.io无法直接读取需要同步检查h5py是否安装。项目目录里Part_1负责数据预处理和可视化Part_2是(2D)2PCA降维Part_3是双通道CNN训练Part_4是CNN-SVM评估。运行顺序不要颠倒因为后面脚本需要读取前面脚本输出的特征。5.2 关键超参数设置与调整下面是几个直接影响最终精度的地方参数推荐范围说明空间窗口大小11×11 ~ 15×15太小缺上下文太大引入噪声降维后主成分数量3 ~ 10要结合分类精度折中CNN batch size64 ~ 128样本少时不宜过大学习率0.001 ~ 0.01Adam的话0.001起SVM的C1 ~ 100与CNN特征尺度有关SVM的gammascale或0.001用GridSearchCV搜索如果训练不收敛先检查批量归一化是否放在卷积之后、激活之前。如果分类结果里某一类全是零检查标签对齐Indian Pines的标签矩阵里某些类只有不到20个像素划分时要设置stratify参数保证每类都有训练样本。5.3 验证时最常见的三个坑第一个坑是数据泄漏直接对全部数据做归一化再划分会把测试集统计信息带进训练。正确做法是先拆分再在训练集上计算均值和标准差。第二个坑是Kappa系数计算错误cohen_kappa_score默认权重是线性权重而遥感论文里常用无加权Kappa保持默认参数即可但要在论文里写明。第三个坑是空间邻域块构建时越界窗口大小超过5×5时图像边缘像素没有完整邻域常见做法是零点填充或反射填充实际项目里多数用torch.nn.functional.pad实现反射填充。最后给出一个快速验证模型是否有效的命令在Part_4目录下运行python evaluate.py脚本会后台加载Part_3里保存的模型权重在测试集上输出分类报告并在logs目录生成混淆矩阵图片。如果跑出来的精度和README偏差过大优先检查数据集版本——不同来源的Indian Pines mat文件波段数和标签编号可能不同必要时按实际形状修改加载代码。我在复现时把窗口设为11降维保留5个主成分分类结果与真实地块形状基本吻合如果你换用PaviaU数据集记得先裁剪掉标签为0的背景边界再训练。本文还有配套的精品资源点击获取