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