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