Image Comparison in JAVA
Tutorial
29
White

Nobody viết ngày 06/03/2022

This tutorial is a summary of the two last tutorials

  1. OCR: Optical Character Recognition
  2. JAVA: Image and Pixel Processing

With the knowledge we have about the way how to process an Image and how to recognize an Image I show you how to compare the contents of two different images. Image Comparison is very tricky. First, the different physical size. Then, the logical differences which are the hue of colors, the blurring pixels, the similarity, etc.

A File-Comparison focuses on the logical content of the files without checking for the art of characters (i.e. the fonts) whether the letters are cursive or bold or colored. The only little problem is the watch-out for Upper and Lower cases.

An Image-Comparison is, as mentioned above, more complex than File-Comparison because it focuses on the contents which are in form of colors, appearances and the viewing perspective of the images. Further, there is no any logical connection between the images. Example: two images have different size, but both show the same content appearance (same picture). Such a difference does not exist on file: either the contents of the files are the same, or they are different.

I have showed you how to analyze an image and how to parse the pixels into three basic colors RED, GREEN and BLUE (RGB) and the control attribute ALPHA (for the rate of Opaqueness). Together they build an ARGB pixel. Today I show you how to use the mentioned knowledge in order to implement an API for Image Comparison. It's relatively simple. The comparing images can be EQUAL or SIMILAR.

  • Equal is here not the equalness of the physical size, but the IDENTICAL appearances. Meaning: all pixels of both images have the same ARGB.
  • Similar is here the similar appearance of the comparing image contents. The similarity could be 100% (IDENTICAL or EQUAL) or 0% (fully different).

If two images show the same appearance, but different physical sizes the larger image should be reduced to the size of the smaller. An enlargement is disadvantageous because the appearance of the smaller has to be inflated and that could falsify the comparison more than a compression.

The API ImageCompare.java. NOTE: this API is NOT thread-safe.

import java.io.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.ImageIO;
// Joe Nartca (C)
public class ImageCompare {
  /**
  @param tolerant float, the tolerant color between 2 pixels (max 0.1f or 10%).
  */
  public ImageCompare(float tolerant) {
    bTol = (int)(((tolerant > 0.1f || tolerant < 0f) ? 0.1f:tolerant)*256);
    rTol = bTol << 16;
    gTol = bTol << 8;
  }
  /**
  Default tolerant: 2% or 0.02f
  */
  public ImageCompare( ) {
    bTol = 5;
    gTol = bTol << 8;
    rTol = bTol << 16;
  }
  //
  public boolean isEqual() {
    if (result == null || ratio < 0.999f) return false;
    return true;
  }
  /**
  @return float gives the percent of similarity
  */
  public float isSimilar( ) {
    return ratio;
  }
  /**
  @return BufferedImage
  */
  public BufferedImage getFirstImage( ) {
    return img_1;
  }
  /**
  @return BufferedImage
  */
  public BufferedImage getSecondImage( ) {
    return img_2;
  }
  /**
  Compare 2 images (file) for Dissimilariry
  @param imgFile_1 String, Image FileName 1
  @param imgFile_2 String, Image FileName 2
  @return float gives the percent of similarity
  @Exception Exception thrown by JAVA
  */
  public float onCompare(String imgFile_1, String  imgFile_2) throws Exception {
    BufferedImage aImg, bImg;
    img_1 = ImageIO.read(new File(imgFile_1));
    img_2 = ImageIO.read(new File(imgFile_2));
    int w = img_1.getWidth(), h = img_1.getHeight();
    int W = img_2.getWidth(), H = img_2.getHeight();
    // adjusting the size for an easy comparison
    if (W > w || H > h) {
      aImg = img_1;
      bImg = resize(img_2, w, h);
    } else if (W < w || H < h) {
      bImg = img_2;
      aImg = resize(img_1, W, H);
    } else {
      aImg = img_1;
      bImg = img_2;
    }
    w = aImg.getWidth(); h = aImg.getHeight();
    float matched = 0f, cont = (float)(w * h);
    //
    result = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); 
    result_1 = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); 
    result_2 = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    int rgb1, rgb2, red1, green1, blue1, red2, green2, blue2, X, red, green, blue;
    for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) {
      rgb1 = aImg.getRGB(x, y); rgb2 = bImg.getRGB(x, y);
      if (rgb1 == rgb2) ++matched;
      else { // +/- tolerant
        red = 0; blue = 0; green = 0;
        red1 = rgb1 & RED; red2 = rgb2 & RED;
        blue1 = rgb1 & BLUE; blue2 = rgb2 & BLUE;
        green1 = rgb1 & GREEN; green2 = rgb2 & GREEN;
        //
        if (red1 != red2) {
          X = red2 > red1? red2 - red1 : red1 - red2;
          if (X > rTol) red = X;
        }
        if (green1 != green2) {
          X = green2 > green1? green2 - green1 : green1 - green2;
          if (X > gTol) green = X;
        }
        if (blue1 != blue2) {
          X = blue2 > blue1? blue2 - blue1 : blue1 - blue2;
          if (X > bTol) blue = X;
        }
        if (red == 0 && green == 0 && blue == 0) ++matched;
        else {
          result.setRGB(x, y, ALPHA | red | green | blue);
          result_1.setRGB(x, y, rgb1);
          result_2.setRGB(x, y, rgb2);
        }
      }
    }
    ratio = matched/cont;
    return ratio;
  }
  //
  public BufferedImage getDissimilarity() {
    return result;
  }
  //
  public BufferedImage getDifferenceOfFirstImage() {
    return result_1;
  }
  //
  public BufferedImage getDifferenceOfSecondImage() {
    return result_2;
  }
  //--------------------------------------------------------------------------------
  private BufferedImage resize(BufferedImage image, int width, int height) {
    try {
      BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
      img.getGraphics().drawImage(image, 0, 0, width, height, null);
      return img;
    } catch (Exception ex) { }
    return image;
  }
  private float ratio = 0f;
  private BufferedImage img_1, img_2;
  private BufferedImage result_1, result_2, result;
  private int rTol, gTol, bTol, ALPHA = 0xFF000000, RED = 0xFF0000, GREEN = 0xFF00, BLUE = 0xFF;
}

