How the depth model works

a primer to Painting → Bas-Relief, on monocular depth estimation and Depth Anything V2
No machine-learning background is assumed.

0. What the model does

One image goes in. Out comes a map of the same size in which every pixel holds a number meaning “how near is this?” — larger is nearer. That is the whole interface.

the painting
ina single photograph — here, a scan of a painting.
the model’s depth map
outthe model’s estimate. Bright = near. The foreground trees and hunters light up; the valley recedes; the far peaks go dark.

Two properties of that output matter more than anything else on this page. First, it is an estimate of relative order, not of metres: the model says the tree is in front of the village, not that it is 40 m in front. Second, the numbers come with an arbitrary scale and offset — run the model twice on two crops and the ranges will not match. Both properties are consequences of how the model is trained (§4), and both shape the relief pipeline (§6).

Strictly the map is disparity — proportional to 1/distance, the convention inherited from stereo vision — which is why near things get large values. For ordering purposes disparity and depth carry the same information.

1. How one image can contain depth

Depth from a single image is geometrically impossible: infinitely many 3-D scenes project to the same picture. What makes it possible in practice is that the world is not arbitrary. Real scenes leak depth through cues that painters have exploited for six centuries:

The model has no rule for any of these. It has seen tens of millions of images and absorbed the statistics that connect appearance to distance — a photographic prior. This is the reason the build guide’s §1 argues about the painting choice: a painting works exactly to the degree that it is painted with photographic cues. Bruegel builds his recession from occlusion, scale and atmosphere, so the prior grabs on. Abstract art gives the prior nothing, and the output is confident nonsense — the model always answers, whether or not the question makes sense.

2. The architecture, briefly

Depth Anything V2 is a vision transformer (ViT) with a dense-prediction head. The image is cut into 14 × 14-pixel patches; each patch becomes a token — a vector summarising that square; a stack of attention layers then lets every patch look at every other patch. That global view is the point: whether one grey square is snow-in-shadow or distant-hillside depends on the horizon, the haze, and the objects around it, and attention is the mechanism that carries that context. A decoder (DPT, for dense prediction transformer) reassembles features from several depths of the stack into one full-resolution map.

image  ->  14x14 patches  ->  tokens  ->  attention stack   ->  DPT decoder  ->  depth map
           (a few thousand)              (every patch sees            (multi-scale
                                          every other patch)           reassembly)

The patch size is why the build guide’s depth.py rounds working resolutions to multiples of 14, and the fixed token budget is why the model has a natural working resolution — feed it the whole painting and fine branches fall below the patch grid, which is what the guide’s tiled refinement (§5.4) exists to recover.

3. How it is trained

The obstacle is labels. Real photographs with true per-pixel depth are scarce and flawed: LiDAR is sparse, stereo rigs mismatch on glass and sky, and no sensor traces a bare branch pixel-perfectly. Synthetic images from renderers have perfect labels but do not look quite like the world. Depth Anything V2 splits the problem into three steps:

The lineage matters for expectations. This family (from MiDaS through Depth Anything) was built to generalise to arbitrary images rather than to win one benchmark — which is exactly the property that lets it work on a 460-year-old painting it was never trained for.

4. The loss, and why the output is relative

Training data mixes sources whose depth units disagree — one dataset in metres, another in disparity, another normalised. The standard solution, inherited from MiDaS, is an affine-invariant loss: before comparing prediction to ground truth, fit the best scale-and-offset that aligns them, and penalise only what remains. The model is therefore never rewarded for absolute numbers, only for getting the shape of the depth field right — and so absolute scale is simply not something it learns. A gradient-matching term is added so predicted depth edges land sharply where true edges are.

This is a fair trade, made deliberately: give up metres, gain the ability to train on everything. It is also why the build guide normalises the model’s output to 0–1 immediately (norm01, §6) and why the physical height of the printed relief is set by RELIEF_MM in the pipeline’s config, never by the model. (Metric variants of these models exist, fine-tuned to output metres for robotics; a relief that rescales everything anyway has no use for them.)

5. How depth models are scored

Two metrics dominate the papers, both computed after the affine alignment above:

Useful context, with a caveat: neither metric measures what a relief cares about most. A model can score well while smearing the boundary of every branch by three pixels — benchmark ground truth is too coarse to punish it. Edge sharpness is exactly where V2 improved over its predecessors (the synthetic-teacher design above), and it is why this pipeline uses V2. The benchmark that finally matters here is physical: whether the hunters separate from the snow on a printed panel.

6. What this means for the relief pipeline

7. Which model to use

The released family spans Small (25 M parameters), Base (98 M) and Large (335 M); the build guide uses Depth-Anything-V2-Large-hf through the Hugging Face transformers API. Large is the right choice for this use: the run is offline, seconds of inference are irrelevant, and edge quality is the commodity the relief consumes. Small exists for real-time and embedded use, where this pipeline never goes.


References