I got satisfactory results by resizing images to an 8x8 thumbnail and then taking the Mean Square Error of the 8-bit RGB color differences between the corresponding pixel in each image.
Step 1. Create thumbnails:
BufferedImage img = ImageIO.read(src);
Image thumbnail = img.getScaledInstance(8, 8, Image.SCALE_AREA_AVERAGING);
Check https://community.oracle.com/docs/DOC-983611 to understand why I chose the slower SCALE_AREA_AVERAGING
over newer, faster methods.
Step 2. Convert Image
thumbnails to BufferedImage
by using the toBufferedImage
method from Java converting Image to BufferedImage. Place the result into a List<BufferedImage>
.
Step 3. Calculate the Mean Squared Error
This method takes two thumbnail-sized images of identical size and returns the difference. Zero means the images are very very similar:
public static double compare(BufferedImage img1, BufferedImage img2) {
int width1 = img1.getWidth();
int width2 = img2.getWidth();
int height1 = img1.getHeight();
int height2 = img2.getHeight();
if ((width1 != width2) || (height1 != height2)) {
throw new IllegalArgumentException("Error: Images dimensions mismatch");
}
int diff2 = 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;
diff2 += Math.pow(r1 - r2, 2) + Math.pow(g1 - g2, 2) + Math.pow(b1 - b2, 2);
}
}
return diff2 * 1.0 / (height1*width1);
}
Step 4. Implement Search
This works by simply finding the image with the smallest difference. Depending on your use-case you may also want to set a threshold above which no image is returned. In my application the best match is always shown to the user so the user can decide if it is the correct image or not, so a hard-coded threshold is not necessary.
public BufferedImage findImage(List<BufferedImage> haystack, BufferedImage needle) {
double lastDiff = Double.MAX_VALUE;
BufferedImage winner = null;
for(BufferedImage candidate: haystack) {
double diff = compare(candidate, needle);
if(diff < lastDiff) {
lastDiff = diff;
winner = candidate;
}
}
return winner;
}