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