package fr.unistra.pelican.algorithms.descriptors.localinvariants; import java.util.ArrayList; import fr.unistra.pelican.*; import fr.unistra.pelican.algorithms.conversion.AverageChannels; import fr.unistra.pelican.algorithms.detection.Harris; import fr.unistra.pelican.algorithms.io.ImageLoader; import fr.unistra.pelican.util.Keypoint; import fr.unistra.pelican.util.Point4D; import fr.unistra.pelican.util.data.*; /** * Chun-Rong Huanga, Chu-Song Chena and Pau-Choo Chung, * "Contrast context histogram - An efficient discriminating * local descriptor for object recognition and image matching" * * ( It's here: http://dx.doi.org/10.1016/j.patcog.2008.03.013 ) * * @author Régis Witz */ public class CCH extends Descriptor { //////////// // INPUTS // //////////// public Image input; ///////////// // OUTPUTS // ///////////// /** Output parameter. */ public KeypointArrayData output; ///////////// // OPTIONS // ///////////// /** Circular window radius, in pixels. */ public int radius = 30; public int distanceQuantization = 3; public int orientationQuantization = 8; ////////////////// // OTHER FIELDS // ////////////////// int rstep; double tethastep; /////////////// // CONSTANTS // /////////////// ///////////////// // CONSTRUCTOR // ///////////////// public CCH() { super.inputs = "input"; super.options = ""; super.outputs = "output"; } //////////////////// // "EXEC" METHODS // //////////////////// public static KeypointArrayData exec( Image input ) { return ( KeypointArrayData ) new CCH().process( input ); } ///////////////////// // "LAUNCH" METHOD // ///////////////////// @SuppressWarnings("unchecked") @Override public void launch() throws AlgorithmException { if ( this.input.getBDim() > 1 ) this.input = (Image) AverageChannels.exec( this.input ); ArrayList<Keypoint> pcs = Harris.exec( this.input ); this.rstep = new Double ( this.radius / (double)this.distanceQuantization ).intValue(); this.tethastep = 2*Math.PI / this.orientationQuantization; int roffset; double tethaoffset; HistogramData histo; HistogramData[] desc; for ( Keypoint key : pcs ) { desc = new HistogramData[ this.distanceQuantization*this.orientationQuantization ]; for ( int k = 0 ; k < this.distanceQuantization ; k++ ) { roffset = k*rstep; for ( int l = 0 ; l < this.orientationQuantization ; l++ ) { tethaoffset = l*tethastep; histo = this.computeRegion( roffset,tethaoffset, key ); desc[ k*this.orientationQuantization + l ] = ( HistogramData ) histo.clone(); } } DataArrayData data = new DataArrayData(); data.setDescriptor( ( Class ) this.getClass() ); data.setValues( desc ); key.data = data; } this.output = new KeypointArrayData(); this.output.setDescriptor( ( Class ) this.getClass() ); this.output.setValues( pcs ); } /////////////////// // OTHER METHODS // /////////////////// private HistogramData computeRegion( int roffset, double tethaoffset, Keypoint pc ) { ArrayList<Double> contrasts = new ArrayList<Double>(); Point4D p; for ( int r = roffset ; r < roffset+this.rstep ; r++ ) for ( double tetha = tethaoffset ; tetha < tethaoffset+this.tethastep ; tetha+=.2 ) { p = CCH.polar2cartesian( r,tetha ); contrasts.add( this.getCenterBasedContrast( p.x,p.y, pc ) ); } return this.getHistogram( contrasts ); } @SuppressWarnings( "unchecked" ) private HistogramData getHistogram( ArrayList<Double> contrasts ) { Double[] histo = { 0.,0. }; // contrast histograms int nbp = 0 ; // number of positive contrast values int nbn = 0; // number of negative contrast values for ( double val : contrasts ) { if ( val >= 0 ) { histo[0] += val; nbp++; } else { histo[1] += val; nbn++; } } // normalize if ( nbp > 0 ) histo[0] /= nbp; // positive contrast histogram if ( nbn > 0 ) histo[1] /= nbn; // negative contrast histogram assert( 0 <= histo[0] && histo[0] <= 1 && -1 <= histo[1] && histo[1] <= 0 && nbp+nbn == contrasts.size() ); HistogramData data = new HistogramData(); data.setDescriptor( ( Class ) this.getClass() ); data.setValues( histo ); // System.out.println( "CCH set histogram { "+histo[0]+";"+histo[1]+" }" ); // System.out.flush(); return data; } private double getCenterBasedContrast( int i, int j, Keypoint pc ) { if ( i < 0 || i > this.input.getXDim() || j < 0 || j > this.input.getYDim() ) return 0; double ip = this.input.getPixelXYDouble( i,j ); double ipc = this.input.getPixelXYDouble( new Double( pc.x ).intValue(), new Double( pc.y ).intValue() ); return ip - ipc; } private static Point4D polar2cartesian( double r, double tetha ) { int x = new Double( r * Math.cos( tetha ) ).intValue(); int y = new Double( r * Math.sin( tetha ) ).intValue(); return new Point4D( x,y ); } @SuppressWarnings( "unchecked" ) public static double distance( Data d1, Data d2 ) { ArrayList<Keypoint> values = ( ArrayList<Keypoint> ) ( ( KeypointArrayData ) d1 ).getValues(); ArrayList<Keypoint> values2 = ( ArrayList<Keypoint> ) ( ( KeypointArrayData ) d2 ).getValues(); int bins = values.get(0).getDescLength(); int bins2 = values2.get(0).getDescLength(); if( bins != bins2 ) { System.err.println( "Incompatible keypoint descriptors lengths !" ); return Double.MAX_VALUE; } double distance = 0, d, min; for ( Keypoint k1 : values ) { min = 1; for ( Keypoint k2 : values2 ) { d = CCH.match( k1,k2 ); if ( d < 1 && d < min ) min = d; } distance += min; } if ( bins > 0 ) distance /= bins; assert 0 <= distance && distance <= 1 : new CCH().getClass().getName() + " distance ¤[0;1] unverified : " + distance + "."; return distance; } public static double match( Keypoint k1, Keypoint k2 ) { // HistogramData[] desc1 = ( HistogramData[] ) k1.data.getValues(); // HistogramData[] desc2 = ( HistogramData[] ) k2.data .getValues(); Data[] desc1 = ( Data[] ) k1.data.getValues(); Data[] desc2 = ( Data[] ) k2.data .getValues(); int bins = desc1.length; if ( bins != desc2.length ) { System.err.println( "Incompatible keypoint descriptors lengths !" ); return 1.0; } Double[] h1; Double[] h2; double distance = 0; for ( int i = 0 ; i < bins ; i++ ) { h1 = ( Double[] ) desc1[i].getValues(); h2 = ( Double[] ) desc2[i].getValues(); assert( h1.length == 2 && h2.length == 2 && h1[0] >= 0 && h2[0] >= 0 && h1[1] <= 0 && h2[1] <= 0 ); distance += Math.abs( h1[0]-h2[0] ); distance += Math.abs( h1[1]-h2[1] ); } if ( bins > 0 ) distance /= bins; assert 0 <= distance && distance <= 1 : new CCH().getClass().getName() + " distance �[0;1] unverified : " + distance + "."; return distance; } public static void main( String[] args ) { Image img = ImageLoader.exec("samples/lenna256.png"); KeypointArrayData data1 = CCH.exec( img ); Image img2 = ImageLoader.exec("samples/cat with umbrella.png"); KeypointArrayData data2 = CCH.exec( img2 ); long t31 = System.currentTimeMillis(); double d = data1.distanceTo( data2 ); long t32 = System.currentTimeMillis(); System.out.println( "1vs2 matching time : " + ( t32-t31 )/1000.0 + " s." ); System.out.println( "distance : " + d + "." ); } }