4、时序特征提取:植被指数(NDVI、EVI、LAI)计算、物候特征提取(SOS、EOS、LOS)

好,咱们进入正题。时序特征提取这块,说白了就是把卫星影像里那些像素级别的数字,翻译成我们能理解的生态语言。你拿到一堆时间序列的遥感影像,不能光盯着灰度值看,得算出有物理意义的指标来。

我个人习惯把这一步分成两个阶段:植被指数计算物候参数提取。前者是基础,后者是应用。咱们一个一个来拆解。

4.1 植被指数计算:NDVI、EVI、LAI

植被指数,本质上是波段间的数学组合。目的是增强植被信号,抑制土壤、大气这些干扰项。我最早做这个的时候,以为公式越复杂越好,后来发现,简单的东西往往最可靠

4.1.1 NDVI(归一化植被指数)

NDVI 是遥感界的「Hello World」。公式简单到令人发指:

NDVI = (NIR - Red) / (NIR + Red)

其中 NIR 是近红外波段,Red 是红波段。对于 Landsat 8 来说,就是 Band 5 和 Band 4。

我在项目中遇到过一个问题:直接用原始 DN 值算 NDVI,结果全是负值。后来才意识到,必须先做辐射定标和大气校正。你想想看,DN 值本身没有物理意义,不校正就是瞎算。

核心要点:

  • NDVI 范围:-1 到 1。健康植被通常在 0.3 到 0.8 之间。
  • 水体、雪、云:通常为负值或接近 0。
  • 裸土、建筑:接近 0。

下面是我常用的 Python 计算片段,用 numpy 向量化操作,速度很快:

import numpy as np

def calculate_ndvi(nir_band, red_band):
    """
    计算 NDVI
    nir_band, red_band: 2D numpy arrays
    """
    # 防止除零
    denominator = nir_band + red_band
    denominator = np.where(denominator == 0, 0.0001, denominator)
    
    ndvi = (nir_band - red_band) / denominator
    # 裁剪到有效范围
    ndvi = np.clip(ndvi, -1, 1)
    return ndvi

避坑指南:我曾经直接用 float32 算,结果在大面积水体区域出现 NaN。后来加了 np.clip 和分母保护,问题解决。另外,输出时建议乘以 10000 存成 int16,能省不少磁盘空间。

4.1.2 EVI(增强型植被指数)

NDVI 在高植被覆盖区容易饱和,说白了就是到了 0.8 以上就分不出差别了。EVI 就是为了解决这个问题。它还加入了蓝波段来校正大气影响。

EVI = 2.5 * (NIR - Red) / (NIR + 6 * Red - 7.5 * Blue + 1)

这里的系数 2.5、6、7.5、1 是经验值,来自 MODIS 的算法。我建议你不要随便改,除非你有充分的物理依据。

嗯,这里要注意:EVI 对大气校正的质量要求更高。如果你用的是 Level-1 数据(未做大气校正),算出来的 EVI 可能还不如 NDVI 靠谱。

指数 优势 劣势 适用场景
NDVI 简单、稳定、计算快 高植被区饱和 低-中植被覆盖
EVI 抗饱和、抗大气 需要蓝波段、计算复杂 高植被覆盖、热带雨林

4.1.3 LAI(叶面积指数)

LAI 不是直接算出来的,而是反演出来的。它定义为单位地表面积上叶片总面积的一半。LAI 和 NDVI/EVI 有很强的经验关系,但更精确的做法是用辐射传输模型。

我常用的经验公式是这样的:

LAI = 0.57 * exp(2.33 * NDVI)

这个公式来自某个经典文献,适用于草地和农田。对于森林,系数要调整。我在做东北森林项目时,发现这个公式高估了针叶林的 LAI,后来改用基于 EVI 的公式才校准过来。

重要提醒:LAI 反演没有万能公式。不同植被类型、不同传感器、不同大气条件,都需要重新标定。如果你追求精度,建议用 PROSAIL 模型做查找表反演,虽然慢,但靠谱。

4.2 物候特征提取:SOS、EOS、LOS

算完植被指数,我们得到的是每个时间点的「快照」。但生态学家关心的是过程:什么时候开始变绿?什么时候开始变黄?生长季有多长?

这就是物候特征提取要干的事。三个核心参数:

  • SOS(Start of Season):生长季开始日期
  • EOS(End of Season):生长季结束日期
  • LOS(Length of Season):生长季长度,LOS = EOS - SOS

