1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2010-06-16
7  * Description : Face Recognition CLI tool
8  *               NOTE: This tool is able to use ORL database which are
9  *               freely available set of images to test face recognition.
10  *               It contain 10 photos of 20 different peoples from slightly
11  *               different angles. See here for details:
12  *               www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html
13  *
14  * Copyright (C) 2010 by Aditya Bhatt <adityabhatt1991 at gmail dot com>
15  *
16  * This program is free software; you can redistribute it
17  * and/or modify it under the terms of the GNU General
18  * Public License as published by the Free Software Foundation;
19  * either version 2, or (at your option)
20  * any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  * GNU General Public License for more details.
26  *
27  * ============================================================ */
28 
29 // Qt includes
30 
31 #include <QCoreApplication>
32 #include <QDir>
33 #include <QImage>
34 #include <QElapsedTimer>
35 
36 // Local includes
37 
38 #include "digikam_debug.h"
39 #include "facialrecognition_wrapper.h"
40 #include "coredbaccess.h"
41 #include "dbengineparameters.h"
42 
43 using namespace Digikam;
44 
toPaths(char ** argv,int startIndex,int argc)45 QStringList toPaths(char** argv, int startIndex, int argc)
46 {
47     QStringList files;
48 
49     for (int i = startIndex ; i < argc ; ++i)
50     {
51         files << QString::fromLocal8Bit(argv[i]);
52     }
53 
54     return files;
55 }
56 
toImages(const QStringList & paths)57 QList<QImage*> toImages(const QStringList& paths)
58 {
59     QList<QImage*> images;
60 
61     foreach (const QString& path, paths)
62     {
63         images << new QImage(path);
64     }
65 
66     return images;
67 }
68 
69 // --------------------------------------------------------------------------------------------------
70 
main(int argc,char ** argv)71 int main(int argc, char** argv)
72 {
73     if ((argc < 2) || ((QString::fromLatin1(argv[1]) == QString::fromLatin1("train")) && (argc < 3)))
74     {
75         qCDebug(DIGIKAM_TESTS_LOG) << "Bad Arguments!!!\nUsage: " << argv[0]
76                  << " identify <image1> <image2> ... | train name <image1> <image2> ... "
77                                                     "| ORL <path to orl_faces>";
78         return 0;
79     }
80 
81     QCoreApplication app(argc, argv);
82     app.setApplicationName(QString::fromLatin1("digikam"));          // for DB init.
83     DbEngineParameters prm    = DbEngineParameters::parametersFromConfig();
84     CoreDbAccess::setParameters(prm, CoreDbAccess::MainApplication);
85     FacialRecognitionWrapper recognizer;
86 
87     if      (QString::fromLatin1(argv[1]) == QString::fromLatin1("identify"))
88     {
89         QStringList paths     = toPaths(argv, 2, argc);
90         QList<QImage*> images = toImages(paths);
91 
92         QElapsedTimer timer;
93         timer.start();
94         QList<Identity> identities = recognizer.recognizeFaces(images);
95         int elapsed                = timer.elapsed();
96 
97         qCDebug(DIGIKAM_TESTS_LOG) << "Recognition took " << elapsed
98                  << " for " << images.size() << ", "
99                  << ((float)elapsed/images.size()) << " per image";
100 
101         for (int i = 0 ; i < paths.size() ; ++i)
102         {
103             qCDebug(DIGIKAM_TESTS_LOG) << "Identified " << identities[i].attribute(QString::fromLatin1("name"))
104                      << " in " << paths[i];
105         }
106     }
107     else if (QString::fromLatin1(argv[1]) == QString::fromLatin1("train"))
108     {
109         QString name = QString::fromLocal8Bit(argv[2]);
110         qCDebug(DIGIKAM_TESTS_LOG) << "Training " << name;
111 
112         QStringList paths     = toPaths(argv, 3, argc);
113         QList<QImage*> images = toImages(paths);
114         Identity identity     = recognizer.findIdentity(QString::fromLatin1("name"), name);
115 
116         if (identity.isNull())
117         {
118             qCDebug(DIGIKAM_TESTS_LOG) << "Adding new identity to database for name " << name;
119             QMap<QString, QString> attributes;
120             attributes[QString::fromLatin1("name")] = name;
121             identity                                = recognizer.addIdentity(attributes);
122         }
123 
124         QElapsedTimer timer;
125         timer.start();
126 
127         recognizer.train(identity, images, QString::fromLatin1("test application"));
128 
129         int elapsed = timer.elapsed();
130         qCDebug(DIGIKAM_TESTS_LOG) << "Training took " << elapsed << " for "
131                  << images.size() << ", "
132                  << ((float)elapsed/images.size()) << " per image";
133     }
134     else if (QString::fromLatin1(argv[1]) == QString::fromLatin1("orl"))
135     {
136         QString orlPath = QString::fromLocal8Bit(argv[2]);
137 
138         if (orlPath.isEmpty())
139         {
140             orlPath = QString::fromLatin1("orl_faces"); // relative to current dir
141         }
142 
143         QDir orlDir(orlPath);
144 
145         if (!orlDir.exists())
146         {
147             qCDebug(DIGIKAM_TESTS_LOG) << "Cannot find orl_faces directory";
148             return 0;
149         }
150 
151         const int OrlIdentities       = 40;
152         const int OrlSamples          = 10;
153         const QString trainingContext = QString::fromLatin1("test application");
154 
155         QMap<int, Identity> idMap;
156         QList<Identity> trainingToBeCleared;
157 
158         for (int i = 1 ; i <= OrlIdentities ; ++i)
159         {
160             QMap<QString, QString> attributes;
161             attributes[QString::fromLatin1("name")] = QString::number(i);
162             Identity identity                       = recognizer.findIdentity(attributes);
163 
164             if (identity.isNull())
165             {
166                 Identity identity2 = recognizer.addIdentity(attributes);
167                 idMap[i]           = identity2;
168                 qCDebug(DIGIKAM_TESTS_LOG) << "Created identity " << identity2.id() << " for ORL directory " << i;
169             }
170             else
171             {
172                 qCDebug(DIGIKAM_TESTS_LOG) << "Already have identity for ORL directory " << i << ", clearing training data";
173                 idMap[i] = identity;
174                 trainingToBeCleared << identity;
175             }
176         }
177 
178         recognizer.clearTraining(trainingToBeCleared, trainingContext);
179         QMap<int, QStringList> trainingImages, recognitionImages;
180 
181         for (int i = 1 ; i <= OrlIdentities ; ++i)
182         {
183             for (int j = 1 ; j <= OrlSamples ; ++j)
184             {
185                 QString path = orlDir.path() + QString::fromLatin1("/s%1/%2.pgm").arg(i).arg(j);
186 
187                 if (j <= OrlSamples / 2)
188                 {
189                     trainingImages[i] << path;
190                 }
191                 else
192                 {
193                     recognitionImages[i] << path;
194                 }
195             }
196         }
197 
198         if (!QFileInfo::exists(trainingImages.value(1).first()))
199         {
200             qCDebug(DIGIKAM_TESTS_LOG) << "Could not find files of ORL database";
201             return 0;
202         }
203 
204         QElapsedTimer timer;
205         timer.start();
206 
207         int correct         = 0;
208         int notRecognized   = 0;
209         int falsePositive   = 0;
210         int totalTrained    = 0;
211         int totalRecognized = 0;
212         int elapsed         = 0;
213 
214         for (QMap<int, QStringList>::const_iterator it = trainingImages.constBegin() ;
215              it != trainingImages.constEnd() ; ++it)
216         {
217             Identity identity = recognizer.findIdentity(QString::fromLatin1("name"), QString::number(it.key()));
218 
219             if (identity.isNull())
220             {
221                 qCDebug(DIGIKAM_TESTS_LOG) << "Identity management failed for ORL person " << it.key();
222             }
223 
224             QList<QImage*> images = toImages(it.value());
225             qCDebug(DIGIKAM_TESTS_LOG) << "Training ORL directory " << it.key();
226             recognizer.train(identity, images, trainingContext);
227             totalTrained        += images.size();
228         }
229 
230         elapsed = timer.restart();
231 
232         if (totalTrained)
233         {
234             qCDebug(DIGIKAM_TESTS_LOG) << "Training 5/10 or ORL took " << elapsed
235                      << " ms, " << ((float)elapsed/totalTrained)
236                      << " ms per image";
237         }
238 
239         for (QMap<int, QStringList>::const_iterator it = recognitionImages.constBegin() ;
240              it != recognitionImages.constEnd() ; ++it)
241         {
242             Identity identity        = idMap.value(it.key());
243             QList<QImage*> images    = toImages(it.value());
244             QList<Identity> results  = recognizer.recognizeFaces(images);
245 
246             qCDebug(DIGIKAM_TESTS_LOG) << "Result for " << it.value().first()
247                      << " is identity " << results.first().id();
248 
249             foreach (const Identity& foundId, results)
250             {
251                 if (foundId.isNull())
252                 {
253                     notRecognized++;
254                 }
255                 else if (foundId == identity)
256                 {
257                     correct++;
258                 }
259                 else
260                 {
261                     falsePositive++;
262                 }
263             }
264 
265             totalRecognized += images.size();
266         }
267 
268         elapsed = timer.elapsed();
269 
270         if (totalRecognized)
271         {
272             qCDebug(DIGIKAM_TESTS_LOG) << "Recognition of 5/10 or ORL took " << elapsed << " ms, " << ((float)elapsed/totalRecognized) << " ms per image";
273             qCDebug(DIGIKAM_TESTS_LOG) << correct       << " of 200 (" << (float(correct)       / totalRecognized*100) << "%) were correctly recognized";
274             qCDebug(DIGIKAM_TESTS_LOG) << falsePositive << " of 200 (" << (float(falsePositive) / totalRecognized*100) << "%) were falsely assigned to an identity";
275             qCDebug(DIGIKAM_TESTS_LOG) << notRecognized << " of 200 (" << (float(notRecognized) / totalRecognized*100) << "%) were not recognized";
276         }
277         else
278         {
279             qCDebug(DIGIKAM_TESTS_LOG) << "No face recognized";
280         }
281     }
282 
283     return 0;
284 }
285