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}