Simple RGB color component chroma key

More and more often we come across the use of chromakey in the most unexpected places. For a long time the thought came to try to realize something of my own.

What is a chromakey, for those who are not familiar? Chromakey is an image-combining technology widely used in film-video-television production. Poison green and bright blue are most often used as the “key” colors, only because such shades are not found in the spectrum of the complexion of the face and hair of a person. For very darkened scenes, such as underground caves, use a bright orange color as the key.

After thinking for a while and going from a distant and not the easiest option using the HSL model, I could not immediately realize something universal. I must say, this "some" time is pretty well dragged on. And then, quite unexpectedly, the thought came to my mind to try with a simple option, simply discarding one of the components and making an alpha channel by its value. The green channel was taken as a basis, as one of the most frequently used in such tasks.
The train of thought was as follows:
- in total, we have 256 gradations of each channel
- everything that, approximately, below the 30th value can be attributed to “black” green
- the option where the green component is dominant is almost certainly green (there is still gradations of gray, close to gray, etc.)

Having a randomly taken image from the Internet

(taken at vid8o.files.wordpress.com/2011/01/chromakey.jpg?w=620 )

and similarly taken background

(I don’t know who belongs to), we

go to implement an algorithm for a 24-bit image and 8 bit mask (alpha channel):

``````byte const black_threshold = 30;
byte* chroma_p = chroma_data;
byte* alpha_p = alpha_data;
for (int counter = 0; counter < size; ++counter)
{
byte b = *chroma_p++;
byte g = *chroma_p++;
byte r = *chroma_p++;
// определим, зеленый ли пиксел перед нами
if (g > r && g > b)
{
// если этот зеленый достаточно черный, то оставим его нетронутым в маске
if (g < black_threshold)
*alpha_p++ = 255;
else
{
// вычислим разницу между компонентами для определения яркости маски
byte m = g - max(r, b);
*alpha_p++ = 255 - m;
}
}
}
``````

We start, we get: a

and a mask overlay image on our background.

As a result, our implementation shows a pretty good, but very rough result. In particular, we see a green fog in the resulting image. Take a look at the histogram of the mask:

We see that the main part of the mask in the transparency region lies in the range 17-100. Therefore, we stretch the interval of values ​​100-255 to the interval 0-255, thereby “killing” the shroud and get just such a histogram

and just such a mask, more similar to what we need

.

As a result, we got rid of the unnecessary veil

,

but we see the remaining halo around the mask and the green “flare” of the tone, which we naturally looked against the original green background and looked quite alien against our new background. In general, the result was cheap, but a number of questions remain. How to deal with a halo? You can try to narrow the mask with a convolution, or try to somehow stretch the histogram of the mask differently.

But I don’t know how to deal with flare, so food is enough for further work. Criticism, advice and guidance are welcome.

I will also be grateful for links to similar works, including their implementation using GPU, OpenCV, etc.

UPD 1: doubled the size of the processed image and masks