☰
高光谱图像分类实战:从特征降维到深度模型的调参与避坑指南
2026/10/17 20:00:06 网站建设 项目流程

写这个系列的时候,我把上一篇的篇幅都花在了预处理和数据准备上。到了这一篇,也就是标题里的“分类 II”,终于到了真正出结果的部分:给定一张高光谱影像和若干带标注的样本,怎么把每个像元可靠地归到正确的类别。高光谱图像分析里的“分类”,从来不是套一个分类器那么简单——波段几百个、标注样本稀少、空间相关性又极强,每一步选择都会直接影响最终那张分类图的可用性。这篇不打算罗列所有算法,而是顺着我实际跑实验的顺序走一遍:先谈任务定位和评价指标,再聊降维和波段筛选,然后分别讲传统分类器和深度模型的调参心得,最后附上一段能直接改着用的代码骨架和踩坑记录。适合刚开始接触高光谱图像、准备做地物精细分类的同学,也适合想从传统方法切到深度学习时少走弯路的基本功扎实的工程师。

1. 高光谱分类的任务定位与方案选择

1.1 高光谱图像究竟“高”在哪里

普通相机只能看到三个波段,也就是 R、G、B;多光谱卫星多到十几个波段;而高光谱成像光谱仪一次性采集几十甚至上百个连续窄波段,覆盖可见光到短波红外的范围,光谱间隔常常只有几个纳米。你可以把每个像元看成一个迷你光谱仪,记录一条连续的光谱曲线——这就是常说的“图谱合一”。

这个特性带来的直接好处是:很多在常规影像下无法区分的物质,在高光谱下可以被分出来。举个例子,同样是绿色,天然草坪、塑胶运动场、树林冠层在 RGB 影像里看起来差不多,但反射光谱曲线的形状差异很明显。矿物的种类识别、城市地物的精细分类、植被的生化参数反演,靠的都是这种“超细光谱分辨率”。

但高光谱分类的困难也源于此。波段数一多,数据体积大、波段相关性高,还伴随混合像元问题。一个尺寸不大的影像,拉平之后往往就是十万级别的样本量,每个样本带两百多维特征。如果直接暴力套分类器,结果几乎必然是一张噪声密布的分类图。所以真正可用的方案必须在降低维度、提取特征、选择模型、空间后处理这几个环节之间做好平衡。

1.2 分类流程全景:从图像到分类图的关键节点

我习惯把高光谱分类流程拆成五个环节:预处理、特征构建、分类器、后处理、精度评价。预处理包括辐射定标、大气校正、几何校正和坏波段剔除,这块上一篇已经聊过;特征构建可以分两部分,一是光谱特征降维,二是在空间上引入纹理或邻域信息;分类器则从简单基线做到深度模型;后处理专门对付逐像元结果里的椒盐噪声;精度评价负责给出可信的数字,而不是一句“效果不错”。

这五个环节里,分类器通常最吸引眼球,但说实话,整个项目的成败往往不取决于模型多新,而取决于前两步和最后一步。很多人拿着某公开数据集,随机抽样做训练测试,模型精度一报就是九十八九,任务换一个场景立刻崩掉,根因十有八九在数据划分,而不是模型本身。所以我在调模型之前,一定先把评价协议定死——用什么指标、用哪块地做验证、会不会有空间泄漏,这些说清楚了,后面才谈得上对比实验。

1.3 精度指标怎么选:OA、AA、Kappa 到底在说什么

评估高光谱分类结果,最常被提到的三个指标是总体精度(OA)、平均精度(AA)和 Kappa 系数,但被误用的场景也不少。

总体精度直观:预测正确的像元数除以全部测试像元数。问题在于如果某个类别占了测试集的大头,少数类别即使全部分错,OA 可能仍然很高。平均精度则先把每个类别的精度单独算出来再取均值,对少数类更友好。Kappa 再进一步,它和“随机猜”做对比,取值通常在 0 到 1 之间,超过 0.8 算不错。

三个指标各有盲区,我一般建议同时输出混淆矩阵,针对每个类看生产精度和用户精度。比如做城市地物分类时,“阴影”类别经常被错分成水体,OA 和 Kappa 可能看着都正常,只有看混淆矩阵才发现问题。具体取舍可以参考这个表:

指标计算思路优点常见盲区
OA正确预测数 / 总测试数直观,便于对比多数类主导时掩盖少数类错误
AA各类精度加起来取平均照顾少数类对样本极小的类别波动大
Kappa扣除随机一致性的吻合度比 OA 更稳健类别分布极端时仍会失真
混淆矩阵逐类别统计真假对应关系能定位具体错分对需要逐类阅读,不直观

