• **start from Gaussian blur** (name-then-use): the output is a weighted average of neighbours, weight = a spatial Gaussian $g_s(\lVert p-q\rVert)$ — close pixels count more. Illustrate on a **1-D scanline** = one row of the image. `fig-bilateral-1d`
• **sidebar — debugging the bilateral** (recipe moved here from Developing, testing & debugging): crank the **range variance $\sigma_r$ very large** → the range term drops out and the bilateral must collapse to a **plain Gaussian** (compare against your already-tested Gaussian); shrink **$\sigma_r$ very small** → pixels only average with near-identical neighbours, so **edges stay untouched** (run it on the half-plane / rectangle). Two extreme settings, two predictable outcomes that bracket the whole filter. `fig-bilateral-limits`
• **the problem**: near an edge the Gaussian **gathers information from across the boundary** — it averages the dark side into the bright side, smearing the step (the same bleed that made the halo)
• **the fix — a range weight**: multiply in a *second* Gaussian on the **value difference**, $g_r(\lvert I_p-I_q\rvert)$, that **down-weights pixels too different in value**. A neighbour that is close *in space* but far *in intensity* (across the edge) gets almost zero weight.
• **the bilateral weight** (Tomasi & Manduchi 1998): $w(p,q)=g_s(\lVert p-q\rVert)\,g_r(\lvert I_p-I_q\rvert)$, and the output is the normalized average $I^{\mathrm{bf}}_p=\frac1{W_p}\sum_q w(p,q)\,I_q$ with $W_p=\sum_q w(p,q)$. Read it back in words ([[Style Reference]] rule 3): *"average my neighbours, but only those that are both nearby and similar to me."* Two widths: $\sigma_s$ in pixels (how big a region), $\sigma_r$ in intensity units (how different is "too different").
• pseudocode block (from the slides): the brute-force double loop — precompute the **spatial** weight $g_s$ once, **recompute the range weight $g_r$ per pair** (it depends on the value difference, so it can't be tabulated or reused from convolution code), accumulate $\sum w\,I_q$ and $\sum w$, divide per pixel; truncate only the spatial Gaussian; RGB 3-D distance for color.
• **1-D demo**: a noisy **step edge** → Gaussian smears the step; the bilateral **denoises each plateau but keeps the step crisp**, because the weight collapses to one side at the edge. Then the **2-D image**: texture smoothed, edges held. `fig-bilateral-1d`, `fig-bilateral-2d`
• **the affinity reading** (the conceptual payoff): the range Gaussian *is* a similarity / **affinity** between two pixels — "how much do they belong together" — computed from their value difference. Every later method in this chapter is a variation on how to choose that affinity.
💡 **Big lesson:** edge-preserving = affinity — use the **color / intensity difference** between two pixels as how much they *belong together* (an **affinity**), then average (or connect) pixels in proportion to it. The bilateral's range weight $g_r$ is the first instance; the *same* affinity drives the bilateral grid, the guided filter, NL-means, colorization, matting and segmentation. *(L4 · first full edge-preserving treatment of the principle; registered in BASIC → Denoising, fully developed here.)*
#### Robustness for values with few neighbours
• **the failure**: a pixel whose value is **rare** (few neighbours within $\sigma_r$) has a tiny, noisy normalizer $W_p$ — the standard normalized bilateral becomes unstable / over-trusts the few similar pixels, and the Durand–Dorsey normalization is, strictly, **wrong** there
• **the fix — unnormalized / delta bilateral** (Aubry et al. 2014): express the output as a **delta from the input** rather than a fresh normalized average — $I^{\mathrm{bf}}_p=I_p+\sum_q w(p,q)\,(I_q-I_p)$ — and **drop the $1/W_p$ division**
• **why it works (intuition)**: each neighbour contributes a *correction* $w\,(I_q-I_p)$ pulling toward the input value; when few neighbours match, you make a **small correction** instead of dividing by a near-zero $W_p$ and amplifying noise — so a lonely value stays close to itself rather than blowing up
• ties to **local Laplacian filters** (Paris / Aubry): the unnormalized form is the bilateral cousin of the multiscale local Laplacian — see [[#Local Laplacian filters]] (and [[Linear pyramids and wavelets]])
#### Debugging — hand-checkable limits
• **large range variance** ($\sigma_r\to\infty$): $g_r\to1$, the range weight does nothing → the bilateral **collapses to a plain Gaussian** blur
• **tiny range variance** ($\sigma_r\to0$): only the pixel itself survives → output ≈ **identity**, edges (and everything) untouched
• these two limits are an **easy correctness test** for an implementation; a middle $\sigma_r$ interpolates between them
• also useful for tuning: pick $\sigma_s$ from the spatial scale of structure to remove, $\sigma_r$ from the contrast of edges to preserve (state the encoding first — see meta)