使用Java编程确定两张图片是否相同

9

我正在JAVA中尝试编写程序,以在屏幕上显示两个图像时(即使它们具有不同的颜色空间),程序能够判断它们是否相等。是否有一段代码可以在提供两张图片时返回布尔值?

其中一个例子是我将RGB PNG转换为灰度PNG。这两张图片看起来相同,我想通过程序来证明它们确实相同。另一个例子是两张图像,在屏幕上显示完全相同的颜色像素,但用于100%透明像素的颜色已更改。


3
你能做一个MD5哈希吗? - MadProgrammer
4
“@Mad:对于这个问题,你最不想使用的可能就是MD5哈希。即使是微小到人类都察觉不了的改变(更别说从RGB到灰度!),图像的MD5哈希值也会完全不同。MD5哈希只适用于确定它们是否是完全相同的图像。” - Mac
1
@anthony-arnold,一张RGB图像可能会有不同的灰度结果。 - agou
1
@agou 是的,但问题有点含糊。当问题需要更多澄清时,我会承认自己发表讽刺性评论。这是我正在解决的问题。 - Anthony
1
@Eric,标题和声明“AKA具有完全相同的像素”是误导性的。我认为你的意思是询问RGB和灰度文件是否代表不同颜色空间中的相同图像。 - rob
显示剩余7条评论
5个回答

4

对于灰度图像,我之前使用均方误差作为衡量两幅图像相似度的方法。只需将每张图像对应像素插入公式中即可。

这不仅可以告诉你它们是否完全相同,而且还可以告诉你两个图像有多不同,尽管这种方法比较粗略。

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

编辑:

注意:这是C#代码而不是Java(抱歉,但最初我是用C#编写的),不过应该很容易转换。

//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));
}

我绝不是数学专家。有没有一个更简单的版本可以在JAVA中实现? - Eric
我今晚会回复你,我有一些C#代码,你可以非常轻松地为Java进行调整。 - TomP89
既然我正在寻找一个精确匹配,而且看起来你正在获取所有像素,那么检查每个像素是否相等不是更容易吗? - Eric
@Eric 是的,你可以这样做,但如果图像完全相同,这将返回零。你需要尝试一下这段代码,因为我已经好几年没用过了。 - TomP89
如果增强图像比原始图像小,那么在 GetPixel(...) 方法上会抛出异常,对吗? - Davide

4
我查看了所有的解决方案,并确定它们可以告诉您图像有多不同,或适用于某些类型的图像,但不是所有类型。这是我想出的解决方案...
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;
    }
}

2
你可以尝试这个:

示例
Wayback Machine 在这里拯救了我们

他们解释了如何比较两张图片。


1

如果你的意思是完全相同,那么需要比较每个像素。

如果你的意思是比较一张RGB图像和一张灰度图像,你需要先将RGB转换为灰度图像,为此,你需要知道如何进行RGB->灰度的转换,有不同的方法可以实现这一点,你可能会得到不同的结果。

编辑,如果在RGB->灰度中使用的方法是线性的,你可以通过比较3个像素来计算公式grey = a*R + b*G + c*B中的a、b、c。


我真的不知道这些图片是如何创建的。 - Eric

0

我尝试过的最简单的方法之一是获取两个图像的像素数组,并使用Arrays.equals方法进行比较。 代码示例:

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;

    }

}

如果两张图片除了背景颜色不同且透明,会发生什么? - Eric
@Eric:这里会检查每个像素值(a,r,g,b),因此即使只有背景颜色不同,它也会产生“无匹配”。 - Ani
这意味着即使图像对用户来说看起来相同,但这将表明它们是不同的。 - Eric
@Eric:是的,我们关注的是程序验证,而不是视觉验证。对吧? :) - Ani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接