5
\$\begingroup\$

I'm working on a project that takes in a video file and does some image processing on each individual frame. The first step in that processing is to take each source frame and generate three monochromatic images based upon the average red, green, and blue values of the image. If that doesn't make sense, I've drawn up an example image for illustration:

Three monochromatic images are produced from each source frame

I achieve this step by passing in a BufferedImage (along with a frame number) to a function that loops over the individual pixels and extracts the color data (via getRGB()), and then setRGB()s it into the the three new images as is appropriate.

Unfortunately the performance is killing me, with it taking up to 4 seconds to render the three images per frame (and, by the way, this is with test footage whose resolution is only 856x480! I'll need to be able to use it on footage of 1080p!) For a 30 second clip at 30FPS, this first step takes around an hour! (~4 seconds x 30 seconds x 30 frames = 3600 seconds) I admit, my 1.4GHz dual core really isn't cut out for this kind of stuff, but if I can render 4 minutes of 1080p 60FPS footage in 10 minutes using Virtualdub, then I know I can squeeze more performance out of this thing.

The code I wrote is below. I'm using Rhino to script Java, but I think it should be clear even to those who normally don't use JavaScript.

importPackage(java.io)
importPackage(javax.imageio)
importPackage(java.awt.image)
importPackage(java.awt)

function makeMonochromaticImages(img, n) {
  var w = img.getWidth()
  var h = img.getHeight()

  // These are the three monochromatic images produced.
  var red   = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
  var green = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
  var blue  = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);

  // I've cached the constructor to avoid constant overload resoloution 
  var _Color = Color['(int,int,int)']

  for(var x = 0; x < w; ++x) {
    for(var y = 0; y < h; ++y) {      
      var c = new Color(img.getRGB(x, y))
      var r = c.getRed()
      var g = c.getGreen()
      var b = c.getBlue()

      var red_grayscaled   = (new _Color(r, r, r)).getRGB()
      var green_grayscaled = (new _Color(g, g, g)).getRGB()
      var blue_grayscaled  = (new _Color(b, b, b)).getRGB()

      red.setRGB(x, y, red_grayscaled)
      green.setRGB(x, y, green_grayscaled)
      blue.setRGB(x, y, blue_grayscaled)
    }
  }

  ImageIO.write(red, "png", new File("work\\newframes\\" + (n) + "-r.png"))
  ImageIO.write(blue, "png", new File("work\\newframes\\" + (n) + "-g.png"))
  ImageIO.write(green, "png", new File("work\\newframes\\" + (n) + "-b.png"))
}

I've already considered some other potential options such as manually converting between Integer and RBG, but this seems really messy. I could also make it multi-threaded and render twice as much at once and fully utilize my two cores, but then the code is still technically slow. I guess I'm looking more for a big picture idea (but I'm also not shying away from micro-optimizations if they will have major impact on performance. Remember for a 856x480 image 410880 pixels have to be processed, so I'm actually creating a lot of Color objects.)

\$\endgroup\$
  • 2
    \$\begingroup\$ What does the profiler say? It's probably the first thing you should do when you have performance issues \$\endgroup\$ – mariosangiorgio Feb 6 '15 at 22:12
  • \$\begingroup\$ Are you compiling the script to java bytecode? This doesn't happen automatically as far as I can see. \$\endgroup\$ – Adrian Leonhard Feb 16 '15 at 21:46
2
\$\begingroup\$

The problem comes from iteratively accessing your image object in your current manner. Take a look below. This is substantially faster.

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;


public class FastRGB {

private final int width;
private final int height;
private final boolean hasAlphaChannel;
private int pixelLength;
private final byte[] pixels;

public FastRGB(BufferedImage image) {
    pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    width = image.getWidth();
    height = image.getHeight();
    hasAlphaChannel = image.getAlphaRaster() != null;
    pixelLength = 3;
    if (hasAlphaChannel) {
        pixelLength = 4;
    }
}

public int getRGB(int x, int y) {
    int pos = (y * pixelLength * width) + (x * pixelLength);
    int argb = -16777216;
    if (hasAlphaChannel) {
        argb = (((int) pixels[pos++] & 0xff) << 24);
    }
    argb += ((int) pixels[pos++] & 0xff);
    argb += (((int) pixels[pos++] & 0xff) << 8);
    argb += (((int) pixels[pos++] & 0xff) << 16);
    return argb;
}
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.