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,22 +11,31 @@
11 #include <exception> 11 #include <exception>
12 #include <string> 12 #include <string>
13 #include <vector> 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 using namespace cv; 18 using namespace cv;
20 -using namespace mm; 19 +using namespace br;
21 20
22 namespace FRsdk { 21 namespace FRsdk {
  22 + // Construct a FaceVACS sdk ImageBody from an opencv Mat
23 struct OpenCVImageBody : public ImageBody 23 struct OpenCVImageBody : public ImageBody
24 { 24 {
25 OpenCVImageBody(const Mat &m, const std::string& _name = "") 25 OpenCVImageBody(const Mat &m, const std::string& _name = "")
26 : w(m.cols), h(m.rows), b(0), rgb(0), n(_name) 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 ~OpenCVImageBody() 41 ~OpenCVImageBody()
@@ -35,7 +44,7 @@ namespace FRsdk { @@ -35,7 +44,7 @@ namespace FRsdk {
35 delete[] b; 44 delete[] b;
36 } 45 }
37 46
38 - bool isColor() const { return true; } 47 + bool isColor() const { return is_color; }
39 unsigned int height() const { return h; } 48 unsigned int height() const { return h; }
40 unsigned int width() const { return w; } 49 unsigned int width() const { return w; }
41 const Byte* grayScaleRepresentation() const { return b; } 50 const Byte* grayScaleRepresentation() const { return b; }
@@ -58,26 +67,20 @@ namespace FRsdk { @@ -58,26 +67,20 @@ namespace FRsdk {
58 } 67 }
59 } 68 }
60 69
61 - void buildByteRepresentation() 70 + void buildByteRepresentation(const Mat & m)
62 { 71 {
63 b = new Byte[w*h]; 72 b = new Byte[w*h];
64 - Rgb* colorp = rgb;  
65 Byte* grayp = b; 73 Byte* grayp = b;
66 for( unsigned int i = 0; i < h; i++ ) { 74 for( unsigned int i = 0; i < h; i++ ) {
67 for( unsigned int k = 0; k < w; k++ ) { 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 grayp++; 77 grayp++;
76 } 78 }
77 } 79 }
78 } 80 }
79 81
80 private: 82 private:
  83 + bool is_color;
81 unsigned int w; 84 unsigned int w;
82 unsigned int h; 85 unsigned int h;
83 Byte* b; 86 Byte* b;
@@ -86,6 +89,10 @@ namespace FRsdk { @@ -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 struct EnrolOpenCVFeedback : public Enrollment::FeedbackBody 96 struct EnrolOpenCVFeedback : public Enrollment::FeedbackBody
90 { 97 {
91 EnrolOpenCVFeedback(Mat *_m) 98 EnrolOpenCVFeedback(Mat *_m)
@@ -94,14 +101,11 @@ namespace FRsdk { @@ -94,14 +101,11 @@ namespace FRsdk {
94 101
95 EnrolOpenCVFeedback() {} 102 EnrolOpenCVFeedback() {}
96 103
97 - void start()  
98 - {  
99 - firvalid = false;  
100 - } 104 + void start() { firvalid = false; }
101 105
102 void processingImage(const FRsdk::Image& img) { (void) img; } 106 void processingImage(const FRsdk::Image& img) { (void) img; }
103 void eyesFound( const FRsdk::Eyes::Location& eyeLoc) { (void) eyeLoc; } 107 void eyesFound( const FRsdk::Eyes::Location& eyeLoc) { (void) eyeLoc; }
104 - void eyesNotFound() {} 108 + void eyesNotFound() { firvalid = false;}
105 void sampleQualityTooLow() {} 109 void sampleQualityTooLow() {}
106 void sampleQuality(const float& f) { (void) f; } 110 void sampleQuality(const float& f) { (void) f; }
107 111
@@ -113,7 +117,7 @@ namespace FRsdk { @@ -113,7 +117,7 @@ namespace FRsdk {
113 firvalid = true; 117 firvalid = true;
114 } 118 }
115 119
116 - void failure() {} 120 + void failure() { firvalid = false; }
117 void end() {} 121 void end() {}
118 122
119 const FRsdk::FIR& getFir() const 123 const FRsdk::FIR& getFir() const
@@ -122,10 +126,7 @@ namespace FRsdk { @@ -122,10 +126,7 @@ namespace FRsdk {
122 return *fir; 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 private: 131 private:
131 FRsdk::CountedPtr<FRsdk::FIR> fir; 132 FRsdk::CountedPtr<FRsdk::FIR> fir;
@@ -139,16 +140,14 @@ struct CT8Initialize : public Initializer @@ -139,16 +140,14 @@ struct CT8Initialize : public Initializer
139 { 140 {
140 static FRsdk::Configuration* CT8Configuration; 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 void initialize() const 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 try { 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 } catch (std::exception &e) { 151 } catch (std::exception &e) {
153 qWarning("CT8Initialize Exception: %s", e.what()); 152 qWarning("CT8Initialize Exception: %s", e.what());
154 CT8Configuration = NULL; 153 CT8Configuration = NULL;
@@ -164,17 +163,20 @@ struct CT8Initialize : public Initializer @@ -164,17 +163,20 @@ struct CT8Initialize : public Initializer
164 163
165 FRsdk::Configuration* CT8Initialize::CT8Configuration = NULL; 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 struct CT8Context 181 struct CT8Context
180 { 182 {
@@ -185,13 +187,12 @@ struct CT8Context @@ -185,13 +187,12 @@ struct CT8Context
185 eyesFinder = new FRsdk::Eyes::Finder(*CT8Initialize::CT8Configuration); 187 eyesFinder = new FRsdk::Eyes::Finder(*CT8Initialize::CT8Configuration);
186 firBuilder = new FRsdk::FIRBuilder(*CT8Initialize::CT8Configuration); 188 firBuilder = new FRsdk::FIRBuilder(*CT8Initialize::CT8Configuration);
187 facialMatchingEngine = new FRsdk::FacialMatchingEngine(*CT8Initialize::CT8Configuration); 189 facialMatchingEngine = new FRsdk::FacialMatchingEngine(*CT8Initialize::CT8Configuration);
188 - enrollmentProcessors.init();  
189 } catch (std::exception &e) { 190 } catch (std::exception &e) {
190 qFatal("CT8Context Exception: %s", e.what()); 191 qFatal("CT8Context Exception: %s", e.what());
191 } 192 }
192 } 193 }
193 194
194 - ~CT8Context() 195 + virtual ~CT8Context()
195 { 196 {
196 delete faceFinder; 197 delete faceFinder;
197 delete eyesFinder; 198 delete eyesFinder;
@@ -199,26 +200,62 @@ struct CT8Context @@ -199,26 +200,62 @@ struct CT8Context
199 delete facialMatchingEngine; 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 try { 207 try {
205 - FRsdk::Sample sample(FRsdk::AnnotatedImage(img, eyes));  
206 FRsdk::SampleSet sampleSet; 208 FRsdk::SampleSet sampleSet;
207 sampleSet.push_back(sample); 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 enrollmentProcessor->process(sampleSet.begin(), sampleSet.end(), enrollmentFeedback); 218 enrollmentProcessor->process(sampleSet.begin(), sampleSet.end(), enrollmentFeedback);
213 - enrollmentProcessors.release(index); 219 + enrollmentProcessors.release(enrollmentProcessor);
  220 + if (!feedback_body->firValid()) return false;
214 } catch (std::exception &e) { 221 } catch (std::exception &e) {
215 qFatal("CT8Context Exception: %s", e.what()); 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 protected: 261 protected:
@@ -230,30 +267,53 @@ protected: @@ -230,30 +267,53 @@ protected:
230 }; 267 };
231 268
232 269
233 -struct CT8Detect : public UntrainableFeature 270 +struct CT8Detect : public UntrainableTransform
234 , public CT8Context 271 , public CT8Context
235 { 272 {
  273 + Q_OBJECT
  274 + // Perform face, then eye detection using the facevacs SDK
236 void project(const Template &src, Template &dst) const 275 void project(const Template &src, Template &dst) const
237 { 276 {
238 try { 277 try {
  278 + // Build an FRsdk image from the input openCV mat
239 FRsdk::CountedPtr<FRsdk::ImageBody> i(new FRsdk::OpenCVImageBody(src)); 279 FRsdk::CountedPtr<FRsdk::ImageBody> i(new FRsdk::OpenCVImageBody(src));
240 FRsdk::Image img(i); 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 FRsdk::Face::LocationSet::const_iterator faceLocationSetIterator = faceLocations.begin(); 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 while (faceLocationSetIterator != faceLocations.end()) { 297 while (faceLocationSetIterator != faceLocations.end()) {
247 FRsdk::Face::Location faceLocation = *faceLocationSetIterator; faceLocationSetIterator++; 298 FRsdk::Face::Location faceLocation = *faceLocationSetIterator; faceLocationSetIterator++;
248 FRsdk::Eyes::LocationSet currentEyesLocations = eyesFinder->find(img, faceLocation); 299 FRsdk::Eyes::LocationSet currentEyesLocations = eyesFinder->find(img, faceLocation);
  300 +
249 if (currentEyesLocations.size() > 0) { 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 dst += src; 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 dst.file.setROIs(ROIs); 319 dst.file.setROIs(ROIs);
@@ -262,61 +322,84 @@ struct CT8Detect : public UntrainableFeature @@ -262,61 +322,84 @@ struct CT8Detect : public UntrainableFeature
262 qFatal("CT8Enroll Exception: %s", e.what()); 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 , public CT8Context 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 void project(const Template &src, Template &dst) const 338 void project(const Template &src, Template &dst) const
276 { 339 {
277 try { 340 try {
278 FRsdk::CountedPtr<FRsdk::ImageBody> i(new FRsdk::OpenCVImageBody(src)); 341 FRsdk::CountedPtr<FRsdk::ImageBody> i(new FRsdk::OpenCVImageBody(src));
279 FRsdk::Image img(i); 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 if (landmarks.size() == 2) { 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 if (ROIs.size() == 1) { 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 } else { 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 } catch (std::exception &e) { 379 } catch (std::exception &e) {
300 qFatal("CT8Enroll Exception: %s", e.what()); 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 public CT8Context 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 const float DefaultNonMatchScore = 0; 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 float score = DefaultNonMatchScore; 399 float score = DefaultNonMatchScore;
317 try { 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 score = (float)facialMatchingEngine->compare(firA, firB); 403 score = (float)facialMatchingEngine->compare(firA, firB);
321 } catch (std::exception &e) { 404 } catch (std::exception &e) {
322 qFatal("CT8Compare Exception: %s", e.what()); 405 qFatal("CT8Compare Exception: %s", e.what());
@@ -326,5 +409,7 @@ struct CT8Compare : public ComparerBase, @@ -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 +