summaryrefslogtreecommitdiffstats
path: root/src/filter/facedetect/facedetect.cpp
blob: c7913d80ed48ee0714045d041398abe41f0f5a89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/**
 * Copyright (C) 2007 binarymillenium
 * Copyright (C) 2011 Dan Dennedy <[email protected]>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include <limits.h>
#include <time.h>
#include <ctype.h>
#include <opencv/cv.h>
#include "frei0r.hpp"

#define USE_ROI
#define PAD (40)

#define FACEBL0R_PARAM_CLASSIFIER (0)

class FaceDetect;
frei0r::construct<FaceDetect> plugin("opencvfacedetect",
				  "detect faces and draw shapes on them",
				  "binarymillenium, ddennedy",
				  2,0, F0R_COLOR_MODEL_PACKED32);

class FaceDetect: public frei0r::filter
{
private:
    IplImage *image;

    unsigned count;
    CvSeq *objects;
    CvRect roi;
    CvMemStorage *storage;
    CvHaarClassifierCascade *cascade;

    // plugin parameters
    f0r_param_double shape;
    f0r_param_double recheck;
    f0r_param_double threads;
    f0r_param_double search_scale;
    f0r_param_double neighbors;
    f0r_param_double smallest;
    f0r_param_double scale;
    f0r_param_double stroke;
    f0r_param_bool   antialias;
    f0r_param_double alpha;
    f0r_param_color  color[5];
	
public:
    FaceDetect(int width, int height)
        : image(0)
        , count(0)
        , objects(0)
        , storage(0)
        , cascade(0)
    {
        roi.width = roi.height = 0;
        register_param("/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml",
                       "Classifier",
                       "Full path to the XML pattern model for recognition; look in /usr/share/opencv/haarcascades"); 
        threads = 0.0; //number of CPUs
        register_param(threads, "Threads", "How many threads to use divided by 100; 0 uses CPU count");
        shape = 0.0;
        register_param(shape, "Shape", "The shape to draw: 0=circle, 0.1=ellipse, 0.2=rectangle, 1=random");
        recheck = 0.025;
        register_param(recheck, "Recheck", "How often to detect an object in number of frames, divided by 1000");
        search_scale = 0.12; // increase size of search window by 20% on each pass
        register_param(search_scale, "Search scale", "The search window scale factor, divided by 10");
        neighbors = 0.02; // require 2 neighbors
        register_param(neighbors, "Neighbors", "Minimum number of rectangles that makes up an object, divided by 100");
        smallest = 0.0; // smallest window size is trained default
        register_param(smallest, "Smallest", "Minimum window size in pixels, divided by 1000");
        scale = 1.0 / 1.5;
        register_param(scale, "Scale", "Down scale the image prior detection");
        stroke = CV_FILLED;
        register_param(stroke, "Stroke", "Line width, divided by 100, or fill if 0");
        antialias = false;
        register_param(antialias, "Antialias", "Draw with antialiasing");
        alpha = 1.0;
        register_param(alpha, "Alpha", "The alpha channel value for the shapes");
        f0r_param_color color0 = {1.0, 1.0, 1.0};
        color[0] = color0;
        register_param(color[0], "Color 1", "The color of the first object");
        f0r_param_color color1 = {0.0, 0.5, 1.0};
        color[1] = color1;
        register_param(color[0], "Color 2", "The color of the second object");
        f0r_param_color color2 = {0.0, 1.0, 1.0};
        color[2] = color2;
        register_param(color[0], "Color 3", "The color of the third object");
        f0r_param_color color3 = {0.0, 1.0, 0.0};
        color[3] = color3;
        register_param(color[0], "Color 4", "The color of the fourth object");
        f0r_param_color color4 = {1.0, 0.5, 0.0};
        color[4] = color4;
        register_param(color[0], "Color 5", "The color of the fifth object");
        srand(::time(NULL));
    }

    ~FaceDetect()
    {
        if (cascade) cvReleaseHaarClassifierCascade(&cascade);
        if (storage) cvReleaseMemStorage(&storage);        
    }

    void update()
    {
        if (!cascade) {
            cvSetNumThreads(cvRound(threads * 100));
            f0r_param_string classifier;
            get_param_value(&classifier, FACEBL0R_PARAM_CLASSIFIER);
            if (classifier) {
                cascade = (CvHaarClassifierCascade*) cvLoad(classifier, 0, 0, 0 );
                if (!cascade)
                    fprintf(stderr, "ERROR: Could not load classifier cascade %s\n", classifier);
                storage = cvCreateMemStorage(0);
            }
            else {
                memcpy(out, in, size * 4);
                return;
            }
        }
        
        // copy input image to OpenCV
        if( !image )
            image = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 4);
        memcpy(image->imageData, in, size * 4);
        
        // only re-detect periodically to control performance and reduce shape jitter
        int recheckInt = abs(cvRound(recheck * 1000));
        if ( recheckInt > 0 && count % recheckInt )
        {
            // skip detect
            count++;
//            fprintf(stderr, "draw-only counter %u\n", count);
        }
        else
        {
            count = 1;   // reset the recheck counter
            if (objects) // reset the list of objects
                cvClearSeq(objects);
            
            double elapsed = (double) cvGetTickCount();

            objects = detect();

            // use detection time to throttle frequency of re-detect vs. redraw (automatic recheck)
            elapsed = cvGetTickCount() - elapsed;
            elapsed = elapsed / ((double) cvGetTickFrequency() * 1000.0);

            // Automatic recheck uses an undocumented negative parameter value,
            // which is not compliant, but technically feasible.
            if (recheck < 0 && cvRound( elapsed / (1000.0 / (recheckInt + 1)) ) <= recheckInt)
                    count += recheckInt - cvRound( elapsed / (1000.0 / (recheckInt + 1)));
//            fprintf(stderr, "detection time = %gms counter %u\n", elapsed, count);
        }
        
        draw();
        
        // copy filtered OpenCV image to output
        memcpy(out, image->imageData, size * 4);
        cvReleaseImage(&image);
    }
    
private:
    CvSeq* detect()
    {
        if (!cascade) return 0;
        double scale = this->scale == 0? 1.0 : this->scale;
        IplImage* gray = cvCreateImage(cvSize(width, height ), 8, 1);
        IplImage* small = cvCreateImage(cvSize(cvRound(width * scale), cvRound(height * scale)), 8, 1);
        int min = cvRound(smallest * 1000);            
        CvSeq* faces = 0;
        
        // use a region of interest to improve performance
        // This idea comes from the More than Technical blog:
        // http://www.morethantechnical.com/2009/08/09/near-realtime-face-detection-on-the-iphone-w-opencv-port-wcodevideo/
        if ( roi.width > 0 && roi.height > 0)
        {
            cvSetImageROI(small, roi);
            CvRect scaled_roi = cvRect(roi.x / scale, roi.y / scale,
                                       roi.width / scale, roi.height / scale);
            cvSetImageROI(image, scaled_roi);
            cvSetImageROI(gray, scaled_roi);
        }
        
        // use an equalized grayscale to improve detection
        cvCvtColor(image, gray, CV_BGR2GRAY);
        // use a smaller image to improve performance
        cvResize(gray, small, CV_INTER_LINEAR);
        cvEqualizeHist(small, small);
        
        // detect with OpenCV
        cvClearMemStorage(storage);
        faces = cvHaarDetectObjects(small, cascade, storage,
                                    search_scale * 10.0,
                                    cvRound(neighbors * 100),
                                    CV_HAAR_DO_CANNY_PRUNING,
                                    cvSize(min, min));
        
#ifdef USE_ROI
        if (!faces || faces->total == 0)
        {
            // clear the region of interest
            roi.width = roi.height = 0;
        }
        else if (faces && faces->total > 0)
        {
            // determine the region of interest from the first detected object
            // XXX: based on the first object only?
            CvRect* r = (CvRect*) cvGetSeqElem(faces, 0);
            
            if (roi.width > 0 && roi.height > 0)
            {
                r->x += roi.x;
                r->y += roi.y;
            }
            int startX = MAX(r->x - PAD, 0);
            int startY = MAX(r->y - PAD, 0);
            int w = small->width - startX - r->width - PAD * 2;
            int h = small->height - startY - r->height - PAD * 2;
            int sw = r->x - PAD, sh = r->y - PAD;
            
            // store the region of interest
            roi.x = startX;
            roi.y = startY,
            roi.width = r->width + PAD * 2 + ((w < 0) ? w : 0) + ((sw < 0) ? sw : 0);
            roi.height = r->height + PAD * 2 + ((h < 0) ? h : 0) + ((sh < 0) ? sh : 0); 
        }
#endif
        cvReleaseImage(&gray);
        cvReleaseImage(&small);
        cvResetImageROI(image);
        return faces;
    }
    
    void draw()
    {
        double scale = this->scale == 0? 1.0 : this->scale;
        CvScalar colors[5] = {
            {{cvRound(color[0].r * 255), cvRound(color[0].g * 255), cvRound(color[0].b * 255), cvRound(alpha * 255)}},
            {{cvRound(color[1].r * 255), cvRound(color[1].g * 255), cvRound(color[1].b * 255), cvRound(alpha * 255)}},
            {{cvRound(color[2].r * 255), cvRound(color[2].g * 255), cvRound(color[2].b * 255), cvRound(alpha * 255)}},
            {{cvRound(color[3].r * 255), cvRound(color[3].g * 255), cvRound(color[3].b * 255), cvRound(alpha * 255)}},
            {{cvRound(color[4].r * 255), cvRound(color[4].g * 255), cvRound(color[4].b * 255), cvRound(alpha * 255)}},
        };
        
        for (int i = 0; i < (objects ? objects->total : 0); i++)
        {
            CvRect* r = (CvRect*) cvGetSeqElem(objects, i);
            CvPoint center;
            int thickness = stroke <= 0? CV_FILLED : cvRound(stroke * 100);
            int linetype = antialias? CV_AA : 8;
            
            center.x = cvRound((r->x + r->width * 0.5) / scale);
            center.y = cvRound((r->y + r->height * 0.5) / scale);
            
            switch (shape == 1.0? (rand() % 3) : cvRound(shape * 10))
            {
            default:
            case 0:
                {
                    int radius = cvRound((r->width + r->height) * 0.25 / scale);
                    cvCircle(image, center, radius, colors[i % 5], thickness, linetype);
                    break;
                }
            case 1:
                {
                    CvBox2D box = {{center.x, center.y}, {r->width / scale, (r->height / scale) * 1.2}, 90};
                    cvEllipseBox(image, box, colors[i % 5], thickness, linetype);
                    break;
                }
            case 2:
                {
                    CvPoint pt1 = {r->x / scale, r->y / scale};
                    CvPoint pt2 = {(r->x + r->width) / scale, (r->y + r->height) / scale};
                    cvRectangle(image, pt1, pt2, colors[i % 5], thickness, linetype);
                    break;
                }
            }
        }
    }
};