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}