Chroma key composing is a post-production technique for composing two images or video streams together based on color hues. This example demonstrates how to remove the background (the green backdrop) and to deal with some noise and reflection that may add some green shade to scene elements.

Below is presented the input image used in this example. It presents many shades of green in the backdrop as well as some noise due to compression.

The process has three steps as described as follows:

1. Converts green pixels to transparency

In this step all pixels are processed in the HSV color space. Green pixels are converted to transparent. Given a pixel p, it's is considered "green" if:

p.hue >= 60 AND p.hue <= 130 AND p.saturation >= 0.4 AND p.lightness >= 0.3

result:

2. Process remaining green pixels due to reflection and compression noise

Some green pixels remain in the image but it cannot be removed since it's a part of the foreground object. This is very common to happen to hair:


In this case this pixels are filtered and its color is balanced to reduce the green shade.

3. Alpha Boundary

The last step adds gradient transparency to every pixel and its neighbors that are not transparent and are in the boundary of the foreground element.

Result:



Source:
package chromaToTransparency;

import marvin.color.MarvinColorModelConverter;
import marvin.image.MarvinImage;
import marvin.io.MarvinImageIO;

	import static marvin.MarvinPluginCollection.*;
	
	public class ChromaToTransparency {
	
		public ChromaToTransparency(){
			MarvinImage image = MarvinImageIO.loadImage("./res/person_chroma.jpg");
			MarvinImage imageOut = new MarvinImage(image.getWidth(), image.getHeight());
			// 1. Convert green to transparency
			greenToTransparency(image, imageOut);
			MarvinImageIO.saveImage(imageOut, "./res/person_chroma_out1.png");
			// 2. Reduce remaining green pixels
			reduceGreen(imageOut);
			MarvinImageIO.saveImage(imageOut, "./res/person_chroma_out2.png");
			// 3. Apply alpha to the boundary
			alphaBoundary(imageOut, 6);
			MarvinImageIO.saveImage(imageOut, "./res/person_chroma_out3.png");
			
		}
		
		private void greenToTransparency(MarvinImage imageIn, MarvinImage imageOut){
			for(int y=0; y<imageIn.getHeight(); y++){
				for(int x=0; x<imageIn.getWidth(); x++){
					
					int color = imageIn.getIntColor(x, y);
					int r = imageIn.getIntComponent0(x, y);
					int g = imageIn.getIntComponent1(x, y);
					int b = imageIn.getIntComponent2(x, y);
					
					double[] hsv = MarvinColorModelConverter.rgbToHsv(new int[]{color});
					
					if(hsv[0] >= 60 && hsv[0] <= 130 && hsv[1] >= 0.4 && hsv[2] >= 0.3){
						imageOut.setIntColor(x, y, 0, 127, 127, 127);
					}
					else{
						imageOut.setIntColor(x, y, color);
					}
					
				}
			}
		}
		
		private void reduceGreen(MarvinImage image){
			for(int y=0; y<image.getHeight(); y++){
				for(int x=0; x<image.getWidth(); x++){
					int r = image.getIntComponent0(x, y);
					int g = image.getIntComponent1(x, y);
					int b = image.getIntComponent2(x, y);
					int color = image.getIntColor(x, y);
					double[] hsv = MarvinColorModelConverter.rgbToHsv(new int[]{color});
					
					if(hsv[0] >= 60 && hsv[0] <= 130 && hsv[1] >= 0.15 && hsv[2] >= 0.15){
						if((r*b) !=0 && (g*g) / (r*b) > 1.5){
							image.setIntColor(x, y, 255, (int)(r*1.4), (int)g, (int)(b*1.4));
						} else{
							image.setIntColor(x, y, 255, (int)(r*1.2), g, (int)(b*1.2));
						}
					}
				}
			}
		}
		
		public static void main(String[] args) {
			new ChromaToTransparency();
		}
	}