Matching

Matching Colour and Light

Black levels, low frequencies, ratios and differences, luminance and colour

Math in Nuke, the series: Merge & Premultiplication · The Grade Node · ColorCorrect · Saturation, Hue & HSV · Keying · Despill · Matching Colour & Light · Edge Extend · Lin, Log & Gamma · Filtering & Resampling · Edge Detection · Vectors · Trigonometry · Expressions & Curves

A paint patch from another frame, a CG element, a plate shot an hour later: half of compositing is making one image look like it was lit like another. There are dozens of gizmos for it, and under the knobs most of them share the same idea, which is surprisingly simple. This page is about that maths. At the end there's a node of mine built on it, with separate amounts for luminance and for hue and saturation.



Useful Links



Index



01. Light and detail

Every pixel of a photo is, roughly, light × surface: how much light reaches that point and what fraction of it the surface sends back. The light changes slowly across the frame (a sunny side, a shadow, a warm lamp), the surface changes quickly (texture, edges, grain). In frequency terms: the light lives in the low frequencies, the detail in the high frequencies.

That's the key to matching. Two images of the same place under different light differ mostly in the low frequencies. Two different textures under the same light differ mostly in the high ones. A good colour match changes the first and leaves the second alone.



02. Matching points: black and white levels

The simplest match uses just two points. Pick the darkest and the brightest value of the image you have into the blackpoint and whitepoint of a Grade, the same two points of the reference into lift and gain: the Grade maps one range onto the other, per channel.

out = (x − blackpoint) / (whitepoint − blackpoint) × (gain − lift) + lift

It's exact on those two points and linear in between, and it's the right tool when the difference is a global exposure and colour cast. The blacks deserve special care, because in a comp they carry the atmosphere: a black object far away isn't black, it's the colour of the haze. Tony Lyons' BlacksMatch is built for exactly that: it lifts the blacks of an element to a black level (even a per-pixel image of it) without touching the mids, with a toe curve that, as I measured on the Grade page, generalises Nuke's own Toe.



03. Matching the low frequencies

When the light changes across the frame, two points aren't enough. The trick used by most match gizmos: blur both images until the detail is gone, compare the blurs, apply the difference to the sharp image.

correction = blur(reference) / blur(source)     out = source × correction

00.30.50.8100.40.81.2position along a rowvaluereference (detail + light)blur(reference): the lightsource: other detail, other lightblur(source)
One row of pixels. The source has its own detail and a different light (pink). The blurs keep only the light (dashed).

Why it works: if source = lights × surface and the blur removes the surface, then blur(source) ≈ lights and blur(reference) ≈ lightr. Multiplying the source by lightr / lights swaps one light for the other and keeps the source's own detail:

00.30.50.8100.40.81.2position along a rowvaluereferencesource x blur(ref) / blur(source)
After the correction: the source has the light of the reference and still its own detail.

On a real image, a patch with a warmer light and a brightness gradient, matched to the plate around it:

The patch before the match: warmer and with a gradient
the patch, before
The patch after the match
after the match, blur 60
The reference
the reference
The correction map
The correction, shown at half brightness (grey = no change): a soft map, bluer on the left where the patch was too warm.


04. The blur size

Everything depends on the blur. Too small, and the blur still contains detail: the correction carries the texture of the reference into the source (with no blur at all, the "match" simply copies the reference). Too big, and it can't follow the light: a shadow edge or a gradient gets averaged away. A good start is a blur a bit bigger than the details that differ between the two images, and smaller than the changes of light you want to follow.

Blur 0: the result copies the reference
blur 0: it copies the reference, detail included
Blur 60
blur 60
Blur 250
blur 250: the gradient isn't fully followed


05. Where to sample, and what to ignore

A plain blur averages everything in its radius, including what you don't want: the black outside a patch, the object you are painting out. Match gizmos blur with the premult, blur, unpremult trick, the same one IBKColour uses to fill holes:

average = blur(image × mask) / blur(mask)

