1 /**
2 * FaceTracker Licence
3 * -------------------
4 * (Academic, non-commercial, not-for-profit licence)
5 *
6 * Copyright (c) 2010 Jason Mora Saragih
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions are met:
11 *
12 * * The software is provided under the terms of this licence stricly for
13 * academic, non-commercial, not-for-profit purposes.
14 * * Redistributions of source code must retain the above copyright notice,
15 * this list of conditions (licence) and the following disclaimer.
16 * * Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions (licence) and the following disclaimer
18 * in the documentation and/or other materials provided with the
19 * distribution.
20 * * The name of the author may not be used to endorse or promote products
21 * derived from this software without specific prior written permission.
22 * * As this software depends on other libraries, the user must adhere to and
23 * keep in place any licencing terms of those libraries.
24 * * Any publications arising from the use of this software, including but
25 * not limited to academic journal and conference publications, technical
26 * reports and manuals, must cite the following work:
27 *
28 * J. M. Saragih, S. Lucey, and J. F. Cohn. Face Alignment through Subspace
29 * Constrained Mean-Shifts. International Journal of Computer Vision
30 * (ICCV), September, 2009.
31 *
32 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
33 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
34 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
35 * EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
36 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
37 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
39 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
40 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
41 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43 package com.jsaragih;
44
45 import java.io.BufferedReader;
46 import java.io.BufferedWriter;
47 import java.io.FileInputStream;
48 import java.io.FileNotFoundException;
49 import java.io.FileReader;
50 import java.io.FileWriter;
51 import java.io.IOException;
52 import java.util.List;
53 import java.util.Scanner;
54
55 import javax.xml.stream.XMLStreamException;
56
57 import org.openimaj.image.FImage;
58 import org.openimaj.image.objectdetection.filtering.OpenCVGrouping;
59 import org.openimaj.image.objectdetection.haar.Detector;
60 import org.openimaj.image.objectdetection.haar.OCVHaarLoader;
61 import org.openimaj.image.objectdetection.haar.StageTreeClassifier;
62 import org.openimaj.image.processing.algorithm.EqualisationProcessor;
63 import org.openimaj.image.processing.resize.ResizeProcessor;
64 import org.openimaj.math.geometry.shape.Rectangle;
65 import org.openimaj.util.pair.ObjectIntPair;
66
67 /**
68 * Face detector.
69 * <p>
70 * Note: the face detector in any input file is ignored and is replaced by the
71 * haarcascade_frontalface_alt2 cascade.
72 *
73 * @author Jason Mora Saragih
74 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
75 */
76 public class FDet {
77 static {
78 Tracker.init();
79 }
80
81 private static final int CV_HAAR_FEATURE_MAX = 3;
82
83 int _min_neighbours;
84 int _min_size;
85 float _img_scale;
86 float _scale_factor;
87 StageTreeClassifier _cascade;
88
89 FImage small_img_;
90
91 private Detector detector;
92
93 private OpenCVGrouping grouping;
94
95 FDet(final String fname, final float img_scale, final float scale_factor,
96 final int min_neighbours, final int min_size) throws IOException,
97 XMLStreamException
98 {
99 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 }