Depth-Aware Vision

RGB-D Scene ClassificationWhen Depth Reveals the Room

A color photo of a bedroom and a hotel room can be pixel-for-pixel almost identical — same bed, same lamp, same beige walls. But hold up a tape measure and the rooms separate instantly: the ceiling height, the distance to the far wall, the scale of the furniture. That tape measure is a depth channel. This is the art of classifying a room when you can see not just its colors, but its geometry.

Prerequisites: you know what a CNN is and what a softmax does, and you have met plain RGB scene classification (a network that outputs one place-label for a whole image). If "a network outputs a probability over scene classes" makes sense, you're ready.
10
Chapters
10
Simulations
0
Assumed Knowledge

Chapter 0: The Bedroom That Is Really a Hotel Room

You are building the perception system for an indoor service robot. It rolls through a doorway, the camera fills with a frame, and your scene classifier must answer one word: what kind of room is this? A bedroom means a private space — knock, wait, do not enter without permission. A hotel room means a serviced space with different rules. The robot's whole behavior pivots on that single label.

So you feed the color image into a strong RGB scene classifier. It returns: bedroom (0.49), hotel_room (0.46). A near coin-flip. You look at the photo yourself and you understand why the model is stuck: the bed, the nightstand, the beige walls, the framed print — a hotel room is designed to look like a generic bedroom. From color alone, the two are genuinely, hopelessly ambiguous. No amount of bigger RGB model fixes this, because the distinguishing information is not in the colors at all.

Now imagine a second sensor on the robot: a depth camera, a device that reports, for every pixel, how far away that surface is — in meters. The output is a depth map: same shape as the color image, but each pixel holds a distance instead of a color. Suddenly the rooms separate. The hotel room is small and boxy with a low ceiling and a wall two and a half meters from the bed. Your bedroom is larger, with a window alcove three meters deep and an eight-foot ceiling. The geometry — room scale, layout, the distance to surfaces — is the discriminating signal, and it lives entirely in the depth channel.

This is RGB-D scene classification: scene recognition from two registered channels, RGB (the three color channels) and D (the per-pixel depth map), used together. The "D" is the new ingredient, and the entire lesson is about what it buys you, how to feed it to a network, and how to combine it with color. Drag the toggle below to watch the same room flip its prediction the instant depth is added.

RGB alone vs RGB-D — depth breaks the tie

Toggle depth on. With RGB only, bedroom and hotel_room are a near-tie. Add the depth channel and the geometry (room scale, ceiling height, far-wall distance) resolves the confusion. Same color pixels, decisive answer.

The misconception to kill on day one: "depth is just a fourth color channel — bolt it onto the RGB input and you're done." It is not. A depth map is not a color; it is a physical measurement in meters with completely different statistics — it has no texture, it has holes where the sensor failed, and its useful signal (slopes, heights, distances) only appears after you compute geometry from it. Treating depth as "RGB's fourth channel" wastes most of what it knows. The rest of this lesson is about respecting the difference.

Why does this matter beyond one robot? RGB-D sensors — Microsoft Kinect, Intel RealSense, the LiDAR scanner in a modern phone — are cheap and everywhere, and indoor robotics, AR, and 3D reconstruction all run on them. Indoor scenes are exactly where RGB classifiers struggle most (rooms share objects and decor), and depth is exactly the cue that disambiguates layout. RGB-D scene classification sits at that intersection: the place where adding one sensor turns a coin-flip into a confident call.

Over the next nine chapters we will pin down the task and its datasets (SUN RGB-D, NYU Depth v2), see precisely what geometric facts depth adds, learn the surprising right way to encode depth (the famous finding that a depth-CNN trained from scratch can beat a fine-tuned RGB-CNN on the depth channel), build a two-stream RGB+depth network, study the three fusion strategies and when each wins, confront the things that make RGB-D genuinely hard, and finish with a live classifier you can toggle from RGB to RGB-D and watch a confusion resolve. Let's start by making the task precise.

An RGB scene classifier reports bedroom 0.49, hotel_room 0.46 for a photo. Why does adding a depth channel often resolve this exact tie when a bigger RGB model would not?

Chapter 1: The Task and Its Datasets

Let us state the task precisely. RGB-D scene classification takes a registered pair — a color image of shape (3, H, W) and a depth map of shape (1, H, W) where every depth pixel reports the distance to the surface at the matching color pixel — and outputs one label from a fixed taxonomy of indoor scene categories. The output is a probability distribution over K scene classes, and we read off the most probable one, exactly as in plain RGB scene classification. The new ingredient is the second, geometric channel.

