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 java.util.List; 033 034import org.openimaj.citation.annotation.Reference; 035import org.openimaj.citation.annotation.ReferenceType; 036import org.openimaj.image.analysis.algorithm.SummedSqTiltAreaTable; 037 038/** 039 * Class describing a Haar-like feature. The features are typically built from 040 * two or three overlapping rectangles, and can represent edges, lines and 041 * centre-surround features. 042 * <p> 043 * The response of applying the feature to a specific point on an image (with a 044 * specific scaling) can be efficiently calculated using summed area tables. 045 * <p> 046 * Internally this implementation caches a scaled version of each rectangle for 047 * a given detection scale. 048 * 049 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 050 * 051 */ 052@Reference( 053 type = ReferenceType.Inproceedings, 054 author = { "Viola, P.", "Jones, M." }, 055 title = "Rapid object detection using a boosted cascade of simple features", 056 year = "2001", 057 booktitle = "Computer Vision and Pattern Recognition, 2001. CVPR 2001. Proceedings of the 2001 IEEE Computer Society Conference on", 058 pages = { " I", "511 ", " I", "518 vol.1" }, 059 number = "", 060 volume = "1", 061 customData = { 062 "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;", 063 "doi", "10.1109/CVPR.2001.990517", 064 "ISSN", "1063-6919 " 065 }) 066public abstract class HaarFeature { 067 /** 068 * The rectangles that make up this feature. 069 */ 070 public WeightedRectangle[] rects; 071 072 private final float correctionFactor; 073 protected WeightedRectangle[] cachedRects; 074 075 /** 076 * Construct a new feature 077 * 078 * @param rects 079 * @param correctionFactor 080 */ 081 private HaarFeature(WeightedRectangle[] rects, final float correctionFactor) { 082 this.rects = rects; 083 this.correctionFactor = correctionFactor; 084 085 cachedRects = new WeightedRectangle[rects.length]; 086 for (int i = 0; i < cachedRects.length; i++) { 087 cachedRects[i] = new WeightedRectangle(0, 0, 0, 0, 0); 088 } 089 } 090 091 final void updateCaches(StageTreeClassifier cascade) { 092 setScale(cascade.cachedScale, cascade.cachedInvArea); 093 } 094 095 /** 096 * Set the current detection scale, setting up the internal caches 097 * appropriately. 098 * 099 * @param scale 100 * the scale 101 * @param invArea 102 * the inverse of the detector area 103 */ 104 public final void setScale(float scale, float invArea) { 105 double sum0 = 0; 106 double area0 = 0; 107 108 int base_w = Integer.MAX_VALUE; 109 int base_h = Integer.MAX_VALUE; 110 int new_base_w = 0; 111 int new_base_h = 0; 112 int kx; 113 int ky; 114 boolean flagx = false; 115 boolean flagy = false; 116 int x0 = 0; 117 int y0 = 0; 118 119 final WeightedRectangle firstArea = rects[0]; 120 for (final WeightedRectangle r : rects) { 121 if ((r.width - 1) >= 0) { 122 base_w = Math.min(base_w, (r.width - 1)); 123 } 124 if ((r.x - firstArea.x - 1) >= 0) { 125 base_w = Math.min(base_w, (r.x - firstArea.x - 1)); 126 } 127 if ((r.height - 1) >= 0) { 128 base_h = Math.min(base_h, (r.height - 1)); 129 } 130 if ((r.y - firstArea.y - 1) >= 0) { 131 base_h = Math.min(base_h, (r.y - firstArea.y - 1)); 132 } 133 } 134 135 base_w += 1; 136 base_h += 1; 137 kx = firstArea.width / base_w; 138 ky = firstArea.height / base_h; 139 140 if (kx <= 0) { 141 flagx = true; 142 new_base_w = Math.round(firstArea.width * scale) / kx; 143 x0 = Math.round(firstArea.x * scale); 144 } 145 146 if (ky <= 0) { 147 flagy = true; 148 new_base_h = Math.round(firstArea.height * scale) 149 / ky; 150 y0 = Math.round(firstArea.y * scale); 151 } 152 153 for (int k = 0; k < rects.length; k++) { 154 final WeightedRectangle r = rects[k]; 155 int x; 156 int y; 157 int width; 158 int height; 159 float correction_ratio; 160 161 if (flagx) { 162 x = (r.x - firstArea.x) * new_base_w / base_w + x0; 163 width = r.width * new_base_w / base_w; 164 } else { 165 x = Math.round(r.x * scale); 166 width = Math.round(r.width * scale); 167 } 168 169 if (flagy) { 170 y = (r.y - firstArea.y) * new_base_h / base_h + y0; 171 height = r.height * new_base_h / base_h; 172 } else { 173 y = Math.round(r.y * scale); 174 height = Math.round(r.height * scale); 175 } 176 177 correction_ratio = correctionFactor * invArea; 178 179 cachedRects[k].weight = (rects[k].weight * correction_ratio); 180 cachedRects[k].x = x; 181 cachedRects[k].y = y; 182 cachedRects[k].width = width; 183 cachedRects[k].height = height; 184 185 if (k == 0) { 186 area0 = width * height; 187 } else { 188 sum0 += cachedRects[k].weight * width * height; 189 } 190 } 191 192 cachedRects[0].weight = (float) (-sum0 / area0); 193 } 194 195 /** 196 * Compute the response of this feature at the given location. The scale of 197 * the feature must have previously been set through a call to 198 * {@link #setScale(float, float)} (this is only required once per scale). 199 * 200 * @param sat 201 * the summed area table(s). If there are tilted features, then 202 * this must include the tilted SAT. 203 * @param x 204 * the x-ordinate for the window being tested 205 * @param y 206 * the y-ordinate for the window being tested 207 * @return the response to the feature 208 */ 209 public abstract float computeResponse(SummedSqTiltAreaTable sat, int x, int y); 210 211 static class TiltedFeature extends HaarFeature { 212 public TiltedFeature(WeightedRectangle[] rects) { 213 super(rects, 2f); 214 } 215 216 @Override 217 public float computeResponse(SummedSqTiltAreaTable sat, int rx, int ry) { 218 float total = 0; 219 for (int i = 0; i < cachedRects.length; i++) { 220 final WeightedRectangle rect = cachedRects[i]; 221 222 final int x = rx + rect.x; 223 final int y = ry + rect.y; 224 final int width = rect.width; 225 final int height = rect.height; 226 227 final float p0 = sat.tiltSum.pixels[y][x]; 228 final float p1 = sat.tiltSum.pixels[y + height][x - height]; 229 final float p2 = sat.tiltSum.pixels[y + width][x + width]; 230 final float p3 = sat.tiltSum.pixels[y + width + height][x + width - height]; 231 232 final float regionSum = p0 - p1 - p2 + p3; 233 234 total += regionSum * rect.weight; 235 } 236 237 return total; 238 } 239 } 240 241 static class NormalFeature extends HaarFeature { 242 public NormalFeature(WeightedRectangle[] rects) { 243 super(rects, 1f); 244 } 245 246 @Override 247 public float computeResponse(SummedSqTiltAreaTable sat, int rx, int ry) { 248 float total = 0; 249 for (int i = 0; i < cachedRects.length; i++) { 250 final WeightedRectangle rect = cachedRects[i]; 251 252 final int x = rx + rect.x; 253 final int y = ry + rect.y; 254 final int width = rect.width; 255 final int height = rect.height; 256 257 final int yh = y + height; 258 final int xw = x + width; 259 260 final float regionSum = sat.sum.pixels[yh][xw] - sat.sum.pixels[yh][x] 261 - sat.sum.pixels[y][xw] + sat.sum.pixels[y][x]; 262 263 total += regionSum * rect.weight; 264 } 265 266 return total; 267 } 268 } 269 270 /** 271 * Create a feature from the given data. The specific type of feature 272 * created depends on whether or not the feature is tilted. 273 * 274 * @param rectList 275 * the rectangles defining the feature 276 * @param tilted 277 * is the feature tilted? 278 * @return the new {@link HaarFeature} object. 279 */ 280 public static HaarFeature create(List<WeightedRectangle> rectList, boolean tilted) { 281 final WeightedRectangle[] rects = rectList.toArray(new WeightedRectangle[rectList.size()]); 282 283 if (tilted) 284 return new TiltedFeature(rects); 285 286 return new NormalFeature(rects); 287 } 288 289 /** 290 * Construct a feature with the given parameters. 291 * 292 * @param tilted 293 * is the feature tilted? 294 * @param x0 295 * x-ordinate of top-left of first rectangle 296 * @param y0 297 * y-ordinate of top-left of first rectangle 298 * @param w0 299 * width of first rectangle 300 * @param h0 301 * height of first rectangle 302 * @param wt0 303 * weight of first rectangle 304 * @param x1 305 * x-ordinate of top-left of second rectangle 306 * @param y1 307 * y-ordinate of top-left of second rectangle 308 * @param w1 309 * width of second rectangle 310 * @param h1 311 * height of second rectangle 312 * @param wt1 313 * weight of second rectangle 314 * @return the feature 315 */ 316 public static HaarFeature create(boolean tilted, 317 int x0, int y0, int w0, int h0, float wt0, 318 int x1, int y1, int w1, int h1, float wt1) 319 { 320 final WeightedRectangle[] rects = new WeightedRectangle[2]; 321 rects[0] = new WeightedRectangle(x0, y0, w0, h0, wt0); 322 rects[1] = new WeightedRectangle(x1, y1, w1, h1, wt1); 323 324 return tilted ? new TiltedFeature(rects) : new NormalFeature(rects); 325 } 326 327 /** 328 * Construct a feature with the given parameters. 329 * 330 * @param tilted 331 * is the feature tilted? 332 * @param x0 333 * x-ordinate of top-left of first rectangle 334 * @param y0 335 * y-ordinate of top-left of first rectangle 336 * @param w0 337 * width of first rectangle 338 * @param h0 339 * height of first rectangle 340 * @param wt0 341 * weight of first rectangle 342 * @param x1 343 * x-ordinate of top-left of second rectangle 344 * @param y1 345 * y-ordinate of top-left of second rectangle 346 * @param w1 347 * width of second rectangle 348 * @param h1 349 * height of second rectangle 350 * @param wt1 351 * weight of second rectangle 352 * @param x2 353 * x-ordinate of top-left of third rectangle 354 * @param y2 355 * y-ordinate of top-left of third rectangle 356 * @param w2 357 * width of third rectangle 358 * @param h2 359 * height of third rectangle 360 * @param wt2 361 * weight of third rectangle 362 * @return the feature 363 */ 364 public static HaarFeature create(boolean tilted, 365 int x0, int y0, int w0, int h0, float wt0, 366 int x1, int y1, int w1, int h1, float wt1, 367 int x2, int y2, int w2, int h2, float wt2) 368 { 369 final WeightedRectangle[] rects = new WeightedRectangle[3]; 370 rects[0] = new WeightedRectangle(x0, y0, w0, h0, wt0); 371 rects[1] = new WeightedRectangle(x1, y1, w1, h1, wt1); 372 rects[2] = new WeightedRectangle(x2, y2, w2, h2, wt2); 373 374 return tilted ? new TiltedFeature(rects) : new NormalFeature(rects); 375 } 376}