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.ipd.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.detector.dog.extractor.AbstractDominantOrientationExtractor;
037import org.openimaj.image.feature.local.engine.ipd.InterestPointImageExtractorProperties;
038import org.openimaj.image.feature.local.extraction.FeatureVectorExtractor;
039import org.openimaj.image.feature.local.extraction.GradientScaleSpaceImageExtractorProperties;
040import org.openimaj.image.feature.local.extraction.ScaleSpaceImageExtractorProperties;
041import org.openimaj.image.feature.local.interest.InterestPointData;
042import org.openimaj.image.feature.local.interest.InterestPointDetector;
043import org.openimaj.image.processing.convolution.FImageGradients;
044
045
046/**
047 * <p>
048 * Class capable of extracting local descriptors from an interest point {@link InterestPointData} from an interest point detector {@link InterestPointDetector}. 
049 * The actual feature extracted is determined by the {@link GradientFeatureProvider} that
050 * is provided by the {@link GradientFeatureProviderFactory} set during
051 * construction.
052 * </p>
053 * <p>
054 * The GradientFeatureExtractor first calculates the dominant orientation
055 * of the image patch described by the {@link InterestPointImageExtractorProperties}
056 * and then iterates over the pixels in an oriented square, centered on the
057 * interest point, passing the gradient and magnitude values of the respective
058 * pixel to the {@link GradientFeatureProvider}.
059 * </p>
060 * <p>
061 * The size of the sampling square is exactly equal to the patch in the properties, this is in turn controlled
062 * by the interest point's scale and possibly its shape. For some types of feature provider, this number
063 * might need to be set based on the internal settings of the provider. 
064 * </p>
065 * 
066 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
067 *
068 */
069public class InterestPointGradientFeatureExtractor implements FeatureVectorExtractor<OrientedFeatureVector, InterestPointImageExtractorProperties<Float,FImage>> {
070        private static final Float INVALID_PIXEL_VALUE = Float.NaN;
071
072        AbstractDominantOrientationExtractor dominantOrientationExtractor;
073        
074        GradientFeatureProviderFactory factory;
075        
076        private GradientScaleSpaceImageExtractorProperties<FImage> currentGradientProperties = new GradientScaleSpaceImageExtractorProperties<FImage>();
077        
078        /**
079         * @param dominantOrientationExtractor how dominant orientations are located
080         * @param factory object used to construct {@link GradientFeatureProvider} instances which in turn
081         * construction the actual features
082         */
083        public InterestPointGradientFeatureExtractor(AbstractDominantOrientationExtractor dominantOrientationExtractor, GradientFeatureProviderFactory factory) {
084                this.dominantOrientationExtractor = dominantOrientationExtractor;
085                this.factory = factory;
086        }
087        
088        /**
089         * Get the GradientScaleSpaceImageExtractorProperties for the given properties.
090         * The returned properties are the same as the input properties, but with the
091         * gradient images added. 
092         * 
093         * For efficiency, this method always returns the same cached GradientScaleSpaceImageExtractorProperties,
094         * and internally updates this as necessary. The gradient images are only recalculated
095         * when the input image from the input properties is different to the cached one.
096         * 
097         * @param properties input properties
098         * @return cached GradientScaleSpaceImageExtractorProperties 
099         */
100        public GradientScaleSpaceImageExtractorProperties<FImage> getCurrentGradientProps(ScaleSpaceImageExtractorProperties<FImage> properties) {
101                if (properties.image != currentGradientProperties.image) {
102                        currentGradientProperties.image = properties.image;
103
104                        //only if the size of the image has changed do we need to reset the gradient and orientation images. 
105                        if (currentGradientProperties.orientation == null || 
106                                        currentGradientProperties.orientation.height != currentGradientProperties.image.height || 
107                                        currentGradientProperties.orientation.width != currentGradientProperties.image.width) { 
108                                currentGradientProperties.orientation = new FImage(currentGradientProperties.image.width, currentGradientProperties.image.height);
109                                currentGradientProperties.magnitude = new FImage(currentGradientProperties.image.width, currentGradientProperties.image.height);                                
110                        }
111
112                        FImageGradients.gradientMagnitudesAndOrientations(currentGradientProperties.image, currentGradientProperties.magnitude, currentGradientProperties.orientation);
113                }
114                
115                currentGradientProperties.x = properties.x;
116                currentGradientProperties.y = properties.y;
117                currentGradientProperties.scale = properties.scale;
118                
119                return currentGradientProperties;
120        }
121
122        @Override
123        public OrientedFeatureVector[] extractFeature(InterestPointImageExtractorProperties<Float,FImage> properties) {
124                GradientScaleSpaceImageExtractorProperties<FImage> gprops = getCurrentGradientProps(properties);
125                
126                float [] dominantOrientations = dominantOrientationExtractor.extractFeatureRaw(gprops);
127
128                OrientedFeatureVector[] ret = new OrientedFeatureVector[dominantOrientations.length];
129
130                for (int i=0; i<dominantOrientations.length; i++) {
131                        ret[i] = createFeature(properties, dominantOrientations[i]);
132                }
133
134                return ret;
135        }
136
137        /*
138         * Iterate over the pixels in a sampling patch provided in the properties instance
139         * and pass the information to a feature provider that will extract the relevant
140         * feature vector.
141         */
142        protected OrientedFeatureVector createFeature(InterestPointImageExtractorProperties<Float,FImage> properties, float orientation) {
143                GradientFeatureProvider provider = factory.newProvider();
144                provider.setPatchOrientation(orientation);
145                
146                //pass over all the pixels in the subimage, they are the sampling area
147                for (int y = 0; y < properties.featureWindowSize; y++) {
148                        for (int x = 0; x < properties.featureWindowSize; x++) {
149                                
150                                //check if the pixel is in the image bounds; if not ignore it
151                                if (properties.image.pixels[y][x] != INVALID_PIXEL_VALUE) {
152                                        //calculate the actual position of the sample in the patch coordinate system
153                                        float sx = (0.5f + x) / properties.featureWindowSize;
154                                        float sy = (0.5f + y )/ properties.featureWindowSize;
155                                        
156                                        provider.addSample(sx, sy, 
157                                                currentGradientProperties.magnitude.pixels[y][x], 
158                                                currentGradientProperties.orientation.pixels[y][x]
159                                        );
160                                }
161                        }
162                }
163
164                return provider.getFeatureVector();
165        }
166}