跳转至

08 - 构建农田场景

本章介绍如何构建规则行列种植的农田场景,以玉米田为例。

农田场景的特点

与森林场景的随机分布不同,农田植物通常按 行列 排列:

  • 行距 (row spacing):相邻行之间的距离(如玉米 0.6-0.75 m)
  • 株距 (in-row spacing):同一行内相邻植株的距离(如玉米 0.2-0.4 m)
  • 植株大小和朝向有一定随机性,但位置基本规则

使用 less.place.grid 创建行列布局

import less

scene = less.Scene()
scene.size = 10.0

# grid() 生成规则网格位置
positions = less.place.grid(scene, spacing=0.75, jitter=0.04)

jitter 参数为每个位置添加微小的随机偏移,使种植不那么"完美",更接近真实田间。

非正方形间距

对于行距和株距不同的情况,我们需要手动构建网格:

import numpy as np

scene_size = 10.0
row_spacing = 0.75      # 行距
in_row_spacing = 0.40   # 株距
margin = 0.5            # 边缘留白

# 计算行数和每行株数
n_rows  = int((scene_size - 2 * margin) / row_spacing) + 1
n_plant = int((scene_size - 2 * margin) / in_row_spacing) + 1

# 构建网格
xs = margin + np.arange(n_rows)  * row_spacing
ys = margin + np.arange(n_plant) * in_row_spacing
gx, gy = np.meshgrid(xs, ys, indexing='ij')

# 添加微小抖动
rng = np.random.RandomState(42)
jitter = 0.04
px = gx.ravel() + rng.uniform(-jitter, jitter, gx.size)
py = gy.ravel() + rng.uniform(-jitter, jitter, gy.size)

positions = np.column_stack([px, py, np.zeros(len(px))])
print(f"总计 {len(positions)} 棵植株 ({n_rows} 行 × {n_plant} 株/行)")

完整示例:玉米田

import less
import numpy as np

# ── 创建场景 ──────────────────────────────────────────────────
scene = less.Scene()
scene.size = 10.0
scene.repetitive = False  # 当前公共默认:有限农田地块

# 土壤:典型壤土
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, canth=0, cbrown=0,
    cw=0.012, cm=0.006, N=1.55,
))

# ── 行列种植 ─────────────────────────────────────────────────
row_spacing = 0.75
in_row_spacing = 0.40
margin = 0.5
rng = np.random.RandomState(42)

n_rows  = int((scene.size - 2 * margin) / row_spacing) + 1
n_plant = int((scene.size - 2 * margin) / in_row_spacing) + 1

xs = margin + np.arange(n_rows)  * row_spacing
ys = margin + np.arange(n_plant) * in_row_spacing
gx, gy = np.meshgrid(xs, ys, indexing='ij')

px = gx.ravel() + rng.uniform(-0.04, 0.04, gx.size)
py = gy.ravel() + rng.uniform(-0.04, 0.04, gy.size)
n = len(px)

positions = np.column_stack([px, py, np.zeros(n)])
scales    = rng.uniform(0.90, 1.10, n)     # ±10% 大小差异
rotations = rng.uniform(0, 360, n)          # 随机朝向

scene.add(maize, positions=positions, scales=scales, rotations=rotations)
print(f"种植 {n} 棵玉米 ({n_rows} 行 × {n_plant} 株/行)")

# ── 构建 ─────────────────────────────────────────────────────
lai = scene.measure(less.LAIMeasurement())
print(f"场景 LAI = {lai:.2f}")

# ── 正射 RGB 图像 ────────────────────────────────────────────
sensor_nadir = less.OpticalImager(
    less.Orthographic(image_size=512),
    bands=[650, 550, 450],
    quality=128,
)
image = scene.simulate(sensor_nadir)
image.save("08_maize_field_nadir.png")

# ── 透视相机(鸟瞰视角)─────────────────────────────────────
half = scene.size / 2
sensor_persp = less.OpticalImager(
    less.Perspective(
        resolution=1024, fov=35,
        position=(half, scene.size + 4, 6),
        target=(half, half, 1.2),
    ),
    bands=[650, 550, 450],
    quality=128,
)
image_persp = scene.simulate(sensor_persp)
image_persp.save("08_maize_field_perspective.png")

print("Tutorial 08 完成!")

配套脚本:scripts/08_crop_field.py

模拟不同生长阶段

通过调整 scales 参数可以模拟不同生长阶段的作物:

# 苗期:缩小到 30%
scales_seedling = np.full(n, 0.3)

# 拔节期:50-60%
scales_jointing = rng.uniform(0.50, 0.60, n)

# 成熟期:90-110%
scales_mature = rng.uniform(0.90, 1.10, n)

如果只改变缩放不改变位置,可通过 InstanceHandle 直接批量更新:

# 初始添加
handle = scene.add(maize, positions=positions, scales=scales_seedling, rotations=rotations)
image_seedling = scene.simulate(sensor_nadir)

# 整批更新到成熟期并提交实例变换
handle.set_scale(scales_mature)         # 数组(长度 = 实例数)
scene.update_instances()
image_mature = scene.simulate(sensor_nadir)

set_scale / set_rotation / set_position 都支持两种调用方式:

形式 用法 用途
单实例 handle.set_scale(0.8, instance=3) 调试 / 单点扰动
批量 handle.set_scale(scales_array) 物候期、生长动态

set_position 还支持传 (N, 3) 数组批量更新所有位置;set_scale 也接受 (N, 3) 数组做非均匀缩放(每个实例 X/Y/Z 各自缩放)。

场景重复的作用

周期边界的目标语义是把 10m × 10m 基本单元周期铺开,等效于无限农田。它将服务于:

  1. BRF 计算:消除场景边缘的光照泄漏
  2. LiDAR 模拟:机载扫描可能覆盖更大范围
  3. 大气散射:需要足够大的地表作为下边界

当前 REPETITIVE_SCENE 已发布(仅限平坦均质地形):True 为无限平铺(wrap 上限 100),整数 >=5 为显式上限。本教程使用默认 False;需要“均质无限冠层”假设的周期基准可直接设置 True

对于有明确边界的单块田(如研究样方),用 scene.repetitive = False 即可——有限场景下的斜射边缘暗化问题已由 silhouette 发射自动修正,不需要再用周期边界绕。

密度与种植参数参考

作物 典型行距 (m) 典型株距 (m) LAI 范围
玉米 0.60-0.75 0.20-0.40 2-5
小麦 0.15-0.25 0.03-0.05 3-7
大豆 0.40-0.60 0.05-0.10 3-6
水稻 0.25-0.30 0.15-0.20 4-8

实际 LAI 取决于植株三维模型的叶面积。上表仅供参考。

下一步

相关 API

  • less.Sceneless.Objectless.Terrain
  • less.place.grid()less.place.random_rotation()
  • less.Prospectless.Lambertian
  • less.LAIMeasurementless.Orthographicless.Perspectiveless.OpticalImager