我的习惯是:实验报告里 OA、AA、Kappa、每类 F1 全给,附加混淆矩阵热力图。宁可数字看起来不漂亮,也一定要能解释清楚“错在哪里”。

2. 特征降维与波段筛选的实操要点

2.1 维数灾难:为什么波段多反而难分类

高光谱影像动辄一两百个波段,但标注样本常常只有几百到几千个。当特征维度接近甚至超过样本量时,算法会遇到 Hughes 现象:随着波段数增加,分类精度先上升,然后反而下降。这背后的道理不复杂,统计学上叫维数灾难。

我经常用一个生活化的类比来解释。想象你在一个房间里找一个开关,低维情况下,你身边总有几个熟悉的位置可以参考;但维度一旦升高,空间被稀释,邻居都离得特别远,“就近参考”就失效了。对很多基于距离、近邻思想的方法,高维空间里的距离度量几乎失去区分能力。模型还容易在训练集上把噪声当成规律,泛化自然变差。

所以降维不是可选项,而是必要前置。哪怕有些算法号称能处理高维数据,也不代表它们在高光谱小样本场景下不出问题。

2.2 PCA 与 MNF:主流降维方法的选择逻辑

主成分分析(PCA)是高光谱最常用的降维手段。它不做任何监督信息的参与,只找数据里方差最大的方向。高光谱相邻波段相关性极高,这反而成全了 PCA:大部分信息会被压缩到前面少数几个主成分里。我实践中的经验,前 10~30 个主成分通常就能保留 95% 以上的方差,个别数据集甚至前 5 个就能到 90%。

具体保留多少,我的做法不是死盯某个固定数字,而是看累计贡献率曲线找拐点:先保留 95% 的方差,再试 98%,拿分类精度去验证。注意一点,保留的维度不是越多越好,多到一定阶段,后面进来的主要是噪声。用分类精度敲定最终维度,比纯看百分数更靠谱。

MNF(最小噪声分离)是另一条路。它先估计噪声协方差,对数据做白化后,再按信噪比排序。思路和 PCA 类似,但它会把噪声优先排在后面,结果往往更干净,尤其适合传感器噪声明显的数据。代价是要先做噪声估计,通常用邻域差分或者局部标准差来近似。这一步带了不少主观设定,不同估法出来的结果差异也大。

我的选择逻辑是:默认先用 PCA 跑通全流程;如果发现某些波段噪声明显拖低精度,再切到 MNF 对比一次。大部分入门场景,PCA 已经足够支撑后续实验。

方法核心原理适合场景常见注意点
PCA按方差大小排序成分大多数高光谱数据需要标准化后再降维
MNF按信噪比排序成分噪声明显的传感器数据噪声估计策略影响结果

2.3 波段选择:另一种保留物理意义的路径

降维还有一种完全不同的思路:直接在原始波段里挑出一部分有代表性的波段,保留波段本身的物理含义。这在做机理分析或者上嵌入式设备时很实用,因为选出来的波段可以直接对应到某个地物的光谱特征。

我常用的波段筛选策略分几步走。第一步,剔除坏波段,水汽吸收带附近信噪比差的波段早期就删掉,这是很多数据的通病。第二步,算每个波段的方差,方差太小说明全区几乎没什么变化,区分能力有限。第三步,看相邻波段的相关系数,如果一连串波段彼此高度相关,里面信息冗余,留下少数几个代表即可。更严格一点,可以用类别可分性指标来选,比如 Jeffries-Matusita 距离,或者算波段与标签的互信息。

波段选择的分类精度不一定比 PCA 高,但可解释性好得多。而且现在主流高光谱影像的波段数动辄两百,选到 20~40 个波段之后,下游模型输入大幅减小,训练和推理都快不少。实操中,我倾向于把波段选择和 PCA 各跑一遍,比较结果后选择更可控的那个。

3. 传统分类器调试实录:SAM、SVM 与随机森林

3.1 光谱角匹配:五分钟的快速基线

光谱角匹配(SAM)的原理非常直观:把像元光谱向量和参考光谱向量都归一化,然后计算两个向量之间的夹角,夹角越小说明越相似。它对乘性光照变化不太敏感,因为归一化把整体亮度差异消除了。