"Registered" is the load-bearing word. It means the two channels are aligned: pixel (i, j) in the depth map describes the same physical point as pixel (i, j) in the color image. Real sensors capture color and depth from slightly different positions, so the raw streams must be warped into alignment before use — the datasets ship pre-registered so you can treat them as a stacked 4-channel image conceptually, even though we will almost never feed them that way.

Two datasets define this field, and you must know both:

DatasetScene classesRGB-D pairsSensor / flavor
NYU Depth v227 scene categories (10 common, used heavily)~1,449 densely-labeled; ~407K raw framesKinect v1; the classic small benchmark, rich per-pixel labels
SUN RGB-D~19 used scene categories (45 total)~10,335 imagesFour sensors (Kinect v1/v2, RealSense, Xtion); the modern standard

NYU Depth v2 (from Nathan Silberman and colleagues at NYU) is the small, beloved starting point: about 1,449 indoor RGB-D pairs with dense per-pixel labels, drawn from categories like bedroom, kitchen, living_room, bathroom, and office. Because it is small, models trained on it alone overfit easily — which is why the from-scratch-depth finding in Chapter 3 was so surprising.

SUN RGB-D (from Song, Lichtenberg, and Xiao at Princeton) is the modern benchmark: about 10,335 RGB-D images captured across four different depth sensors, with a scene-classification split that most papers report on (commonly 19 categories with enough examples). Mixing four sensors is deliberate — it forces models to handle the different noise and resolution characteristics of each device, which is exactly the robustness a deployed system needs.

Notice what these taxonomies have in common: they are indoor. Beaches and forests are easy from color; the hard, depth-hungry problem is telling apart the rooms a robot actually drives through. Let us do a quick worked sanity-check on dataset scale, because the smallness of these sets shapes everything that follows.

Worked example — why "from scratch" sounds crazy here. SUN RGB-D has ~10,335 images. Split 80/20 train/test gives ~8,268 training images across 19 classes — about 435 images per class. ImageNet, by contrast, has ~1,281,167 training images, roughly 1,281 per class across 1000 classes — and people still pretrain on it before fine-tuning. So training a deep CNN from scratch on 435 images per class should overfit badly. Hold that arithmetic: 435 vs 1,281 per class. It makes the Chapter-3 result — a from-scratch depth-CNN beating a pretrained RGB-CNN on the depth channel — genuinely counterintuitive, and the explanation will be all the more satisfying.

The widget below lets you scrub through the two datasets' class lists and see, per class, roughly how many RGB-D pairs you get to train on. Watch how thin the per-class data is — that scarcity is the constraint every design choice in this lesson fights against.

Dataset explorer — classes and per-class data

Switch datasets and watch the per-class bar chart. SUN RGB-D is larger and multi-sensor; NYU is small and densely labeled. Either way, per-class counts are small — the central constraint.

Common mistake: assuming RGB-D datasets are as big as ImageNet because "depth is everywhere now." They are not, and the reason is cost: every RGB-D pair needs a depth sensor on site, careful registration, and (for NYU) dense human labeling. The whole field operates in a small-data regime, which is why pretraining, transfer, and clever depth encodings matter so much more here than in everyday RGB classification. Data scarcity is not a footnote; it is the protagonist.
SUN RGB-D has ~10,335 images. With an 80/20 split over 19 scene classes, roughly how many training images per class do you get, and what is the practical consequence?

Chapter 2: What Exactly Does Depth Add

It is easy to say "depth helps." Let us be precise about what it adds, because every benefit we name will later justify a design choice. Depth contributes three things that color simply does not contain.

Benefit 1: absolute scale. A color image is scale-ambiguous — a dollhouse kitchen and a real kitchen photograph identically, because color has no units. Depth has units: meters. So a depth map tells you the room is 4 m wide, the ceiling is 2.6 m up, the table is 0.7 m tall. Scale alone separates a cramped hotel room from a spacious master bedroom, a closet from a hallway, a model from the real thing. This is the single biggest thing depth knows that color cannot.

Benefit 2: surface layout and 3D structure. From depth you can recover which pixels are floor, wall, and ceiling, and how surfaces are oriented — their surface normals, the direction each tiny patch of surface faces. A floor faces up, a wall faces sideways, a tabletop faces up at a certain height. This layout — "big horizontal surface low down (floor), two vertical surfaces meeting (corner), small horizontal surface at table height" — is a fingerprint of room type that color textures cannot provide.

Benefit 3: robustness to appearance. Color is hostage to lighting, paint, and clutter. A kitchen at night, a kitchen painted red, a kitchen full of party guests — the colors change wildly, but the geometry (counter height, cabinet layout, the open box of the room) is stable. Depth is invariant to exactly the nuisances that wreck color, so it carries complementary, more stable evidence.

