all lessons / computer_vision_3d / 12 · learned depth & MVS lesson 12 / 13

Learned depth — monocular, self-supervised, and MVS

Everything so far needed a crutch: a depth sensor (lesson 03), multiple posed views (lesson 05 and CV 05–07), or 3D labels to supervise a network (lesson 11). None of those is an ordinary photograph. So the question this lesson forces: can we recover 3D from a single, uncalibrated, unposed image — with learning? Monocular depth is ill-posed — it is exactly the scale ambiguity of CV 04/05 and lesson 01's inverse-rendering problem, where a small near object and a large far one are indistinguishable. There is nothing to invert from one view. A network escapes only by supplying learned priors. We cover three regimes: supervised monocular depth (and why "metric" is different from "relative"), the elegant self-supervised loop that learns depth from video with no labels at all, and learned multi-view stereo when you do have several views and want dense geometry.

The plan
Four moves. (1) Show why monocular depth needs priors — a single image has infinitely many consistent depth maps, so a network is not inverting geometry but pattern-matching learned scene statistics. (2) Supervised monocular depth, and the crucial split between relative (ordinal / affine-invariant, robust, trained across mixed datasets with a scale-invariant loss) and metric depth (actual meters, which requires the camera's focal length as input). (3) The self-supervised loop — the elegant part: a DepthNet and a PoseNet trained jointly with no ground truth, supervised only by warping one video frame onto another and measuring photometric error; an exact instance of lesson 01's render→compare→backprop loop. (4) Learned multi-view stereo — plane sweeps, cost volumes, and 3D-CNN regularization (the MVSNet family) that turn several posed views into dense high-quality geometry. The widget makes tangible the one signal shared by stereo, MVS, and the self-supervised photometric loss: the matching cost as a function of hypothesized depth, and why it goes flat on textureless surfaces.

1 · Why monocular depth needs priors

Start from the fact CV 04 proved and lesson 01 built the whole track on: projection divides by depth. A pixel x = f·X/Z fixes a ray, not a point — scale the world point along its ray to (tX, tY, tZ) and the t cancels, so every depth along the ray lands on the same pixel. For a single image this means: every pixel's depth is a free variable. A depth map assigns one number per pixel; nothing in the image constrains it. Formally, given one image I there are infinitely many depth maps D such that back-projecting (lesson 03: pcam = D·K-1[u,v,1]) and re-projecting reproduces I. The extreme case is global scale: a photograph of a dollhouse and a photograph of a real house can be pixel-identical (this is why film miniatures work). Depth-from-one-view is not under-determined by a little — it is under-determined by an entire function.

So how can a network output a depth map at all? Not by inverting geometry — there is nothing to invert from one view, no second ray to intersect (contrast CV 05's triangulation, which needs two). It escapes the ambiguity the only way possible: by learning priors — regularities of how real scenes look, absorbed from training data. The cues it learns are exactly the ones human monocular vision uses:

The consequence to internalize: monocular depth is pattern-matching learned scene statistics, not solving a geometry problem. That is why it generalizes imperfectly (a scene unlike anything in training gets guessed badly) and can be actively fooled by unusual scale cues — a forced-perspective photo, a giant prop, an Ames room — where the learned priors point confidently the wrong way. The network is answering "what depth map is most likely, given scenes I've seen?", which is a probabilistic answer to a question that has no unique deterministic one.

The trap: "the network measured the depth"
A monocular depth model did not measure anything — it has one image, and one image cannot fix depth (§1). It produced the most likely depth under its learned prior. Treat its output as a strong, prior-driven guess, not a measurement: it will be wrong exactly where the scene violates the training distribution (unusual object sizes, novel domains, forced perspective). If you need a number you can trust — a robot grasp, an AR anchor, a measurement — you need either metric supervision with the right intrinsics (§2) or a second view (§4), not a relative-depth network.

2 · Supervised monocular depth, and metric vs relative

The supervised recipe is the obvious one: collect images each paired with a ground-truth depth map (from LiDAR, RGB-D, or a stereo rig), and train a network — an encoder-decoder or, in the modern form, a dense-prediction transformer (DPT) — to regress D = gθ(I), minimizing a per-pixel depth loss. This works within a fixed domain. The difficulty is that ground-truth depth is scene- and sensor-specific: indoor RGB-D is in meters at one scale, driving LiDAR at another, and each dataset has its own camera. Naïvely regressing absolute meters ties the network to one sensor and one scene scale, and it fails to transfer.

The scale-invariant loss. The fix that unlocked cross-dataset training (MiDaS, then DPT) is to stop asking for absolute depth and instead compare prediction to ground truth up to an unknown global scale and shift. Concretely, before computing the error you align the prediction D to the target D* by the best affine fit — solve for the scalar s and offset t that minimize ‖(sD + t) − D* (a 2-parameter least squares, in closed form) — and penalize only the residual:

L = mins,t ‖ (s·D + t) − D* ‖ .

Because the global scale and shift are factored out before scoring, the network is free to output depth in its own arbitrary units, and datasets at wildly different scales can be mixed in one training run. What the network learns is therefore relative depth — the ordering and relative structure of the scene (what is nearer than what, and by roughly how much in affine-invariant terms), not meters. This is often called ordinal or affine-invariant depth. Depth Anything pushes the same idea to scale by pseudo-labeling: a teacher model trained on the labeled data annotates millions of unlabeled internet images, and a student trains on that vastly larger, more diverse pseudo-labeled set — a semi-supervised trick that dramatically improves generalization (it is the depth-flavored cousin of the self-training in CV 14).

Getting back to meters needs the focal length. Relative depth cannot be metric because the scale-invariant loss threw scale away. To recover actual meters, the network needs the one piece of information that disambiguates size from distance: the camera's focal length f (more generally the intrinsics K). Recall from CV 04 that an object of real size X at depth Z projects to x = f·X/Z pixels — the same pixel size results from a small-near or large-far object unless you know f, which fixes the pixels↔angle conversion and lets the learned size priors resolve to a distance. So metric monocular models (ZoeDepth, Metric3D) take f (or the full K, or an equivalent FOV/canonical-camera transform) as an explicit input alongside the image. Feed the wrong focal length and a metric model's depths are systematically off by that scale factor — the same silent scale error as the wrong-K back-projection trap of lesson 03.

VariantSupervisionOutputNeeds intrinsics?Example
Supervised metricGT depth, single sensormeters (that sensor)implicit (fixed camera)early depth CNNs
Relative / affine-invariantmixed GT, scale-invariant lossordinal / up-to-affinenoMiDaS, DPT, Depth Anything
Metric monocularGT depth + camera modelmetersyes (focal length / K)ZoeDepth, Metric3D
Self-supervised (§3)video photometric consistencydepth up to scaleneeds K for the warpMonodepth2, SfM-Learner

Which you want is set by the task, not by benchmark numbers. Relative depth suffices — and is more robust — when you only need ordering or relative structure: computational-photography effects (portrait-mode background blur, relighting), depth-aware compositing, or seeding a novel-view generator. Metric depth is mandatory the moment a real length enters the loop: robot grasping and obstacle distances, AR object placement that must sit correctly on a real table, or any measurement. Reaching for a relative-depth model where you need meters is a common and quiet mistake — it will look plausible and be unusably off in scale.

3 · Self-supervised depth from video — geometry as the loss

This is the elegant one, and the payoff of the whole track's framing. Ground-truth depth is expensive and sensor-bound; what if the only supervision were ordinary video — a moving camera, no labels, no poses? The Monodepth2 / SfM-Learner loop does exactly this, and the trick is to make geometry itself the loss. Two networks, trained jointly:

The warp. With a depth for every target pixel and the relative pose, we can synthesize the target frame out of a source frame — and this synthesis chains together every operation from earlier lessons. For each target pixel p:

  1. Back-project through the intrinsics and its predicted depth into a 3D point in the target camera's frame (lesson 03): P = D(p)·K-1\,p.
  2. Transform by the predicted relative pose (lesson 02) into the source camera's frame: P' = T\,P = R\,P + t.
  3. Reproject that 3D point into the source image (the projection geometry of CV 04/05): ps ∼ K\,P'.
  4. Sample the source image's color at ps (bilinearly, so the whole warp is differentiable) and paint it at p in the reconstructed target Ît.

Do this for every pixel and you have re-rendered the target view from the source view's pixels, using only the predicted depth and pose. The single warp that maps a target pixel to its source location is:

ps ∼ K\,\big(\,R\;\big(D(p)\,K-1p\big) + t\,\big) .

The loss. If — and only if — the predicted depth and pose are correct, the reconstructed target Ît matches the real target It. So the supervision is the photometric error between them (in practice a blend of an L1 term and structural similarity), plus an edge-aware smoothness regularizer that lets depth vary sharply only where image gradients are strong (object boundaries) and stay smooth on flat surfaces:

L = photo(It, Ît) + λ · smooth(D) .

Both networks are trained by backprop through this differentiable warp — no depth labels, no pose labels, ever. The correct depth and pose are simply the ones that best explain how pixels actually move between consecutive frames. Read this against lesson 01's spine: the representation is (depth map + relative pose), the renderer is the warp-and-sample above, the loss is photometric, and the update is gradient descent through it. Self-supervised depth is the inverse-rendering loop with a depth+pose representation — the same loop as NeRF (lesson 09), with a far cheaper renderer.

Two wrinkles break the naïve version, and the fixes are instructive because they're pure geometry, not hacks:

The reframe (again)
Self-supervised depth looks like magic — depth from unlabeled video — until you see it is lesson 01's loop with the cheapest possible renderer. The scene representation is a per-frame depth map plus an SE(3) pose; "rendering" is warping a neighbor's pixels through that geometry (lessons 02–03 and CV 04/05); the loss is photometric. There is no new principle here — only a new, label-free source of supervision (video consistency) plugged into machinery you already have. This is the payoff of framing 3D as inverse rendering: a genuinely new capability drops out of recombining known pieces.

4 · Learned multi-view stereo (MVS)

Monocular depth trades accuracy for needing only one image. When you do have several posed views of a scene and want dense, high-quality geometry, learned multi-view stereo is the tool. Classical multi-view stereo and SLAM (lesson 03, CV 05) recovered only sparse points — triangulated feature matches. Learned MVS produces a dense depth map per view, and the core idea is the plane sweep.

Plane sweep. Fix a reference view. Hypothesize a set of fronto-parallel depth planes at candidate depths {Z1, …, ZK} (usually sampled uniformly in inverse depth, since depth resolution degrades like Z2 — the lesson 03 / CV 05 reason). For each hypothesized depth Z, warp every neighboring view onto that plane and back into the reference frame. Warping an image via a plane is a homography H(Z) (CV 05: a plane induces a homography between two views), parameterized by the known relative poses and intrinsics. The signal is this: at the true depth, all the warped neighbor views agree with the reference (they are all imaging the same real surface point), so the photometric variance across views is low / the matching score is high; at a wrong depth they disagree.

Cost volume. Stack the per-plane matching costs into a 3D cost volume of shape H × W × K — for each reference pixel and each depth hypothesis, a scalar cost (built in practice from the variance of deep features across the warped views, not raw pixels). This volume is noisy and has ambiguities (textureless regions, repeats). So regularize it with a 3D CNN that smooths and sharpens along the spatial and depth axes, enforcing that neighboring pixels have consistent, spatially coherent depth. Finally, collapse the depth axis to a single depth per pixel with a soft-argmin — a softmax over the (negated) cost along depth, then the cost-weighted expected depth:

D(p) = Σk Zk · softmaxk( −c(p, Zk) ) .

The soft-argmin is differentiable (unlike a hard argmax), giving sub-plane depth precision and letting the whole thing — feature extractor, cost volume, 3D CNN — train end-to-end against ground-truth depth. This is the MVSNet family. Run it per reference view and fuse the resulting depth maps into a single point cloud or mesh by back-projecting each (lesson 03) and merging with a cross-view consistency check that rejects points not confirmed by enough neighbors — landing back on the point-cloud/mesh representations of lessons 03 and 05. A closely related task is depth completion: given a sparse LiDAR sweep and an aligned RGB image, a network fills in dense metric depth, using the image for structure and the sparse returns for absolute scale — a practical hybrid of the metric-supervised (§2) and multi-view ideas that is heavily used in autonomous driving.

The widget below isolates the signal at the heart of all three regimes — stereo, MVS, and even the self-supervised photometric loss: the matching cost as a function of hypothesized depth, and what makes its valley sharp, flat, or ambiguous.

Plane-sweep matching cost — the signal behind stereo, MVS, and self-supervised depth
A reference and a source camera on baseline B look at a patch at true depth Ztrue. For each hypothesized depth Z we compute the disparity d = f·B/Z (the horizontal shift, per CV 05), sample the source's 1-D intensity pattern at the shifted patch, and score the photometric cost |ref − source|. The curve is cost vs. depth hypothesis; the marked valley is the recovered depth. Turn texture up → a sharp unique valley at Ztrue (depth recovered). Turn it down / flip "flat wall" → the curve goes nearly flat and depth is ambiguous — the textureless failure of stereo/MVS (CV 05) and exactly why the self-supervised photometric loss (§3) also fails on blank walls.
True depth
— m
Recovered = argmin
— m
Cost at minimum
Ambiguous?
no
Show the core JS
// scene intensity as a 1-D texture: sum of a few sinusoids, scaled by contrast.
// (repeated-texture mode uses a single frequency → periodic → false matches)
function tex(x){
  if (repeated) return contrast * Math.sin(0.9*x);           // periodic → aliases
  return contrast * (Math.sin(0.7*x) + 0.6*Math.sin(1.9*x+1) + 0.4*Math.sin(3.1*x));
}
// the reference sees the patch at its true position; disparity at TRUE depth:
var dTrue = f*B / Ztrue;                                      // px  (CV 05: d = fB/Z)

// sweep depth hypotheses Z; each implies a horizontal shift d(Z) = fB/Z.
// cost = mean |ref_patch - source_patch(shifted by (d(Z) - dTrue))| over the patch.
for (var i=0;i<K;i++){
  var Z = Zmin + (Zmax-Zmin)*i/(K-1);
  var d = f*B / Z;
  var shift = d - dTrue;                 // 0 exactly when Z == Ztrue → patches align
  var c = 0;
  for (var s=-W; s<=W; s++){
    var ref = tex(s);                    // reference patch sample
    var src = tex(s + shift) + noise();  // source patch, mis-aligned by `shift`
    c += Math.abs(ref - src);
  }
  cost[i] = c/(2*W+1);                   // low ONLY where shift≈0 IF texture is rich
}
// argmin over cost = recovered depth. Flat texture → cost≈const → no valley → ambiguous.

Where this points next

We can now pull 3D from ordinary images: relative or metric depth from a single frame via learned priors (§1–2), depth and ego-motion from unlabeled video by turning geometry into a photometric loss (§3), and dense reconstruction from a handful of posed views with plane-sweep cost volumes (§4). But every method so far — classical or learned — has been reconstructive: it recovers the 3D that was there, in front of some camera, and it recovers only the parts a camera actually saw. Two frontiers remain. First, generation: can we hallucinate plausible, complete 3D — the unseen back of an object, or a whole scene from a text prompt — by lifting the 2D diffusion priors of CV 17 into 3D (Score Distillation, image-to-3D, feed-forward Gaussians)? Second, the world moves: static reconstruction must extend to dynamic scenes (4D / deformable NeRF and Gaussians). Lesson 13 — the finale — takes on generative 3D and dynamics, then steps back to the systems question every interview eventually reaches: given a real product and its constraints, which representation, which supervision, and which metric do you actually choose? Self-supervised depth and MVS are the last purely reconstructive tools; from here the track turns to creating 3D and shipping it.

Takeaway
Monocular depth is ill-posed — one image admits infinitely many depth maps (the scale ambiguity of lesson 01 and CV 04/05) — so a network cannot invert geometry; it supplies learned priors (perspective, texture gradients, known sizes, occlusion) and pattern-matches scene statistics, which is why it generalizes imperfectly and can be fooled by unusual scale cues. Supervised depth split into relative (affine-invariant, trained on mixed data with a scale-invariant loss — MiDaS/DPT/Depth Anything, no intrinsics needed) and metric (actual meters — ZoeDepth/Metric3D — which requires the focal length as input, since f is what disambiguates size from distance). Self-supervised depth learns from raw video with no labels: a DepthNet and PoseNet are trained jointly by warping a source frame onto the target (back-project · transform · reproject · sample) and minimizing photometric error — lesson 01's inverse-rendering loop with a depth+pose representation — handling occlusion with the min reprojection error over frames and moving objects with auto-masking. Learned MVS takes posed views to dense geometry: plane-sweep neighbors via per-depth homographies, build a cost volume, regularize with a 3D CNN, and soft-argmin over depth (MVSNet), then fuse depth maps into a cloud/mesh. All three share one signal — the matching cost's valley over depth — which goes flat on textureless surfaces, the failure mode common to stereo, MVS, and the photometric loss alike.

Interview prompts