做基线实验时,SAM 特别合适。如果你手里已经有端元光谱,或者能直接标注出几个类别对应的纯像元光谱,SAM 可以在不训练的情况下快速给出一张分类图,帮你确认数据整体上“能不能分”。从经验看,如果 SAM 的结果就乱成一团,说明要么标注光谱不可靠,要么预处理还没做好,这时候先回去查数据,比继续堆模型更有价值。

但 SAM 的局限也很明确:它完全忽略噪声协方差,对混合像元无能为力,也不输出类别概率。所以它只能当快速体检工具,很难当最终方案。真要做精度有保障的分类,还得看 SVM 这些带训练阶段的方法。

3.2 SVM:高光谱分类绕不开的经典选项

支持向量机在高光谱小样本高维场景下表现出了很强的泛化能力,尤其是配合 RBF(径向基函数)核,把原始光谱映射到高维后再找分类超平面,线性不可分的类别也往往能切开。

RBF 核有两个关键参数,惩罚系数 C 和核宽度 gamma。C 太大容易过拟合,C 太小容易欠拟合;gamma 控制单一样本的影响范围,太大了决策边界会碎成一块块。我的调参习惯是,先用默认参数跑通流程,再做网格搜索,搜索网格一般取:

C: [1, 10, 100] gamma: [0.001, 0.01, 0.1, 1]

配合 5 折交叉验证,找到当前数据上最稳的参数。注意,SVM 对特征尺度很敏感,PCA 之前先做标准化是必须的,否则暗目标、亮目标的整体强度差异会压过光谱形状差异,分类结果完全跑偏。

还有一个容易忽略的细节:类别不平衡时,给 SVC 设置class_weight='balanced'往往比手动过采样省事,而且稳定性不错。另外,高光谱影像逐像元推理时,SVM 的决策函数在百万像元级别会偏慢,如果工程上要快,可以退半步用线性核或者借助近似方法,精度略有损失但速度提升明显。

3.3 随机森林:稳健但容易吃内存

随机森林也是高光谱分类里的常客。它一次对特征做随机子集抽样,构造多棵决策树再投票,对高维数据有天然的容忍度,一定程度上不需要严格降维。更重要的是,它能直接输出特征重要度,画个柱状图就能看出哪些波段贡献大,这个信息可以反哺波段选择。

参数上我一般先把n_estimators设到 500,再观察精度是否收敛;max_features取特征数的平方根是常见做法;min_samples_leaf留 5~10 个样本,能避免树过深导致过拟合。高光谱类别多且不均衡时,设置class_weight='balanced'也能明显改善少数类的表现。

随机森林最大的问题是资源占用。要是把一个超大影像全部拉平成百万行、两百列的矩阵喂进去,内存很容易爆。我踩过这个坑之后,不再一次性全量加载,而是分层分块读取,数据类型尽量用 float32,必要的时候先用 PCA 压到几十维,再进随机森林。

三个传统方法放在一起看,SAM 适合快速验证、SVM 适合追求精度的中小样本、随机森林适合快速尝试无需精细调参的场景。在实际项目中,我会把 SAM 当基线,SVM 当默认主力,随机森林用来交叉验证结论。

4. 深度学习分类:结构选型与小样本训练

4.1 从一维卷积还是三维卷积入手

深度学习在高光谱分类里的思路主要分成三类:一维卷积、二维卷积、三维卷积。

一维卷积直接把每个像元的光谱曲线当输入,卷积核沿波段方向滑动。它只在光谱维度上做特征提取,参数少,最适合标注样本特别有限的情况。二维卷积则把像元周围取出的邻域块当成“多通道图像”输入,常见的做法是先做 PCA 降到 10~30 维,再按 patch 输入网络,这样能利用空间上下文。三维卷积更进一步,把空间和光谱同时卷起来,理论上更贴合“图谱一体”的数据特性,但参数量大,小样本下特别容易过拟合。

我的建议很直接:样本少的项目,先跑 1D-CNN,把流程所有环节跑通,再去试 2D/3D。别一上来就堆一个大网络,训练时间和调试成本会迅速吞掉你的耐心。等确认数据划分、预处理、后处理都稳了,精度瓶颈确实在特征表达上,再升级到空谱联合模型,收益才大。

4.2 Patch 大小与空间上下文怎么权衡

用空间信息的核心问题是:邻域开多大。常见的 patch 尺寸有 7×7、9×9、11×11、15×15。patch 太小,空间上下文不足,结果和逐像元差不多,还是到处是椒盐点;patch 太大,边缘处不同类别的像元被搅在一起,网络会更难学,训练也变慢。

