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.objectdetection.haar;
031
032import org.openimaj.citation.annotation.Reference;
033import org.openimaj.citation.annotation.ReferenceType;
034import org.openimaj.image.analysis.algorithm.SummedSqTiltAreaTable;
035
036/**
037 * A classification stage. The stage is made up of an ensemble of classifiers
038 * (which may themselves be trees or stumps). In order to evaluate the stage,
039 * the ensemble of classifiers is tested in order and the responses are summed.
040 * If the summed response exceeds a threshold (corresponding to a certain
041 * error-rate set during training) then the stage will pass and the
042 * {@link #successStage()} should be evaluated. If the stage fails, then the
043 * {@link #failureStage()} will be evaluated. The actual coordination of calling
044 * the {@link #successStage()} or {@link #failureStage()} is performed by the
045 * {@link StageTreeClassifier}.
046 * 
047 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
048 */
049@Reference(
050                type = ReferenceType.Inproceedings,
051                author = { "Viola, P.", "Jones, M." },
052                title = "Rapid object detection using a boosted cascade of simple features",
053                year = "2001",
054                booktitle = "Computer Vision and Pattern Recognition, 2001. CVPR 2001. Proceedings of the 2001 IEEE Computer Society Conference on",
055                pages = { " I", "511 ", " I", "518 vol.1" },
056                number = "",
057                volume = "1",
058                customData = {
059                                "keywords", " AdaBoost; background regions; boosted simple feature cascade; classifiers; face detection; image processing; image representation; integral image; machine learning; object specific focus-of-attention mechanism; rapid object detection; real-time applications; statistical guarantees; visual object detection; feature extraction; image classification; image representation; learning (artificial intelligence); object detection;",
060                                "doi", "10.1109/CVPR.2001.990517",
061                                "ISSN", "1063-6919 "
062                })
063public class Stage {
064        float threshold;
065        Classifier[] ensemble;
066
067        /**
068         * The next stage to evaluate should this one pass; if this stage passes and
069         * this is <code>null</code> then the whole {@link StageTreeClassifier}
070         * passes.
071         */
072        Stage successStage;
073
074        /**
075         * The next stage to evaluate should this one fail; if this stage fails and
076         * this is <code>null</code> then the whole {@link StageTreeClassifier}
077         * fails.
078         */
079        Stage failureStage;
080
081        /**
082         * Do any of the {@link ValueClassifier}s have negative values? If not, we
083         * can perform an optimisation in
084         * {@link #pass(StageTreeClassifier, SummedSqTiltAreaTable, float, int, int)}
085         * to shortcut checking the entire ensemble.
086         */
087        private boolean hasNegativeValueFeatures;
088
089        /**
090         * Construct a new stage.
091         * 
092         * @param threshold
093         *            the threshold for the stage to pass
094         * @param trees
095         *            the classifier trees in the stage
096         * @param successStage
097         *            the next stage after this one, or null if this is the last
098         * @param failureStage
099         *            the failure stage (for trees rather than cascades)
100         */
101        public Stage(float threshold, Classifier[] trees, Stage successStage, Stage failureStage) {
102                this.threshold = threshold;
103                this.ensemble = trees;
104                this.successStage = successStage;
105                this.failureStage = failureStage;
106
107                this.hasNegativeValueFeatures = checkForNegativeValueFeatures();
108        }
109
110        private boolean checkForNegativeValueFeatures() {
111                for (int i = 0; i < ensemble.length; i++) {
112                        if (checkForNegativeValueFeatures(ensemble[i]))
113                                return true;
114                }
115                return false;
116        }
117
118        private boolean checkForNegativeValueFeatures(Classifier classifier) {
119                if (classifier instanceof ValueClassifier) {
120                        if (((ValueClassifier) classifier).value < 0)
121                                return true;
122                } else {
123                        if (checkForNegativeValueFeatures(((HaarFeatureClassifier) classifier).left))
124                                return true;
125                        if (checkForNegativeValueFeatures(((HaarFeatureClassifier) classifier).right))
126                                return true;
127                }
128
129                return false;
130        }
131
132        /**
133         * Test whether a stage passes. For the stage to pass, the sum of the
134         * responses from the internal classification trees or stumps must exceed
135         * the stage's threshold.
136         * 
137         * @param sat
138         *            the summed area tables (integral images)
139         * @param wvNorm
140         *            the normalisation based on the current window variance
141         * @param x
142         *            the x-ordinate of the top-left of the window being tested
143         * @param y
144         *            the y-ordinate of the top-left of the window being tested
145         * @return true if the stage passes; false otherwise
146         */
147        public boolean pass(final SummedSqTiltAreaTable sat, final float wvNorm,
148                        final int x, final int y)
149        {
150                float total = 0;
151
152                // Optimisation: if there are no negative valued features in the
153                // ensemble, then the sum can only increase & it is cheaper to perform
154                // the threshold check on each iteration
155                if (hasNegativeValueFeatures) {
156                        for (int i = 0; i < ensemble.length; i++) {
157                                total += ensemble[i].classify(sat, wvNorm, x, y);
158                        }
159
160                        return total >= threshold;
161                } else {
162                        for (int i = 0; i < ensemble.length; i++) {
163                                total += ensemble[i].classify(sat, wvNorm, x, y);
164                                if (total >= threshold)
165                                        return true;
166                        }
167
168                        return false;
169                }
170        }
171
172        /**
173         * Update the caches for a given scale (given by
174         * {@link StageTreeClassifier#cachedScale}). The {@link Stage} itself
175         * doesn't cache anything, but it's classifiers might...
176         * 
177         * @param cascade
178         *            the tree of stages
179         */
180        void updateCaches(StageTreeClassifier cascade) {
181                for (int i = 0; i < ensemble.length; i++) {
182                        ensemble[i].updateCaches(cascade);
183                }
184        }
185
186        /**
187         * Get the next stage to evaluate should this one pass; if this stage passes
188         * and this is <code>null</code> then the whole {@link StageTreeClassifier}
189         * passes.
190         * 
191         * @return the next stage should
192         *         {@link #pass(SummedSqTiltAreaTable, float, int, int)} return
193         *         true.
194         */
195        public Stage successStage() {
196                return successStage;
197        }
198
199        /**
200         * Get the next stage to evaluate should this one fail; if this stage fails
201         * and this is <code>null</code> then the whole {@link StageTreeClassifier}
202         * fails.
203         * 
204         * @return the next stage should
205         *         {@link #pass(SummedSqTiltAreaTable, float, int, int)} return
206         *         false.
207         */
208        public Stage failureStage() {
209                return failureStage;
210        }
211}