package fr.unistra.pelican.algorithms.descriptors.texture;
import fr.unistra.pelican.*;
import fr.unistra.pelican.algorithms.geometric.Subdivide;
import fr.unistra.pelican.util.data.*;
/**
* Edge histogram descriptor (EHD) from MPEG-7 standard.
*
* Chee Sun Won, Dong Kwon Park, and Soo-Jun Park,
* "Efficient Use of MPEG-7 Edge Histogram Descriptor".
*
* ( Get it there :
* <url>http://etrij.etri.re.kr/Cyber/servlet/GetFile?fileid=SPF-1041924741673</url> )
*
* @author Régis Witz
* @date 8.4.2009
*/
public class EdgeHistogram extends Descriptor {
////////////
// FIELDS //
////////////
/** Input image. */
public Image input;
/** Output feature. */
public DataArrayData output;
/** With {@link #size} = 2 you respect MPEG-7, wich says
* the size of EHD feature must be 4x4x5 = 80.
*/
public int size = 2;
/** If an image block pixel values x <i>filter</i> > {@link #tEdge},
* this image block corresponding to <i>filter</i>.
*/
public double tEdge = 0.01;
/** If an image block is composed of pixel values different for less than {@link #tMonoton},
* this block is considered as part of a monoton region (no edge).
*/
public double tMonoton = 0.01;
/////////////////
// CONSTRUCTOR //
/////////////////
/** Algorithm specifications. */
public EdgeHistogram() {
super();
super.inputs = "input";
super.options = "tEdge,tMonoton,size";
super.outputs = "output";
}
///////////////
// CONSTANTS //
///////////////
/** Used in vertical edge detection operations. */
private static final int VERTICAL_EDGE = 0;
/** Used in horizontal edge detection operations. */
private static final int HORIZONTAL_EDGE = 1;
/** Used in up-right/bottom-left diagonal edge detection operations. */
private static final int DIAGONAL_45_EDGE = 2;
/** Used in up-left/bottom-right diagonal edge detection operations. */
private static final int DIAGONAL_135_EDGE = 3;
/** Used in non directional edge detection operations. */
private static final int NON_DIRECTIONAL_EDGE = 4;
/** Square root of 2. */
private static final double V2 = Math.sqrt(2.);
/** Filters allowing to judge of the edgeness of an image block. */
private static final double[][] filters =
{
// vertical filter :
{ 1,-1,
1,-1 }
,
// horizontal filter :
{ 1, 1,
-1,-1 }
,
// 45-diagonal filter :
{ V2, 0,
0,V2 }
,
// 135-diagonal filter :
{ 0,V2,
V2, 0 }
,
// non directional filter :
{ 2,-2,
-2, 2 }
,
};
/** Number of bins of a sub image histogram. */
private static final int nbBins = filters.length;
/////////////
// METHODS //
/////////////
@SuppressWarnings("unchecked")
@Override
/** @see Algorithm */
public void launch() throws AlgorithmException {
if ( this.size < 2 ) throw new PelicanException( "Size must be at least 2, sorry." );
this.size = new Double( Math.pow( 2.,this.size ) ).intValue();
int nbSubimages = this.size*this.size;
HistogramData[] histograms = new HistogramData[ nbSubimages ];
Image[] subimages = new Image[1];
subimages[0] = this.input;
for ( int i = 1 ; i <= this.size ; i++ ) {
Image[] tmp = new Image[ 4*subimages.length ];
for ( int j = 0 ; j < subimages.length ; j++ ) {
Image[] inputs = Subdivide.exec( subimages[j], Subdivide.Uniform2x2 );
for ( int k = 0 ; k < 4 ; k++ ) tmp[ 4*j+k ] = inputs[k];
inputs = null;
}
subimages = tmp;
tmp = null;
}
Image[] imageblocks;
for ( int i = 0 ; i < this.size ; i++ ) {
for ( int j = 0 ; j < this.size ; j++ ) {
imageblocks = Subdivide.exec( subimages[ ( i*this.size )+j ], Subdivide.Lil22 ) ;
// initialize then fill the local histogram corresponding to this subimage
Double[] bins = new Double[ nbBins ];
for ( int bin = 0 ; bin < nbBins ; bin++ ) bins[bin] = new Double( 0 );
for ( int b = 0 ; b < imageblocks.length ; b++ )
this.edgeClassification( imageblocks[b], bins );
// normalize histogram.
//
if ( imageblocks.length > 0 )
for ( int bin = 0 ; bin < nbBins ; bin++ ) bins[bin] /= imageblocks.length;
// TODO: here, histogram should not be filled with bins directly. instead, each
// bin of bins should be quantized to be a 3-bits number.
// see the paper for more details.
// as HistogramData contains doubles, an other data structure should be used.
HistogramData histogram = new HistogramData();
histogram.setDescriptor( ( Class ) EdgeHistogram.class );
histogram.setValues( bins );
histograms[ i*this.size + j ] = histogram;
// clear trash
for ( int b = 0 ; b < imageblocks.length ; b++ ) imageblocks[b] = null;
imageblocks = null;
}
}
this.output = new DataArrayData();
this.output.setDescriptor( ( Class ) EdgeHistogram.class );
this.output.setValues( histograms );
}
/** Tries to find if an image block depicts an edge. If so, increments the appropriate bin
* value of the local histogram. If no edge was found, this method does nothing.
* <p>
* Please note that this implies that, at the very end of this algorithm, each 5-bin histogram
* length is not always equal to 1, but is in fact less than or equal to 1. This allows to
* consider information regarding non-edge distribution ( <i>ie.</i> smoothness ) in EHD.
*
* @param block 2x2 image block.
* @param bins Local histogram for a subimage. It must have exactly 5 bins.
*/
private void edgeClassification( Image block, Double[] bins ) {
if ( isEdge( block,VERTICAL_EDGE ) ) bins[ VERTICAL_EDGE ]++;
else if ( isEdge( block,HORIZONTAL_EDGE ) ) bins[ HORIZONTAL_EDGE ]++;
else if ( isEdge( block,DIAGONAL_45_EDGE ) ) bins[ DIAGONAL_45_EDGE ]++;
else if ( isEdge( block,DIAGONAL_135_EDGE ) ) bins[ DIAGONAL_135_EDGE ]++;
else if ( isMonoton( block ) ) ; // this is why histo length can be < 1.
else bins[ NON_DIRECTIONAL_EDGE ]++; // yes, filters[NON_DIRECTIONAL_EDGE]'s not used
// this is dificult to define such filter ...
}
/** Attempts to find if an image block <tt>block</tt> is an edge of type <tt>type</tt>.
* @param block 2x2 image block.
* @param type [ {@link #VERTICAL_EDGE} | {@link #HORIZONTAL_EDGE}
* | {@link #DIAGONAL_45_EDGE} | {@link #DIAGONAL_135_EDGE}
* | {@link #NON_DIRECTIONAL_EDGE} ]
* @return <tt>true</tt> if <tt>block</tt> is an edge of type <tt>type</tt>,
* or else <tt>falsek</tt>.
*/
private boolean isEdge( Image block,int type ) {
double[] filter = filters[ type ];
double res = block.getPixelXYDouble( 0,0 ) * filter[0]
* block.getPixelXYDouble( 1,0 ) * filter[1]
* block.getPixelXYDouble( 0,1 ) * filter[2]
* block.getPixelXYDouble( 1,1 ) * filter[3]
;
return res > this.tEdge;
}
private boolean isMonoton( Image block ) {
double p00 = block.getPixelXYDouble( 0,0 );
double p10 = block.getPixelXYDouble( 1,0 );
double p01 = block.getPixelXYDouble( 0,1 );
double p11 = block.getPixelXYDouble( 1,1 );
if ( !okay( p00,p10 ) ) return false;
if ( !okay( p00,p01 ) ) return false;
if ( !okay( p00,p11 ) ) return false;
if ( !okay( p10,p01 ) ) return false;
if ( !okay( p10,p11 ) ) return false;
if ( !okay( p01,p11 ) ) return false;
return true;
}
private boolean okay( double p1, double p2 ) {
double min = Math.min( p1,p2 );
double max = Math.max( p1,p2 );
if ( min + tMonoton < max ) return false;
return true;
}
//////////////////////
// DISTANCE METHODS //
//////////////////////
/** Distance between two given precomputed {@link Data},
* customized for this descriptor.
*
* @param d1 First data
* @param d2 Second data
* @return Distance €[0;1] between <tt>d1</tt> and <tt>d2</tt>.
*/
public static double distance( Data d1, Data d2 ) {
Data[] h1 = ( Data[] ) ( ( DataArrayData )d1 ).getValues();
Data[] h2 = ( Data[] ) ( ( DataArrayData )d2 ).getValues();
int len = h1.length;
if ( len != h2.length ) {
System.err.println( "Incompatible histogram numbers : "+len+" vs "+h2.length +"." );
return 1.;
}
double[] g1 = new double[ nbBins ]; // global histogram corresponding to d1
double[] g2 = new double[ nbBins ]; // global histogram corresponding to d2
// distances. the latters are non standard adds of the paper.
double local = 0;
double global = 0;
double semiglobal = 0;
for ( int h = 0 ; h < len ; h++ ) {
local += subimagesDistance( h1[h],h2[h] );
Double[] b1 = ( Double[] ) h1[h].getValues();
Double[] b2 = ( Double[] ) h2[h].getValues();
for ( int b = 0 ; b < nbBins ; b++ ) {
// fill global histograms
g1[b] += b1[b];
g2[b] += b2[b];
}
}
// compute global distance
for ( int b = 0 ; b < nbBins ; b++ )
global += Math.abs( g1[b]-g2[b] );
global *= nbBins;
int side = new Double( Math.sqrt( len ) ).intValue();
// if you have any doubts, remember how {@link #size} was computed ...
double semiglobalverti = 0; // semi global distances 1,2,3,4
double semiglobalhoriz = 0; // semi global distances 5,6,7,8
for ( int i = 0 ; i < side ; i++ ) {
for ( int j = 0 ; j < side ; j++ ) {
semiglobalverti += subimagesDistance( h1[ j*side+i ],h2[ j*side+i ] );
semiglobalhoriz += subimagesDistance( h1[ i*side+j ],h2[ i*side+j ] );
}
}
// propagate these semiglobal distances
semiglobal += semiglobalverti;
semiglobal += semiglobalhoriz;
// this was tiring to generalize this one T_T
// I didn't know if java compiler is clever, had to extract loop invarants ..
int _s = side/2;
int o10 = _s; // offset for semiglobal distance 10
int o11 = _s*side; // offset for semiglobal distance 11
int o12 = _s*side+_s; // offset for semiglobal distance 12
int o13 = (_s/2)*side+_s; // offset for semiglobal distance 13
int o;
for ( int i = 0 ; i < _s ; i++ ) {
for ( int j = 0 ; j < _s ; j++ ) {
// partly compute each semi global distance of 9,10,11,12,13
o = _s*i+j;
semiglobal += subimagesDistance( h1[ o ],h2[ o ] );
semiglobal += subimagesDistance( h1[ o+o10 ],h2[ o+o10 ] );
semiglobal += subimagesDistance( h1[ o+o11 ],h2[ o+o11 ] );
semiglobal += subimagesDistance( h1[ o+o12 ],h2[ o+o12 ] );
semiglobal += subimagesDistance( h1[ o+o13 ],h2[ o+o13 ] );
} }
// total normalized distance..
double distance = ( local + global + semiglobal ) / ( len*8 + side*5 );
return distance;
}
/** Pretty much a {@link HistogramData#distance} method, less secured but faster.
* @param h1 One subimage.
* @param h2 Another subimage.
* @return Distance €[0;1] between the two subimages <tt>h1</tt> and <tt>h2</tt>.
*/
private static double subimagesDistance( Data h1, Data h2 ) {
Double[] b1 = ( Double[] ) h1.getValues();
Double[] b2 = ( Double[] ) h2.getValues();
double distance = 0;
for ( int b = 0 ; b < nbBins ; b++ )
distance += Math.abs( b1[b]-b2[b] );
return distance;
}
////////////////////
// "EXEC" METHODS //
////////////////////
public static DataArrayData exec( Image input ) {
return ( DataArrayData ) new EdgeHistogram().process( input );
}
public static DataArrayData exec( Image input, double tEdge, double tMonoton ) {
return ( DataArrayData ) new EdgeHistogram().process( input,tEdge,tMonoton );
}
public static DataArrayData exec( Image input, int size ) {
return ( DataArrayData ) new EdgeHistogram().process( input,size );
}
public static DataArrayData exec( Image input, double tEdge, double tMonoton, int size ) {
return ( DataArrayData ) new EdgeHistogram().process( input,tEdge,tMonoton,size );
}
}