001/** 002 * FaceTracker Licence 003 * ------------------- 004 * (Academic, non-commercial, not-for-profit licence) 005 * 006 * Copyright (c) 2010 Jason Mora Saragih 007 * All rights reserved. 008 * 009 * Redistribution and use in source and binary forms, with or without 010 * modification, are permitted provided that the following conditions are met: 011 * 012 * * The software is provided under the terms of this licence stricly for 013 * academic, non-commercial, not-for-profit purposes. 014 * * Redistributions of source code must retain the above copyright notice, 015 * this list of conditions (licence) and the following disclaimer. 016 * * Redistributions in binary form must reproduce the above copyright 017 * notice, this list of conditions (licence) and the following disclaimer 018 * in the documentation and/or other materials provided with the 019 * distribution. 020 * * The name of the author may not be used to endorse or promote products 021 * derived from this software without specific prior written permission. 022 * * As this software depends on other libraries, the user must adhere to and 023 * keep in place any licencing terms of those libraries. 024 * * Any publications arising from the use of this software, including but 025 * not limited to academic journal and conference publications, technical 026 * reports and manuals, must cite the following work: 027 * 028 * J. M. Saragih, S. Lucey, and J. F. Cohn. Face Alignment through Subspace 029 * Constrained Mean-Shifts. International Journal of Computer Vision 030 * (ICCV), September, 2009. 031 * 032 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED 033 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 034 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 035 * EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 036 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 037 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 038 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 039 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 040 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 041 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 042 */ 043package com.jsaragih; 044 045import java.io.BufferedReader; 046import java.io.BufferedWriter; 047import java.io.FileInputStream; 048import java.io.FileNotFoundException; 049import java.io.FileReader; 050import java.io.FileWriter; 051import java.io.IOException; 052import java.util.List; 053import java.util.Scanner; 054 055import javax.xml.stream.XMLStreamException; 056 057import org.openimaj.image.FImage; 058import org.openimaj.image.objectdetection.filtering.OpenCVGrouping; 059import org.openimaj.image.objectdetection.haar.Detector; 060import org.openimaj.image.objectdetection.haar.OCVHaarLoader; 061import org.openimaj.image.objectdetection.haar.StageTreeClassifier; 062import org.openimaj.image.processing.algorithm.EqualisationProcessor; 063import org.openimaj.image.processing.resize.ResizeProcessor; 064import org.openimaj.math.geometry.shape.Rectangle; 065import org.openimaj.util.pair.ObjectIntPair; 066 067/** 068 * Face detector. 069 * <p> 070 * Note: the face detector in any input file is ignored and is replaced by the 071 * haarcascade_frontalface_alt2 cascade. 072 * 073 * @author Jason Mora Saragih 074 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 075 */ 076public class FDet { 077 static { 078 Tracker.init(); 079 } 080 081 private static final int CV_HAAR_FEATURE_MAX = 3; 082 083 int _min_neighbours; 084 int _min_size; 085 float _img_scale; 086 float _scale_factor; 087 StageTreeClassifier _cascade; 088 089 FImage small_img_; 090 091 private Detector detector; 092 093 private OpenCVGrouping grouping; 094 095 FDet(final String fname, final float img_scale, final float scale_factor, 096 final int min_neighbours, final int min_size) throws IOException, 097 XMLStreamException 098 { 099 final FileInputStream fis = new FileInputStream(fname); 100 this._cascade = OCVHaarLoader.read(fis); 101 fis.close(); 102 103 this._img_scale = img_scale; 104 this._scale_factor = scale_factor; 105 this._min_neighbours = min_neighbours; 106 this._min_size = min_size; 107 108 this.setupDetector(); 109 } 110 111 FDet() { 112 try { 113 this._cascade = OCVHaarLoader.read(OCVHaarLoader.class.getResourceAsStream("haarcascade_frontalface_alt2.xml")); 114 } catch (final Exception e) { 115 throw new RuntimeException(e); 116 } 117 } 118 119 /** 120 * Detect faces in an image 121 * 122 * @param im 123 * the image 124 * @return the detected faces 125 */ 126 public List<Rectangle> detect(final FImage im) { 127 final int w = Math.round(im.width / this._img_scale); 128 final int h = Math.round(im.height / this._img_scale); 129 130 this.small_img_ = ResizeProcessor.resample(im, w, h).processInplace( 131 new EqualisationProcessor()); 132 133 List<Rectangle> rects = this.detector.detect(this.small_img_); 134 rects = ObjectIntPair.getFirst(this.grouping.apply(rects)); 135 for (final Rectangle r : rects) { 136 r.scale(this._img_scale); 137 } 138 139 return rects; 140 } 141 142 private void setupDetector() { 143 this.detector = new Detector(this._cascade, this._scale_factor); 144 this.grouping = new OpenCVGrouping(this._min_neighbours); 145 this.detector.setMinimumDetectionSize(this._min_size); 146 } 147 148 static FDet load(final String fname) throws FileNotFoundException { 149 BufferedReader br = null; 150 try { 151 br = new BufferedReader(new FileReader(fname)); 152 final Scanner sc = new Scanner(br); 153 return FDet.read(sc, true); 154 } finally { 155 try { 156 br.close(); 157 } catch (final IOException e) { 158 } 159 } 160 } 161 162 void save(final String fname) throws IOException { 163 BufferedWriter bw = null; 164 try { 165 bw = new BufferedWriter(new FileWriter(fname)); 166 167 this.write(bw); 168 } finally { 169 try { 170 if (bw != null) 171 bw.close(); 172 } catch (final IOException e) { 173 } 174 } 175 } 176 177 void write(final BufferedWriter s) { 178 // _cascade. 179 // int i,j,k,l; 180 // s.write( 181 // IO.Types.FDET.ordinal() + " " 182 // + _min_neighbours + " " 183 // + _min_size + " " 184 // + _img_scale + " " 185 // + _scale_factor + " " 186 // + _cascade.count + " " 187 // + _cascade.orig_window_size.width + " " 188 // + _cascade.orig_window_size.height + " " 189 // ); 190 // 191 // for(i = 0; i < _cascade->count; i++){ 192 // s + _cascade->stage_classifier[i].parent + " " 193 // + _cascade->stage_classifier[i].next + " " 194 // + _cascade->stage_classifier[i].child + " " 195 // + _cascade->stage_classifier[i].threshold + " " 196 // + _cascade->stage_classifier[i].count + " "; 197 // for(j = 0; j < _cascade->stage_classifier[i].count; j++){ 198 // CvHaarClassifier* classifier = 199 // &_cascade->stage_classifier[i].classifier[j]; 200 // s + classifier->count + " "; 201 // for(k = 0; k < classifier->count; k++){ 202 // s + classifier->threshold[k] + " " 203 // + classifier->left[k] + " " 204 // + classifier->right[k] + " " 205 // + classifier->alpha[k] + " " 206 // + classifier->haar_feature[k].tilted + " "; 207 // for(l = 0; l < CV_HAAR_FEATURE_MAX; l++){ 208 // s + classifier->haar_feature[k].rect[l].weight + " " 209 // + classifier->haar_feature[k].rect[l].r.x + " " 210 // + classifier->haar_feature[k].rect[l].r.y + " " 211 // + classifier->haar_feature[k].rect[l].r.width + " " 212 // + classifier->haar_feature[k].rect[l].r.height + " "; 213 // } 214 // } 215 // s + classifier->alpha[classifier->count] + " "; 216 // } 217 // } 218 } 219 220 /** 221 * Read the Face detector. 222 * 223 * @param s 224 * @param readType 225 * @return the face detector 226 */ 227 public static FDet read(final Scanner s, final boolean readType) { 228 // FIXME: maybe this should actually read the cascade!! 229 if (readType) { 230 final int type = s.nextInt(); 231 assert (type == IO.Types.FDET.ordinal()); 232 } 233 234 final FDet fdet = new FDet(); 235 fdet._min_neighbours = s.nextInt(); 236 fdet._min_size = s.nextInt(); 237 fdet._img_scale = s.nextFloat(); 238 fdet._scale_factor = s.nextFloat(); 239 final int n = s.nextInt(); 240 241 // m = sizeof(CvHaarClassifierCascade)+n*sizeof(CvHaarStageClassifier); 242 // _cascade = (CvHaarClassifierCascade*)cvAlloc(m); 243 // memset(_cascade,0,m); 244 // _cascade->stage_classifier = (CvHaarStageClassifier*)(_cascade + 1); 245 // _cascade->flags = CV_HAAR_MAGIC_VAL; 246 // _cascade->count = n; 247 248 // s >> _cascade->orig_window_size.width >> 249 // _cascade->orig_window_size.height; 250 s.next(); 251 s.next(); 252 253 for (int i = 0; i < n; i++) { 254 // s >> _cascade->stage_classifier[i].parent 255 s.next(); 256 // >> _cascade->stage_classifier[i].next 257 s.next(); 258 // >> _cascade->stage_classifier[i].child 259 s.next(); 260 // >> _cascade->stage_classifier[i].threshold 261 s.next(); 262 // >> _cascade->stage_classifier[i].count; 263 final int count = s.nextInt(); 264 265 // _cascade->stage_classifier[i].classifier = 266 // (CvHaarClassifier*)cvAlloc(_cascade->stage_classifier[i].count* 267 // sizeof(CvHaarClassifier)); 268 for (int j = 0; j < count; j++) { 269 // CvHaarClassifier* classifier = 270 // &_cascade->stage_classifier[i].classifier[j]; 271 // s >> classifier->count; 272 final int ccount = s.nextInt(); 273 274 // classifier->haar_feature = (CvHaarFeature*) 275 // cvAlloc(classifier->count*(sizeof(CvHaarFeature) + 276 // sizeof(float) + sizeof(int) + sizeof(int)) + 277 // (classifier->count+1)*sizeof(float)); 278 // classifier->threshold = 279 // (float*)(classifier->haar_feature+classifier->count); 280 // classifier->left = (int*)(classifier->threshold + 281 // classifier->count); 282 // classifier->right = (int*)(classifier->left + 283 // classifier->count); 284 // classifier->alpha = (float*)(classifier->right + 285 // classifier->count); 286 for (int k = 0; k < ccount; k++) { 287 // s >> classifier->threshold[k] 288 s.next(); 289 // >> classifier->left[k] 290 s.next(); 291 // >> classifier->right[k] 292 s.next(); 293 // >> classifier->alpha[k] 294 s.next(); 295 // >> classifier->haar_feature[k].tilted; 296 s.next(); 297 for (int l = 0; l < FDet.CV_HAAR_FEATURE_MAX; l++) { 298 // s >> classifier->haar_feature[k].rect[l].weight 299 s.next(); 300 // >> classifier->haar_feature[k].rect[l].r.x 301 s.next(); 302 // >> classifier->haar_feature[k].rect[l].r.y 303 s.next(); 304 // >> classifier->haar_feature[k].rect[l].r.width 305 s.next(); 306 // >> classifier->haar_feature[k].rect[l].r.height; 307 s.next(); 308 } 309 } 310 // s >> classifier->alpha[classifier->count]; 311 s.next(); 312 } 313 } 314 315 fdet.setupDetector(); 316 317 return fdet; 318 } 319 320 /** 321 * @return the _min_size 322 */ 323 public int get_min_size() 324 { 325 return this._min_size; 326 } 327 328 /** 329 * @param _min_size the _min_size to set 330 */ 331 public void set_min_size( final int _min_size ) 332 { 333 this._min_size = _min_size; 334 this.setupDetector(); 335 } 336}