wikillm

Paper · 2024-03-22 · cs.CV

Metric3D v2: A Versatile Monocular Geometric Foundation Model for Zero-shot Metric Depth and Surface Normal Estimation

Predicts metric depth and surface normals from one RGB image, given the focal length. Training images are mapped to one canonical focal length, and a ConvGRU block refines depth and normal jointly, with a depth-normal consistency loss so normals can learn from depth-only data. Author-reported: NYUv2 zero-shot AbsRel 0.067 (ViT-g), KITTI zero-shot AbsRel 0.051 (ViT-g).

Monocular depth estimationSurface normal estimationFoundation modelVision Transformer (ViT) backbonesmonocular-depthsurface-normalsmetric-depthzero-shotgeometric-foundation-model

arXivPDFCodeMarkdown14 min read

AI-compiled by claude-haiku-5-5 on 2026-10-19, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.

In plain words

A single photo does not tell you how far away things are. A telephoto shot of a small object and a wide-angle shot of a large one can look the same, so the camera's focal length is the missing piece. This paper's models estimate both the distance to every pixel and the direction each surface faces, in real units, from one image.

The trick is to make the training data look as if every photo came from one standard camera. The photos are resized or the distance labels are rescaled to that camera. The network then learns one consistent notion of scale. At use time the model's output is scaled back to the real camera.

Surface direction is harder to label at scale, because good labels need dense 3D reconstructions. The authors let the direction estimate learn from the distance estimate: the two are refined together in a loop, and a consistency term ties them. That lets the direction part use the much larger pool of distance-only data.

TL;DR

  • Predicts metric depth (in metres) and per-pixel surface normals from one RGB image, assuming the focal length is known §3.1.
  • Canonical camera transform: training images or depth labels are rescaled to one canonical focal length, so one model handles many camera models §3.2.
  • Depth and normal are refined jointly with recurrent ConvGRU updates, and a depth-normal consistency loss lets normals learn from depth-only data §3.3 §3.4.
  • Author-reported: strong zero-shot results on NYUv2 and KITTI, and state of the art on many depth and normal benchmarks §1 tab. 1 tab. 2 tab. 4.

Problem

Metric depth models trained on one dataset's camera do not transfer to other cameras. Affine-invariant depth models generalize better but cannot give real distances §1. Normal estimators have little zero-shot ability because outdoor normal labels are scarce; the authors report under 20K outdoor normal-labelled images against about 9488K depth-labelled ones §3.3. Because the focal length is unknown, the same image can correspond to different distances §3.1 fig. 6 fig. 7. The authors assume known focal lengths for training and test images §3.1. Sensor size does not change metric depth, but focal length does §3.1.

Method

Pipeline overview fig. 4 fig. 9:

  1. Canonical camera transform §3.2. Two variants. In CSTM_label, the ground-truth depth is multiplied by \omega_d = f^c / f and the image is unchanged §3.2. In CSTM_image, the image is resized by \omega_r = f^c / f and the depth label is resized spatially without value scaling §3.2. The canonical focal length is f^c = 1000 px §4 [supp §1.2]. A random patch crop follows; it changes only the field of view and principal point §3.2.
  2. Encoder and decoder §4 [supp §1]. Two families are described: a ConvNeXt-L encoder (ImageNet-22K) with an AdaBins-style decoder and hourglass head, and DINOv2-with-registers ViTs with DPT decoders. The decoder outputs a low-resolution depth \hat{D}_c and an unnormalized normal \hat{N}_u at H/4 \times W/4, plus hidden features §3.3.
  3. Joint recurrent refinement §3.3 fig. 10. Starting from the decoder's outputs, T+1 steps update hidden features with ConvGRU sub-blocks eq. 2 eq. 4. Two projection heads predict additive updates for depth and normal from the hidden state eq. 3 eq. 4. The paper gives 4 steps for ViT-S and 8 for ViT-L and ViT-g [supp tab. 1].
  4. Output §3.3 eq. 5. After upsampling, depth passes through a ReLU to keep it non-negative, and normals are normalized to unit length.
  5. De-canonical transform §3.2. In the label variant the predicted depth is divided by \omega_d. In the image variant the prediction is resized back to the original size without scaling.

