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}