001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.image.connectedcomponent.proc; 031 032import java.util.List; 033 034import org.openimaj.feature.DoubleFV; 035import org.openimaj.feature.FeatureVectorProvider; 036import org.openimaj.image.pixel.ConnectedComponent; 037import org.openimaj.image.pixel.ConnectedComponent.ConnectMode; 038import org.openimaj.image.pixel.Pixel; 039import org.openimaj.image.processor.connectedcomponent.ConnectedComponentProcessor; 040import org.openimaj.math.util.Interpolation; 041 042/** 043 * Distance-from-centroid descriptor for convex shapes. Sweeps the 044 * edge of the shape over all angles in 0..360 and records the distance 045 * from the centroid. 046 * 047 * Scale invariance is optionally achieved by normalising the 048 * resultant vector to sum to 1. 049 * 050 * Rotation invariance is optionally achieved by measuring angles 051 * from the dominant orientation of the connected component. 052 * 053 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 054 * 055 */ 056public class BoundaryDistanceDescriptor implements ConnectedComponentProcessor, FeatureVectorProvider<DoubleFV> { 057 /** 058 * The number of samples 059 */ 060 public final static int DESCRIPTOR_LENGTH = 360; 061 062 /** 063 * The descriptor vector, measusring distance from centroid per degree 064 */ 065 public double [] descriptor = new double[DESCRIPTOR_LENGTH]; 066 protected boolean normaliseScale; 067 protected boolean normaliseAngle; 068 069 /** 070 * Construct the BoundaryDistanceDescriptor with both scale and 071 * orientation normalisation enabled 072 */ 073 public BoundaryDistanceDescriptor() { 074 this(true, true); 075 } 076 077 /** 078 * Construct the BoundaryDistanceDescriptor with optional scale and 079 * orientation invariance. 080 * @param normaliseDistance enable scale invariance 081 * @param normaliseAngle enable rotation invariance 082 */ 083 public BoundaryDistanceDescriptor(boolean normaliseDistance, boolean normaliseAngle) { 084 this.normaliseScale = normaliseDistance; 085 this.normaliseAngle = normaliseAngle; 086 } 087 088 @Override 089 public void process(ConnectedComponent cc) { 090 cc = new ConnectedComponent(cc.calculateConvexHull()); //make shape convex 091 092 List<Pixel> bound = cc.getInnerBoundary(ConnectMode.CONNECT_8); 093 double [] centroid = cc.calculateCentroid(); 094 double direction = cc.calculateDirection(); 095 096 float[] distances = new float[bound.size()]; 097 float[] angle = new float[bound.size()]; 098 int count = 0; 099 100 for (int i=0; i<bound.size(); i++) { 101 Pixel p = bound.get(i); 102 double o = p.y - centroid[1]; 103 double a = p.x - centroid[0]; 104 105 float dist = (float) Math.sqrt((a*a) + (o*o)); 106 distances[i] = dist; 107 108 if (normaliseAngle) { 109 angle[i] = (float) (direction - Math.atan2(o, a)); 110 } else { 111 angle[i] = (float) (Math.atan2(o, a)); 112 } 113 114 angle[i] = (float) ((angle[i] %= 2.0*Math.PI) >= 0 ? angle[i] : (angle[i] + 2.0*Math.PI)); 115 angle[i] = (float) (360.0 * angle[i] / (2.0*Math.PI)); 116 } 117 118 for (int i=0; i<DESCRIPTOR_LENGTH; i++) { 119 int index1 = -1; 120 int index2 = -1; 121 122 for (int j=0; j<angle.length; j++) { 123 int n = ((j+1 == angle.length) ? 0 : j+1); 124 125 float aj = angle[j]; 126 float an = angle[n]; 127 128 if (an > 350 && aj < 10) if (i<10) an-=360; else aj+=360; 129 if (aj > 350 && an < 10) if (i<10) aj-=360; else an+=360; 130 131 if (aj==i) { 132 index1 = j; 133 index2 = j; 134 break; 135 } else if (aj<an) { 136 if (i <= an && i > aj) { 137 index1 = j; 138 index2 = n; 139 break; 140 } 141 } else { 142 if (i <= aj && i > an) { 143 index1 = j; 144 index2 = n; 145 break; 146 } 147 } 148 } 149 150 151 descriptor[i] = Interpolation.lerp(i, angle[index1], distances[index1], angle[index2], distances[index2]); 152 count += descriptor[i]; 153 } 154 155 if (normaliseScale) { 156 for (int i=0; i<DESCRIPTOR_LENGTH; i++) 157 descriptor[i] /= count; 158 } 159 } 160 161 /** 162 * Get the feature vector as a double array 163 * @return the feature vector 164 */ 165 public double[] getFeatureVectorArray() { 166 return descriptor; 167 } 168 169 @Override 170 public DoubleFV getFeatureVector() { 171 return new DoubleFV(getFeatureVectorArray()); 172 } 173}