And the Verification app TestIC.java

import java.io.*;
import java.awt.image.*;
import javax.imageio.ImageIO;
import javax.swing.*;
//
import joeapp.color.ImageCompare;
// Joe Nartca (C)
public class TestIC extends JFrame {
  public TestIC(String... a) throws Exception {
    setTitle("Image Comparison");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JLabel lab1 = new JLabel("First Image");
    JLabel msg = new JLabel("Image Comparison");
    String dir = System.getProperty("user.dir");
    JTextField txt1 = new JTextField(a.length == 2?a[0]:dir+"\\images\\Bitcoin.png", 24);
    JLabel lab2 = new JLabel("Second Image");
    JTextField txt2 = new JTextField(a.length == 2?a[1]:dir+"\\images\\FataMorgana.png", 24);
    JComboBox<String> cBox = new JComboBox<>(new String[] { "Compare", "isEqual", "isSimilar", "Diff.Of 1st Image",
                                                            "Diff.Of 2nd Image", "The Dissimilarity", "1st Image", "2nd Image" 
                                                          }
                                             );
    JButton but = new JButton("No Image");
    but.addActionListener(e -> {
      cBox.grabFocus();
    });
    final ImageCompare ic = new ImageCompare(0.1f);
    JLabel labc = new JLabel("What Function?");
    cBox.addActionListener(e -> {
      try {
        String f1 = txt1.getText().replace("/", File.separator);
        String f2 = txt2.getText().replace("/", File.separator);
        if (f1 == null || f1.length() == 0 || f2 == null || f2.length() == 0) {
          msg.setText("Require 2 Image files");
          return;
        }
        boolean b = false;
        but.setText(null); but.setIcon(null);
        int i1 = f1.lastIndexOf(File.separator);
        int i2 = f1.lastIndexOf(File.separator);
        i1 = i1 < 0? 0:i1+1; i2 = i2 < 0? 0:i2+1;
        //
        BufferedImage img = null;
        String n1 = f1.substring(i1);
        String n2 = f2.substring(i2);
        switch (cBox.getSelectedIndex()) {
        case 0: ratio = ic.onCompare(f1, f2);
                msg.setText("The Similarity Ratio:"+String.format("%3d %%", (int)(ratio*100)));
                but.setText(String.format("%3d %%", (int)(ratio*100))+" similar");
                break;
        case 1: b = ic.isEqual();
                msg.setText(n1+" and "+n2+" is "+(b?"Equal":"NOT Equal"));
                but.setText((b?"Equal":"NOT Equal"));
                break;
        case 2: ratio = ic.isSimilar( );
                msg.setText(n1+" and "+n2+" share "+String.format("%3d %%", (int)(ratio*100))+" Simiarity");
                but.setText(String.format("%3d %%", (int)(ratio*100))+"Similarity");
                break;
        case 3: if (ratio > 0) {
                  msg.setText("The Difference of 1st.Image");
                  but.setIcon(new ImageIcon(ic.getDifferenceOfFirstImage()));
                } else but.setText("NO Similarity of 1st.Image");
                break;
        case 4: if (ratio > 0) {
                  msg.setText("The Difference of 2nd.Image");
                  but.setIcon(new ImageIcon(ic.getDifferenceOfSecondImage()));
                } else but.setText("NO Similarity of 2nd.Image");
                break;
        case 5: if (ratio > 0) {
                  msg.setText("The Dissimilarity");
                  but.setIcon(new ImageIcon(ic.getDissimilarity()));
                } else but.setText("NO Similarity between 2 Images");
                break;
        case 6: img = ic.getFirstImage();
                if (img != null) {
                  msg.setText("The First Image");
                  but.setIcon(new ImageIcon(img));
                } else but.setText("NO First Images");
                break;
        default:img = ic.getSecondImage();
                if (img != null) {
                  msg.setText("The Second Image");
                  but.setIcon(new ImageIcon(img));
                } else but.setText("NO Second Image");
                break;
        }
        pack();
      } catch (Exception ex) {
        msg.setText("Exception:"+ex.toString());
      }
    });
    msg.setHorizontalAlignment(JLabel.CENTER);
    JPanel north = new JPanel();
    north.add(lab1); north.add(txt1); north.add(lab2); north.add(txt2);
    JPanel center = new JPanel();
    center.add(labc); center.add(cBox);
    JPanel south = new JPanel();
    south.add(msg); south.add(but);

    add("North", north);
    add("Center", center);
    add("South", south);
    pack();
    setVisible(true);
  }
  //
  public static void main(String... a) throws Exception {
    UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
    new TestIC(a);
  }
  private float ratio = 0f;
}

