第10章 - 地面分类、DEM 与点云栅格化

10.1 从点云到 DEM 的三段路

DEM 不是“点云一插值就出来的图”。中间至少隔三道处理,每一道都会影响最终精度:

原始点云            ┌─ 噪声点剔除 ─┐   ┌─ 地面点识别 ─┐   ┌─ 栅格插值 ─┐
(raw)  ───────────▶│ ELM/outlier  │──▶│ SMRF/PMF/CSF │──▶│ writers.gdal│──▶ DEM
                    └──────────────┘   └──────────────┘   └────────────┘
                                           │
                                           └─▶ 归一化高度 HeightAboveGround

错误会沿链路放大:噪声没清干净,地面分类会拿噪声当“最低点”参考,整片地形被拉低;分类漏掉建筑边缘或误吞植被,DEM 出现台阶与隆起;插值参数选错,把点云问题变成栅格问题。

所以判据是:分类质量决定 DEM 的形状,插值参数只决定分辨率外观。先分类,再调插值。

10.2 地面分类:三种算法的取舍

PDAL 提供三个主流地面滤波:SMRF、PMF、CSF,都把地面点标为 Classification = 2。选型取决于地形与数据特征,不是“哪个新用哪个”。

算法 原理 强项 弱项
filters.smrf 简单形态学 + 可调坡度/阈值 综合表现最好,对城市与山地都稳 参数相对多,极陡地形需调
filters.pmf 渐进形态学开运算 经典方法,平坦地形表现好 官方明确说 SMRF “performs much better”,有退化行为
filters.csf 布料模拟(Cloth Simulation) 反坡地形(如挖方、桥下)表现好 参数物理意义不直观,调参依赖经验

官方在 filters.pmf 页面顶部直接警告:filters.smrf performs much better, with fewer degenerate behaviors. You should prefer this over filters.pmf in most cases.

默认选 SMRF。 只有做算法对比、复现论文结果,或 SMRF 在特定地形上明显失败时才换。CSF 值得独立保留:它应对逆向坡面(河道下切、路堑)的能力是 SMRF 的补充。

10.2.1 filters.smrf:参数与物理含义

[
    "input.laz",
    {
        "type": "filters.smrf",
        "window": 18.0,
        "slope": 0.15,
        "threshold": 0.5,
        "cell": 1.0,
        "scalar": 1.25,
        "returns": "last, only"
    },
    "ground.laz"
]
选项 默认 含义
window 18.0 最大窗口尺寸;决定能识别多大的地物
slope 0.15 坡度(rise/run),控制地形跟随的松紧
threshold 0.5 高程阈值,超过即判为非地面
cell 1.0 栅格化单元尺寸
scalar 1.25 高程缩放系数
cut 0.0 净切割尺寸;0 表示跳过切割步骤
returns “last, only” 参与分类的回波类型
ignore 无 忽略某维度取值范围的区间
classbits 空 选择性忽略 synthetic / keypoint / withheld 点
dir 无 输出中间栅格的调试目录

调法:

window 要大于最大的地物。 城市建筑 20–50 米宽,window 就该在 30–50;林区树冠连片需要更大。太小会把大地物当成地形,DEM 上出现整块隆起。

slope 决定陡坡会不会被误判。 陡坡高差变化快,slope 太小会把坡面判成非地面。官方示例给出的论文优化组合(Pingel Table 3)是 scalar=1.2, slope=0.2, threshold=0.45, window=16.0——slope 比默认大,说明默认值在陡地形上偏保守。

returns 默认只用末次回波与单次回波,因为末次回波更可能打到地面;首回波多打到树冠和屋顶。稀疏植被区末次回波可能不足,可改成 "last, intermediate, only",代价是噪声增加。

cut 保持 0。 它是净切割步骤,能去掉部分地下点,也可能削掉真实沟底。负异常交给 filters.elm 更可控。

10.2.2 三个算法共享的地面类选项

