View Javadoc

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 }