Here is the unifying idea: color tells you what materials and objects are present; depth tells you the shape and scale of the space they live in. Indoor scenes are defined as much by their spatial configuration as by their contents, so depth is not a small bonus — it is half the definition of the room.

Let us turn one benefit into arithmetic, because "depth gives scale" should be concrete. A depth sensor reports distance Z at each pixel. With the camera's focal length f (in pixels) and the pixel coordinates, you back-project a pixel to a real 3D point. The horizontal real-world distance X for a pixel that sits u pixels from the image center is X = Z · u / f. Let us compute it.

Worked example — turning depth into meters. A pixel lies u = 200 pixels right of the image center, the depth sensor reports Z = 3.0 m there, and the camera focal length is f = 500 pixels. Then the real-world horizontal offset is X = Z · u / f = 3.0 · 200 / 500 = 3.0 · 0.4 = 1.2 m to the right of center. Do this for the far-left and far-right wall pixels and you read off the room's width directly — say left wall at −2.0 m and right wall at +2.0 m gives a 4.0 m room. Color cannot do this. There is no f, no Z, no meters in an RGB pixel — only an unitless triple of color values. The whole metric world is what depth unlocks.

The widget makes this tangible: a side-view of a room with adjustable ceiling height and far-wall distance, with the recovered "room box" drawn from those depth-derived measurements. Slide the geometry and watch the inferred scene category change — small box reads "hotel/closet," tall open box reads "open room/lobby."

Depth gives a metric room box

Adjust ceiling height and far-wall distance — the two facts depth hands you for free. The recovered metric "room box" shifts the inferred scene from a tight serviced room to a tall open space. Color alone never sees these meters.

Ceiling height (m)2.50
Far-wall distance (m)2.60
Misconception: "depth just gives a slightly sharper edge map, like a better contrast image." No — depth is a different physical quantity. An edge map is still derived from color intensities and inherits all of color's lighting problems. Depth measures distance directly, in meters, independent of lighting and paint. The right mental model is not "enhanced color" but "a second sensor measuring the geometry of the space," carrying information color literally cannot encode.
A pixel sits u = 150 pixels from the image center, depth Z = 2.0 m, focal length f = 600 px. What is its real-world horizontal offset X = Z·u/f, and what does this illustrate?

Chapter 3: Encoding Depth — Raw vs HHA

We have a depth map. How do we feed it to a CNN? This is the most important and most counterintuitive chapter in the lesson, because the obvious answer is the wrong one and the field's clever answer pays off twice.

The obvious answer: raw depth — take the single-channel meters-per-pixel map, maybe normalize it to 0–1, and shove it into a CNN as a grayscale image. This works, but it leaves value on the table. A raw CNN must learn, from scarce data, to compute geometry like "is this pixel floor?" or "how high above the ground is this?" — facts that are not directly visible in the raw distance values. With only ~435 images per class, that is a lot to ask.

The clever answer is HHA encoding (from Saurabh Gupta and colleagues), which turns the one-channel depth map into a three-channel image where each channel is a hand-computed geometric quantity:

ChannelWhat it encodesWhy it helps
H — Horizontal disparityInverse depth (1/Z): close = bright, far = darkEmphasizes nearby structure, compresses far distances like the eye does
H — Height above groundEach pixel's height in meters above the estimated floorDirectly exposes "floor=0, table=0.7m, ceiling=2.5m" — a huge layout cue
A — Angle with gravitySurface-normal angle vs the gravity (down) directionSeparates floors/ceilings (face up/down) from walls (face sideways)

The name is the three channels: Horizontal disparity, Height above ground, Angle with gravity. The genius is that HHA pre-computes the geometry the network would otherwise have to learn. It also makes the depth signal look like a natural three-channel image — which means you can initialize the depth-CNN with weights pretrained on ImageNet RGB, transferring useful low-level filters (edges, blobs) into the depth stream.

Now the famous finding, and the reason this chapter exists. Gupta et al. and follow-up work observed something startling on NYU/SUN RGB-D: a depth-CNN trained from scratch (or with HHA) on the depth channel can match or beat a CNN that was fine-tuned from ImageNet on the RGB channel. A from-scratch net beating a pretrained one? Three reasons resolve the paradox:

Reason 1 — the domain gap. ImageNet RGB features are tuned to natural-color textures (fur, sky, foliage). A depth/HHA map has none of that — it is smooth surfaces and geometry. So an ImageNet-pretrained net applied to depth is starting from features that do not transfer well; its head start is mostly irrelevant.

Reason 2 — depth is simpler to learn. Geometry has less variability than color appearance (no lighting, no texture, no paint). With less to learn, a from-scratch depth-CNN can succeed on small data where a from-scratch RGB-CNN would overfit. The HHA channels hand it the hard geometry for free, so it only has to learn the easy combination.