三个算法都引入 ground_cls_opts.md 的三个选项:ground_class(默认 2,地面点分类值)、other_class(默认 1,非地面点分类值)、only_ground(默认 false)。

默认会把非地面点统统设成 1。输入数据若已分好建筑(6)、植被(3/4/5),必须设 only_ground: true,否则下游分类信息全被抹掉。

{
    "type": "filters.smrf",
    "only_ground": true
}

10.2.3 filters.csf:布料模拟

[
    "input.laz",
    {
        "type": "filters.csf",
        "resolution": 1.0,
        "threshold": 0.5,
        "hdiff": 0.3,
        "rigidness": 3,
        "iterations": 500
    },
    "ground_csf.laz"
]
选项 默认 含义
resolution 1.0 布料网格分辨率
threshold 0.5 分类阈值
hdiff 0.3 高差阈值;用于陡坡后处理
smooth true 是否做坡度后处理
step 0.65 时间步长
rigidness 3 布料刚度(1–3)
iterations 500 最大迭代次数
returns “last, only” 回波类型

CSF 的直觉模型:把一块布倒扣在地形上,在重力下下落,被地形最高处托住,最终形状就是地面。rigidness 越大布越硬,越不跟小起伏走;resolution 越小布越密,细节越多但越慢。

它的强项是反坡:SMRF 在“地面被上方物体遮挡”的地形(桥下、路堑)容易把地面判成非地面,CSF 的布料是连续面,能把这类区域补回来。

10.3 抽出地面点

分类完不算结束——filters.smrf 只改 Classification,所有点都还在。要生成 DEM,必须把非地面点真正过滤掉:

{ "type": "filters.expression", "expression": "Classification == 2" }

等价的旧写法是 filters.range + limits: "Classification[2:2]",但它将在 PDAL 3.0 废弃(见第 8 章),新 pipeline 一律用 filters.expression。

顺序陷阱:必须先抽地面再 writers.gdal。直接把 SMRF 输出喂给 writer,非地面点也参与插值,得到的是 DSM 而不是 DEM——不是错误,是需求不同:

产物 用什么点 说明
DEM 仅 Classification == 2 裸地面,去掉建筑与植被
DSM 全部点 含建筑顶与树冠
HAG 两者都要 用地面点算基准,差值即高度

10.4 归一化高度:HAG 的两个算法

有了地面点,就能算每个点相对地面的高度(Height Above Ground)。这是很多下游任务(建筑提取、植被分割、树高统计)的输入。

10.4.1 filters.hag_nn:基于邻域插值

[
    "input.laz",
    { "type": "filters.smrf" },
    {
        "type": "filters.hag_nn",
        "count": 3,
        "max_distance": 50.0,
        "allow_extrapolation": false
    },
    "hag.laz"
]
选项 默认 含义
count 1 用几个地面邻居做插值
max_distance 无 只用该距离内的地面点
allow_extrapolation false 点落在地面点包围盒外时是否外推;false 则 HAG 设 0
class 2 地面点的分类值

count = 1(默认)是“找最近的地面点,用它的 Z 作基准”,粗糙:地面点稀疏时单点误差会传给整片非地面点,建筑密集区建议 3–5。max_distance 是安全阀,避免“用隔壁山头的高度做基准”。

allow_extrapolation: false 有副作用:落在地面点包围盒之外的非地面点,HAG 会被设成 0(数据边缘、水体常见)。统计 HAG 分布时这些 0 值要按 Classification != 2 && HeightAboveGround > 0 过滤掉。

10.4.2 filters.hag_dem:基于已有 DEM

如果已经有一份 DEM(比如上一轮生产的、或外购的),可以直接用它做基准:

[
    "input.laz",
    {
        "type": "filters.hag_dem",
        "raster": "existing_dem.tif",
        "band": 1,
        "nodata_hag": 0.0,
        "zero_ground": true
    },
    "hag.laz"
]

