GeoAI 树冠高度估算指南:基于 DINOv2 + DPT 的高分辨率 RGB 遥感影像 canopy height 制图
GeoAI 树冠高度估算指南:基于 DINOv2 + DPT 的高分辨率 RGB 遥感影像 canopy height 制图
本指南围绕 GeoAI 项目中的 geoai.canopy 模块展开,讲解如何仅凭 RGB 航拍/卫星影像直接预测逐像元的树冠高度(单位:米),支撑林业分析、碳储量估算与城市树木覆盖评估。读完本文,你将掌握模型变体选型、CanopyHeightEstimation 类与 canopy_height_estimation() 便捷函数的完整用法、瓦片化推理的参数调优,以及基于源码实现的底层原理。
模块定位与应用场景
docs/canopy.md 对 canopy 模块的定位十分清晰:利用深度学习模型从卫星影像中进行树冠高度估算,并且支持大规模预测,典型应用包括:
- 林业分析(forestry analysis):快速生成区域尺度的树冠高度图,替代或补充大范围 LiDAR 测量;
- 碳储量估算(carbon stock estimation):树冠高度是生物量与碳储量反演的关键输入;
- 城市树木覆盖评估(urban tree cover assessment):为城市规划与生态监测提供量化指标。
在 GeoAI 项目中,该模块的实现位于 geoai/canopy.py(约 1400 行),配套的实战 Notebook 为 docs/examples/canopy_height.ipynb,后者以 NEON 森林站点航拍影像 + LiDAR 真值 CHM(Canopy Height Model)演示了从数据下载到结果可视化的完整流程。
技术原理:自监督 ViT 骨干 + DPT 解码器
geoai.canopy 集成了 Meta(FAIR)的 HighResCanopyHeight 模型,核心架构为:
- DINOv2 骨干网络:使用自监督 Vision Transformer 提取多尺度视觉特征;
- DPT(Dense Prediction Transformer)解码器:将多尺度特征重组、融合并逐级上采样,最终输出与输入同分辨率的逐像素高度图。
模型在 NAIP 航拍影像 + 机载 LiDAR 真值 上训练,论文出处为 Tolan 等人 2024 年发表于 Remote Sensing of Environment 的工作("Very high resolution canopy height maps from RGB imagery using self-supervised vision transformer and convolutional decoder trained on Aerial Lidar"),这一信息在 geoai/canopy.py 的模块 docstring 中有明确记载。
从源码结构看,模型组件以 _ 前缀私有类的形式完整内嵌在模块内,无需额外依赖即可独立运行:
_SSLVisionTransformer:在_DinoVisionTransformer基础上按out_indices(默认(4, 11, 17, 23))输出四层中间特征,并支持frozen_stages冻结策略与任意尺寸输入的自适应 padding(_AdaptivePadding);_DPTHead:包含_ReassembleBlocks(多尺度特征重组)、_PreActResidualConvUnit(预激活残差卷积)与_FeatureFusionBlock(特征融合 + 2 倍上采样),最终经_HeadDepth输出高度;_SSLAE:将骨干与解码头组合成完整的自监督编码-解码模型,支持huge=True(embed_dim=1280、32 层)与标准版(embed_dim=1024、24 层)两种规格。
推理前,输入图像会按模型训练时的统计量做归一化(geoai/canopy.py):
self._norm_mean = [0.420, 0.411, 0.296]
self._norm_std = [0.213, 0.156, 0.143]
模型原始输出为归一化高度值,predict() 会乘以 scale_factor(默认 10.0)并经过 ReLU 截断(非负)后,换算成以米为单位的树冠高度(见 geoai/canopy.py)。
可用模型变体与选型建议
模块内置 4 个官方模型变体,定义于 geoai/canopy.py 的 MODEL_VARIANTS 常量中,可通过 list_canopy_models() 随时查询:
| 模型名称 | 文件大小 | 压缩 | 适用场景 |
|---|---|---|---|
compressed_SSLhuge(默认) |
749M | 是(INT8 动态量化) | CPU 友好,通用性最佳 |
SSLhuge_satellite |
2.9G | 否 | 卫星影像效果最佳,需 GPU |
compressed_SSLlarge |
400M | 是 | CPU 友好的消融/轻量模型 |
compressed_SSLhuge_aerial |
749M | 是 | 在航拍影像上微调,适合 NEON/NAIP 等航拍数据 |
选型建议(与 Notebook 一致):航拍影像(NEON、NAIP)选 compressed_SSLhuge_aerial;卫星影像(如 Maxar)选 SSLhuge_satellite;通用场景默认 compressed_SSLhuge 即可。
需要说明的源码细节:压缩模型(compressed=True)的 checkpoint 存的是量化后的 state dict,加载时先在 CPU 上通过 torch.quantization.quantize_dynamic 得到 qint8 量化模型以对齐键名;若目标设备是 GPU,则会反量化出 float32 权重并装入全新浮点模型(geoai/canopy.py)。此外,checkpoint 加载统一使用 weights_only=True 限制反序列化,避免任意 Python 对象执行(安全性设计,见 geoai/canopy.py)。
安装与依赖
geoai.canopy 的硬性依赖为 PyTorch(torch / torchvision)与 rasterio,缺失时导入模块会直接抛出带安装提示的 ImportError(geoai/canopy.py):
pip install torch torchvision rasterio
GeoAI 本身对 canopy 相关导出采用惰性导入机制,geoai.canopy 只有在实际使用时才被加载(见 geoai/init.py 中 CanopyHeightEstimation、canopy_height_estimation、list_canopy_models 三个符号的懒加载注册),因此无需担心导入整个 geoai 包带来的额外开销。
模型 checkpoint 默认缓存到 ~/.cache/geoai/canopy/(即 DEFAULT_CACHE_DIR,geoai/canopy.py),首次使用时自动下载,已存在则直接复用,不会重复下载。
快速上手:完整预测流程
1. 导入与查看可用模型
import geoai
from geoai.canopy import CanopyHeightEstimation, list_canopy_models
models = list_canopy_models()
for name, desc in models.items():
print(f"{name:30s} -> {desc}")
list_canopy_models() 返回 {模型名: 描述} 字典,可方便地核对各变体适用场景。
2. 获取示例数据
Notebook 使用 NEON 的 WLOU 森林站点数据:一张 RGB 航拍影像与一张对应的 LiDAR 真值 CHM,通过 geoai.download_file() 下载:
image_path = geoai.download_file("<RGB 影像 URL>")
chm_path = geoai.download_file("<CHM 真值 URL>")
download_file 位于 geoai/utils/download.py,支持进度条显示、断点重名跳过(overwrite=False 时文件已存在则直接返回)与 ZIP 自动解压,适合在 Notebook 中交互式取数。
3. 初始化模型
# 航拍数据选 aerial 微调版;默认 checkpoint 首次使用自动下载(约 749 MB)
estimator = CanopyHeightEstimation(model_name="compressed_SSLhuge_aerial")
CanopyHeightEstimation 的构造参数(geoai/canopy.py)包括:
model_name:模型变体,见上文选型表;checkpoint_path:本地 checkpoint 路径,传入后跳过自动下载(测试TestCanopyInit验证了该分支,见 tests/test_canopy.py);device:'cpu'、'cuda'、'cuda:0'等;不传则自动选择——CUDA 可用时用cuda,否则退回cpu;cache_dir:checkpoint 缓存目录,默认~/.cache/geoai/canopy/。
4. 执行预测
output_path = "canopy_height_output.tif"
height_map = estimator.predict(
image_path,
output_path=output_path,
batch_size=4,
)
predict() 返回形状为 (height, width) 的 2D numpy 数组,每个像素值即树冠高度(米)。predict 的完整参数(geoai/canopy.py):
| 参数 | 默认值 | 说明 |
|---|---|---|
input_path |
必填 | 输入 RGB GeoTIFF 路径 |
output_path |
None |
输出 GeoTIFF 路径;为 None 时仅返回数组 |
tile_size |
256 |
瓦片尺寸,模型期望 256×256 输入 |
overlap |
128 |
相邻瓦片重叠像素数,越大拼接越平滑但计算量越大;合法范围为 0 <= overlap < tile_size |
batch_size |
4 |
单批处理的瓦片数,越大越吃显存/内存但速度更快 |
scale_factor |
10.0 |
模型输出换算为米的倍率,由原模型设定 |
5. 检查与可视化结果
print(f"Height map shape: {height_map.shape}")
print(f"Height range: {height_map.min():.2f} - {height_map.max():.2f} meters")
print(f"Mean height: {height_map.mean():.2f} meters")
visualize() 方法可一键生成对比图(geoai/canopy.py):
# 传入原图 + 高度数组:并排显示“输入影像 | 预测高度图”
fig = estimator.visualize(image_path, height_map, cmap="viridis")
# 只传入高度 GeoTIFF:单独显示高度图
fig = estimator.visualize(output_path, cmap="viridis")
常用参数:figsize=(16, 6)、cmap='viridis'(默认)、vmin=0、vmax(默认取高度正值的 98 分位数,抗离群值)、title 与 output_path(保存图件,dpi=150)。visualize 内部对 uint8/uint16/float 影像分别做归一化后再用 matplotlib 渲染,色条统一标注 "Height (meters)"。
6. 便捷函数:一行完成预测
对于一次性快速预测,可直接调用 canopy_height_estimation()(geoai/canopy.py),其内部等价于“创建实例 + predict”两步:
from geoai.canopy import canopy_height_estimation
height_map2 = canopy_height_estimation(
image_path,
output_path="canopy_height_output2.tif",
model_name="compressed_SSLhuge_aerial",
)
大规模影像的瓦片化推理原理
面对大范围影像,predict() 并非整幅喂给模型,而是按 tile_size 切块、batch_size 分批推理后再无缝拼合。其实现逻辑(geoai/canopy.py)值得注意:
- 网格划分:
step = tile_size - overlap,行/列数按ceil((尺寸 - overlap) / step)计算,并对影像边缘做零填充以对齐网格; - 重叠加权混合:每个瓦片乘上一个 raised-cosine(升余弦)权重图 再累加——中心区域权重为 1,向边缘按余弦斜坡衰减到 0(
_make_weight_map,geoai/canopy.py),重叠区加权平均后消除瓦片接缝伪影; - 归一化平均:累加后除以权重计数,裁回原始尺寸输出。
_make_weight_map 的正确性在 tests/test_canopy.py 中有专门测试:中心为 1、四角接近 0、上下/左右对称、数值落在 [0,1] 区间、overlap<=0 时退化为全 1 矩阵。这些测试同时印证了输入归一化(_normalize_image)、checkpoint 安全加载(拒绝含 pickle 回调的对象)等关键路径的行为。
另外两个输入约束来自源码:
- 波段要求:输入 GeoTIFF 必须至少 3 个波段,
predict()取前 3 个作为 RGB,否则抛出ValueError; - 数值范围归一化:uint8 除以 255、uint16 除以 65535、float 数据若最大值 >1 则按最大值归一化,统一转换到 [0,1] 区间再送入模型。
输出 GeoTIFF 的格式细节
当指定 output_path 时,结果会写成保留原影像地理参考(profile 继承自输入)的单波段 GeoTIFF,属性更新为(geoai/canopy.py):
dtype="float32":高度值以 32 位浮点存储;count=1:单波段输出;compress="lzw":无损压缩,显著减小大区域成果文件体积;nodata=0:无数据值标记为 0(与高度非负的输出特性一致);- 同时移除可能继承自 RGB 输入的
photometric(如 RGB)设置,避免写单波段图时出错。
这意味着预测结果可以直接在 QGIS、ArcGIS 等 GIS 软件中叠加原始影像查看,无需额外配准。
依赖声明与懒加载验证
geoai.canopy 在包级导出中注册了 3 个公开符号(geoai/init.py),通过 __getattr__ 惰性加载,避免导入 geoai 时即拉取 PyTorch/rasterio 重依赖。模块自身的 __all__ 导出(geoai/canopy.py)包含 CanopyHeightEstimation、canopy_height_estimation、list_canopy_models、MODEL_VARIANTS、DEFAULT_CACHE_DIR 五项,tests/test_canopy.py 对导入行为、函数签名、构造参数、未知模型报错等均有覆盖,可作为 API 契约的权威参考。
使用注意事项与限制
- 默认 checkpoint 体积较大:首次使用
compressed_SSLhuge需下载约 749 MB;请预留足够磁盘空间与网络带宽,下载失败时模块会清理部分文件并给出可手动下载的提示; SSLhuge_satellite需要 GPU:2.9G 全量模型在 CPU 上推理不现实,若机器无 CUDA 设备请改用压缩变体;- overlap 参数约束:
overlap必须满足0 <= overlap < tile_size,否则predict()直接抛ValueError;追求更平滑的拼接效果可在默认 128 基础上增大(上限tile_size // 2附近),代价是更多计算量; - 结果解读:输出单位为米,且经 ReLU 截断保证非负;与机载 LiDAR 真值 CHM 对比时,建议参照 Notebook 的做法按 98 分位数设置显示上限,避免极端值拉伸色带。
继续深入
- 完整可运行示例:docs/examples/canopy_height.ipynb(含 NEON 数据下载、真值对比、三图并排可视化)
- 模块源码:geoai/canopy.py(模型结构、量化加载、瓦片拼接全实现)
- 模块测试:tests/test_canopy.py(API 契约与关键算法行为验证)
- 模块文档:docs/canopy.md