Ask Your Question
2

Opencv java - Load image to GUI

asked 2013-03-29 05:02:14 -0600

mholecy gravatar image

updated 2013-03-29 05:12:39 -0600

I'm developing an application using Java Opencv-2.4.4 and swing GUI. Problem is that I'm unable to find any solution, that shows efficient way how to print processed image (saved in Mat object) to java swing GUI. For this moment I'm using this clumsy solution:

javax.swing.JLabel outputImage;
outputImage.setIcon(new javax.swing.ImageIcon("/home/username/Output.png"));

private void sliderStateChanged(javax.swing.event.ChangeEvent evt) { 
       .
       .
  Mat canny; // Here is saved what I want to plot
  String filename = "/home/username/Output.png";
  Highgui.imwrite(filename, canny);  // write to disk
  outputImage.setIcon(new ImageIcon(ImageIO.read(new File(filename)))); //update Icon
       .
       .
}

When user changes some values, inputs etc .., in GUI I have to overwrite Output.png on disk and then update jLabel with new image from disk.

Is there any more elegant / efficient solution to this ? Is it posible to plot or convert Mat object directly to Canvas or Image or anything that is printable as image in swing ?

edit retag flag offensive close merge delete

3 answers

Sort by » oldest newest most voted
2

answered 2014-01-14 22:38:32 -0600

Hi,

using System.arraycopy is more than 2 times faster than using raster setDataElements method:

public Image toBufferedImage(Mat m){
        int type = BufferedImage.TYPE_BYTE_GRAY;
        if ( m.channels() > 1 ) {
            type = BufferedImage.TYPE_3BYTE_BGR;
        }
        int bufferSize = m.channels()*m.cols()*m.rows();
        byte [] b = new byte[bufferSize];
        m.get(0,0,b); // get all the pixels
        BufferedImage image = new BufferedImage(m.cols(),m.rows(), type);
        final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        System.arraycopy(b, 0, targetPixels, 0, b.length);  
        return image;

    }
edit flag offensive delete link more

Comments

This is indeed faster than the solution I've posted above (grayscale conversion only marginally, but color images are converted roughly about twice as fast).

Thanks for posting an even better solution.

Lucky Luke gravatar imageLucky Luke ( 2014-01-17 03:38:07 -0600 )edit

Hi Lucky,

I see. I believe there's an even faster way to do it in case we are able to point to native memory instead of copying it. But I also think this is fast enough for what most people will do, so I've delayed this solution (nor could I make it so far).

Daniel Baggio gravatar imageDaniel Baggio ( 2014-01-18 21:21:54 -0600 )edit
6

answered 2013-03-29 07:29:10 -0600

Lucky Luke gravatar image

updated 2013-03-30 15:50:26 -0600

While I'm not sure the following is the most efficient (or even elegant) solution, it works and is definitely better than this "writing to disk..., read from the disk again"-approach:

/**
 * Converts/writes a Mat into a BufferedImage.
 * 
 * @param bgr Mat of type CV_8UC3 or CV_8UC1
 * @return BufferedImage of type TYPE_INT_RGB or TYPE_BYTE_GRAY
 */
public static BufferedImage matToBufferedImage(Mat bgr) {
    int width = bgr.width();
    int height = bgr.height();
    BufferedImage image;
    WritableRaster raster;

    if (bgr.channels()==1) {
        image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
        raster = image.getRaster();

        byte[] px = new byte[1];

        for (int y=0; y<height; y++) {
            for (int x=0; x<width; x++) {
                bgr.get(y,x,px);
                raster.setSample(x, y, 0, px[0]);
            }
        }
    } else {
        image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        raster = image.getRaster();

        byte[] px = new byte[3];
        int[] rgb = new int[3];

        for (int y=0; y<height; y++) {
            for (int x=0; x<width; x++) {
                bgr.get(y,x,px);
                rgb[0] = px[2];
                rgb[1] = px[1];
                rgb[2] = px[0];
                raster.setPixel(x,y,rgb);
            }
        }
    }

    return image;
}

EDIT

Ahh, thanks to an earlier post by Alexander Smorkalov (see http://answers.opencv.org/question/6281/is-there-a-way-to-grab-all-the-pixels-of-a-matrix/?answer=6286#post-id-6286 ) I've figured out to do this much faster by retrieving all image data in one junk.

My first attempt with ImageIO.read given an ByteArrayInputStream of said data yielded promising results at first (6 to 7 times faster), but right after posting the method, that piece of code didn't even work any longer (no idea why it did work at some point earlier). Think this has something to do with unavailable/uninitialized ImageReader or (sorry, I'm not too confident with Java and get easily lost...).

Anyways, a second attempt (without ImageIO and ByteArrayInputStreams and what not) yielded even better results. Observe:

/**
 * Converts/writes a Mat into a BufferedImage.
 * 
 * @param matrix Mat of type CV_8UC3 or CV_8UC1
 * @return BufferedImage of type TYPE_3BYTE_BGR or TYPE_BYTE_GRAY
 */
public static BufferedImage matToBufferedImage(Mat matrix) {
    int cols = matrix.cols();
    int rows = matrix.rows();
    int elemSize = (int)matrix.elemSize();
    byte[] data = new byte[cols * rows * elemSize];
    int type;

    matrix.get(0, 0, data);

    switch (matrix.channels()) {
        case 1:
            type = BufferedImage.TYPE_BYTE_GRAY;
            break;

        case 3: 
            type = BufferedImage.TYPE_3BYTE_BGR;

            // bgr to rgb
            byte b;
            for(int i=0; i<data.length; i=i+3) {
                b = data[i];
                data[i] = data[i+2];
                data[i+2] = b;
            }
            break;

        default:
            return null;
    }

    BufferedImage image = new BufferedImage(cols, rows, type);
    image.getRaster().setDataElements(0, 0, cols, rows, data);

    return image;
}

Quick benchmark results, comparing this new method to the initially posted one (converting a 640x480 Mat to BufferedImage, 10 times, then taking the average):

  • with a grayscale (8-bit, 1 channels) mat: 127 times faster
  • with a bgr (8-bit, 3 channels) mat: 15 times faster

Not surprisingly this is much faster than the first version (and also faster than my ImageIO experiments). But I think we might even do better, by getting rid of that i=i+3 loop which swaps the R ... (more)

edit flag offensive delete link more

Comments

Lucky Luke thank you for marvelous answer. My application is more responsive than ever.

mholecy gravatar imagemholecy ( 2013-03-31 17:01:43 -0600 )edit

Nice solution, but have you thought about the other way around?

Stegger gravatar imageStegger ( 2013-06-01 08:17:51 -0600 )edit

On my system converting my 640x480 camera's images this way is taking a breathtaking 40 msec or so. Yet another edit would be received with delight

Anniepoo gravatar imageAnniepoo ( 2013-08-01 19:42:11 -0600 )edit

cool! this is faster than HighGui.imencode(".png",mat,memory) I was using until discovering this post.

x gravatar imagex ( 2013-09-22 17:22:58 -0600 )edit

Please take note of Daniel's solution (using System.arraycopy) which is again faster than this.

Lucky Luke gravatar imageLucky Luke ( 2014-01-17 03:39:36 -0600 )edit
0

answered 2013-11-10 21:28:41 -0600

What about the solution in http://answers.opencv.org/question/18304/which-class-can-show-an-image-in-java/ ? That seems simpler and faster.

edit flag offensive delete link more

Question Tools

1 follower

Stats

Asked: 2013-03-29 05:02:14 -0600

Seen: 23,457 times

Last updated: Jan 14 '14