File indexing completed on 2025-03-09 03:54:59
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2019-07-09 0007 * Description : Preprocessor for openface nn model 0008 * 0009 * SPDX-FileCopyrightText: 2019 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com> 0010 * SPDX-FileCopyrightText: 2019-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 "openfacepreprocessor.h" 0017 0018 // Qt includes 0019 0020 #include <QFile> 0021 #include <QTime> 0022 #include <QString> 0023 #include <QDataStream> 0024 #include <QStandardPaths> 0025 0026 // Local includes 0027 0028 #include "digikam_debug.h" 0029 #include "fullobjectdetection.h" 0030 0031 namespace Digikam 0032 { 0033 0034 // --------------------------------------- Static global variables ----------------------------------- 0035 0036 /** Template for face landmark to perform alignment with open face 0037 * This variable must be declared as static so that it is allocated as long as 0038 * digiKam is still running. We need that because this variable is the internal data 0039 * for matrix faceTemplate below. 0040 */ 0041 static float FACE_TEMPLATE[3][2] = { 0042 {18.639072F, 16.249624F}, 0043 {75.73048F, 15.18443F }, 0044 {47.515285F, 49.38637F } 0045 }; 0046 0047 // --------------------------------------------------------------------------------------------------- 0048 0049 OpenfacePreprocessor::OpenfacePreprocessor() 0050 : outImageSize (cv::Size(96, 96)), 0051 faceTemplate (cv::Mat(3, 2, CV_32F, &FACE_TEMPLATE)), 0052 outerEyesNosePositions( {36, 45, 33} ) 0053 { 0054 } 0055 0056 OpenfacePreprocessor::~OpenfacePreprocessor() 0057 { 0058 } 0059 0060 bool OpenfacePreprocessor::loadModels() 0061 { 0062 QString appPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, 0063 QLatin1String("digikam/facesengine"), 0064 QStandardPaths::LocateDirectory); 0065 0066 QString data = QLatin1String("shapepredictor.dat"); 0067 QString spdata = appPath + QLatin1Char('/') + data; 0068 0069 QFile model(spdata); 0070 RedEye::ShapePredictor* const temp = new RedEye::ShapePredictor(); 0071 0072 qCDebug(DIGIKAM_FACEDB_LOG) << "Start reading shape predictor file"; 0073 0074 if (model.open(QIODevice::ReadOnly)) 0075 { 0076 QDataStream dataStream(&model); 0077 dataStream.setFloatingPointPrecision(QDataStream::SinglePrecision); 0078 dataStream >> *temp; 0079 sp = *temp; 0080 model.close(); 0081 } 0082 else 0083 { 0084 delete temp; 0085 0086 qCCritical(DIGIKAM_FACEDB_LOG) << "Cannot found faces engine model" << data; 0087 qCCritical(DIGIKAM_FACEDB_LOG) << "Faces recognition feature cannot be used!"; 0088 0089 return false; 0090 } 0091 0092 delete temp; 0093 0094 qCDebug(DIGIKAM_FACEDB_LOG) << "Finish reading shape predictor file"; 0095 0096 return true; 0097 } 0098 0099 cv::Mat OpenfacePreprocessor::process(const cv::Mat& image) 0100 { 0101 if (!sp.num_parts()) 0102 { 0103 return image; 0104 } 0105 0106 int type = image.type(); 0107 qCDebug(DIGIKAM_FACEDB_LOG) << "type: " << type; 0108 0109 cv::Mat gray; 0110 0111 if ((type == CV_8UC3) || (type == CV_16UC3)) 0112 { 0113 cv::cvtColor(image, gray, CV_RGB2GRAY); // 3 channels 0114 } 0115 else 0116 { 0117 cv::cvtColor(image, gray, CV_RGBA2GRAY); // 4 channels 0118 } 0119 0120 if ((type == CV_16UC3) || (type == CV_16UC4)) 0121 { 0122 gray.convertTo(gray, CV_8UC1, 1 / 255.0); 0123 } 0124 0125 cv::Rect new_rect(0, 0, image.cols, image.rows); 0126 cv::Mat landmarks(3, 2, CV_32F); 0127 0128 mutex.lock(); 0129 FullObjectDetection object = sp(gray, new_rect); 0130 mutex.unlock(); 0131 0132 for (size_t i = 0 ; i < outerEyesNosePositions.size() ; ++i) 0133 { 0134 int index = outerEyesNosePositions[i]; 0135 landmarks.at<float>((int)i, 0) = object.part(index)[0]; 0136 landmarks.at<float>((int)i, 1) = object.part(index)[1]; 0137 /* 0138 qCDebug(DIGIKAM_FACESENGINE_LOG) << "index = " << index 0139 << ", landmarks: (" << landmarks.at<float>(i, 0) 0140 << ", " << landmarks.at<float>(i, 1) << ")" << QT_ENDL; 0141 */ 0142 } 0143 0144 qCDebug(DIGIKAM_FACEDB_LOG) << "Full object detection and landmard computation finished"; 0145 0146 // qCDebug(DIGIKAM_FACEDB_LOG) << "Finish computing landmark in " << timer.restart() << " ms"; 0147 0148 cv::Mat affineTransformMatrix = cv::getAffineTransform(landmarks, faceTemplate); 0149 cv::Mat alignedFace; 0150 cv::warpAffine(image, alignedFace, affineTransformMatrix, outImageSize); 0151 0152 if (alignedFace.empty()) 0153 { 0154 qCDebug(DIGIKAM_FACEDB_LOG) << "Face alignment failed!"; 0155 return image; 0156 } 0157 else 0158 { 0159 qCDebug(DIGIKAM_FACEDB_LOG) << "Align face finished"; 0160 } 0161 0162 return alignedFace; 0163 } 0164 0165 } // namespace Digikam