Reason 3 — complementary, cleaner signal. For indoor scenes specifically, geometry is often the more discriminative cue, and it is less corrupted by nuisance variation. A model reading clean geometry can outscore one reading messy, ambiguous color.

The key insight that reframes depth encoding: the question is never "is pretraining always best?" but "does the pretraining domain match my input?" ImageNet pretraining is gold for RGB and lead for raw depth, because depth statistics are nothing like natural color. HHA's real trick is two-fold: it pre-computes geometry the small dataset can't afford to learn, and it reshapes depth into an image-like 3-channel form so that (where it does help) RGB pretraining can partially transfer. Encoding is not preprocessing trivia — it decides whether your scarce data is spent learning geometry or learning to read geometry that was handed to you.

Below, toggle between raw depth and the three HHA channels for the same room. Watch how the floor, table, and ceiling pop out in the height channel, and how walls separate from floors in the angle channel — structure that is invisible in the flat raw-depth grayscale.

Raw depth vs HHA — the same room, four ways

Flip through raw depth and the three HHA channels. The height channel paints floor/table/ceiling by elevation; the angle channel separates up-facing surfaces from walls. The geometry the network would have to learn is now drawn in for it.

Misconception: "always fine-tune from ImageNet — pretraining is free accuracy." Free only when the domains match. On the depth channel, ImageNet features can be a worse starting point than random init, because the pretrained filters expect color textures that depth does not have. The competent move is to check the input domain first: HHA-encode depth so some transfer is possible, and do not be surprised when a from-scratch depth net wins on small RGB-D sets. Pretraining is a tool, not a law.
A depth-CNN trained from scratch beats an ImageNet-fine-tuned RGB-CNN on the depth channel of a small RGB-D set. The best single explanation is:

Chapter 4: Two-Stream RGB + Depth Encoders

We have two channels with two different jobs: color reads materials and objects, depth reads geometry and scale. The natural architecture follows directly from that split — give each its own network. This is the two-stream network: one CNN encoder for RGB, a separate CNN encoder for depth (or HHA), each producing its own scene descriptor, which are then combined.

Why two streams instead of one network eating a stacked 4-channel image? Because, as Chapter 3 showed, RGB and depth have different statistics and different best-pretraining. The RGB stream wants ImageNet-RGB initialization and learns color-texture filters; the depth stream wants HHA input and its own (possibly from-scratch) filters tuned to smooth geometry. Forcing them to share one set of early filters makes each worse at its job. Separate streams let each specialize.

Let us trace the data flow with shapes, because the shapes are the design. The RGB image (3, 224, 224) flows through the RGB backbone to a feature map, which global-average-pools to an RGB descriptor — say a vector of length 512. In parallel, the HHA depth image (3, 224, 224) flows through the depth backbone to its own 512-vector. Now we hold two 512-dim descriptors, one per modality, each summarizing the whole image from its sensor's point of view.

RGB (3,224,224)
color image
→ RGB CNN →
v_rgb (512,)
color descriptor
fusion
combine
v_dep (512,)
depth descriptor
← Depth CNN ←
HHA (3,224,224)
encoded depth

The two descriptors then meet in a fusion step (the whole of Chapter 5), and the fused result feeds a final linear classifier and softmax. The key structural fact: the streams run independently until fusion, so each can be trained, initialized, and even swapped out separately. You can take a strong off-the-shelf RGB scene model as one stream and pair it with a small depth net as the other.

Let us put one number on "two streams beat one" with a toy. Suppose the RGB stream alone gets a class right 70% of the time and the depth stream alone 65%, and — crucially — their errors are somewhat independent because they read different physics. If the two were perfectly independent and we accept a prediction when either is right, the chance both are wrong is 0.30 · 0.35 = 0.105, so "either right" = 89.5%. Real fusion is not that generous (errors are correlated), but the principle holds: complementary, partly-independent streams cover each other's mistakes.

Worked example — why complementarity is the point. Take 100 test images. RGB alone is right on 70, depth alone on 65. If their mistakes overlapped completely (depth fails only where RGB fails), the pair could do no better than 70 — depth adds nothing. But suppose depth is right on 20 of the 30 images RGB gets wrong (because those are the geometry-decided cases like bedroom-vs-hotel). Then a good fusion can reach 70 + 20 = 90 correct. The gain is exactly the size of the set "depth right where RGB wrong." That is why we measure not just each stream's accuracy but how complementary their errors are — the whole value of the second stream lives there.

The widget lets you set each stream's solo accuracy and how independent their errors are, and watch the fused accuracy. Crank independence up and fusion soars; set the errors fully correlated and the second stream becomes useless — a vivid lesson in what makes a second modality worth its compute.