Losses §3.4:

  • Depth: L_d = L_{PWN} + L_{VNL} + L_{silog} + L_{RPNL} eq. 9. PWN is the pair-wise normal regression loss, VNL the virtual normal loss, and silog the scale-invariant log loss §3.4.
  • Random proposal normalization loss (RPNL), the paper's main depth addition eq. 8. It takes M random patches, each with a side between 0.125 and 0.5 of the image, normalizes the ground-truth and predicted patch values by their median and mean absolute deviation, and averages the absolute difference. The paper sets M = 32 §3.4.
  • Normal: when ground-truth normals exist, an aleatoric-uncertainty-aware loss is used §3.4. Otherwise a depth-normal consistency loss compares the predicted normal with a pseudo-normal from the predicted depth, computed by least squares §3.4. The consistency term uses real-world depth, not the canonical depth §3.4.
  • Total: L = w_d L_d + w_n L_n + w_{d-n} L_{d-n} with w_d = 0.5, w_n = 1, w_{d-n} = 0.01 eq. 10.
Pseudocode
# Canonical camera transform, label variant  [§3.2]
def to_canonical_label(depth_gt, fx, canonical_focal=1000.0):
    ratio = canonical_focal / fx     # omega_d = f^c / f  [§3.2]
    depth_c = ratio * depth_gt       # supervise in canonical space  [§3.2]
    return depth_c                   # image is unchanged  [§3.2]
Pseudocode
# Canonical camera transform, image variant  [§3.2]
def to_canonical_image(image, depth_gt, fx, canonical_focal=1000.0):
    ratio = canonical_focal / fx     # omega_r = f^c / f  [§3.2]
    image_c = resize(image, ratio)   # principal point scales too  [§3.2]
    depth_c = resize(depth_gt, ratio)  # resize only, no value scaling  [§3.2]
    return image_c, depth_c
Pseudocode
# Joint depth-normal refinement, T+1 steps  [§3.3] [eq. 2] [eq. 3] [eq. 4]
# Input:  depth0 (H/4, W/4), normal0 (H/4, W/4, 3), hidden0 (decoder)
# Output: depth (H/4, W/4), normal (H/4, W/4, 3), unit-length after upsample

def refine_depth_and_normal(depth0, normal0, hidden0, steps):
    depth = depth0
    normal = normal0
    hidden = hidden0
    for step in range(steps):
        # ConvGRU updates the hidden state from the current predictions [eq. 4]
        hidden = conv_gru(depth, normal, hidden0, hidden)
        # Two projection heads predict additive updates from hidden [eq. 4]
        delta_depth = head_depth(hidden)
        delta_normal = head_normal(hidden)
        # Apply the updates additively  [eq. 3]
        depth = depth + delta_depth
        normal = normal + delta_normal
    return depth, normal
Pseudocode
# Random proposal normalization loss  [§3.4] [eq. 8]
# M = 32 proposals; crop side 0.125 to 0.5 of the image  [§3.4]

def rpnl(pred, gt, num_proposals=32):
    total = 0.0
    for patch in random_patches(pred, gt, num_proposals):
        # Median-centre and divide by mean absolute deviation  [eq. 8]
        gt_norm = normalize_by_mad(gt[patch])
        pred_norm = normalize_by_mad(pred[patch])
        # Mean absolute difference over the patch's pixels  [eq. 8]
        total = total + mean(abs(gt_norm - pred_norm))
    return total / num_proposals
Pseudocode
# Total training loss  [eq. 10]
# Normal term: aleatoric NLL with GT normals, else consistency  [§3.4]

def training_loss(depth_c, normal, gt_depth_c, gt_normal, depth_metric):
    loss_depth = depth_loss(depth_c, gt_depth_c)     # eq. 9  [§3.4]
    if gt_normal is not None:
        loss_normal = normal_nll(normal, gt_normal)  # [§3.4]
    else:
        loss_normal = 0.0
    loss_consistency = depth_normal_consistency(depth_metric, normal)
    # Weights w_d, w_n, w_{d-n} as stated in the paper  [eq. 10]
    return 0.5 * loss_depth + 1.0 * loss_normal + 0.01 * loss_consistency

Hyperparameters

