Colour Keying with Expressions

I love the expression node. It’s powerful, it’s quick and it’s easy to use. One of the useful things you can do with it is create your own little colour keyer. Yes, there are a number of keying options, and yes, this is effectively just the Quick Key. This is more of an experiment in learning the mechanics behind one of the most important techniques in compositing.

Installing

Sorry folks, no pre-made node for this one yet, but all you need to do is copy and paste the code below into an expression node, add a couple of user knobs and you can key to your hearts content.

 

Code Breakdown

The expression node, for those who don’t know, simply iterates over each pixel and run a number of expressions per channel. It can store variables to help keep the code tidy, and can take advantage of nukes expression syntax and node parenting. Very useful. Create one of these to start.

Firstly, we’re going to need to be able to pick the colour we want to key, and have a way of controlling the range of the keying, so add two user RGB Color knobs (Right click the properties bin of the expression node and select Manage User Knobs, then hit Add to add one at a time). Give the first one the name “target” and the other the name “ratio” (What name you use in the Name field is what we’ll need for the expression node, the Label is what’s displayed to the user).

r_ratio = abs(target.r/(target.r + target.g + target.b) -  r/(r+g+b))
g_ratio = abs(target.g/(target.r + target.g + target.b) -  g/(r+g+b))
b_ratio = abs(target.b/(target.r + target.g + target.b) -  b/(r+g+b))
valid = r_ratio < ratio.r && g_ratio < ratio.g && b_ratio < ratio.b
valid ? 1 - (r_ratio + g_ratio + b_ratio)/(ratio.r + ratio.g + ratio.b) : 0

Above is all the code we’ll need to make this work. At the top of the expression node are 4 lines with boxes on the left and right. The left hand is where we put the variable names (r_ratio, g_ratio, b_ratio and valid), and the right is where we put what to store in the variable (The actual code). Then in the first channel of the expression node, add the last line of code above, and make sure it’s red, green and blue boxes are ticked. And voila, that’s it!

But how does it work? What we’re doing is comparing the target colour against every pixel, and as long as the ratio r, g and b is within the specified range, we return a single value between 0 and 1 of it’s similarity. This will make more sense in a second. Let’s look at each line for now.

r_ratio = abs(target.r/(target.r + target.g + target.b) -  r/(r+g+b))

The first three lines are the exact same thing, just for each different channel. We’re adding up the target colour values so that we can divide each channel into it to give us the ratio of that colour. We then deduct the ratio of the current pixel which we calculate in the same way. We then want to get the absolute value, that is, if it’s negative we want to make it positive, so that we can minus it from 1 later. What this is telling us is how similar the colour’s ratio is to the target’s ratio. If it’s the exact same, we’ll have 0, and the further away it is, the greater the number it will be.

For example, say our target colour is (0.4, 0.4, 0.4), and the current pixel we’re assessing is (0.2, 0.8, 0.5). The breakdown for each would be:

total target = 0.4 + 0.4 + 0.4 = 1.2
total current = 0.2 + 0.8 + 0.5 = 1.5
red similarity = ( 0.4 / 1.2 ) - ( 0.2 / 1.5 ) = 0.333... - 0.133... = 0.2
green similarity = ( 0.4 / 1.2 ) - ( 0.8 / 1.5 ) =  0.333... - 0.533... = 0.2 (absolute)
blue similarity = (0.4 / 1.2 ) - (0.5 / 1.5 ) = 0.333... - 0.333... = 0.0

Now, we only want assess pixels which have values within the range we specify using the ratio RGB knob, so we want to compare these with the variable “valid”.

valid = r_ratio < ratio.r && g_ratio < ratio.g && b_ratio < ratio.b

This simply checks if our calculated similarity is less than the ratio for each channel (The && means AND, that is, it will only return True if all statements are true).

valid ? 1 - (r_ratio + g_ratio + b_ratio)/(ratio.r + ratio.g + ratio.b) : 0

This is actually an if / else statement, with the syntax:  (check if this is true) ? (do this if true) : (do this if false). So if our valid variable is True, ie, all values are in range, then we add them up and divide by the total sum of ratio to give us a single value of how close it is. However, because this value will be 0 for an exact match, and larger the more different it is, we deduct it from 1 so that we have 1 for an exact match with decreasing values. If the valid statement is False, we simply give it a 0 value.

This is all that’s needed, although there is a flaw. If the target colour is quite dark, then any pixel that is very close to black, will always fit in the ratio check. This can be annoying as the purpose of this tool is to isolate colour ranges, and not luma values. We can get around this, but it requires an extra expression node and another user knob.

Extra Tweaking

To add it, add a user knob with the name “thresh_low”, and call it “Low Threshold”. You should also name the current node something unique as you’ll use that in the next part. I used “RK” for now. Now drop down another expression node and add it before the other.

strip_low_r = parent.RK.thresh_low * parent.RK.target.r/(parent.RK.target.r + parent.RK.target.g + parent.RK.target.b)
strip_low_g = parent.RK.thresh_low * parent.RK.target.g/(parent.RK.target.r + parent.RK.target.g + parent.RK.target.b)
strip_low_b = parent.RK.thresh_low * parent.RK.target.b/(parent.RK.target.r + parent.RK.target.g + parent.RK.target.b)
r > strip_low_r ? r : 0
g > strip_low_g ? g : 0
b > strip_low_b ? b : 0

As before, the first 3 lines are all the same, just for each different channel. Store them as variables the same as for the previous node. Note however, that they are referencing a parent node, which in this case is the keyer node we just made. In my case, this node is named RK. You would need to change that to the name of your node if you used different naming (or vice versa).

What the line does is get a percentage of the ratio of the target colour by multiplying it by the low threshold number. We then use the last three lines, one in each channel box with only it’s corresponding channel turned on, to turn any values below this percentage to black. This stops the ratio keyer from picking them out.

 

This is more of a useful introduction to how the concept of keying works than a true keying tool, but does work quite well for extracting simple keying mattes. Just select your target colour (Pro tip: Use Ctrl+Alt+LMB to select a colour from the node above while looking at a lower node to immediately see the result) and then tweak the inidividual RGB channels of the ratio knob to include or exclude pixels with more of that colour.

Advertisement
Colour Keying with Expressions

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s