File indexing completed on 2025-03-09 03:54:59
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam 0004 * 0005 * Date : 2010-09-02 0006 * Description : A convenience class for a standalone face detector 0007 * 0008 * SPDX-FileCopyrightText: 2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0009 * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "facedetector.h" 0016 0017 // Qt includes 0018 0019 #include <QSharedData> 0020 #include <QStandardPaths> 0021 0022 // Local includes 0023 0024 #include "digikam_debug.h" 0025 #include "opencvdnnfacedetector.h" 0026 0027 namespace Digikam 0028 { 0029 0030 class Q_DECL_HIDDEN FaceDetector::Private : public QSharedData 0031 { 0032 public: 0033 0034 explicit Private() 0035 : m_dnnDetectorBackend(nullptr) 0036 { 0037 } 0038 0039 ~Private() 0040 { 0041 delete m_dnnDetectorBackend; 0042 } 0043 0044 OpenCVDNNFaceDetector* backend() 0045 { 0046 if (!m_dnnDetectorBackend) 0047 { 0048 if (m_parameters.contains(QLatin1String("useyolov3")) && 0049 m_parameters.value(QLatin1String("useyolov3")).toBool()) 0050 { 0051 m_dnnDetectorBackend = new OpenCVDNNFaceDetector(DetectorNNModel::YOLO); 0052 } 0053 else 0054 { 0055 m_dnnDetectorBackend = new OpenCVDNNFaceDetector(DetectorNNModel::SSDMOBILENET); 0056 } 0057 } 0058 0059 return m_dnnDetectorBackend; 0060 } 0061 0062 const OpenCVDNNFaceDetector* constBackend() const 0063 { 0064 return m_dnnDetectorBackend; 0065 } 0066 0067 void applyParameters() 0068 { 0069 if (!backend()) 0070 { 0071 return; 0072 } 0073 0074 // TODO Handle settings 0075 0076 /* 0077 for (QVariantMap::const_iterator it = m_parameters.constBegin() ; 0078 it != m_parameters.constEnd() ; ++it) 0079 { 0080 if (it.key() == QLatin1String("accuracy")) 0081 { 0082 backend()->setAccuracy(it.value().toDouble()); 0083 } 0084 else if (it.key() == QLatin1String("speed")) 0085 { 0086 backend()->setAccuracy(1.0 - it.value().toDouble()); 0087 } 0088 else if (it.key() == QLatin1String("specificity")) 0089 { 0090 backend()->setSpecificity(it.value().toDouble()); 0091 } 0092 else if (it.key() == QLatin1String("sensitivity")) 0093 { 0094 backend()->setSpecificity(1.0 - it.value().toDouble()); 0095 } 0096 } 0097 */ 0098 } 0099 0100 public: 0101 0102 QVariantMap m_parameters; 0103 0104 private: 0105 0106 OpenCVDNNFaceDetector* m_dnnDetectorBackend; 0107 }; 0108 0109 // --------------------------------------------------------------------------------- 0110 0111 FaceDetector::FaceDetector() 0112 : d(new Private) 0113 { 0114 } 0115 0116 FaceDetector::FaceDetector(const FaceDetector& other) 0117 : d(other.d) 0118 { 0119 } 0120 0121 FaceDetector& FaceDetector::operator=(const FaceDetector& other) 0122 { 0123 d = other.d; 0124 0125 return *this; 0126 } 0127 0128 FaceDetector::~FaceDetector() 0129 { 0130 // TODO: implement reference counter 0131 } 0132 0133 QString FaceDetector::backendIdentifier() const 0134 { 0135 return QLatin1String("Deep Neural Network"); 0136 } 0137 0138 QList<QRectF> FaceDetector::detectFaces(const QImage& image, const QSize& originalSize) 0139 { 0140 QList<QRectF> result; 0141 0142 if (image.isNull() || !image.size().isValid()) 0143 { 0144 return result; 0145 } 0146 0147 try 0148 { 0149 Q_UNUSED(originalSize); 0150 0151 cv::Size paddedSize(0, 0); 0152 cv::Mat cvImage = d->backend()->prepareForDetection(image, paddedSize); 0153 QList<QRect> absRects = d->backend()->detectFaces(cvImage, paddedSize); 0154 result = toRelativeRects(absRects, 0155 QSize(cvImage.cols - 2*paddedSize.width, 0156 cvImage.rows - 2*paddedSize.height)); 0157 0158 return result; 0159 } 0160 catch (cv::Exception& e) 0161 { 0162 qCCritical(DIGIKAM_FACESENGINE_LOG) << "cv::Exception:" << e.what(); 0163 } 0164 catch (...) 0165 { 0166 qCCritical(DIGIKAM_FACESENGINE_LOG) << "Default exception from OpenCV"; 0167 } 0168 0169 return result; 0170 } 0171 0172 QList<QRectF> FaceDetector::detectFaces(const DImg& image, const QSize& originalSize) 0173 { 0174 QList<QRectF> result; 0175 0176 if (image.isNull() || !image.size().isValid()) 0177 { 0178 return result; 0179 } 0180 0181 try 0182 { 0183 Q_UNUSED(originalSize); 0184 0185 cv::Size paddedSize(0, 0); 0186 cv::Mat cvImage = d->backend()->prepareForDetection(image, paddedSize); 0187 QList<QRect> absRects = d->backend()->detectFaces(cvImage, paddedSize); 0188 result = toRelativeRects(absRects, 0189 QSize(cvImage.cols - 2*paddedSize.width, 0190 cvImage.rows - 2*paddedSize.height)); 0191 return result; 0192 } 0193 catch (cv::Exception& e) 0194 { 0195 qCCritical(DIGIKAM_FACESENGINE_LOG) << "cv::Exception:" << e.what(); 0196 } 0197 catch (...) 0198 { 0199 qCCritical(DIGIKAM_FACESENGINE_LOG) << "Default exception from OpenCV"; 0200 } 0201 0202 return result; 0203 } 0204 0205 QList<QRectF> FaceDetector::detectFaces(const QString& imagePath) 0206 { 0207 QList<QRectF> result; 0208 0209 try 0210 { 0211 cv::Size paddedSize(0, 0); 0212 cv::Mat cvImage = d->backend()->prepareForDetection(imagePath, paddedSize); 0213 QList<QRect> absRects = d->backend()->detectFaces(cvImage, paddedSize); 0214 result = toRelativeRects(absRects, 0215 QSize(cvImage.cols - 2*paddedSize.width, 0216 cvImage.rows - 2*paddedSize.height)); 0217 } 0218 catch (cv::Exception& e) 0219 { 0220 qCCritical(DIGIKAM_FACESENGINE_LOG) << "cv::Exception:" << e.what(); 0221 } 0222 catch (...) 0223 { 0224 qCCritical(DIGIKAM_FACESENGINE_LOG) << "Default exception from OpenCV"; 0225 } 0226 0227 return result; 0228 } 0229 0230 void FaceDetector::setParameter(const QString& parameter, const QVariant& value) 0231 { 0232 d->m_parameters.insert(parameter, value); 0233 d->applyParameters(); 0234 } 0235 0236 void FaceDetector::setParameters(const QVariantMap& parameters) 0237 { 0238 for (QVariantMap::const_iterator it = parameters.begin() ; it != parameters.end() ; ++it) 0239 { 0240 d->m_parameters.insert(it.key(), it.value()); 0241 } 0242 0243 d->applyParameters(); 0244 } 0245 0246 QVariantMap FaceDetector::parameters() const 0247 { 0248 return d->m_parameters; 0249 } 0250 0251 int FaceDetector::recommendedImageSize(const QSize& availableSize) const 0252 { 0253 Q_UNUSED(availableSize); 0254 0255 return OpenCVDNNFaceDetector::recommendedImageSizeForDetection(); 0256 } 0257 0258 // -- Static methods ------------------------------------------------------------- 0259 0260 QRectF FaceDetector::toRelativeRect(const QRect& abs, const QSize& s) 0261 { 0262 if (s.isEmpty()) 0263 { 0264 return QRectF(); 0265 } 0266 0267 return QRectF(qreal(abs.x()) / qreal(s.width()), 0268 qreal(abs.y()) / qreal(s.height()), 0269 qreal(abs.width()) / qreal(s.width()), 0270 qreal(abs.height()) / qreal(s.height())); 0271 } 0272 0273 QRect FaceDetector::toAbsoluteRect(const QRectF& rel, const QSize& s) 0274 { 0275 return QRectF(rel.x() * s.width(), 0276 rel.y() * s.height(), 0277 rel.width() * s.width(), 0278 rel.height() * s.height()).toRect(); 0279 } 0280 0281 QList<QRectF> FaceDetector::toRelativeRects(const QList<QRect>& absoluteRects, const QSize& size) 0282 { 0283 QList<QRectF> result; 0284 0285 Q_FOREACH (const QRect& r, absoluteRects) 0286 { 0287 result << toRelativeRect(r, size); 0288 } 0289 0290 return result; 0291 } 0292 0293 QList<QRect> FaceDetector::toAbsoluteRects(const QList<QRectF>& relativeRects, const QSize& size) 0294 { 0295 QList<QRect> result; 0296 0297 Q_FOREACH (const QRectF& r, relativeRects) 0298 { 0299 result << toAbsoluteRect(r, size); 0300 } 0301 0302 return result; 0303 } 0304 0305 } // namespace Digikam