Values in the code are from the released training configs at code_commit. Paper values come from the main text (§4) and supplement ([supp §1]).

NameValueWhereNotes
Canonical focal length f^c1000 px§4 [supp §1.2] vit.raft5.giant2.pyMain-text ablation tests 250, 500, 1000, 1500, 2500 §4; supplement tests 250 to 2000 [supp §1.2]
Depth range, ViT[0.1 m, 200 m][supp §1] vit.raft5.giant2.pyUsed as the depth normalization bounds
Depth range, ConvNeXt[0.3 m, 150 m], 256 bins[supp §1] convlarge.0.3_150.pyPaper says AdaBins-style decoder with 256 bins
Input crop, ViT616 × 1064§4 vit.raft5.giant2.pyMultiple of 28
Input crop, ConvNeXt512 × 960 (paper) / 544 × 1216 (code)§4 convlarge.0.3_150.pyPaper and code disagree; see Paper vs code
Refinement steps T+14 (ViT-S), 8 (ViT-L, ViT-g)[supp tab. 1] vit.raft5.small.py vit.raft5.giant2.pyPaper says the best step count depends on size tab. 13
Hidden feature scales1/14, 1/7, 1/4[supp §1]Three ConvGRU sub-blocks
OptimizerAdamW§4 vit.raft5.large.pyCode: \beta = (0.9, 0.999), \epsilon = 10^{-6}; paper gives no betas or epsilon
Learning rate, encoder1e-5 (ViT-S, ViT-L), 8e-6 (ViT-g)vit.raft5.large.py vit.raft5.giant2.pyPaper says 1e-4 "for all layers" §4
Learning rate, decoder1e-4vit.raft5.large.pyMatches the paper's 1e-4
Weight decay1e-3 (encoder), 0.01 (decoder)vit.raft5.large.pyNot stated in paper
LR schedulePolynomial, power 0.9, min 1e-6§4 vit.raft5.large.pyPolynomial decay is stated in §4
WarmupLinear, 500 iters (ViT-S, ViT-L), 1000 (ViT-g)vit.raft5.large.py vit.raft5.giant2.pyNot stated in paper
Training iterations800K§4 vit.raft5.large.pymax_iters 800010 in code
Batch size192 on 48 A100 GPUs§4 vit.raft5.large.pyCode gives per-GPU batch: 4 (ViT-L), 3 (ViT-g), 6 (ViT-S); see Paper vs code
Horizontal flip50%§4 vit.raft5.large.pyCode uses prob 0.4 across the dataset pipelines
Dataset balanceEqual share per dataset per mini-batch[supp §1.1]Follows DiverseDepth's sampling
Depth loss weight w_d0.5eq. 10Released configs use different loss weights; see Paper vs code
Normal loss weight w_n1eq. 10Released configs use 1.0 or 1.5
Consistency weight w_{d-n}0.01eq. 10Released configs use 0.001 or 0.01
RPNL proposals M32§3.4 HDNL_random.pyConfig overrides to 10 in the ViT-L and ViT-g NYU configs
RPNL crop size0.125 to 0.5 of image side§3.4Released code samples depth ranges instead
Fine-tuning, steps20K (main text) / 6K LR decay, ~2K to converge (supplement)§4 [supp §1.2] vit.raft5.giant2.nyu.pyPaper gives two values; code uses 20,010 iters
Fine-tuning, LR1e-6 to 1e-7 (paper, Adam) / encoder 5e-7, decoder 1e-5 (code)[supp §1.2] vit.raft5.giant2.nyu.pyCode uses AdamW
Fine-tuning, batch16 (ViT-g), 32 (ViT-L) (paper) / 2 per GPU (code)[supp §1.2] vit.raft5.giant2.nyu.py
Pseudo-label weight in fine-tuning0.01[supp §1.2]Uses predictions of the pre-fine-tuned model
Encoder pretrainingDINOv2 with registers, 142M images (ViT); ImageNet-22K (ConvNeXt-L)§4 [supp §1]
Training data16 public RGB-D sets, over 16M images (main text); 18 sets, ~16M (supplement)§4 [supp §1.1]ConvNeXt models use 11 sets, ~8M images [supp §1.1]
Depth bins, ConvNeXt256[supp §1]AdaBins-style decoder

