17 - 由激光雷达点云重建场景¶
less.lidar 把机载、地基或无人机激光雷达点云转换为可以模拟的三维场景:从点云提取地形,分割单木,重建树冠外形,再在树冠内填充叶片。输入 .las 或 .laz 文件,输出一个 less.Scene。
使用前安装可选组件:
本章使用的样地点云:als_plot.las(1.6 MB,约 123 m × 108 m 的林分)。下载后放到工作目录。
处理流程¶
| 步骤 | 函数 | 作用 |
|---|---|---|
| 1 | load_las |
读取点云,转换到 LESS 坐标 |
| 2 | ground_filter |
布料模拟滤波,区分地面点和植被点 |
| 3 | make_rasters |
生成数字高程模型、数字表面模型和冠层高度模型 |
| 4 | segment_trees |
在冠层高度模型上用分水岭方法分割单木 |
| 5 | reconstruct_crowns |
重建树冠外形 |
| 6 | populate_leaves |
在树冠内放置叶片 |
| 6b | populate_understory |
生成林下层(可选) |
from_las() 一次完成全部步骤,也可以逐步调用。
最简用法¶
import less
from less import lidar
scene = lidar.from_las("als_plot.las", lai=2.0, leaf_area=0.2)
scene.illumination = less.Illumination(
source=less.Sun(zenith=30, azimuth=180), atmosphere=less.NoAtmosphere())
sensor = less.OpticalImager(less.Orthographic(resolution=0.5),
bands=[550, 650, 850], quality=32)
brf = scene.simulate(sensor).to_brf()
brf.save("reconstructed.png", bands=[2, 1, 0]) # 近红外、红、绿合成
Orthographic(resolution=0.5) 按 0.5 m 的地面分辨率成像,像元数由场景范围决定。生成的叶片几何默认保存在 <点云文件名>_lidar/ 目录下。
模拟之前可以用 scene.show() 打开查看器,检查地形、树冠和叶片是否合理(见第 6 章)。
主要参数¶
叶面积与叶片¶
scene = lidar.from_las(
"als_plot.las",
lai=2.0, # 整个场景的叶面积指数(含林窗)
leaf_area=0.05, # 单片叶面积(m²)
leaf_shape="square", # "square"(两个三角形)或 "disk"(12 个三角形)
leaf_angle="spherical",
)
叶面积可以用三种方式之一给出:lai(整个场景)、lad(每棵树内的叶面积密度,m² m⁻³)或 lai_per_tree(按树编号分别给出)。叶面积指数较高的大场景会生成几百万片叶子,使用方形叶片并适当增大 leaf_area(0.05–0.1 m²)可以明显减少三角形数量。
叶片总面积的目标值是“叶面积指数 × 场景面积”。叶片数为整数,而且部分叶片可能落在树冠外被舍去,可以用 scene.measure(less.LAIMeasurement()) 检查实际结果。
树冠形状与叶片位置¶
method 决定树冠外形,sampling 决定叶片放在哪里:
method |
说明 | 适用 |
|---|---|---|
convex_hull(默认) |
每棵树取凸包 | 点密度较低的机载数据(每平方米 5 点以下) |
alpha_shape |
按 alpha 阈值筛选的四面体 | 高密度机载或地基数据,能表示树冠的凹陷 |
voxel |
三维体素 | 需要按体素控制叶面积密度 |
sampling |
叶片位置 |
|---|---|
density(默认) |
随机选取该树的激光点,再加高斯抖动 jitter_sigma |
uniform |
在树冠外形内均匀分布 |
默认的 density 方式下叶片跟随点云分布,method 只影响树冠体积,从而影响叶片总数;改用 uniform 时,树冠外形直接决定叶片的分布范围。
林下层¶
scene = lidar.from_las(
"als_plot.las", lai=2.0, leaf_area=0.2,
understory_lai=0.3, # 0 表示不生成
understory_height_range=(0.3, 2.0), # 离地高度(m)
understory_leaf_area=0.02,
understory_leaf_angle="planophile",
)
林下层是一个单独的物体,叶片密度跟随低处激光点的分布。
地形¶
normalize=False(默认)保留真实地形。normalize=True 把每个点的高度减去该处的地面高程,场景使用平地,适合不关心地形起伏的样地模拟。
缓存¶
cache=True 会保存点云分类、栅格、分割和树冠重建的中间结果。之后只改变叶面积、叶片光谱或叶倾角时可以直接复用;点云或重建参数改变后会自动重新计算。
默认值¶
| 参数 | 默认值 | 说明 |
|---|---|---|
resolution |
0.5 | 栅格分辨率(m) |
method |
"convex_hull" |
树冠外形 |
min_tree_height |
2.0 | 冠层高度阈值(m) |
min_tree_distance |
3.0 | 相邻树顶的最小水平距离(m) |
leaf_area |
0.01 | 单片叶面积(m²) |
leaf_shape |
"square" |
叶片形状 |
leaf_angle |
"spherical" |
叶倾角分布 |
understory_lai |
0.0 | 不生成林下层 |
normalize |
False |
保留地形 |
cache |
False |
不缓存 |
write_obj |
False |
只保存二进制几何,不写 OBJ |
output_mode |
"merge" |
所有叶片合并为一个物体;"groups" 按树分组 |
分步调用¶
分步调用可以保存中间结果,例如只保存一次树冠,之后反复尝试不同的叶面积:
import numpy as np
points = lidar.load_las("als_plot.las")
points = lidar.ground_filter(points)
rasters = lidar.make_rasters(points, resolution=0.5)
segments = lidar.segment_trees(rasters, min_height=2.0, min_distance=3.0)
crowns = lidar.reconstruct_crowns(points, segments, method="convex_hull",
rasters=rasters, min_canopy_height=2.0)
crowns.save("plot.crowns")
crowns = lidar.Crowns.load("plot.crowns")
forest, positions = lidar.populate_leaves(crowns, lai=2.0, leaf_area=0.2, out_path="forest.obj")
scene = less.Scene()
scene.size = points.extent
scene.terrain = less.Terrain(property=less.SpectrumDB("dark_soil_mollisol"),
dem=rasters.dem.astype(np.float32))
scene.illumination = less.Illumination(
source=less.Sun(zenith=30, azimuth=180), atmosphere=less.NoAtmosphere())
scene.add(forest, positions=positions)
reconstruct_crowns 需要传入 rasters,用于去掉冠层以下的点。
更换光谱¶
重建后的场景可以直接更换叶片和地表的光谱,不需要重新重建:
scene = lidar.from_las("als_plot.las", lai=1.5, leaf_area=0.2, normalize=True, cache=True)
scene.illumination = less.Illumination(
source=less.Sun(zenith=45, azimuth=180), atmosphere=less.NoAtmosphere())
baseline = scene.simulate(sensor).to_brf()
baseline.save("plot_baseline.png", bands=[2, 1, 0])
scene.objects["forest"].set_property("leaves", less.SpectrumDB("larch_leaf_green"))
scene.terrain.set_property(less.SpectrumDB("loam_brown"))
larch = scene.simulate(sensor).to_brf()
larch.save("plot_larch.png", bands=[2, 1, 0])
project = less.Project(scene=scene, title="Plot 01")
project.add_sensor(sensor, name="Nadir multispectral")
project.save_directory("plot01.less")
适用范围¶
- 只重建叶片,不生成树干和枝条;
- 叶面积需要用户给出,不从点云反演;
- 林下层作为一个整体,不区分单株灌木。