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 tree of classifier stages. In the case that the tree is degenerate and all
038 * {@link Stage}s have <code>null</code> {@link Stage#failureStage()}s, then the
039 * tree is known as a <strong>cascade</strong>.
040 * <p>
041 * The general idea is that for a given window in the image being tested
042 * (defined by an x,y position and scale), the stage tree is evaluated. If when
043 * evaluating the tree a leaf node is hit (i.e. a {@link Stage} that passes
044 * successfully, but has a <code>null</code> {@link Stage#successStage()}) then
045 * the tree is said to have passed, and indicates a potential object detection
046 * within the window. If a {@link Stage} fails to pass and has a
047 * <code>null</code> {@link Stage#failureStage()} then the tree is said to have
048 * failed, indicating the object in question was not found.
049 * <p>
050 * In order to achieve good performance, this implementation pre-computes and
051 * caches variables related to a given detection scale. This means that it is
052 * <strong>NOT safe</strong> to use a detector based on this stage
053 * implementation in a multi-threaded environment such that multiple images are
054 * being tested at a given time. It is however safe to use this implementation
055 * with a detector that multi-threads its detection across the x and y window
056 * positions for a fixed scale:
057 * 
058 * <code><pre>
059 *  StageTreeClassifier cascade = ...
060 *  
061 *      for each scale {
062 *              cascade.setScale(scale);
063 *              
064 *              //the x and y search could be threaded...
065 *              for each y {
066 *                      for each x {
067 *                              cascade.matches(sat, x, y); {
068 *                      }
069 *              }
070 * }
071 * </pre></code>
072 * 
073 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
074 */
075@Reference(
076                type = ReferenceType.Inproceedings,
077                author = { "Viola, P.", "Jones, M." },
078                title = "Rapid object detection using a boosted cascade of simple features",
079                year = "2001",
080                booktitle = "Computer Vision and Pattern Recognition, 2001. CVPR 2001. Proceedings of the 2001 IEEE Computer Society Conference on",
081                pages = { " I", "511 ", " I", "518 vol.1" },
082                number = "",
083                volume = "1",
084                customData = {
085                                "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;",
086                                "doi", "10.1109/CVPR.2001.990517",
087                                "ISSN", "1063-6919 "
088                })
089public class StageTreeClassifier {
090        /**
091         * The width of the classifier
092         */
093        int width;
094
095        /**
096         * The height of the classifier
097         */
098        int height;
099
100        /**
101         * The name of the classifier
102         */
103        String name;
104
105        /**
106         * Does the classifier contain tilted features?
107         */
108        boolean hasTiltedFeatures;
109
110        /**
111         * The root of the stage tree
112         */
113        Stage root;
114
115        // cached values for the scale being processed
116        float cachedScale; // the current scale
117        float cachedInvArea; // the inverse of the current (scaled) detection window
118        int cachedW; // the width of the current (scaled) detection window
119        int cachedH; // the height of the current (scaled) detection window
120
121        /**
122         * Construct the {@link StageTreeClassifier} with the given parameters.
123         * 
124         * @param width
125         *            the width of the classifier
126         * @param height
127         *            the height of the classifier
128         * @param name
129         *            the name of the classifier
130         * @param hasTiltedFeatures
131         *            are there tilted haar-like features in the classifiers?
132         * @param root
133         *            the root of the tree of stages
134         */
135        public StageTreeClassifier(int width, int height, String name, boolean hasTiltedFeatures, Stage root) {
136                this.width = width;
137                this.height = height;
138                this.name = name;
139                this.hasTiltedFeatures = hasTiltedFeatures;
140                this.root = root;
141        }
142
143        float computeWindowVarianceNorm(SummedSqTiltAreaTable sat, int x, int y) {
144                x += Math.round(cachedScale); // shift by 1 scaled px to centre box
145                y += Math.round(cachedScale);
146
147                final float sum = sat.sum.pixels[y + cachedH][x + cachedW] + sat.sum.pixels[y][x] -
148                                sat.sum.pixels[y + cachedH][x] - sat.sum.pixels[y][x + cachedW];
149                final float sqSum = sat.sqSum.pixels[y + cachedH][x + cachedW] + sat.sqSum.pixels[y][x] -
150                                sat.sqSum.pixels[y + cachedH][x] - sat.sqSum.pixels[y][x + cachedW];
151
152                final float mean = sum * cachedInvArea;
153                float wvNorm = sqSum * cachedInvArea - mean * mean;
154                wvNorm = (float) ((wvNorm >= 0) ? Math.sqrt(wvNorm) : 1);
155
156                return wvNorm;
157        }
158
159        /**
160         * Set the current detection scale. This must be called before calling
161         * {@link #classify(SummedSqTiltAreaTable, int, int)}.
162         * <p>
163         * Internally, this goes through all the stages and their individual
164         * classifiers and pre-caches information related to the current scale to
165         * avoid lots of expensive recomputation of values that don't change for a
166         * given scale.
167         * 
168         * @param scale
169         *            the current scale
170         */
171        public void setScale(float scale) {
172                this.cachedScale = scale;
173
174                // following the OCV code... -2 to make a slightly smaller box within
175                // window
176                cachedW = Math.round(scale * (width - 2));
177                cachedH = Math.round(scale * (height - 2));
178                cachedInvArea = 1.0f / (cachedW * cachedH);
179
180                updateCaches(root);
181        }
182
183        /**
184         * Recursively update the caches of all the stages to reflect the current
185         * scale.
186         * 
187         * @param s
188         *            the stage to update
189         */
190        private void updateCaches(Stage s) {
191                s.updateCaches(this);
192
193                if (s.successStage != null)
194                        updateCaches(s.successStage);
195                if (s.failureStage != null)
196                        updateCaches(s.failureStage);
197        }
198
199        /**
200         * Apply the classifier to the given image at the given position.
201         * Internally, this will apply each stage to the image. If all stages
202         * complete successfully a detection is indicated.
203         * <p>
204         * This method returns the number of stages passed if all stages pass; if a
205         * stage fails, then (-1 * number of successful stages) is returned. For
206         * example a value of 20 indicates the successful detection from a total of
207         * 20 stages, whilst -10 indicates an unsuccessful detection due to a
208         * failure on the 11th stage.
209         * 
210         * @param sat
211         *            the summed area table(s) for the image in question. If there
212         *            are tilted features, this must include the tilted SAT.
213         * @param x
214         *            the x-ordinate of the top-left of the current window
215         * @param y
216         *            the y-ordinate of the top-left of the current window
217         * @return > 0 if a detection was made; <=0 if no detection was made. The
218         *         magnitude indicates the number of stages that passed.
219         */
220        public int classify(SummedSqTiltAreaTable sat, int x, int y) {
221                final float wvNorm = computeWindowVarianceNorm(sat, x, y);
222
223                // all stages need to match for this cascade to match
224                int matches = 0; // the number of stages that pass
225                Stage stage = root;
226                while (true) { // until success or failure
227                        if (stage.pass(sat, wvNorm, x, y)) {
228                                matches++;
229                                stage = stage.successStage;
230                                if (stage == null) {
231                                        return matches;
232                                }
233                        } else {
234                                stage = stage.failureStage;
235                                if (stage == null) {
236                                        return -matches;
237                                }
238                        }
239                }
240        }
241
242        /**
243         * Get the classifier width
244         * 
245         * @return the width
246         */
247        public int getWidth() {
248                return width;
249        }
250
251        /**
252         * Get the classifier height
253         * 
254         * @return the height
255         */
256        public int getHeight() {
257                return height;
258        }
259
260        /**
261         * Get the classifier name
262         * 
263         * @return the name
264         */
265        public String getName() {
266                return name;
267        }
268
269        /**
270         * Does the classifier use tilted haar-like features?
271         * 
272         * @return true if tilted features are used; false otherwise.
273         */
274        public boolean hasTiltedFeatures() {
275                return hasTiltedFeatures;
276        }
277}