Two streams: complementarity drives the gain

Set each stream's solo accuracy and how independent their mistakes are. Fused accuracy climbs only when the streams err on different images. Fully correlated errors = the second stream is dead weight.

RGB stream accuracy0.70
Depth stream accuracy0.65
Error independence0.60
Misconception: "two streams are better because the model has more parameters." No — the win is complementarity, not capacity. If the depth stream simply re-learned the same cues as RGB (correlated errors), doubling the parameters would barely help and might overfit the small data. Two streams pay off precisely because depth and color read different physics and therefore fail on different images. Always ask not "is the second stream accurate?" but "is it accurate where the first one fails?"
RGB alone is right on 70/100 images; depth is right on 20 of the 30 RGB gets wrong. The best fusion accuracy is about, and the reason is:

Chapter 5: Fusion — Early, Late, and Cross-Attention

The two streams must meet. Where they meet is the fusion strategy, and it is the single most-studied design choice in RGB-D recognition. Three strategies dominate, defined by how early or late the modalities mix.

Early fusion combines the raw inputs (or very early features) before most processing — e.g. stack RGB and depth into a 4-channel (or 6-channel with HHA) image and run one network. It lets the model learn joint low-level patterns from pixel one, but it forces the two modalities to share early filters (the Chapter-4 problem) and to be perfectly registered. Early fusion shines when the channels are tightly aligned and their low-level interaction matters.

Late fusion runs the two streams fully separately to two predictions (two probability distributions, or two descriptors), then combines them at the end — average the probabilities, sum the log-probabilities, or concatenate descriptors before a final classifier. It is simple, robust, lets each stream use its own best pretraining, and degrades gracefully if one sensor fails. Its cost: the modalities never interact at the feature level, so a cue that requires jointly seeing color and depth (e.g. "a shiny surface at table height") is harder to capture.

Cross-attention fusion is the modern middle ground: let the streams talk to each other at the feature level via attention. Each modality's features attend to the other's — depth features can ask "which color regions are at this geometry?" and color features can ask "what is the shape of this textured region?" — producing fused features that are genuinely joint. It is the most powerful and the most data-hungry and compute-heavy.

StrategyWhere they mixWins whenCost / risk
EarlyAt the input / early featuresTight registration; low-level interaction mattersForces shared filters; brittle to misalignment
LateAt the final predictionsModalities mostly independent; want robustness & simplicityNo feature-level interaction; misses joint cues
Cross-attentionMid-level features, both waysLots of data; joint cues matter; chasing SOTAExpensive; data-hungry; can overfit small sets

Because late fusion is the workhorse — the first thing to try and the most robust — let us do it by hand. The clean way to combine two independent classifiers is to add their log-probabilities (equivalently, multiply the probabilities). Why log-prob and not raw average? Multiplying probabilities is the Bayesian "AND of independent evidence": a class wins only if both streams find it plausible, so adding logs lets each stream veto a class the other liked. Let us compute it.

Worked example — late fusion by summing log-probs. Three classes {bedroom, hotel, kitchen}. RGB probabilities: bedroom 0.50, hotel 0.45, kitchen 0.05. Depth probabilities: bedroom 0.20, hotel 0.70, kitchen 0.10. RGB alone says bedroom (a near-tie). Now fuse by summing logs: ln prgb + ln pdep.
• bedroom: ln(0.50)+ln(0.20) = (−0.693)+(−1.609) = −2.302
• hotel: ln(0.45)+ln(0.70) = (−0.799)+(−0.357) = −1.156
• kitchen: ln(0.05)+ln(0.10) = (−2.996)+(−2.303) = −5.299
Largest (least negative) is hotel at −1.156. The depth stream's confidence flipped the answer from bedroom to hotel — exactly the disambiguation we wanted. Re-normalizing exp of those sums gives a proper distribution; the point is the order changed because both streams had to agree.

The widget lets you set the two streams' distributions and watch all three fusion rules side by side: simple average, log-prob sum (product), and a learned weighted blend. See how log-prob sum lets a confident stream override a hesitant one, while a naive average sometimes washes the signal out.

Three fusion rules on the same two streams

Set how confident each stream is in "hotel". Compare average-of-probs vs sum-of-log-probs (product) vs a weighted blend. Watch the log-prob rule let a confident depth stream veto a hesitant RGB call.

RGB P(hotel)0.45
Depth P(hotel)0.70
Depth weight (blend)0.50
Misconception: "cross-attention fusion is the best, so always use it." On small RGB-D datasets (Chapter 1: ~435 images/class), a heavy cross-attention fusion frequently overfits and loses to plain late fusion. Late fusion's simplicity is a feature: fewer parameters, each stream uses its own pretraining, and it survives a dead sensor. The right default is late fusion; reach for cross-attention only with lots of data and a real need for joint cues. Fanciness is not accuracy.
RGB gives bedroom 0.50, hotel 0.45; depth gives bedroom 0.18, hotel 0.70. Under late fusion by summing log-probabilities, which class wins and why?

