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.feature.local.detector.dog.extractor; 031 032import org.openimaj.feature.OrientedFeatureVector; 033import org.openimaj.image.FImage; 034import org.openimaj.image.MBFImage; 035import org.openimaj.image.feature.local.descriptor.gradient.GradientFeatureProvider; 036import org.openimaj.image.feature.local.descriptor.gradient.GradientFeatureProviderFactory; 037import org.openimaj.image.feature.local.descriptor.gradient.SIFTFeatureProvider; 038import org.openimaj.image.feature.local.extraction.GradientScaleSpaceImageExtractorProperties; 039import org.openimaj.image.feature.local.extraction.ScaleSpaceImageExtractorProperties; 040import org.openimaj.image.processing.convolution.FImageGradients; 041 042/** 043 * <p> 044 * Class capable of extracting local descriptors from a circular region in an 045 * image defined by its scale and centre. The actual feature extracted is 046 * determined by the {@link GradientFeatureProvider} that is provided by the 047 * {@link GradientFeatureProviderFactory} set during construction. 048 * </p> 049 * <p> 050 * The GradientFeatureExtractor first calculates the dominant orientation of the 051 * image patch described by the {@link ScaleSpaceImageExtractorProperties} and 052 * then iterates over the pixels in an oriented square, centered on the interest 053 * point, passing the gradient and magnitude values of the respective pixel to 054 * the {@link GradientFeatureProvider}. 055 * </p> 056 * <p> 057 * The size of the sampling square, relative to scale is set by a single 058 * parameter, magnification. For some types of feature provider, this number 059 * might need to be set based on the internal settings of the provider. For 060 * example, with a {@link SIFTFeatureProvider} this will probably be set to a 061 * constant multiplied by the number of spatial bins of the feature. For SIFT, 062 * this constant is typically around 3, so with a standard 4-spatial binned SIFT 063 * provider, the magnification factor of the extractor should be about 12. 064 * </p> 065 * 066 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 067 * 068 */ 069public class ColourGradientFeatureExtractor implements ScaleSpaceFeatureExtractor<OrientedFeatureVector, MBFImage> { 070 AbstractDominantOrientationExtractor dominantOrientationExtractor; 071 072 GradientFeatureProviderFactory factory; 073 074 private GradientScaleSpaceImageExtractorProperties<FImage> currentGradientProperties = new GradientScaleSpaceImageExtractorProperties<FImage>(); 075 076 protected MBFImage image; 077 protected FImage[] magnitudes; 078 protected FImage[] orientations; 079 080 /** 081 * The magnification factor determining the size of the sampling region 082 * relative to the scale of the interest point. 083 */ 084 protected float magnification = 12; 085 086 /** 087 * Construct with the given orientation extractor and gradient feature 088 * provider. The default magnification factor of 12 is used. 089 * 090 * @param dominantOrientationExtractor 091 * the orientation extractor 092 * @param factory 093 * the gradient feature provider 094 */ 095 public ColourGradientFeatureExtractor(AbstractDominantOrientationExtractor dominantOrientationExtractor, 096 GradientFeatureProviderFactory factory) 097 { 098 this.dominantOrientationExtractor = dominantOrientationExtractor; 099 this.factory = factory; 100 } 101 102 /** 103 * Construct with the given orientation extractor, gradient feature provider 104 * and magnification factor determining the size of the sampling region 105 * relative to the scale of the interest point. 106 * 107 * @param dominantOrientationExtractor 108 * the orientation extractor 109 * @param factory 110 * the gradient feature provider 111 * @param magnification 112 * the magnification factor. 113 */ 114 public ColourGradientFeatureExtractor(AbstractDominantOrientationExtractor dominantOrientationExtractor, 115 GradientFeatureProviderFactory factory, float magnification) 116 { 117 this(dominantOrientationExtractor, factory); 118 this.magnification = magnification; 119 } 120 121 @Override 122 public OrientedFeatureVector[] extractFeature(ScaleSpaceImageExtractorProperties<MBFImage> properties) { 123 final GradientScaleSpaceImageExtractorProperties<FImage> gprops = getCurrentGradientProps(properties); 124 125 final float[] dominantOrientations = dominantOrientationExtractor.extractFeatureRaw(gprops); 126 127 final OrientedFeatureVector[] ret = new OrientedFeatureVector[dominantOrientations.length]; 128 129 for (int i = 0; i < dominantOrientations.length; i++) { 130 ret[i] = createFeature(dominantOrientations[i]); 131 } 132 133 return ret; 134 } 135 136 /** 137 * Get the GradientScaleSpaceImageExtractorProperties for the given 138 * properties. The returned properties are the same as the input properties, 139 * but with the gradient images added. 140 * 141 * For efficiency, this method always returns the same cached 142 * GradientScaleSpaceImageExtractorProperties, and internally updates this 143 * as necessary. The gradient images are only recalculated when the input 144 * image from the input properties is different to the cached one. 145 * 146 * @param properties 147 * input properties 148 * @return cached GradientScaleSpaceImageExtractorProperties 149 */ 150 public GradientScaleSpaceImageExtractorProperties<FImage> getCurrentGradientProps( 151 ScaleSpaceImageExtractorProperties<MBFImage> properties) 152 { 153 if (properties.image != image) { 154 image = properties.image; 155 currentGradientProperties.image = image.bands.get(0); 156 157 // only if the size of the image has changed do we need to reset the 158 // gradient and orientation images. 159 if (currentGradientProperties.orientation == null || 160 currentGradientProperties.orientation.height != currentGradientProperties.image.height || 161 currentGradientProperties.orientation.width != currentGradientProperties.image.width) 162 { 163 currentGradientProperties.orientation = new FImage(currentGradientProperties.image.width, 164 currentGradientProperties.image.height); 165 currentGradientProperties.magnitude = new FImage(currentGradientProperties.image.width, 166 currentGradientProperties.image.height); 167 168 if (magnitudes == null) { 169 magnitudes = new FImage[image.bands.size() - 1]; 170 orientations = new FImage[image.bands.size() - 1]; 171 } 172 173 for (int i = 0; i < magnitudes.length; i++) { 174 magnitudes[i] = new FImage(currentGradientProperties.image.width, 175 currentGradientProperties.image.height); 176 orientations[i] = new FImage(currentGradientProperties.image.width, 177 currentGradientProperties.image.height); 178 } 179 } 180 181 FImageGradients.gradientMagnitudesAndOrientations(currentGradientProperties.image, 182 currentGradientProperties.magnitude, currentGradientProperties.orientation); 183 184 for (int i = 0; i < magnitudes.length; i++) { 185 FImageGradients.gradientMagnitudesAndOrientations(image.getBand(i + 1), magnitudes[i], orientations[i]); 186 } 187 } 188 189 currentGradientProperties.x = properties.x; 190 currentGradientProperties.y = properties.y; 191 currentGradientProperties.scale = properties.scale; 192 193 return currentGradientProperties; 194 } 195 196 /* 197 * Iterate over the pixels in a sampling patch around the given feature 198 * coordinates and pass the information to a feature provider that will 199 * extract the relevant feature vector. 200 */ 201 protected OrientedFeatureVector createFeature(final float orientation) { 202 final float fx = currentGradientProperties.x; 203 final float fy = currentGradientProperties.y; 204 final float scale = currentGradientProperties.scale; 205 206 // create a new feature provider and initialise it with the dominant 207 // orientation 208 final GradientFeatureProvider[] sfe = new GradientFeatureProvider[magnitudes.length]; 209 for (int i = 0; i < magnitudes.length; i++) { 210 sfe[i] = factory.newProvider(); 211 sfe[i].setPatchOrientation(orientation); 212 } 213 214 // the integer coordinates of the patch 215 final int ix = Math.round(fx); 216 final int iy = Math.round(fy); 217 218 final float sin = (float) Math.sin(orientation); 219 final float cos = (float) Math.cos(orientation); 220 221 // get the amount of extra sampling outside the unit square requested by 222 // the feature 223 final float oversampling = sfe[0].getOversamplingAmount(); 224 225 // this is the size of the unit bounding box of the patch in the image 226 // in pixels 227 final float boundingBoxSize = magnification * scale; 228 229 // the amount of extra sampling per side in pixels 230 final float extraSampling = oversampling * boundingBoxSize; 231 232 // the actual sampling area is bigger than the boundingBoxSize by an 233 // extraSampling on each side 234 final float samplingBoxSize = extraSampling + boundingBoxSize + extraSampling; 235 236 // In the image, the box (with sides parallel to the image frame) that 237 // contains the 238 // sampling box is: 239 final float orientedSamplingBoxSize = Math.abs(sin * samplingBoxSize) + Math.abs(cos * samplingBoxSize); 240 241 // now half the size and round to an int so we can iterate 242 final int orientedSamplingBoxHalfSize = Math.round(orientedSamplingBoxSize / 2.0f); 243 244 // get the images and their size 245 final int width = magnitudes[0].width; 246 final int height = magnitudes[0].height; 247 248 // now pass over all the pixels in the image that *might* contribute to 249 // the sampling area 250 for (int y = -orientedSamplingBoxHalfSize; y <= orientedSamplingBoxHalfSize; y++) { 251 for (int x = -orientedSamplingBoxHalfSize; x <= orientedSamplingBoxHalfSize; x++) { 252 final int px = x + ix; 253 final int py = y + iy; 254 255 // check if the pixel is in the image bounds; if not ignore it 256 if (px >= 0 && px < width && py >= 0 && py < height) { 257 // calculate the actual position of the sample in the patch 258 // coordinate system 259 final float sx = 0.5f + ((-sin * y + cos * x) - (fx - ix)) / boundingBoxSize; 260 final float sy = 0.5f + ((cos * y + sin * x) - (fy - iy)) / boundingBoxSize; 261 262 // if the pixel is in the bounds of the sampling area then 263 // add it 264 if (sx > -oversampling && sx < 1 + oversampling && sy > -oversampling && sy < 1 + oversampling) { 265 for (int i = 0; i < magnitudes.length; i++) { 266 sfe[i].addSample(sx, sy, magnitudes[i].pixels[py][px], orientations[i].pixels[py][px]); 267 } 268 } 269 } 270 } 271 } 272 273 final OrientedFeatureVector first = sfe[0].getFeatureVector(); 274 final OrientedFeatureVector fv = new OrientedFeatureVector(sfe.length * first.length(), orientation); 275 System.arraycopy(first.values, 0, fv.values, 0, first.values.length); 276 277 for (int i = 1; i < magnitudes.length; i++) { 278 System.arraycopy(sfe[i].getFeatureVector().values, 0, fv.values, i * first.values.length, first.values.length); 279 } 280 281 return fv; 282 } 283}