digiKam
opencvdnnfacerecognizer_p.h
Go to the documentation of this file.
1 /* ============================================================
2  *
3  * This file is a part of digiKam
4  *
5  * Date : 2020-05-22
6  * Description : Wrapper of face recognition using OpenFace
7  *
8  * Copyright (C) 2019 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
9  * Copyright (C) 2020-2022 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 2020 by Nghia Duong <minhnghiaduong997 at gmail dot com>
11  *
12  * This program is free software; you can redistribute it
13  * and/or modify it under the terms of the GNU General
14  * Public License as published by the Free Software Foundation;
15  * either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * ============================================================ */
24 
25 #ifndef OPENCV_DNN_FACERECOGNIZER_P_H
26 #define OPENCV_DNN_FACERECOGNIZER_P_H
27 
29 
30 // C++ includes
31 
32 #include <iostream>
33 
34 // Qt includes
35 
36 #include <QElapsedTimer>
37 
38 // Local includes
39 
40 #include "digikam_debug.h"
41 #include "dnnfaceextractor.h"
42 #include "facedbaccess.h"
43 #include "facedb.h"
44 #include "kd_tree.h"
45 
46 namespace Digikam
47 {
48 
49 class Q_DECL_HIDDEN OpenCVDNNFaceRecognizer::Private
50 {
51 public:
52 
54  : method (method),
55  tree (nullptr),
56  kNeighbors (5),
57  threshold (0.4),
58  newDataAdded (true)
59  {
60  for (int i = 0 ; i < 1 ; ++i)
61  {
62  extractors << new DNNFaceExtractor;
63  }
64 
65  switch (method)
66  {
67  case SVM:
68  {
69  svm = cv::ml::SVM::create();
70  svm->setKernel(cv::ml::SVM::LINEAR);
71  break;
72  }
73 
74  case OpenCV_KNN:
75  {
76  knn = cv::ml::KNearest::create();
77  knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE);
78  knn->setIsClassifier(true);
79  break;
80  }
81 
82  case Tree:
83  {
84  tree = FaceDbAccess().db()->reconstructTree();
85  break;
86  }
87 
88  case DB:
89  {
90  break;
91  }
92 
93  default:
94  {
95  qFatal("Invalid classifier");
96  }
97  }
98  }
99 
101  {
102  QVector<DNNFaceExtractor*>::iterator extractor = extractors.begin();
103 
104  while (extractor != extractors.end())
105  {
106  delete *extractor;
107  extractor = extractors.erase(extractor);
108  }
109 
110  delete tree;
111  }
112 
113 public:
114 
115  bool trainSVM();
116  bool trainKNN();
117 
118  int predictSVM(const cv::Mat& faceEmbedding);
119  int predictKNN(const cv::Mat& faceEmbedding);
120 
121  int predictKDTree(const cv::Mat& faceEmbedding) const;
122  int predictDb(const cv::Mat& faceEmbedding) const;
123 
124  bool insertData(const cv::Mat& position, const int label, const QString& context = QString());
125 
126 public:
127 
129 
130  QVector<DNNFaceExtractor*> extractors;
131  cv::Ptr<cv::ml::SVM> svm;
132  cv::Ptr<cv::ml::KNearest> knn;
133 
136  float threshold;
137 
139 
140 public:
141 
142  class ParallelRecognizer;
143  class ParallelTrainer;
144 };
145 
146 class OpenCVDNNFaceRecognizer::Private::ParallelRecognizer : public cv::ParallelLoopBody
147 {
148 public:
149 
151  const QList<QImage*>& images,
152  QVector<int>& ids)
153  : images (images),
154  ids (ids),
155  d (d)
156  {
157  ids.resize(images.size());
158  }
159 
160  void operator()(const cv::Range& range) const override
161  {
162  for(int i = range.start ; i < range.end ; ++i)
163  {
164  int id = -1;
165 
166  cv::Mat faceEmbedding = d->extractors[i%(d->extractors.size())]->getFaceEmbedding(OpenCVDNNFaceRecognizer::prepareForRecognition(*images[i]));
167 
168  switch (d->method)
169  {
170  case SVM:
171  {
172  id = d->predictSVM(faceEmbedding);
173  break;
174  }
175 
176  case OpenCV_KNN:
177  {
178  id = d->predictKNN(faceEmbedding);
179  break;
180  }
181 
182  case Tree:
183  {
184  id = d->predictKDTree(faceEmbedding);
185  break;
186  }
187 
188  case DB:
189  {
190  id = d->predictDb(faceEmbedding);
191  break;
192  }
193 
194  default:
195  {
196  qCWarning(DIGIKAM_FACEDB_LOG) << "Not recognized classifying method";
197  }
198  }
199 
200  ids[i] = id;
201  }
202  }
203 
204 private:
205 
206  const QList<QImage*>& images;
207  QVector<int>& ids;
208 
210 
211 private:
212 
213  Q_DISABLE_COPY(ParallelRecognizer)
214 };
215 
216 class OpenCVDNNFaceRecognizer::Private::ParallelTrainer: public cv::ParallelLoopBody
217 {
218 public:
219 
221  const QList<QImage*>& images,
222  const int& id,
223  const QString& context)
224  : images (images),
225  id (id),
226  context (context),
227  d (d)
228  {
229  }
230 
231  void operator()(const cv::Range& range) const override
232  {
233  for(int i = range.start ; i < range.end ; ++i)
234  {
235  cv::Mat faceEmbedding = d->extractors[i%(d->extractors.size())]->
236  getFaceEmbedding(OpenCVDNNFaceRecognizer::prepareForRecognition(*images[i]));
237 
238  if (!d->insertData(faceEmbedding, id, context))
239  {
240  qCWarning(DIGIKAM_FACEDB_LOG) << "Fail to register a face of identity" << id;
241  }
242  }
243  }
244 
245 private:
246 
247  const QList<QImage*>& images;
248  const int& id;
249  const QString& context;
250 
252 
253 private:
254 
255  Q_DISABLE_COPY(ParallelTrainer)
256 };
257 
259 {
260  QElapsedTimer timer;
261  timer.start();
262 
263  svm->train(FaceDbAccess().db()->trainData());
264 
265  qCDebug(DIGIKAM_FACEDB_LOG) << "Support vector machine trains in" << timer.elapsed() << "ms";
266 
267  return (svm->isTrained());
268 }
269 
271 {
272  QElapsedTimer timer;
273  timer.start();
274 
275  knn->train(FaceDbAccess().db()->trainData());
276 
277  qCDebug(DIGIKAM_FACEDB_LOG) << "KNN trains in" << timer.elapsed() << "ms";
278 
279  return (knn->isTrained());
280 }
281 
282 int OpenCVDNNFaceRecognizer::Private::predictSVM(const cv::Mat& faceEmbedding)
283 {
284  if (newDataAdded)
285  {
286  if (!trainSVM())
287  {
288  return -1;
289  }
290 
291  newDataAdded = false;
292  }
293 
294  return (int(svm->predict(faceEmbedding)));
295 }
296 
297 int OpenCVDNNFaceRecognizer::Private::predictKNN(const cv::Mat& faceEmbedding)
298 {
299  if (newDataAdded)
300  {
301  if (!trainKNN())
302  {
303  return -1;
304  }
305 
306  newDataAdded = false;
307  }
308 
309  cv::Mat output;
310  knn->findNearest(faceEmbedding, kNeighbors, output);
311 
312  return (int(output.at<float>(0)));
313 }
314 
315 int OpenCVDNNFaceRecognizer::Private::predictKDTree(const cv::Mat& faceEmbedding) const
316 {
317  if (!tree)
318  {
319  return -1;
320  }
321 
322  // Look for K-nearest neighbor which have the cosine distance greater than the threshold.
323 
324  QMap<double, QVector<int> > closestNeighbors = tree->getClosestNeighbors(faceEmbedding, threshold, 0.8, kNeighbors);
325 
326  QMap<int, QVector<double> > votingGroups;
327 
328  for (QMap<double, QVector<int> >::const_iterator iter = closestNeighbors.cbegin();
329  iter != closestNeighbors.cend();
330  ++iter)
331  {
332  for (QVector<int>::const_iterator node = iter.value().cbegin();
333  node != iter.value().cend();
334  ++node)
335  {
336  int label = (*node);
337 
338  votingGroups[label].append(iter.key());
339  }
340  }
341 
342  double maxScore = 0.0;
343  int prediction = -1;
344 
345  for (QMap<int, QVector<double> >::const_iterator group = votingGroups.cbegin();
346  group != votingGroups.cend();
347  ++group)
348  {
349  double score = 0.0;
350 
351  for (int i = 0 ; i < group.value().size() ; ++i)
352  {
353  score += (threshold - group.value()[i]);
354  }
355 
356  if (score > maxScore)
357  {
358  maxScore = score;
359  prediction = group.key();
360  }
361  }
362 
363  return prediction;
364 }
365 
366 int OpenCVDNNFaceRecognizer::Private::predictDb(const cv::Mat& faceEmbedding) const
367 {
368  QMap<double, QVector<int> > closestNeighbors = FaceDbAccess().db()->getClosestNeighborsTreeDb(faceEmbedding, threshold, 0.8, kNeighbors);
369 
370  QMap<int, QVector<double> > votingGroups;
371 
372  for (QMap<double, QVector<int> >::const_iterator iter = closestNeighbors.cbegin();
373  iter != closestNeighbors.cend();
374  ++iter)
375  {
376  for (int i = 0 ; i < iter.value().size() ; ++i)
377  {
378  votingGroups[iter.value()[i]].append(iter.key());
379  }
380  }
381 
382  double maxScore = 0.0;
383  int prediction = -1;
384 
385  for (QMap<int, QVector<double> >::const_iterator group = votingGroups.cbegin();
386  group != votingGroups.cend();
387  ++group)
388  {
389  double score = 0.0;
390 
391  for (int i = 0 ; i < group.value().size() ; ++i)
392  {
393  score += (threshold - group.value()[i]);
394  }
395 
396  if (score > maxScore)
397  {
398  maxScore = score;
399  prediction = group.key();
400  }
401  }
402 
403  return prediction;
404 }
405 
406 bool OpenCVDNNFaceRecognizer::Private::insertData(const cv::Mat& nodePos, const int label, const QString& context)
407 {
408  int nodeId = FaceDbAccess().db()->insertFaceVector(nodePos, label, context);
409 
410  if (nodeId <= 0)
411  {
412  qCWarning(DIGIKAM_FACEDB_LOG) << "error inserting face embedding to database";
413  }
414 
415  if (method == DB)
416  {
417  if (! FaceDbAccess().db()->insertToTreeDb(nodeId, nodePos))
418  {
419  qCWarning(DIGIKAM_FACEDB_LOG) << "Error insert face embedding";
420 
421  return false;
422  }
423  }
424  else if (method == Tree)
425  {
426  KDNode* const newNode = tree->add(nodePos, label);
427 
428  if (newNode)
429  {
430  newNode->setNodeId(nodeId);
431  }
432  else
433  {
434  qCWarning(DIGIKAM_FACEDB_LOG) << "Error insert new node" << nodeId;
435 
436  return false;
437  }
438  }
439 
440  return true;
441 }
442 
443 } // namespace Digikam
444 
445 #endif // OPENCV_DNN_FACERECOGNIZER_P_H
Definition: dnnfaceextractor.h:48
Definition: facedbaccess.h:43
FaceDb * db() const
Definition: facedbaccess.cpp:141
QMap< double, QVector< int > > getClosestNeighborsTreeDb(const cv::Mat &position, float sqRange, float cosThreshold, int maxNbNeighbors) const
getClosestNeighbors : return a list of closest neighbor, limited by maxNbNeighbors and sqRange
Definition: facedb_dnn_spatial.cpp:116
KDTree * reconstructTree() const
reconstructTree: reconstruct KD-Tree from data in the database
Definition: facedb_dnn.cpp:62
int insertFaceVector(const cv::Mat &faceEmbedding, const int label, const QString &context) const
insertFaceVector : insert a new face embedding to database
Definition: facedb_dnn.cpp:31
Definition: kd_node.h:43
void setNodeId(int id)
Definition: kd_node.cpp:170
Definition: kd_tree.h:34
Definition: opencvdnnfacerecognizer_p.h:147
void operator()(const cv::Range &range) const override
Definition: opencvdnnfacerecognizer_p.h:160
ParallelRecognizer(OpenCVDNNFaceRecognizer::Private *d, const QList< QImage * > &images, QVector< int > &ids)
Definition: opencvdnnfacerecognizer_p.h:150
Definition: opencvdnnfacerecognizer_p.h:217
ParallelTrainer(OpenCVDNNFaceRecognizer::Private *d, const QList< QImage * > &images, const int &id, const QString &context)
Definition: opencvdnnfacerecognizer_p.h:220
void operator()(const cv::Range &range) const override
Definition: opencvdnnfacerecognizer_p.h:231
Definition: opencvdnnfacerecognizer_p.h:50
int predictKDTree(const cv::Mat &faceEmbedding) const
Definition: opencvdnnfacerecognizer_p.h:315
Classifier method
Definition: opencvdnnfacerecognizer_p.h:128
QVector< DNNFaceExtractor * > extractors
Definition: opencvdnnfacerecognizer_p.h:130
KDTree * tree
Definition: opencvdnnfacerecognizer_p.h:134
bool newDataAdded
Definition: opencvdnnfacerecognizer_p.h:138
~Private()
Definition: opencvdnnfacerecognizer_p.h:100
cv::Ptr< cv::ml::SVM > svm
Definition: opencvdnnfacerecognizer_p.h:131
int predictDb(const cv::Mat &faceEmbedding) const
Definition: opencvdnnfacerecognizer_p.h:366
cv::Ptr< cv::ml::KNearest > knn
Definition: opencvdnnfacerecognizer_p.h:132
int predictKNN(const cv::Mat &faceEmbedding)
Definition: opencvdnnfacerecognizer_p.h:297
Private(Classifier method)
Definition: opencvdnnfacerecognizer_p.h:53
int predictSVM(const cv::Mat &faceEmbedding)
Definition: opencvdnnfacerecognizer_p.h:282
bool insertData(const cv::Mat &position, const int label, const QString &context=QString())
Definition: opencvdnnfacerecognizer_p.h:406
int kNeighbors
Definition: opencvdnnfacerecognizer_p.h:135
bool trainSVM()
Definition: opencvdnnfacerecognizer_p.h:258
bool trainKNN()
Definition: opencvdnnfacerecognizer_p.h:270
float threshold
Definition: opencvdnnfacerecognizer_p.h:136
Definition: opencvdnnfacerecognizer.h:42
static cv::Mat prepareForRecognition(QImage &inputImage)
Definition: opencvdnnfacerecognizer.cpp:50
Classifier
Definition: opencvdnnfacerecognizer.h:46
@ OpenCV_KNN
Definition: opencvdnnfacerecognizer.h:48
@ DB
Definition: opencvdnnfacerecognizer.h:50
@ Tree
Definition: opencvdnnfacerecognizer.h:49
@ SVM
Definition: opencvdnnfacerecognizer.h:47
Definition: datefolderview.cpp:43
Definition: scan.h:26