Welcome to Firefly
中文

NVIDIA AIBOX 实现单目深度估计

KKenn

NVIDIA AIBOX 单目深度估计

深度感知是机器人定位、建图、避障以及自动驾驶环境感知的基础能力。常用深度获取方案对比如下:

方案代表硬件主要局限
机械式 LiDAR16/32 线雷达千元级起步,体积与功耗较大,含旋转部件
结构光 / ToFRealSense、Kinect有效距离约 10m 量级,强光与透明、反光物体失效
双目立体视觉双目相机依赖基线标定,长期使用存在标定漂移
单目深度估计任意 USB / CSI 摄像头输出为相对深度;单帧恢复绝对深度属病态问题,精度存在理论上限

单目深度估计不依赖几何约束,而是由卷积神经网络从单张 RGB 图像中学习单目线索——透视关系、遮挡、纹理梯度、物体先验尺寸——到深度的映射,以监督学习方式在大规模"图像-深度"配对数据集(如 NYU Depth v2)上训练。其硬件成本与部署门槛显著低于其他方案:无需专用传感器,无需标定,复用现有摄像头即可。

NVIDIA 系列 AIBOX ​

AIBOX 系列搭载 NVIDIA 原装 Jetson Orin 核心模组:

AIBOX-OrinNXAIBOX-OrinNano
模组Jetson Orin NX 16GBJetson Orin Nano 8GB
AI 性能157 TOPS67 TOPS
GPU搭载 32 个 Tensor Core 的 1024 核 NVIDIA Ampere 架构 GPU搭载 32 个 Tensor Core 的 1024 核 NVIDIA Ampere 架构 GPU
CPU8 核 Arm Cortex - A78 64 位 CPU
2MB L2 + 4MB L3
6 核 Arm Cortex A78 64 位 CPU
1.5MB L2 + 4MB L3
DDR16GB 128 位 LPDDR5 102.4GB/s8GB 128 位 LPDDR5 68 GB/s
HDMI4K@60Hz4K@30Hz

针对深度估计任务,AIBOX 的架构优势有三点:

  1. 统一内存(Unified Memory)。CPU 与 GPU 共享同一物理内存,视频采集、解码与推理之间不存在跨总线数据拷贝。jetson.utils.cudaToNumpy() 可将 GPU 显存中的深度场直接映射为 numpy 数组供 CPU 侧业务逻辑读取,相比 x86 + 独立显卡架构省去了 CPU 与 GPU 之间的 PCIe 拷贝开销。
  2. 异构算力冗余充足。深度估计模型为纯卷积结构,可充分利用 GPU 的 CUDA 核心与 Tensor Core。AIBOX 算力显著高于 FastDepth 论文的实验平台 TX2(Ampere 架构在 FP16/INT8 推理上的吞吐优势尤为明显),运行该类模型后仍有充足算力并发承载检测、跟踪等其他视觉任务。
  3. 多路视频硬解码。AIBOX-OrinNX 的 NVDEC 支持同时硬解码 18 路 1080p30 视频,配合统一内存的零拷贝特性,可在单机上构建"多路解码 + 逐路单目深度估计"的处理管线。

FastDepth 算法原理

网络架构

FastDepth 采用编码器-解码器(encoder-decoder)结构,为全卷积网络(FCN),输入 224×224 RGB 图像,输出同尺寸的单通道深度图。

  • 编码器(Encoder)采用 MobileNet:以 depthwise separable convolution(深度可分离卷积)替代标准卷积,将空间滤波与通道混合解耦为两步执行,计算量降至标准卷积的 1/8~1/9。
  • 解码器(Decoder)为论文核心贡献。与分类任务仅依赖编码器不同,深度估计的解码器需逐级恢复空间分辨率并输出稠密深度图,其计算量无法像编码器那样通过逐层下采样压缩,因此解码器的低延迟设计是嵌入式实时化的关键难点。FastDepth 解码器采用最近邻上采样(nearest-neighbor interpolation)+ depthwise separable convolution的组合恢复分辨率:相比转置卷积(transposed convolution),该设计可避免棋盘伪影(checkerboard artifacts)并进一步压缩计算量;同时通过跳跃连接(skip connection)融合编码器各阶段的特征,保留高分辨率细节。

网络裁剪

在轻量架构的基础上,FastDepth 进一步采用 NetAdapt 进行通道级裁剪。NetAdapt 是平台感知(platform-aware)的裁剪算法:以目标平台实测的逐层延迟为依据,迭代削减各层通道数,在满足延迟约束的前提下尽可能保持精度。类比嵌入式开发中按 MIPS 预算逐级裁剪功能模块的做法:每层的裁剪比例不由人工设定,而是由平台实测延迟反馈迭代确定。

