Highlight differences between images
Asked Answered
S

3

8

There is this image comparison code I am supposed to modify to highlight/point out the difference between two images. Is there a way to modify this code so as to highlight the differences in images. If not any suggestion on how to go about it would be greatly appreciated.

 int width1 = img1.getWidth(null);
            int width2 = img2.getWidth(null);
            int height1 = img1.getHeight(null);
            int height2 = img2.getHeight(null);
            if ((width1 != width2) || (height1 != height2)) {
                System.err.println("Error: Images dimensions mismatch");
                System.exit(1);
            }
            long diff = 0;
            for (int i = 0; i < height1; i++) {
                for (int j = 0; j < width1; j++) {
                    int rgb1 = img1.getRGB(j, i);
                    int rgb2 = img2.getRGB(j, i);
                    int r1 = (rgb1 >> 16) & 0xff;
                    int g1 = (rgb1 >> 8) & 0xff;
                    int b1 = (rgb1) & 0xff;
                    int r2 = (rgb2 >> 16) & 0xff;
                    int g2 = (rgb2 >> 8) & 0xff;
                    int b2 = (rgb2) & 0xff;
                    diff += Math.abs(r1 - r2);
                    diff += Math.abs(g1 - g2);
                    diff += Math.abs(b1 - b2);
                }
            }
            double n = width1 * height1 * 3;
            double p = diff / n / 255.0;
            return (p * 100.0);
Smelly answered 29/7, 2014 at 18:34 Comment(1)
I'm assuming you're using a BufferedImage class? This edit is pretty simple. All you'd have to do is set each pixel in your image to be the difference. I'll write an answer for you.Dr
D
13

What I would do is set each pixel to be the difference between one pixel in one image and the corresponding pixel in the other image. The difference that is being calculated in your original code is based on the L1 norm. This is also called the sum of absolute differences too. In any case, write a method that would take in your two images, and return an image of the same size that sets each location to be the difference for each pair of pixels that share the same location in the final image. Basically, this will give you an indication as to which pixels are different. The whiter the pixel, the more difference there is between these two corresponding locations.

I'm also going to assume you're using a BufferedImage class, as getRGB() methods are used and you are bit-shifting to access individual channels. In other words, make a method that looks like this:

public static BufferedImage getDifferenceImage(BufferedImage img1, BufferedImage img2) {
    int width1 = img1.getWidth(); // Change - getWidth() and getHeight() for BufferedImage
    int width2 = img2.getWidth(); // take no arguments
    int height1 = img1.getHeight();
    int height2 = img2.getHeight();
    if ((width1 != width2) || (height1 != height2)) {
        System.err.println("Error: Images dimensions mismatch");
        System.exit(1);
    }

    // NEW - Create output Buffered image of type RGB
    BufferedImage outImg = new BufferedImage(width1, height1, BufferedImage.TYPE_INT_RGB);

    // Modified - Changed to int as pixels are ints
    int diff;
    int result; // Stores output pixel
    for (int i = 0; i < height1; i++) {
        for (int j = 0; j < width1; j++) {
            int rgb1 = img1.getRGB(j, i);
            int rgb2 = img2.getRGB(j, i);
            int r1 = (rgb1 >> 16) & 0xff;
            int g1 = (rgb1 >> 8) & 0xff;
            int b1 = (rgb1) & 0xff;
            int r2 = (rgb2 >> 16) & 0xff;
            int g2 = (rgb2 >> 8) & 0xff;
            int b2 = (rgb2) & 0xff;
            diff = Math.abs(r1 - r2); // Change
            diff += Math.abs(g1 - g2);
            diff += Math.abs(b1 - b2);
            diff /= 3; // Change - Ensure result is between 0 - 255
            // Make the difference image gray scale
            // The RGB components are all the same
            result = (diff << 16) | (diff << 8) | diff;
            outImg.setRGB(j, i, result); // Set result
        }
    }

    // Now return
    return outImg;
}

To call this method, simply do:

outImg = getDifferenceImage(img1, img2);

This is assuming that you are calling this within a method of your class. Have fun and good luck!

