11 - LiDAR 点云模拟¶
本章介绍如何使用 LESS 模拟机载 LiDAR (ALS) 和地面 LiDAR (TLS) 点云数据。
LiDAR 模拟原理¶
LiDAR(光探测与测距)通过发射激光脉冲并记录回波信号来获取三维空间信息。LESS 模拟的过程是:
- 根据扫描仪参数和平台轨迹,计算每个激光脉冲的发射位置和方向
- 对 Mesh 计算表面交点,对
TurbidBoundary计算激光在介质内的传播区间 - 计算脉冲展宽、波束发散、体散射、路径消光和多次回波
- 输出三维点云,也可以保留完整波形
Mesh 与统计植被¶
LiDAR 使用场景中已有的几何和光学属性,不需要创建另一套树冠。显式叶片使用
Mesh;统计树冠使用 TurbidBoundary 和 OpticalVegetation:
tree.add_component(
"crown",
less.TurbidBoundary("tree.obj", group="crown"),
)
tree.set_property(
"crown",
less.OpticalVegetation(
leaf=less.Lambertian(
reflectance=0.35,
transmittance=0.15,
),
leaf_area_density=1.5,
leaf_angle_distribution="spherical",
),
)
统计树冠的回波由沿激光路径连续分布的叶片后向散射形成。地面和 Mesh 回波在 穿过树冠时也会发生衰减。单站 LiDAR 的发射路径与接收路径重合,因此模型使用 同一空隙的相关传播概率,而不是把上下行遮挡当成两个独立事件。这一处理对应 零相位角处的遮挡隐藏效应,不需要用户设置经验热点因子。
Mesh 和 TurbidBoundary 可以出现在同一个场景或同一个 Object 中。三个后端
使用相同的统计树冠波形定义。
LiDAR 系统的三个组件¶
LESS 的 LiDAR 模拟由三个组件构成:
Scanner —— 扫描仪参数¶
import less
scanner = less.Scanner(
beam_divergence=0.5, # 波束发散角 (mrad)
pulse_rate=200_000, # 脉冲重复频率 (Hz)
scan_freq=80.0, # 扫描频率 (Hz)
scan_angle=27.0, # 半扫描角 (°)
wavelengths=[1064], # 激光波长 (nm)
beam_samples=8, # 波束采样数(模拟波束发散)
)
| 参数 | 含义 | ALS 典型值 | TLS 典型值 |
|---|---|---|---|
beam_divergence |
波束发散角 (mrad) | 0.3-0.5 | 0.1-0.3 |
pulse_rate |
脉冲频率 (Hz) | 100k-400k | - |
scan_freq |
扫描频率 (Hz) | 50-200 | - |
scan_angle |
半扫描角 (°) | 15-30 | - |
wavelengths |
激光波长 (nm) | 1064 | 1550 |
beam_samples |
波束采样点数 | 4-16 | 1-4 |
angular_resolution |
TLS 角分辨率 (°) | - | 0.05-0.2 |
Platform —— 平台类型¶
# 无人机
platform = less.UAV(altitude=50.0, speed=5.0)
# 有人飞机
platform = less.Aircraft(altitude=1000.0, speed=60.0)
# 地面三脚架
platform = less.Tripod(height=1.5)
# 车载
platform = less.Vehicle(height=2.5, speed=5.0)
Legs/Positions —— 飞行路径或扫描位置¶
# ALS:定义飞行航线(起点 → 终点)
legs = [
less.Waypoint(-10, 25, active=True), # 起点
less.Waypoint(60, 25, active=True), # 终点
]
# TLS:定义扫描站点
legs = [
less.ScanPosition(
x=25, y=25, # 站点位置
azimuth_range=(0, 360), # 水平扫描范围
zenith_range=(5, 80), # 垂直扫描范围
),
]
完整示例:机载 LiDAR (ALS)¶
import less
import numpy as np
# ── 构建森林场景 ─────────────────────────────────────────────
scene = less.Scene()
scene.size = 50.0
scene.repetitive = False # 周期与 LiDAR 工作流当前均未公共发布
scene.terrain = less.Terrain(property=less.Lambertian(reflectance=0.15))
scene.illumination = less.Illumination(source=less.Sun(zenith=30, azimuth=150), atmosphere=less.NoAtmosphere())
tree = less.Object("ash", mesh=less.examples.asset_path("FREX.obj"))
tree.set_property("leaves", less.Prospect(cab=40, car=10, cw=0.012, cm=0.008, N=1.5))
tree.set_property("stem_branch", less.Lambertian(reflectance=0.08))
rng = np.random.RandomState(42)
positions = less.place.random(scene, n=60, min_dist=3.0, seed=42)
n = len(positions)
scene.add(tree, positions=positions,
scales=less.place.uniform_scale(n, 0.6, 1.5, seed=42),
rotations=less.place.random_rotation(n, seed=42))
sensor_als = less.LiDARSurvey(
scanner=less.Scanner(
beam_divergence=0.5,
pulse_rate=200_000,
scan_freq=80.0,
scan_angle=27.0,
wavelengths=[1064],
beam_samples=8,
),
platform=less.UAV(altitude=50.0, speed=5.0),
legs=[
less.Waypoint(-10, 25, active=True),
less.Waypoint(60, 25, active=True),
],
range_resolution=0.15, # 距离分辨率 (m)
name="ALS LiDAR",
)
# ── 模拟 ─────────────────────────────────────────────────────
print("开始 ALS 模拟...")
pointcloud = scene.simulate(sensor_als)
# ── 保存点云 ─────────────────────────────────────────────────
pointcloud.save("11_als_pointcloud.ply")
print(f"点云大小: {pointcloud.num_points} 个点")
print(f"X 范围: {pointcloud.points[:, 0].min():.1f} ~ {pointcloud.points[:, 0].max():.1f} m")
print(f"Y 范围: {pointcloud.points[:, 1].min():.1f} ~ {pointcloud.points[:, 1].max():.1f} m")
print(f"Z 范围: {pointcloud.points[:, 2].min():.1f} ~ {pointcloud.points[:, 2].max():.1f} m")
配套脚本:
scripts/11_lidar.py
地面 LiDAR (TLS)¶
TLS 从固定站点进行半球扫描,获得高密度的近距离点云:
sensor_tls = less.LiDARSurvey(
scanner=less.Scanner(
beam_divergence=0.2,
wavelengths=[1550],
beam_samples=1,
angular_resolution=0.1, # 角分辨率 0.1°
),
platform=less.Tripod(height=1.5),
legs=[
less.ScanPosition(
x=25, y=25,
azimuth_range=(0, 360), # 全方位扫描
zenith_range=(5, 80), # 5° ~ 80°(避开天顶和地面)
),
],
range_resolution=0.02, # 2 cm 距离分辨率
name="TLS",
)
pointcloud_tls = scene.simulate(sensor_tls)
pointcloud_tls.save("11_tls_pointcloud.ply")
TLS vs ALS 对比¶
| 特性 | ALS (机载) | TLS (地面) |
|---|---|---|
| 视角 | 自上而下 | 自下而上/水平 |
| 覆盖面积 | 大(航线长度) | 小(站点周围) |
| 点密度 | 中等(1-20 pt/m²) | 高(>100 pt/m²) |
| 树冠穿透 | 较好 | 遮挡严重 |
| 典型应用 | 大面积测图、CHM | 树干测量、结构分析 |
点云数据结构¶
PointCloud 对象包含以下属性:
pc = scene.simulate(sensor_als)
# 坐标 (N × 3)
print(pc.points.shape) # (N, 3)
print(pc.points[:5]) # 前 5 个点的 xyz
# 点数
print(pc.num_points) # 返回点总数
# 强度
print(pc.intensity.shape) # (N,)
# 回波次序
print(pc.return_number) # 第几次回波
print(pc.num_returns) # 该脉冲总回波数
获取完整波形¶
pc = scene.simulate(sensor_als, product="waveform")
waveform = pc.waveform["data"]
print(waveform.shape) # pulse × range bin × wavelength
print(pc.waveform["min_range"]) # 第一个距离窗口的起点,m
print(pc.waveform["range_resolution"]) # 距离采样间隔,m
print(pc.waveform["wavelengths"]) # 激光波长,nm
point_cloud 和 waveform 使用同一套传播计算。离散回波是从合并后的表面与
体散射波形中提取的,因此点云中的树冠回波、林下回波和地面回波与保存的波形
保持一致。
多航线扫描¶
真实的机载 LiDAR 任务通常包含多条平行航线:
# 三条平行航线,间距 20m
legs = []
for y_offset in [15, 25, 35]:
legs.extend([
less.Waypoint(-10, y_offset, active=True),
less.Waypoint(60, y_offset, active=True),
])
sensor_multi = less.LiDARSurvey(
scanner=less.Scanner(
beam_divergence=0.5, pulse_rate=200_000,
scan_freq=80.0, scan_angle=20.0,
wavelengths=[1064], beam_samples=8,
),
platform=less.UAV(altitude=50.0, speed=5.0),
legs=legs,
range_resolution=0.15,
name="Multi-strip ALS",
)
应用场景¶
| 应用 | 所需数据 | 方法 |
|---|---|---|
| 冠层高度模型 (CHM) | ALS 点云 | 最高点 - 地面点 |
| LAI 反演 | ALS 点云 | Gap fraction 方法 |
| 单木分割 | ALS/TLS | 分水岭、深度学习 |
| 胸径测量 (DBH) | TLS | 圆拟合 |
| 生物量估算 | ALS | 高度-面积统计 |
下一步¶
相关 API¶
less.Scanner、less.LiDARSurveyless.Aircraft、less.UAV、less.Tripod、less.Vehicleless.Waypoint、less.ScanPositionless.LiDARProduct.save()、less.LiDARProduct.waveforms