jetson-inference 中的实现

开源项目 jetson-inference 的 depthNet 类实现了 FastDepth 系列模型,内置两个预训练模型,以 ONNX 格式交付,首次运行时由 TensorRT 针对当前硬件编译优化引擎:

模型启用参数编码器特点
FCN-Mobilenet--network=fcn-mobilenet(默认)MobileNet推理速度快,适合实时流
FCN-ResNet18--network=fcn-resnet18ResNet18精度更高,速度略低

模型原始输出为 224×224 单通道浮点深度场(相对深度),可视化阶段经直方图均衡化与伪彩映射上采样至输入图像分辨率。

部署流程

以下步骤在 AIBOX 出厂系统上执行,无需刷机。

编译安装

sudo nvpmodel -m 0
sudo apt-get update
sudo apt-get install git cmake libpython3-dev python3-numpy

git clone --recursive --depth=1 https://github.com/dusty-nv/jetson-inference
cd jetson-inference

mkdir build
cd build
cmake ../
make -j$(nproc)
sudo make install
sudo ldconfig

说明:

  • cmake 阶段会运行 CMakePreBuild.sh 脚本,以交互方式下载预训练模型(含 DepthNet 系列),按提示选择所需模型即可;模型也可事后通过 tools/download-models.sh 补充下载。
  • 编译产物位于 jetson-inference/build/aarch64/,示例可执行文件在 bin/ 子目录;Python 模块(jetson_inference、jetson_utils)随 sudo make install 安装至系统 Python 路径。
  • 若不在本机构建,官方亦提供 Docker 容器方案(docker/run.sh),详见官方文档。

参考:https://github.com/dusty-nv/jetson-inference/blob/master/docs/building-repo-2.md

运行示例

处理示例图片(%i 为通配符序号占位符):

# C++ 示例(位于 build/aarch64/bin)
$ ./depthnet "images/room_*.jpg" images/test/depth_room_%i.jpg

# Python 示例(位于 python/examples)
$ ./depthnet.py "images/room_*.jpg" images/test/depth_room_%i.jpg

8

处理视频流或摄像头(V4L2 设备或 MIPI CSI):

$ ./depthnet /dev/video0      # USB 摄像头
$ ./depthnet.py csi://0       # MIPI CSI 摄像头

首次运行每个模型时,TensorRT 会针对当前硬件编译优化引擎,耗时数分钟属正常现象;引擎编译结果缓存至磁盘,后续运行恢复正常加载速度。

常用参数

参数取值说明
--networkfcn-mobilenet / fcn-resnet18选用模型,默认 fcn-mobilenet
--visualizeinput / depth / input,depth显示内容组合
--depth-size浮点数(默认 1.0)深度图相对输入图像的缩放比例
--filter-modepoint / linear上采样插值方式
--colormapviridis-inverted(默认)等伪彩映射方案

原始深度场访问与尺度标定

伪彩可视化图仅供直观观察。工程应用中需通过 depthNet.GetDepthField() 获取原始深度场——GPU 上的 224×224 单通道浮点矩阵,经统一内存映射为 numpy 数组后可直接在 CPU 侧访问:

import jetson.inference
import jetson.utils
import numpy as np

# load mono depth network
net = jetson.inference.depthNet()

# depthNet re-uses the same memory for the depth field,
# so you only need to map it to numpy once (not every frame)
depth_field = net.GetDepthField()
depth_numpy = jetson.utils.cudaToNumpy(depth_field)

print(f"depth field resolution is {depth_field.width}x{depth_field.height}")

while True:
    img = input.Capture()   # assumes a videoSource stream was created
    net.Process(img)

    # wait for GPU to finish before touching the field from CPU
    jetson.utils.cudaDeviceSynchronize()

    # relative depth statistics of the current frame
    min_depth = np.amin(depth_numpy)
    max_depth = np.amax(depth_numpy)

使用原始深度场需理解以下语义约束:

  1. 输出为相对深度。数值反映画面内各区域的深浅关系,数值范围随场景变化,不具跨场景可比性。伪彩图中的直方图均衡化会进一步拉伸色带,因此伪彩图的色标不可用于测距。
  2. 获取米制深度需尺度标定。常用方法:以场景中已知距离的参照物(人员、货架、地面标线)反解尺度因子;或与 IMU、双目基线等传感模态融合恢复尺度。这是单目方案相对双目/ToF/LiDAR 的本质边界。
  3. 分辨率与上采样伪影。原始深度场为 224×224,上采样至输入分辨率后存在平滑伪影;对空间分辨率有要求的应用应评估该指标是否满足需求。
简体中文