Dr answered 29/7, 2014 at 20:26 Comment(1)
new BufferedImage(width, height ... should be new BufferedImage(width1, height1Mccomb
G
23

This solution did the trick for me. It highlights differences, and has the best performance out of the methods I've tried. (Assumptions: images are the same size. This method hasn't been tested with transparencies.)

Average time to compare a 1600x860 PNG image 50 times (on same machine):

  • JDK7 ~178 milliseconds
  • JDK8 ~139 milliseconds

Does anyone have a better/faster solution?

public static BufferedImage getDifferenceImage(BufferedImage img1, BufferedImage img2) {
    // convert images to pixel arrays...
    final int w = img1.getWidth(),
            h = img1.getHeight(), 
            highlight = Color.MAGENTA.getRGB();
    final int[] p1 = img1.getRGB(0, 0, w, h, null, 0, w);
    final int[] p2 = img2.getRGB(0, 0, w, h, null, 0, w);
    // compare img1 to img2, pixel by pixel. If different, highlight img1's pixel...
    for (int i = 0; i < p1.length; i++) {
        if (p1[i] != p2[i]) {
            p1[i] = highlight;
        }
    }
    // save img1's pixels to a new BufferedImage, and return it...
    // (May require TYPE_INT_ARGB)
    final BufferedImage out = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
    out.setRGB(0, 0, w, h, p1, 0, w);
    return out;
}

Usage:

import javax.imageio.ImageIO;
import java.io.File;

ImageIO.write(
        getDifferenceImage(
                ImageIO.read(new File("a.png")),
                ImageIO.read(new File("b.png"))),
        "png",
        new File("output.png"));

Some inspiration...

Gaikwar answered 6/8, 2014 at 2:21 Comment(8)
Thanks for posting this solution. It solved my problem as well. However, I used IntStream.range() instead of a for-loop, and it runs faster (even with a call to an AtomicDouble.getAndIncrement() in the lambda).Footless
@Footless - Excellent! Can you show us the code and performance comparisons?Gaikwar
Awesome work..very fast also..thank you for the answerImmobile
How does this work to work on specific part of the image?Anemo
@Anemo - depends, but that's a completely different question. If you're comparing a "square" shape, you could probably work it out yourself... I'll leave that one to you though.Gaikwar
I tried changing the X & Y Coordinates in the parameters but that gave me out of bound exception. I wanted to only highlight differences in let say top right of the image.Anemo
@Anemo - create a new stackoverflow question. This isn't the best place for a discussion. (Add the link here, and I'll take a look). Reading the docs, it looks like you can limit the BufferedImage.getRGB to a specific "square" range... docs.oracle.com/javase/7/docs/api/java/awt/image/…Gaikwar
see #55072438 for the new questionGroce
D
13

What I would do is set each pixel to be the difference between one pixel in one image and the corresponding pixel in the other image. The difference that is being calculated in your original code is based on the L1 norm. This is also called the sum of absolute differences too. In any case, write a method that would take in your two images, and return an image of the same size that sets each location to be the difference for each pair of pixels that share the same location in the final image. Basically, this will give you an indication as to which pixels are different. The whiter the pixel, the more difference there is between these two corresponding locations.

I'm also going to assume you're using a BufferedImage class, as getRGB() methods are used and you are bit-shifting to access individual channels. In other words, make a method that looks like this:

public static BufferedImage getDifferenceImage(BufferedImage img1, BufferedImage img2) {
    int width1 = img1.getWidth(); // Change - getWidth() and getHeight() for BufferedImage
    int width2 = img2.getWidth(); // take no arguments
    int height1 = img1.getHeight();
    int height2 = img2.getHeight();
    if ((width1 != width2) || (height1 != height2)) {
        System.err.println("Error: Images dimensions mismatch");
        System.exit(1);
    }

    // NEW - Create output Buffered image of type RGB
    BufferedImage outImg = new BufferedImage(width1, height1, BufferedImage.TYPE_INT_RGB);

    // Modified - Changed to int as pixels are ints
    int diff;
    int result; // Stores output pixel
    for (int i = 0; i < height1; i++) {
        for (int j = 0; j < width1; j++) {
            int rgb1 = img1.getRGB(j, i);
            int rgb2 = img2.getRGB(j, i);
            int r1 = (rgb1 >> 16) & 0xff;
            int g1 = (rgb1 >> 8) & 0xff;
            int b1 = (rgb1) & 0xff;
            int r2 = (rgb2 >> 16) & 0xff;
            int g2 = (rgb2 >> 8) & 0xff;
            int b2 = (rgb2) & 0xff;
            diff = Math.abs(r1 - r2); // Change
            diff += Math.abs(g1 - g2);
            diff += Math.abs(b1 - b2);
            diff /= 3; // Change - Ensure result is between 0 - 255
            // Make the difference image gray scale
            // The RGB components are all the same
            result = (diff << 16) | (diff << 8) | diff;
            outImg.setRGB(j, i, result); // Set result
        }
    }

    // Now return
    return outImg;
}

To call this method, simply do:

outImg = getDifferenceImage(img1, img2);

This is assuming that you are calling this within a method of your class. Have fun and good luck!

Dr answered 29/7, 2014 at 20:26 Comment(1)
new BufferedImage(width, height ... should be new BufferedImage(width1, height1Mccomb
M
1

Just to note that the answer from @NickGrealy can be made 10 times faster if you don't need to keep the first image and modify it in place.

Example:

// img1 will be updated with the changes from img2
public static BufferedImage getDifferenceImage(BufferedImage img1, BufferedImage img2) {
    byte[] magenta = {-1, 0, -1};
    byte[] buff1 = ((DataBufferByte) img1.getRaster().getDataBuffer()).getData();
    byte[] buff2 = ((DataBufferByte) img2.getRaster().getDataBuffer()).getData();

    for (int i = 1; i < buff1.lenght; i += 4) {
        if (buff1[i] != buff2[i]) {
            System.arraycopy(magenta, 0, buff1, i, 3);
        }
    }
}

I needed a fast approach to use on potentially lot of images for visual regression checking.

It runs in < 2 ms on my machine, and I am in a case where img1 is already saved on disk so I don't need to play with it, I'm just interested in the differences to be updated in the buffered image and write it to a new location for further inspection.

Measly answered 9/12, 2021 at 17:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.