The division turns the blur into an average of the pixels inside the mask only. That's also why a good match node has an occlusion mask: if the reference contains something that the source doesn't (the actor you're removing, a rig), its low frequencies end up in the correction as a soft "ghost". Mask it out of the sampling and the ghost goes away.

A patch taken from far away
a patch taken from another part of the frame
The ghost of the tower leg in the matched patch
matched with blur 60: the dark leg of the reference leaves a ghost


06. Multiply or add

The correction can be a ratio or a difference:

multiply:   out = source × blur(ref) / blur(source)
add:   out = source + blur(ref) − blur(source)

Multiply is the physical choice for a change of light: twice the light makes every value twice as bright, the shadows included, and black stays black. Add fits what sits on top of the image: haze, flare, veiling glare, a lifted black level. They are equivalent in a log space (a ratio in linear is a difference in log), which is why some tools work there. Multiply has one danger: a dark or empty area divides by almost zero. That's why the ratio is usually clamped.



07. Luminance and colour, separately

Often you want only part of the match: the brightness of the reference but the colour of the source, or the other way round. Match gizmos usually offer it as checkboxes or a menu (full colour, luminance only, colour only). The maths behind it: split each colour into its luminance Y and its chroma, the colour divided by its luminance:

Y = 0.2125 r + 0.7154 g + 0.0721 b     chroma = (r, g, b) / Y

A colour is its luminance times its chroma, and so is the correction, exactly:

ref / source = (Yref / Ysource) × (chromaref / chromasource)

The first factor changes only the brightness, the second only the hue and saturation. And once they are separated, you don't have to choose all or nothing. Raise each factor to a power between 0 and 1 and you get any amount of each:

correction = (Yref / Ysource)luminance × (chromaref / chromasource)hue/sat

A power is the right way to take "half" of a ratio: half of doubling the light is ×1.41, not ×1.5, because two half steps must multiply back to the full one. For the additive version the split is the same idea with sums: luminance × (Yref − Ysource) + hue/sat × (chroma difference). Some tools work in HSV instead, which needs care because the hue is an angle and wraps around; the ratio of chromas has no angle and nothing to wrap.

Luminance only
luminance 1, hue/sat 0: the brightness is fixed, the patch is still warm
Colour only
luminance 0, hue/sat 1: the colour is fixed, the gradient is still there


08. Time: hold, average, clamp

A blur of every frame flickers whenever something moves through it. Three cures: hold the reference (and sometimes the source) on one frame, so the correction is the same on every frame; average the correction over a few frames (a TimeEcho in average mode); and clamp it, so a dark or empty area can't produce an extreme ratio.



09. ColourMatch, my node

So I put the two ideas together in one node, built and tested in Nuke 17. Its knobs, from top to bottom:

- blur size: the low-frequency filter of chapter 04.
- luminance and hue / saturation: the two amounts of chapter 07, from 0 (keep the source) to 1 (match the reference).
- math: multiply or add (chapter 06).
- hold reference frame and ref frame, average frames, ratio min / max: chapter 08.
- show: the result or the correction map; mix, and a mask input.

It has three inputs: source (what you correct), reference (what it should look like) and mask. The alpha of the source and of the reference says where to sample, so a roto in the reference's alpha is the occlusion matte of chapter 05. I checked it numerically: with both amounts at 1 it gives back the reference exactly when the source is a graded copy of it; with luminance only, the result has the luminance of the reference and the chroma of the source, and vice versa; the add mode removes an offset exactly.



Match Lab

The patch of chapter 03, matched live with the maths of the node. Move the blur, the two amounts, switch between multiply and add. before shows the patch untouched, correction the correction map at half brightness. Drag on the image to compare with the reference. The node at the bottom of the page follows your settings.

Reference Patch
Loading…
math
view

The Lab blurs with three box blurs, which is close to, but not exactly, Nuke's Gaussian Blur: the same blur size can look a little different.



Nodes to paste in Nuke

Click a box, copy it (Ctrl+C / Cmd+C) and paste it in Nuke's Node Graph (Ctrl+V / Cmd+V).

ColourMatch. My node from chapter 09: inputs source, reference and mask; blur size, luminance and hue / saturation amounts, multiply or add, hold, average and clamp of the correction.

Black toe. The BlacksMatch curve (Tony Lyons) in one Expression: set the black level you want, nothing goes below it and everything above reach × black is untouched.



The math series

The other pages about maths in Nuke:
Merge & Premultiplication The Grade Node ColorCorrect Saturation, Hue & HSV Keying Despill Edge Extend Lin, Log & Gamma Filtering & Resampling Edge Detection Vectors Trigonometry Expressions & Curves
And if you like this, have a look at the Matrices in Nuke series.

Leave a comment