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.LinkedList; 037import java.util.List; 038 039import org.openimaj.citation.annotation.Reference; 040import org.openimaj.citation.annotation.ReferenceType; 041import org.openimaj.image.analysis.watershed.Component; 042import org.openimaj.util.tree.TreeNode; 043 044/** 045 * Takes a merge tree from the watershed algorithm and detects MSERs. The 046 * {@link #detect()} method returns a list of MSER components. The components 047 * are also marked as MSERs in the merge tree, such that the tree can be used to 048 * determine the hierarchical nature of the MSERs. Use the 049 * {@link Component#isMSER} to determine if the component in the tree is an 050 * MSER. 051 * 052 * <blockquote> (From http://www.vlfeat.org/overview/mser.html) The stability of 053 * an extremal region R is the inverse of the relative area variation of the 054 * region R when the intensity level is increased by d. Formally, the variation 055 * is defined as: 056 * 057 * <pre> 058 * <code> 059 * |R(+d) - R| 060 * ----------- 061 * |R| 062 * </code> 063 * </pre> 064 * 065 * where |R| denotes the area of the extremal region R, R(+d) is the extremal 066 * region +d levels up which contains R and |R(+d) - R| is the area difference 067 * of the two regions. 068 * 069 * A stable region has a small variation. The algorithm finds regions which are 070 * "maximally stable", meaning that they have a lower variation than the regions 071 * one level below or above. Note that due to the discrete nature of the image, 072 * the region below / above may be coincident with the actual region, in which 073 * case the region is still deemed maximal. 074 * 075 * However, even if an extremal region is maximally stable, it might be rejected 076 * if: 077 * 078 * it is too big (see the parameter maxArea); it is too small (see the parameter 079 * minArea); it is too unstable (see the parameter maxVariation); it is too 080 * similar to its parent MSER (see the parameter minDiversity). </blockquote> 081 * 082 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 083 * 084 */ 085@Reference( 086 type = ReferenceType.Article, 087 author = { "J Matas", "O Chum", "M Urban", "T Pajdla" }, 088 title = "Robust wide-baseline stereo from maximally stable extremal regions", 089 year = "2004", 090 journal = "Image and Vision Computing", 091 pages = { "761 ", " 767" }, 092 url = "http://www.sciencedirect.com/science/article/pii/S0262885604000435", 093 number = "10", 094 volume = "22", 095 customData = { 096 "issn", "0262-8856", 097 "doi", "10.1016/j.imavis.2004.02.006", 098 "keywords", "Robust metric" 099 }) 100public class MSERDetector { 101 /** The maximum area an MSER can take, in pixels */ 102 private int maxArea = Integer.MAX_VALUE; 103 104 /** The minimum area an MSER can be, in pixels */ 105 private int minArea = 1; 106 107 /** The minimum stability an MSER must have */ 108 private float maxVariation = 1; 109 110 /** The minimum diversity with its parent than an MSER must have */ 111 private float minDiversity = 0; 112 113 /** The stability delta */ 114 private int delta = 10; 115 116 /** The tree to be processed */ 117 private TreeNode<Component> mergeTree = null; 118 119 /** 120 * Constructor that takes the merge tree from the watershed algorithm. 121 * 122 * @param mergeTree 123 * The merge tree 124 */ 125 public MSERDetector(TreeNode<Component> mergeTree) { 126 this.mergeTree = mergeTree; 127 } 128 129 /** 130 * Detect MSERs in the merge tree provided in the constructor. 131 * 132 * @return a list of detected regions. 133 */ 134 public List<Component> detect() { 135 final List<Component> detectedRegions = processTree(mergeTree); 136 return detectedRegions; 137 } 138 139 /** 140 * Traverses the tree and attempts to find the minima (in terms of the rate 141 * of change of size over intensity) in the chains that form from the 142 * branches of the tree. 143 * 144 * @param mergeTree 145 * The merge tree. 146 * @return A list of stable regions 147 */ 148 private List<Component> processTree(TreeNode<Component> mergeTree) { 149 final List<Component> detectedRegions = new ArrayList<Component>(); 150 if (mergeTree != null) 151 processTreeAux(mergeTree, detectedRegions, new LinkedList<TreeNode<Component>>()); 152 return detectedRegions; 153 } 154 155 /** 156 * Recursive tree-traversal method for the processTree function. 157 * 158 * @param treeNode 159 * The current tree node being processed 160 * @param components 161 * The list of stable regions so far detected 162 * @param path 163 * The path to treeNode that has been taken so far 164 */ 165 private void processTreeAux(TreeNode<Component> treeNode, List<Component> components, 166 LinkedList<TreeNode<Component>> path) 167 { 168 // Is there a branch or a chain? 169 // Also no need to iterate down the tree if the intensity level of 170 // this component is already less than the delta, because no regions 171 // below this point couldn't possibly be considered stable. 172 if (treeNode.getChildren() != null && treeNode.getChildren().size() > 0 && 173 treeNode.getValue().pivot.value >= delta) 174 { 175 path.add(treeNode); 176 177 // Recursive traversal of tree 178 for (final TreeNode<Component> node : treeNode.getChildren()) 179 processTreeAux(node, components, path); 180 } 181 182 // Remove this node, if present 183 // (it won't be present for leaf nodes) 184 path.remove(treeNode); 185 final Component child = treeNode.getValue(); 186 187 // The size of the component must be within the defined limits 188 if (child.size() < maxArea && child.size() > minArea) { 189 // Now calculate whether this component could be an MSER 190 final Component parent = getAppropriateParent(path, child.pivot.value, delta); 191 192 // If there's no appropriate parent node (above delta) then 193 // this node cannot be an MSER 194 if (parent != null) { 195 final int intensityDifference = Math.abs(parent.pivot.value - child.pivot.value); 196 197 // MSERs can only occur if the difference is greater than the 198 // delta. 199 if (intensityDifference >= delta) { 200 float variation; 201 202 if (intensityDifference == delta) 203 variation = Math.abs(child.size() - parent.size()) / (float) parent.size(); 204 else 205 variation = 0; // the virtual node at level-delta has 206 // the same size as the current 207 208 // The variation must be less than the defined maximum 209 if (variation < maxVariation) { 210 child.isMSER = true; 211 components.add(child); 212 213 // duplicate detection: if any children are mser of 214 // similar size, then remove them 215 for (final TreeNode<Component> childNode : treeNode.getChildren()) { 216 if (childNode.getValue().isMSER) { 217 final float div = (float) (child.size() - childNode.getValue().size()) 218 / (float) child.size(); 219 220 if (div < minDiversity) { 221 childNode.getValue().isMSER = false; 222 components.remove(childNode.getValue()); 223 } 224 } 225 } 226 } 227 } 228 } 229 } 230 } 231 232 /** 233 * Returns the next appropriate parent where the parent component has a 234 * greylevel that is at least delta more than the current greylevel 235 * 236 * @param path 237 * The path to search 238 * @param currentGL 239 * The current grey level of the node 240 * @param delta 241 * The delta to use 242 * @return The next component up the tree path that has an appropriate 243 * greylevel, or NULL if there isn't one 244 */ 245 private Component getAppropriateParent(LinkedList<TreeNode<Component>> path, int currentGL, int delta) { 246 // Iterate through the path backwards 247 for (int i = path.size() - 1; i >= 0; i--) { 248 if (Math.abs(path.get(i).getValue().pivot.value - currentGL) >= delta) 249 return path.get(i).getValue(); 250 } 251 252 return null; 253 } 254 255 /** 256 * @return the maxArea 257 */ 258 public int getMaxArea() { 259 return maxArea; 260 } 261 262 /** 263 * @param maxArea 264 * the maxArea to set 265 */ 266 public void setMaxArea(int maxArea) { 267 this.maxArea = maxArea; 268 } 269 270 /** 271 * @return the minArea 272 */ 273 public int getMinArea() { 274 return minArea; 275 } 276 277 /** 278 * @param minArea 279 * the minArea to set 280 */ 281 public void setMinArea(int minArea) { 282 this.minArea = minArea; 283 } 284 285 /** 286 * @return the maxVariation 287 */ 288 public float getMaxVariation() { 289 return maxVariation; 290 } 291 292 /** 293 * @param maxVariation 294 * the maxVariation to set 295 */ 296 public void setMaxVariation(float maxVariation) { 297 this.maxVariation = maxVariation; 298 } 299 300 /** 301 * @return the minDiversity 302 */ 303 public float getMinDiversity() { 304 return minDiversity; 305 } 306 307 /** 308 * @param minDiversity 309 * the minDiversity to set 310 */ 311 public void setMinDiversity(float minDiversity) { 312 this.minDiversity = minDiversity; 313 } 314 315 /** 316 * @return the mergeTree 317 */ 318 public TreeNode<Component> getMergeTree() { 319 return mergeTree; 320 } 321 322 /** 323 * @return the delta 324 */ 325 public int getDelta() { 326 return delta; 327 } 328 329 /** 330 * @param delta 331 * the delta to set 332 */ 333 public void setDelta(int delta) { 334 this.delta = delta; 335 } 336}