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}