Chapter 6: Why RGB-D Is Genuinely Hard

Depth is powerful, but it is not a magic wand, and a competent engineer respects its failure modes. Three forces make RGB-D scene classification hard even with the second sensor — and each one shapes how you build and evaluate the system.

Force 1: indoor scenes share objects and geometry. The same problem that plagues RGB scenes does not vanish with depth. A study room and a small office both have a desk, a chair, and a wall — similar objects and similar geometry. Depth helps most where rooms differ in scale or layout (bedroom vs hotel), but where two rooms share both contents and shape, even RGB-D is stuck. Depth is a strong cue, not an oracle.

Force 2: depth sensors are noisy and incomplete. This is the big one. Consumer depth cameras fail in characteristic ways: missing pixels (holes) where the sensor got no return — shiny, dark, transparent, or distant surfaces reflect or absorb the projected pattern and report no depth. Glass windows, mirrors, black TVs, and anything past the sensor's range come back as zero or garbage. A raw depth map can be 10–30% holes. If your model treats those zeros as "distance = 0 meters" it will hallucinate a wall in your face.

Force 3: depth noise and resolution. Even where it returns a value, depth is noisier than color, and the noise grows with distance — a structured-light sensor's error scales roughly with the square of the range, so a far wall's depth is far less trustworthy than a near table's. Depth maps are also often lower-resolution and blurry at object edges (the "flying pixels" between foreground and background). The geometry you compute (heights, normals) inherits all of this.

The practical upshot is a pipeline step you cannot skip: depth completion / hole-filling. Before HHA or any geometry, you fill the missing pixels — classically with a cross-bilateral filter guided by the color image (colorization-style inpainting), or with a learned depth-completion network. Skip it and your height and angle channels are corrupted exactly where the holes are.

The key insight about depth noise: the second sensor adds information and a new failure surface. The art is to use depth where it is reliable (near, opaque, matte surfaces — where geometry is crisp) and to down-weight or repair it where it is not (holes, far ranges, glass). This is why late fusion is so attractive in practice: if the depth stream is corrupted on a hard image, the RGB stream still stands, and a well-calibrated fusion leans on whichever modality is trustworthy for that image. Robust RGB-D is not "depth always helps" — it is "trust depth where depth is trustworthy."

The widget injects realistic sensor failures into a depth map — raise the hole rate and the distance-dependent noise — and shows how the recovered geometry (and the resulting prediction) degrades, then how hole-filling partially rescues it. This is the gap between a clean benchmark depth map and a real robot's sensor.

Depth sensor reality: holes, range-noise, and repair

Add missing-pixel holes and distance-dependent noise to a depth map, then toggle hole-filling. Watch clean geometry rot into corrupted geometry — and watch repair bring it partway back. This is the benchmark-to-robot gap.

Hole rate (missing pixels)0.15
Range noise0.20
Misconception: "treat missing depth pixels as zero — zero is just far away." Catastrophic. A zero from a hole means "no measurement," not "distance = 0." Feeding zeros as real depth makes the model compute a surface at the camera lens, poisoning the height and normal channels. The correct handling is an explicit validity mask plus hole-filling, never silent zeros. Confusing "missing" with a real value is the single most common RGB-D bug.
Your robot's raw depth map has 20% missing pixels (holes) on a glass wall. What is the correct handling, and why?

Chapter 7: ShowcaseToggle Depth, Watch the Confusion Resolve

Everything converges here. This is a working two-stream RGB-D classifier you can drive and break. It holds five candidate indoor scenes — bedroom, hotel_room, kitchen, office, living_room — and two streams: an RGB stream reading color/object cues, and a depth stream reading geometry cues. You set the image's features with the sliders, choose whether depth is fused in, and watch the live distribution and the top-1 call.

The RGB features are the kind of object/material cues color sees: bed-like furniture, cooking surfaces, desk/work setup. The depth features are the geometric cues depth sees: room scale (small→large), ceiling height. No single feature decides a scene; the combination across both modalities does — and depth's job is to break the ties color cannot.

Three things to try, each replaying a chapter:

