public float calculateDifference(BufferedImage b1, BufferedImage b2){
float error = 0;
for(int y = 0; y < sizeY; y++){
for(int x = 0; x < sizeX; x++){
Color c1 = new Color(b1.getRGB(x, y));
Color c2 = new Color(b2.getRGB(x, y));
error += Math.abs(c1.getRed() - c2.getRed());
error += Math.abs(c1.getGreen() - c2.getGreen());
error += Math.abs(c1.getBlue() - c2.getBlue());
error += Math.abs(c1.getAlpha() - c2.getAlpha());
}
}
return error;
}
I have this function that compares two bufferedimages. It returns a higher error if the two images are more different. The only problem is it runs really slowly so is there any more efficient way to do this? Any way to lower the runtime would really help.
Yes you can optimize the internal for loop.
Don't create new Color objects. Use directly the int value of RGB. This will limit the number of Objects created and the frequency of call to Garbage collection.
int color1 = b1.getRGB(x, y);
int alpha1 = (color1 >> 24) & 0xFF;
int red1 = (color1 >> 16) & 0xFF;
int green1 = (color1 >> 8) & 0xFF;
int blue1 = (color1 >> 0) & 0xFF;
int color2 = b2.getRGB(x, y);
int alpha2 = (color2 >> 24) & 0xFF;
int red2 = (color2 >> 16) & 0xFF;
int green2 = (color2 >> 8) & 0xFF;
int blue2 = (color2 >> 0) & 0xFF;
error += Math.abs(red1 - red2);
error += Math.abs(green1 - green2);
error += Math.abs(blue1 - blue2);
error += Math.abs(alpha1 - alpha2);
Related
I have the following code:
private static int pixelDiff(int rgb1, int rgb2) {
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;
return Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
}
and it works without a problem, but it takes to long and i don't know how it optimize it.
So the basic is, that i want to compare two images and get the percentage of difference.
Therefor I load the RGB of both images and compare them with this code.
My question: Is it possible to optimize this code, or do you have any idea to compare two images(not only that they are equal)
UPDATE:
here is the full code:
private double getDifferencePercent(BufferedImage img1, BufferedImage img2) {
int width = img1.getWidth();
int height = img1.getHeight();
int width2 = img2.getWidth();
int height2 = img2.getHeight();
if (width != width2 || height != height2) {
throw new IllegalArgumentException(String.format("Images must have the same dimensions: (%d,%d) vs. (%d,%d)", width, height, width2, height2));
}
long diff = 0;
for (int y = height - 1; y >= 0; y--) {
for (int x = width - 1; x >= 0; x--) {
diff += pixelDiff(img1.getRGB(x, y), img2.getRGB(x, y));
}
}
long maxDiff = 765L * width * height;
return 100.0 * diff / maxDiff;
}
private static int pixelDiff(int rgb1, int rgb2) {
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;
return Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
}
I checked this with a profiler and it shows that pixelDiff() is very slow.
Given two colors A and B, I would like to get the resulting color C, that is the most possible realistic natural mix of the A and B.
Example :
Red + Yellow = Orange
Blue + Yellow = Green
Red + Blue = Purple
Blue + White = Light Blue
Blue + Black = Dark Blue
etc...
Can I get it with ARGB representation of the given colors?
We can call a function which returns result array when give two arrays as parameters. But Arrays should be same sizes.
public int getAvgARGB(int[] clr1, int[] clr2){
int[] returnArray = new int[clr1.length];
for(int i=0; i<clr1.length;i++){
int a1[i] = (clr1[i] & 0xFF000000) >>> 24;
int r1[i] = (clr1[i] & 0x00FF0000) >> 16;
int g1[i] = (clr1[i] & 0x0000FF00) >> 8;
int b1[i] = (clr1[i] & 0x000000FF) ;
int a2[i] = (clr2[i] & 0xFF000000) >>> 24;
int r2[i] = (clr2[i] & 0x00FF0000) >> 16;
int g2[i] = (clr2[i] & 0x0000FF00) >> 8;
int b2[i] = (clr2[i] & 0x000000FF) ;
int aAvg = (a1[i] + a2[i]) / 2;
int rAvg = (r1[i] + r2[i]) / 2;
int gAvg = (g1[i] + g2[i]) / 2;
int bAvg = (b1[i] + b2[i]) / 2;
int returnArray[i] = (aAvg << 24) + (rAvg << 16) + (gAvg << 8) + bAvg;
}
return returnArray;
}
Can anyone see what the issue is when I try to convert my 8 bit image into an 4 bit image?
I am testing using the 8 bit image found here: http://laurashoe.com/2011/08/09/8-versus-16-bit-what-does-it-really-mean/
You can tell how the 4 bit image should look like but mine is almost purely black.
// get color of the image and convert to grayscale
for(int x = 0; x <img.getWidth(); x++) {
for(int y = 0; y < img.getHeight(); y++) {
int rgb = img.getRGB(x, y);
int r = (rgb >> 16) & 0xF;
int g = (rgb >> 8) & 0xF;
int b = (rgb & 0xF);
int grayLevel = (int) (0.299*r+0.587*g+0.114*b);
int gray = (grayLevel << 16) + (grayLevel << 8) + grayLevel;
img.setRGB(x,y,gray);
}
}
You should use 0xFF not 0xF,as 0xF means only last four bits, wchich will tell you almost nothing about the color, since in RGB an color is 8 bit.
try if this work:
// get color of the image and convert to grayscale
for(int x = 0; x <img.getWidth(); x++) {
for(int y = 0; y < img.getHeight(); y++) {
int rgb = img.getRGB(x, y);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = (rgb & 0xFF);
int grayLevel = (int) (0.299*r+0.587*g+0.114*b);
int gray = (grayLevel << 16) + (grayLevel << 8) + grayLevel;
img.setRGB(x,y,gray);
}
}
Since the code has been edited out from the question, here it is with the confirmed solution from the comments:
// get color of the image and convert to grayscale
for(int x = 0; x <img.getWidth(); x++) {
for(int y = 0; y < img.getHeight(); y++) {
int rgb = img.getRGB(x, y);
// get the upper 4 bits from each color component
int r = (rgb >> 20) & 0xF;
int g = (rgb >> 12) & 0xF;
int b = (rgb >> 4) & 0xF;
int grayLevel = (int) (0.299*r+0.587*g+0.114*b);
// use grayLevel value as the upper 4 bits of each color component of the new color
int gray = (grayLevel << 20) + (grayLevel << 12) + (grayLevel << 4);
img.setRGB(x,y,gray);
}
}
Note that the resulting image only looks like 4-bit grayscale, but still uses int as the RGB value.
8 bit image values are in range [0, 255] because pow(2, 8) = 256
To get 4 bit image values which will be in range [0, 15] as pow(2, 4) = 16,
we need to divide each pixel value by 16 -> range [0, 255] / 16 = range [0, 15].
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("crowd.jpeg")
#Convert the image to grayscale
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.imshow(gray_img, cmap='gray')
Grayscale image
bit_4 = np.divide(gray_img, 16).astype('uint8')
plt.imshow(bit_4, cmap='gray')
Bit4 image
I need to toggle on/off RGB channels of an image, but I am stuck and my code is buggy.
Can you help me figure out how to do this the right way? This is my code:
The function channels is called when 1 of 3 checkboxes has changed its state and provides the arguments which are true == selected
public void channels(boolean red, boolean green, boolean blue) {
if (this.img != null) {// checks if the image is set
char r = 0xFF, g = 0xFF, b = 0xFF;
if (red == false) {
r = 0x00;
}
if (green == false) {
g = 0x00;
}
if (blue == false) {
b = 0x00;
}
BufferedImage tmp = new BufferedImage(
img.getWidth(),
img.getHeight(),
BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < img.getWidth(); i++) {
for (int j = 0; j < img.getHeight(); j++) {
int rgb = img.getRGB(i, j);
int red = (rgb >> 16) & r;
int green = (rgb >> 8) & g;
int blue = (rgb >> 0) & b;
int gbr = (red << 16) | (green << 8) | blue;// EDITED
tmp.setRGB(i, j, gbr);
}
}
img = tmp;
repaint();
} else {
//show error
}
}
Thank you for your help!
How about this optimized version, with a lot less bit shifting?
public void channels(boolean showRed, boolean showGreen, boolean showBlue) {
if (this.origImg!= null) {// checks if the image is set
int channelMask = 0xff << 24 | (showRed ? 0xff : 0) << 16 | (showGreen ? 0xff : 0) << 8 | (showBlue ? 0xff : 0);
BufferedImage tmp = new BufferedImage(origImg.getWidth(), origImg.getHeight(), BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < origImg.getWidth(); i++) {
for (int j = 0; j < origImg.getHeight(); j++) {
int rgb = origImg.getRGB(i, j);
tmp.setRGB(i, j, rgb & channelMask);
}
}
img = tmp;
repaint();
} else {
//show error
}
}
A faster approach yet, would probably be to use a channeled Raster, or at least a Raster configuration that allows band sub-sampling (see Raster.createChild(...) method, especially the last parameter).
LookupOp, as mentioned by #trashgod is also a good idea, and probably faster than the getRGB()/setRGB() approach.
It looks like you're shifting in the bits wrong. Shouldn't it be: int gbr = (red << 16) | (green << 8) | blue;? You basically want to shift back in the same order as how you shifted out to begin with.
Also, once you have cleared the corresponding colour, there's no way for you to get it back. You'll need to store a copy of the original image somewhere. When it's time to turn the channel back on, simply copy the original pixel from the original image back.
Assuming that you have the original image stored somewhere as origImg, I would modify your for loop so that if the channel is toggled on, copy from the original image.
for (int i = 0; i < img.getWidth(); i++) {
for (int j = 0; j < img.getHeight(); j++) {
int rgb = img.getRGB(i, j);
int origRGB = origImg.getRGB(i, j);
int redPixel = red ? (origRGB >> 16) & r : (rgb >> 16) & r;
int greenPixel = green ? (origRGB >> 8) & g : (rgb >> 8) & g;
int bluePixel = blue ? origRGB & b : rgb & b;
int gbr = (redPixel << 16) | (greenPixel << 8) | bluePixel;
tmp.setRGB(i, j, gbr);
}
}
Given an int from a DataBuffer which has ARGB data packed in it with the masks
A = 0xFF000000
R = 0xFF0000
G = 0xFF00
B = 0xFF
I'm doing the following but wonder if there isn't a faster method in Java?
DataBuffer db1 = img1.getData().getDataBuffer();
DataBuffer db2 = img2.getData().getDataBuffer();
int x, y;
int totalDiff = 0;
for (int i = 0; i < WIDTH * HEIGHT; ++i) {
x = db1.getElem(i);
y = db2.getElem(i);
totalDiff += Math.abs((x & 0xFF) - (y & 0xFF))
+ Math.abs(((x & 0xFF00) >> 8) - ((y & 0xFF00) >> 8))
+ Math.abs(((x & 0xFF0000) >> 16) - ((y & 0xFF0000) >> 16 ));
}
If you really need the speed up you might to check the type of DataBuffer and provide optimized code for the concrete type such that you save the calls to getElem(i). This will speed up your code a little bit.
Something like this:
DataBuffer db1 = img1.getData().getDataBuffer();
DataBuffer db2 = img2.getData().getDataBuffer();
int totalDiff = 0;
int x, y;
if (db1 instanceof DataBufferInt && db2 instanceof DataBufferInt) {
int[] data1 = ((DataBufferInt) db1).getData();
int[] data2 = ((DataBufferInt) db2).getData();
for (int i = 0; i < WIDTH * HEIGHT; ++i) {
x = data1[i];
y = data2[i];
totalDiff += Math.abs((x & 0xFF) - (y & 0xFF))
+ Math.abs(((x & 0xFF00) >> 8) - ((y & 0xFF00) >> 8))
+ Math.abs(((x & 0xFF0000) >> 16) - ((y & 0xFF0000) >> 16));
}
} else {
for (int i = 0; i < WIDTH * HEIGHT; ++i) {
x = db1.getElem(i);
y = db2.getElem(i);
totalDiff += Math.abs((x & 0xFF) - (y & 0xFF))
+ Math.abs(((x & 0xFF00) >> 8) - ((y & 0xFF00) >> 8))
+ Math.abs(((x & 0xFF0000) >> 16) - ((y & 0xFF0000) >> 16));
}
}
Edit:
Another idea that would bring you a MUCH higher speed up. If this is just a heuristic it might be enough to calculate the difference of a somewhat "downsampled" version of your images. Replace ++i through i+=10 and gain a speed up by factor 10. Of course if this makes sense depends on the types of your images.
Edit:
In one comment you mentioned it's a fitness function for a GA ... in this case it might be enough to grab 100 (or just 10?) random locations from your images and compare the pixels at that locations. The gained speed up will most probably outdo the loss in accuracy.
Agree with #Arne.
You could also remove the shift rights
(x & 0xFF0000) >> 16) - ((y & 0xFF0000) >> 16 ).
You know that abs(XX0000 - YY0000) is only going to be in the range 0-255.
It would help if you could suggest what it is you are trying to determine?
That is, can the pixel information be store more conducively to what you are trying to acheive, for example as chrominance (YUV, YCrCb)?
If calculating the sum of squares is acceptable for you, it is faster:
for (int i = 0; i < WIDTH * HEIGHT; ++i) {
x = db1.getElem(i);
y = db2.getElem(i);
int dr = ((x & 0xFF0000) >> 16) - ((y & 0xFF0000) >> 16 );
int dg = ((x & 0xFF00) >> 8) - ((y & 0xFF00) >> 8);
int db = (x & 0xFF) - (y & 0xFF);
totalDiff += dr*dr + dg*dg + db*db;
}