/*
* This file is part of the Caliph and Emir project: http://www.SemanticMetadata.net.
*
* 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-2006 by Mathias Lux (mathias@juggle.at)
* http://www.juggle.at, http://www.SemanticMetadata.net
*
* This code is based on the EdgeHistogram implementation of Roman Divotkey
* (divotkey@ims.tuwien.ac.at) and Katharina Tomanec (tomanec@gmx.at),
* Vienna University of Technology, who published his code on GPL at
* http://cbvr.ims.tuwien.ac.at/download.html
*/
package at.lux.imageanalysis;
import java.awt.image.BufferedImage;
import java.util.StringTokenizer;
/**
* This class implements the EdgeHistogram descriptor from the MPEG-7 standard.
* <p/>
* This file is part of the Caliph and Emir project: http://www.SemanticMetadata.net
* <br>Date: 31.01.2006
* <br>Time: 23:07:39
*
* @author Mathias Lux, mathias@juggle.at
*/
public class EdgeHistogramImplementation implements VisualDescriptor {
public static final int BIN_COUNT = 80;
private int[] bins = new int[80];
private int treshold = 11;
private double width;
private double height;
private int num_block = 1100;
/**
* Constant used in case there is no edge in the image.
*/
private static final int NoEdge = 0;
/**
* Constant used in case there is a vertical edge.
*/
private static final int vertical_edge = 1;
/**
* Constant used in case there is a horizontal edge.
*/
private static final int horizontal_edge = 2;
/**
* Constant used in case there is no directional edge.
*/
private static final int non_directional_edge = 3;
/**
* Constant used in case there is a diagonal of 45 degree.
*/
private static final int diagonal_45_degree_edge = 4;
/**
* Constant used in case there is a digaonal of 135 degree.
*/
private static final int diagonal_135_degree_edge = 5;
/**
* The grey level that has been found after converting from RGB.
*/
private double[][] grey_level;
/**
* The bins have to be quantized with help of this quantization table.
* The first row is used for the vertical bins,
* the second for the horizontal bins,
* the third for the 45 degree bins,
* the fourth for the 135 degree and the last for the non-directional edges.
*/
private static double[][] QuantTable =
{{0.010867, 0.057915, 0.099526, 0.144849, 0.195573, 0.260504, 0.358031, 0.530128},
{0.012266, 0.069934, 0.125879, 0.182307, 0.243396, 0.314563, 0.411728, 0.564319},
{0.004193, 0.025852, 0.046860, 0.068519, 0.093286, 0.123490, 0.161505, 0.228960},
{0.004174, 0.025924, 0.046232, 0.067163, 0.089655, 0.115391, 0.151904, 0.217745},
{0.006778, 0.051667, 0.108650, 0.166257, 0.224226, 0.285691, 0.356375, 0.450972}};
/**
* Array, where the bins are saved before they have been quantized.
*/
private double Local_Edge_Histogram[] = new double[80];
private int blockSize = -1;
private BufferedImage image;
/**
* The actual edge histogram.
*/
protected int[] edgeHistogram;
/**
* Allocates a new <code>EdgeHistogramDescriptor</code> so that it represents the
* media located in the file.
*/
public EdgeHistogramImplementation(BufferedImage image) {
this.image = image;
width = image.getWidth();
height = image.getHeight();
extractFeature();
edgeHistogram = setEdgeHistogram();
}
public void extract(BufferedImage image) {
this.image = image;
width = image.getWidth();
height = image.getHeight();
extractFeature();
edgeHistogram = setEdgeHistogram();
}
public EdgeHistogramImplementation(String descriptor) {
setStringRepresentation(descriptor);
}
/**
* Allocates a new <code>EdgeHistogramDescriptor</code>
*/
public EdgeHistogramImplementation() {
}
/**
* Sets the Bin Counts after they have been quantized.
*
* @param bins array of 80 bins
* @throws Exception indicates conditions that a reasonable application might want to catch.
*/
private void setBinCounts(int[] bins) throws Exception {
for (int i = 0; i <= EdgeHistogramImplementation.BIN_COUNT - 1; i++) {
this.bins[i] = bins[i];
}
}
/**
* Responsible for setting the Bin Counts in view of the position of the bins.
*
* @param pos position of the bins
* @param value value of the bin
* @throws ArrayIndexOutOfBoundsException is necessary, since the bins are stored in an array
*/
public void setBinCounts(int pos, int[] value) throws ArrayIndexOutOfBoundsException {
bins[pos] = value[pos];
}
/**
* Gets the Bins of the array.
*
* @throws Exception indicates conditions that a reasonable application might want to catch.
*/
public int getBinCounts() throws Exception {
int i = 0;
return bins[i];
}
/**
* If the maximum of the five edge strengths is greater than a thrshold, then the image block is
* considered to habe the corresponding edge in it. Otherwise, the image block contains no edge.
* The default value is 11.
*/
public void setThreshold() {
treshold = 11;
}
public int getThreshold() {
return treshold;
}
/**
* The image is splitted into 16 local regions, each of them is divided into a fixed number
* of image_blocks, depending on width and height of the image.
* The size of this image_block is here computed.
*
* @return b_size
*/
private int getblockSize() {
if (blockSize < 0) {
double a = (int) (Math.sqrt((width * height) / num_block));
blockSize = (int) (Math.floor((a / 2)) * 2);
if (blockSize == 0)
blockSize = 2;
}
return blockSize;
}
/**
* The width and height of the media is taken to convert the RGB values into luminance values.
* returns returns the grey_level of the pixel
*/
public void makeGreyLevel() {
grey_level = new double[(int) width][(int) height];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
grey_level[x][y] = getYfromRGB(image.getRGB(x, y));
}
}
}
/**
* The image_block is divided into four sub-blocks, for these the luminance mean values
* are used for the edge detection later.
* This is the first sub-block.
*
* @param i indices from the image_block
* @param j indices from the image_block
* @return returns the average brightness of the first sub-block.
*/
private double getFirstBlockAVG(int i, int j) {
double average_brightness = 0;
if (grey_level[i][j] != 0) {
for (int m = 0; m <= (getblockSize() >> 1) - 1; m++) {
for (int n = 0; n <= (getblockSize() >> 1) - 1; n++) {
average_brightness = average_brightness + grey_level[i + m][j + n];
}
}
} else {
System.err.println("Grey level not initialized.");
}
double bs = getblockSize() * getblockSize();
double div = 4 / bs;
average_brightness = average_brightness * div;
return average_brightness;
}
/**
* The image_block is divided into four sub-blocks, for these the luminance mean values
* are used for the edge detection later.
* This is the second sub-block.
*
* @param i indices from the image_block
* @param j indices from the image_block
* @return returns the average brightness of the seconc sub-block.
*/
private double getSecondBlockAVG(int i, int j) {
double average_brightness = 0;
if (grey_level[i][j] != 0)
for (int m = (int) (getblockSize() >> 1); m <= getblockSize() - 1; m++) {
for (int n = 0; n <= (getblockSize() >> 1) - 1; n++) {
average_brightness += grey_level[i + m][j + n];
}
}
else {
System.err.println("Grey level not initialized.");
}
double bs = getblockSize() * getblockSize();
double div = 4 / bs;
average_brightness = average_brightness * div;
return average_brightness;
}
/**
* The image_block is divided into four sub-blocks, for these the luminance mean values
* are used for the edge detection later.
* This is the third sub-block.
*
* @param i indices from the image_block
* @param j indices from the image_block
* @return returns the average brightness of the third sub-block.
*/
private double getThirdBlockAVG(int i, int j) {
double average_brightness = 0;
if (grey_level[i][j] != 0) {
for (int m = 0; m <= (getblockSize() >> 1) - 1; m++) {
for (int n = (int) (getblockSize() >> 1); n <= getblockSize() - 1; n++) {
average_brightness += grey_level[i + m][j + n];
}
}
} else {
System.err.println("Grey level not initialized.");
}
double bs = getblockSize() * getblockSize();
double div = 4 / bs;
average_brightness = average_brightness * div;
return average_brightness;
}
/**
* The image_block is divided into four sub-blocks, for these the luminance mean values
* are used for the edge detection later.
* This is the fourth sub-block.
*
* @param i indices from the image_block
* @param j indices from the image_block
* @return returns the average brightness of the fourth sub-block.
*/
private double getFourthBlockAVG(int i, int j) {
double average_brightness = 0;
for (int m = (int) (getblockSize() >> 1); m <= getblockSize() - 1; m++) {
for (int n = (int) (getblockSize() >> 1); n <= getblockSize() - 1; n++) {
average_brightness += grey_level[i + m][j + n];
}
}
double bs = getblockSize() * getblockSize();
double div = 4 / bs;
average_brightness = average_brightness * div;
return average_brightness;
}
/**
* The four mean values from the sub-blocks are convolved with filter coefficients.
* Then the maximum of these results is found and compared with a threshold.
*
* @param i indices of image_block
* @param j indices of image_block
* @return e_index returns the index, where the maximum edge is found.
*/
private int getEdgeFeature(int i, int j) {
double average[] = {getFirstBlockAVG(i, j), getSecondBlockAVG(i, j),
getThirdBlockAVG(i, j), getFourthBlockAVG(i, j)};
double th = this.treshold;
double edge_filter[][] = {{1.0, -1.0, 1.0, -1.0},
{1.0, 1.0, -1.0, -1.0},
{Math.sqrt(2), 0.0, 0.0, -Math.sqrt(2)},
{0.0, Math.sqrt(2), -Math.sqrt(2), 0.0},
{2.0, -2.0, -2.0, 2.0}};
double[] strengths = new double[5];
int e_index;
for (int e = 0; e < 5; e++) {
for (int k = 0; k < 4; k++) {
strengths[e] += average[k] * edge_filter[e][k];
}
strengths[e] = Math.abs(strengths[e]);
}
double e_max = 0.0;
e_max = strengths[0];
e_index = EdgeHistogramImplementation.vertical_edge;
if (strengths[1] > e_max) {
e_max = strengths[1];
e_index = EdgeHistogramImplementation.horizontal_edge;
}
if (strengths[2] > e_max) {
e_max = strengths[2];
e_index = EdgeHistogramImplementation.diagonal_45_degree_edge;
}
if (strengths[3] > e_max) {
e_max = strengths[3];
e_index = EdgeHistogramImplementation.diagonal_135_degree_edge;
}
if (strengths[4] > e_max) {
e_max = strengths[4];
e_index = EdgeHistogramImplementation.non_directional_edge;
}
if (e_max < th) {
e_index = EdgeHistogramImplementation.NoEdge;
}
return (e_index);
}
/**
* <code>extractFeature()</code> takes all the information about the types of edge, that
* have been found in the sub-images and applies the methods getFirstBlockAVG(int i,int j) through
* getFourthBlockAVG(int i, int j) on the whole image. returns Local_Edge_Histogram returns the 80 bins
*/
public void extractFeature() {
makeGreyLevel();
int sub_local_index = 0;
int EdgeTypeOfBlock = 0;
int[] count_local = new int[16];
for (int i = 0; i < 16; i++) {
count_local[i] = 0;
}
for (int j = 0; j <= height - getblockSize(); j += getblockSize())
for (int i = 0; i <= width - getblockSize(); i += getblockSize()) {
sub_local_index = (int) ((i << 2) / width) + ((int) ((j << 2) / height) << 2);
count_local[sub_local_index]++;
EdgeTypeOfBlock = getEdgeFeature(i, j);
switch (EdgeTypeOfBlock) {
case EdgeHistogramImplementation.NoEdge:
break;
case EdgeHistogramImplementation.vertical_edge:
Local_Edge_Histogram[sub_local_index * 5]++;
break;
case EdgeHistogramImplementation.horizontal_edge:
Local_Edge_Histogram[sub_local_index * 5 + 1]++;
break;
case EdgeHistogramImplementation.diagonal_45_degree_edge:
Local_Edge_Histogram[sub_local_index * 5 + 2]++;
break;
case EdgeHistogramImplementation.diagonal_135_degree_edge:
Local_Edge_Histogram[sub_local_index * 5 + 3]++;
break;
case EdgeHistogramImplementation.non_directional_edge:
Local_Edge_Histogram[sub_local_index * 5 + 4]++;
break;
}//switch(EdgeTypeOfBlock)
}//for(i)
for (int k = 0; k < 80; k++) {
Local_Edge_Histogram[k] /= count_local[(int) k / 5];
}
}
/**
* In the <code>setEdgeHistogram()</code> method the value for each edgeHistogram bin are quantized by
* quantization tables. Five quantisation tables for five different edge types are existing.
*
* @return returns 80 bins.
*/
public int[] setEdgeHistogram() {
int Edge_HistogramElement[] = new int[80];
// int j = 0;
double iQuantValue = 0;
double value[] = Local_Edge_Histogram;
for (int i = 0; i < 80; i++) {
for (int j = 0; j < 8; j++) {
bins[i] = j;
if (j < 7)
iQuantValue = (EdgeHistogramImplementation.QuantTable[i % 5][j] + EdgeHistogramImplementation.QuantTable[i % 5][j + 1]) / 2.0;
else
iQuantValue = 1.0;
if (value[i] <= iQuantValue) {
break;
}
}
/*
// old version ...
int j = 0;
while (true) {
if (j < 7)
iQuantValue = (EdgeHistogramImplementation.QuantTable[i % 5][j] + EdgeHistogramImplementation.QuantTable[i % 5][j + 1]) / 2.0;
else
iQuantValue = 1.0;
if (value[i] <= iQuantValue)
break;
j++;
}
bins[i] = j;
*/
}
return bins;
}
/**
* Calculates the distance between two images taking the edge edgeHistogram into account. take 480 as soft upper
* border, 0 is hard lower border because there are 80 bins <= 7.
*
* @param edgeHistogramA defines the first point
* @param edgeHistogramB defines the second point
* @return the distance from [0, 480]
*/
public static float calculateDistance(int[] edgeHistogramA, int[] edgeHistogramB) {
if (edgeHistogramA == null) System.err.println("Input edgeHistogram a is null!");
if (edgeHistogramB == null) System.err.println("Input edgeHistogram b is null!");
float result = 0f;
// Todo: this first for loop should sum up the differences of the non quantized edges. Check if this code is right!
for (int i = 0; i < edgeHistogramA.length; i++) {
// first version is the un-quantisized version, according to the mpeg-7 docs part 8 this version is quite okay as though its nearly linear quantization
// result += Math.abs((float) edgeHistogramA[i] - (float) edgeHistogramB[i]);
result += Math.abs((float) EdgeHistogramImplementation.QuantTable[i % 5][edgeHistogramA[i]] - (float) EdgeHistogramImplementation.QuantTable[i % 5][edgeHistogramB[i]]);
}
for (int i = 0; i <= 4; i++) {
result += 5f * Math.abs((float) edgeHistogramA[i] - (float) edgeHistogramB[i]);
}
for (int i = 5; i < 80; i++) {
result += Math.abs((float) edgeHistogramA[i] - (float) edgeHistogramB[i]);
}
return result;
}
private static int[] RGB2YCRCB(int[] pixel, int[] result) {
double yy = (0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2]) / 256.0;
result[0] = (int) (219.0 * yy + 16.5);
result[1] = (int) (224.0 * 0.564 * (pixel[2] / 256.0 * 1.0 - yy) + 128.5);
result[2] = (int) (224.0 * 0.713 * (pixel[0] / 256.0 * 1.0 - yy) + 128.5);
return result;
}
private static int getYfromRGB(int rgb) {
int b = rgb & 255;
int g = rgb >> 8 & 255;
int r = rgb >> 16 & 255;
double yy = (0.299 * r + 0.587 * g + 0.114 * b) / 256.0;
return (int) (219.0 * yy + 16.5);
}
/**
* 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 EdgeHistogramImplementation)) return -1f;
EdgeHistogramImplementation e = (EdgeHistogramImplementation) descriptor;
return calculateDistance(e.edgeHistogram, edgeHistogram);
}
/**
* Creates a String representation from the descriptor.
*
* @return the descriptor as String.
*/
public String getStringRepresentation() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("edgehistogram;");
stringBuilder.append(edgeHistogram[0]);
for (int i = 1; i < edgeHistogram.length; i++) {
stringBuilder.append(' ');
stringBuilder.append(edgeHistogram[i]);
}
return stringBuilder.toString();
}
/**
* Sets the descriptor value from a String.
*
* @param descriptor the descriptor as String.
*/
public void setStringRepresentation(String descriptor) {
String[] parts = descriptor.split(";");
if (!parts[0].equals("edgehistogram")) {
throw new UnsupportedOperationException("This is no valid representation of a EdgeHistogram descriptor!");
}
int[] bins = new int[80];
StringTokenizer st = new StringTokenizer(parts[1], " ");
int count = 0;
while (st.hasMoreElements()) {
bins[count] = Integer.parseInt(st.nextToken());
count++;
}
edgeHistogram = bins;
}
}