Results

Author-reported. Setup: models trained on about 16M images §4. The zero-shot rows use one checkpoint for all benchmarks §4. Table 1 is metric depth with no scale alignment §4. Table 2 is normals. Table 4 is affine-invariant depth, where the paper aligns scale and shift per image before scoring §4. The affine-invariant results are not comparable with the metric results.

Metric depth, zero-shot (ZS) and fine-tuned (FT), subset tab. 1:

SettingMetricBaselineThis paperSource
NYUv2, ZSδ1 / AbsRel / RMSPolymax (ZS): 0.969 / 0.067 / 0.250ViT-g: 0.980 / 0.067 / 0.260; ViT-L: 0.975 / 0.063 / 0.251tab. 1
NYUv2, ZS, ConvNeXt-Lδ1 / AbsRel / RMSZoeDepth FT: 0.953 / 0.077 / 0.277Conv-L CSTM_label: 0.944 / 0.083 / 0.310tab. 1
NYUv2, FTδ1 / AbsRel / RMSDepthAnything FT: 0.984 / 0.056 / 0.206ViT-g FT: 0.987 / 0.045 / 0.187; ViT-L FT: 0.989 / 0.047 / 0.183tab. 1
KITTI, ZSδ1 / AbsRel / RMSZeroDepth ZS: 0.910 / 0.102 / 4.044ViT-g: 0.977 / 0.051 / 2.403; ViT-L: 0.974 / 0.052 / 2.511tab. 1
KITTI, FTδ1 / AbsRel / RMSDepthAnything FT: 0.982 / 0.046 / 1.869ViT-g FT: 0.989 / 0.039 / 1.766; ViT-L FT: 0.985 / 0.044 / 1.985tab. 1

Surface normals on NYUv2, zero-shot (ZS) and fine-tuned (FT), subset tab. 2:

SettingMetricBaselineThis paperSource
NYUv2, ZS11.25° / 22.5° / 30° / mean / medianPolymax ZS: 0.656 / 0.822 / 0.878 / 13.1 / 7.1ViT-g: 0.664 / 0.831 / 0.881 / 13.3 / 7.0; ViT-L: 0.662 / 0.831 / 0.881 / 13.1 / 7.1tab. 2
NYUv2, ZSRMS_normalPolymax ZS: 20.4ViT-L ZS: 21.1; ViT-g ZS: 21.3tab. 2
NYUv2, FT11.25° / 22.5° / 30° / mean / medianBae et al. ZS: 0.597 / 0.775 / 0.837 / 16.0 / 8.4ViT-L FT: 0.688 / 0.849 / 0.898 / 12.0 / 6.5tab. 2
ScanNet (in-domain, ViT-g)11.25° / 22.5° / 30° / mean / medianTiltedSN: 0.693 / 0.839 / 0.886 / 12.6 / 6.00.778 / 0.901 / 0.935 / 9.2 / 5.0tab. 2

Affine-invariant depth, five zero-shot sets, AbsRel / δ1, subset tab. 4:

MethodNYUv2KITTIDIODE (full)ETH3DSource
Marigold0.055 / 0.9610.099 / 0.9160.308 / 0.7730.065 / 0.960tab. 4
DepthAnything (ViT-L)0.043 / 0.9810.076 / 0.9470.277 / 0.7590.127 / 0.882tab. 4
This paper, ViT-L, CSTM_label0.042 / 0.9800.046 / 0.9790.141 / 0.8820.042 / 0.987tab. 4
This paper, ViT-g, CSTM_label0.043 / 0.9810.044 / 0.9820.136 / 0.8950.042 / 0.983tab. 4

ScanNet is not zero-shot for the ViT models, so those entries are struck out tab. 4.

Ablations on the training set, zero-shot AbsRel, subset:

SettingMetricBaselineThis paperSource
Canonical transform (NYU, zero-shot)AbsRelw/o CSTM: 0.584; CamConvs: 0.333CSTM_image: 0.210; CSTM_label: 0.212tab. 9
Canonical transform (KITTI, zero-shot)AbsRelw/o CSTM: 0.568; CamConvs: 0.178CSTM_image: 0.097; CSTM_label: 0.081tab. 9
RPNL (NYUv2, zero-shot)AbsRelbaseline: 0.230; baseline + SSIL: 0.216baseline + RPNL: 0.210tab. 10
Joint depth-normal optimization (DIODE outdoor, normal median error)median errorW.o. depth: 16.25; W.o. consistency: 16.45Ours: 14.91tab. 11
Joint depth-normal optimization (DIODE outdoor, depth AbsRel)AbsRelW.o. recurrent block: 0.309; W.o. consistency: 0.310Ours: 0.304tab. 11

KITTI dense SLAM (Droid-SLAM with predicted depth), translational RMS drift on sequence 00, lower is better: Droid 33.9% and Droid+Ours 1.44% tab. 7. Scene reconstruction on 9 NYUv2 scenes is in tab. 6. The full tables are in the paper; this card lists the headline rows.

Paper vs code

Searched: the paper's source (summary_of_changes.tex and rebuttal.tex) links github.com/YvanYin/Metric3D. The owner matches the lead author. The clone is at code_commit. Weights are not in the clone, so checkpoints were not checked. The configs read are under training/mono/configs/RAFTDecoder/ (training) and mono/ (inference).

  • Learning rate. Paper: "initial learning rate 0.0001 for all layers" §4. Code: encoder 1e-5 (ViT-S and ViT-L) or 8e-6 (ViT-g), decoder 1e-4 vit.raft5.large.py vit.raft5.giant2.py.
  • Batch size. Paper: 192 on 48 A100 GPUs §4. Code: per-GPU batch 4 (ViT-L) vit.raft5.large.py, 3 (ViT-g) vit.raft5.giant2.py, 6 (ViT-S) vit.raft5.small.py. The GPU count is not in the configs, so the total batch cannot be checked. 4 × 48 = 192 fits the ViT-L config.
  • Horizontal flip. Paper: 50% §4. Code: prob 0.4 in each dataset pipeline, for example vit.raft5.large.py.
  • Loss weights. Paper: w_d = 0.5, w_n = 1, w_{d-n} = 0.01 eq. 10. Released ViT-L config: VNL 1.0, GRU sequence 0.5, normal NLL 1.0, consistency 0.01 with depth_detach=True vit.raft5.large.py. ViT-g NYU config: VNL 1.0, GRU sequence 1.0, normal NLL 1.5, consistency 0.001 vit.raft5.giant2.nyu.py vit.raft5.giant2.nyu.py. The ViT-g general config has normal NLL 1.5 and consistency 0.01 vit.raft5.giant2.py. The configs also add a GRU sequence loss and a sky regularizer that eq. 9 does not list.
  • Silog loss. Paper: L_{silog} is part of L_d eq. 9. None of the RAFTDecoder configs list a silog loss in their decoder or GRU loss lists vit.raft5.large.py. The supplement says silog is turned off for MapillaryPSD only [supp §1.1].
  • RPNL. Paper: random spatial patches, side 0.125 to 0.5 of the image, M = 32 §3.4. Code: the loss named HDNRandomLoss samples depth-value intervals instead of image patches. The minimum is drawn from [0, 0.75] of the depth range and the maximum at least 0.1 above it HDNL_random.py. It normalizes by the median and by the mean absolute deviation, which matches the denominator of eq. 8. The class default is 32 HDNL_random.py. The configs set 10 (ViT-L) vit.raft5.large.py and 10 (ViT-g NYU) vit.raft5.giant2.nyu.py. A second loss, HDSNRandomLoss, uses 20 vit.raft5.large.py.
  • Depth-normal consistency. Paper: uses real-world depth, not canonical depth §3.4. Code: depth_detach=True in the ViT-L and ViT-g configs vit.raft5.large.py vit.raft5.giant2.py, so the consistency gradient does not reach depth. The paper does not mention this.
  • Canonical ratio. Training uses f_x only transform.py. Inference uses the mean of f_x and f_y do_test.py. The paper assumes f^c_x = f^c_y §3.2.
  • Depth at inference. Output is divided by the label scale factor do_test.py and clipped to (0, 300) m do_test.py. The paper does not mention the clip.
  • Fine-tuning. Paper: 20K steps on 8 GPUs §4; supplement: Adam, LR from 1e-6 to 1e-7 over 6K steps, converges in about 2K [supp §1.2]. Code: AdamW, 20,010 iterations, encoder LR 5e-7, decoder LR 1e-5, 2 per GPU vit.raft5.giant2.nyu.py.
  • ConvNeXt decoder. Main text: UNet §4. Supplement: AdaBins-style decoder with 256 bins plus an hourglass head [supp §1]. Code: HourglassDecoder config with depth range (0.3, 150) and crop 544 × 1216 convlarge.0.3_150.py.
  • Warmup. Not stated in the paper. Code gives 500 or 1000 iterations vit.raft5.large.py.

