package at.lux.imageanalysis;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.LinkedList;
import java.util.StringTokenizer;
/*
* Based on the source on following license:
* =========================================
* Created on 05.11.2004
* Color Structure Descriptor for MPEG7 VizIR
*
* Copyright (C) 2004 Adis Buturovic, Vienna University of Technology
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Contact address: adis@ims.tuwien.ac.at
* For full description see MPEG7-CSD.pdf
*/
public class ColorStructureImplementation implements VisualDescriptor {
public ColorStructureImplementation() {
quantizationLevels = 64;
}
public ColorStructureImplementation(int quantizationLevels) {
this.quantizationLevels = quantizationLevels;
}
public ColorStructureImplementation(BufferedImage image) {
try {
extractFeature(image);
} catch (Exception e) {
// todo: some error handling ...
e.printStackTrace();
}
}
/**
* Quantity of color quantization bins
*/
private int quantizationLevels = 64;
/**
* Subspaces needed for the quantization
*/
private static int subspace = 0;
/**
* Color Structure Histogram
*/
protected float[] ColorHistogram = null;
/**
* Quantization table for 256, 128, 64 and 32 quantisation bins.
* <p/>
* <br>form:
* <code><br>
* subspace0 , subspace1 , subspace2 , subspace3 , subspace4 <br>
* Hue,Sum , Hue,Sum , Hue,Sum , Hue,Sum , Hue,Sum - 256 Levels [offset pos 0 - pos 9]<br>
* Hue,Sum , Hue,Sum , Hue,Sum , Hue,Sum , Hue,Sum - 128 Levels [offset pos 10 - pos 19]<br>
* Hue,Sum , Hue,Sum , Hue,Sum , Hue,Sum , Hue,Sum - 64 Levels [offset pos 20 - pos 29]<br>
* Hue,Sum , Hue,Sum , Hue,Sum , Hue,Sum , Hue,Sum - 32 Levels [offset pos 30 - pos 39]<br>
* </code>
*/
private static final int[] quantTable = {1, 32, 4, 8, 16, 4, 16, 4, 16, 4, // Hue, Sum - subspace 0,1,2,3,4 for 256 levels
1, 16, 4, 4, 8, 4, 8, 4, 8, 4, // Hue, Sum - subspace 0,1,2,3,4 for 128 levels
1, 8, 4, 4, 4, 4, 8, 2, 8, 1, // Hue, Sum - subspace 0,1,2,3,4 for 64 levels
1, 8, 4, 4, 4, 4, 4, 1, 4, 1}; // Hue, Sum - subspace 0,1,2,3,4 for 32 levels
/**
* The <code>extractFeature(MediaContent)</code> class loads the media content, convert it to HMMD color space and executes
* the CSD extraction and quantization.
*
* @param image the image to be analyzed.
* @throws Exception in some cases ...
*/
public void extractFeature(BufferedImage image) throws Exception {
// load image to BufferedImage
double height = image.getHeight();
double width = image.getWidth();
int temp[][] = new int[(int) height - 1][(int) width - 1];
// if (width > height) {
// System.out.println("\nExit - vizir bug: file unsupported -> MediaFrame.getPixelAt");
// System.exit(0);
// }
int ir[][] = temp;
int ig[][] = temp;
int ib[][] = temp;
int iH[][] = temp;
int iMax[][] = temp;
int iMin[][] = temp;
int iDiff[][] = temp;
int iSum[][] = temp;
// convert BufferedImage to double int array for every RGB color
// ant then covert RGB values into HMMD
WritableRaster raster = image.getRaster();
int[] pixel = new int[3];
for (int ch = 0; ch < height - 1; ch++) { //row
for (int cw = 0; cw < width - 1; cw++) {//column
raster.getPixel(cw, ch, pixel);
ir[ch][cw] = pixel[0]; // RED
ig[ch][cw] = pixel[1]; // GREEN
ib[ch][cw] = pixel[2]; // BLUE
int[] tempHMMD = RGB2HMMD(ir[ch][cw], ig[ch][cw], ib[ch][cw]);
iH[ch][cw] = tempHMMD[0]; // H
iMax[ch][cw] = tempHMMD[1]; // Max
iMin[ch][cw] = tempHMMD[2]; // Min
iDiff[ch][cw] = tempHMMD[3]; // Diff
iSum[ch][cw] = tempHMMD[4]; // Sum
}
}
ColorHistogram = HMMDColorStuctureExtraction(iH, iMax, iMin, iDiff, iSum, (int) height, (int) width); // extract HMMD colors and make histogram
// if ( quantizationLevels != 256 ) ColorHistogram = reQuantization(ColorHistogram); // requantize and normalize histogram to 0-255 range
ColorHistogram = reQuantization(ColorHistogram);
}
/**
* The <code>HMMDColorStuctureExtraction(int[][], int[][], int[][], int[][], int[][], int, int)</code> class builds the Color Structure Histogram.
*
* @param iH - Hue values of the frame
* @param iMax - Max values of the frame
* @param iMin - Min values of the frame
* @param iDiff - Diff values of the frame
* @param iSum - values of the frame
* @param height - height of the frame
* @param width - width of the frame
* @return float[] - Color Structure Histogram
* @throws Exception in some cases ...
*/
private float[] HMMDColorStuctureExtraction(int iH[][], int iMax[][], int iMin[][], int iDiff[][], int iSum[][], int height, int width) throws Exception {
long hw = height * width;
long p = Math.round(0.5 * Math.log(hw)) - 8;
if (p < 0) p = 0;
double K = Math.pow(2, p);
double E = 8 * K;
int m = 0;
width--;
height--; // um ueberlauf in der schleife zu vermeiden - 24.07.2005
if (quantizationLevels == 0) {
setQuantizationLevels(64);
System.out.println("WARNING: quantization size will be set to default: 64");
} // default value is 64
float h[] = new float[quantizationLevels]; // CSD temp
int t[] = new int[quantizationLevels]; // CSD temp - int sollte stimmen
for (int i = 0; i < quantizationLevels; i++) { /* t[i] = 0; */
h[i] = 0.0f;
}
for (int y = 0; y < ((height - E) + 1); y += K) {
for (int x = 0; x < ((width - E) + 1); x += K) {
// re initialize the local histogram t[m]
for (m = 0; m < quantizationLevels; m++) t[m] = 0;
//collect local histogram over pixels in structuring element
for (int yy = y; yy < y + E; yy += K)
for (int xx = x; xx < x + E; xx += K) {
// get quantized pixel color and update local histogramm
// The 256-cell color space is quantized non-uniformly as follows.
// First, the HMMD Color space is divided into 5 subspaces:
// subspaces 0,1,2,3, and 4.
// This subspace division is defined along the Diff(colorfulness) axis of the HMMD Color space.
// The subspaces are defined by cut-points which determine the following diff axis intervals:
// [(0,6),(6,20),(20,60),(60,110),(110,255)].
// Second, each color subspace is uniformly quantized along Hue and Sum axes, where the
// number of Quantization levels along each axis is defined in the Table for each operating point.
//
// Example:
// 64 levels 32 levels
//
// Subspace Hue Sum Hue Sum
// 0 1 8 1 8
// 1 4 4 4 4
// 2 4 4 4 4
// 3 8 2 4 1
// 4 8 1 4 1
int offset = 0; // offset position in the quantization table
int q = 0;
try {
// define the subspace along the Diff axis
if (iDiff[xx][yy] < 7) subspace = 0;
else if ((iDiff[xx][yy] > 6) && (iDiff[xx][yy] < 21)) subspace = 1;
else if ((iDiff[xx][yy] > 19) && (iDiff[xx][yy] < 61)) subspace = 2;
else if ((iDiff[xx][yy] > 59) && (iDiff[xx][yy] < 111)) subspace = 3;
else if ((iDiff[xx][yy] > 109) && (iDiff[xx][yy] < 256)) subspace = 4;
// HMMD Color Space quantization
// see MPEG7-CSD.pdf
if (quantizationLevels == 256) {
offset = 0;
//m = (int)(((float)iH[xx][yy] / quantizationLevels) * quantTable[offset+subspace] + ((float)iSum[xx][yy] / quantizationLevels) * quantTable[offset+subspace+1]);
m = (int) ((iH[xx][yy] / quantizationLevels) * quantTable[offset + subspace] + (iSum[xx][yy] / quantizationLevels) * quantTable[offset + subspace + 1]);
} else if (quantizationLevels == 128) {
offset = 10;
//m = (int)(((float)iH[xx][yy] / quantizationLevels) * quantTable[offset+subspace] + ((float)iSum[xx][yy] / quantizationLevels) * quantTable[offset+subspace+1]);
m = (int) ((iH[xx][yy] / quantizationLevels) * quantTable[offset + subspace] + (iSum[xx][yy] / quantizationLevels) * quantTable[offset + subspace + 1]);
} else if (quantizationLevels == 64) {
offset = 20;
//m = (int)(((float)iH[xx][yy] / quantizationLevels) * quantTable[offset+subspace] + ((float)iSum[xx][yy] / quantizationLevels) * quantTable[offset+subspace+1]);
m = (int) ((iH[xx][yy] / quantizationLevels) * quantTable[offset + subspace] + (iSum[xx][yy] / quantizationLevels) * quantTable[offset + subspace + 1]);
} else if (quantizationLevels == 32) {
offset = 30;
//m = (int)(((float)iH[xx][yy] / quantizationLevels) * quantTable[offset+subspace] + ((float)iSum[xx][yy] / quantizationLevels) * quantTable[offset+subspace+1]);
m = (int) ((iH[xx][yy] / quantizationLevels) * quantTable[offset + subspace] + (iSum[xx][yy] / quantizationLevels) * quantTable[offset + subspace + 1]);
// System.out.println("m: " + m);
}
t[m]++;
}
catch (Exception e) {
e.printStackTrace();
System.err.println("PROB? - quant. schleife: x = " + xx + " y = " + yy);
System.out.println("quantizationLevels = " + quantizationLevels);
System.out.println("subspace = " + subspace);
}
}
// increment the color structure histogramm for each color present in the structuring element
for (m = 0; m < quantizationLevels; m++) {
if (t[m] > 0) h[m]++;
}
}
}
int S = (width - (int) E + (int) K) / (int) K * ((height - (int) E + (int) K) / (int) K);
for (m = 0; m < quantizationLevels; m++) {
h[m] = h[m] / S;
}
return h;
}
/**
* The <code>reQuantization(float[] colorHistogramTemp)</code> class is responsible for re-quantization of
* the CSD Histogram and normalizing to 8-bit code values
*
* @return float[] - normalized Color Structure Histogram
*/
private float[] reQuantization(float[] colorHistogramTemp) {
float[] uniformCSD = new float[colorHistogramTemp.length];
for (int i = 0; i < colorHistogramTemp.length; i++) {
// System.out.print(colorHistogramTemp[i] + " ");
// System.out.println(" --- ");
if (colorHistogramTemp[i] == 0) uniformCSD[i] = 0; //
else if (colorHistogramTemp[i] < 0.000000001f)
uniformCSD[i] = (int) Math.round((((float) colorHistogramTemp[i] - 0.32f) / (1f - 0.32f)) * 140 + (115 - 35 - 35 - 20 - 25 - 1)); // (int)Math.round((1f / 0.000000001f) * (float)colorHistogramTemp[i]);
else if (colorHistogramTemp[i] < 0.037f)
uniformCSD[i] = (int) Math.round((((float) colorHistogramTemp[i] - 0.32f) / (1f - 0.32f)) * 140 + (115 - 35 - 35 - 20 - 25));
else if (colorHistogramTemp[i] < 0.08f)
uniformCSD[i] = (int) Math.round((((float) colorHistogramTemp[i] - 0.32f) / (1f - 0.32f)) * 140 + (115 - 35 - 35 - 20));
else if (colorHistogramTemp[i] < 0.195f)
uniformCSD[i] = (int) Math.round((((float) colorHistogramTemp[i] - 0.32f) / (1f - 0.32f)) * 140 + (115 - 35 - 35));
else if (colorHistogramTemp[i] < 0.32f)
uniformCSD[i] = (int) Math.round((((float) colorHistogramTemp[i] - 0.32f) / (1f - 0.32f)) * 140 + (115 - 35));
else if (colorHistogramTemp[i] > 0.32f)
uniformCSD[i] = (int) Math.round((((float) colorHistogramTemp[i] - 0.32f) / (1f - 0.32f)) * 140 + 115);
else uniformCSD[i] = (int) Math.round((255f / 1f) * (float) colorHistogramTemp[i]);
}
return uniformCSD;
}
/**
* The <code>RGB2HMMD (int ir, int ig, int ib)</code> class is responsible for converting RGB values to HMMD values
*
* @param ir - RED component
* @param ig - GREEN component
* @param ib - BLUE component
* @return int[] - HMMD value of the pixle
* @throws Exception
* @author adis@ims.tuwien.ac.at
*/
private static int[] RGB2HMMD(int ir, int ig, int ib) throws Exception {
int HMMD[] = new int[5];
float max = (float) Math.max(Math.max(ir, ig), Math.max(ig, ib));
float min = (float) Math.min(Math.min(ir, ig), Math.min(ig, ib));
float diff = (max - min);
float sum = (float) ((max + min) / 2.);
float hue = 0;
if (diff == 0) hue = 0;
else if (ir == max && (ig - ib) > 0) hue = 60 * (ig - ib) / (max - min);
else if (ir == max && (ig - ib) <= 0) hue = 60 * (ig - ib) / (max - min) + 360;
else if (ig == max) hue = (float) (60 * (2. + (ib - ir) / (max - min)));
else if (ib == max) hue = (float) (60 * (4. + (ir - ig) / (max - min)));
diff /= 2;
HMMD[0] = (int) (hue);
HMMD[1] = (int) (max);
HMMD[2] = (int) (min);
HMMD[3] = (int) (diff);
HMMD[4] = (int) (sum);
return (HMMD);
}
/**
* With <code>setQuantizationLevels(int)</code> class you can set the quantisation size
*
* @param number
* @throws Exception
*/
public void setQuantizationLevels(int number) throws Exception {
if ((number != 32) && (number != 64) && (number != 128) && (number != 256))
throw new Exception("have to be chosen from: 32, 64, 128, 256");
quantizationLevels = number;
}
/**
* Added by Mathias Lux, just an L1 distance function ...
*
* @param descriptor
* @return
*/
public float getDistance(VisualDescriptor descriptor) {
float result = -1f;
if (descriptor instanceof ColorStructureImplementation) {
ColorStructureImplementation c2 = (ColorStructureImplementation) descriptor;
if (c2.quantizationLevels == quantizationLevels) {
// quant levels match, so we can calc distance ...
result = 0f;
for (int i = 0; i < ColorHistogram.length; i++) {
result += Math.abs(ColorHistogram[i] - c2.ColorHistogram[i]);
}
}
}
return result;
}
public String getStringRepresentation() {
StringBuilder sb = new StringBuilder(256);
sb.append("colorstructure;");
for (float value : ColorHistogram) {
sb.append((int) value);
sb.append(' ');
}
return sb.toString();
}
public void setStringRepresentation(String descriptor) {
String[] parts = descriptor.split(";");
if (!parts[0].equals("colorstructure")) {
throw new UnsupportedOperationException("This is no valid representation of a ColorStructur descriptor!");
}
LinkedList<Float> vals = new LinkedList<Float>();
StringTokenizer st = new StringTokenizer(parts[1], " ");
while (st.hasMoreElements()) {
vals.add(Float.parseFloat(st.nextToken()));
}
if ((vals.size() != 32) && (vals.size() != 64) && (vals.size() != 128) && (vals.size() != 256))
throw new UnsupportedOperationException("This is no valid representation of a ColorStructur descriptor! " +
"Quant level has to be 32, 64, 128 or 256.");
ColorHistogram = new float[vals.size()];
quantizationLevels = vals.size();
}
}