4.2.1 时间序列重构

在提取物候参数之前,必须先做一件事:平滑去噪。原始 NDVI 时间序列充满了噪声——云、阴影、气溶胶都会造成异常值。

我常用的方法有三种:

  1. S-G 滤波(Savitzky-Golay):保留细节,适合高时间分辨率数据。
  2. 双逻辑斯蒂拟合(Double Logistic):物候学意义明确,参数可解释。
  3. Whittaker 平滑:速度快,适合大数据量。

我个人习惯用双逻辑斯蒂拟合。为什么?因为它拟合出来的曲线参数直接对应 SOS 和 EOS,不需要再额外做阈值判断。

from scipy.optimize import curve_fit
import numpy as np

def double_logistic(t, a, b, c, d, e, f):
    """
    双逻辑斯蒂函数
    t: 时间(儒略日)
    a: 背景值(冬季NDVI)
    b: 振幅(生长季峰值-背景值)
    c: SOS相关参数
    d: SOS相关参数
    e: EOS相关参数
    f: EOS相关参数
    """
    return a + b * (1/(1+np.exp(-c*(t-d))) - 1/(1+np.exp(-e*(t-f))))

# 拟合示例(伪代码)
# popt, _ = curve_fit(double_logistic, doy, ndvi_smoothed, p0=initial_guess)

经验之谈:初始参数 p0 很重要。我一般用冬季 NDVI 均值作为 a,用峰值减去冬季值作为 b。c 和 e 初始设为 0.1,d 设为 150(约 5 月底),f 设为 250(约 9 月初)。这样拟合收敛快,不容易掉进局部最优。

4.2.2 阈值法提取 SOS/EOS

如果你不想用拟合方法,阈值法是最直观的。常用的阈值有:

  • 固定阈值:比如 NDVI > 0.2 就算进入生长季。简单粗暴,但不同区域不通用。
  • 动态阈值:比如 NDVI 达到当年振幅的 20% 就算 SOS。适应性更好。

动态阈值的计算步骤:

  1. 计算每年 NDVI 的振幅:Amplitude = max(NDVI) - min(NDVI)
  2. 设定阈值比例:比如 20%
  3. SOS = 第一个 NDVI 超过 (min + 0.2 * Amplitude) 的日期
  4. EOS = 最后一个 NDVI 低于 (min + 0.2 * Amplitude) 的日期

注意:阈值法对噪声敏感。我建议在提取前先做一次中值滤波(窗口大小 3-5 个时间点),能有效减少误判。

4.2.3 物候参数的质量控制

提取出来的 SOS 和 EOS 不一定都合理。我一般会做以下过滤:

  • SOS 必须在 1 到 180 天之间(北半球)
  • EOS 必须在 180 到 365 天之间
  • LOS 必须大于 30 天(否则可能是噪声)
  • 峰值 NDVI 必须大于 0.3(否则可能是裸土或水体)

我曾经在青藏高原的项目中,发现有些像元的 SOS 算出来是第 5 天——1 月份就进入生长季了?这显然不合理。后来排查发现是雪盖造成的 NDVI 异常。加上积雪掩膜后,数据才正常。

4.3 本章知识体系

下面这张图概括了本章的核心逻辑。从原始影像到物候参数,每一步都有坑,每一步也都有技巧。

时序特征提取:从影像到物候参数 多时相遥感影像(Landsat/Sentinel/MODIS) 预处理:辐射定标 → 大气校正 → 云掩膜 植被指数计算 NDVI = (NIR-Red)/(NIR+Red) EVI = 2.5*(NIR-Red)/(NIR+6*Red-7.5*Blue+1) LAI = f(NDVI/EVI) 时间序列重构:S-G滤波 / 双逻辑斯蒂拟合 / Whittaker平滑 物候参数:SOS(开始) → EOS(结束) → LOS(长度)

这张图把整个流程串起来了。你从最上面的原始影像开始,一路往下走,每一步都有对应的算法和工具。我个人建议你先把 NDVI 算熟,再碰 EVI 和 LAI。物候提取这块,先从阈值法入手,理解原理后再上拟合方法。

好了,这一章的内容就到这里。代码和公式都给了,剩下的就是动手跑一遍。记住,遥感分析不是玄学,是工程。每一步都要问自己:这个参数合理吗?这个结果能解释吗?

专注资料整理