File indexing completed on 2025-01-19 03:57:55
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2010-09-03 0007 * Description : Face detection benchmarker 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 "detectionbenchmarker.h" 0017 0018 // Local includes 0019 0020 #include "digikam_debug.h" 0021 #include "tagscache.h" 0022 0023 namespace Digikam 0024 { 0025 0026 DetectionBenchmarker::DetectionBenchmarker(FacePipeline::Private* const d) 0027 : totalImages (0), 0028 faces (0), 0029 totalPixels (0), 0030 facePixels (0), 0031 trueNegativeImages (0), 0032 falsePositiveImages (0), 0033 truePositiveFaces (0), 0034 falseNegativeFaces (0), 0035 falsePositiveFaces (0), 0036 d (d) 0037 { 0038 } 0039 0040 void DetectionBenchmarker::process(const FacePipelineExtendedPackage::Ptr& package) 0041 { 0042 if (package->databaseFaces.isEmpty()) 0043 { 0044 // Detection / Recognition 0045 0046 qCDebug(DIGIKAM_GENERAL_LOG) << "Benchmarking image" << package->info.name(); 0047 0048 FaceUtils utils; 0049 QList<FaceTagsIface> groundTruth = utils.databaseFaces(package->info.id()); 0050 0051 QList<FaceTagsIface> testedFaces = utils.toFaceTagsIfaces(package->info.id(), 0052 package->detectedFaces, 0053 package->recognitionResults, 0054 package->image.originalSize()); 0055 0056 QList<FaceTagsIface> unmatchedTrueFaces = groundTruth; 0057 QList<FaceTagsIface> unmatchedTestedFaces = testedFaces; 0058 QList<FaceTagsIface> matchedTrueFaces; 0059 0060 int trueFaces = groundTruth.size(); 0061 const double minOverlap = 0.75; 0062 0063 qCDebug(DIGIKAM_GENERAL_LOG) << "There are" << trueFaces << "faces to be detected. The detector found" << testedFaces.size(); 0064 0065 ++totalImages; 0066 faces += trueFaces; 0067 totalPixels += package->image.originalSize().width() * package->image.originalSize().height(); 0068 0069 Q_FOREACH (const FaceTagsIface& trueFace, groundTruth) 0070 { 0071 ++faces; 0072 QRect rect = trueFace.region().toRect(); 0073 facePixels += rect.width() * rect.height(); 0074 0075 Q_FOREACH (const FaceTagsIface& testedFace, testedFaces) 0076 { 0077 if (trueFace.region().intersects(testedFace.region(), minOverlap)) 0078 { // cppcheck-suppress useStlAlgorithm 0079 matchedTrueFaces << trueFace; 0080 unmatchedTrueFaces.removeOne(trueFace); 0081 break; 0082 } 0083 } 0084 } 0085 0086 Q_FOREACH (const FaceTagsIface& testedFace, testedFaces) 0087 { 0088 Q_FOREACH (const FaceTagsIface& trueFace, groundTruth) 0089 { 0090 if (trueFace.region().intersects(testedFace.region(), minOverlap)) 0091 { // cppcheck-suppress useStlAlgorithm 0092 unmatchedTestedFaces.removeOne(testedFace); 0093 break; 0094 } 0095 } 0096 } 0097 0098 if (groundTruth.isEmpty()) 0099 { 0100 if (testedFaces.isEmpty()) 0101 { 0102 ++trueNegativeImages; 0103 } 0104 else 0105 { 0106 qCDebug(DIGIKAM_GENERAL_LOG) << "The image, truly without faces, is false-positive"; 0107 ++falsePositiveImages; 0108 } 0109 } 0110 0111 truePositiveFaces += matchedTrueFaces.size(); 0112 falseNegativeFaces += unmatchedTrueFaces.size(); 0113 falsePositiveFaces += unmatchedTestedFaces.size(); 0114 qCDebug(DIGIKAM_GENERAL_LOG) << "Faces detected correctly:" 0115 << matchedTrueFaces.size() 0116 << ", faces missed:" 0117 << unmatchedTrueFaces.size() 0118 << ", faces falsely detected:" 0119 << unmatchedTestedFaces.size(); 0120 } 0121 0122 package->processFlags |= FacePipelinePackage::WrittenToDatabase; 0123 Q_EMIT processed(package); 0124 } 0125 0126 /** 0127 * NOTE: Bench performance code. No need i18n here 0128 */ 0129 QString DetectionBenchmarker::result() const 0130 { 0131 qCDebug(DIGIKAM_GENERAL_LOG) << "Per-image:" 0132 << trueNegativeImages 0133 << falsePositiveFaces; 0134 qCDebug(DIGIKAM_GENERAL_LOG) << "Per-face:" 0135 << truePositiveFaces 0136 << falseNegativeFaces 0137 << falsePositiveFaces; // 26 7 1 0138 0139 int negativeImages = trueNegativeImages + falsePositiveImages; 0140 int trueFaces = truePositiveFaces + falseNegativeFaces; 0141 QString specificityWarning, sensitivityWarning; 0142 0143 if (negativeImages < (0.2 * totalImages)) 0144 { 0145 specificityWarning = QString::fromUtf8("<p><b>Note:</b><br/> " 0146 "Only %1 of the %2 test images have <i>no</i> depicted faces. " 0147 "This means the result is cannot be representative; " 0148 "it can only be used to compare preselected collections, " 0149 "and the specificity and false-positive rate have little meaning. </p>") 0150 .arg(negativeImages).arg(totalImages); 0151 negativeImages = qMax(negativeImages, 1); 0152 } 0153 0154 if (trueFaces == 0) 0155 { 0156 sensitivityWarning = QString::fromUtf8("<p><b>Note:</b><br/> " 0157 "No picture in the test collection contained a face. " 0158 "This means that sensitivity and PPV have no meaning and will be zero. </p>"); 0159 trueFaces = 1; 0160 } 0161 0162 // collection properties 0163 double pixelCoverage = facePixels / totalPixels; 0164 // per-image 0165 double specificity = double(trueNegativeImages) / negativeImages; 0166 double falsePositiveRate = double(falsePositiveImages) / negativeImages; 0167 // per-face 0168 double sensitivity = double(truePositiveFaces) / trueFaces; 0169 double ppv = double(truePositiveFaces) / (truePositiveFaces + falsePositiveFaces); 0170 0171 return QString::fromUtf8("<p>" 0172 "<u>Collection Properties:</u><br/>" 0173 "%1 Images <br/>" 0174 "%2 Faces <br/>" 0175 "%3% of pixels covered by faces <br/>" 0176 "</p>" 0177 "%8" 0178 "%9" 0179 "<p>" 0180 "<u>Per-Image Performance:</u> <br/>" 0181 "Specificity: %4% <br/>" 0182 "False-Positive Rate: %5%" 0183 "</p>" 0184 "<p>" 0185 "<u>Per-Face Performance:</u> <br/>" 0186 "Sensitivity: %6% <br/>" 0187 "Positive Predictive Value: %7% <br/>" 0188 "</p>" 0189 "<p>" 0190 "In other words, if a face is detected as face, it will " 0191 "with a probability of %7% truly be a face. " 0192 "Of all true faces, %6% will be detected. " 0193 "Given face with no images on it, the detector will with a probability " 0194 "of %5% falsely find a face on it. " 0195 "</p>") 0196 .arg(totalImages).arg(faces).arg(pixelCoverage * 100, 0, 'f', 1) 0197 .arg(specificity * 100, 0, 'f', 1).arg(falsePositiveRate * 100, 0, 'f', 1) 0198 .arg(sensitivity * 100, 0, 'f', 1).arg(ppv * 100, 0, 'f', 1) 0199 .arg(specificityWarning).arg(sensitivityWarning); 0200 } 0201 0202 } // namespace Digikam 0203 0204 #include "moc_detectionbenchmarker.cpp"