File indexing completed on 2025-01-19 03:57:56
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-09-03 0007 * Description : Integrated, multithread face detection / recognition 0008 * 0009 * SPDX-FileCopyrightText: 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2012-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "facepipeline_p.h" 0017 0018 // Local includes 0019 0020 #include "digikam_debug.h" 0021 #include "databasewriter.h" 0022 #include "detectionbenchmarker.h" 0023 #include "detectionworker.h" 0024 #include "recognitionbenchmarker.h" 0025 #include "recognitionworker.h" 0026 #include "trainerworker.h" 0027 #include "facepreviewloader.h" 0028 #include "faceitemretriever.h" 0029 #include "parallelpipes.h" 0030 #include "scanstatefilter.h" 0031 0032 namespace Digikam 0033 { 0034 0035 FacePipeline::FacePipeline() 0036 : d(new Private(this)) 0037 { 0038 qRegisterMetaType<FacePipelineExtendedPackage::Ptr>("FacePipelineExtendedPackage::Ptr"); 0039 } 0040 0041 FacePipeline::~FacePipeline() 0042 { 0043 shutDown(); 0044 0045 delete d->databaseFilter; 0046 delete d->previewThread; 0047 delete d->detectionWorker; 0048 delete d->parallelDetectors; 0049 delete d->recognitionWorker; 0050 delete d->databaseWriter; 0051 delete d->trainerWorker; 0052 qDeleteAll(d->thumbnailLoadThreads); 0053 delete d->detectionBenchmarker; 0054 delete d->recognitionBenchmarker; 0055 delete d; 0056 } 0057 0058 void FacePipeline::shutDown() 0059 { 0060 cancel(); 0061 d->wait(); 0062 } 0063 0064 bool FacePipeline::hasFinished() const 0065 { 0066 return d->hasFinished(); 0067 } 0068 0069 QString FacePipeline::benchmarkResult() const 0070 { 0071 if (d->detectionBenchmarker) 0072 { 0073 return d->detectionBenchmarker->result(); 0074 } 0075 0076 if (d->recognitionBenchmarker) 0077 { 0078 return d->recognitionBenchmarker->result(); 0079 } 0080 0081 return QString(); 0082 } 0083 0084 void FacePipeline::plugDatabaseFilter(FilterMode mode) 0085 { 0086 d->databaseFilter = new ScanStateFilter(mode, d); 0087 } 0088 0089 void FacePipeline::plugRerecognizingDatabaseFilter() 0090 { 0091 plugDatabaseFilter(ReadUnconfirmedFaces); 0092 d->databaseFilter->tasks = FacePipelineFaceTagsIface::ForRecognition; 0093 } 0094 0095 void FacePipeline::plugRetrainingDatabaseFilter() 0096 { 0097 plugDatabaseFilter(ReadConfirmedFaces); 0098 d->databaseFilter->tasks = FacePipelineFaceTagsIface::ForTraining; 0099 } 0100 0101 void FacePipeline::plugFacePreviewLoader() 0102 { 0103 d->previewThread = new FacePreviewLoader(d); 0104 } 0105 0106 void FacePipeline::plugFaceDetector() 0107 { 0108 d->detectionWorker = new DetectionWorker(d); 0109 0110 connect(d, SIGNAL(accuracyAndModel(double,bool)), 0111 d->detectionWorker, SLOT(setAccuracyAndModel(double,bool)), 0112 Qt::QueuedConnection); 0113 } 0114 0115 void FacePipeline::plugParallelFaceDetectors() 0116 { 0117 if (QThread::idealThreadCount() <= 1) 0118 { 0119 plugFaceDetector(); 0120 return; 0121 } 0122 0123 // limit number of parallel detectors to 3, because of memory cost (cascades) 0124 0125 const int n = qMin(3, QThread::idealThreadCount()); 0126 d->parallelDetectors = new ParallelPipes; 0127 0128 for (int i = 0 ; i < n ; ++i) 0129 { 0130 DetectionWorker* const worker = new DetectionWorker(d); 0131 0132 connect(d, SIGNAL(accuracyAndModel(double,bool)), 0133 worker, SLOT(setAccuracyAndModel(double,bool)), 0134 Qt::QueuedConnection); 0135 0136 d->parallelDetectors->add(worker); 0137 } 0138 } 0139 0140 void FacePipeline::plugFaceRecognizer() 0141 { 0142 d->recognitionWorker = new RecognitionWorker(d); 0143 0144 connect(d, SIGNAL(accuracyAndModel(double,bool)), 0145 d->recognitionWorker, SLOT(setThreshold(double,bool)), 0146 Qt::QueuedConnection); 0147 } 0148 0149 void FacePipeline::plugDatabaseWriter(WriteMode mode) 0150 { 0151 d->databaseWriter = new DatabaseWriter(mode, d); 0152 } 0153 0154 void FacePipeline::plugTrainer() 0155 { 0156 d->trainerWorker = new TrainerWorker(d); 0157 } 0158 0159 void FacePipeline::plugDetectionBenchmarker() 0160 { 0161 d->detectionBenchmarker = new DetectionBenchmarker(d); 0162 } 0163 0164 void FacePipeline::plugRecognitionBenchmarker() 0165 { 0166 d->recognitionBenchmarker = new RecognitionBenchmarker(d); 0167 } 0168 0169 void FacePipeline::plugDatabaseEditor() 0170 { 0171 plugDatabaseWriter(NormalWrite); 0172 } 0173 0174 void FacePipeline::construct() 0175 { 0176 if (d->previewThread) 0177 { 0178 d->pipeline << d->previewThread; 0179 qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add preview thread"; 0180 } 0181 0182 if (d->parallelDetectors) 0183 { 0184 d->pipeline << d->parallelDetectors; 0185 qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add parallel thread detectors"; 0186 } 0187 else if (d->detectionWorker) 0188 { 0189 d->pipeline << d->detectionWorker; 0190 qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add single thread detector"; 0191 } 0192 0193 if (d->recognitionWorker) 0194 { 0195 d->pipeline << d->recognitionWorker; 0196 qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add recognition worker"; 0197 } 0198 0199 if (d->detectionBenchmarker) 0200 { 0201 d->pipeline << d->detectionBenchmarker; 0202 qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add detection benchmaker"; 0203 } 0204 0205 if (d->recognitionBenchmarker) 0206 { 0207 d->pipeline << d->recognitionBenchmarker; 0208 qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add recognition benchmaker"; 0209 } 0210 0211 if (d->databaseWriter) 0212 { 0213 d->pipeline << d->databaseWriter; 0214 qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add database writer"; 0215 } 0216 0217 if (d->trainerWorker) 0218 { 0219 d->pipeline << d->trainerWorker; 0220 qCDebug(DIGIKAM_GENERAL_LOG) << "Face PipeLine: add faces trainer"; 0221 } 0222 0223 if (d->pipeline.isEmpty()) 0224 { 0225 qCWarning(DIGIKAM_GENERAL_LOG) << "Nothing plugged in. It is a noop."; 0226 return; 0227 } 0228 0229 connect(d, SIGNAL(startProcess(FacePipelineExtendedPackage::Ptr)), 0230 d->pipeline.first(), SLOT(process(FacePipelineExtendedPackage::Ptr)), 0231 Qt::QueuedConnection); 0232 0233 for (int i = 0 ; i < (d->pipeline.size() - 1) ; ++i) 0234 { 0235 connect(d->pipeline.at(i), SIGNAL(processed(FacePipelineExtendedPackage::Ptr)), 0236 d->pipeline.at(i + 1), SLOT(process(FacePipelineExtendedPackage::Ptr)), 0237 Qt::QueuedConnection); 0238 } 0239 0240 connect(d->pipeline.last(), SIGNAL(processed(FacePipelineExtendedPackage::Ptr)), 0241 d, SLOT(finishProcess(FacePipelineExtendedPackage::Ptr)), 0242 Qt::QueuedConnection); 0243 0244 d->applyPriority(); 0245 } 0246 0247 void FacePipeline::setPriority(QThread::Priority priority) 0248 { 0249 if (d->priority == priority) 0250 { 0251 return; 0252 } 0253 0254 d->priority = priority; 0255 d->applyPriority(); 0256 } 0257 0258 QThread::Priority FacePipeline::priority() const 0259 { 0260 return d->priority; 0261 } 0262 0263 void FacePipeline::cancel() 0264 { 0265 d->stop(); 0266 } 0267 0268 bool FacePipeline::process(const ItemInfo& info) 0269 { 0270 QString filePath = info.filePath(); 0271 0272 if (filePath.isNull()) 0273 { 0274 qCWarning(DIGIKAM_GENERAL_LOG) << "ItemInfo has no valid file path. Skipping."; 0275 return false; 0276 } 0277 0278 FacePipelineExtendedPackage::Ptr package = d->filterOrBuildPackage(info); 0279 0280 if (!package) 0281 { 0282 return false; 0283 } 0284 0285 d->send(package); 0286 0287 return true; 0288 } 0289 0290 bool FacePipeline::process(const ItemInfo& info, 0291 const DImg& image) 0292 { 0293 FacePipelineExtendedPackage::Ptr package = d->filterOrBuildPackage(info); 0294 0295 if (!package) 0296 { 0297 return false; 0298 } 0299 0300 package->image = image; 0301 d->send(package); 0302 0303 return true; 0304 } 0305 0306 /* 0307 bool FacePipeline::add(const ItemInfo& info, 0308 const QRect& rect, 0309 const DImg& image) 0310 { 0311 FacePipelineExtendedPackage::Ptr package = d->buildPackage(info); 0312 package->image = image; 0313 package->detectionImage = image; 0314 package->faces << Face(rect); 0315 d->send(package); 0316 } 0317 */ 0318 0319 void FacePipeline::train(const ItemInfo& info, 0320 const QList<FaceTagsIface>& databaseFaces) 0321 { 0322 train(info, databaseFaces, DImg()); 0323 } 0324 0325 void FacePipeline::train(const ItemInfo& info, 0326 const QList<FaceTagsIface>& databaseFaces, 0327 const DImg& image) 0328 { 0329 FacePipelineExtendedPackage::Ptr package = d->buildPackage(info, 0330 FacePipelineFaceTagsIfaceList(databaseFaces), 0331 image); 0332 package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForTraining); 0333 d->send(package); 0334 } 0335 0336 FaceTagsIface FacePipeline::confirm(const ItemInfo& info, 0337 const FaceTagsIface& databaseFace, 0338 int assignedTagId, 0339 const TagRegion& assignedRegion) 0340 { 0341 return confirm(info, databaseFace, DImg(), assignedTagId, assignedRegion); 0342 } 0343 0344 FaceTagsIface FacePipeline::confirm(const ItemInfo& info, 0345 const FaceTagsIface& databaseFace, 0346 const DImg& image, 0347 int assignedTagId, 0348 const TagRegion& assignedRegion) 0349 { 0350 FacePipelineFaceTagsIface face = FacePipelineFaceTagsIface(databaseFace); 0351 face.assignedTagId = assignedTagId; 0352 face.assignedRegion = assignedRegion; 0353 face.roles |= FacePipelineFaceTagsIface::ForConfirmation; 0354 FacePipelineExtendedPackage::Ptr package = d->buildPackage(info, face, image); 0355 0356 d->send(package); 0357 0358 return FaceTagsEditor::confirmedEntry(face, assignedTagId, assignedRegion); 0359 } 0360 0361 FaceTagsIface FacePipeline::addManually(const ItemInfo& info, 0362 const DImg& image, 0363 const TagRegion& assignedRegion) 0364 { 0365 FacePipelineFaceTagsIface face; // giving a null face => no existing face yet, add it 0366 face.assignedTagId = -1; 0367 face.assignedRegion = assignedRegion; 0368 face.roles |= FacePipelineFaceTagsIface::ForEditing; 0369 FacePipelineExtendedPackage::Ptr package = d->buildPackage(info, face, image); 0370 0371 package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForEditing); 0372 d->send(package); 0373 0374 return FaceTagsEditor::unconfirmedEntry(info.id(), face.assignedTagId, face.assignedRegion); 0375 } 0376 0377 FaceTagsIface FacePipeline::editRegion(const ItemInfo& info, 0378 const DImg& image, 0379 const FaceTagsIface& databaseFace, 0380 const TagRegion& newRegion) 0381 { 0382 FacePipelineFaceTagsIface face = FacePipelineFaceTagsIface(databaseFace); 0383 face.assignedTagId = -1; 0384 face.assignedRegion = newRegion; 0385 face.roles |= FacePipelineFaceTagsIface::ForEditing; 0386 FacePipelineExtendedPackage::Ptr package = d->buildPackage(info, face, image); 0387 0388 package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForEditing); 0389 d->send(package); 0390 0391 face.setRegion(newRegion); 0392 0393 return face; 0394 } 0395 0396 FaceTagsIface FacePipeline::editTag(const ItemInfo& info, 0397 const FaceTagsIface& databaseFace, 0398 int newTagId) 0399 { 0400 FacePipelineFaceTagsIface face = FacePipelineFaceTagsIface(databaseFace); 0401 face.assignedTagId = newTagId; 0402 face.assignedRegion = TagRegion(); 0403 face.roles |= FacePipelineFaceTagsIface::ForEditing; 0404 0405 FacePipelineExtendedPackage::Ptr package = d->buildPackage(info, face, DImg()); 0406 0407 package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForEditing); 0408 d->send(package); 0409 0410 return face; 0411 } 0412 0413 void FacePipeline::remove(const ItemInfo& info, 0414 const FaceTagsIface& databaseFace) 0415 { 0416 FacePipelineExtendedPackage::Ptr package = d->buildPackage(info, 0417 FacePipelineFaceTagsIface(databaseFace), 0418 DImg()); 0419 package->databaseFaces.setRole(FacePipelineFaceTagsIface::ForEditing); 0420 d->send(package); 0421 } 0422 0423 void FacePipeline::process(const QList<ItemInfo>& infos) 0424 { 0425 d->processBatch(infos); 0426 } 0427 0428 void FacePipeline::setAccuracyAndModel(double value, bool yolo) 0429 { 0430 Q_EMIT d->accuracyAndModel(value, yolo); 0431 } 0432 0433 } // namespace Digikam 0434 0435 #include "moc_facepipeline.cpp"