7

I wanted to subtract two images pixel by pixel to check how much they are similar. Images have the same size one is little darker and beside brightness they don't differ. But I get those little dots in the result. Did I subtract those two images rigth? Both are bmp files.

import java.awt.image.BufferedImage;
import java.io.File;   
import javax.imageio.ImageIO;

public class Main2 {
    public static void main(String[] args) throws Exception {
        int[][][] ch = new int[4][4][4];
        BufferedImage image1 = ImageIO.read(new File("1.bmp"));
        BufferedImage image2 = ImageIO.read(new File("2.bmp"));
        BufferedImage image3 = new BufferedImage(image1.getWidth(), image1.getHeight(), image1.getType());
        int color;
        for(int x = 0; x < image1.getWidth(); x++)
            for(int y = 0; y < image1.getHeight(); y++) {
                color = Math.abs(image2.getRGB(x, y) - image1.getRGB(x, y));                
                image3.setRGB(x, y, color);
            }
        ImageIO.write(image3, "bmp",  new File("image.bmp"));


    }
}

Image 1 enter image description here

Image 2

enter image description here

Resultenter image description here

Yoda
  • 17,363
  • 67
  • 204
  • 344
  • 1
    Well why don't you look at the values of the corresponding pixels and figure out whether it's right or not? – Oliver Charlesworth Feb 16 '14 at 01:26
  • I don't understand the downvote, admittedly. @Oli Charlesworth That the pixel values are *not* right is already visible in the resulting image. – Marco13 Feb 16 '14 at 01:50
  • That was intended to be a hint to crack open a debugger (or something) and take a look at some of the numerical values being calculated... – Oliver Charlesworth Feb 16 '14 at 01:51

3 Answers3

12

The problem here is that you can't subtract the colors direcly. Each pixel is represented by one int value. This int value consists of 4 bytes. These 4 bytes represent the color components ARGB, where

A = Alpha
R = Red
G = Green
B = Blue

(Alpha is the opacity of the pixel, and always 255 (that is, the maximum value) in BMP images).

Thus, one pixel may be represented by

(255, 0, 254, 0)

When you subtract another pixel from this one, like (255, 0, 255, 0), then the third byte will underflow: It would become -1. But since this is part of ONE integer, the resulting color will be something like

(255, 0, 254, 0) - 
(255, 0, 255, 0) = 
(255, 255, 255, 0)

and thus, be far from what you would expect in this case.


The key point is that you have to split your color into the A,R,G and B components, and perform the computation on these components. In the most general form, it may be implemented like this:

int argb0 = image0.getRGB(x, y);
int argb1 = image1.getRGB(x, y);

int a0 = (argb0 >> 24) & 0xFF;
int r0 = (argb0 >> 16) & 0xFF;
int g0 = (argb0 >>  8) & 0xFF;
int b0 = (argb0      ) & 0xFF;

int a1 = (argb1 >> 24) & 0xFF;
int r1 = (argb1 >> 16) & 0xFF;
int g1 = (argb1 >>  8) & 0xFF;
int b1 = (argb1      ) & 0xFF;

int aDiff = Math.abs(a1 - a0);
int rDiff = Math.abs(r1 - r0);
int gDiff = Math.abs(g1 - g0);
int bDiff = Math.abs(b1 - b0);

int diff = 
    (aDiff << 24) | (rDiff << 16) | (gDiff << 8) | bDiff;
result.setRGB(x, y, diff);

Since these are grayscale images, the computations done here are somewhat redundant: For grayscale images, the R, G and B components are always equal. And since the opacity is always 255, it does not have to be treated explicitly here. So for your particular case, it should be sufficient to simplify this to

int argb0 = image0.getRGB(x, y);
int argb1 = image1.getRGB(x, y);

// Here the 'b' stands for 'blue' as well
// as for 'brightness' :-)
int b0 = argb0 & 0xFF;
int b1 = argb1 & 0xFF;
int bDiff = Math.abs(b1 - b0);

int diff = 
    (255 << 24) | (bDiff << 16) | (bDiff << 8) | bDiff;
result.setRGB(x, y, diff);
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • (Thinking) Since this is a one-byte only operation, would it not be sufficient -- and easier AND faster -- to use `bDiff = argb0 ^ argb1`? – Jongware Feb 16 '14 at 01:53
  • I don't think that this would bring the expected results. Imagine pixels with binary representations ending in `...010` and `...001` (that is, 2 and 1 in decimal). The difference should be 1, but an XOR will yield `...011` = 3. – Marco13 Feb 16 '14 at 02:01
  • Ah, correct -- I missed that. But, nice to see full code anyway! – Jongware Feb 16 '14 at 02:02
  • 1
    @Marco13 Shouldn't that be "255 << 24" in the penultimate line? – Mark Setchell Feb 16 '14 at 09:35
  • @Mark Setchell Of course you're right, fixed it, thanks – Marco13 Feb 16 '14 at 15:11
0

You did not "subtract one pixel from the other" correctly. getRGB returns "an integer pixel in the default RGB color model (TYPE_INT_ARGB)". What you are seeing is an "overflow" from one byte into the next, and thus from one color into the next.

Suppose you have colors 804020 - 404120 -- this is 3FFF00; the difference in the G component, 1 gets output as FF.

The correct procedure is to split the return value from getRGB into separate red, green, and blue, subtract each one, make sure they fit into unsigned bytes again (I guess your Math.abs is okay) and then write out a reconstructed new RGB value.

Jongware
  • 22,200
  • 8
  • 54
  • 100
0

I found this which does what you want. It does seem to do the same thing and it may be more "correct" than your code. I assume it's possible to extract the source code.

http://tutorial.simplecv.org/en/latest/examples/image-math.html

/Fredrik Wahlgren

  • This is a link-only answer, which is discouraged on StackOverflow. Please include the important parts from the link in your answer. That way if the link ever breaks your answer can still be useful. – Rob Watts Apr 22 '14 at 17:49