选项:raster(必填,GDAL 可读的 DEM)、band(默认 1)、nodata_hag(默认 0.0)、min_clamp/max_clamp(把 HAG 钳制在区间内)、zero_ground(默认 true,地面点 HAG 直接设 0)、class(默认 2)。

zero_ground: true 很实用:地面点的 HAG 定义上就是 0,没必要查栅格,还避免 DEM 与点云轻微不匹配造成的负值。

关键是 DEM 与点云的坐标系必须一致。不匹配时不报错,只会给出一片毫无意义的 HAG,动手前用 pdal info --summary 分别看两边 SRS。

10.5 writers.gdal:六种统计量与一个必填参数

writers.gdal 是 PDAL 唯一能做“点云 → 栅格”插值的 writer(writers.raster 只能写 filter 已经生成的栅格,不能从点云插值)。它的模型很简单:

对每个栅格单元,取落在“单元中心 + radius”范围内的点,
按 output_type 指定的统计量算出一个值。
[
    "ground.laz",
    {
        "type": "writers.gdal",
        "filename": "dem.tif",
        "resolution": 1.0,
        "output_type": "min",
        "gdaldriver": "GTiff"
    }
]

10.5.1 output_type 的六种取值

默认 output_type = "all",会写 6 个波段:

值 波段含义 典型用途
min 单元内最小值 DEM 首选,最不易被残留地物抬高
max 最大值 DSM、冠层高度
mean 平均值 平滑地形,但会被地物拉高
idw Shepard 反距离加权 点稀疏时更平滑
count 单元内点数 密度图、覆盖度检查
stdev 总体标准差 粗糙度、质量诊断

min 与 mean 的差别就是“残留地物”的差别:分类漏掉一栋楼,min 只在楼的几个像元上错,mean 会把整栋楼的平均高度带进 DEM。DEM 生产默认用 min。

除了六个固定值,还支持百分位波段 p<i>:

{
    "type": "writers.gdal",
    "filename": "dem_p.tif",
    "resolution": 1.0,
    "output_type": "p5,p50,p95",
    "binmode": true
}

百分位只在 binmode: true 时生效(binmode 表示只用落在像元内部的点,不做半径搜索)。

10.5.2 resolution 必填,radius 有默认值

resolution 是像元边长,没有默认值,必须给,漏了直接报错。radius 是搜索半径,默认 resolution × √2;官方点明的陷阱是:圆罩不住整个像元时,有些落在像元内的点根本不会被考虑。点云稀疏或分辨率小时要显式放大:"resolution": 0.5, "radius": 1.0。

10.5.3 空像元与 nodata

没有点落入搜索范围的像元写 nodata,默认值取决于 data_type:double/float/int/short 用 -9999,unsigned int/short 用 9999,unsigned char 用 255,char 用 -128。

要让空像元被邻域插值补上,用 window_size:

{
    "type": "writers.gdal",
    "filename": "dem_filled.tif",
    "resolution": 1.0,
    "output_type": "min",
    "window_size": 2,
    "nodata": -9999
}

window_size 表示允许从多远(以像元为单位)的捐赠像元取值并做反距离加权:1 是 3×3,2 是 5×5。别开太大——它本质是给空洞插值,窗口越大越容易把远处地形拉进来,在山脊陡坎处造假地形。要补洞,先回点云看为什么没有地面点。

其余常用选项:gdaldriver(默认 GTiff)、data_type、dimension(默认 Z)、bounds 或 origin_x/origin_y/width/height(二选一,不能同时给)、override_srs/default_srs、metadata、pdal_metadata、allow_empty。

10.6 完整 DEM 生产 pipeline

把上面的步骤串成一条可复用的生产链路。这是本章的核心示例,覆盖 噪声 → 分类 → 抽地面 → 栅格化 全流程:

[
    {
        "type": "readers.las",
        "filename": "raw.laz"
    },
    { "type": "filters.assign", "value": "Classification = 0" },
    { "type": "filters.elm", "cell": 20.0, "threshold": 0.5 },
    { "type": "filters.outlier", "method": "statistical", "mean_k": 12, "multiplier": 2.5 },
    {
        "type": "filters.smrf",
        "window": 33.0,
        "slope": 0.15,
        "threshold": 0.5,
        "cell": 1.0,
        "scalar": 1.25
    },
    { "type": "filters.expression", "expression": "Classification == 2" },
    {
        "type": "writers.gdal",
        "filename": "dem_1m.tif",
        "resolution": 1.0,
        "radius": 1.5,
        "output_type": "min",
        "gdaldriver": "GTiff",
        "data_type": "float32",
        "nodata": -9999,
        "pdal_metadata": true
    }
]

参数依据:assign 归零清掉上一手可能错误的分类,避免 classbits 受污染;elm 的 cell=20 取平均点间距的若干倍,threshold=0.5 按地形起伏定;outlier 比默认 8/2.0 稍宽,免得把稀疏植被当噪声;window=33 大于城区最大建筑宽度;radius=1.5 显式放大,避免圆罩不住像元;min 最抗地物残留。

验收:pdal info --summary dem_1m.tif 看范围与 SRS,gdalinfo 确认波段数(这里应为 1)。忘了写 output_type 会得到 6 个波段,这是最常见的产出错误。

10.6.1 分块处理大范围数据

一条 pipeline 处理几千平方公里会撞上内存墙,按瓦片分块:

# 按 2000 米瓦片切开,带 50 米重叠避免边缘分类缺邻域
pdal tile "province.laz" "tiles/tile_#.laz" --length 2000 --buffer 50