我一般从 7×7 开始,结合影像的空间分辨率做判断。分辨率越高,地物细节越丰富,patch 可以稍微大一点;纯像元级别的分类任务,patch 开到 5×5 即可。取 patch 的时候还要注意边缘补齐策略,我常用反射填充而不是零填充,因为零填充会在边缘引入一条不真实的光谱边界,被模型当成特征学走。

另外提醒一句:patch-based 方法在训练测试划分上更容易“作弊”,因为相邻 patch 重叠区域极大。如果随机把像素分到训练测试两边,模型其实已经见过了邻居信息,精度自然虚高。所以这类方法更吃训练区域和测试区域的划分协议,后面第五节再展开。

4.3 小样本训练的四条保命技巧

高光谱标注成本高,小样本是常态。我总结了几条实在的技巧。

第一,数据增强。给光谱曲线加小幅高斯噪声,或者用相邻波段的插值生成虚拟样本;如果输入是 patch,还可以做随机翻转和旋转。增强强度不要太大,否则样本开始偏离真实分布,反而有害。

第二,正则化。Dropout 放在全连接层之前,权重衰减调小,比如 1e-4 左右。训练时设置早停,监控验证集 loss,连续多少个 epoch 不降就停止。

第三,学习率策略。我习惯用 Adam 类优化器,初始学习率从 1e-3 开始,配合热身和余弦退火。学习率是深度学习模型里最敏感的单点参数,宁可先保守,也不要一步把 loss 震飞。

第四,类别不均衡。直接用采样器把每类样本调成相似数量,或者在有监督训练时给少数类更高的损失权重。高光谱的小类别往往正是关键类别,比如稀有矿物的识别,这类错误影响很大。

如今的趋势还有自监督预训练,拿大块无标注数据做掩码重建或者对比学习,先让模型学会光谱结构,再在少量标注上微调分类头。这个路线对高光谱特别有吸引力,因为无标注数据随处都是,但也别指望它完全替代标注。

5. 可直接复用的实验骨架与结果评估

5.1 数据组织与预处理环节

代码跑起来之前,先把数据格式弄成一致的。我习惯的假设是:已经有一张预处理后的高光谱图像img,形状是(height, width, n_bands);对应的标签图label,形状是(height, width),背景类用 0 表示。这样可以直接把三维图像拉成二维矩阵。

import numpy as np from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA # 假设 img 和 label 已经加载,形状如下 # img: (h, w, n_bands), label: (h, w) h, w, n_bands = img.shape mask = label > 0 X = img[mask].astype(np.float32) y = label[mask].astype(np.int64) # 标准化很重要,SVM/PCA 对尺度敏感 scaler = StandardScaler().fit(X) X_scaled = scaler.transform(X) # 保留95%方差;也可以固定n_components=30 pca = PCA(n_components=0.95) X_pca = pca.fit_transform(X_scaled) print("原始波段数:", n_bands) print("PCA保留维度:", X_pca.shape[1])

这里的mask很重要,把背景像元剔掉,避免“0 类别”干扰训练。PCA 用方差比例指定维度,比硬编码一个数字更灵活。

5.2 训练 SVM 和网格搜索的代码骨架

接着用 sklearn 做网格搜索,找到当前数据上表现最稳的 SVM 参数。这里只写骨干,实际使用时可以根据数据规模调整搜索范围。

from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC param_grid = { "kernel": ["rbf"], "C": [1, 10, 100], "gamma": [0.01, 0.1, 1], } svm = SVC(class_weight="balanced", probability=False) grid = GridSearchCV( svm, param_grid, cv=5, scoring="accuracy", n_jobs=-1, verbose=1 ) grid.fit(X_train, y_train) print("best params:", grid.best_params_) y_pred = grid.predict(X_test)

这里最容易被忽略的是交叉验证的划分方式。默认cv=5是随机划分,但高光谱图像像元之间空间自相关很强,同一地块相邻像元光谱高度相似。真要严谨,训练测试应该按空间区域划分,而不是随机像素划分。最简单的做法是:把整幅图按行切成若干块,选前几块训练、后几块测试,或者做空间分块的交叉验证。这样得到的精度才更接近真实泛化能力。

5.3 后处理与精度统计的输出

逐像元分类完成之后,立刻可以看到一大片椒盐噪声。此时别急着改模型,先用中值滤波做后处理,常常能带来肉眼可见的提升。中值滤波的好处在于去噪同时保留边缘,对“类别孤立小点”非常有效。