alt text

alt text

alt text

alt text

alt text

and the comparing Image files

  • Bitcoin.png / FataMorgana.png
  • BigOnion / Small_BigOnion.png

for downloading HERE
Joe

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Nobody

20 bài viết.
557 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
1 2
Chao Cac Ban I was absent for a very long time... To my wonder that Kipalog is still alive. It's a very good news. Today I show you a brief tutor...
Nobody viết 5 tháng trước
1 2
White
1 0
(Ảnh) I found this question in a Vietnamese forum ((Link)). The questioner is certainly not a man who's studied Computer Science (or in Vietnamese...
Nobody viết 4 tháng trước
1 0
White
1 0
Hi Some developers whose native language is NOT English seem to be either bad in English or too lazy to read the APIs. However I have an impressio...
Nobody viết 2 tháng trước
1 0
Bài viết liên quan
White
14 4
(Link) (Link) (Link) Ở 2 phần tut trước, mình đã hướng dẫn khá chi tiết cách viết một ứng dụng camera có tích hợp chức năng nhận diện khuôn mặ...
HoangPH viết gần 7 năm trước
14 4
White
24 4
(Ảnh) Nếu máy tính của bạn đã bị lây nhiễm, mã độc có thể lây lan tới trang web của bạn thông qua trình soạn thảo văn bản và (Link). Dùng các mật ...
Juno_okyo viết gần 5 năm trước
24 4
Male avatar
2 1
Bạn đang xài máy ảo Genymotion để chạy thử các ứng dụng của Android. Nếu bạn đang sử dụng Android Studio thì bạn càng dễ dàng mở máy ảo Genymotion...
pdnghiadev viết gần 7 năm trước
2 1
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
20 bài viết.
557 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!