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"