File indexing completed on 2025-01-19 03:57:41

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2020-05-13
0007  * Description : Testing tool for different variations in dnn face detection of face engines
0008  *
0009  * SPDX-FileCopyrightText: 2020 by Nghia Duong <minhnghiaduong997 at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 // Qt includes
0016 
0017 #include <QApplication>
0018 #include <QCommandLineParser>
0019 #include <QMainWindow>
0020 #include <QScrollArea>
0021 #include <QHBoxLayout>
0022 #include <QVBoxLayout>
0023 #include <QListWidget>
0024 #include <QListWidgetItem>
0025 #include <QDir>
0026 #include <QImage>
0027 #include <QElapsedTimer>
0028 #include <QLabel>
0029 #include <QPen>
0030 #include <QPainter>
0031 
0032 // Local includes
0033 
0034 #include "digikam_debug.h"
0035 #include "opencvdnnfacedetector.h"
0036 #include "facedetector.h"
0037 
0038 using namespace Digikam;
0039 
0040 class MainWindow : public QMainWindow
0041 {
0042     Q_OBJECT
0043 
0044 public:
0045 
0046     explicit MainWindow(const QDir& directory, QWidget* const parent = nullptr);
0047     ~MainWindow() override;
0048 
0049 private:
0050 
0051     QPixmap showCVMat(const cv::Mat& cvimage)           const;
0052     QList<QRectF> detectFaces(const QString& imagePath) const;
0053     void extractFaces(const QImage& img, QImage& imgScaled, const QList<QRectF>& faces);
0054 
0055     QWidget* setupFullImageArea();
0056     QWidget* setupPaddedImage();
0057     QWidget* setupCroppedFaceArea();
0058     QWidget* setupImageList(const QDir& directory);
0059 
0060 private Q_SLOTS:
0061 
0062     void slotDetectFaces(const QListWidgetItem* imageItem);
0063 
0064 private:
0065 
0066     OpenCVDNNFaceDetector* m_detector;
0067 
0068     QLabel*                m_fullImage;
0069     QLabel*                m_paddedImage;
0070     QListWidget*           m_imageListView;
0071     QListWidget*           m_croppedfaceList;
0072 };
0073 
0074 MainWindow::MainWindow(const QDir &directory, QWidget* const parent)
0075     : QMainWindow(parent)
0076 {
0077     setWindowTitle(QLatin1String("Face detection Test"));
0078 
0079     m_detector                          = new OpenCVDNNFaceDetector(DetectorNNModel::YOLO);
0080 
0081     // Image area
0082 
0083     QWidget* const imageArea            = new QWidget(this);
0084 
0085     // TODO add control panel to adjust detection hyper parameters
0086 
0087     QWidget* const controlPanel         = new QWidget;
0088 
0089     QHBoxLayout* const processingLayout = new QHBoxLayout(imageArea);
0090     processingLayout->addWidget(setupFullImageArea());
0091     processingLayout->addWidget(setupPaddedImage());
0092     processingLayout->addWidget(setupCroppedFaceArea());
0093     processingLayout->addWidget(controlPanel);
0094 
0095     QSizePolicy spHigh(QSizePolicy::Preferred, QSizePolicy::Expanding);
0096     imageArea->setSizePolicy(spHigh);
0097 
0098     QWidget*     const mainWidget       = new QWidget(this);
0099     QVBoxLayout* const mainLayout       = new QVBoxLayout(mainWidget);
0100 
0101     mainLayout->addWidget(imageArea);
0102     mainLayout->addWidget(setupImageList(directory));
0103 
0104     setCentralWidget(mainWidget);
0105 }
0106 
0107 MainWindow::~MainWindow()
0108 {
0109     delete m_detector;
0110     delete m_fullImage;
0111     delete m_paddedImage;
0112     delete m_imageListView;
0113     delete m_croppedfaceList;
0114 }
0115 
0116 void MainWindow::slotDetectFaces(const QListWidgetItem* imageItem)
0117 {
0118     QString imagePath = imageItem->text();
0119     qCDebug(DIGIKAM_TESTS_LOG) << "Loading " << imagePath;
0120     QImage img(imagePath);
0121     QImage imgScaled(img.scaled(416, 416, Qt::KeepAspectRatio));
0122 
0123     // clear faces layout
0124 
0125     QListWidgetItem* wItem = nullptr;
0126 
0127     while ((wItem = m_croppedfaceList->item(0)) != nullptr)
0128     {
0129         delete wItem;
0130     }
0131 
0132     QList<QRectF> faces    = detectFaces(imagePath);
0133 
0134     extractFaces(img, imgScaled, faces);
0135 
0136     // Only setPixmap after finishing drawing bboxes around detected faces
0137 
0138     m_fullImage->setPixmap(QPixmap::fromImage(imgScaled));
0139 }
0140 
0141 QPixmap MainWindow::showCVMat(const cv::Mat& cvimage) const
0142 {
0143     if ((cvimage.cols * cvimage.rows) != 0)
0144     {
0145         cv::Mat rgb;
0146         QPixmap p;
0147         cv::cvtColor(cvimage, rgb, (-2*cvimage.channels()+10));
0148         p.convertFromImage(QImage(rgb.data, rgb.cols, rgb.rows, QImage::Format_RGB888));
0149 
0150         return p;
0151     }
0152 
0153     return QPixmap();
0154 }
0155 
0156 QList<QRectF> MainWindow::detectFaces(const QString& imagePath) const
0157 {
0158     QImage img(imagePath);
0159 /*
0160     cv::Mat img;
0161     img = cv::imread(imagePath.toStdString());
0162 
0163     if (!img.data )
0164     {
0165         qCDebug(DIGIKAM_TESTS_LOG) <<  "Open cv Could not open or find the image";
0166     }
0167 */
0168     QList<QRectF> faces;
0169 
0170     try
0171     {
0172         QElapsedTimer timer;
0173         unsigned int elapsedDetection = 0;
0174         timer.start();
0175 
0176         // NOTE detection with filePath won't work when format is not standard
0177         // NOTE unexpected behaviour with detecFaces(const QString&)
0178 
0179         cv::Size paddedSize(0, 0);
0180         cv::Mat cvImage       = m_detector->prepareForDetection(img, paddedSize);
0181         QList<QRect> absRects = m_detector->detectFaces(cvImage, paddedSize);
0182         faces                 = FaceDetector::toRelativeRects(absRects,
0183                                                               QSize(cvImage.cols - 2*paddedSize.width,
0184                                                               cvImage.rows - 2*paddedSize.height));
0185         elapsedDetection      = timer.elapsed();
0186 
0187         // debug padded image
0188 
0189         m_paddedImage->setPixmap(showCVMat(cvImage));
0190 
0191         qCDebug(DIGIKAM_TESTS_LOG) << "(Input CV) Found " << absRects.size() << " faces, in " << elapsedDetection << "ms";
0192     }
0193     catch (cv::Exception& e)
0194     {
0195         qCWarning(DIGIKAM_TESTS_LOG) << "cv::Exception:" << e.what();
0196     }
0197     catch (...)
0198     {
0199         qCWarning(DIGIKAM_TESTS_LOG) << "Default exception from OpenCV";
0200     }
0201 
0202     return faces;
0203 }
0204 
0205 void MainWindow::extractFaces(const QImage& img, QImage& imgScaled, const QList<QRectF>& faces)
0206 {
0207     if (faces.isEmpty())
0208     {
0209         qCWarning(DIGIKAM_TESTS_LOG) << "No face detected";
0210         return;
0211     }
0212 
0213     qCDebug(DIGIKAM_TESTS_LOG) << "Coordinates of detected faces : ";
0214 
0215     Q_FOREACH (const QRectF& r, faces)
0216     {
0217         qCDebug(DIGIKAM_TESTS_LOG) << r;
0218     }
0219 
0220     QPainter painter(&imgScaled);
0221     QPen paintPen(Qt::green);
0222     paintPen.setWidth(1);
0223     painter.setPen(paintPen);
0224 
0225     Q_FOREACH (const QRectF& rr, faces)
0226     {
0227         QRect  rectDraw = FaceDetector::toAbsoluteRect(rr, imgScaled.size());
0228         QRect  rect     = FaceDetector::toAbsoluteRect(rr, img.size());
0229         QImage part     = img.copy(rect);
0230 
0231         // Show cropped faces
0232 
0233         QIcon croppedFace(QPixmap::fromImage(part.scaled(qMin(img.size().width(), 100),
0234                                                          qMin(img.size().width(), 100),
0235                                                          Qt::KeepAspectRatio)));
0236 
0237         m_croppedfaceList->addItem(new QListWidgetItem(croppedFace, QLatin1String("")));
0238         painter.drawRect(rectDraw);
0239     }
0240 }
0241 
0242 QWidget* MainWindow::setupFullImageArea()
0243 {
0244     m_fullImage = new QLabel;
0245     m_fullImage->setScaledContents(true);
0246 
0247     QSizePolicy spImage(QSizePolicy::Preferred, QSizePolicy::Expanding);
0248     spImage.setHorizontalStretch(3);
0249     m_fullImage->setSizePolicy(spImage);
0250 
0251     return m_fullImage;
0252 }
0253 
0254 QWidget* MainWindow::setupPaddedImage()
0255 {
0256     m_paddedImage = new QLabel;
0257     m_paddedImage->setScaledContents(true);
0258 
0259     QSizePolicy spImage(QSizePolicy::Preferred, QSizePolicy::Expanding);
0260     spImage.setHorizontalStretch(3);
0261     m_paddedImage->setSizePolicy(spImage);
0262 
0263     return m_paddedImage;
0264 }
0265 
0266 QWidget* MainWindow::setupCroppedFaceArea()
0267 {
0268     QScrollArea* const facesArea = new QScrollArea(this);
0269     facesArea->setWidgetResizable(true);
0270     facesArea->setAlignment(Qt::AlignRight);
0271     facesArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0272     facesArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0273 
0274     QSizePolicy spImage(QSizePolicy::Preferred, QSizePolicy::Expanding);
0275     spImage.setHorizontalStretch(1);
0276 
0277     facesArea->setSizePolicy(spImage);
0278 
0279     m_croppedfaceList            = new QListWidget(this);
0280 
0281     m_croppedfaceList->setViewMode(QListView::IconMode);
0282     m_croppedfaceList->setIconSize(QSize(200, 150));
0283     m_croppedfaceList->setResizeMode(QListWidget::Adjust);
0284     m_croppedfaceList->setFlow(QListView::TopToBottom);
0285     m_croppedfaceList->setWrapping(false);
0286     m_croppedfaceList->setDragEnabled(false);
0287 
0288 /*
0289     connect(m_croppedfaceList, &QListWidget::currentItemChanged,
0290             this,            &MainWindow::slotDetectFaces);
0291 */
0292 
0293     facesArea->setWidget(m_croppedfaceList);
0294 
0295     return facesArea;
0296 }
0297 
0298 QWidget* MainWindow::setupImageList(const QDir& directory)
0299 {
0300     // Itemlist area
0301 
0302     QScrollArea* const itemsArea = new QScrollArea;
0303     itemsArea->setWidgetResizable(true);
0304     itemsArea->setAlignment(Qt::AlignBottom);
0305     itemsArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0306     itemsArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0307 
0308     QSizePolicy spLow(QSizePolicy::Preferred, QSizePolicy::Fixed);
0309     itemsArea->setSizePolicy(spLow);
0310 
0311     m_imageListView              = new QListWidget(this);
0312     m_imageListView->setViewMode(QListView::IconMode);
0313     m_imageListView->setIconSize(QSize(200, 150));
0314     m_imageListView->setResizeMode(QListWidget::Adjust);
0315     m_imageListView->setFlow(QListView::LeftToRight);
0316     m_imageListView->setWrapping(false);
0317     m_imageListView->setDragEnabled(false);
0318 
0319     QStringList subjects         = directory.entryList(QDir::Files | QDir::NoDotAndDotDot);
0320 
0321     for (QStringList::const_iterator iter  = subjects.cbegin();
0322                                      iter != subjects.cend();
0323                                      ++iter)
0324     {
0325         QString filePath            = directory.filePath(*iter);
0326         QListWidgetItem* const item = new QListWidgetItem(QIcon(filePath), filePath);
0327 
0328         m_imageListView->addItem(item);
0329     }
0330 
0331     connect(m_imageListView, &QListWidget::currentItemChanged,
0332             this, &MainWindow::slotDetectFaces);
0333 
0334     itemsArea->setWidget(m_imageListView);
0335 
0336     return itemsArea;
0337 }
0338 
0339 QCommandLineParser* parseOptions(const QCoreApplication& app)
0340 {
0341     QCommandLineParser* const parser = new QCommandLineParser();
0342     parser->addOption(QCommandLineOption(QLatin1String("dataset"), QLatin1String("Data set folder"), QLatin1String("path relative to data folder")));
0343     parser->addHelpOption();
0344     parser->process(app);
0345 
0346     return parser;
0347 }
0348 
0349 int main(int argc, char* argv[])
0350 {
0351     QApplication app(argc, argv);
0352     app.setApplicationName(QString::fromLatin1("digikam"));          // for DB init.
0353 
0354     // Options for commandline parser
0355 
0356     QCommandLineParser* const parser = parseOptions(app);
0357 
0358     if (! parser->isSet(QLatin1String("dataset")))
0359     {
0360         qWarning("Data set is not set !!!");
0361 
0362         return 1;
0363     }
0364 
0365     QDir dataset(parser->value(QLatin1String("dataset")));
0366 
0367     MainWindow* const window = new MainWindow(dataset, nullptr);
0368     window->show();
0369 
0370     return app.exec();
0371 }
0372 
0373 #include "benchmark_dnndetection_gui.moc"