9

In JAVA I am trying to programmatically tell if 2 images are equal when displayed on the screen (AKA same image even though they have different color spaces. Is there a piece of code that will return a boolean when presented 2 images?

One of the examples I have is a RGB PNG that I converted to a greyscale PNG. Both images look the same and I would like to prove this programmatically. Another example is two images where they display the exact same color pixels to the screen but the color used for 100% transparent pixels has changed.

Eric
  • 6,563
  • 5
  • 42
  • 66
  • 3
    Could you do a MD5 hash? – MadProgrammer May 17 '13 at 06:28
  • Convert RGB one to grayscale and `Arrays.equals()`? – Anthony May 17 '13 at 06:34
  • [JavaCV](http://code.google.com/p/javacv/) offers some nice analysis on images with Java, based on OpenCV. Take a look at the object `ImageComparator`. – Menno May 17 '13 at 06:34
  • 4
    @Mad: an MD5 hash would be about the last thing you'd want to use for this. One tiny imperceptible (to a human) change (let alone RGB -> greyscale!), and the MD5 hashes of the images would be completely different. An MD5 hash would only be useful for determining if they were **exactly** the same image. – Mac May 17 '13 at 06:34
  • 1
    @anthony-arnold, one RGB image could have different greyscale results. – agou May 17 '13 at 06:35
  • http://stackoverflow.com/questions/6444869/how-do-i-read-pixels-from-a-png-file – ellak May 17 '13 at 06:36
  • @Mac Agreed. But there's no mention of error range between pixels. If the images were saved in different formats, then, obviously, it would be pointless. – MadProgrammer May 17 '13 at 06:39
  • 1
    @agou Yeah, but the question was kind of vague. I'll be the first to admit to posting sarcastic comments when a question needs more clarification. It's a problem I'm working on. – Anthony May 17 '13 at 06:40
  • 1
    @Eric the title and statement "AKA have the exact same pixels" are misleading. I think you mean to ask whether the RGB and grayscale files represent the same image in different color spaces. – rob May 17 '13 at 06:51
  • Free from memory, after Larry Wall: "Sure we could write a method to optimize the heck out of a routine, and your definition of 'the heck' would be a parameter to the method". – 0xCAFEBABE May 17 '13 at 07:24
  • @rob You are correct. I updated the question. – Eric May 26 '13 at 18:10
  • 1
    @Eric I had this same problem and I used a similarity measure called Multi Scale Structural Similarity Measure. There's a python implementation in SciKit Learn and a pretty non-performant C implementation knocking around out there. I worked with someone who implemented in Java with excellent results. – VanBantam Apr 27 '19 at 21:19

5 Answers5

4

For grayscale images I've used Mean Square Error as a measure of how different two images are before. Just plug the corresponding pixels from each image into the formula.

Not only can this tell you if they are exactly the same, but also it can tell you how different two images are, albeit in a rather crude manner.

https://en.wikipedia.org/wiki/Mean_squared_error

EDIT:

Note: This is C# code not Java (apologies but that's what I wrote it in originally), however it should be easily transferable.

//Calculates the MSE between two images
private double MSE(Bitmap original, Bitmap enhanced)
{
    Size imgSize = original.Size;
    double total = 0;

    for (int y = 0; y < imgSize.Height; y++)
    {
        for (int x = 0; x < imgSize.Width; x++)
        {
            total += System.Math.Pow(original.GetPixel(x, y).R - enhanced.GetPixel(x, y).R, 2);

        }

    }

    return (total / (imgSize.Width * imgSize.Height));
}
Eric
  • 6,563
  • 5
  • 42
  • 66
TomP89
  • 1,420
  • 3
  • 11
  • 29
  • I am by no means a math expert. Is there a simpler version of this I could implement in JAVA? – Eric May 26 '13 at 18:10
  • I will reply to you tonight, I have some c# code, which you could "very" easily adapt for Java – TomP89 May 29 '13 at 07:28
  • Since I am looking for an exact match and it looks like you are grabbing all of the pixels, wouldn't it be easier to check if each pixel is equal? – Eric Jun 03 '13 at 15:24
  • @Eric Yes you can, but I'm fairly sure that this will return zero if the images are exactly the same anyway. You'll have to try the code, I haven't used it in a few years now – TomP89 Jun 05 '13 at 07:27
  • If the enhanced image is smaller than the original one, we will get an exception on the GetPixel(...) method, right? – Davide Sep 03 '17 at 17:08
4

I looked at all of the solutions and determined that they could tell you how different the images were or worked for some types of images, but not all of them. Here is the solution I came up with...

package image.utils;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.swing.ImageIcon;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Utility methods used to interact with images.
 */
public class ImageUtils {

    private final static Logger logger = LoggerFactory.getLogger(ImageUtils.class);

    private static final boolean equals(final int[] data1, final int[] data2) {
        final int length = data1.length;
        if (length != data2.length) {
            logger.debug("File lengths are different.");
            return false;
        }
        for(int i = 0; i < length; i++) {
            if(data1[i] != data2[i]) {

                //If the alpha is 0 for both that means that the pixels are 100%
                //transparent and the color does not matter. Return false if 
                //only 1 is 100% transparent.
                if((((data1[i] >> 24) & 0xff) == 0) && (((data2[i] >> 24) & 0xff) == 0)) {
                    logger.debug("Both pixles at spot {} are different but 100% transparent.", Integer.valueOf(i));
                } else {
                    logger.debug("The pixel {} is different.", Integer.valueOf(i));
                    return false;
                }
            }
        }
        logger.debug("Both groups of pixels are the same.");
        return true;
    }

    private static final int[] getPixels(final BufferedImage img, final File file) {

        final int width = img.getWidth();
        final int height = img.getHeight();
        int[] pixelData = new int[width * height];

        final Image pixelImg; 
        if (img.getColorModel().getColorSpace() == ColorSpace.getInstance(ColorSpace.CS_sRGB)) {
            pixelImg = img;
        } else {
            pixelImg = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(img, null);
        }

        final PixelGrabber pg = new PixelGrabber(pixelImg, 0, 0, width, height, pixelData, 0, width);

        try {
            if(!pg.grabPixels()) {
                throw new RuntimeException();
            }
        } catch (final InterruptedException ie) {
            throw new RuntimeException(file.getPath(), ie);
        }

        return pixelData;
    }

    /**
     * Gets the {@link BufferedImage} from the passed in {@link File}.
     * 
     * @param file The <code>File</code> to use.
     * @return The resulting <code>BufferedImage</code>
     */
    @SuppressWarnings("unused")
    final static BufferedImage getBufferedImage(final File file) {
        Image image;

        try (final FileInputStream inputStream = new FileInputStream(file)) {
            // ImageIO.read(file) is broken for some images so I went this 
            // route
            image = Toolkit.getDefaultToolkit().createImage(file.getCanonicalPath());

            //forces the image to be rendered
            new ImageIcon(image);
        } catch(final Exception e2) {
            throw new RuntimeException(file.getPath(), e2);
        }

        final BufferedImage converted = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
        final Graphics2D g2d = converted.createGraphics();
        g2d.drawImage(image, 0, 0, null);
        g2d.dispose();
        return converted;
    }

    /**
     * Compares file1 to file2 to see if they are the same based on a visual 
     * pixel by pixel comparison. This has issues with marking images different
     * when they are not. Works perfectly for all images.
     * 
     * @param file1 First file to compare
     * @param file2 Second image to compare
     * @return <code>true</code> if they are equal, otherwise 
     *         <code>false</code>.
     */
    private final static boolean visuallyCompareJava(final File file1, final File file2) {
        return equals(getPixels(getBufferedImage(file1), file1), getPixels(getBufferedImage(file2), file2));
    }

    /**
     * Compares file1 to file2 to see if they are the same based on a visual 
     * pixel by pixel comparison. This has issues with marking images different
     * when they are not. Works perfectly for all images.
     * 
     * @param file1 Image 1 to compare
     * @param file2 Image 2 to compare
     * @return <code>true</code> if both images are visually the same.
     */
    public final static boolean visuallyCompare(final File file1, final File file2) {

        logger.debug("Start comparing \"{}\" and \"{}\".", file1.getPath(), file2.getPath());

        if(file1 == file2) {
            return true;
        }

        boolean answer = visuallyCompareJava(file1, file2);

        if(!answer) {
            logger.info("The files \"{}\" and \"{}\" are not pixel by pixel the same image. Manual comparison required.", file1.getPath(), file2.getPath());
        }

        logger.debug("Finish comparing \"{}\" and \"{}\".", file1.getPath(), file2.getPath());

        return answer;
    }

    /**
     * @param file The image to check
     * @return <code>true</code> if the image contains one or more pixels with
     *         some percentage of transparency (Alpha)
     */
    public final static boolean containsAlphaTransparency(final File file) {
        logger.debug("Start Alpha pixel check for {}.", file.getPath());

        boolean answer = false;
        for(final int pixel : getPixels(getBufferedImage(file), file)) {
            //If the alpha is 0 for both that means that the pixels are 100%
            //transparent and the color does not matter. Return false if 
            //only 1 is 100% transparent.
            if(((pixel >> 24) & 0xff) != 255) {
                logger.debug("The image contains Aplha Transparency.");
                return true;
            }
        }

        logger.debug("The image does not contain Aplha Transparency.");
        logger.debug("End Alpha pixel check for {}.", file.getPath());

        return answer;
    }
}
Eric
  • 6,563
  • 5
  • 42
  • 66
2

You can try this

Example
Wayback Machine to the rescue here

They explain how to compare two images

aran
  • 10,978
  • 5
  • 39
  • 69
Java
  • 2,451
  • 10
  • 48
  • 85
1

If you mean exactly the same, compare each pixel.

If you mean compare a RGB image and a greyscale image, you need to convert the RGB to greyscale first, for doing this, you need to know how you did RGB->Greyscale before, there're different ways of doing this and you could get different results.

Edit, if the method it used in RGB->Greyscale is liner, you could work out a,b,c in the formula grey = a*R + b*G + c*B by comparing 3 pixels.

Eric
  • 6,563
  • 5
  • 42
  • 66
agou
  • 728
  • 1
  • 10
  • 24
0

One of the easiest approach I tried is getting the pixel array of both images and comparing them with Arrays.equals method. Code sample :

package image_processing;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;

import javax.imageio.ImageIO;

public class CheckPixels {
    public static void main(String args[]) throws IOException {

        File pic1 = new File("D:\\ani\\img1.png");
        File pic2 = new File("D:\\ani\\img2.png");
        if (Arrays.equals(returnPixelVal(pic1), returnPixelVal(pic2))) {
            System.out.println("Match");
        } else {
            System.out.println("No match");
        }

    }

    public static byte[] returnPixelVal(File in) {

        BufferedImage img = null;
        File f = null;
        byte[] pixels = null;
        // read image
        try {
            f = in;
            img = ImageIO.read(f);
            pixels = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
        } catch (IOException e) {
            System.out.println(e);
        }

        return pixels;

    }

}
Ani
  • 123
  • 1
  • 1
  • 14
  • What happens if 2 images are the same except that the background color is different and transparent? – Eric Sep 13 '17 at 17:37
  • @Eric: Here, each pixel value (a,r,g,b) is checked, so even if only background color is different , it will produce "No match". – Ani Sep 21 '17 at 04:48
  • So this means that even though the images look identical to the user, this will indicate that they are different. – Eric Sep 21 '17 at 05:43
  • @Eric : Yeah.We are concerned about programmatic validation , not visual. Isn't it? :) – Ani Sep 21 '17 at 05:56