draft · v0.1.226
💬Comments welcome. To leave a note, select any text and click the note / highlight button that pops up — or open the panel with the tab at the top-right (‹). Notes are visible only inside our private review group.
Computational Photography, an AI-powered Slopendium — 04 Basic image processing and ISP
expand to📖 Full book outline1 parts · 19 chapters · 162 sections · 70 figures embedded · 4 placeholders · double-click a figure to enlarge
Part 4 BASIC IMAGE PROCESSING AND ISP
At the end of this part, the readers will be able to implement a complete ISP or very basic version of a photo editing software (mini lightroom) with exposure, contrast, etc.
4.1 Image representation
fig-image-memory-layout
fig-image-memory-layout · a 2×3 RGB image packed into 1-D memory two ways — interleaved HWC (NumPy) vs planar CHW (ML), with the stride formulas (Image representation, BASIC) 🟨
fig-edge-handling-photo
fig-edge-handling-photo · the four edge modes on a real crop — black / clamp / mirror / wrap continued past the border; real-image companion to `fig-edge-handling` (Image representation → falling off the edge, BASIC)
4.2 What the numbers mean
4.3 Developing, Testing and Debugging
4.4 Point operations
fig-operation-types
fig-operation-types · the three image-operation types — range (point) vs domain (spatial) vs neighborhood (Values / point ops, BASIC) 🟨
4.4.9 Converting to black and white
4.5 Histograms
fig-histogram
fig-histogram · an image histogram (per-channel) + cumulative histogram; its shape depends on the encoding space (BASIC tone mapping) 🟨
fig-histogram-encoding-spaces
fig-histogram-encoding-spaces · the same image's luminance histogram in linear / gamma (sRGB) / log; 18% gray marked at 0.18 / ~0.46 / ~0.75 — the encoding reshapes the value axis (BASIC histograms) 🟩
fig-histogram-equalization
fig-histogram-equalization · histogram equalization — the CDF used as the transfer curve, before/after (BASIC) 🟨
fig-histogram-matching
fig-histogram-matching · histogram matching — source + target images and their histograms → matched result, with the composed transfer curve $\text{CDF}_\text{tgt}^{-1}\circ\text{CDF}_\text{src}$ (BASIC histograms)
• the **histogram** — the distribution of pixel values — is the natural companion to point operations: this short chapter is how to *read* it, and how it can *drive* an automatic tone curve
4.6 Global tone mapping
fig-reinhard-curve
fig-reinhard-curve · the Reinhard global tone curve L/(1+L) mapping [0,∞)→[0,1); naive clip vs Reinhard on a simulated-HDR image (BASIC) 🟨
fig-tonemap-real
fig-tonemap-real · global vs local tone mapping RESULT on a real HDR photo (seal at marina): naive single exposure · global Reinhard (one slope flattens local contrast → hazy) · local bilateral base+detail split (range fits, detail stays crisp) — real-image companion to `fig-tonemap-global-vs-local` (BASIC tone mapping)
fig-zone-system
fig-zone-system · the Zone System strip 0–X, Zone V = 18% mid-grey (scene → print) (BASIC) 🟨
• a **quick intro** here (full treatment in the HDR chapter): tone mapping = choosing the **mapping from scene tones to display tones**, the natural payoff of point operations
4.6.8 HDR on every screen: the gain map
4.7 Beauty curves: the camera "look"
4.8 Neighborhood operations and convolution
fig-convolution-slide
fig-convolution-slide · a kernel sliding over an image, weighted-summing a neighborhood into one output pixel (Convolution, BASIC) 🟨
fig-convolution-flip
fig-convolution-flip · the flip: where-from vs where-to — convolution `g(x−x')` vs correlation 🟨
fig-psf-impulse
fig-psf-impulse · impulse in → kernel out: convolving a Dirac reads off the PSF / impulse response 🟨
fig-blur-zoo
fig-blur-zoo · box vs Gaussian kernels — profiles + 2-D stencils, Gaussian truncated at ~3σ 🟨
fig-unsharp-mask
fig-unsharp-mask · unsharp-mask decomposition: input − blur = detail (high-pass), output = input + k·detail 🟨
fig-sharpen-kernel
fig-sharpen-kernel · the sharpening kernel δ − blur as a +centre / −surround stencil 🟨
fig-separable
fig-separable · separability — a 2-D Gaussian = 1-D ⊗ 1-D (blur rows then columns); O(r²) → O(2r) 🟨
fig-gradient-sobel
fig-gradient-sobel · Sobel x / y → gradient magnitude (edge strength) on an image 🟨
fig-convolution-probability
fig-convolution-probability · convolution as the sum of random variables: box ⊛ box = triangle (two dice) 🟨
equations
convolution (I*g)(x) = Σ I(x')·g(x−x')
Gaussian g(x) ∝ exp(−x²/2σ²)
unsharp out = in + k·(in − blur(in))
sharpening kernel δ − g
4.8.9 Is imaging blur usually a shift-invariant convolution?
4.9 Sharpening
fig-unsharp-mask
fig-unsharp-mask · unsharp-mask decomposition: input − blur = detail (high-pass), output = input + k·detail 🟨
fig-sharpen-kernel
fig-sharpen-kernel · the sharpening kernel δ − blur as a +centre / −surround stencil 🟨
4.10 Fourier
fig-fourier-basis-matrix
fig-fourier-basis-matrix · the DFT as a change-of-basis matrix (real & imaginary parts) 🟨
fig-sine-eigenvectors
fig-sine-eigenvectors · sine waves are the eigenvectors of convolution: same wave out, only amplitude/phase change 🟨
fig-motion-blur-calculator
fig-motion-blur-calculator · live motion-blur calculator: a 3-D scene (walking figure or a car) imaged by a virtual camera, the blur rendered by **temporal supersampling** (many instants across the exposure, averaged). Set resolution / sensor / focal length (35 mm-equiv) / distance / exposure, add camera-shake **rates** in rotation (pitch/yaw/roll) and translation (x/y/z) plus a subject speed; the readout gives the blur at the subject in world (cm/m/in) and screen (px / % width) units, broken down by source. A per-pixel **G-buffer** (depth + subject mask) drives exact motion-vector arrows and a blur-magnitude heatmap: the subject carries its own motion, the background only camera shake. Environment backdrops + car colour picker. Static fallback is a screenshot. *3D models generated with Meshy AI.*
fig-fourier-magnitude-phase
fig-fourier-magnitude-phase · an image's Fourier magnitude & phase, and the phase-swap demo (phase carries structure) 🟨
fig-compact-space-frequency
fig-compact-space-frequency · compact in space ⇔ spread in frequency (narrow vs wide Gaussian and its transform) 🟨
fig-aliasing
fig-aliasing · sampling a sine too coarsely → aliasing (high frequency folds to a low one) 🟨
fig-sampling-comb
fig-sampling-comb · sampling = multiply by a comb → spectral replicas; pre-filter (low-pass) before downsampling 🟨
fig-sinc-vs-practical
fig-sinc-vs-practical · ideal sinc reconstruction vs a practical filter, in space and frequency 🟨
fig-deblur-preview
fig-deblur-preview · the deblur preview: sharp → blurred + noise → naive inverse amplifies noise; MTF 🟨
fig-diagonalize-2x2
fig-diagonalize-2x2 ·
fig-fourier-conventions
fig-fourier-conventions ·
fig-heat-diffusion-1d
fig-heat-diffusion-1d ·
equations
DFT Î(u) = Σ I(x)·e^{−2πi ux/N}
Euler e^{iθ} = cos θ + i sin θ
convolution theorem ℱ{I*g} = ηĝ
eigen-relation g * e^{iωx} = ĝ(ω)·e^{iωx}
Nyquist — sample > 2× the highest frequency
4.10.7 A small bestiary of transforms
4.11 Sampling, downsampling, and aliasing
• promoted from a section of [[#Fourier]] into its own chapter (it earns one): a digital image is a **sampling** of a continuous scene, and detail finer than the grid **aliases** — folds down to a wrong, low frequency (moiré, wagon-wheel).
• **aliasing = a high frequency in disguise** (sine sampled below 2×/cycle → looks like a slower sine); 2-D version = moiré on a facade. Figures: `fig-aliasing`, `fig-aliasing-photo`.
• **Nyquist–Shannon**: sample at >2× the highest frequency; the **Nyquist frequency** is half the sampling rate; above it folds down irreversibly.
• **frequency-domain mechanism**: sampling is **multiplication by a Dirac comb** in space, so by the convolution theorem it becomes a **convolution in Fourier** — but by an unusually *simple* spectrum. The comb is **self-dual**: the Fourier transform of a Dirac comb is **another train of Diracs** (spaced at the sampling rate). And that is exactly why sampling is *easy* to reason about in Fourier: convolving by a single Dirac is just a **shift**, so convolving by a *train* of Diracs is just a **sum of shifted copies**. The spectrum is simply **replicated** at every multiple of the sampling rate — no smearing, just shifts. (This is the payoff of Fourier being built on the **shift theorem**: the one convolution that would otherwise be messy collapses into pure translation.) Band-limited copies sit apart; too-wide copies **overlap** = aliasing (`fig-sampling-comb`). Big lesson **L4.13**.
• **cure**: low-pass **pre-filter** (blur) *before* downsampling. Interactive demos: `fig-sampling-1d-demo` (1-D sampling/aliasing/pre-filter, tick-marked samples + spectrum) and `fig-sampling-2d-demo` (downsample the Prudential facade with/without pre-filter → moiré on/off).
• sidebars: aliasing **without** Fourier (pixel = area average); aliasing **on purpose** (KinéCam moiré encodes camera motion).
• **ideal sinc** reconstruction (perfect frequency box ⇒ infinite spatial ripple) is unreachable; practical filters compromise (`fig-sinc-vs-practical`) — handed off to [[#Resampling and upsampling]].
• xref: built on [[#Fourier]]; reconstruction / upsampling lives in [[#Resampling and upsampling]].
4.11.1 Aliasing: a high frequency in disguise
4.11.2 Nyquist and the sampling theorem
4.11.3 In two dimensions, aliasing has a direction
4.11.4 Sampling in the frequency domain: spectral replicas
4.11.5 Seeing it: sampling and pre-filtering, hands on
4.11.6 The ideal reconstruction filter: sinc, and why it is unreachable
4.12 Resampling and upsampling
fig-resample-forward-inverse
fig-resample-forward-inverse · concrete 2× upsampling on a 5×5 → 10×10 rainbow grid: FORWARD pushes input (i,j)→output (2i,2j) so 1 of every 2×2 output block is filled and 3 are black holes (regular lattice of gaps); INVERSE loops output, samples input via f⁻¹ (nearest) → every pixel filled (2×2 colour blocks). Forward leaves holes, inverse fills everything (Resampling, BASIC)
fig-linear-interp-1d
fig-linear-interp-1d · 1-D linear interpolation — `im[1.3]` from its two neighbours 🟨
fig-bilinear
fig-bilinear · bilinear interpolation — the 4-neighbour weighting on the unit square 🟨
fig-resample-kernels-real
fig-resample-kernels-real · the kernel ladder on a REAL photo (Boston skyline + rainbow) resampled under a 35° rotation, magnified on a detail: nearest (blocky) → bilinear (smeared) → bicubic (edges restored) → Lanczos (sharpest) — adds Lanczos + the rotation case to `fig-interp-comparison`
fig-reconstruction-pipeline
fig-reconstruction-pipeline · the resampling pipeline: sample → reconstruct → prefilter → resample 🟨
fig-aliasing-photo
fig-aliasing-photo · aliasing/moiré on a real photo: a window-grid facade decimated with no prefilter folds into moiré bands — the 2-D, real-image companion to `fig-aliasing` (Sampling and aliasing)
equations
1-D linear interp
general resample out(x) = Σ I(x')·k(f⁻¹(x) − x')
Lanczos `sinc(πx)·sinc(πx/a)`
Mitchell–Netravali cubic (B, C)
4.13 Pyramids and wavelets
fig-gaussian-pyramid
fig-gaussian-pyramid · the Gaussian pyramid — repeated blur + downsample tower (Pyramids, BASIC) 🟨
fig-laplacian-pyramid
fig-laplacian-pyramid · the Laplacian pyramid — band-pass levels (G_k − expand(G_{k+1})) 🟨
fig-pyramid-encode-decode
fig-pyramid-encode-decode · the Laplacian pyramid as an encoder → decoder (exact reconstruction) 🟨
fig-pyramid-frequency-bands
fig-pyramid-frequency-bands · each pyramid level = a frequency band (concentric Fourier rings) 🟨
fig-kernel-spectra
fig-kernel-spectra · five common kernels (box, Gaussian, derivative ∂/∂x, Laplacian, unsharp mask) shown in space and as their Fourier magnitudes — a filter's spectrum IS its per-frequency gain (low-pass leaky vs clean, directional/isotropic high-pass, boosting)
fig-coring
fig-coring · coring — zero the small detail coefficients (noise), keep the large ones 🟨
equations
reduce / expand (blur + ↓2 / ↑2 + blur)
L_k = G_k − expand(G_{k+1})
reconstruction G_k = L_k + expand(G_{k+1})
4.13.10 Pyramid blending
4.13.11 Other applications
4.14 Image metrics
fig-ssim-vs-mse
fig-ssim-vs-mse · same PSNR, very different SSIM — pixel error ≠ perceived quality (Image metrics, BASIC) 🟨
• we **often need to compare two images** — fidelity, alignment, an ML training loss
equations
MSE = mean(‖I−J‖²)
PSNR = 10·log₁₀(MAX²/MSE)
SSIM = luminance·contrast·structure (local windows)
4.15 Denoising basics
fig-denoising-before-after
fig-denoising-before-after · denoising a real colour crop (realistic shot+read noise): noisy vs Gaussian (smears edges) vs bilateral (edge-preserving) (Denoising, BASIC)
fig-averaging-convergence
fig-averaging-convergence · averaging N noisy frames → noise drops as 1/√N (1, 3, 9, 25 frames), on a real colour photo with realistic shot+read noise (Denoising, BASIC)
4.16 Demosaicking
fig-demosaick-real-bayer
fig-demosaick-real-bayer · the PS3 raw mosaic (NO-PARKING sign) demosaicked: ground truth · simulated RGGB Bayer mosaic · naive bilinear (false colour on the window grid) · green-based (clean), with a zoom (Demosaicking, BASIC)
fig-demosaick-before-after
fig-demosaick-before-after · Bayer → RGB: the mosaic, naive bilinear demosaick (zipper / fringing), edge-aware (Demosaicking, BASIC) 🟨
4.16.2 Fuji X-Trans: a larger, irregular CFA
4.16.4 The task: full RGB at every pixel
4.16.10 Classic (non-learning) demosaicking: the general strategy
4.17 Auto-exposure and auto white balance
fig-ae-metering
fig-ae-metering · **interactive** AE metering modes: pick a scene + mode (average / centre-weighted / movable+resizable spot); the demo exposes so the metered region averages 18% and reports metered tone, gain, and clipped %; "where you meter sets the exposure"
fig-white-balance-real
fig-white-balance-real · white balance computed from scratch (von Kries per-channel gain in linear light): a warm-casted photo corrected by gray-world vs white-patch/max-RGB, with the recovered channel gains (PS1)
4.18 File formats and compression
fig-jpeg-pipeline
fig-jpeg-pipeline · the JPEG encoder — RGB → opponent colour → chroma subsample → 8×8 DCT → quantize → entropy code (File formats, BASIC) 🟨
fig-dct-basis
fig-dct-basis · the 8×8 DCT-II cosine basis (DC top-left → high frequency bottom-right) 🟨
fig-chroma-subsampling
fig-chroma-subsampling · chroma subsampling grids 4:4:4 / 4:2:2 / 4:2:0 (chroma sampled coarser than luma) 🟨
fig-jpeg-artifacts
fig-jpeg-artifacts · JPEG blocking & ringing at low quality (original vs q=8, zoomed crop) 🟨
fig-jpeg-quality-levels
fig-jpeg-quality-levels · JPEG quality sweep (q=85→40→20→10→5): the same photo's edge-crop at each quality with the whole-image file size beneath — degradation grows as size drops (File formats, BASIC) 🟩
4.19 Recap ISP, non-destructive editing:
fig-isp-block-diagram
fig-isp-block-diagram · the ISP pipeline: RAW → black level → demosaick → white balance → denoise → tone/colour → sharpen → gamma → JPEG (Recap ISP, BASIC) 🟨
4.19.2 Pipeline design and tuning
4.19.3 The ISP, evolving: traditional → learned → generative