BookNotesSlidesP-sets Syllabusdraft · v0.2.49.1
◀▲⌂▶
💬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.
💡 In a hurry? Jump to this chapter’s 2 big lessons ↓

8.2 What the numbers mean⧉

Images usually encode a pixel as a triplet of numbers, but the exact meaning of those numbers can vary significantly. The numbers usually stand for photometric quantities (think of the number of photons received at a photosite), but they may be encoded non-linearly. And different images may use different color spaces, and even RGB comes in many variants. A large share of image-processing bugs ("why are my colors wrong, my blend dark, my HDR muddy?") trace straight back to encoding and color-space mismatches.

💡 Big lesson (L4.1) — pixel numbers are meaningless on their own

The numbers in an image array carry no meaning by themselves. The same triple (0.5, 0.2, 0.1) is a different color, and a different amount of light, depending on two things the array does not contain:

Strip those two away and you are left with a grid of anonymous numbers. Every operation that matters later (white balance, HDR merging, blur and deblur, any color conversion) silently assumes a particular answer to both questions, and gets the wrong result if the assumption is wrong. Hence the discipline of the whole book: never touch a pixel without knowing its color space and its value encoding. These are two common causes of otherwise puzzling image differences.

8.2.1 What the numbers actually mean: encoding⧉

💡 Big lesson (recurrence of (L4.6) and (L4.4)) — mind the encoding

Use the domain expected by the operation. Decode the transfer function before linear-light arithmetic, and encode appropriately for display. Decoding a rendered file does not undo tone mapping or restore scene-linear measurements.

The same stored value can represent different light levels under different transfer functions. This matters immediately when averaging two images or adjusting exposure. Use the space required by the operation, and convert deliberately:

A deblurring model must account for the domain in which the physical blur occurred (Figure 6.8.1). An encoded-domain solver can be valid if it includes the nonlinear encoding in its forward model. Poisson blending instead chooses a gradient-domain appearance objective, which can legitimately differ across spaces (Figure 8.2.1).

fig-deconv-gamma-linear
Figure 6.8.1 (reproduced). Deblurring belongs in linear light. A sharp image is blurred in linear light (a true average of light), then the blur is inverted two ways: on the linear values (the right model) and on the gamma-encoded values (the wrong model, with halos and lost contrast, because gamma and blur do not commute). Both use the same finite number of iterations, so neither recovers the source exactly; at the default settings the linear result scores 28.9 dB and the gamma one 26.0 dB, and the ordering can depend on the source and the blur. Same operation, right and wrong encoding. Pick a real photo (the default) or a synthetic grating (set its contrast and period) that dips into the shadows, where the gamma curve bends hardest and the error is largest; on a photo the gap is not always visually obvious, since most local neighborhoods span a narrow tonal range where gamma is nearly linear. Even where the PSNR is clearly worse, it is the grating that makes the error plain. Developed fully as an inverse problem in Chapter 7.1, Imaging as Bayesian inference with probabilistic priors.
fig-poisson-blend-spaces
Figure 8.2.1. The same seamless clone, solved in three color encodings. One composite (a dolphin cloned into a pool) run through the identical Poisson solve in linear, gamma, and Chong's illumination-invariant log space (Chong et al. 2008), then converted back to sRGB for display, so the only difference between the panels is the space the arithmetic ran in. The re-leveling that hides the seam behaves differently in each: linear leaves the pasted dolphin a garish purple, gamma is milder, and log lands closest to a natural gray. A forward reference; the interactive version and the full story are in Chapter 10.8, Poisson image editing.

There is a further wrinkle: the values in a finished JPEG, or a scanned film frame, are usually not even a clean gamma encoding of scene light. The camera's processor (or the film's chemistry) has already applied tone curves, saturation boosts, and contrast S-curves to make the picture look good straight out of the camera: beautification, not measurement. So a consumer JPEG is roughly: scene light → camera "look" → gamma → 8-bit, with the physical meaning of the numbers scrambled along the way.

State the encoding and color space at every stage. Decoding sRGB recovers display-linear RGB; it does not undo a camera’s tone mapping to recover scene radiance. Raw development also needs black-level, white-level, and camera-color calibration.

Store encoding, color space, range, and alpha convention with the array. A conversion must update both the numbers and their description. Relabeling an sRGB array as linear does not decode it, and an integer-to-float cast does not perform a color conversion. Validate operation inputs and make conversions explicit when intent or information loss is involved.

Figure 8.2.2 distinguishes a deliberate look from an encoding mistake. A neutral or punchy rendition can be intentional; double encoding is a different error. Chapter 45.15, Rendering a raw DNG — from sensor numbers to a faithful image develops the raw-data interpretation in detail.

