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 完成!")
模拟不同生长阶段¶
通过调整 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 基本单元周期铺开,等效于无限农田。它将服务于:
- BRF 计算:消除场景边缘的光照泄漏
- LiDAR 模拟:机载扫描可能覆盖更大范围
- 大气散射:需要足够大的地表作为下边界
当前 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.Scene、less.Object、less.Terrainless.place.grid()、less.place.random_rotation()less.Prospect、less.Lambertianless.LAIMeasurement、less.Orthographic、less.Perspective、less.OpticalImager