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_blue | R_red | R_green | R_swir | NDCI | NDSI |
|---|---|---|---|---|---|---|
| 浓密植被 | 0.06 | 0.04 | 0.09 | 0.22 | −0.20 | −0.42 |
| 清水体 | 0.08 | 0.05 | 0.07 | 0.02 | −0.23 | +0.56 |
| 厚云 | 0.72 | 0.68 | 0.70 | 0.30 | −0.03 | +0.40 |
| 薄云(水体上空) | 0.30 | 0.26 | 0.28 | 0.10 | −0.07 | +0.47 |
观察:NDCI 在植被上为 −0.20,与厚云 −0.03 差距明显,但水体 −0.23 与植被接近,单用 NDCI 会把水体当“非云”而漏掉水体上空的薄云(−0.07 也偏负)。NDSI 能把厚云、薄云、水体都判为正,却把植被判为负——两者恰好互补。
阈值设定与组合逻辑
从示例分布可读出两条边界: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),否则会出现极端值污染阈值统计。