from scipy.ndimage import median_filter from sklearn.metrics import accuracy_score, cohen_kappa_score, confusion_matrix # 把预测结果填回整图 pred_map = np.zeros((h, w), dtype=np.int64) pred_map[mask] = y_pred # 中值滤波,窗口别太大,5x5 够用 smooth_map = median_filter(pred_map, size=5, mode="nearest") # 评估时只用有标注的像元 y_smooth = smooth_map[mask] oa = accuracy_score(y_test, y_smooth_test) kappa = cohen_kappa_score(y_test, y_smooth_test) cm = confusion_matrix(y_test, y_smooth_test) print("OA:", round(oa, 4)) print("Kappa:", round(kappa, 4)) print("Confusion Matrix:") print(cm)

画分类图的时候,我习惯把背景设成白色或黑色,各类别用一套固定配色,保证不同实验结果之间可以直接对比。分类图的平滑、空间分布、边缘形态,有时候比几个数字更说明问题。

6. 高频问题排查与个人避坑清单

6.1 分类图变成“椒盐画”怎么办

逐像元预测的结果出现大量零散错分点,是最常见的高光谱分类现象。原因在于模型没利用空间一致性,相邻像元被分到完全不同的类别。解决思路有两个方向:一是后处理,用中值滤波、多数投票或者超像素约束;二是前置,在模型结构里引入空间邻域信息,比如直接用 patch 输入。

我的经验是,先花五分钟做后处理,不要立刻改模型。中值滤波窗口从 3×3 开始,最多 5×5,窗口再大会把细碎地物本身磨平。超像素约束效果更好,但依赖分割参数,调起来更费时间。

6.2 训练曲线震荡不收敛怎么回事

深度学习训练时 loss 下不去或者震荡,按下面的顺序检查。首先看数据有没有标准化,高光谱不同波段成像能量差异很大,不归一化模型会很难学。其次看学习率,Adam 初始学习率 1e-3 已经是比较高,如果震荡就降到 1e-4。最后看 batch size,太小时梯度噪声大,训练曲线就抖。

还有一个隐蔽的问题:标签编号不连续。比如类别编号是 1、2、4,少了 3,交叉熵损失会把 3 也当成一个类别来算,模型输出层维度设错了,训练必然异常。写数据管线时留意这点。

6.3 精度虚高:数据划分是最大的坑

高光谱分类报告出来的精度,最容易被人质疑的就是数据划分方式。随机把像素分到训练和测试,测试像元往往和训练像元来自同一片农地或者同一条道路,光谱高度相似,模型等于开卷考试,精度再高也没有说服力。

解决方法是分块划分。最简单的版本是把图像按空间位置切成几大块,比如上下四块或左右四块,用不相邻的块分别做训练和测试。更严格的做法是拿另一景影像或另一个时间的数据做外部验证。我现在每次实验前先写划分代码,把这个协议固定下来,后面怎么调模型都不动它。

6.4 内存爆炸和推理太慢的处理

高光谱影像大一点,全像元矩阵很容易把内存撑爆。我的处理方案有三条:一是全部用 float32,不要留下 float64 的默认值;二是读数据时按行块或列块处理,不要让整景影像同时存在于内存里;三是降维之后再做后续计算,几百维的矩阵规模能缩小几个数量级。

推理速度方面,深度学习模型用 GPU 自然快;传统模型如果跑全图慢,可以先对类别有代表性的像元做推理,再配后处理补全,或者按块并行。工程上千万不要迷信单机的全部一次性计算,分块处理是标准答案。

小样本下类别消失也是个容易踩的坑,某一类标注太少,训练时被模型直接忽略。我的做法是先合并语义相近的类别,或者干脆把样本量极少的类别从实验评估中剔除,在报告里注明即可。强行保留一个小样本类别,只会让训练过程不稳定、指标一团糟,对结论也没有帮助。

如果只让我挑一条最有价值的建议,那就是:先把数据划分协议定死,再去调模型。很多“精度暴涨”和“复现失败”,本质上都是同一句话——测试集和训练集没分开。最后分享一个小技巧:后处理中值滤波的窗口不要贪大,3×3 到 5×5 足够;太大的窗口会把河边细碎耕地、城市小屋顶这类关键细节直接磨掉。希望这篇高光谱图像分析“分类 II”的经验帖,能帮你少踩几个我踩过的坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询