Make an ambiguous bedroom/hotel. Set high "bed furniture", and with depth OFF watch bedroom and hotel_room sit in a near-tie (Chapter 0's coin-flip). Now flip depth ON and set "room scale" small with a low ceiling — the depth stream votes hotel and late fusion resolves the tie. You have reproduced the whole premise live.
Toggle depth on and off repeatedly. Watch the confidence of the top call jump when depth is fused — the gestalt of this lesson in one button.
Crank the depth-sensor noise. This corrupts the depth stream the way holes and range-noise do (Chapter 6). Watch the fused call degrade toward the RGB-only answer — a vivid demo of why robust fusion must lean on the trustworthy modality.

Live RGB-D classifier — fuse depth, break it with noise

Set the RGB (object) and depth (geometry) features, toggle depth fusion, then push depth-sensor noise up to watch the gain collapse. The right panel shows the live top-5 ranking and which modality is driving the call.

RGB: bed furniture0.80
RGB: cooking surfaces0.05
RGB: desk / work setup0.10
Depth: room scale (small→large)0.20
Depth: ceiling height0.30
Depth-sensor noise0.00
What you just proved to yourself: an RGB-D classifier is two descriptors → fused logits → softmax, exactly as Chapters 4–5 described — and its advantage over RGB is concentrated on the images where geometry decides the answer (bedroom vs hotel). Turn depth on and those ties resolve; corrupt the depth stream and the gain decays back toward RGB-only. That is the entire arc of this lesson in one interactive panel: depth helps where geometry matters, exactly as much as the depth stream can be trusted.

If you removed this simulation, would you lose understanding? Yes — reading "depth resolves the bedroom/hotel tie" is abstract until you watch the two bars cross the instant you fuse a small-room depth reading, and watch the gain melt as you add sensor noise. The napkin drawing of this whole lesson is exactly this: color in, depth in, fuse, and let geometry break the ties.

Chapter 8: Evaluation — Measuring the Depth Gain

You understand the models; here is how to judge them honestly. RGB-D scene classification has its own evaluation culture, and three ideas cover most of it: mean per-class accuracy, the depth-gain breakdown, and the confusion matrix.

Mean per-class accuracy, not overall accuracy. RGB-D datasets are imbalanced — bedroom and living_room have many images, some classes have very few. Overall accuracy (fraction of all images correct) is dominated by the big classes; a model can ignore rare classes and still score well. So the standard metric is mean class accuracy: compute accuracy within each class, then average those numbers equally. A rare class counts as much as a common one. SUN RGB-D papers report mean class accuracy precisely because of this imbalance.

Worked example — overall vs mean-class accuracy. Three classes: bedroom (60 images, 54 correct), kitchen (30 images, 24 correct), bathroom (10 images, 2 correct).
Overall accuracy = (54+24+2)/(60+30+10) = 80/100 = 80%.
Per-class: bedroom 54/60 = 90%, kitchen 24/30 = 80%, bathroom 2/10 = 20%.
Mean class accuracy = (90+80+20)/3 = 63.3%.
The overall number (80%) hides that bathroom is nearly broken; the mean-class number (63.3%) exposes it. On imbalanced RGB-D sets, always report mean-class — it is the honest number, and it is usually much lower.

The depth-gain breakdown is the RGB-D-specific evaluation. The whole reason to add depth is the lift it provides, so you must measure it per class: report RGB-only accuracy, RGB-D accuracy, and the difference, broken down by scene. The pattern is always the same and always instructive — depth gives a large gain on geometry-decided classes (bedroom vs hotel, hallway vs room) and a small or zero gain on classes color already nails (kitchen by its appliances, bathroom by its fixtures). Reading this breakdown tells you exactly where your second sensor is earning its keep.

Here is the mean-class metric and the depth-gain breakdown in code — first from scratch, then the one-liner spirit:

python
import numpy as np

# preds, labels: (N,) predicted / true class indices; K classes
def mean_class_accuracy(preds, labels, K):
    accs = []
    for c in range(K):
        mask = (labels == c)                 # images whose TRUE class is c
        if mask.sum() == 0: continue
        accs.append((preds[mask] == c).mean())  # accuracy WITHIN class c
    return np.mean(accs)                      # average classes equally — imbalance-fair

# depth-gain breakdown: per-class lift from adding depth
def depth_gain(preds_rgb, preds_rgbd, labels, K):
    for c in range(K):
        m = (labels == c)
        a_rgb  = (preds_rgb[m]  == c).mean()
        a_rgbd = (preds_rgbd[m] == c).mean()
        print(c, "rgb", round(a_rgb,2), "rgbd", round(a_rgbd,2), "gain", round(a_rgbd-a_rgb,2))

Notice the metric averages classes, not images — that single choice is what makes the number honest on imbalanced data. The confusion matrix tells you which errors and whether depth fixed them. Compare the RGB-only confusion matrix to the RGB-D one: the off-diagonal mass on geometry-decided pairs (bedroom↔hotel_room) should shrink when you add depth, while pairs color already separated barely move. A good RGB-D model's improvement is visible as specific confusion cells deflating — that is depth doing its job, made visible.

The interactive below shows the per-class depth-gain breakdown. Each row is a scene; the two bars are RGB-only and RGB-D accuracy, and the highlighted gap is the depth gain. Tap a row to read its story — notice the gain is large exactly where geometry decides the class.

Depth-gain breakdown — where the second sensor earns its keep

Each row: RGB-only accuracy (teal) vs RGB-D accuracy (warm), gap = depth gain. Tap a row. Big gains land on geometry-decided scenes; near-zero gains on classes color already nails.

Tap a scene row to read where depth helps and where it does not.
Misconception: "RGB-D beats RGB, so report one number and you're done." Wrong granularity. The interesting truth is per class: a single mean number can hide that depth helped three classes a lot and hurt one (where noisy depth misled the model). Always report the per-class depth-gain breakdown and compare the two confusion matrices — a single aggregate gain tells you depth helped on average but not where, and "where" is the whole engineering story.
Classes: bedroom (90% acc), kitchen (80%), bathroom (20%), with 60/30/10 images respectively. What is the mean-class accuracy, and why is it preferred over overall accuracy here?

Chapter 9: Connections & Where It's Used

RGB-D scene classification is a hub, not an island. The pattern you built — read two complementary channels, encode each well, fuse them, and respect each sensor's failure modes — recurs across vision, robotics, and remote sensing. Naming those connections turns one lesson into a map.

The RGB sibling. Everything here sits on top of plain RGB scene classification: the holistic "name the whole place" task, global average pooling, top-1 vs top-5, and the label-ambiguity ceiling. If any of that felt shaky, see Visual Scene Classification — this lesson is that one plus a depth channel, and the depth channel is the entire story.

Other modalities, same shape. The two-stream-and-fuse pattern is everywhere. Acoustic scene classification asks "is this audio a park, a metro, an airport?" — whole-signal classification with the same top-k culture, over a spectrogram. Remote-sensing scene classification labels satellite tiles (forest, residential, industrial), often fusing optical bands with elevation (a depth-like geometric channel) — the same RGB-plus-geometry fusion, bird's-eye. Each is "this lesson, new sensor."

Beyond depth maps: point clouds. A depth map is a 2.5D view — geometry from one camera position. Lift it to full 3D and you get a point cloud, a set of (x, y, z) points you can classify with point-cloud networks. RGB-D is the gateway to 3D understanding; the same geometry that disambiguated the room here becomes explicit 3D coordinates there. See NeRF & 3D Gaussian Splatting for where dense geometry leads.

The fusion machinery. The attention used in cross-attention fusion is the same mechanism taught in Vision Transformer and The Transformer; the broad two-encoders-one-decision pattern underlies Multimodal Foundation Models and Multimodal RAG; and the convolutional backbones in each stream are the subject of vision architectures generally. RGB-D is where you first feel why multimodal fusion is worth its complexity: a coin-flip becomes a confident call.

The one idea to carry out the door: a depth channel is not a fourth color — it is a second physics. Color reads materials and objects; depth reads the shape and scale of the space. The whole craft of RGB-D is to encode each sensor in the form it deserves (HHA over raw depth), fuse them so each can veto the other (late fusion as the robust default), and trust depth only where depth is trustworthy. Master that and you have mastered not just RGB-D scenes but the template for every multimodal system you will build.

Cheat sheet

ThingWhat to remember
TaskOne indoor-scene label from a registered RGB (3,H,W) + depth (1,H,W) pair
DatasetsNYU Depth v2 (~1,449 dense, Kinect); SUN RGB-D (~10,335, 4 sensors) — small-data regime (~435/class)
What depth addsAbsolute scale (meters), surface layout / normals, robustness to lighting & clutter
EncodingHHA = Horizontal disparity + Height-above-ground + Angle-with-gravity; pre-computes geometry & enables transfer
SurpriseFrom-scratch (HHA) depth-CNN can beat fine-tuned RGB-CNN on depth — domain gap + simpler/cleaner geometry
ArchitectureTwo-stream: separate RGB & depth encoders → fuse. Win comes from complementary (not just extra) errors
FusionEarly / Late / Cross-attention. Late (sum log-probs) = robust default; cross-attn = data-hungry SOTA
Why hardShared indoor objects + depth holes (treat as invalid, never zero) + distance-growing noise
MetricsMean class accuracy (imbalance-fair) + per-class depth-gain breakdown + compare confusion matrices

"Geometry is not true, it is advantageous." — Henri Poincare. In RGB-D scene classification, geometry is exactly the advantage: the meters color cannot see are what tell the bedroom from the hotel room.