Geometry & 3D Vision
Depth Anything
A family of monocular depth models that predict relative inverse depth with a DINOv2 encoder and a DPT decoder, trained by teacher–student pseudo-labelling of 62 million unlabeled images, with a synthetic-only teacher in V2.
intermediate
Depth Anything is a family of networks for monocular depth estimation by researchers at the University of Hong Kong (HKU) and TikTok; the first paper also lists CUHK and ZJU. Given one RGB image, a Depth Anything model predicts a dense map of relative inverse depth (disparity), correct up to an unknown scale and shift, as MiDaS does. The architecture is a vision transformer encoder initialized from DINOv2 with the DPT decoder. The family’s contribution is a way of scaling the training data: a teacher trained on labeled images assigns pseudo depth labels to tens of millions of unlabeled images, and a student learns from them. The first version, Depth Anything (Yang, Kang, Huang, Xu, Feng and Zhao, CVPR 2024; called V1 in its successor), trained the teacher on 1.5 million labeled images and the student on those plus 62 million pseudo-labeled ones, under strong perturbations and a feature-alignment loss to the frozen DINOv2 encoder. Depth Anything V2 (Yang, Kang, Huang, Zhao, Xu, Feng and Zhao, NeurIPS 2024) trained a much larger teacher on 595,000 synthetic images only, and its students on the 62 million pseudo-labeled real images alone; it also introduced the DA-2K benchmark and released metric-depth models.
The first paper was read in its CVF open-access copy with its supplementary material, and V2 in its NeurIPS proceedings copy with the appendix; section, table and figure numbers below are from those copies. Code details come from the two official repositories at pinned commits (references). The DINOv2 paper (Oquab et al.) was not read: what this article says about DINOv2 comes from the Depth Anything papers and code, and the DINO article describes the encoder.
Problem Addressed
MiDaS showed that depth datasets with incompatible labels can be pooled by a loss that ignores each image’s scale and shift, and that such a model can be tested by zero-shot transfer to unseen datasets; the MiDaS article covers that recipe. Its limit, as the Depth Anything authors put it, is data coverage: labeled depth comes from sensors, stereo matching or structure from motion, all costly to collect at scale, so MiDaS still fails in some scenes (V1, Section 1). Depth Anything’s answer was to label nothing new and instead use plain photographs. Unlabeled images are cheap, diverse and, once a good depth model exists, annotated by one forward pass, which also gives denser labels than LiDAR (V1, Section 1).
Two observations shaped the method. First, the authors report that naively adding pseudo-labeled images to 1.5 million labeled ones did not improve on the labeled-only model; they conjecture that a student sharing the teacher’s architecture and pre-training makes similar correct and wrong predictions on the unlabeled images, so it learns little new (V1, Section 3.2). Second, an auxiliary semantic segmentation task, which earlier work had used to help depth, gave no gain once the depth model was already strong (V1, Section 3.3).
V2 addressed a different weakness. The authors found V1 robust on complex scenes but weak on transparent objects, reflections and fine structures, where Marigold, a model built on Stable Diffusion, did better while being slower and larger (V2, Section 1, Figure 2 and Table 1). They traced the coarse predictions to noise in real labels: depth sensors miss transparent surfaces, stereo matching fails on repetitive or textureless patterns, structure from motion fails on moving objects, and real depth maps miss thin structures and boundaries (V2, Section 2, Figures 3 and 4). On the validation set of the NTIRE 2024 Transparent Surface Challenge, MiDaS v3.1 scored 0.259 and V1 0.535 in a zero-shot setting (V2, Table 12).
Architecture
All Depth Anything models are a DINOv2 ViT encoder, from a self-supervised model family, with patch size 14 followed by the DPT decoder of Ranftl, Bochkovskiy and Koltun (2021), whose reassemble-and-fuse design is described in the MiDaS article (V1, Section 4.1, and supplement, Section 1; V2, Section 7.1). The output is one non-negative value per pixel, read as inverse depth up to scale and shift: in both official repositories the relative-depth model ends in a ReLU (depth_anything/dpt.py, depth_anything_v2/dpt.py).
Encoder
V1 released three sizes, ViT-S (24.8M parameters), ViT-B (97.5M) and ViT-L (335.3M) (V1, Table 2). V2 added a ViT-G model of 1.3 billion parameters (V2, Section 3 and abstract); as of November 2026 the V2 README lists the Small, Base and Large checkpoints with the same parameter counts and marks Giant as “Coming soon”. The encoder starts from DINOv2 weights for the teacher (V1, Section 3.1). V2 chose DINOv2 after a comparison: trained on synthetic images only, ViT-L encoders from BEiT, SAM and SynCLR transferred worse to real test sets than DINOv2-L, and only DINOv2-G gave satisfying results on real images (V2, Section 3, Figure 5 and Table 13). DINOv2-G with registers was clearly worse than the original DINOv2-G in that comparison, so both teacher and students use the original encoders (V2, Section B.6).
Decoder and tapped layers
The DPT head takes four sets of encoder tokens. The V2 README notes a change between versions: V1 “unintentionally” decoded the last four transformer layers, while V2 taps intermediate layers, a change that did not improve details or accuracy. The code agrees: V1 calls get_intermediate_layers(x, 4, ...), and V2 taps blocks 2, 5, 8 and 11 for ViT-S and ViT-B, 4, 11, 17 and 23 for ViT-L, and 9, 19, 29 and 39 for ViT-G. The decoder widths grow with the encoder, from 64 features for ViT-S to 384 for ViT-G (model_configs in the V2 README).
Training
Both versions train in stages: a teacher on labeled images, pseudo-labels from on an unlabeled set, and a student trained on the result. V1 writes the labeled set as , the unlabeled set as , and the pseudo-labeled set as (V1, Section 3, Eq. 4).
Labeled data and the affine-invariant loss (V1)
The authors reproduced MiDaS’s training, whose code was not released, as the first stage (V1, Section 3.1). Depth is converted to disparity and normalized to in each map. Prediction and label are then each shifted and scaled to zero translation and unit scale,
and the loss is the mean absolute difference of the normalized maps,
with the prediction (V1, Eqs. 1–3). This is MiDaS’s robust normalization without trimming; the MiDaS article explains why the loss must ignore scale and shift when datasets are mixed.
The labeled set has 1.5 million images from six datasets (V1, Table 1): BlendedMVS (115K), DIML (927K), HRWSI (20K), IRS (103K), MegaDepth (128K) and TartanAir (306K); these counts add up to about 1.6 million rather than the 1.5 million the paper states. The table gives the label source as stereo for all of them except MegaDepth (structure from motion). This is fewer datasets than the 12 of MiDaS v3.1 for three stated reasons: NYU Depth v2 and KITTI were left out so that the evaluation on them stays zero-shot, the 3D Movies and WSVD sets of MiDaS are no longer available, and ReDWeb is of low quality and resolution (V1, Section 3.1). Sky regions, found by a pretrained semantic segmentation model, get disparity 0 (V1, Section 3.1). Trained on each labeled set alone, HRWSI, with only 20K images, gave the best mean zero-shot results, which the authors read as showing that data diversity counts a lot (V1, Section 4.5 and Table 6).
Pseudo-labels under strong perturbations (V1)
The unlabeled set has about 62 million images from eight public datasets, used without their labels: BDD100K (8.2M), Google Landmarks (4.1M), ImageNet-21K (13.1M), LSUN (9.8M), Objects365 (1.7M), Open Images V7 (7.8M), Places365 (6.5M) and SA-1B (11.1M) (V1, Table 1). The pseudo-labels come from the best teacher, a ViT-L model (V1, Section 4.1). The student is re-initialized rather than fine-tuned from the teacher, following the self-training method ST++ for semantic segmentation, which the paper cites (V1, Section 3.2).
The key change is to make the student’s task harder than the teacher’s. The teacher labels clean images, but the student sees the same images under strong perturbations: strong colour distortions (colour jittering and Gaussian blurring) and a spatial distortion, CutMix (V1, Section 3.2; data augmentation covers both kinds). CutMix pastes a rectangle of one unlabeled image into another, with a binary mask that is 1 inside the rectangle:
The student’s prediction on is compared with the teacher’s pseudo-labels region by region, with the affine-invariant loss computed separately inside each region and weighted by its area:
(V1, Eqs. 5–8). CutMix is applied with probability 0.5, to images already distorted in colour. The authors’ reading is that the perturbations force the student to seek extra visual knowledge and learn representations invariant to them, which helps on unseen images (V1, Sections 1 and 3.2). The paper does not give the jitter or blur parameters. V2 states that, “same as V1”, the 10% of regions with the largest loss in each pseudo-labeled sample are ignored as possibly noisy labels (V2, Section 5.2); the V1 paper and supplement do not mention this rule, and V1’s training code is not in its repository.
Feature alignment with DINOv2 (V1)
To keep the semantic knowledge of the pretrained encoder, the student’s features are pulled towards those of a frozen DINOv2 encoder, , by a cosine loss (V1, Section 3.3, Eq. 9):
Two details matter. No projection head maps into a new space first, because a randomly initialized projector made the alignment loss dominate early training. And a tolerance margin drops from the loss every pixel whose cosine similarity already exceeds , because DINOv2 gives similar features to different parts of one object, such as a car’s front and rear, whose depths differ; the margin leaves room for depth-specific features (V1, Section 3.3). The paper sets but does not say whether the average still runs over all pixels once some are dropped. The total loss is the average of , and . The authors had first tried an auxiliary segmentation task with about 4,000 classes produced by RAM, Grounding DINO and HQ-SAM on the unlabeled images, without gain, and conjecture that decoding into discrete classes loses too much semantic information (V1, Section 3.3).
In the supplement’s ViT-S ablations, the mean zero-shot AbsRel over six test sets was 0.188 with (every pixel aligned), 0.175 with 0.85 and 0.178 with 0.70; applying to labeled instead of unlabeled images gave 0.179, against 0.180 without it (supplement, Tables 1 and 2).
Procedure (V1)
The labeled datasets are concatenated without re-sampling. The teacher trains for 20 epochs on labeled images; the student then makes one pass over all unlabeled images, with labeled and unlabeled images in a 1:2 ratio in each batch. The encoder’s learning rate is and the decoder’s ten times larger, with AdamW and linear decay; labeled images get only horizontal flips (V1, Section 4.1). Images are resized so the shorter side is 518 and cropped to (supplement, Section 1); the supplement’s Section 3 instead speaks of “the widely adopted 512×512 training resolution” when it calls the resolution too low.
Synthetic-only teacher and pseudo-labeled students (V2)
V2 kept the teacher–student structure but changed what the teacher learns from. Synthetic images have exact depth, including thin structures and transparent or reflective surfaces, but they differ from real photographs in style and colour and cover only the scene types of their graphics engines (V2, Sections 2 and 3). V2’s answer has three steps (V2, Section 5.1, Figure 7):
- Train a teacher with the largest encoder, DINOv2-G, on synthetic images only.
- Pseudo-label the 62 million unlabeled real images of V1 with it.
- Train students of four sizes on the pseudo-labeled real images only.
The synthetic set has 595K images from five datasets: BlendedMVS (115K), Hypersim (60K), IRS (103K), TartanAir (306K) and Virtual KITTI 2 (20K) (V2, Table 7); the rows add up to 604K, although the table’s heading gives 595K. Comparing the two papers’ tables, three of these, BlendedMVS, IRS and TartanAir, were already in V1’s labeled set, where V1’s Table 1 lists their labels as stereo; V2 dropped DIML, HRWSI and MegaDepth and added Hypersim and Virtual KITTI 2. The V2 text describes the change as replacing all labeled real images with synthetic ones. The authors give three roles to the pseudo-labeled real images: they bridge the synthetic-to-real gap, they widen scene coverage, and they transfer the giant teacher’s knowledge to smaller models, a distillation done at the level of labels on extra data rather than of features or logits (V2, Section 4). The DINOv2-G teacher trained on synthetic data alone still failed on patterns its training set lacked, such as clouds in the sky and human heads (V2, Figure 6). Adding a real labeled set, HRWSI, to the synthetic training set, although it was only 5% of the images, made the predictions coarser, so the teacher sees synthetic data only (V2, Section B.9).
On labeled images V2 uses the scale-and-shift-invariant loss and MiDaS’s gradient-matching loss with weights 1:2; the authors found unhelpful on real labels but beneficial for sharpness with synthetic ones, raising its weight from the default 0.5 to 2.0 as a trade-off between metrics and sharpness (V2, Sections 5.2, 7.1 and B.7). On pseudo-labeled images the V1 feature-alignment loss is kept (V2, Section 5.2). The teacher trains with batch size 64 for 160K iterations, the students with batch size 192 for 480K iterations, with Adam, learning rates (encoder) and (decoder), crops, and the datasets simply concatenated (V2, Section 7.1). Training the students without the synthetic images gave slightly better results for ViT-S and ViT-B, which is why the final students see only pseudo-labeled images (V2, Section 7.4 and Table 5).
Pseudo-labels with and without perturbations at toy scale
The script below (PyTorch 2.14.1, NumPy 2.5.4, SciPy 1.18.1) repeats V1’s teacher–student experiment on synthetic grayscale scenes, rendered with ray-cast depth: a checkered floor and back wall at 5–12 m and one to three shaded spheres, from cameras 1.0–1.8 m above the floor. A teacher learns from 300 labeled scenes; it pseudo-labels 1,500 unlabeled scenes from its clean images; and two re-initialized students train on labeled and pseudo-labeled images in V1’s 1:2 ratio, one on the clean unlabeled images and one under strong perturbations: contrast and brightness jitter (colour jitter reduces to these on gray images), Gaussian blur and CutMix with V1’s region-wise loss. The perturbation strengths are our choice, since the paper gives none. A fourth model, which the paper does not train, applies the same perturbations to the labeled images of the teacher, to separate augmentation from pseudo-labelling. All four use V1’s affine-invariant loss (Eqs. 1–3), the same small encoder–decoder CNN instead of a ViT, 600 Adam steps of 18 images each, and no feature alignment, since no pretrained encoder is used.
The test set is 200 further scenes, shown twice: in the training appearance (“test A”) and in a held-out appearance (“test B”: contrast halved, brightness raised, a blur of pixel and fresh noise). The geometry is identical, so only appearance differs. The measure is the zero-shot protocol of MiDaS and V1: AbsRel after a per-image least-squares fit of scale and shift in disparity, with depth capped at each image’s largest true depth. The network’s output is read as disparity (inverse depth) throughout, with depth measured along the optical axis.
import sys
import numpy as np
import torch, torch.nn as nn, torch.nn.functional as F
from scipy.ndimage import convolve1d
SEED = int(sys.argv[1]) if len(sys.argv) > 1 else 0
STEPS = int(sys.argv[2]) if len(sys.argv) > 2 else 600
torch.set_num_threads(3)
# ---- Synthetic scenes: checkered floor and wall, 1-3 shaded spheres; depth by ray casting ----
H, W, Fpx = 48, 64, 48.0
u, v = np.meshgrid(np.arange(W) + 0.5, np.arange(H) + 0.5)
rx, ry = (u - W / 2) / Fpx, (v - H / 2) / Fpx
def render(rng): # clean intensity and depth Z along the optical axis (m)
h = rng.uniform(1.0, 1.8); Zw = rng.uniform(5.0, 12.0)
Z = np.full((H, W), Zw); X, Y = rx * Zw, ry * Zw
alb = 0.45 + 0.15 * ((np.floor(X / 0.5) + np.floor(Y / 0.5)) % 2)
shade = np.full((H, W), 0.8)
floor = ry > 0
zf = np.where(floor, h / np.where(floor, ry, 1.0), 1e9)
on_floor = floor & (zf < Z)
Z = np.where(on_floor, zf, Z)
alb = np.where(on_floor, 0.3 + 0.4 * ((np.floor(rx * zf / 0.5) + np.floor(zf / 0.5)) % 2), alb)
shade = np.where(on_floor, 1.0, shade)
light = np.array([-0.4, -0.8, -0.45]); light /= np.linalg.norm(light)
for _ in range(rng.integers(1, 4)):
r = rng.uniform(0.3, 0.9); cz = rng.uniform(2.0, Zw - 1.0)
cx = rng.uniform(-0.5, 0.5) * cz * W / Fpx
c = np.array([cx, h - r, cz])
b = rx * c[0] + ry * c[1] + c[2]; a = rx**2 + ry**2 + 1
disc = b**2 - a * (c @ c - r**2)
t = (b - np.sqrt(np.maximum(disc, 0))) / a
hit = (disc > 0) & (t > 0) & (t < Z)
n = (np.stack([rx * t, ry * t, t], -1) - c) / r
Z = np.where(hit, t, Z)
alb = np.where(hit, rng.uniform(0.3, 0.9), alb)
shade = np.where(hit, 0.25 + 0.75 * np.clip(n @ light, 0, 1), shade)
return (alb * shade).astype(np.float32), Z.astype(np.float32)
def gauss(sigma):
k = int(np.ceil(3 * sigma)); x = np.arange(-k, k + 1)
g = np.exp(-x**2 / (2 * sigma**2)); return g / g.sum()
def domain_A(img, rng): # training appearance
return (img + rng.normal(0, 0.02, img.shape)).astype(np.float32)
def domain_B(img, rng): # held-out appearance: half contrast, brighter, blurred
m, g = img.mean(), gauss(1.0)
img = m + 0.5 * (img - m) + 0.15
img = convolve1d(convolve1d(img, g, axis=0, mode="nearest"), g, axis=1, mode="nearest")
return (img + rng.normal(0, 0.02, img.shape)).astype(np.float32)
def scenes(n, seed):
rng = np.random.default_rng(seed)
return [render(rng) for _ in range(n)]
lab = scenes(300, 100 * SEED + 1) # labeled set: images and disparity labels
unl = scenes(1500, 100 * SEED + 2) # unlabeled set: depth is never used for training
test = scenes(200, 10_000) # the same test scenes for every seed
na = np.random.default_rng(100 * SEED + 3)
XL = torch.tensor(np.stack([domain_A(i, na) for i, _ in lab]))[:, None]
DL = torch.tensor(np.stack([1 / z for _, z in lab]))
XU = torch.tensor(np.stack([domain_A(i, na) for i, _ in unl]))[:, None]
ZU = np.stack([z for _, z in unl])
nt = np.random.default_rng(10_001)
XA = torch.tensor(np.stack([domain_A(i, nt) for i, _ in test]))[:, None]
XB = torch.tensor(np.stack([domain_B(i, nt) for i, _ in test]))[:, None]
ZT = np.stack([z for _, z in test])
# ---- Network: small encoder-decoder, linear output read as disparity up to scale and shift ----
class Net(nn.Module):
def __init__(s, c=8):
super().__init__()
blk = lambda i, o, st: nn.Sequential(nn.Conv2d(i, o, 3, st, 1), nn.ReLU(), nn.Conv2d(o, o, 3, 1, 1), nn.ReLU())
s.e1, s.e2, s.e3, s.e4 = blk(1, c, 1), blk(c, 2 * c, 2), blk(2 * c, 4 * c, 2), blk(4 * c, 4 * c, 2)
s.d3, s.d2, s.d1 = blk(8 * c, 4 * c, 1), blk(6 * c, 2 * c, 1), blk(3 * c, c, 1)
s.out = nn.Conv2d(c, 1, 1)
def forward(s, x):
up = lambda a, b: torch.cat([F.interpolate(a, size=b.shape[2:], mode="bilinear", align_corners=False), b], 1)
e1 = s.e1(x); e2 = s.e2(e1); e3 = s.e3(e2); e4 = s.e4(e3)
d = s.d3(up(e4, e3)); d = s.d2(up(d, e2)); d = s.d1(up(d, e1))
return s.out(d)[:, 0]
# ---- Affine-invariant MAE (Depth Anything, Eqs. 1-3), optionally inside a region mask ----
def normalize(d, m): # d, m: (B, P); median and mean absolute deviation over m
dn = torch.where(m, d, torch.nan)
t = dn.nanmedian(1, keepdim=True).values
s = (dn - t).abs().nanmean(1, keepdim=True)
return (d - t) / (s + 1e-6)
def ssi_mae(pred, target, m=None): # per-image loss, mean over the masked pixels
p, g = pred.flatten(1), target.flatten(1)
m = torch.ones_like(p, dtype=torch.bool) if m is None else m.flatten(1)
r = (normalize(p, m) - normalize(g, m)).abs() * m
return r.sum(1) / m.sum(1).clamp(min=1)
# ---- Strong perturbations: intensity jitter, Gaussian blur, CutMix (Eqs. 5-8) ----
def perturb(x, rng): # x: (B, 1, H, W); colour distortions reduce to intensity on gray images
out = []
for img in x:
if rng.random() < 0.8:
mu = img.mean()
img = mu + rng.uniform(0.5, 1.5) * (img - mu) # contrast
img = img * rng.uniform(0.5, 1.5) # brightness
if rng.random() < 0.5:
g = torch.tensor(gauss(rng.uniform(0.1, 2.0)), dtype=torch.float32)
k = len(g)
img = F.conv2d(F.pad(img[None], (k // 2,) * 4, mode="replicate"), (g[:, None] * g[None, :])[None, None])[0]
out.append(img)
return torch.stack(out)
def cutmix(x, y, rng): # returns mixed images, targets of both parts, and mask M (1 = part a)
B = len(x); perm = torch.tensor(rng.permutation(B))
M = torch.zeros(B, H, W, dtype=torch.bool)
for i in range(B):
r = np.sqrt(rng.uniform(0, 1)); h, w = max(1, int(r * H)), max(1, int(r * W))
y0, x0 = rng.integers(0, H - h + 1), rng.integers(0, W - w + 1)
M[i, y0:y0 + h, x0:x0 + w] = True
xm = torch.where(M[:, None], x, x[perm])
return xm, y, y[perm], M
def loss_mixed(net, x, y, rng, strong):
if strong:
x = perturb(x, rng)
if rng.random() < 0.5:
xm, ya, yb, M = cutmix(x, y, rng)
p = net(xm); frac = M.flatten(1).float().mean(1)
return (frac * ssi_mae(p, ya, M) + (1 - frac) * ssi_mae(p, yb, ~M)).mean()
return ssi_mae(net(x), y).mean()
def flip(x, y, rng): # horizontal flips, the only augmentation on labeled images
f = torch.tensor(rng.random(len(x)) < 0.5)
return torch.where(f[:, None, None, None], x.flip(-1), x), torch.where(f[:, None, None], y.flip(-1), y)
def train(n_lab, pseudo=None, strong_lab=False, strong_unl=False, tag=0):
torch.manual_seed(1000 * SEED + tag); rng = np.random.default_rng(1000 * SEED + tag)
net = Net(); opt = torch.optim.Adam(net.parameters(), 1e-3)
for _ in range(STEPS):
i = rng.integers(0, len(XL), n_lab)
x, y = flip(XL[i], DL[i], rng)
loss = loss_mixed(net, x, y, rng, strong_lab)
if pseudo is not None: # 1:2 labeled to pseudo-labeled; average of the two losses
j = rng.integers(0, len(XU), 2 * n_lab)
loss = 0.5 * (loss + loss_mixed(net, XU[j], pseudo[j], rng, strong_unl))
opt.zero_grad(); loss.backward(); opt.step()
return net.eval()
@torch.no_grad()
def predict(net, X):
return torch.cat([net(X[k:k + 100]) for k in range(0, len(X), 100)]).numpy()
def absrel(disp, z): # LS scale and shift in disparity, depth capped at the max true depth
A = np.stack([disp.ravel(), np.ones(z.size)], 1)
s, t = np.linalg.lstsq(A, 1 / z.ravel(), rcond=None)[0]
zp = 1 / np.clip(s * disp.ravel() + t, 1 / z.max(), None)
return np.mean(np.abs(zp - z.ravel()) / z.ravel())
def score(disp, Z):
return np.mean([absrel(d, z) for d, z in zip(disp, Z)])
mean_map = DL.mean(0).numpy()
print(f"seed {SEED}, {STEPS} steps per model; AbsRel after per-image scale and shift in disparity")
print(f"image-blind mean disparity map: test A {score(np.repeat(mean_map[None], 200, 0), ZT):.3f}")
teacher = train(18, tag=1)
pseudo = torch.tensor(predict(teacher, XU)) # teacher sees clean unlabeled images
print(f"pseudo-label error on the unlabeled set: {score(pseudo.numpy(), ZU):.3f}")
models = {"teacher (labeled only)": teacher,
"teacher + strong perturbations": train(18, strong_lab=True, tag=2),
"student, plain pseudo-labels": train(6, pseudo, tag=3),
"student, perturbed pseudo-labels": train(6, pseudo, strong_unl=True, tag=4)}
print(f"{'model':34s} test A (seen look) test B (held-out look)")
for name, net in models.items():
print(f"{name:34s} {score(predict(net, XA), ZT):10.3f} {score(predict(net, XB), ZT):16.3f}")
Output (seed 0; the script takes the seed as its first argument):
seed 0, 600 steps per model; AbsRel after per-image scale and shift in disparity
image-blind mean disparity map: test A 0.156
pseudo-label error on the unlabeled set: 0.037
model test A (seen look) test B (held-out look)
teacher (labeled only) 0.039 0.383
teacher + strong perturbations 0.078 0.138
student, plain pseudo-labels 0.040 0.362
student, perturbed pseudo-labels 0.042 0.100
Seeds 1 and 2 were also run (the same script with argument 1 or 2). Over seeds 0–2, AbsRel on test B is 0.227–0.383 for the teacher and 0.287–0.362 for the plain student, both worse than the image-blind mean disparity map (0.156–0.157), which scores the same on both looks because it ignores the image. The two models trained under perturbations reach 0.100–0.152 (perturbed student) and 0.119–0.138 (perturbed teacher) on test B. On test A every model stays between 0.036 and 0.084, and the teacher’s pseudo-labels on the unlabeled set are about as accurate as the teacher on test A (0.035–0.044). Each seed trains four models and took about three minutes on the CPU used here with three threads.
Three readings follow from these numbers and the code’s construction. First, plain pseudo-labels change little: the plain student stays close to the teacher on the seen look at every seed (0.040–0.044 against 0.036–0.047), as V1’s Table 9 found at scale, and on the held-out look it is better than the teacher at seed 0 and worse at seeds 1 and 2. In our reading, a student fitted to the teacher’s outputs on more images of the same look learns the same reliance on that look. Second, the held-out gain comes with the perturbations: both models trained under them are far better on test B. This is the mechanism V1 describes, a clean target predicted from a distorted input; but test B differs from test A by contrast, brightness and blur, the kind of change the perturbations simulate (its contrast factor of 0.5 and blur of lie inside the ranges the perturbations draw from), so the toy shows the mechanism, not how far it carries to real images. Third, the toy does not settle whether pseudo-labels add anything beyond augmentation: the perturbed student beat the perturbed teacher on test B at seeds 0 and 1 and lost at seed 2, but it kept more accuracy on the seen look at every seed (0.042–0.053 against 0.055–0.084). In our reading, one difference in the code may explain this, though the toy does not test it: as in V1, the student’s labeled images stay clean, so half of its loss still supervises the seen look, while every image of the perturbed teacher goes through the random perturbations.
Inference
The input is one RGB image of any size, with no camera intrinsics. In the official code the image is resized with bicubic interpolation so that both sides are at least 518 pixels, keeping the aspect ratio and making both sides multiples of the patch size 14, normalized with ImageNet mean and standard deviation, and the output is resized bilinearly to the input size (image2tensor and infer_image in depth_anything_v2/dpt.py; supplement, Section 1). The V2 README’s --input-size option raises the resolution for finer output, and the paper reports that predictions get sharper at two and four times the training resolution without retraining (V2, Section B.8). The output is relative inverse depth, larger for nearer points, with an unknown scale and shift per image; turning it into meters needs either alignment to known depths or one of the metric models below. The V1 README lists inference times on a V100 GPU of 12, 13 and 20 ms for the Small, Base and Large models, excluding pre- and post-processing.
Performance
Zero-shot protocol
Both papers evaluate relative depth on datasets unseen in training, aligning each prediction to the ground truth in scale and shift as MiDaS does (supplement, Section 1; V2, Section 7.2), and report AbsRel (lower is better) and , the fraction of pixels with (higher is better; V1, Section 4.2). The MiDaS paper used AbsRel only on ETH3D and Sintel; on KITTI, NYU Depth v2 and TUM it reports the percentage of pixels outside the threshold, the complement of , under its own crops and caps (the MiDaS article). The six V1 test sets are KITTI, NYU Depth v2, MPI Sintel, DDAD (a driving dataset, which the papers cite to Guizilini et al., 2020), ETH3D and DIODE (a dense indoor and outdoor depth dataset, cited to Vasiljevic et al., 2019); V2 drops DDAD. The papers do not restate the crops or depth caps of each test set, and this article describes DDAD and DIODE only as the Depth Anything papers do.
Results of V1
V1’s Table 2 compares its models with the strongest MiDaS v3.1 model, DPT-BEiT-L-512, which, unlike Depth Anything, trained on KITTI and NYU Depth v2 images:
| Model | Encoder | KITTI AbsRel / | NYU AbsRel / | Sintel AbsRel / | DDAD AbsRel / | ETH3D AbsRel / | DIODE AbsRel / |
|---|---|---|---|---|---|---|---|
| MiDaS v3.1 | ViT-L | 0.127 / 0.850 | 0.048 / 0.980 | 0.587 / 0.699 | 0.251 / 0.766 | 0.139 / 0.867 | 0.075 / 0.942 |
| Depth Anything | ViT-S | 0.080 / 0.936 | 0.053 / 0.972 | 0.464 / 0.739 | 0.247 / 0.768 | 0.127 / 0.885 | 0.076 / 0.939 |
| Depth Anything | ViT-B | 0.080 / 0.939 | 0.046 / 0.979 | 0.432 / 0.756 | 0.232 / 0.786 | 0.126 / 0.884 | 0.069 / 0.946 |
| Depth Anything | ViT-L | 0.076 / 0.947 | 0.043 / 0.981 | 0.458 / 0.760 | 0.230 / 0.789 | 0.127 / 0.882 | 0.066 / 0.952 |
The ViT-L model is ahead of MiDaS v3.1 on every set; the ViT-S model, under a tenth of MiDaS’s size, is ahead on KITTI, Sintel, DDAD and ETH3D but behind on NYU Depth v2 and DIODE. The ablation that motivates the method, with ViT-L and AbsRel only, is V1’s Table 9:
| Strong perturbations | KITTI | NYU | Sintel | DDAD | ETH3D | DIODE | |||
|---|---|---|---|---|---|---|---|---|---|
| ✓ | 0.085 | 0.053 | 0.492 | 0.245 | 0.134 | 0.070 | |||
| ✓ | ✓ | 0.085 | 0.054 | 0.481 | 0.242 | 0.138 | 0.073 | ||
| ✓ | ✓ | ✓ | 0.081 | 0.048 | 0.469 | 0.235 | 0.134 | 0.068 | |
| ✓ | ✓ | ✓ | ✓ | 0.076 | 0.043 | 0.458 | 0.230 | 0.127 | 0.066 |
Plain pseudo-labels left KITTI unchanged, helped on Sintel and DDAD and hurt on NYU, ETH3D and DIODE; adding the perturbations, and then the feature alignment, improved every column over the row above.
V1 also tested its encoder as an initialization for other tasks. Fine-tuned with metric depth in the ZoeDepth framework, it reached AbsRel 0.056 and 0.984 on NYU Depth v2 and AbsRel 0.046 and 0.982 on KITTI (Tables 3 and 4), and the encoder beat both the MiDaS v3.1 encoder and the original DINOv2 encoder under the same fine-tuning (Tables 10 and 11). Fine-tuned for semantic segmentation with Mask2Former, it reached 84.8 mIoU single-scale and 86.2 multi-scale on Cityscapes, and 59.4 mIoU on ADE20K (Tables 7 and 8).
Results of V2
On the standard benchmarks V2 is close to V1 rather than better (V2, Table 2):
| Model | Encoder | KITTI AbsRel / | NYU AbsRel / | Sintel AbsRel / | ETH3D AbsRel / | DIODE AbsRel / |
|---|---|---|---|---|---|---|
| Depth Anything V1 | ViT-L | 0.076 / 0.947 | 0.043 / 0.981 | 0.458 / 0.760 | 0.127 / 0.882 | 0.066 / 0.952 |
| Depth Anything V2 | ViT-S | 0.078 / 0.936 | 0.053 / 0.973 | 0.500 / 0.718 | 0.142 / 0.851 | 0.073 / 0.942 |
| Depth Anything V2 | ViT-L | 0.074 / 0.946 | 0.045 / 0.979 | 0.487 / 0.752 | 0.131 / 0.865 | 0.066 / 0.952 |
| Depth Anything V2 | ViT-G | 0.075 / 0.948 | 0.044 / 0.979 | 0.506 / 0.772 | 0.132 / 0.862 | 0.065 / 0.954 |
The authors write that V2 is better than MiDaS but merely comparable with V1 on these sets, and argue that the qualities V2 aims at, fine detail, complex layouts and transparent objects, are not measured by them; they add that test sets such as NYU Depth v2 have wrong depth on mirrors and thin structures, so a better model can score lower (V2, Sections 6.1 and 7.2, Figure 8). On the transparent-surface challenge’s validation set, V2 reached 0.836 zero-shot, against 0.259 for MiDaS v3.1 and 0.535 for V1 (V2, Table 12).
The pseudo-labeled real images matter most for the small students. Trained on synthetic images only, ViT-S had AbsRel 0.104 on KITTI and 89.8% on DA-2K; trained on pseudo-labeled real images only, 0.078 and 95.3% (V2, Table 5). As a check on label quality, a model trained on DIML’s real images with the teacher’s pseudo-labels did better on every test set than one trained on the same images with DIML’s own labels, for example 89.7% against 80.2% on DA-2K (V2, Table 6).
DA-2K
DA-2K is V2’s benchmark for relative depth: 1,000 images with 2,000 annotated pixel pairs, each pair labelled with which point is closer, the format of the earlier DIW dataset (V2, Section 6.2). The images were downloaded from Flickr with keywords that GPT-4 proposed for eight scenarios, indoor, outdoor, non-real, transparent or reflective, adverse style, aerial, underwater and object, and are mostly about pixels (V2, Sections 6.2, C.2 and C.3). Pairs came from two pipelines. In the first, two key points from SAM’s automatic masks were sampled and four models (V1, Marigold, GeoWizard and V2 itself) voted on their order, and pairs on which the models disagreed, with a predicted depth ratio above 3, went to human annotators. In the second, annotators chose challenging pairs by hand, which is the only pipeline for the transparent and object scenarios (V2, Sections 6.2 and C.3). Every annotation was checked by the two other annotators. The score is the percentage of pairs ordered correctly; according to DA-2K.md, the annotation file always lists the closer point first.
V2’s Table 3 reports 86.8% for Marigold, 88.1% for GeoWizard, 85.8% for DepthFM and 88.5% for V1, against 95.3%, 97.0%, 97.1% and 97.4% for the V2 students from ViT-S to ViT-G. The authors present DA-2K as a supplement to dense benchmarks, not a replacement, since correct ordering of sparse pairs is far from accurate dense depth (V2, Section 6.2). Pairs on which all four voting models agreed were re-sampled rather than annotated (Figure 9a), and the authors added the manual pipeline because pairs that every model ordered wrongly would never be flagged (Section 6.2). In our reading, the automatically selected pairs are therefore, by construction, pairs on which at least one of the voting models, among them V2 itself, was wrong, and scores on them depend on which models voted.
Metric depth
Both versions turn the relative model into metric models by fine-tuning the encoder with a new decoder on metric labels. V1 followed ZoeDepth’s framework and code, replacing its MiDaS encoder, with the encoder’s learning rate set to 1/50 of the decoder’s (supplement, Section 1); trained on NYU Depth v2 or KITTI and tested zero-shot on other datasets, it beat ZoeDepth on all five, for example AbsRel 0.500 against 0.520 on SUN RGB-D and 0.085 against 0.106 on Virtual KITTI 2 (V1, Table 5). V2 repeated the ZoeDepth-style experiment (V2, Table 4: AbsRel 0.056 on NYU Depth v2 and 0.045 on KITTI with ViT-L), but its released metric models were instead fine-tuned on synthetic data, Hypersim for indoor and Virtual KITTI 2 for outdoor scenes, because models trained on NYU Depth v2 or KITTI inherited those datasets’ noise (V2, Section 7.3). In the V2 metric code the head ends in a sigmoid multiplied by a maximum depth, 20 m for the indoor and 80 m for the outdoor models, trained with a scale-invariant log loss of the form of Eigen et al.’s, with and a square root (SiLogLoss in metric_depth/util/loss.py; metric_depth/depth_anything_v2/dpt.py); its training script runs 120 epochs on 8 GPUs with batch size 4 per GPU (dist_train.sh). The monocular depth estimation article describes metric models, such as Metric3D, that account for the focal length explicitly.
Strengths
- Data that needs no labels. The 62 million training images in both versions carry no depth annotation of their own; one forward pass of a teacher labels them (V1, Section 1).
- Generalization tested zero-shot. Neither version trains on its test datasets, including KITTI and NYU Depth v2, where MiDaS v3.1 did (V1, Section 3.1).
- Fine detail and transparent surfaces (V2). The synthetic teacher gives V2 sharper boundaries and much better scores on transparent surfaces than V1 (V2, Table 12, Section B.10 and Figure 13).
- A range of sizes. Models from 24.8 million to 1.3 billion parameters; the V2 authors note that V1’s smallest model is the most widely used because of its real-time speed (V2, Section 3). The READMEs list integrations, among them Hugging Face Transformers and Apple Core ML.
- A transferable encoder. The depth-trained encoder fine-tunes well to metric depth and to semantic segmentation (V1, Tables 7–11; V2, Table 8).
Limitations
- Relative output. The base models predict inverse depth with an unknown scale and shift per image; metric output needs fine-tuned heads, which are tied to their training domain (indoor or outdoor, with a fixed maximum depth).
- Per-image prediction. Each frame is processed alone, so in our reading nothing ties scale and shift across video frames; the V2 README says only that the larger model has better temporal consistency, and the authors released Video Depth Anything separately for consistent video depth (README, as of November 2026).
- Failure cases (V2). The authors note that V1 struggles with transparent objects and reflections, and that the synthetic-only teacher fails on scene content its synthetic data lacks, such as skies and people (V2, Sections 1 and 3, Figure 6).
- Cost. Training on 62 million images is computationally heavy, and the synthetic sets are not diverse enough, which the authors list as V2’s limitations (V2, Section D). V1’s supplement lists its largest encoder (ViT-L) and its training resolution as limits (supplement, Section 3).
- Benchmarks that cannot see the gains. By the V2 authors’ own account, standard benchmarks are noisy and of low resolution and do not reflect V2’s improvements (V2, Section 6.1 and Table 2), so, in our reading, the claimed advantages rest partly on qualitative figures and on DA-2K, a benchmark the same authors built.
- Reproducibility. Neither repository contains the code for training the relative-depth models; the V2 repository has training code only for the metric models, and V1’s for metric depth through ZoeDepth (repositories at the pinned commits). The giant V2 model is listed as “Coming soon” (V2 README, as of November 2026).
Historical Importance
MiDaS made relative, affine-invariant depth trainable on mixed datasets; Depth Anything kept that formulation and loss and changed the data: first by adding tens of millions of pseudo-labeled images, then by replacing real labels with synthetic ones for the teacher. The depth estimation hub places it among the depth foundation models that followed MiDaS. Its two findings, that pseudo-labels did little for a strong student until the student’s task was made harder, and that a precise synthetic teacher plus pseudo-labeled real images beats noisy real labels, are claims about data rather than architecture; the network itself is an off-the-shelf DINOv2 encoder with a DPT decoder. The V2 paper also reports that V1 served as the pre-trained model for all leading teams of the third Monocular Depth Estimation Challenge (V2, Section 1).
Variants
- Depth Anything (January 2024). ViT-S, ViT-B and ViT-L relative models, metric models for NYU Depth v2 and KITTI built on ZoeDepth, a depth-conditioned ControlNet and segmentation configurations (V1 README; the README dates the release to 22 January 2024 and the CVPR acceptance to 27 February 2024).
- Depth Anything V2 (June 2024). A synthetic-only DINOv2-G teacher and students on pseudo-labeled real images; four relative models from 24.8M to 1.3B parameters, six metric models (Small, Base and Large for Hypersim and Virtual KITTI 2) and the DA-2K benchmark (V2 README, release dated 14 June 2024).
- Later projects. As of November 2026 the V2 README also announces Prompt Depth Anything (December 2024), which prompts the models with low-resolution LiDAR for metric depth, and Video Depth Anything (January 2025), for consistent depth over long videos. Neither was read for this article.
Implementations
As of November 2026, the official repositories are LiheYoung/Depth-Anything for V1 and DepthAnything/Depth-Anything-V2 for V2. Both provide inference scripts for images and videos, a Gradio demo and metric-depth code; checkpoints are hosted on Hugging Face, and both model families are supported in Hugging Face Transformers. Both repositories carry the Apache-2.0 licence in their LICENSE files. The V2 README states that the Small model is under Apache-2.0 and the Base, Large and Giant models under CC-BY-NC-4.0; the V1 README states no separate licence for its models. The V2 README warns that the Transformers port resizes with Pillow while the official code uses OpenCV, so predictions may differ slightly.
Related
- Monocular Depth Estimation
Predicting a depth map from a single image, why one view fixes depth only up to scale, and how relative and metric models are trained with supervised, scale-and-shift-invariant and self-supervised losses and then evaluated.
- DINO
A family of self-supervised models from Meta in which a student network learns to match the softmax output of a momentum teacher on different crops of the same image, with centering and sharpening to prevent collapse; DINOv2 and DINOv3 scale the recipe into frozen general-purpose backbones.
- Depth Estimation
Recovering the distance to the scene at every pixel of an image, from stereo pairs, many views, active sensors, or a single image.
- Data Augmentation
Training a network on randomly transformed copies of its training images, such as crops, flips, colour changes, erasing and mixing, so that it learns invariances the small training set cannot show, provided each transformation keeps the label correct.
- Computer Vision Foundation Models
Large models pretrained once on broad image, image–text or mask data and adapted to many vision tasks, covering where the term comes from, the main families (contrastive image–text encoders, self-supervised encoders, promptable segmenters, open-set detectors, vision–language models and generative models), how they are adapted and evaluated, how they scale, and their limits and risks.
- Depth Estimation Error Metrics
The standard error measures for predicted depth maps (absolute and squared relative error, RMSE, log RMSE, threshold accuracy, the scale-invariant log error and the log10 error), which errors each one rewards, and the evaluation conventions that change the reported numbers.
References
- Yang, L., Kang, B., Huang, Z., Xu, X., Feng, J. & Zhao, H. (2024). Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data. IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), 10371–10381.
- Yang, L., Kang, B., Huang, Z., Xu, X., Feng, J. & Zhao, H. (2024). Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data, Supplementary Material. CVF Open Access, CVPR 2024.
- Yang, L., Kang, B., Huang, Z., Zhao, Z., Xu, X., Feng, J. & Zhao, H. (2024). Depth Anything V2. Advances in Neural Information Processing Systems 37 (NeurIPS), 21875–21911.
- Yang, L. et al. Depth Anything V2: official code, README, DA-2K description and metric-depth code (GitHub repository DepthAnything/Depth-Anything-V2 at commit a561b84 of 24 March 2026, as of November 2026).
- Yang, L. et al. Depth Anything: official code and README (GitHub repository LiheYoung/Depth-Anything at commit 1d03336 of 7 July 2024, as of November 2026).
- Ranftl, R., Lasinger, K., Hafner, D., Schindler, K. & Koltun, V. (2022). Towards Robust Monocular Depth Estimation: Mixing Datasets for Zero-Shot Cross-Dataset Transfer. IEEE Transactions on Pattern Analysis and Machine Intelligence, 44(3), 1623–1637.
- Ranftl, R., Bochkovskiy, A. & Koltun, V. (2021). Vision Transformers for Dense Prediction. IEEE/CVF International Conference on Computer Vision (ICCV), 12159–12168.
- Oquab, M., Darcet, T., Moutakanni, T., Vo, H. V., Szafraniec, M., Khalidov, V., Fernandez, P., Haziza, D., Massa, F., El-Nouby, A., et al. (2024). DINOv2: Learning Robust Visual Features without Supervision. Transactions on Machine Learning Research (TMLR) (not read).
- Bhat, S. F., Birkl, R., Wofk, D., Wonka, P. & Müller, M. (2023). ZoeDepth: Zero-shot Transfer by Combining Relative and Metric Depth. arXiv:2302.12288 (not read).