尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

高光谱数据预处理全流程:从DN到反射率的Python代码与实战

高光谱数据预处理全流程:从DN到反射率的Python代码与实战 简介本资源是一套面向人工智能与机器学习初学者及遥感图像处理实践者的高光谱数据预处理Python代码集聚焦解决高维光谱数据噪声大、维度高、易受仪器与环境干扰等核心问题适用于遥感分类、作物品质分析如桃子糖度预测、地物识别等典型应用场景。压缩包共15个文件含2个核心Python脚本pretreatment.py实现光谱校正、去噪、平滑、标准化与PCA降维等全流程预处理demo.py提供端到端调用示例、1个CSV格式实测光谱数据peach_spectra_brix.csv含桃子样本多波段反射率及糖度标签以及12张可视化结果图PNG直观展示各步骤处理效果。资源大小仅2.48MB轻量易上手。目前已有1370人学习下载代码结构清晰、注释完整配套图像涵盖光谱曲线对比、降维散点图、噪声前后效果等关键环节可直接复用于课程实验、毕业设计或科研预研阶段的数据清洗与特征工程环节。 拿到一景高光谱数据打开第一个波段矩阵满屏噪点RGB合成图偏色严重提取一条像元光谱曲线毛刺比狗牙还密。这是很多刚接触高光谱数据预处理的同学都会遇到的情况——不是传感器坏了而是数据还没“洗干净”。高光谱数据预处理说白了就是把传感器记录的原始数字值DN一步步换算成具有物理意义、跨时间跨传感器可比的地表反射率同时把噪声、水汽吸收带、基线漂移这些干扰因素压下去让光谱曲线真正能用于后续的分类、反演或矿物识别。很多从“高光谱数据预处理方法python代码.zip”这类资源入手的朋友最关心的问题通常是这段代码到底是干嘛的每一行在解决什么问题我拿到自己的数据该怎么改这篇就顺着完整的预处理流程把每一步的原理、代码和踩坑经验一次讲透适合遥感、农业、地质、环境方向的Python初学和中阶用户也适合手里有PRISMA、Sentinel-2或无人机高光谱数据但还没理清处理逻辑的研究生。1. 拿到高光谱数据后先分清三个物理量再谈预处理1.1 ENVI标准格式里的波段顺序和元数据到底读没读对高光谱数据最常碰到的格式是ENVI标准格式一个后缀为.hdr的文本头文件一个后缀为.dat的二进制数据文件有时也写成.img。很多人用spectral库打开数据后第一件事就是print(data.shape)看到类似(1000, 1000, 224)的三维数组就以为万事大吉其实这里最容易埋雷。ENVI头文件里写了samples、lines、bands、data type、interleaveBSQ/BIL/BIP、wavelength等关键信息。interleave决定数据读进内存之后各维度的含义BSQ是波段按序排列每波段是一整块BIL是每一行先按波段排列再换下一行BIP则是每个像元的所有波段连续存储。spectral库通常能自动处理interleave但当你用numpy去做通道操作时一定要搞清楚自己读进来的数组哪一维是行、哪一维是列、哪一维是波段。我自己习惯统一用“lines, samples, bands”即波段在最后一维的结构这样后续所有光谱维运算都可以用axis-1省去大量转置困扰。还有一个高频翻车点wavelength的单位。PRISMA L1产品按纳米给出而某些地面光谱仪或USGS光谱库用的是微米。做波段筛选、光谱重采样时如果不先统一单位波长偏移一个数量级所有后续处理全错。凡是读到wavelength之后再参与运算先打印前三个值和最后一个值确认单位import numpy as np from spectral.io import envi img envi.open(data/PRISMA_L1.hdr) data np.asarray(img.load(), dtypenp.float32) print(原始数据维度:, data.shape) # (lines, samples, bands) meta img.metadata wavelength np.array([float(w) for w in meta.get(wavelength, [])], dtypenp.float32) print(波段数:, len(wavelength)) print(波长范围: {:.2f} nm ~ {:.2f} nm.format(wavelength[0], wavelength[-1]))数据量太大时load()一次性把整个立方体读进内存很容易导致内存崩溃。更好的习惯是先用np.memmap做映射只对感兴趣的子区域做全量读取或者分批处理。实际处理一景PRISMA L1数据约1000×1000×234波段float32内存占用接近1GB如果同时做多个中间变量很容易被内存卡死。1.2 DN、辐射率、反射率三者之间差着整个大气层原始数据里的DNDigital Number只是一个无量纲的量化值它跟地物本身的性质有关系但还叠加了传感器响应、太阳高度角、大气散射吸收等因素的干扰。同样一块地夏天和冬天拍的DN值不同上午和下午拍的DN值也不同不同传感器拍的DN值更不可比。所以预处理的第一步是先把DN换算成传感器入瞳处的辐亮度也就是辐射率Radiance这一步叫辐射定标。然后再通过大气校正把辐射率换算成地表反射率Reflectance。反射率才是地物真正的“身份证”。它表示地物反射太阳辐射的能力理论上不随太阳高度角、传感器位置而变化因此在时间序列分析、多传感器联合建模、光谱库匹配中必须使用反射率数据。辐射定标环节各个传感器给的换算方式不一样。PRISMA L1产品在元数据里给出辐射率缩放因子常见用法是L DN / scale_factor。如果传感器给的是gain和offset则L DN * gain offset。很多无人机的多光谱相机或自己定标的成像光谱仪给的是一组波段的定标系数表。写成通用函数def dn_to_radiance(data, gainNone, offset0.0, scale_factorNone): if scale_factor is not None: # PRISMA等卫星常见的DN除以缩放因子 return data.astype(np.float32) / scale_factor if gain is not None: return data.astype(np.float32) * gain offset raise ValueError(需要提供scale_factor或gain参数)这一步看起来简单但很多人会忽略数据类型转换。如果直接把uint16的DN跟float32的gain做运算numpy会做类型提升结果倒是float但如果你预先用int型数组保存中间结果数据就被截断了。最好一开始读入数据时就指定dtypenp.float32。2. 高光谱转反射率辐射定标与暗目标大气校正的Python实现2.1 没有ENVI FLAASH时怎么用Python做简化大气校正ENVI的FLAASH模块是目前用得最多的大气校正工具底层是MODTRAN辐射传输模型精度高但它不是Python生态的。如果你在Linux服务器上批量跑数据或者不想依赖GUI就必须用Python实现替代方案。完整的辐射传输模型有6S通过py6s库和MODTRAN的Python封装但这些模型对大气参数能见度、水汽柱含量、气溶胶光学厚度要求高实测数据里通常没有这些参数。在没有大气实测参数的情况下工程上最常用的简化方法是暗目标法Dark Object SubtractionDOS。这个方法的基本假设是影像里存在一些地表反射率近似为0的像元比如极深水体、山体阴影传感器在这些像元处测得的辐射值完全来自大气程辐射haze。用每个波段的暗像元值减去全波段最低辐射率就完成了粗略的大气散射校正。def dos1_correction(radiance, solar_zenith_deg, esun, d_au): # 取每个波段1%分位数作为暗像元估算值 dark np.percentile(radiance, 1, axis(0, 1)) corr radiance - dark.reshape(1, 1, -1) corr np.clip(corr, 0.0, None) cos_theta np.cos(np.deg2rad(solar_zenith_deg)) reflectance np.pi * corr * d_au**2 / (esun * cos_theta) return reflectance其中d_au是日地距离天文单位可以用一个简化公式估算def earth_sun_distance(day_of_year): return 1.00011 0.034221 * np.cos(2 * np.pi * (day_of_year - 172) / 365.25)esun是波段中心波长处的太阳光谱辐照度。严格做法是用每个波段的波谱响应函数对太阳光谱做积分但快速实现时可以查太阳光谱表如ASTM E-490标准太阳光谱取每个波段中心波长的值。因为这个简化算法损失了一部分精度它更适合植被指数、光谱角分类这类“相对敏感”的应用如果要做绝对反射率反演还是建议用6S或正规商业软件。2.2 拿到L2D反射率产品时最容易忽略的缩放系数问题PRISMA官方网站既提供L1辐射率产品也提供L2D大气校正后的反射率产品。很多图省事直接下载L2D的同学读进数据后看到反射率值在0到1之间还挺正常但另一些人会看到数值最大到几万以为平台出bug了。实际上L2D反射率有时被存储为扩大10000倍的整数即反射率百分比×100这是为了用整数格式压缩存储体积。处理时记得除以10000否则后面所有建模都会因为数值量级失衡而崩溃。更隐蔽的一个坑是L2D产品里波长范围只覆盖部分大气窗口水汽吸收段直接为空值或0这部分如果不加处理直接进入特征提取会把无数个假0当成真实反射率送进模型。所以即使你用的是官方反射率产品坏波段筛查和空值处理这一步也绝对不能省。3. 坏波段不剔除后面全是白做水汽带与低信噪比波段的自动识别3.1 哪些波段天生就是“坑”水汽吸收带和传感器响应边缘高光谱传感器的波段不是每个都好用的。大气中的水汽会强烈吸收特定波段的电磁波导致传感器在这些波段接到的信号大部分是噪声。最典型的是1350~1450nm和1800~1950nm两个强水汽吸收带还有930~980nm附近的弱吸收区。在航空或地面高光谱中940nm附近的水汽吸收也会让特定波段信噪比明显下降。矿物识别和植被参数反演通常只用大气窗口内的波段所以第一步就是把这几个区间干掉。除了水汽波段传感器本身在光谱响应范围的边缘也有严重噪声。比如有些传感器在400nm以下的蓝紫波段响应曲线快速下降信噪比很差在长波红外端的响应也未必稳定。这些波段的特征不应该参与建模否则噪声会主导分类结果。3.2 用空间方差代替人眼自动定位低信噪比波段人眼看单波段灰度图判断噪声效率低还容易漏。更可靠的方法是计算每个波段的信噪比。真实场景里高光谱影像相邻像元之间往往有空间相关性而随机噪声则没有。一个实用的工程指标是把每个波段按一定窗口做空间平滑计算平滑前后的差异差异大的波段噪声占比就高。简单实现如下from scipy.ndimage import uniform_filter def estimate_band_noise(data, window5): # 对每一行做滑窗平均用原始值减去平滑值的标准差估计噪声 smoothed uniform_filter(data, size(window, window, 1)) noise data - smoothed noise_std noise.std(axis(0, 1)) signal data.mean(axis(0, 1)) snr signal / (noise_std 1e-10) return snr有了每个波段的snr估计值可以设置一个阈值。实际经验是SNR低于整体中位数的70%的波段应该直接剔除水汽吸收带的波段无论如何都要去除而不是只做平滑。因为水汽带的信号本身已经没有了平滑只会把噪声摊到相邻波段上。一套自动筛选逻辑可以这样写def select_good_bands(wavelength, snr, water_bandsTrue): bad_mask np.zeros_like(wavelength, dtypebool) if water_bands: bad_mask | ((wavelength 1350) (wavelength 1450)) bad_mask | ((wavelength 1800) (wavelength 1950)) # SNR低于中位数70%的也标记为坏波段 threshold np.median(snr) * 0.7 bad_mask | (snr threshold) return ~bad_mask这里有一个极其关键的细节波段筛选一定要在辐射定标之后做不能在DN数据上做。因为DN数据经过传感器响应曲线调制后信噪比分布与实际物理量的信噪比分布并不相同可能漏掉真实低质量波段或者误伤一些本来质量尚可但在DN上增益偏低的波段。3.3 波段列表同步裁剪的完整示例防止波长与数据错位坏波段剔除后的索引同步问题是另一个高频翻车点。假设你用mask剔除了数据但忘了同时裁剪wavelength数组后面做重采样、光谱匹配时会莫名出现IndexError或者更隐蔽地——光谱曲线的横坐标全错位了。good_mask select_good_bands(wavelength, snr) data_clean data[:, :, good_mask] wavelength_clean wavelength[good_mask] print(原始波段:, data.shape[-1], 筛选后:, data_clean.shape[-1]) print(保留波段范围:, wavelength_clean[0], ~, wavelength_clean[-1])此时还建议把你剔除掉的波段以日志形式打印出来或存到一个文本文件里方便在论文方法部分写明——审稿人经常会问预处理剔除了哪些波段、依据是什么。4. 光谱平滑、连续统去除与基线校正从Savitzky-Golay到ALS4.1 Savitzky-Golay滤波为什么比移动平均更靠谱光谱平滑的目的是压掉随机高频噪声但代价是可能把细微的吸收特征也磨平了。移动平均是最简单粗暴的平滑方式但它对尖锐吸收特征的破坏非常明显相当于用矩形窗对信号做卷积会让谱峰变矮变胖。Savitzky-GolaySG滤波不同它是在滑动窗口内用多项式做局部最小二乘拟合再用拟合值替换窗口中心点。它的核心优势是能在降噪的同时较好地保持谱峰的形状、高度和宽度。在Python里调用SG滤波只需要一行from scipy.signal import savgol_filter data_smooth savgol_filter(data_clean, window_length15, polyorder3, axis-1)必须注意的是window_length必须是奇数且polyorder必须小于window_length。对于高光谱影像数据建议把window_length控制在9~21之间polyorder选2或3。地面ASD光谱仪采集的2151点光谱噪声更大窗口可以放宽到31~51。窗口太大或阶数太高会让吸收特征被过度拟合而产生伪峰。我自己的习惯是先用窗口11、阶数3跑一遍画出几条典型光谱曲线目检如果曲线仍然毛刺明显再逐步加大窗口每次加2如果曲线出现了明显的过冲或谷底抬高说明窗口过大了。4.2 连续统去除突出吸收特征的标准做法高光谱矿物识别和植被红边分析的常规操作里连续统去除Continuum Removal是一个不可回避的步骤。它的思路很直观光谱曲线可以看作一个整体的上升/下降趋势包络线加上局部吸收特征。把整条光谱除以它的包络线吸收谷就会变成0到1之间的特征值吸收越深数值越小。这样得到的归一化光谱消除了一定的亮度影响比直接用反射率做波段比值更稳定。最简单的连续统去除是对某个吸收特征区间用区间两端点连线当作包络线def continuum_removal_on_range(wl, refl, start, end): mask (wl start) (wl end) wl_sub wl[mask] refl_sub refl[mask] # 两端点连线 line np.interp(wl_sub, [wl_sub[0], wl_sub[-1]], [refl_sub[0], refl_sub[-1]]) cr refl_sub / line return wl_sub, cr对于全波段光谱则需要用凸包求出上包络线。实现方式from scipy.spatial import ConvexHull def continuum_removal_full(wl, refl): points np.column_stack([wl, refl]) hull ConvexHull(points) # 提取凸包中位于“上侧”的顶点按波长排序 upper_idx [] for i in hull.vertices: # 过滤掉明显位于下包络的点 if points[i, 1] np.interp(points[i, 0], wl, refl): upper_idx.append(i) upper_idx sorted(upper_idx, keylambda i: wl[i]) if len(upper_idx) 2: return np.ones_like(refl) envelope np.interp(wl, wl[upper_idx], refl[upper_idx]) envelope[envelope 0] np.nan return refl / envelope这里最容易被坑的是如果去掉水汽吸收带后的波长不连续直接做全波段凸包会得到一段跨越间隙的假包络线扭曲吸收特征。稳妥做法是先把波长切成连续区间逐段做连续统去除再拼接结果。如果原始反射率中存在0值或负值也要提前处理否则除以包络线时会产生极大值。4.3 基线校正什么时候需要什么时候别做高光谱反射率数据本身有物理意义一般不需要做基线校正因为大气校正后基线已经比较平。但如果你处理的是荧光光谱、拉曼光谱、或者实验室近红外漫反射光谱基线漂移是常态。环境变化、样品颗粒度变化都会导致基线整体抬升或倾斜。此时用ALSAsymmetric Least Squares做基线校正是个好选择。ALS的原理用一个通俗类比它试图拟合一条平滑基线并让基线贴近数据中“向下”的部分而把吸收峰视为向上偏差不参与基线拟合。关键参数有两个lambda控制基线平滑程度p控制允许数据高于基线的比例。python里虽然可以装pybaselines库但自己写一个几十行的实现并不难from scipy import sparse from scipy.sparse.linalg import spsolve def als_baseline(y, lam1e5, p0.01, niter10): n len(y) D sparse.diags([1, -2, 1], [0, -1, -2], shape(n - 2, n)) W np.ones(n) for _ in range(niter): W_ sparse.diags(W) z spsolve(W_ lam * D.T D, W * y) W p * (y z) (1 - p) * (y z) return z调参经验p越小基线越贴下包络lam越大基线越平滑。对于荧光光谱我常用lam1e6到1e7、p0.001到0.01。如果光谱本身已经是反射率形状基线上整体随波长缓慢变化还给它做ALS会把红边等真实光谱特征误判成基线漂移拉平这是得不偿失的。所以“基线校正”不是普遍必需步骤得先判断数据形态再决定。5. 归一化与重采样让不同时间、不同传感器采集的光谱具备可比性5.1 四种常用光谱归一化的适用边界同一片地不同时期测的光谱整体亮度可能不同。光照角度、传感器增益、大气残余都可能导致整体亮度的差异。归一化的目的不是把光谱变成同一个形状而是把与地物属性无关的整体亮度、基线平移等干扰压掉让后续模型更关注光谱的形状差异。Min-Max归一化是最常见的做法公式是(x - min) / (max - min)。它把每条光谱压到0到1之间适用于机器学习模型做输入标准化但它对异常值非常敏感——一个坏像元的尖峰就可能把正常数据的取值范围压得很扁。SNV标准正态变量变换对近红外光谱特别常用。它的做法是每个像元减去该像元所有波段的平均值再除以标准差。这个变换对固体颗粒大小差异导致的散射变化很有用是近红外光谱预处理的经典方法。矢量归一化则把光谱当成一个向量除以它的模长使所有光谱具有单位长度。它在光谱匹配和光谱角分类中经常见到因为光谱角本身对向量长度不敏感。但要注意矢量归一化会保留形状差异却可能放大噪声——如果光谱本身噪声大但整体能量低归一化后噪声会被放大。def minmax_norm(x, axis-1): xmin x.min(axisaxis, keepdimsTrue) xmax x.max(axisaxis, keepdimsTrue) return (x - xmin) / (xmax - xmin 1e-10) def snv_norm(x, axis-1): mu x.mean(axisaxis, keepdimsTrue) sigma x.std(axisaxis, keepdimsTrue) return (x - mu) / (sigma 1e-10) def vector_norm(x, axis-1): return x / (np.linalg.norm(x, axisaxis, keepdimsTrue) 1e-10)一个很重要的实践原则先做坏波段剔除、平滑最后才做归一化。如果在满是噪声的原始数据上先做归一化噪声会被放大到与信号同等量级后面的平滑也很难救回来。5.2 一阶导数和二阶导数到底在干嘛导数光谱是另一种变换虽然它不叫归一化但在预处理中的作用常和归一化并列讨论。一阶导数能消除基线平移放大光谱的斜率变化二阶导数能进一步突出吸收峰的尖锐程度同时对基线倾斜也不敏感。在植被遥感里红边位置的确定就经常用到一阶导数最大值的位置。def spectral_derivative(data, wavelength, order1): return np.gradient(data, wavelength, axis-1, edge_order1)在求导之前光谱一定要经过较充分的平滑。导数运算是噪声放大器一旦原始光谱的毛刺没压干净导数结果会变成一片刺猬毛。做二阶导数时SG滤波的polyorder至少要等于导数阶数加1但实际工程里更稳妥的是先做一次平滑再求导。如果你需要稳定的导数光谱可以考虑直接用SG滤波的deriv参数计算导数它内部会对窗口内多项式做解析求导比先平滑再差分更稳deriv1 savgol_filter(data_smooth, window_length15, polyorder3, deriv1, deltanp.diff(wavelength_clean).mean(), axis-1)这里delta必须传波段间隔否则导数结果会失真。5.3 跨传感器光谱重采样插值不是万能的当你要把自家的高光谱数据与公开光谱库如USGS光谱库匹配或者把不同传感器的产品放在一起建模时必须把光谱重采样到同一套波长网格上。最基础的实现是线性插值from scipy.interpolate import interp1d def resample_spectra(wl_old, data, wl_new, kindlinear): f interp1d(wl_old, data, kindkind, axis-1, bounds_errorFalse, fill_valuenp.nan) out f(wl_new) return out这里的核心问题不是插值代码本身而是插值前的信号处理。如果原始数据是5nm采样的高光谱目标是30nm采样的多光谱波段直接线性插值相当于只取了目标波段中心波长处的值丢弃了波段响应范围内的其他光谱信息。这必然引入采样误差。正确做法是先对原始光谱做高斯卷积高斯核宽度约等于目标传感器波段宽度再做插值。说白了插值只是“取点”卷积才是真正模拟传感器的光谱响应。边界处理也要注意。原始光谱范围外的目标波长默认填NaN后续要处理掉不能直接丢给模型。如果原始光谱两端噪声大重采样前建议把两端各切掉一段波长避免边缘插值污染目标波段。6. 预处理不是做完就完效果验证与可复现流水线搭建6.1 用信噪比改善、光谱形态和光谱角三个指标检验结果预处理做得对不对不能靠“看起来光滑了”来评价得有量化指标。第一个指标是信噪比改善。用前面提到的噪声估计方法分别计算预处理前和平滑后的SNR看提升了多少。如果SNR没提升反降了说明平滑窗口过大把真实信号也削掉了。第二个指标是光谱形态。选择几种典型地物植被、裸土、水体、建筑物画处理前后的光谱曲线。健康的植被光谱应该有明显的绿光反射峰、红光吸收谷、红边陡升和近红外高平台如果平滑后红边变缓或者吸收谷变浅、变宽说明窗口过大。裸土光谱应该整体平缓没有剧烈的锯齿状抖动。第三个指标是光谱角Spectral Angle Mapper, SAM。如果你有同一地物的多次观测或同一区域不同期的影像可以计算处理前后这两条光谱的SAM值。理想情况下经反射率转换和归一化后同一地物的光谱夹角应该明显变小说明你在去除环境干扰上是成功的。def spectral_angle(a, b): cos_theta np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) 1e-10) cos_theta np.clip(cos_theta, -1.0, 1.0) return np.arccos(cos_theta)一个额外的验证手段是用预处理后的光谱反演一个已知参数比如用植被指数NDVI反演LAI对比反演精度是否明显优于未预处理的数据。如果反演结果反而变差了先别急着怀疑算法回头检查是不是哪个环节把信号处理坏了。6.2 把整个流程封装成函数链让每步中间结果都留得下来预处理步骤多、参数杂最怕的就是“跑了一次结果找不回来”。我强烈建议把整个流程封装成一条流水线函数同时让每个中间步骤的结果都能单独保存。def preprocess_pipeline(data_raw, wavelength, gainNone, offset0.0, solar_zenith_degNone, esunNone, d_auNone, sg_window15, sg_poly3, norm_methodnone): data dn_to_radiance(data_raw, gaingain, offsetoffset) if solar_zenith_deg is not None: data dos1_correction(data, solar_zenith_deg, esun, d_au) data, wavelength remove_bad_bands(data, wavelength) data savgol_filter(data, sg_window, sg_poly, axis-1) if norm_method minmax: data minmax_norm(data) elif norm_method snv: data snv_norm(data) elif norm_method vector: data vector_norm(data) return data, wavelength流水线封装的时候每个环节都建议用print函数输出当前数组的shape和范围这样跑完一圈你能清楚地看到数据每一步的变化。实际工作中我还会在函数里输出一个JSON格式的处理日志记录当天日期、输入文件路径、所有参数值以及坏波段索引。这样可以保证任何一步出问题你都能回溯到具体是在哪个环节引入的。对于大场景数据每处理完一个批次就把中间结果写到硬盘而不是全部堆积在内存里。可以用np.savez_compressed或者h5py保存三维数组文件名带上前置步骤标记例如prisma_20231015_radiance.npz、prisma_20231015_reflectance.npz防止中断后推倒重来。6.3 关于数据精度和内存的几个实战细节处理过程中至少要避开的四个陷阱第一个是数据类型截断。DN转辐射率后中间变量的dtype务必保持float32或float64。如果直接用int16数组做减法暗目标校正时减出一个负数再强制赋给uint16数组直接溢出变成65535整景数据全废。第二个是水汽波段剔除后的索引错位。删了波段以后好多人继续用原来的波段列表切片导致光谱曲线横坐标和纵坐标长度不一致。这个问题看起来低级但我在实际审阅代码时真的频繁遇到。解决方式就是把“保留波段索引”写成函数内的局部变量并且每次裁剪同时更新data和wavelength。第三个是反射率负值。DOS校正后如果出现少量负反射率不要直接扔掉或置0了事。负值出现可能意味着暗像元选取过暗、太阳天顶角估计不准或数据本身存在传感器偏移。直接置0会人为造成光谱吸收深度加深影响后续连续统去除和吸收特征提取。正确做法是先临时置0但在日志里记录负值像元数量和空间分布如果负值范围较大应检查暗像元分位数比如从1%提高到2%~5%。第四个是内存翻倍问题。float16能省内存但精度不足float32是合理选择。在处理高光谱立方体时尽量避免写data2 func(data1)这类链式操作产生过多的临时副本。能原地操作的用out参数比如savgol_filter可以传入axis参数但scipy底层有些实现依然会创建副本。真遇到内存吃紧一个实用做法是把影像按行分块处理块之间只留少量重叠行用于平滑窗边缘处理完再拼接。对高光谱影像来说这个分块思路比升级内存更实在。我在实际处理PRISMA和无人机高光谱数据时最深的体会是预处理阶段花的时间往往比后面的建模还多。但这一步省不得数据的质量决定了模型精度的上限。把预处理做成可复现的流水线每一步都留下中间结果遇到问题能回溯、能对比才能把“从数据到结论”的链条走扎实。如果你手上正好有“高光谱数据预处理方法python代码.zip”这类资源建议别急着全部一键运行逐步对照本文的流程先弄懂每个环节在解决什么物理问题再根据你的传感器参数调整对应环节的代码逻辑。数据不同、传感器不同预处理的细节必然有差异而这个差异正是你作为研究者或工程师不可替代的价值所在。最后想分享的一个小技巧是预处理参数调完之后把对应的一组光谱曲线和参数记录固定在代码注释里隔几个月再回来看你会感谢当时留下了这些细节。本文还有配套的精品资源点击获取
返回列表