拓冰建站拓冰建站
首页 / 资讯中心 / 正文

高光谱数据预处理全流程详解:从DN值到反射率与Python实现

简介在光谱分析与机器学习建模中数据质量往往决定模型上限。原始高光谱影像包含噪声、水汽吸收带与传感器误差若未经过系统预处理分类或回归任务极易失效。本文从最基础的DN值数字量化值出发解析辐射定标与反射率转换原理并详细覆盖Savitzky-Golay平滑、导数光谱、SNV与MSC散射校正及归一化等关键步骤。通过可运行的Python代码展示完整预处理管线如何将三维影像整理为高质量特征矩阵。无论是农业遥感、食品检测还是材料分析掌握这套流程都能有效提升模型稳定性与精度。文章兼顾理论与实践助力工程落地。 做高光谱分析的都知道一句话Garbage ingarbage out。很多人拿到一景高光谱影像第一件事就是直接跑分类、跑回归结果模型指标差得离谱回头到处找网络结构问题。其实问题往往不在模型而在你喂给模型的数据本身。高光谱数据预处理就是这个链条里最容易被低估、又最决定成败的一环。这篇内容围绕一份“高光谱数据预处理方法python代码”的代码包展开我会把一条完整的预处理管线拆开讲清楚坏波段怎么剔、DN值怎么转反射率、光谱曲线怎么平滑、导数变换什么时候用、SNV和MSC分别解决什么问题、最后怎么把三维高光谱影像整理成能直接训练模型的特征矩阵。文章里给出的代码不是玩具示例是能直接改路径跑通的程度。适合遥感、农业、食品检测、材料分析方向的研究生和工程师也适合刚入门的同学照着把整个流程过一遍。1. 高光谱数据预处理全流程拆解从DN值到可用光谱1.1 高光谱数据到底“脏”在哪里高光谱影像是典型的“图谱合一”数据每个像元都带有一条连续的光谱曲线波段数从几十到几百不等。但也正因为波段多、信息密数据里的问题同样被放大。传感器响应不稳定、大气吸收散射、光照角度差异、仪器噪声、颗粒粒径不均这些因素都会混进光谱曲线里。如果把这些脏数据直接拿去做分类或回归模型学到的往往是噪声和干扰而不是目标本身的理化特征。高光谱数据中最常见的脏数据问题有几类一是水汽吸收波段在近红外和中红外区间大气中的水汽会严重吸收电磁波导致这些波段基本没有有效信号二是传感器本身的坏像元和死波段响应值恒定不变或者完全随机跳动三是光谱曲线的基线漂移同一个样品测两次曲线形态一样但整体抬高或压低四是随机噪声尤其是波段数越多后端波段的信噪比通常越低五是颗粒散射引起的非线性变化这在粉末、叶片、土壤这类非均质样品上特别明显。预处理要做的就是把这些干扰逐层剥离把光谱还原成能反映物质理化性质的信号。这一步做扎实了后面不管用什么模型效果都能好一个档次。1.2 从原始DN值到反射率的完整链路高光谱传感器记录的最原始信号叫DN值Digital Number是一个无量纲的整数直接来自模数转换。DN值本身没有物理意义不能跨传感器、跨时间、跨光照条件比较。要使用高光谱数据第一步就是把DN值转成辐亮度Radiance这一步叫辐射定标然后再把辐亮度转成反射率Reflectance这一步叫大气校正。辐射定标比较简单传感器厂商会给出每个波段的增益gain和偏置offset公式就是一条直线L DN × gain offset。辐亮度有明确物理单位表示传感器接收到的辐射能量。但辐亮度仍然受光照强度、观测角度、大气状况影响同一物体在不同时间测出来的辐亮度差别很大所以科研和应用中更常用反射率。反射率的定义是物体反射的辐射能量占入射辐射能量的比例是物质的固有光学属性。从辐亮度转反射率严格的做法是用辐射传输模型做大气校正比如FLAASH、6S、MODTRAN这些。基于常理推断大多数处理代码包会简化这一步因为大气校正需要同步的 atmospheric 参数和专业软件不适合写成轻量Python函数。更实用的简化方案是经验线性法在野外或实验场景放置已知反射率的黑白参考板通过实测DN值和已知反射率拟合一条线性关系把所有DN值都映射到反射率空间。1.3 这份代码包在完整链路里承担的角色这份代码包解决的问题是拿到原始高光谱数据之后、进入建模之前的那一整段工作。具体来说包含六个任务坏波段剔除、辐射定标与反射率转换、光谱平滑去噪、光谱导数变换、散射校正SNV/MSC、归一化与数据整理。如果输入数据已经是反射率产品辐射定标和大气校正那一步可以直接跳过。需要清醒认识的是预处理救不了错误数据。如果传感器标定参数给错了或者原始数据在采集时大面积过曝任何平滑和校正都补不回来。预处理的定位是“去伪存真”把有效信息从噪声和干扰中提取出来而不是制造本不存在的信号。2. 环境准备与代码包结构先把工具链搭对2.1 Python开发环境与依赖项这份代码的核心依赖是NumPy、SciPy和Matplotlib如果涉及ENVI格式高光谱影像的读取还会用到spectral库。NumPy负责数组运算SciPy提供Savitzky-Golay滤波等信号处理函数Matplotlib用来画预处理前后的光谱曲线对比图spectral库是用来直接读ENVI标准格式的。在安装依赖之前先把Python环境准备好。建议直接用Anaconda创建独立环境避免多个项目之间的包版本冲突。下面这几条命令可以完成环境创建和依赖安装conda create -n hyperspectral python3.9 -y conda activate hyperspectral pip install numpy scipy matplotlib spectral如果只是临时跑个小数据不想用Anaconda也可以直接用系统Python安装pip install numpy scipy matplotlib spectral安装过程中最常见的坑是ModuleNotFoundError: No module named spectral遇到这个就说明spectral库没装上或者装错环境了。检查一下当前Python解释器路径确保和pip安装目标一致。另外spectral库在部分Python 3.11版本上可能会有编译问题所以我个人建议把Python版本控制在3.9或3.10实测最稳定。2.2 zip代码包的标准目录结构与模块职责一份规范的高光谱预处理代码包目录结构应当按功能模块划分。你拿到的zip解压后推荐的目录是这样hyperspectral_preprocess/ ├── main.py # 主入口参数配置与流程调度 ├── preprocessing/ │ ├── __init__.py │ ├── bad_band.py # 坏波段剔除 │ ├── radiometric.py # 辐射定标与反射率转换 │ ├── smoothing.py # SG平滑与导数变换 │ ├── scatter_correct.py # SNV和MSC散射校正 │ └── normalize.py # 归一化与数据整理 ├── utils/ │ ├── __init__.py │ ├── io_utils.py # ENVI数据读取与保存 │ └── plot_utils.py # 光谱曲线可视化 ├── config.yaml # 参数配置文件 └── requirements.txt # 依赖列表main.py 是串起整条线的入口读取config.yaml里的参数按顺序调用各个预处理模块。preprocessing目录里每个子模块只做一件事bad_band.py管坏波段smoothing.py管平滑和导数这样设计的好处是方便替换和调试。比如你拿到了新的数据源可能只需要改config.yaml里的水汽吸收段范围或者替换radiometric.py里的定标参数而不需要动其他代码。config.yaml里通常会配置这些参数数据文件路径、输出路径、坏波段列表或波长范围、SG滤波窗口大小和多项式阶数、是否做一阶导数、是否做SNV或MSC、归一化方式。把参数从代码里分离出来是工程上比较稳妥的做法。如果某天你被人拉去处理小数据集改配置文件比改代码快得多也不容易改坏逻辑。2.3 读取ENVI格式数据的关键姿势高光谱数据最常以ENVI标准格式存储通常包含一个二进制数据文件和一个.hdr头文件。用spectral库读取非常简单import spectral.io.envi as envi img envi.open(data/raw_image.hdr) metadata img.metadata wavelength metadata.get(wavelength) data img.load()有一个容易踩的坑是hdr文件里的数据格式描述interleave字段可能是bsq、bil或bip三种方式的波段存储顺序不同。spectral库会自动处理但如果你用numpy的fromfile函数直接读二进制就必须根据头文件里的interleave、samples、lines、bands来手动reshape到正确维度。读取后通常得到三维数组shape为(rows, cols, bands)。后面做的所有预处理本质上都是沿着bands这个轴对光谱曲线做变换所以在代码里要时刻留意轴的方向。我习惯在预处理函数里统一用axis-1表示波段轴这样无论输入是二维样本矩阵还是三维影像都能自动适配不用来回改。3. 核心算法实现坏波段剔除、反射率转换、平滑、导数、散射校正与归一化3.1 坏波段怎么剔先看波长范围再看统计特征坏波段剔除是预处理的第一步也是最容易被人忽略的一步。我见过不少项目直接把全部波段送进模型结果训练时间翻倍、精度反而下降就是因为水汽吸收波段和死波段把有效信号都稀释了。坏波段主要分两类。第一类是已知的水汽吸收波段大气中的水蒸气在近红外和中红外有几个强吸收区间。通常需要剔掉这些区间附近的波段不同传感器具体范围略有差异但大致可以按经验区间来。第二类是传感器自身的死波段和坏像元响应表现为某一波段的像元值几乎恒定或者噪声方差异常大。在实际代码里我会用两个条件筛选先按波长范围剔除已知吸收段再按统计特征剔除恒定通道。一个可行的实现import numpy as np def remove_bad_bands(data, wavelength, bad_rangesNone, std_threshold1e-6): 剔除坏波段。 data: (rows, cols, bands) 或 (samples, bands) wavelength: 每个波段对应的中心波长 bad_ranges: 需要剔除的波长区间列表如 [(1350, 1450), (1800, 1950)] valid_idx np.ones(len(wavelength), dtypebool) if bad_ranges: for start, end in bad_ranges: valid_idx ~((wavelength start) (wavelength end)) # 剔除方差过小的“死波段” band_std np.std(data, axistuple(range(data.ndim - 1))) valid_idx band_std std_threshold return data[..., valid_idx], wavelength[valid_idx]需要注意std_threshold不能设得太高否则会误删有效波段。我通常是先跑一遍打印所有波段的方差分布人工扫一眼再定阈值。这一步虽然多花几分钟但能避免削足适履。3.2 高光谱如何转反射率两种最常用的换算思路“高光谱如何转反射率”这个问题在我的后台被反复问到。答案取决于数据源。如果你手头是无人机和地面光谱仪数据通常用经验线性法如果是星载高光谱数据原始文件里会带辐射定标参数。辐射定标转辐亮度的公式很简单def dn_to_radiance(dn, gain, offset): DN值转辐亮度。gain和offset是逐波段的数组。 return dn * gain offset而从辐亮度到反射率最工程化的做法是经验线性法。在目标区域放置一块已知反射率的参考板设参考板反射率为R_ref实测它的DN均值是D_ref传感器增益和大气路径的联合影响就近似为R_ref / D_ref。把这个比值应用到所有像元上def empirical_line_reflectance(radiance, ref_dn, ref_reflectance): 经验线性法反射率转换。 ref_dn: 参考板实测DN或辐亮度 ref_reflectance: 参考板的已知反射率 scale ref_reflectance / ref_dn return radiance * scale对星载数据如果文件头里提供了ESUN太阳光谱辐照度和太阳天顶角可以用简化的物理模型def radiance_to_reflectance(radiance, esun, solar_zenith_angle, d1.0): 简化反射率转换。d是日地距离修正因子地外场景取1。 cos_theta np.cos(np.radians(solar_zenith_angle)) reflectance (np.pi * radiance * d**2) / (esun * cos_theta) return np.clip(reflectance, 0, 1)这种方法在平地和晴朗条件下精度尚可但遇到复杂地形或大气状况不稳定时还是要靠FLAASH这类专业大气校正软件。需要说明的是这些只是基于常见实践的补充严格的大气校正不在轻量代码包的范围内。3.3 Savitzky-Golay平滑窗口宽度和多项式阶次怎么定光谱平滑是为了压制随机噪声同时又不能把真实吸收特征给磨平。Savitzky-Golay滤波器是光谱预处理里最常用的平滑方法它的核心思想是用一个固定窗口内的多项式拟合来替代原始点值既有平滑效果又能保留峰谷形态。在Python里直接用scipy实现from scipy.signal import savgol_filter def sg_smooth(data, window11, polyorder2): SG平滑沿最后一个轴波段轴操作。 return savgol_filter(data, window_lengthwindow, polyorderpolyorder, axis-1)参数选择上窗口宽度和多项式阶数需要配合着调。窗口越大平滑程度越强但太大会把精细的吸收峰抹掉多项式阶数越高保留细节的能力越强但过高会保留噪声。基于常理推断光谱曲线这种缓变信号窗口取7到15之间多项式阶数取2或3是比较稳妥的组合。我比较推荐先看原始光谱曲线如果噪声很大再慢慢增加窗口每加一次就画一次图对比别一上来就开大窗口。3.4 导数光谱一阶导数消除基线二阶导数增强细节导数光谱是放大光谱曲线细微差异的利器。一阶导数消去掉常数基线偏移二阶导数可以增强重叠峰的分离度也能突出光谱曲线的拐点和肩部特征。实际操作中一阶导数比二阶导数更要谨慎使用因为求导会放大高频噪声。如果原始数据信噪比不高直接求导得到的基本就是一整条锯齿线。正确顺序是先做SG平滑再做导数变换这样能有效抑制噪声放大。def derivative_spectrum(data, wavelength, order1): 对光谱求一阶或二阶导数。 wavelength: 波长数组用于计算相邻波段间隔。 if order 1: return np.gradient(data, wavelength, axis-1, edge_order1) elif order 2: d1 np.gradient(data, wavelength, axis-1, edge_order1) return np.gradient(d1, wavelength, axis-1, edge_order1) else: raise ValueError(order只能是1或2)用np.gradient而不是直接差分好处是可以自动处理非均匀波段间隔。有些高光谱传感器的波段间隔并不完全一致如果直接用相邻点差分等价于假设间隔均匀这会引入系统性偏差。3.5 SNV和MSC两种散射校正怎么选散射校正是近红外光谱分析里绕不开的一步在粉末、颗粒、叶片这类样品上尤其重要。同一个化学成分的样品因为颗粒大小、表面状态不同光谱曲线会发生基线平移甚至倾斜。SNV和MSC就是用来校正这类散射效应的。SNV标准正态变换是对每条光谱单独做标准化减去该样本光谱均值再除以标准差。公式是 x_snv (x - mean(x)) / std(x)。这样做可以把不同样本间由散射导致的整体偏移和尺度差异压缩到同一水平。实现很简洁def snv_correct(data): SNV散射校正对每个样本光谱独立操作。 mean np.mean(data, axis-1, keepdimsTrue) std np.std(data, axis-1, keepdimsTrue) return (data - mean) / stdMSC多元散射校正的思路稍有不同。它先用全体样本的平均光谱作为“理想光谱”参照然后对每条光谱进行回归拟合求出每个样本自己的偏移系数和倾斜系数再减去偏移、除以斜率def msc_correct(data): MSC散射校正。 data: 二维数组 (samples, bands) ref np.mean(data, axis0) out np.zeros_like(data) for i in range(data.shape[0]): # 最小二乘拟合 y kx b poly_coeffs np.polyfit(ref, data[i], 1) out[i] (data[i] - poly_coeffs[1]) / poly_coeffs[0] return out两者的适用场景略有差别。SNV不依赖样本集整体适合在线快速处理逐条到达的光谱MSC依赖于全样本的统计适合批量处理。在农产品的近红外定量分析里这两种方法效果接近我一般两个都跑一遍看谁让模型精度更高。另外要提醒一下MSC函数默认输入是二维样本矩阵如果你手里的数据是三维影像就要先reshape成(samples, bands)再处理最后再reshape回影像形状。3.6 归一化与数据整理进模型前的最后一步做完平滑、导数、散射校正之后光谱数据还不能直接进模型。不同波段的值域范围差别很大直接用原始值训练模型数值范围大的波段会主导梯度更新所以要做归一化。常用的方式有三种min-max归一化把数据缩放到[0, 1]区间z-score标准化把数据变成均值为0、方差为1以及按行归一化对每条光谱除以该光谱的最大值或和值。高光谱数据预处理通常用z-score标准化因为它能保留波段间的相对差异。def zscore_normalize(data): z-score标准化沿波段轴计算均值和标准差。 mean np.mean(data, axis-1, keepdimsTrue) std np.std(data, axis-1, keepdimsTrue) return (data - mean) / std def minmax_normalize(data): min-max归一化沿波段轴计算最小值和最大值。 vmin np.min(data, axis-1, keepdimsTrue) vmax np.max(data, axis-1, keepdimsTrue) return (data - vmin) / (vmax - vmin 1e-12)注意这里加了一个1e-12的极小值防止某个样本在所有波段上值完全一致导致除零报错。这种边界情况在实际数据里真的会出现尤其在剔完坏波段之后。归一化做完后数据通常要整理成模型能接收的格式。如果做分类或回归需要把三维影像展平成二维样本矩阵每一行是一个样本的光谱每一列是一个波段然后和标签数组对齐划分训练集和测试集。这一步虽然简单但容易出错展平的时候别忘了按波段轴顺序展开否则样本和波段对应关系就全乱了。4. 完整Python代码解析能直接跑通的高光谱预处理管线4.1 主流程代码从文件读取到输出结果一步到位把所有预处理模块串起来以后主流程可以写成一个可复用的类。这个类的设计思路是初始化时传入配置参数然后调用run方法完成全部预处理。如下代码演示了一份可跑的管线import numpy as np from scipy.signal import savgol_filter import spectral.io.envi as envi class HyperspectralPreprocessor: 高光谱数据预处理管线。 def __init__(self, bad_rangesNone, sg_window11, sg_polyorder2, use_derivativeFalse, derivative_order1, use_snvFalse, use_mscFalse, normalize_methodzscore): self.bad_ranges bad_ranges or [] self.sg_window sg_window self.sg_polyorder sg_polyorder self.use_derivative use_derivative self.derivative_order derivative_order self.use_snv use_snv self.use_msc use_msc self.normalize_method normalize_method self.wavelength None def load_envi(self, hdr_path): 读取ENVI格式数据。 img envi.open(hdr_path) metadata img.metadata self.wavelength np.array(metadata.get(wavelength, []), dtypefloat) return img.load() def remove_bad_bands(self, data): 坏波段剔除。 valid_idx np.ones(data.shape[-1], dtypebool) if len(self.wavelength) data.shape[-1]: for start, end in self.bad_ranges: valid_idx ~((self.wavelength start) (self.wavelength end)) band_std np.std(data, axistuple(range(data.ndim - 1))) valid_idx band_std 1e-6 if self.wavelength is not None and len(self.wavelength) valid_idx.size: self.wavelength self.wavelength[valid_idx] return data[..., valid_idx] def sg_smooth(self, data): SG平滑。 return savgol_filter(data, window_lengthself.sg_window, polyorderself.sg_polyorder, axis-1) def derivative(self, data): 导数变换。 if len(self.wavelength) ! data.shape[-1]: # 波长信息缺失时退化为均匀间隔 d1 np.gradient(data, axis-1, edge_order1) else: d1 np.gradient(data, self.wavelength, axis-1, edge_order1) return d1 if self.derivative_order 1 else np.gradient(d1, self.wavelength, axis-1, edge_order1) def snv(self, data): SNV校正。 mean np.mean(data, axis-1, keepdimsTrue) std np.std(data, axis-1, keepdimsTrue) return (data - mean) / std def msc(self, data): MSC校正需要先展平成二维。 original_shape data.shape if data.ndim 3: rows, cols, bands original_shape data data.reshape(-1, bands) ref np.mean(data, axis0) out np.zeros_like(data) for i in range(data.shape[0]): coeffs np.polyfit(ref, data[i], 1) out[i] (data[i] - coeffs[1]) / coeffs[0] return out.reshape(original_shape) def normalize(self, data): 归一化。 if self.normalize_method zscore: mean np.mean(data, axis-1, keepdimsTrue) std np.std(data, axis-1, keepdimsTrue) return (data - mean) / (std 1e-12) elif self.normalize_method minmax: vmin np.min(data, axis-1, keepdimsTrue) vmax np.max(data, axis-1, keepdimsTrue) return (data - vmin) / (vmax - vmin 1e-12) else: raise ValueError(normalize_method仅支持zscore或minmax) def run(self, data): 执行完整预处理流程。 print(step1: 剔除坏波段) data self.remove_bad_bands(data) print(step2: SG平滑) data self.sg_smooth(data) if self.use_derivative: print(step3: 导数变换) data self.derivative(data) if self.use_snv: print(step3/4: SNV校正) data self.snv(data) if self.use_msc: print(step3/4: MSC校正) data self.msc(data) print(最后一步: 归一化) data self.normalize(data) return data4.2 调用方式与效果验证使用时的调用方式很直接# 配置预处理管线 preprocessor HyperspectralPreprocessor( bad_ranges[(1350, 1450), (1800, 1950)], sg_window11, sg_polyorder2, use_derivativeFalse, use_snvFalse, use_mscTrue, normalize_methodzscore ) # 读取数据 data preprocessor.load_envi(data/raw_image.hdr) # 执行预处理 data_processed preprocessor.run(data) print(f原始数据形状: {data.shape}) print(f预处理后数据形状: {data_processed.shape})每次跑完预处理我都建议画一张对比图把原始光谱和预处理后光谱叠在一起看。如果平滑后曲线在大吸收峰附近出现了明显的凹陷变形说明窗口开大了如果曲线仍然毛刺很多说明窗口没开够。这个视觉检查的过程虽然原始但比任何指标都直观。4.3 可视化辅助判断预处理效果好不好图上一眼就知道一个称职的预处理流程必须配合可视化工具。Matplotlib画光谱曲线叠加展示不同样本的平均光谱或原始曲线import matplotlib.pyplot as plt def plot_spectra(data, wavelength, titleSpectra, samples20): 随机抽取若干条光谱曲线并绘制。 plt.figure(figsize(12, 5)) # 展平后随机抽取样本 if data.ndim 3: rows, cols, bands data.shape flat data.reshape(-1, bands) idx np.random.choice(flat.shape[0], samples, replaceFalse) selected flat[idx] else: selected data[:samples] for spec in selected: plt.plot(wavelength, spec, linewidth0.8) plt.xlabel(Wavelength (nm)) plt.ylabel(Reflectance) plt.title(title) plt.grid(alpha0.3) plt.show()这条曲线画出来能帮你判断三件事曲线形态是否符合该物质的典型光谱特征噪声是否被有效压制不同样本之间是否还有严重的基线漂移。如果一切正常预处理后的数据就可以放心交给模型了。5. 高光谱数据预处理高频问题排查从zip解压到光谱曲线异常5.1 zip代码包解压失败的常见原因这份代码打包成zip发布之后最常被问到的问题反而是解压失败。常见的提示无非两种file is not a zip file和invalid zip archive: could not find eocd。先看第一种。用Linux的file命令可以快速确认文件真实类型file hyperspectral_preprocess.zip如果输出显示Zip archive data说明文件本身没问题可能是下载过程中改名或传输损坏如果输出是HTML document或者ASCII text那说明你下载到的是一个网页跳转页不是真正的zip文件。在网盘或部分下载工具里这种情况很常见重新用浏览器的直接链接下载一般能解决。第二种报错could not find eocd里的eocd是zip文件的结尾标识找不到它说明zip文件的尾部结构缺失下载不完整。解决办法很简单重新下载或者换一个下载工具。尝试用7-Zip或Bandizip这类专业工具打开有时能恢复部分内容但我不建议在核心代码上赌这个重新下载最省心。Windows下解压时还要注意不要手动把压缩包后缀改成.zip。有些文件实际是7z或rar格式强行改名后缀会让压缩软件识别失败。用压缩软件自带的“打开方式”识别真实格式或者右键查看文件属性里的类型说明。这里多说一句zip密码破解这类需求不在讨论范围内。如果是你自己压缩时设了密码又忘了试试压缩软件里的“测试”功能看看能不能通过密码校验如果确认密码遗失只能重新找源文件。与其想着暴力破解不如养成好习惯压缩代码时把密码记录到密码管理器里。5.2 读取ENVI格式数据失败怎么排查如果你用的是这份代码包里的load_envi函数读取失败通常有四个原因。第一是hdr文件缺失或路径错误。ENVI格式要求.hdr文件和二进制数据文件放在同一目录文件名保持一致。代码里传入的路径是.hdr文件的路径spectral库会根据它自动找到同名的数据文件。如果只传了二进制文件路径读取必然失败。第二是数据文件不完整。高光谱影像文件动辄几个GB传输过程中容易截断。可以用文件的字节大小和.hdr里的描述对比数据文件大小应该等于samples × lines × bands × 每个像素字节数。第三是.hdr文件里的interleave描述和实际二进制排列不一致。这种情况虽然少但一旦遇到读取结果会是一团乱码。建议用ENVI软件打开原文件确认格式或者在spectral库读取后打印data.shape检查维度是否合理。第四是路径中包含中文和空格。spectral库在部分系统上对中文路径支持不友好建议所有数据路径和代码路径一律用英文中间不要加空格。5.3 预处理后光谱曲线出现负值和跳变预处理后光谱出现负值不一定是错误。导数变换后的光谱天然有正有负这是正常的z-score标准化后出现负值也正常。但如果反射率转换后出现大面积负值就要小心了。最常见的原因有三个。一是暗电流扣除不足传感器在没有光照时也存在微弱响应如果定标参数里没有扣除暗电流低反射率区域的DN值可能被减成负数。二是经验线性法用的参考板反射率设置不合理如果参考板反射率标定得偏高映射到暗目标上就会变成负值。三是坏波段剔除不彻底某些波段的噪声过大在转换后产生负反射率。处理方式先检查原始DN值是否出现过零或负值如果有需要先做暗电流校正如果原始数据正常检查定标参数是否合理。对于个别负值可以用np.clip截断到0但要注意这只是应急手段根本问题还是出在定标或校正环节。跳变问题则通常是波段间拼接导致的。部分传感器由多个探测器拼接而成在拼接处会出现相邻波段响应不一致形成“台阶”。这种情况可以试着在跳变处做局部平滑或者干脆剔除跳变附近的波段。5.4 关于处理性能和工程化的一些心得高光谱数据量很大一张标准影像少说也有几百万个像元。如果按最直接的方式用三层for循环遍历每个像元做预处理跑完一次要好几个小时这是新手最容易犯的效率错误。正确姿势是用numpy的张量运算像savgol_filter和np.gradient这些函数都支持沿着指定轴对整个数组操作一次调用就能处理全部数据效率高两个数量级。如果数据实在太大内存装不下可以考虑用numpy的memmap模式做分块处理或者先对影像做降采样试验流程再对全分辨率数据跑正式结果。工程化方面把预处理参数像SG窗口、坏波段区间、归一化方式这类东西集中到配置文件里比写死在代码里好维护得多。我自己在实际项目中会把不同预处理参数组合包装成几套预设比如“光谱平滑SNV”适合粉末样品“一阶导数MSC”适合溶液样品这样切换场景时只需要改一个参数组的名字不用重跑代码。5.5 拿到新数据源时的排查路径如果你第一次处理某类高光谱数据建议按这个顺序排查先看原始影像的假彩色合成图确认数据范围和地物形态正常再随机抽取几条光谱曲线观察有没有明显的水汽吸收谷和噪声段然后跑坏波段剔除画出剔除后的波段覆盖范围再做平滑和反射率转换对比转换前后的光谱形态最后才做散射校正、归一化和建模。这套流程我用了很多年基本能定位90%的数据问题。很多时候问题不在模型参数而在预处理环节的某一个细节参数设错了。每次跑预处理多留一份中间结果的图和数据后续排查问题时能省掉大量返工时间。高光谱数据预处理的方法论远不止这一篇文章能讲完但核心逻辑是相通的每一步变换都要知道它解决什么问题原理是什么参数改变会带来什么后果。拿到任何一份新数据先做预处理再建模这个习惯远比选什么模型更重要。如果你刚开始接触高光谱建议拿一份小尺寸影像把这篇文章里的代码从头到尾跑一遍遇到问题再看第5章的排查表应该能把整条管线顺利跑通。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门