The RPNL sampler draws from numpy's global RNG (np.random.uniform) with no seed set in the loss HDNL_random.py, so runs are not reproducible unless the caller seeds numpy.

Pitfalls

  • Scale is only as good as the focal length. Metric output needs the true f §3.1. The paper's zero-shot claims use dataset intrinsics. Image-only inputs with guessed focal lengths are a different setting, and the paper uses guessed values only for qualitative in-the-wild results §4.
  • Eq. 10 vs §3.4. Eq. 10 always adds w_{d-n} L_{d-n}. §3.4 introduces the consistency loss as the replacement for GT-normal supervision. Pick one reading and say which you used.
  • RPNL is ambiguous. The paper defines patch crops; the code uses depth intervals. The two give different gradients. Implement the one you can justify and note which.
  • Learning rate. Paper says 1e-4 for all layers; code uses 10× lower encoder LR. Using 1e-4 everywhere is not the released setting.
  • Loss weights differ across configs. The normal NLL weight is 1.0 in some configs and 1.5 in others; the consistency weight is 0.001 or 0.01. The paper's single set of weights does not match any one config.
  • Canonical focal is 1000 px in every config, but the set of tested values differs (2500 in the main text, 2000 in the supplement).
  • Canonical transform changes the numerical range. With the label variant the network's depth is in canonical units, so its raw output is not metres. Use the de-canonical step on every output path.
  • Clamp and bins. The ViT head clamps depth to the normalization bounds RAFTDepthNormalDPTDecoder5.py. The bounds and bin counts for ViT and ConvNeXt differ, so do not reuse one setting for both.
  • Datasets. The paper gives 16 sets §4 in one place and 18 in another [supp §1.1]. The number of images also varies by model (8M for ConvNeXt, 16M for ViT). Treat the exact dataset list as unresolved.
  • Hidden costs. ViT-g runs at 5.0 fps and uses 15.6 GB GPU memory on an A100 40 GB, with no TensorRT or ONNX optimization [supp tab. 1]. The ViT models use bfloat16 for speed [supp tab. 1].
  • Affine-invariant results are not metric. The affine benchmarks align scale and shift per image, so the numbers in tab. 4 are not comparable with the metric numbers in tab. 1.
  • Fine-tuning needs pseudo-labels. The paper's fine-tune loss uses the pre-fine-tuned model's outputs as extra labels [supp §1.2]. Without them, the annotation-absent regions get no supervision.

Lineage

  • Builds on: canonical pose-space normalization in human reconstruction §1; RAFT-style iterative refinement, which the joint depth-normal module adapts to normals §3.3; scale-shift invariant losses from MiDaS and LeReS §3.4; DINOv2 with registers and DPT decoders §4; ConvNeXt-L §4; AdaBins decoder [supp §1].
  • Compared against: CamConvs, which encodes the camera in the network, and SSIL, the scale-shift invariant loss, in the ablations tab. 9 tab. 10. Metric baselines include ZoeDepth and ZeroDepth tab. 1. Normal baselines include Bae et al. and Polymax tab. 2.
  • Superseded by: not known from content already in this wiki.

Source and license

Adapted from “Metric3D v2: A Versatile Monocular Geometric Foundation Model for Zero-shot Metric Depth and Surface Normal Estimation” by Mu Hu, Wei Yin, Chi Zhang, Zhipeng Cai, Xiaoxiao Long, Kaixuan Wang, Hao Chen, Gang Yu, Chunhua Shen, Shaojie Shen (arXiv:2404.15506), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/YvanYin/Metric3D at commit eb5b6fac0dc1 (BSD-2-Clause). Any quoted code is shown under that license.