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