Commit 670ffb1152db5e127d12c1c5ae3d3150479c3c12

Authored by caotto
1 parent 659370e6

Preliminary update to the ct8 plugin

Various changes to make the plugin consistent with the current API.
Switch a number of data structures to QT things instead of openCV equivalents.
Make FRsdk::OpenCVImageBody construct either an rgb or grayscale representation
depending on input image type instead of always building both.
Mark failure cases in both the detection and enrollment transforms.
Allow the enrollment transform to work even if eye detection hasn't been done
yet (the sdk will automatically do detection using the default parameters).
Showing 1 changed file with 164 additions and 79 deletions
sdk/plugins/ct8.cpp
... ... @@ -11,22 +11,31 @@
11 11 #include <exception>
12 12 #include <string>
13 13 #include <vector>
14   -#include <mm_plugin.h>
  14 +#include <openbr_plugin.h>
15 15  
16   -#include "model.h"
17   -#include "resource.h"
  16 +#include "core/resource.h"
18 17  
19 18 using namespace cv;
20   -using namespace mm;
  19 +using namespace br;
21 20  
22 21 namespace FRsdk {
  22 + // Construct a FaceVACS sdk ImageBody from an opencv Mat
23 23 struct OpenCVImageBody : public ImageBody
24 24 {
25 25 OpenCVImageBody(const Mat &m, const std::string& _name = "")
26 26 : w(m.cols), h(m.rows), b(0), rgb(0), n(_name)
27 27 {
28   - buildRgbRepresentation(m);
29   - buildByteRepresentation();
  28 + // The ImageBody only needs to construct a grayscale or rgb
  29 + // representation (whichever is indicated by isColor()), not both.
  30 + if (m.channels() == 1) {
  31 + is_color = false;
  32 + buildByteRepresentation(m);
  33 + }
  34 + else {
  35 + buildRgbRepresentation(m);
  36 + is_color = true;
  37 + }
  38 +
30 39 }
31 40  
32 41 ~OpenCVImageBody()
... ... @@ -35,7 +44,7 @@ namespace FRsdk {
35 44 delete[] b;
36 45 }
37 46  
38   - bool isColor() const { return true; }
  47 + bool isColor() const { return is_color; }
39 48 unsigned int height() const { return h; }
40 49 unsigned int width() const { return w; }
41 50 const Byte* grayScaleRepresentation() const { return b; }
... ... @@ -58,26 +67,20 @@ namespace FRsdk {
58 67 }
59 68 }
60 69  
61   - void buildByteRepresentation()
  70 + void buildByteRepresentation(const Mat & m)
62 71 {
63 72 b = new Byte[w*h];
64   - Rgb* colorp = rgb;
65 73 Byte* grayp = b;
66 74 for( unsigned int i = 0; i < h; i++ ) {
67 75 for( unsigned int k = 0; k < w; k++ ) {
68   - float f = (float) colorp->r;
69   - f += (float) colorp->g;
70   - f += (float) colorp->b;
71   - f /= 3.0f;
72   - if( f > 255.0f) f = 255.0f;
73   - *grayp = (Byte) f;
74   - colorp++;
  76 + *grayp = (Byte) m.at<uchar>(i,k);
75 77 grayp++;
76 78 }
77 79 }
78 80 }
79 81  
80 82 private:
  83 + bool is_color;
81 84 unsigned int w;
82 85 unsigned int h;
83 86 Byte* b;
... ... @@ -86,6 +89,10 @@ namespace FRsdk {
86 89 };
87 90  
88 91  
  92 + // Enrollment::FeedbackBody subclasses are used as a set of callbacks
  93 + // during facevacs enrollment. This class keeps track of whether or not
  94 + // enrollment has failed (checkable via firValid()), and the extracted fir
  95 + // (getFir)
89 96 struct EnrolOpenCVFeedback : public Enrollment::FeedbackBody
90 97 {
91 98 EnrolOpenCVFeedback(Mat *_m)
... ... @@ -94,14 +101,11 @@ namespace FRsdk {
94 101  
95 102 EnrolOpenCVFeedback() {}
96 103  
97   - void start()
98   - {
99   - firvalid = false;
100   - }
  104 + void start() { firvalid = false; }
101 105  
102 106 void processingImage(const FRsdk::Image& img) { (void) img; }
103 107 void eyesFound( const FRsdk::Eyes::Location& eyeLoc) { (void) eyeLoc; }
104   - void eyesNotFound() {}
  108 + void eyesNotFound() { firvalid = false;}
105 109 void sampleQualityTooLow() {}
106 110 void sampleQuality(const float& f) { (void) f; }
107 111  
... ... @@ -113,7 +117,7 @@ namespace FRsdk {
113 117 firvalid = true;
114 118 }
115 119  
116   - void failure() {}
  120 + void failure() { firvalid = false; }
117 121 void end() {}
118 122  
119 123 const FRsdk::FIR& getFir() const
... ... @@ -122,10 +126,7 @@ namespace FRsdk {
122 126 return *fir;
123 127 }
124 128  
125   - bool firValid() const
126   - {
127   - return firvalid;
128   - }
  129 + bool firValid() const { return firvalid; }
129 130  
130 131 private:
131 132 FRsdk::CountedPtr<FRsdk::FIR> fir;
... ... @@ -139,16 +140,14 @@ struct CT8Initialize : public Initializer
139 140 {
140 141 static FRsdk::Configuration* CT8Configuration;
141 142  
  143 + // ct8 plugin initialization, load a FRsdk config file, and register
  144 + // the shortcut for using FaceVACS feature extraction/comparison
142 145 void initialize() const
143 146 {
144   - QFile file(":/3rdparty/ct8/activationkey.cfg");
145   - file.open(QFile::ReadOnly);
146   - QByteArray data = file.readAll();
147   - file.close();
148   - std::istringstream istream(QString(data).arg(Globals.SDKPath+"/models/ct8").toStdString());
149   -
150 147 try {
151   - CT8Configuration = new FRsdk::Configuration(istream);
  148 + // Need to do something different wrt getting the config file location -cao
  149 + CT8Configuration = new FRsdk::Configuration("C:/FVSDK_8_6_0/etc/frsdk.cfg");
  150 + Globals->abbreviations.insert("CT8","Open+CT8Detect!CT8Enroll:CT8Compare");
152 151 } catch (std::exception &e) {
153 152 qWarning("CT8Initialize Exception: %s", e.what());
154 153 CT8Configuration = NULL;
... ... @@ -164,17 +163,20 @@ struct CT8Initialize : public Initializer
164 163  
165 164 FRsdk::Configuration* CT8Initialize::CT8Configuration = NULL;
166 165  
167   -MM_REGISTER(Initializer, CT8Initialize, false)
168   -
  166 +BR_REGISTER(Initializer, CT8Initialize)
169 167  
170   -class CT8EnrollmentProcessorResource : public Resource<FRsdk::Enrollment::Processor>
  168 +// Adaptor class adding a default constructor to FRsdk::Enrollment::Processor
  169 +// so that it can be used with Resource
  170 +class CT8EnrollmentProcessor : public FRsdk::Enrollment::Processor
171 171 {
172   - QSharedPointer<FRsdk::Enrollment::Processor> make() const
  172 +public:
  173 + CT8EnrollmentProcessor() : FRsdk::Enrollment::Processor(*CT8Initialize::CT8Configuration)
173 174 {
174   - return QSharedPointer<FRsdk::Enrollment::Processor>(new FRsdk::Enrollment::Processor(*CT8Initialize::CT8Configuration));
  175 + //
175 176 }
176 177 };
177 178  
  179 +typedef Resource<CT8EnrollmentProcessor> CT8EnrollmentProcessorResource;
178 180  
179 181 struct CT8Context
180 182 {
... ... @@ -185,13 +187,12 @@ struct CT8Context
185 187 eyesFinder = new FRsdk::Eyes::Finder(*CT8Initialize::CT8Configuration);
186 188 firBuilder = new FRsdk::FIRBuilder(*CT8Initialize::CT8Configuration);
187 189 facialMatchingEngine = new FRsdk::FacialMatchingEngine(*CT8Initialize::CT8Configuration);
188   - enrollmentProcessors.init();
189 190 } catch (std::exception &e) {
190 191 qFatal("CT8Context Exception: %s", e.what());
191 192 }
192 193 }
193 194  
194   - ~CT8Context()
  195 + virtual ~CT8Context()
195 196 {
196 197 delete faceFinder;
197 198 delete eyesFinder;
... ... @@ -199,26 +200,62 @@ struct CT8Context
199 200 delete facialMatchingEngine;
200 201 }
201 202  
202   - void enroll(const FRsdk::Image &img, const FRsdk::Eyes::Location &eyes, Mat *m) const
  203 + // Enroll an FRsdk::Sample (can be various types, generally an image that
  204 + // maybe has some extra data like detected eye locations).
  205 + bool enroll(const FRsdk::Sample &sample, Mat *m) const
203 206 {
204 207 try {
205   - FRsdk::Sample sample(FRsdk::AnnotatedImage(img, eyes));
206 208 FRsdk::SampleSet sampleSet;
207 209 sampleSet.push_back(sample);
208 210  
209   - FRsdk::Enrollment::Feedback enrollmentFeedback(new FRsdk::EnrolOpenCVFeedback(m));
210   - int index;
211   - QSharedPointer<FRsdk::Enrollment::Processor> enrollmentProcessor = enrollmentProcessors.acquire(index);
  211 +
  212 + FRsdk::EnrolOpenCVFeedback * feedback_body = new FRsdk::EnrolOpenCVFeedback(m);
  213 + FRsdk::CountedPtr<FRsdk::Enrollment::FeedbackBody> feedback_ptr(feedback_body);
  214 +
  215 + FRsdk::Enrollment::Feedback enrollmentFeedback(feedback_ptr);
  216 +
  217 + CT8EnrollmentProcessor * enrollmentProcessor = enrollmentProcessors.acquire();
212 218 enrollmentProcessor->process(sampleSet.begin(), sampleSet.end(), enrollmentFeedback);
213   - enrollmentProcessors.release(index);
  219 + enrollmentProcessors.release(enrollmentProcessor);
  220 + if (!feedback_body->firValid()) return false;
214 221 } catch (std::exception &e) {
215 222 qFatal("CT8Context Exception: %s", e.what());
  223 + return false;
216 224 }
  225 + return true;
217 226 }
218 227  
219   - static FRsdk::Position toPosition(const Point2f &point)
  228 +
  229 + // Input: an image, and pre-detected eye locations, returns false if enrollment fails
  230 + bool enroll(const FRsdk::Image &img, const FRsdk::Eyes::Location &eyes, Mat *m) const
220 231 {
221   - return FRsdk::Position(point.x, point.y);
  232 + try {
  233 + FRsdk::Sample sample(FRsdk::AnnotatedImage(img, eyes));
  234 + return enroll(sample, m);
  235 + } catch (std::exception &e) {
  236 + qFatal("CT8Context Exception: %s", e.what());
  237 + return false;
  238 + }
  239 + return true;
  240 + }
  241 +
  242 + // Input: an image, no eye locations (facevacs will do detection with
  243 + // default parameters. Returns false if enrollment fails
  244 + bool enroll(const FRsdk::Image &img, Mat *m) const
  245 + {
  246 + try {
  247 + FRsdk::Sample sample(img);
  248 + return enroll(sample, m);
  249 + } catch (std::exception &e) {
  250 + qFatal("CT8Context Exception: %s", e.what());
  251 + return false;
  252 + }
  253 + return true;
  254 + }
  255 +
  256 + static FRsdk::Position toPosition(const QPointF &point)
  257 + {
  258 + return FRsdk::Position(point.x(), point.y());
222 259 }
223 260  
224 261 protected:
... ... @@ -230,30 +267,53 @@ protected:
230 267 };
231 268  
232 269  
233   -struct CT8Detect : public UntrainableFeature
  270 +struct CT8Detect : public UntrainableTransform
234 271 , public CT8Context
235 272 {
  273 + Q_OBJECT
  274 + // Perform face, then eye detection using the facevacs SDK
236 275 void project(const Template &src, Template &dst) const
237 276 {
238 277 try {
  278 + // Build an FRsdk image from the input openCV mat
239 279 FRsdk::CountedPtr<FRsdk::ImageBody> i(new FRsdk::OpenCVImageBody(src));
240 280 FRsdk::Image img(i);
241   - FRsdk::Face::LocationSet faceLocations = faceFinder->find(img, 0.01);
  281 +
  282 + // .01 here should be a parameter -cao
  283 + FRsdk::Face::LocationSet faceLocations = faceFinder->find(img, 0.01f);
  284 +
  285 + // If the face finder doesn't find anything mark the output as a failure
  286 + if (faceLocations.empty() ) {
  287 + dst.file.setBool("FTE");
  288 + return;
  289 + }
242 290  
243   - QList<Rect> ROIs;
244   - QList<Point2f> landmarks;
  291 + QList<QRectF> ROIs;
  292 + QList<QPointF> landmarks;
245 293 FRsdk::Face::LocationSet::const_iterator faceLocationSetIterator = faceLocations.begin();
  294 + bool any_eyes = false;
  295 +
  296 + // Attempt to detect eyes in any face ROIs that were detected
246 297 while (faceLocationSetIterator != faceLocations.end()) {
247 298 FRsdk::Face::Location faceLocation = *faceLocationSetIterator; faceLocationSetIterator++;
248 299 FRsdk::Eyes::LocationSet currentEyesLocations = eyesFinder->find(img, faceLocation);
  300 +
249 301 if (currentEyesLocations.size() > 0) {
250   - ROIs.append(Rect(faceLocation.pos.x(), faceLocation.pos.y(), faceLocation.width, faceLocation.width));
251   - landmarks.append(Point2f(currentEyesLocations.front().first.x(), currentEyesLocations.front().first.y()));
252   - landmarks.append(Point2f(currentEyesLocations.front().second.x(), currentEyesLocations.front().second.y()));
  302 + any_eyes = true;
  303 + ROIs.append(QRectF(faceLocation.pos.x(), faceLocation.pos.y(), faceLocation.width, faceLocation.width));
  304 + landmarks.append(QPointF(currentEyesLocations.front().first.x(), currentEyesLocations.front().first.y()));
  305 + landmarks.append(QPointF(currentEyesLocations.front().second.x(), currentEyesLocations.front().second.y()));
  306 +
253 307 dst += src;
254 308 }
255 309  
256   - if (!Globals.EnrollAll && !dst.isEmpty()) break;
  310 + if (any_eyes && !Globals->enrollAll && !dst.isEmpty()) break;
  311 + }
  312 +
  313 + // If eye detection failed, mark the output as a failure
  314 + if (!any_eyes) {
  315 + dst.file.setBool("FTE");
  316 + return;
257 317 }
258 318  
259 319 dst.file.setROIs(ROIs);
... ... @@ -262,61 +322,84 @@ struct CT8Detect : public UntrainableFeature
262 322 qFatal("CT8Enroll Exception: %s", e.what());
263 323 }
264 324  
265   - if (!Globals.EnrollAll && dst.isEmpty()) dst += Mat();
  325 + if (!Globals->enrollAll && dst.isEmpty()) dst += Mat();
266 326 }
267 327 };
268 328  
269   -MM_REGISTER(Feature, CT8Detect, false)
  329 +BR_REGISTER(Transform, CT8Detect)
270 330  
271 331  
272   -struct CT8Enroll : public UntrainableFeature
  332 +struct CT8Enroll : public UntrainableTransform
273 333 , public CT8Context
274 334 {
  335 + Q_OBJECT
  336 + // enroll an image using the facevacs sdk. Generates a facevacs "fir" which
  337 + // is their face representation.
275 338 void project(const Template &src, Template &dst) const
276 339 {
277 340 try {
278 341 FRsdk::CountedPtr<FRsdk::ImageBody> i(new FRsdk::OpenCVImageBody(src));
279 342 FRsdk::Image img(i);
280 343  
281   - QList<Point2f> landmarks = src.file.landmarks();
  344 + // If we already have eye locations, use them
  345 + QList<QPointF> landmarks = src.file.landmarks();
  346 + bool enroll_succeeded = false;
282 347 if (landmarks.size() == 2) {
283   - enroll(img, FRsdk::Eyes::Location(toPosition(landmarks[0]), toPosition(landmarks[1])), dst.mp());
284   - dst.file.insert("CT8_First_Eye_X", landmarks[0].x);
285   - dst.file.insert("CT8_First_Eye_Y", landmarks[0].y);
286   - dst.file.insert("CT8_Second_Eye_X", landmarks[1].x);
287   - dst.file.insert("CT8_Second_Eye_Y", landmarks[1].y);
  348 + enroll_succeeded = enroll(img, FRsdk::Eyes::Location(toPosition(landmarks[0]), toPosition(landmarks[1])), &(dst.m()));
288 349  
289   - QList<Rect> ROIs = src.file.ROIs();
  350 + // Transfer previously detectd eye and face locations to the output dst.
  351 + dst.file.insert("CT8_First_Eye_X", landmarks[0].x());
  352 + dst.file.insert("CT8_First_Eye_Y", landmarks[0].y());
  353 + dst.file.insert("CT8_Second_Eye_X", landmarks[1].x());
  354 + dst.file.insert("CT8_Second_Eye_Y", landmarks[1].y());
  355 +
  356 + QList<QRectF> ROIs = src.file.ROIs();
290 357 if (ROIs.size() == 1) {
291   - dst.file.insert("CT8_Face_X", ROIs.first().x);
292   - dst.file.insert("CT8_Face_Y", ROIs.first().y);
293   - dst.file.insert("CT8_Face_Width", ROIs.first().width);
294   - dst.file.insert("CT8_Face_Height", ROIs.first().height);
  358 + dst.file.insert("CT8_Face_X", ROIs.first().x());
  359 + dst.file.insert("CT8_Face_Y", ROIs.first().y());
  360 + dst.file.insert("CT8_Face_Width", ROIs.first().width());
  361 + dst.file.insert("CT8_Face_Height", ROIs.first().height());
295 362 }
296 363 } else {
297   - dst = Mat();
  364 + // If we don't have eye locations already, calling enroll here
  365 + // will cause facevacs to perform detection using default
  366 + // parameters (and we will not receive the detected locations
  367 + // as output).
  368 + enroll_succeeded = enroll(img, &(dst.m()));
  369 + }
  370 + // If enrollment failed, mark this image as a failure. This will
  371 + // typically only happen if we aren't using pre-detected eye
  372 + // locations
  373 + if (!enroll_succeeded)
  374 + {
  375 + dst.file.setBool("FTE");
  376 + return;
298 377 }
  378 +
299 379 } catch (std::exception &e) {
300 380 qFatal("CT8Enroll Exception: %s", e.what());
301 381 }
302 382 }
303 383 };
304 384  
305   -MM_REGISTER(Feature, CT8Enroll, false)
  385 +BR_REGISTER(Transform, CT8Enroll)
306 386  
307 387  
308   -struct CT8Compare : public ComparerBase,
  388 +struct CT8Compare : public Distance,
309 389 public CT8Context
310 390 {
311   - float compare(const Mat &srcA, const Mat &srcB) const
  391 + Q_OBJECT
  392 +
  393 + // Compare pre-extracted facevacs templates
  394 + float _compare(const Template &srcA, const Template &srcB) const
312 395 {
313 396 const float DefaultNonMatchScore = 0;
314   - if (!srcA.data || !srcB.data) return DefaultNonMatchScore;
  397 + if (!srcA.m().data || !srcB.m().data) return DefaultNonMatchScore;
315 398  
316 399 float score = DefaultNonMatchScore;
317 400 try {
318   - FRsdk::FIR firA = firBuilder->build((FRsdk::Byte*)srcA.data, srcA.cols);
319   - FRsdk::FIR firB = firBuilder->build((FRsdk::Byte*)srcB.data, srcB.cols);
  401 + FRsdk::FIR firA = firBuilder->build( (FRsdk::Byte *) srcA.m().data, srcA.m().cols);
  402 + FRsdk::FIR firB = firBuilder->build( (FRsdk::Byte *) srcB.m().data, srcB.m().cols);
320 403 score = (float)facialMatchingEngine->compare(firA, firB);
321 404 } catch (std::exception &e) {
322 405 qFatal("CT8Compare Exception: %s", e.what());
... ... @@ -326,5 +409,7 @@ struct CT8Compare : public ComparerBase,
326 409 }
327 410 };
328 411  
329   -MM_REGISTER(Comparer, CT8Compare, false)
330   -MM_REGISTER_ALGORITHM(CT8, "Open+CT8Detect!CT8Enroll:CT8Compare")
  412 +BR_REGISTER(Distance, CT8Compare)
  413 +
  414 +#include "plugins/ct8.moc"
  415 +
... ...