跳转至

09 - 多光谱与高光谱模拟

本章介绍如何使用 LESS 模拟多光谱和高光谱遥感数据,包括自定义波段、预定义卫星传感器、以及不同大气模型的选择。

光谱波段类型

LESS 提供了多种方式来指定模拟的光谱波段:

离散波段

最简单的方式,直接指定中心波长列表:

import less

# 自定义波段(单位:nm)
bands = [475, 560, 668, 717, 842]

sensor = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=bands,
)

预定义卫星传感器

LESS 内置了常用卫星传感器的光谱响应函数 (SRF):

# Sentinel-2A MSI(13 个波段)
sensor_s2 = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=less.Sentinel2A(),
    spectral_resolution=5,
    quality=128,
    name="Sentinel-2A",
)

# Landsat 8 OLI(11 个波段)
sensor_l8 = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=less.Landsat8_OLI(),
    spectral_resolution=5,
    quality=128,
    name="Landsat-8",
)

使用 SRF 波段时,模拟会考虑光谱响应函数的形状,而不是简单的中心波长。这对宽波段传感器(如 Landsat)更为精确。

高光谱

连续光谱,指定起始波长、终止波长和步长:

# 400-2500 nm,每 10 nm 一个波段(共 211 个波段)
bands_hyper = less.Hyperspectral(start=400, stop=2500, step=10)

sensor_hyper = less.OpticalImager(
    less.Orthographic(image_size=256),  # 高光谱用较小图像加快速度
    bands=bands_hyper,
    quality=64,
    name="Hyperspectral",
)

高光谱模拟的计算量 = 波段数 × 单波段计算量。211 个波段会比 3 个波段慢约 70 倍(LESS 内部有批处理优化,实际比例更小)。

大气模型选择

光照模型决定了入射到场景的太阳辐射光谱。不同的光照模型适用于不同的波段范围:

HosekWilkieAtmosphere —— 可见光天空模型

# 适合 320-720 nm 范围,物理天空辐亮度分布
scene.illumination = less.Illumination(source=less.Sun(zenith=30, azimuth=150), atmosphere=less.HosekWilkieAtmosphere(turbidity=2.5))

特点:天空辐亮度具有真实的角分布(地平线附近更亮、太阳周围有光晕)。但只覆盖可见光波段。

SimpleSpectralAtmosphere —— 宽波段大气模型

# 适合 300-2500 nm 全波段
scene.illumination = less.Illumination(source=less.Sun(zenith=30, azimuth=150), atmosphere=less.SimpleSpectralAtmosphere(turbidity=2.5))

特点:内置 Rayleigh 散射、气溶胶消光、臭氧和水汽吸收的大气模型。天空散射光为各向同性。覆盖全短波范围,适合多光谱和高光谱模拟。

NoAtmosphere —— 真空

# TOA 直射不衰减,也不产生天空散射
scene.illumination = less.Illumination(
    source=less.Sun(zenith=30, azimuth=150),
    atmosphere=less.NoAtmosphere(),
)

特点:真空边界不会衰减 TOA 直射,也不会生成漫射。若已知大气直射和漫射传输系数,使用 PrescribedAtmosphere

Atmosphere —— 原生大气传输

# 太阳几何与大气状态分别设置
scene.illumination = less.Illumination(
    source=less.Sun(zenith=30, azimuth=150),
    atmosphere=less.Atmosphere.standard(
        "midlatitude_summer",
        aerosol="continental",
        aot550=0.2,
        ground_altitude_km=0.0,
    ),
)

原生模型计算 300–2500 nm 的气体吸收、Rayleigh 散射、气溶胶散射以及直射/漫射多次散射。普通安装已经包含模型和参数数据库。大气层顶成像、参数含义和适用范围见原生大气与大气层顶成像

大气剖面选项: | 参数值 | 含义 | |--------|------| | "tropical" | 热带 | | "midlatitude_summer" | 中纬度夏季 | | "midlatitude_winter" | 中纬度冬季 | | "subarctic_summer" | 亚北极夏季 | | "subarctic_winter" | 亚北极冬季 | | "us_standard" | 美国标准大气 |

气溶胶类型: | 参数值 | 含义 | |--------|------| | "continental" | 大陆型 | | "maritime" | 海洋型 | | "urban" | 城市型 | | "desert" | 沙漠型 | | "no_aerosols" | 无气溶胶 |

less.SixSAtmosphere 使用 6S,需要另行安装 less3d[atmosphere]

完整示例:多传感器模拟

import less
import numpy as np

# ── 构建玉米田场景 ───────────────────────────────────────────
scene = less.Scene()
scene.size = 10.0
scene.repetitive = False  # REPETITIVE_SCENE 尚未公共发布

scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
scene.illumination = less.Illumination(source=less.Sun(zenith=30, azimuth=150), atmosphere=less.NoAtmosphere())

maize = less.Object("maize", mesh=less.examples.asset_path("maize.obj"))
maize.set_property(less.Prospect(cab=45, car=10, cw=0.012, cm=0.006, N=1.55))

# 行列种植
rng = np.random.RandomState(42)
row_sp, plant_sp, margin = 0.75, 0.40, 0.5
n_rows  = int((10.0 - 2 * margin) / row_sp) + 1
n_plant = int((10.0 - 2 * margin) / plant_sp) + 1
xs = margin + np.arange(n_rows) * row_sp
ys = margin + np.arange(n_plant) * plant_sp
gx, gy = np.meshgrid(xs, ys, indexing='ij')
positions = np.column_stack([
    gx.ravel() + rng.uniform(-0.04, 0.04, gx.size),
    gy.ravel() + rng.uniform(-0.04, 0.04, gy.size),
    np.zeros(gx.size),
])
scene.add(maize, positions=positions,
          scales=rng.uniform(0.9, 1.1, len(positions)),
          rotations=rng.uniform(0, 360, len(positions)))
sensor_rgb = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=[650, 550, 450],
    quality=128, name="RGB",
)
img_rgb = scene.simulate(sensor_rgb)
img_rgb.save("09_rgb.png")

# ── 2. Sentinel-2A 模拟 ─────────────────────────────────────
sensor_s2 = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=less.Sentinel2A(),
    spectral_resolution=5,
    quality=128, name="Sentinel-2A",
)
img_s2 = scene.simulate(sensor_s2)
img_s2.save("09_sentinel2a.tif")

# 计算 NDVI(Sentinel-2A: B4=红 index 3, B8=NIR index 7)
data_s2 = img_s2.data
red_s2 = data_s2[:, :, 3]   # B4 (665 nm)
nir_s2 = data_s2[:, :, 7]   # B8 (842 nm)
ndvi_s2 = (nir_s2 - red_s2) / (nir_s2 + red_s2 + 1e-10)
print(f"Sentinel-2A NDVI: {np.nanmean(ndvi_s2):.3f} (mean)")

# ── 3. 高光谱 ────────────────────────────────────────────────
sensor_hyper = less.OpticalImager(
    less.Orthographic(image_size=256),
    bands=less.Hyperspectral(start=400, stop=1000, step=5),
    quality=64, name="Hyperspectral",
)
img_hyper = scene.simulate(sensor_hyper)
img_hyper.save("09_hyperspectral.tif")

# 提取场景平均光谱
data_hyper = img_hyper.data
mean_spectrum = np.mean(data_hyper, axis=(0, 1))
print(f"高光谱波段数: {mean_spectrum.shape[0]}")
print(f"平均辐亮度 (500 nm): {mean_spectrum[20]:.4f} W/m²/sr/nm")

print("Tutorial 09 完成!")

配套脚本:scripts/09_multispectral.py

NDVI 计算示例

归一化植被指数 (NDVI) 是最常用的植被指数,利用红光和近红外波段的差异:

NDVI = (NIR - Red) / (NIR + Red)
# 使用自定义波段
sensor = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=[650, 842],    # Red, NIR
    quality=128,
)
image = scene.simulate(sensor)
data = image.data

red = data[:, :, 0]
nir = data[:, :, 1]
ndvi = (nir - red) / (nir + red + 1e-10)

# NDVI 典型范围
# 裸土:   0.05 ~ 0.15
# 稀疏植被:0.15 ~ 0.40
# 密集植被:0.40 ~ 0.90

光谱分析:植被红边

红边(Red Edge, ~700-750 nm)是植被光谱最显著的特征之一——从红光的强吸收到近红外的高反射之间的急剧跃变。

使用高光谱模拟可以精确捕捉红边特征:

# 红边区域细致采样
sensor_rededge = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=less.Hyperspectral(start=650, stop=800, step=2),  # 2 nm 分辨率
    quality=128,
)
image_re = scene.simulate(sensor_rededge)
data_re = image_re.data

# 提取某个像元的光谱
pixel_spectrum = data_re[256, 256, :]
wavelengths = np.arange(650, 800, 2)

# 红边位置 = 一阶导数最大值对应的波长
deriv = np.gradient(pixel_spectrum, 2)  # 2 nm 步长
red_edge_pos = wavelengths[np.argmax(deriv)]
print(f"红边位置: {red_edge_pos} nm")

下一步

相关 API

  • less.SpectralBandsless.Hyperspectral
  • less.Sentinel2Aless.Landsat8_OLI
  • less.OpticalImagerless.Orthographic
  • less.Atmosphereless.HosekWilkieAtmosphere
  • less.SimpleSpectralAtmosphereless.PrescribedAtmosphereless.NoAtmosphere