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 */ 030/** 031 * 032 */ 033package org.openimaj.image.feature.local.detector.mser; 034 035import java.util.ArrayList; 036import java.util.List; 037 038import org.openimaj.citation.annotation.Reference; 039import org.openimaj.citation.annotation.ReferenceType; 040import org.openimaj.image.FImage; 041import org.openimaj.image.analysis.watershed.Component; 042import org.openimaj.image.analysis.watershed.MergeTreeBuilder; 043import org.openimaj.image.analysis.watershed.WatershedProcessor; 044import org.openimaj.image.analysis.watershed.feature.ComponentFeature; 045import org.openimaj.util.tree.TreeNode; 046 047/** 048 * Detector for MSER features. 049 * 050 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 051 */ 052@Reference( 053 type = ReferenceType.Article, 054 author = { "J Matas", "O Chum", "M Urban", "T Pajdla" }, 055 title = "Robust wide-baseline stereo from maximally stable extremal regions", 056 year = "2004", 057 journal = "Image and Vision Computing", 058 pages = { "761 ", " 767" }, 059 url = "http://www.sciencedirect.com/science/article/pii/S0262885604000435", 060 number = "10", 061 volume = "22", 062 customData = { 063 "issn", "0262-8856", 064 "doi", "10.1016/j.imavis.2004.02.006", 065 "keywords", "Robust metric" 066 }) 067public class MSERFeatureGenerator { 068 /** 069 * A way of representing how the MSER should be processed. 070 * 071 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 072 * 073 */ 074 public enum MSERDirection { 075 /** 076 * Upwards detection 077 */ 078 Up, 079 /** 080 * Downwards detection 081 */ 082 Down, 083 /** 084 * Upwards and Downwards detection 085 */ 086 UpAndDown 087 } 088 089 private int delta = 10; 090 private int maxArea = Integer.MAX_VALUE; 091 private int minArea = 1; 092 private float maxVariation = Float.MAX_VALUE; 093 private float minDiversity = 0; 094 private Class<? extends ComponentFeature>[] featureClasses; 095 096 /** 097 * Default constructor 098 * 099 * @param featureClasses 100 * features to generate for each mser 101 */ 102 public MSERFeatureGenerator(Class<? extends ComponentFeature>... featureClasses) { 103 this.featureClasses = featureClasses; 104 } 105 106 /** 107 * Constructor that takes all the parameters for the MSER process. 108 * 109 * @param delta 110 * @param maxArea 111 * @param minArea 112 * @param maxVariation 113 * @param minDiversity 114 * @param featureClasses 115 * features to generate for each mser 116 */ 117 public MSERFeatureGenerator(int delta, int maxArea, int minArea, float maxVariation, float minDiversity, 118 Class<? extends ComponentFeature>... featureClasses) 119 { 120 this(featureClasses); 121 122 this.delta = delta; 123 this.maxArea = maxArea; 124 this.minArea = minArea; 125 this.maxVariation = maxVariation; 126 this.minDiversity = minDiversity; 127 } 128 129 /** 130 * Performs a watershed then an MSER detection on the given image and 131 * returns the MSERs. 132 * 133 * @param img 134 * The image to analyse. 135 * @return A list of {@link Component}s 136 */ 137 public List<Component> generateMSERs(FImage img) { 138 return generateMSERs(img, MSERDirection.UpAndDown); 139 } 140 141 /** 142 * Performs a watershed then an MSER detection on the given image and 143 * returns the MSERs. 144 * 145 * @param img 146 * The image to analyse.# 147 * @param dir 148 * The direction which to process the MSERS 149 * @return A list of {@link Component}s 150 */ 151 public List<Component> generateMSERs(FImage img, MSERDirection dir) { 152 final List<MergeTreeBuilder> mtb = performWatershed(img); 153 final List<Component> regions = performMSERDetection(mtb, dir); 154 return regions; 155 } 156 157 /** 158 * Perform the watershed algorithm on the given image. 159 * 160 * @param img 161 * The image to perform the watershed on 162 * @return A tuple of {@link MergeTreeBuilder}s (down first, up second) 163 */ 164 public List<MergeTreeBuilder> performWatershed(FImage img) { 165 // Create the image analysis object 166 final WatershedProcessor watershedUp = new WatershedProcessor(featureClasses); 167 final WatershedProcessor watershedDown = new WatershedProcessor(featureClasses); 168 final MergeTreeBuilder treeBuilderUp = new MergeTreeBuilder(); 169 watershedUp.addComponentStackMergeListener(treeBuilderUp); 170 final MergeTreeBuilder treeBuilderDown = new MergeTreeBuilder(); 171 watershedDown.addComponentStackMergeListener(treeBuilderDown); 172 173 // ----------------------------------------------------------------- 174 // Watershed the image to get the tree 175 // ----------------------------------------------------------------- 176 // bottom-up watershed 177 watershedUp.processImage(img); 178 179 // Invert the image, as we must detect MSERs from both top-down 180 // and bottom-up. 181 img = img.inverse(); 182 // top-down watershed 183 watershedDown.processImage(img); 184 185 // Return the image to its original state. 186 img = img.inverse(); 187 188 final List<MergeTreeBuilder> mtb = new ArrayList<MergeTreeBuilder>(); 189 mtb.add(treeBuilderDown); 190 mtb.add(treeBuilderUp); 191 return mtb; 192 } 193 194 /** 195 * Performs MSER detection on the trees provided. The input list must be a 196 * list containing {@link MergeTreeBuilder}s, the first being the downward 197 * watershed, the second being the upward watershed. 198 * 199 * @param mtbs 200 * The list of {@link MergeTreeBuilder}s 201 * @param dir 202 * The direction to detect MSERs from 203 * @return A list of {@link Component}s 204 */ 205 public List<Component> performMSERDetection(List<MergeTreeBuilder> mtbs, MSERDirection dir) { 206 // Remove the MSER component flags in the trees (in case they're being 207 // reused) 208 clearTree(mtbs.get(0).getTree()); 209 clearTree(mtbs.get(1).getTree()); 210 211 // ----------------------------------------------------------------- 212 // Now run the MSER detector on it 213 // ----------------------------------------------------------------- 214 // bottom up detection 215 // System.out.println( mtbs.get(1).getTree() ); 216 List<Component> regionsUp = null; 217 if (mtbs.get(1).getTree() != null && (dir == MSERDirection.Up || dir == MSERDirection.UpAndDown)) { 218 final MSERDetector mser = new MSERDetector(mtbs.get(1).getTree()); 219 mser.setDelta(this.delta); 220 mser.setMaxArea(this.maxArea); 221 mser.setMinArea(this.minArea); 222 mser.setMaxVariation(this.maxVariation); 223 mser.setMinDiversity(this.minDiversity); 224 regionsUp = mser.detect(); 225 // System.out.println( "Top-down detected: "+regionsUp ); 226 } 227 228 // top-down detection 229 List<Component> regionsDown = null; 230 if (mtbs.get(0).getTree() != null && (dir == MSERDirection.Down || dir == MSERDirection.UpAndDown)) { 231 final MSERDetector mser2 = new MSERDetector(mtbs.get(0).getTree()); 232 mser2.setDelta(this.delta); 233 mser2.setMaxArea(this.maxArea); 234 mser2.setMinArea(this.minArea); 235 mser2.setMaxVariation(this.maxVariation); 236 mser2.setMinDiversity(this.minDiversity); 237 regionsDown = mser2.detect(); 238 // System.out.println( "Bottom-up detected: "+regionsDown ); 239 } 240 241 final List<Component> regions = new ArrayList<Component>(); 242 if (regionsUp != null) 243 regions.addAll(regionsUp); 244 if (regionsDown != null) 245 regions.addAll(regionsDown); 246 247 // System.out.println( "Detected "+regions.size()+" regions "); 248 // System.out.println( "Detected "+countMSERs( mtbs.get(0).getTree() 249 // )+" in down tree" ); 250 // System.out.println( "Detected "+countMSERs( mtbs.get(1).getTree() 251 // )+" in up tree" ); 252 return regions; 253 } 254 255 /** 256 * Removes all the MSER flags from the components in the tree 257 * 258 * @param tree 259 * The tree to clear MSER flags 260 */ 261 private void clearTree(TreeNode<Component> tree) { 262 if (tree == null) 263 return; 264 final Component c = tree.getValue(); 265 if (c != null) 266 c.isMSER = false; 267 if (tree.getChildren() != null) 268 for (final TreeNode<Component> child : tree.getChildren()) 269 clearTree(child); 270 } 271 272 /** 273 * Returns a count of the number of components in the tree that are marked 274 * as MSERs. 275 * 276 * @param tree 277 * The tree to count MSERs in 278 * @return the count 279 */ 280 public int countMSERs(TreeNode<Component> tree) { 281 if (tree == null) 282 return 0; 283 int retVal = 0; 284 final Component c = tree.getValue(); 285 if (c != null && c.isMSER) 286 retVal++; 287 if (tree.getChildren() != null) 288 for (final TreeNode<Component> child : tree.getChildren()) 289 retVal += countMSERs(child); 290 return retVal; 291 292 } 293 294 /** 295 * @return the delta 296 */ 297 public int getDelta() { 298 return delta; 299 } 300 301 /** 302 * @param delta 303 * the delta to set 304 */ 305 public void setDelta(int delta) { 306 this.delta = delta; 307 } 308 309 /** 310 * @return the maxArea 311 */ 312 public int getMaxArea() { 313 return maxArea; 314 } 315 316 /** 317 * @param maxArea 318 * the maxArea to set 319 */ 320 public void setMaxArea(int maxArea) { 321 this.maxArea = maxArea; 322 } 323 324 /** 325 * @return the minArea 326 */ 327 public int getMinArea() { 328 return minArea; 329 } 330 331 /** 332 * @param minArea 333 * the minArea to set 334 */ 335 public void setMinArea(int minArea) { 336 this.minArea = minArea; 337 } 338 339 /** 340 * @return the maxVariation 341 */ 342 public float getMaxVariation() { 343 return maxVariation; 344 } 345 346 /** 347 * @param maxVariation 348 * the maxVariation to set 349 */ 350 public void setMaxVariation(float maxVariation) { 351 this.maxVariation = maxVariation; 352 } 353 354 /** 355 * @return the minDiversity 356 */ 357 public float getMinDiversity() { 358 return minDiversity; 359 } 360 361 /** 362 * @param minDiversity 363 * the minDiversity to set 364 */ 365 public void setMinDiversity(float minDiversity) { 366 this.minDiversity = minDiversity; 367 } 368 369}