2. 多波段光谱指数云检测掩膜

为什么单靠蓝光阈值不够

例1用蓝光反射率阈值能抓住厚云,但植被在蓝光波段的反射率本身就不低,水体上空薄云的信号又常被暗背景“压”得接近阈值。多波段光谱指数的思路是:用两个波段的比值或归一化差值放大云与地物的光谱差异,让阈值判断更稳。本节聚焦两类常用指数:

  • NDCI(归一化差异云指数):NDCI = (R_red − R_blue) / (R_red + R_blue)。云在蓝光反射强、红光相对弱,NDCI 偏负;植被红光吸收强,NDCI 偏正,因此该指数对“植被 vs 云”区分力强。
  • NDSI 类雪/云指数:NDSI = (R_green − R_swir) / (R_green + R_swir)。云在绿光高、短波红外低,NDSI 偏正;水体在短波红外极低,NDSI 也偏正,这正是水体上空容易误检的根源。

下面用一组教学示例反射率(0–1,已做大气校正的相对值)演示两种指数在植被、水体、厚云、薄云四类像元上的取值差异,并据此设定阈值。

示例数据与指数计算

像元类型R_blueR_redR_greenR_swirNDCINDSI
浓密植被0.060.040.090.22−0.20−0.42
清水体0.080.050.070.02−0.23+0.56
厚云0.720.680.700.30−0.03+0.40
薄云(水体上空)0.300.260.280.10−0.07+0.47

观察:NDCI 在植被上为 −0.20,与厚云 −0.03 差距明显,但水体 −0.23 与植被接近,单用 NDCI 会把水体当“非云”而漏掉水体上空的薄云(−0.07 也偏负)。NDSI 能把厚云、薄云、水体都判为正,却把植被判为负——两者恰好互补。

NDCI 与 NDSI 在四类像元上的取值分布(示例数据) +0.6 0 −0.6 植被 −0.20 −0.42 水体 −0.23 +0.56 厚云 −0.03 +0.40 薄云(水体上) −0.07 +0.47 NDCI NDSI 组合判定:NDSI > 0.35 且 NDCI > −0.10 → 云;仅 NDSI 高而 NDCI 很低 → 多为水体,需复核 植被被 NDCI 排除,水体被 NDCI 排除,厚云/薄云同时满足两条件

阈值设定与组合逻辑

从示例分布可读出两条边界:NDSI 在云上均 ≥ 0.40,在植被上为 −0.42,取 NDSI > 0.35 可把植被排除;但水体 NDSI 达 0.56,会误入。此时用 NDCI 做二次过滤:云 NDCI 在 −0.03 ~ −0.07,水体 −0.23,取 NDCI > −0.10 可剔除水体。组合条件为:

cloud = (NDSI > 0.35) & (NDCI > -0.10)

注意阈值必须来自本景数据的直方图或样本统计,不能照搬示例值。实操时先对整景计算两指数,分别取植被、水体、云三类样本区统计均值与分位数,再在分位间隙中选阈值。

可执行实现(Python / NumPy)

import numpy as np

def nd_index(a, b):
    denom = a + b
    out = np.full(a.shape, np.nan, dtype=np.float32)
    valid = np.abs(denom) > 1e-6
    out[valid] = (a[valid] - b[valid]) / denom[valid]
    return out

def cloud_mask_multiband(blue, red, green, swir,
                         ndsi_thr=0.35, ndci_thr=-0.10):
    ndci = nd_index(red, blue)      # (R_red - R_blue)/(R_red + R_blue)
    ndsi = nd_index(green, swir)    # (R_green - R_swir)/(R_green + R_swir)
    mask = (ndsi > ndsi_thr) & (ndci > ndci_thr)
    mask &= np.isfinite(ndci) & np.isfinite(ndsi)
    return mask.astype(np.uint8), ndci, ndsi

# 示例:用上表四类像元构造 2x2 影像验证
blue  = np.array([[0.06, 0.08], [0.72, 0.30]], dtype=np.float32)
red   = np.array([[0.04, 0.05], [0.68, 0.26]], dtype=np.float32)
green = np.array([[0.09, 0.07], [0.70, 0.28]], dtype=np.float32)
swir  = np.array([[0.22, 0.02], [0.30, 0.10]], dtype=np.float32)

mask, ndci, ndsi = cloud_mask_multiband(blue, red, green, swir)
print(np.round(ndci, 2))
print(np.round(ndsi, 2))
print(mask)
# 预期 mask: [[0,0],[1,1]] —— 植被、水体为0,厚云、薄云为1

运行后应得到 mask = [[0,0],[1,1]],与上表判定一致。若把 ndci_thr 放宽到 −0.25,水体(−0.23)会被误判为云,这正是需要重点比较的差异点。

植被与水体上空的差异对比

场景单用 NDSI单用 NDCI组合判定主要问题
植被上空厚云云检出,植被也误检云漏检(NDCI 偏负)正确NDCI 单独用会漏云
水体上空薄云云与水体都判正云漏检正确NDSI 单独用会把水体当云
清水体无云误检为云正确排除正确NDSI 对水体敏感

结论:NDSI 负责“抓住云”,NDCI 负责“排除植被与水体”,二者组合才在两类背景上都稳定。实际作业中还应叠加例1的蓝光高反射约束,进一步压制亮沙地等干扰。

阈值调试建议:对整景分别输出 NDCI、NDSI 直方图,观察云峰与背景峰之间的谷底位置;组合阈值取谷底而非均值,可减少薄云漏检。若水体占比大,优先收紧 NDCI 下限。
归一化指数对负反射率或近零分母敏感,务必先做无效值掩膜(如 np.abs(a+b)>1e-6),否则会出现极端值污染阈值统计。