/*
* This file is part of Caliph & Emir.
*
* Caliph & Emir is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Caliph & Emir is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Caliph & Emir; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Copyright statement:
* --------------------
* (c) 2002-2005 by Mathias Lux (mathias@juggle.at)
* http://www.juggle.at, http://caliph-emir.sourceforge.net
*/
package at.lux.imageanalysis;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
/**
* Class for extrcating & comparing MPEG-7 based CBIR descriptor ColorLayout
*
* @author Mathias Lux, mathias@juggle.at
*/
public class ColorLayoutImpl implements VisualDescriptor {
// static final boolean debug = true;
protected int[][] shape;
protected int imgYSize, imgXSize;
protected BufferedImage img;
protected static int[] availableCoeffNumbers = {1, 3, 6, 10, 15, 21, 28, 64};
protected int[] YCoeff, CbCoeff, CrCoeff;
protected int numCCoeff = 28, numYCoeff = 64;
protected static int[] arrayZigZag = {
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
};
protected static double[][] arrayCosin = {
{
3.535534e-01, 3.535534e-01, 3.535534e-01, 3.535534e-01,
3.535534e-01, 3.535534e-01, 3.535534e-01, 3.535534e-01
},
{
4.903926e-01, 4.157348e-01, 2.777851e-01, 9.754516e-02,
-9.754516e-02, -2.777851e-01, -4.157348e-01, -4.903926e-01
},
{
4.619398e-01, 1.913417e-01, -1.913417e-01, -4.619398e-01,
-4.619398e-01, -1.913417e-01, 1.913417e-01, 4.619398e-01
},
{
4.157348e-01, -9.754516e-02, -4.903926e-01, -2.777851e-01,
2.777851e-01, 4.903926e-01, 9.754516e-02, -4.157348e-01
},
{
3.535534e-01, -3.535534e-01, -3.535534e-01, 3.535534e-01,
3.535534e-01, -3.535534e-01, -3.535534e-01, 3.535534e-01
},
{
2.777851e-01, -4.903926e-01, 9.754516e-02, 4.157348e-01,
-4.157348e-01, -9.754516e-02, 4.903926e-01, -2.777851e-01
},
{
1.913417e-01, -4.619398e-01, 4.619398e-01, -1.913417e-01,
-1.913417e-01, 4.619398e-01, -4.619398e-01, 1.913417e-01
},
{
9.754516e-02, -2.777851e-01, 4.157348e-01, -4.903926e-01,
4.903926e-01, -4.157348e-01, 2.777851e-01, -9.754516e-02
}
};
protected static int[][] weightMatrix = new int[3][64];
protected BufferedImage colorLayoutImage;
public ColorLayoutImpl() {
// empty constructor, used for new instance if setStringRepresentation is called later.
}
/**
* Create a ColorLayout Object from the given BufferedImage. 6 Y and 3 C Coefficients are used,
* if you want to use another number you have to set it with the Setters.
*
* @param image the input image
*/
public ColorLayoutImpl(BufferedImage image) {
this.img = image;
imgYSize = image.getHeight();
imgXSize = image.getWidth();
init();
}
/**
* Uses the method {@link at.lux.imageanalysis.ColorLayoutImpl#setStringRepresentation(String)}
*
* @param descriptorValues
* @see at.lux.imageanalysis.ColorLayoutImpl#setStringRepresentation(String)
*/
public ColorLayoutImpl(String descriptorValues) {
setStringRepresentation(descriptorValues);
}
/**
* Create a ColorLayout Object from the given BufferedImage with the desired number of Coefficients
*
* @param image the input image
* @param numberOfYCoeff desired number of Y Coefficients
* @param numberOfCCoeff desired number of Cr and Cb Coefficients
*/
public ColorLayoutImpl(int numberOfYCoeff, int numberOfCCoeff, BufferedImage image) {
this.numCCoeff = getRightCoeffNumber(numberOfCCoeff);
this.numYCoeff = getRightCoeffNumber(numberOfYCoeff);
this.img = image;
imgYSize = image.getHeight();
imgXSize = image.getWidth();
init();
}
/**
* init used by all constructors
*/
private void init() {
shape = new int[3][64];
YCoeff = new int[64];
CbCoeff = new int[64];
CrCoeff = new int[64];
colorLayoutImage = null;
extract();
}
public void extract(BufferedImage bimg) {
this.img = bimg;
imgYSize = img.getHeight();
imgXSize = img.getWidth();
init();
}
private void createShape() {
int y_axis, x_axis;
int i, k, x, y, j;
long[][] sum = new long[3][64];
int[] cnt = new int[64];
double yy = 0.0;
int R, G, B;
//init of the blocks
for (i = 0; i < 64; i++) {
cnt[i] = 0;
sum[0][i] = 0;
sum[1][i] = 0;
sum[2][i] = 0;
shape[0][i] = 0;
shape[1][i] = 0;
shape[2][i] = 0;
}
WritableRaster raster = img.getRaster();
int[] pixel = {0, 0, 0};
for (y = 0; y < imgYSize; y++) {
for (x = 0; x < imgXSize; x++) {
raster.getPixel(x, y, pixel);
R = pixel[0];
G = pixel[1];
B = pixel[2];
y_axis = (int) (y / (imgYSize / 8.0));
x_axis = (int) (x / (imgXSize / 8.0));
k = (y_axis << 3) + x_axis;
//RGB to YCbCr, partition and average-calculation
yy = (0.299 * R + 0.587 * G + 0.114 * B) / 256.0;
sum[0][k] += (int) (219.0 * yy + 16.5); // Y
sum[1][k] += (int) (224.0 * 0.564 * (B / 256.0 * 1.0 - yy) + 128.5); // Cb
sum[2][k] += (int) (224.0 * 0.713 * (R / 256.0 * 1.0 - yy) + 128.5); // Cr
cnt[k]++;
}
}
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
for (k = 0; k < 3; k++) {
if (cnt[(i << 3) + j] != 0)
shape[k][(i << 3) + j] = (int) (sum[k][(i << 3) + j] / cnt[(i << 3) + j]);
else
shape[k][(i << 3) + j] = 0;
}
}
}
}
private static void Fdct(int[] shapes) {
int i, j, k;
double s;
double[] dct = new double[64];
//calculation of the cos-values of the second sum
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
s = 0.0;
for (k = 0; k < 8; k++)
s += arrayCosin[j][k] * shapes[8 * i + k];
dct[8 * i + j] = s;
}
}
for (j = 0; j < 8; j++) {
for (i = 0; i < 8; i++) {
s = 0.0;
for (k = 0; k < 8; k++)
s += arrayCosin[i][k] * dct[8 * k + j];
shapes[8 * i + j] = (int) Math.floor(s + 0.499999);
}
}
}
private static int quant_ydc(int i) {
int j;
if (i > 192)
j = 112 + ((i - 192) >> 2);
else if (i > 160)
j = 96 + ((i - 160) >> 1);
else if (i > 96)
j = 32 + (i - 96);
else if (i > 64)
j = 16 + ((i - 64) >> 1);
else
j = i >> 2;
return j;
}
private static int quant_cdc(int i) {
int j;
if (i > 191)
j = 63;
else if (i > 160)
j = 56 + ((i - 160) >> 2);
else if (i > 144)
j = 48 + ((i - 144) >> 1);
else if (i > 112)
j = 16 + (i - 112);
else if (i > 96)
j = 8 + ((i - 96) >> 1);
else if (i > 64)
j = (i - 64) >> 2;
else
j = 0;
return j;
}
private static int quant_ac(int i) {
int j;
if (i > 255)
i = 255;
//if(i > 239)
//i = 239;
if (i < -256)
i = -256;
if ((Math.abs(i)) > 127)
j = 64 + ((Math.abs(i)) >> 2);
else if ((Math.abs(i)) > 63)
j = 32 + ((Math.abs(i)) >> 1);
else
j = Math.abs(i);
j = (i < 0) ? -j : j;
j += 128;
//j+=132;
return j;
}
private int extract() {
createShape();
Fdct(shape[0]);
Fdct(shape[1]);
Fdct(shape[2]);
YCoeff[0] = quant_ydc(shape[0][0] >> 3) >> 1;
CbCoeff[0] = quant_cdc(shape[1][0] >> 3);
CrCoeff[0] = quant_cdc(shape[2][0] >> 3);
//quantization and zig-zagging
for (int i = 1; i < 64; i++) {
YCoeff[i] = quant_ac((shape[0][(arrayZigZag[i])]) >> 1) >> 3;
CbCoeff[i] = quant_ac(shape[1][(arrayZigZag[i])]) >> 3;
CrCoeff[i] = quant_ac(shape[2][(arrayZigZag[i])]) >> 3;
}
setYCoeff(YCoeff);
setCbCoeff(CbCoeff);
setCrCoeff(CrCoeff);
return 0;
}
private void setYCoeff(int[] YCoeff) {
StringBuilder b = new StringBuilder(256);
for (int i = 0; i < numYCoeff; i++) {
b.append(YCoeff[i]).append(' ');
}
// System.out.println("y: " + b.toString());
}
private void setCbCoeff(int[] CbCoeff) {
StringBuilder b = new StringBuilder(256);
for (int i = 0; i < numCCoeff; i++) {
b.append(CbCoeff[i]).append(' ');
}
// System.out.println("cb: " + b.toString());
}
private void setCrCoeff(int[] CrCoeff) {
StringBuilder b = new StringBuilder(256);
for (int i = 0; i < numCCoeff; i++) {
b.append(CrCoeff[i]).append(' ');
}
// System.out.println("cr: " + b.toString());
}
/**
* Nicht alle Werte sind laut MPEG-7 erlaubt ....
*/
private static int getRightCoeffNumber(int num) {
int val = 0;
if (num <= 1)
val = 1;
else if (num <= 3)
val = 3;
else if (num <= 6)
val = 6;
else if (num <= 10)
val = 10;
else if (num <= 15)
val = 15;
else if (num <= 21)
val = 21;
else if (num <= 28)
val = 28;
else if (num > 28) val = 64;
return val;
}
/**
* Takes two ColorLayout Coeff sets and calculates similarity.
*
* @return -1.0 if data is not valid.
*/
public static double getSimilarity(int[] YCoeff1, int[] CbCoeff1, int[] CrCoeff1, int[] YCoeff2, int[] CbCoeff2, int[] CrCoeff2) {
int numYCoeff1, numYCoeff2, CCoeff1, CCoeff2, YCoeff, CCoeff;
//Numbers of the Coefficients of two descriptor values.
numYCoeff1 = YCoeff1.length;
numYCoeff2 = YCoeff2.length;
CCoeff1 = CbCoeff1.length;
CCoeff2 = CbCoeff2.length;
//take the minimal Coeff-number
YCoeff = Math.min(numYCoeff1, numYCoeff2);
CCoeff = Math.min(CCoeff1, CCoeff2);
setWeightingValues();
int j;
int[] sum = new int[3];
int diff;
sum[0] = 0;
for (j = 0; j < YCoeff; j++) {
diff = (YCoeff1[j] - YCoeff2[j]);
sum[0] += (weightMatrix[0][j] * diff * diff);
}
sum[1] = 0;
for (j = 0; j < CCoeff; j++) {
diff = (CbCoeff1[j] - CbCoeff2[j]);
sum[1] += (weightMatrix[1][j] * diff * diff);
}
sum[2] = 0;
for (j = 0; j < CCoeff; j++) {
diff = (CrCoeff1[j] - CrCoeff2[j]);
sum[2] += (weightMatrix[2][j] * diff * diff);
}
//returns the distance between the two desciptor values
return Math.sqrt(sum[0] * 1.0) + Math.sqrt(sum[1] * 1.0) + Math.sqrt(sum[2] * 1.0);
}
private static void setWeightingValues() {
weightMatrix[0][0] = 2;
weightMatrix[0][1] = weightMatrix[0][2] = 2;
weightMatrix[1][0] = 2;
weightMatrix[1][1] = weightMatrix[1][2] = 1;
weightMatrix[2][0] = 4;
weightMatrix[2][1] = weightMatrix[2][2] = 2;
for (int i = 0; i < 3; i++) {
for (int j = 3; j < 64; j++)
weightMatrix[i][j] = 1;
}
}
private static BufferedImage YCrCb2RGB(int[][] rgbSmallImage) {
BufferedImage br = new BufferedImage(8, 8, BufferedImage.TYPE_INT_RGB);
WritableRaster r = br.getRaster();
double rImage, gImage, bImage;
int pixel[] = new int[3];
for (int i = 0; i < 64; i++) {
rImage = ((rgbSmallImage[0][i] - 16.0) * 256.0) / 219.0;
gImage = ((rgbSmallImage[1][i] - 128.0) * 256.0) / 224.0;
bImage = ((rgbSmallImage[2][i] - 128.0) * 256.0) / 224.0;
pixel[0] = Math.max(0, (int) ((rImage) + (1.402 * bImage) + 0.5)); //R
pixel[1] = Math.max(0, (int) ((rImage) + (-0.34413 * gImage) + (-0.71414 * bImage) + 0.5)); //G
pixel[2] = Math.max(0, (int) ((rImage) + (1.772 * gImage) + 0.5)); //B
r.setPixel(i % 8, i >> 3, pixel);
}
return br;
}
public BufferedImage getColorLayoutImage() {
if (colorLayoutImage != null)
return colorLayoutImage;
else {
int[][] smallReImage = new int[3][64];
// inverse quantization and zig-zagging
smallReImage[0][0] = IquantYdc((YCoeff[0]));
smallReImage[1][0] = IquantCdc((CbCoeff[0]));
smallReImage[2][0] = IquantCdc((CrCoeff[0]));
for (int i = 1; i < 64; i++) {
smallReImage[0][(arrayZigZag[i])] = IquantYac((YCoeff[i]));
smallReImage[1][(arrayZigZag[i])] = IquantCac((CbCoeff[i]));
smallReImage[2][(arrayZigZag[i])] = IquantCac((CrCoeff[i]));
}
// inverse Discrete Cosine Transform
Idct(smallReImage[0]);
Idct(smallReImage[1]);
Idct(smallReImage[2]);
// YCrCb to RGB
colorLayoutImage = YCrCb2RGB(smallReImage);
return colorLayoutImage;
}
}
private static void Idct(int[] iShapes) {
int u, v, k;
double s;
double[] dct = new double[64];
//calculation of the cos-values of the second sum
for (u = 0; u < 8; u++) {
for (v = 0; v < 8; v++) {
s = 0.0;
for (k = 0; k < 8; k++)
s += arrayCosin[k][v] * iShapes[8 * u + k];
dct[8 * u + v] = s;
}
}
for (v = 0; v < 8; v++) {
for (u = 0; u < 8; u++) {
s = 0.0;
for (k = 0; k < 8; k++)
s += arrayCosin[k][u] * dct[8 * k + v];
iShapes[8 * u + v] = (int) Math.floor(s + 0.499999);
}
}
}
private static int IquantYdc(int i) {
int j;
i = i << 1;
if (i > 112)
j = 194 + ((i - 112) << 2);
else if (i > 96)
j = 162 + ((i - 96) << 1);
else if (i > 32)
j = 96 + (i - 32);
else if (i > 16)
j = 66 + ((i - 16) << 1);
else
j = i << 2;
return j << 3;
}
private static int IquantCdc(int i) {
int j;
if (i > 63)
j = 192;
else if (i > 56)
j = 162 + ((i - 56) << 2);
else if (i > 48)
j = 145 + ((i - 48) << 1);
else if (i > 16)
j = 112 + (i - 16);
else if (i > 8)
j = 97 + ((i - 8) << 1);
else if (i > 0)
j = 66 + (i << 2);
else
j = 64;
return j << 3;
}
private static int IquantYac(int i) {
int j;
i = i << 3;
i -= 128;
if (i > 128)
i = 128;
if (i < -128)
i = -128;
if ((Math.abs(i)) > 96)
j = ((Math.abs(i)) << 2) - 256;
else if ((Math.abs(i)) > 64)
j = ((Math.abs(i)) << 1) - 64;
else
j = Math.abs(i);
j = (i < 0) ? -j : j;
return j << 1;
}
private static int IquantCac(int i) {
int j;
i = i << 3;
i -= 128;
if (i > 128)
i = 128;
if (i < -128)
i = -128;
if ((Math.abs(i)) > 96)
j = ((Math.abs(i) << 2) - 256);
else if ((Math.abs(i)) > 64)
j = ((Math.abs(i) << 1) - 64);
else
j = Math.abs(i);
j = (i < 0) ? -j : j;
return j;
}
public int getNumberOfCCoeff() {
return numCCoeff;
}
public void setNumberOfCCoeff(int numberOfCCoeff) {
this.numCCoeff = numberOfCCoeff;
}
public int getNumberOfYCoeff() {
return numYCoeff;
}
public void setNumberOfYCoeff(int numberOfYCoeff) {
this.numYCoeff = numberOfYCoeff;
}
/**
* Compares one descriptor to another.
*
* @param descriptor
* @return the distance from [0,infinite) or -1 if descriptor type does not match
*/
public float getDistance(VisualDescriptor descriptor) {
if (!(descriptor instanceof ColorLayoutImpl)) return -1f;
ColorLayoutImpl cl = (ColorLayoutImpl) descriptor;
return (float) ColorLayoutImpl.getSimilarity(YCoeff, CbCoeff, CrCoeff, cl.YCoeff, cl.CbCoeff, cl.CrCoeff);
}
public String getStringRepresentation() {
StringBuilder sb = new StringBuilder(256);
StringBuilder sbtmp = new StringBuilder(256);
for (int i = 0; i < numYCoeff; i++) {
sb.append(YCoeff[i]);
if (i + 1 < numYCoeff) sb.append(' ');
}
sb.append("z");
for (int i = 0; i < numCCoeff; i++) {
sb.append(CbCoeff[i]);
if (i + 1 < numCCoeff) sb.append(' ');
sbtmp.append(CrCoeff[i]);
if (i + 1 < numCCoeff) sbtmp.append(' ');
}
sb.append("z");
sb.append(sbtmp);
return sb.toString();
}
public void setStringRepresentation(String descriptor) {
String[] coeffs = descriptor.split("z");
String[] y = coeffs[0].split(" ");
String[] cb = coeffs[1].split(" ");
String[] cr = coeffs[2].split(" ");
numYCoeff = y.length;
numCCoeff = Math.min(cb.length, cr.length);
YCoeff = new int[numYCoeff];
CbCoeff = new int[numCCoeff];
CrCoeff = new int[numCCoeff];
for (int i = 0; i < numYCoeff; i++) {
YCoeff[i] = Integer.parseInt(y[i]);
}
for (int i = 0; i < numCCoeff; i++) {
CbCoeff[i] = Integer.parseInt(cb[i]);
CrCoeff[i] = Integer.parseInt(cr[i]);
}
}
public int[] getYCoeff() {
return YCoeff;
}
public int[] getCbCoeff() {
return CbCoeff;
}
public int[] getCrCoeff() {
return CrCoeff;
}
}