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}