View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package org.openimaj.image.feature.dense.gradient.dsift;
31  
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  import org.openimaj.feature.local.list.LocalFeatureList;
36  import org.openimaj.feature.local.list.MemoryLocalFeatureList;
37  import org.openimaj.image.FImage;
38  import org.openimaj.image.Image;
39  import org.openimaj.image.processing.convolution.FGaussianConvolve;
40  import org.openimaj.image.processor.SinglebandImageProcessor;
41  import org.openimaj.math.geometry.shape.Rectangle;
42  import org.openimaj.util.array.ArrayUtils;
43  import org.openimaj.util.pair.IntObjectPair;
44  
45  /**
46   * A scale-space pyramid of dense SIFT for {@link FImage}s. Dense sift features
47   * are extracted for the given bin sizes (scales). The image is optionally
48   * smoothed with a Gaussian before each scale.
49   * <p>
50   * The {@link PyramidDenseSIFT} is not thread safe, but is reusable like the
51   * {@link DenseSIFT} analyser.
52   * 
53   * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
54   * @param <IMAGE>
55   *            Type of image to be processed.
56   * 
57   */
58  public class PyramidDenseSIFT<IMAGE extends Image<?, IMAGE> & SinglebandImageProcessor.Processable<Float, FImage, IMAGE>>
59  		extends
60  		AbstractDenseSIFT<IMAGE>
61  {
62  	/**
63  	 * Scales at which the dense SIFT features are extracted. Each value is used
64  	 * as bin size for the {@link DenseSIFT}.
65  	 */
66  	int[] sizes;
67  
68  	/**
69  	 * The image is smoothed by a Gaussian kernel of standard deviation size /
70  	 * magnificationFactor.
71  	 */
72  	float magnificationFactor;
73  
74  	private List<AbstractDenseSIFT<IMAGE>> levels;
75  
76  	/**
77  	 * Construct the pyramid dense sift extractor. The magnification factor is
78  	 * used to determine how to smooth the image before extracting the features
79  	 * at each level: the smoothing sigma at each level is the bin size at that
80  	 * level divided by the magnification factor. If the magnification factor is
81  	 * 0, then no smoothing will be applied at any level.
82  	 * 
83  	 * @param dsift
84  	 *            the underlying dense sift extractor (typically a
85  	 *            {@link DenseSIFT} (or {@link ApproximateDenseSIFT}) or
86  	 *            {@link ColourDenseSIFT} depending on the image type).
87  	 * @param magFactor
88  	 *            the magnification factor
89  	 * @param sizes
90  	 *            the scales (bin sizes for dense sift)
91  	 */
92  	public PyramidDenseSIFT(AbstractDenseSIFT<IMAGE> dsift, float magFactor, int... sizes) {
93  		this.sizes = sizes;
94  		this.magnificationFactor = magFactor;
95  
96  		levels = new ArrayList<AbstractDenseSIFT<IMAGE>>(sizes.length);
97  		for (int i = 0; i < sizes.length; i++) {
98  			levels.add(dsift.clone());
99  		}
100 	}
101 
102 	@Override
103 	public void analyseImage(IMAGE image, Rectangle originalBounds) {
104 		final Rectangle bounds = originalBounds;
105 
106 		for (int i = 0; i < sizes.length; i++) {
107 			final int size = sizes[i];
108 			final int offset = (int) Math.floor(3f / 2f * (ArrayUtils.maxValue(sizes) - size));
109 
110 			final IMAGE smoothed;
111 			if (magnificationFactor == 0) {
112 				smoothed = image;
113 			} else {
114 				final float sigma = size / magnificationFactor;
115 				smoothed = image.process(new FGaussianConvolve(sigma));
116 			}
117 
118 			// extract DSIFT
119 			bounds.x = originalBounds.x + offset;
120 			bounds.y = originalBounds.y + offset;
121 
122 			final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
123 			dsift.setBinWidth(size);
124 			dsift.setBinHeight(size);
125 			dsift.analyseImage(smoothed, bounds);
126 		}
127 	}
128 
129 	@Override
130 	public LocalFeatureList<FloatDSIFTKeypoint> getFloatKeypoints() {
131 		final LocalFeatureList<FloatDSIFTKeypoint> kpts = new MemoryLocalFeatureList<FloatDSIFTKeypoint>(getNumOriBins()
132 				* getNumBinsX() * getNumBinsY());
133 
134 		for (int i = 0; i < sizes.length; i++) {
135 			final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
136 			kpts.addAll(dsift.getFloatKeypoints());
137 		}
138 
139 		return kpts;
140 	}
141 
142 	@Override
143 	public LocalFeatureList<ByteDSIFTKeypoint> getByteKeypoints() {
144 		final LocalFeatureList<ByteDSIFTKeypoint> kpts = new MemoryLocalFeatureList<ByteDSIFTKeypoint>(getNumOriBins()
145 				* getNumBinsX() * getNumBinsY());
146 
147 		for (int i = 0; i < sizes.length; i++) {
148 			final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
149 			kpts.addAll(dsift.getByteKeypoints());
150 		}
151 
152 		return kpts;
153 	}
154 
155 	@Override
156 	public LocalFeatureList<FloatDSIFTKeypoint> getFloatKeypoints(float energyThreshold) {
157 		final LocalFeatureList<FloatDSIFTKeypoint> kpts = new MemoryLocalFeatureList<FloatDSIFTKeypoint>(getNumOriBins()
158 				* getNumBinsX() * getNumBinsY());
159 
160 		for (int i = 0; i < sizes.length; i++) {
161 			final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
162 			kpts.addAll(dsift.getFloatKeypoints(energyThreshold));
163 		}
164 
165 		return kpts;
166 	}
167 
168 	@Override
169 	public LocalFeatureList<ByteDSIFTKeypoint> getByteKeypoints(float energyThreshold) {
170 		final LocalFeatureList<ByteDSIFTKeypoint> kpts = new MemoryLocalFeatureList<ByteDSIFTKeypoint>(getNumOriBins()
171 				* getNumBinsX() * getNumBinsY());
172 
173 		for (int i = 0; i < sizes.length; i++) {
174 			final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
175 			kpts.addAll(dsift.getByteKeypoints(energyThreshold));
176 		}
177 
178 		return kpts;
179 	}
180 
181 	/**
182 	 * Get the SIFT descriptors from the previous call to
183 	 * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)}
184 	 * in the form of a list of local features with float vectors.
185 	 * 
186 	 * @return a list of {@link FloatDSIFTKeypoint}s.
187 	 */
188 	public List<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>> getFloatKeypointsGrouped() {
189 		final List<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>> prs = new ArrayList<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>>(
190 				sizes.length);
191 
192 		for (int i = 0; i < sizes.length; i++) {
193 			final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
194 
195 			prs.add(new IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>(sizes[i], dsift.getFloatKeypoints()));
196 		}
197 
198 		return prs;
199 	}
200 
201 	/**
202 	 * Get the SIFT descriptors from the previous call to
203 	 * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)}
204 	 * in the form of a list of local features with byte vectors.
205 	 * 
206 	 * @return a list of {@link ByteDSIFTKeypoint}s.
207 	 */
208 	public List<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>> getByteKeypointsGrouped() {
209 		final List<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>> prs = new ArrayList<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>>(
210 				sizes.length);
211 
212 		for (int i = 0; i < sizes.length; i++) {
213 			final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
214 
215 			prs.add(new IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>(sizes[i], dsift.getByteKeypoints()));
216 		}
217 
218 		return prs;
219 	}
220 
221 	/**
222 	 * Get the SIFT descriptors from the previous call to
223 	 * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)}
224 	 * in the form of a list of local features with float vectors. Only the
225 	 * features with an energy above the given threshold will be returned.
226 	 * 
227 	 * @param energyThreshold
228 	 *            the threshold on the feature energy
229 	 * 
230 	 * @return a list of {@link FloatDSIFTKeypoint}s.
231 	 */
232 	public List<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>> getFloatKeypointsGrouped(float energyThreshold) {
233 		final List<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>> prs = new ArrayList<IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>>(
234 				sizes.length);
235 
236 		for (int i = 0; i < sizes.length; i++) {
237 			final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
238 
239 			prs.add(new IntObjectPair<LocalFeatureList<FloatDSIFTKeypoint>>(sizes[i], dsift
240 					.getFloatKeypoints(energyThreshold)));
241 		}
242 
243 		return prs;
244 	}
245 
246 	/**
247 	 * Get the SIFT descriptors from the previous call to
248 	 * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)}
249 	 * in the form of a list of local features with byte vectors. Only the
250 	 * features with an energy above the given threshold will be returned.
251 	 * 
252 	 * @param energyThreshold
253 	 *            the threshold on the feature energy
254 	 * 
255 	 * @return a list of {@link ByteDSIFTKeypoint}s.
256 	 */
257 	public List<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>> getByteKeypointsGrouped(float energyThreshold) {
258 		final List<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>> prs = new ArrayList<IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>>(
259 				sizes.length);
260 
261 		for (int i = 0; i < sizes.length; i++) {
262 			final AbstractDenseSIFT<IMAGE> dsift = levels.get(i);
263 
264 			prs.add(new IntObjectPair<LocalFeatureList<ByteDSIFTKeypoint>>(sizes[i],
265 					dsift.getByteKeypoints(energyThreshold)));
266 		}
267 
268 		return prs;
269 	}
270 
271 	/**
272 	 * Get the computed raw dense SIFT descriptors from the previous call to
273 	 * {@link #analyseImage(Image)} or {@link #analyseImage(Image, Rectangle)} .
274 	 * The descriptors are grouped by the sizes at which they were extracted.
275 	 * 
276 	 * @return the descriptors.
277 	 */
278 	public float[][][] getLevelDescriptors() {
279 		final float[][][] descr = new float[sizes.length][][];
280 
281 		for (int i = 0; i < sizes.length; i++) {
282 			descr[i] = levels.get(i).getDescriptors();
283 		}
284 
285 		return descr;
286 	}
287 
288 	/**
289 	 * Get the bin sizes
290 	 * 
291 	 * @return the bin sizes
292 	 */
293 	public int[] getSizes() {
294 		return sizes;
295 	}
296 
297 	/**
298 	 * Not supported.
299 	 * 
300 	 * @throws UnsupportedOperationException
301 	 */
302 	@Override
303 	public void setBinWidth(int size) {
304 		throw new UnsupportedOperationException();
305 	}
306 
307 	/**
308 	 * Not supported.
309 	 * 
310 	 * @throws UnsupportedOperationException
311 	 */
312 	@Override
313 	public void setBinHeight(int size) {
314 		throw new UnsupportedOperationException();
315 	}
316 
317 	/**
318 	 * This returns the bin size of the zeroth level only. {@inheritDoc}
319 	 */
320 	@Override
321 	public int getBinWidth() {
322 		return sizes[0];
323 	}
324 
325 	/**
326 	 * This returns the bin size of the zeroth level only. {@inheritDoc}
327 	 */
328 	@Override
329 	public int getBinHeight() {
330 		return sizes[0];
331 	}
332 
333 	@Override
334 	public int getNumBinsX() {
335 		return levels.get(0).getNumBinsX();
336 	}
337 
338 	@Override
339 	public int getNumBinsY() {
340 		return levels.get(0).getNumBinsY();
341 	}
342 
343 	@Override
344 	public int getNumOriBins() {
345 		return levels.get(0).getNumOriBins();
346 	}
347 
348 	@Override
349 	public float[][] getDescriptors() {
350 		int len = 0;
351 		for (int i = 0; i < sizes.length; i++) {
352 			len += levels.get(i).getDescriptors().length;
353 		}
354 
355 		final float[][] descr = new float[len][];
356 
357 		int offset = 0;
358 		for (int i = 0; i < sizes.length; i++) {
359 			final float[][] ldescr = levels.get(i).getDescriptors();
360 			for (int j = 0; j < ldescr.length; j++) {
361 				descr[offset++] = ldescr[j];
362 			}
363 		}
364 
365 		return descr;
366 	}
367 }