跳转至

17 - 由激光雷达点云重建场景

less.lidar 把机载、地基或无人机激光雷达点云转换为可以模拟的三维场景:从点云提取地形,分割单木,重建树冠外形,再在树冠内填充叶片。输入 .las 或 .laz 文件,输出一个 less.Scene。

使用前安装可选组件:

pip install "less3d[lidar]"

本章使用的样地点云: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")

适用范围

  • 只重建叶片,不生成树干和枝条;
  • 叶面积需要用户给出,不从点云反演;
  • 林下层作为一个整体,不区分单株灌木。

下一步