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}