# 每块单独跑 DEM pipeline
for f in tiles/*.laz; do
    base=$(basename "$f" .laz)
    pdal pipeline dem.json \
        --stage.reader.filename="$f" \
        --stage.writer.filename="dem_$base.tif"
done

两点必须注意:瓦片间留 buffer,否则边界处分类因缺邻域判错,接边出现缝;所有瓦片用同一 origin,保证栅格对齐。

10.7 与 las2dem / LAStools 的取舍

做 DEM 最常被比较的是 LAStools 的 las2dem,能力重叠大但适用场景不同。

维度 PDAL LAStools
许可 BSD 3-Clause,商用无约束 混合,批量处理需授权
流程 声明式 JSON pipeline,可版本化 命令行串联,靠脚本拼接
分类 SMRF/PMF/CSF lasground/lasground_new
插值 像元统计 min/max/mean/idw/count/stdev、百分位 TIN 插值与多种栅格化模式
嵌入 C++/Python 库 以命令行工具为主
大数据 流式 + 分块 + COPC/EPT 远程读 原生多核分块

三种情况:要把处理嵌进自己的系统、流程需要版本化审计、许可要求宽松、或输入是 COPC/EPT 云数据 —— 选 PDAL。团队已熟悉 LAStools 命令行、依赖 las2dem 的 TIN 插值、需要其质检工具链 —— 选 LAStools(注意商用授权)。最常见的是混合:PDAL 做格式转换、去噪、COPC 封装,地面分类与 DEM 按质量要求选工具,最后用 PDAL 统一质检与元数据归档。

一条实践判断值得记:如果 DEM 精度验收没过,先怀疑分类,别急着换插值。换插值算法带来的差异通常小于分类漏掉一片建筑。

10.8 辅助:轮廓与密度

filters.hexbin 生成实际覆盖轮廓(六边形密度面),比矩形范围更贴近真实数据边界。关键参数:edge_length(默认自动估算)、threshold(默认 15,一个六边形至少多少点才算有数据)、sample_size(默认 5000)、boundary/density(输出文件)、ogrdriver(默认 GeoJSON)。

[
    "ground.laz",
    { "type": "filters.hexbin", "edge_length": 100, "boundary": "coverage.geojson" },
    "writers.null"
]

轮廓可直接当 DEM 的有效范围掩膜——矩形 DEM 的四个角常常是 nodata,一裁就干净了。

filters.dem 是另一条栅格化路径:不算统计量,而是把点云转成 GDAL 栅格数据集供 pipeline 内部使用(喂给 filters.hag_dem 或 writers.raster)。DEM 需要在 pipeline 内被二次使用时走这条,而不是落地 TIFF 再读回来。

10.9 排错

现象 原因 处理
输出 TIFF 有 6 个波段 没写 output_type 显式 output_type: "min"
报错缺 resolution 该参数无默认值 必须显式给
DEM 上建筑轮廓清晰 没抽地面点 加 filters.expression: Classification == 2
DEM 整体偏低 噪声被当地面基准 分类前加 elm + outlier
DEM 有台阶状突起 地物边缘被当成地面 加大 window,或换 CSF
大片 nodata 点稀或 radius 太小 调大 radius;必要时 window_size 补洞
原有植被分类全丢 only_ground 默认 false 设 only_ground: true
HAG 大量为 0 allow_extrapolation 默认 false 设 true,或统计时排除
hag_dem 结果无意义 DEM 与点云 SRS 不一致 pdal info --summary 核对
瓦片接边有缝 分块没留 buffer 用 pdal tile --buffer
分类极慢、内存暴涨 SMRF 需全局栅格化 分块;先降采样试参数

最后一条展开:SMRF 内部把整块点云栅格化再做形态学运算,内存与覆盖范围成正比,不可能流式运行(第 1 章的 standard 模式)。100 平方公里的 1 米格网就是 1 亿个栅格单元,跑之前先估内存。

10.10 生产建议

分类参数按地形分档。 城区 window 33–50、林区 50+、山地 18–25 且 slope 0.2+,用命令行覆盖切换:

pdal pipeline dem.json --filters.smrf.window=50 --filters.smrf.slope=0.2

DEM 分辨率由用途定,不由数据密度定。 1:500 用 0.5 米格网,1:2000 用 1 米,区域规划 5–10 米。比数据密度还细只会得到插值出来的假细节。

保留中间产物。 分类后的 LAZ 比 DEM 更有价值:改参数可以重跑插值,重新分类却很贵。把 ground.laz 连同 pipeline JSON 一起归档。

交付带 pdal_metadata。 pipeline 与参数会以 base64 写进 TIFF 元数据,验收方拿到图就能看到用了什么参数。

用轮廓裁掉 nodata 边角。 矩形 DEM 四角通常是空的,用 filters.hexbin 的 boundary 做掩膜。

10.11 小结

主题 关键事实
三阶段 去噪 → 分类 → 插值;分类质量决定 DEM 形状
SMRF 默认 window=18.0、slope=0.15、threshold=0.5、cell=1.0、scalar=1.25、cut=0.0
SMRF 调参 window 必须大于最大地物;陡地形加大 slope;论文优化组合 1.2/0.2/0.45/16
PMF 默认 cell_size=1、slope=1.0、max_window_size=33、initial_distance=0.15、max_distance=2.5;官方建议优先 SMRF
CSF 默认 resolution=1.0、threshold=0.5、hdiff=0.3、rigidness=3、iterations=500
地面类选项 ground_class=2、other_class=1、only_ground=false
抽地面 filters.expression: Classification == 2(range 将废弃)
HAG hag_nn(count=1、class=2)或 hag_dem(band=1、zero_ground=true)
writers.gdal resolution 必填;radius 默认 resolution×√2;output_type 默认 all=6 波段
DEM 统计量 min 最抗地物残留,是 DEM 生产的默认选择
nodata double/float/int/short 用 -9999;unsigned 用 9999;uchar 用 255
补洞 window_size(0 关闭,1=3×3,2=5×5),开大易造假地形
与 LAStools PDAL 强在可嵌入与云数据;las2dem 强在 TIN 插值与工具链