fig-numbers-mean-versions
Figure 8.2.2. One set of pixel values, three defensible-looking images. From a single raw DNG: (1) the scene-linear data correctly encoded to sRGB, radiometrically right but dull, because it carries no camera tone curve; (2) the same numbers with the encoding mishandled (gamma applied twice), milky and washed; (3) the correct render plus the beauty pass, what a viewer expects. None is "the" picture; each is a different, defensible reading of the same array, which is the whole point.

A black-level error can lift shadows and introduce a color cast before any tone adjustment (Figure 8.2.3). Subtract the calibrated offset in the same units as the decoded raw samples. An image histogram with no values near zero is a diagnostic clue, not a measurement of black level: the scene may contain no dark surface, and flare can add real light. In this book's own case (the figure), the file declared a black level of zero while nothing in the data sat below about 0.18 of full scale, with the highest floor in the green channel, which is where the cast came from. Prefer trusted calibration, masked sensor pixels, or matched dark frames to subtracting the image minimum.

fig-black-level-pedestal-bug
Figure 8.2.3. A file that lies about its numbers. The same DNG rendered twice by the same pipeline; the only difference is the black level. Left: the level taken at the file's word (zero), so the sensor's pedestal stays in the data, every value is lifted by roughly 0.18, and the shadows cannot reach black; the green channel's larger pedestal tints the whole frame. Right: the per-channel pedestal measured from the data and subtracted. A real bug from building this book's raw developer, caught because the histogram had no left half.

A useful programming analogy is to make the image representation part of its type. A buffer tagged as linear sRGB and a buffer tagged as encoded Display P3 should not be interchangeable merely because both contain three floats per pixel. A physical-light blend could require linear inputs; a color conversion could declare its source primaries and reference white. The conversion then becomes an explicit operation rather than an assumption hidden in a function name.

Such a type system would not choose the intended rendering or infer missing metadata, but it could catch a large class of mistakes: adding images with different representations, applying a transfer function twice, or treating a log value as a linear intensity. In ordinary array code, carry those assumptions as metadata and check them at boundaries. That is manual type checking for the image pipeline.

The black-level example came from a practical failure in this book's raw developer. A dusk photograph initially appeared milky with a green cast despite otherwise plausible processing. The supplied zero black-level value was accepted without checking whether it described the decoded samples. An offset mismatch early in the pipeline can survive every later stage and look like a poor tone or white-balance choice. The example motivates checking the decoder's units and offset handling, together with calibration; the minimum value in that photograph alone cannot prove the correct subtraction.

8.2.2 What the numbers actually mean: color spaces⧉

Those three letters, "RGB," hide more than they reveal. Several independent decisions all travel under the same name, which is why two arrays can both be rightly called "RGB" and still be incompatible. The decisions that vary:

what variesexampleswhere it bites
channel orderRGB vs BGR (OpenCV)red and blue swap silently
color space / primariessRGB vs Adobe RGB vs Display P3same numbers, different actual colors → Chapter 4.1, Linear color reproduction and CIE XYZ
encodinglinear vs gammathe section above
range$[0, 1]$ float vs $[0, 255]$ uint8 vs 10/12-bitscale factors, overflow
alpha conventionpremultiplied vs straight→ Chapter 19.1, Transparency and compositing

A bare array announces none of this: the information lives in metadata (the file header, an embedded ICC color profile). The advice is correspondingly simple: read the metadata, don't assume. When two images that "should" match stubbornly don't, this table is the first place to look.

Recap: big lessons of this chapter

💡 Big lesson (L4.1) — pixel numbers are meaningless on their own

The numbers in an image array carry no meaning by themselves. The same triple (0.5, 0.2, 0.1) is a different color, and a different amount of light, depending on two things the array does not contain:

  • its color space: which primaries and white point those R, G, B refer to (sRGB? Adobe RGB? Display P3? a camera's native raw space?); and
  • its value encoding: how a stored number maps to light (linear, gamma, log, or a camera "look" curve).

Strip those two away and you are left with a grid of anonymous numbers. Every operation that matters later (white balance, HDR merging, blur and deblur, any color conversion) silently assumes a particular answer to both questions, and gets the wrong result if the assumption is wrong. Hence the discipline of the whole book: never touch a pixel without knowing its color space and its value encoding. These are two common causes of otherwise puzzling image differences.

💡 Big lesson (recurrence of (L4.6) and (L4.4)) — mind the encoding

Use the domain expected by the operation. Decode the transfer function before linear-light arithmetic, and encode appropriately for display. Decoding a rendered file does not undo tone mapping or restore scene-linear measurements.