Article : Java Image Processing Cookbook1

Java Image Processing Cookbook1

Introduction

Chroma Key is a technique for mixing two images together, in which a color (or a small color range) from the overlay image is made transparent, revealing another image (the background) behind it. This technique is also known as greenscreen or bluescreen, since green and blue screens are often used to denote the overlay image's region which must be removed or not rendered over the background image.

Implementation of chroma key composition or mixing is trivial, but results are often not perfect since it is difficult to have a clean separation between pixels belonging to the foreground (which must be rendered over a background image) and pixels that must be considered transparent or translucent (which shouldn't be rendered) due to antialias effects, compression artifacts or other reasons. Problems may also occur when the transparent or translucent region is not homogeneous, which makes the determination of the transparency/translucency values hard or imprecise.

In this chapter we'll see some simple examples of chroma key and suggestions on how to improve the results. For an example, we'll render a UFO image with a green background (which will not be rendered) over the Johns Hopkins University's Upper Quad (see images below).

Background image -- Johns Hopkins University's Upper Quad

Overlay image (green pixels will not be rendered)

In this chapter transparent pixels are pixels that, when overlaid over others, will not change their colors, being, for all practical purposes, invisible. Translucent pixels, when overlaid over others, may change their colors depending on the amount of translucency. Translucent pixels can be considered partially transparent.

Transparency

One simple way to implement chroma key composition is using a color that will be considered fully transparent or invisible when one image is overlaid into another. We can then loop over all pixels in the overlay image: if a pixel's color is the same as the transparent color then we skip it, otherwise we paint it over the background image.

With this technique, a pixel in the overlay image will be either painted over the background image or not painted at all -- This technique is implemented in the application in the class TransparencyByColors, shown below.

TransparencyByColors.java

 1 /*

 2  * Part of the Java Image Processing Cookbook, please see

 3  * http://www.lac.inpe.br/~rafael.santos/JIPCookbook.jsp

 4  * for information on usage and distribution.

 5  * Rafael Santos (rafael.santos@lac.inpe.br)

 6  */

 7 package howto.chromakey;

 8  

 9 import java.awt.image.BufferedImage;

 10 import java.awt.image.WritableRaster;

 11 import java.io.File;

 12 import java.io.IOException;

 13  

 14 import javax.imageio.ImageIO;

 15 import javax.swing.JFrame;

 16  

 17 import com.sun.media.jai.widget.DisplayJAI;

 18  

 19 /**

 20  * This class demonstrates how to select a color for transparency to create overlays of images. 

 21  * It reads two images from the disk, drawing the second one over the first one but considering 

 22  * green pixels on the second one as transparent.

 23  */

 24 public class TransparencyByColors

 25  {

 26  /**

 27  * @param args the arguments for the application (will be ignored)

 28  * @throws IOException 

 29  */

 30  public static void main(String[] args) throws IOException

 31  {

 32  // Read the input images. We assume that the first image is the background, and that it is larger

 33  // than the second image.

 34  BufferedImage background = ImageIO.read(new File("dsc00099_large.jpg"));

 35  WritableRaster raster = background.getRaster();

 36  BufferedImage layer = ImageIO.read(new File("UFO.png"));

 37  int width = layer.getWidth();

 38  int height = layer.getHeight();

 39  // We will shift the overlay image over the background this amount.

 40  int shiftX = 72;

 41  int shiftY = 80;

 42  // Slow method: scan all input (layer) image pixels, plotting only those which are not green.

 43  int lPixel,red,green,blue;

 44  for(int w=0;w

 45  for(int h=0;h

 46  {

 47  lPixel = layer.getRGB(w,h);

 48  if ((lPixel&0x00FFFFFF) != 0x00FF00) // Not pure green!

 49  {

 50  red = (int)((lPixel&0x00FF0000)>>>16); // Red level

 51  green = (int)((lPixel&0x0000FF00)>>>8); // Green level

 52  blue = (int) (lPixel&0x000000FF); // Blue level

 53  // Set the pixel on the output image's raster.

 54  raster.setPixel(w+shiftX,h+shiftY,new int[]{red,green,blue,255}); 

 55  }

 56  } // end for

 57  // Save the image as a PNG via ImageIO.

 58  ImageIO.write(background,"PNG",new File("transparency.png"));

 59  // Display the input and output images.

 60  JFrame frame = new JFrame("Transparency by Colors"); 

 61  frame.add(new DisplayJAI(background));

 62  frame.pack();

 63  frame.setVisible(true);

 64  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

 65  }

 66  }

This application will open the background and overlay image, and for each pixel in the overlay image will get its color. If the pixel is not exactly green it will be paint over the raster of the background image, which will be stored in disk and displayed in a JFrame. The resulting image is shown below.

Overlaid image (using transparency)

We can see that using a single color as transparent yields noticeable problems: there are some green streaks on the edges of the UFO, which can be seen in a enlarged region of the overlaid image, shown below. This happened because the UFO image is antialiased, therefore there are some pixels' colors which are a mixture of the edges and the green background, not being pure green, therefore painted over the background image.

Detail of the overlaid image (using transparency)

Translucency

A better way to do a chroma key composition is to use different degrees of transparency, so overlay and background pixels' colors will be mixed accordingly to the similarity of the overlay pixel color to the color defined as transparent.

In order to achieve this, we must calculate the amount of translucency for each pixel, and use this amount to weight how much of the background and overlay colors will influence the final color of a pixel. One simple way do to this is to calculate the distance from a overlay pixel's color to the color that must be transparent, normalizing this value.

With a translucency value between 0 (totally translucent or invisible) and 1 (totally opaque), we can calculate each color component of a pixel as R=L*translucency+B*(1-translucency), where R is the output color value, L is the input overlay color value and B is the input background color value.

This technique is implemented in the application in the class TranslucencyByColors, shown below. The amount of translucency is calculated as the Euclidean distance between the color on the layer image and the color which should be transparent (green), limited to 100 units of distance and normalized to the range [0,1].

TranslucencyByColors.java

 1 /*

 2  * Part of the Java Image Processing Cookbook, please see

 3  * http://www.lac.inpe.br/~rafael.santos/JIPCookbook.jsp

 4  * for information on usage and distribution.

 5  * Rafael Santos (rafael.santos@lac.inpe.br)

 6  */

 7 package howto.chromakey;

 8  

 9 import java.awt.image.BufferedImage;

 10 import java.awt.image.WritableRaster;

 11 import java.io.File;

 12 import java.io.IOException;

 13  

 14 import javax.imageio.ImageIO;

 15 import javax.swing.JFrame;

 16  

 17 import com.sun.media.jai.widget.DisplayJAI;

 18  

 19 /**

 20  * This class demonstrates how to select a color for translucency. It reads two images from the disk,

 21  * drawing the second one over the first one but considering green pixels on the second one as

 22  * transparent/translucent -- the closer the color is to green, the more transparent it is.

 23  */

 24 public class TranslucencyByColors

 25  {

 26  /**

 27  * @param args the arguments for the application (will be ignored)

 28  * @throws IOException 

 29  */

 30  public static void main(String[] args) throws IOException

 31  {

 32  // Read the input images. We assume that the first image is the background, and that it is larger

 33  // than the second image.

 34  BufferedImage background = ImageIO.read(new File("dsc00099_large.jpg"));

 35  WritableRaster raster = background.getRaster();

 36  BufferedImage layer = ImageIO.read(new File("UFO.png"));

 37  int width = layer.getWidth();

 38  int height = layer.getHeight();

 39  // We will shift the overlay image over the background this amount.

 40  int shiftX = 72;

 41  int shiftY = 80;

 42  // Slow method: scan all input (layer) image pixels and corresponding background pixels.

 43  // Calculate its "greenness" and translucency and recreate the pixels' values, plotting

 44  // them over the background.

 45  int iPixel,lPixel;

 46  int refRed = 0;

 47  int refGreen = 255;

 48  int refBlue = 0;

 49  int iRed,iGreen,iBlue,lRed,lGreen,lBlue,oRed,oGreen,oBlue;

 50  for(int w=0;w

 51  for(int h=0;h

 52  {

 53  iPixel = background.getRGB(w+shiftX,h+shiftY);

 54  iRed = (int)((iPixel&0x00FF0000)>>>16); // Red level

 55  iGreen = (int)((iPixel&0x0000FF00)>>>8); // Green level

 56  iBlue = (int) (iPixel&0x000000FF); // Blue level

 57  lPixel = layer.getRGB(w,h);

 58  lRed = (int)((lPixel&0x00FF0000)>>>16); // Red level

 59  lGreen = (int)((lPixel&0x0000FF00)>>>8); // Green level

 60  lBlue = (int) (lPixel&0x000000FF); // Blue level

 61  // Calculate the translucency, based on the green value of the layer. To make calculations

 62  // easier, let's assume that the translucency is a value between 0 (invisible) and 1 (opaque).

 63  double distance = Math.sqrt((refRed-lRed)*(refRed-lRed)+

 64  (refGreen-lGreen)*(refGreen-lGreen)+

 65  (refBlue-lBlue)*(refBlue-lBlue));

 66  // Convert distance to the range 0-100.

 67  distance = Math.min(distance,100f);

 68  float translucency = ((float)distance/100f);

 69  // Recalculate the RGB coordinates of the layer and background pixels, using the translucency

 70  // as a weight.

 71  oRed = (int)(translucency*lRed+(1-translucency)*iRed);

 72  oGreen = (int)(translucency*lGreen+(1-translucency)*iGreen);

 73  oBlue = (int)(translucency*lBlue+(1-translucency)*iBlue);

 74  // Set the pixel on the output image's raster.

 75  raster.setPixel(w+shiftX,h+shiftY,new int[]{oRed,oGreen,oBlue,255}); 

 76  } // end for

 77  // Save the image as a PNG via ImageIO.

 78  ImageIO.write(background,"PNG",new File("translucency.png"));

 79  // Display the input and output images.

 80  JFrame frame = new JFrame("Translucency by Colors"); 

 81  frame.add(new DisplayJAI(background));

 82  frame.pack();

 83  frame.setVisible(true);

 84  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

 85  }

 86  }

The resulting image is shown below. It is slightly better looking than the overlay created with only transparency, but we still can see some green edges in parts of the image.

Overlaid image (using translucency)

Detail of the overlaid image (using translucency)

Another way to calculate translucency is using a color space based on hue -- this way, we can calculate the differences between the transparent color and a pixel's color in a more intuitive way.

The next example shows how to calculate the translucency using the HSB (hue, saturation, brightness) color space and a user-defined tolerance value. With a high tolerance value, even colors different than the reference green will be considered translucent, while with a low value, only colors really close to the reference green will be translucent. More details on the tolerance value will be shown below.

The example is implemented in the application in class TranslucencyByColorsHSB.

TranslucencyByColorsHSB.java

 1 /*

 2  * Part of the Java Image Processing Cookbook, please see

 3  * http://www.lac.inpe.br/~rafael.santos/JIPCookbook.jsp

 4  * for information on usage and distribution.

 5  * Rafael Santos (rafael.santos@lac.inpe.br)

 6  */

 7 package howto.chromakey;

 8  

 9 import java.awt.Color;

 10 import java.awt.image.BufferedImage;

 11 import java.awt.image.WritableRaster;

 12 import java.io.File;

 13 import java.io.IOException;

 14  

 15 import javax.imageio.ImageIO;

 16 import javax.swing.JFrame;

 17  

 18 import com.sun.media.jai.widget.DisplayJAI;

 19  

 20 /**

 21  * This class demonstrates how to select a color for translucency. It reads two images from the disk,

 22  * drawing the second one over the first one but considering green pixels on the second one as

 23  * transparent/translucent -- the closer the color is to green, the more transparent it is.

 24  * In this example, the similarity to green will be calculated using the hue coordinate (HSB color

 25  * space).

 26  */

 27 public class TranslucencyByColorsHSB

 28  {

 29  /**

 30  * @param args the arguments for the application (will be ignored)

 31  * @throws IOException 

 32  */

 33  public static void main(String[] args) throws IOException

 34  {

 35  // Read the input images. We assume that the first image is the background, and that it is larger

 36  // than the second image.

 37  BufferedImage background = ImageIO.read(new File("dsc00099_large.jpg"));

 38  WritableRaster raster = background.getRaster();

 39  BufferedImage layer = ImageIO.read(new File("UFO.png"));

 40  int width = layer.getWidth();

 41  int height = layer.getHeight();

 42  // We will shift the overlay image over the background this amount.

 43  int shiftX = 72;

 44  int shiftY = 80;

 45  // Slow method: scan all input (layer) image pixels and corresponding background pixels.

 46  // Calculate its "greenness" and translucency and recreate the pixels' values, plotting

 47  // them over the background.

 48  int iPixel,lPixel;

 49  float targetHue = 1f/3f;

 50  float tolerance = 0.1f;

 51  int iRed,iGreen,iBlue,lRed,lGreen,lBlue,oRed,oGreen,oBlue;

 52  for(int w=0;w

 53  for(int h=0;h

 54  {

 55  // Background pixels.

 56  iPixel = background.getRGB(w+shiftX,h+shiftY);

 57  iRed = (int)((iPixel&0x00FF0000)>>>16); // Red level

 58  iGreen = (int)((iPixel&0x0000FF00)>>>8); // Green level

 59  iBlue = (int) (iPixel&0x000000FF); // Blue level

 60  // Layer pixels.

 61  lPixel = layer.getRGB(w,h);

 62  lRed = (int)((lPixel&0x00FF0000)>>>16); // Red level

 63  lGreen = (int)((lPixel&0x0000FF00)>>>8); // Green level

 64  lBlue = (int) (lPixel&0x000000FF); // Blue level

 65  float[] lHSB = Color.RGBtoHSB(lRed,lGreen,lBlue,null);

 66  // Calculate the translucency, based on the green value of the layer, using HSB coordinates.

 67  // To make calculations easier, let's assume that the translucency is a value between 0 

 68  // (invisible) and 1 (opaque).

 69  float deltaHue = Math.abs(lHSB[0]-targetHue);

 70  float translucency = (deltaHue/tolerance);

 71  translucency = Math.min(translucency,1f);

 72  // Recalculate the RGB coordinates of the layer and background pixels, using the translucency

 73  // as a weight.

 74  oRed = (int)(translucency*lRed+(1-translucency)*iRed);

 75  oGreen = (int)(translucency*lGreen+(1-translucency)*iGreen);

 76  oBlue = (int)(translucency*lBlue+(1-translucency)*iBlue);

 77  // Set the pixel on the output image's raster.

 78  raster.setPixel(w+shiftX,h+shiftY,new int[]{oRed,oGreen,oBlue,255}); 

 79  } // end for

 80  // Save the image as a PNG via ImageIO.

 81  ImageIO.write(background,"PNG",new File("translucencyHSB.png"));

 82  // Display the input and output images.

 83  JFrame frame = new JFrame("Translucency by Colors (HSB)"); 

 84  frame.add(new DisplayJAI(background));

 85  frame.pack();

 86  frame.setVisible(true);

 87  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

 88  }

 89  

 90  }

The resulting image (shown below) appears even better than the obtained with other approaches in this chapter. The green tint in the UFO's edge is almost unnoticeable.

Overlaid image (using translucency calculated in the HSB color space)

Detail of overlaid image (using translucency calculated in the HSB color space)

A low tolerance value will ensure that only pixels which color is very close to green will be translucent, probably causing some greenish pixels to be only partially translucent. A large value will make greenish pixels more transparent, but can affect also other colors, making them translucent too. The figure below shows details on the resulting images obtained with three different tolerance values. We can see that a value of 0.4 removes all the greenish tint around the edges of the UFO, but also makes the other colors partially translucent, specially the ones which hue is close to green.

Composition using tolerance value 0.01

Composition using tolerance value 0.1

Composition using tolerance value 0.4

Tips and tricks

In the examples in this chapter we used a PNG overlay image (lossless compression) which was set over a JPEG image. If we use a JPEG or other lossy compressed image as the overlay, the lossy compression artifacts may cause smudges and other artifacts when overlaid.

This is shown in the example below, where a highly compressed image was overlaid into another. Even with a good composition algorithm the compression artifacts causes the green halo and other defects. To avoid this, we must use losslessly compressed images as overlays, or at least images compressed without too much loss of information.

Overlaid image (with a compressed overlay image)

Detail of overlaid image (with a compressed overlay image)

Some problems that may occur (and possible solutions) are:

  • A non-homogeneous background in the overlay image (which is bound to happen in real green or blue backgrounds). In this case it may help use more lenient tolerance values, or even use more than one transparent color and calculating translucency based on relative translucency to those colors.
  • Non-background regions with the same color as the background: for example, if a person is wearing a shirt with a drawing with colors similar to the background, those colors may cause "holes" when the images are overlaid. In this case we may consider a smarter algorithm, which will detect small regions that can be considered as non-background and rendered without translucency. There is no guarantee that this will work, since it is very hard to determine which small regions with similar colors are background and foreground (we expect background to be a large, contiguous region, but depending on the subject holes may naturally occur -- think about a person with his/her hands on his/her hips.)

Some other possible ways to get better looking results are:

  • Using not only the hue, but also saturation and brightness/lightness information to calculate translucency. This will help when there are slightly green tinted white, gray or black colors in the overlay. Very light, dark colors can be considered foreground regardless of the hue.