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.dense.gradient.dsift; 031 032import java.util.ArrayList; 033import java.util.List; 034 035import org.openimaj.feature.local.list.LocalFeatureList; 036import org.openimaj.feature.local.list.MemoryLocalFeatureList; 037import org.openimaj.image.FImage; 038import org.openimaj.image.Image; 039import org.openimaj.image.processing.convolution.FGaussianConvolve; 040import org.openimaj.image.processor.SinglebandImageProcessor; 041import org.openimaj.math.geometry.shape.Rectangle; 042import org.openimaj.util.array.ArrayUtils; 043import org.openimaj.util.pair.IntObjectPair; 044 045/** 046 * A scale-space pyramid of dense SIFT for {@link FImage}s. Dense sift features 047 * are extracted for the given bin sizes (scales). The image is optionally 048 * smoothed with a Gaussian before each scale. 049 * <p> 050 * The {@link PyramidDenseSIFT} is not thread safe, but is reusable like the 051 * {@link DenseSIFT} analyser. 052 * 053 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 054 * @param <IMAGE> 055 * Type of image to be processed. 056 * 057 */ 058public class PyramidDenseSIFT<IMAGE extends Image<?, IMAGE> & SinglebandImageProcessor.Processable<Float, FImage, IMAGE>> 059 extends 060 AbstractDenseSIFT<IMAGE> 061{ 062 /** 063 * Scales at which the dense SIFT features are extracted. Each value is used 064 * as bin size for the {@link DenseSIFT}. 065 */ 066 int[] sizes; 067 068 /** 069 * The image is smoothed by a Gaussian kernel of standard deviation size / 070 * magnificationFactor. 071 */ 072 float magnificationFactor; 073 074 private List<AbstractDenseSIFT<IMAGE>> levels; 075 076 /** 077 * Construct the pyramid dense sift extractor. The magnification factor is 078 * used to determine how to smooth the image before extracting the features 079 * at each level: the smoothing sigma at each level is the bin size at that 080 * level divided by the magnification factor. If the magnification factor is 081 * 0, then no smoothing will be applied at any level. 082 * 083 * @param dsift 084 * the underlying dense sift extractor (typically a 085 * {@link DenseSIFT} (or {@link ApproximateDenseSIFT}) or 086 * {@link ColourDenseSIFT} depending on the image type). 087 * @param magFactor 088 * the magnification factor 089 * @param sizes 090 * the scales (bin sizes for dense sift) 091 */ 092 public PyramidDenseSIFT(AbstractDenseSIFT<IMAGE> dsift, float magFactor, int... sizes) { 093 this.sizes = sizes; 094 this.magnificationFactor = magFactor; 095 096 levels = new ArrayList<AbstractDenseSIFT<IMAGE>>(sizes.length); 097 for (int i = 0; i < sizes.length; i++) { 098 levels.add(dsift.clone()); 099 } 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}