Commit 1b8a5d53923f4771fe89d373da196d7183c736ac

Authored by Scott Klum
2 parents 7693fd5f b735d779

Merge pull request #435 from biometrics/stasm_multiface

Stasm properly uses internal detector to landmark all faces in image
3rdparty/stasm/stasm/CMakeLists.txt deleted
1 -# Stasm CMakeLists  
2 -  
3 -aux_source_directory(stasm/src STASM_SRC)  
4 -include_directories(stasm/include)  
5 -  
6 -#aux_source_directory(tasm/src TASM_SRC)  
7 -#include_directories(tasm/include)  
8 -  
9 -add_library(stasm SHARED ${STASM_SRC} ${TASM_SRC})  
10 -qt5_use_modules(stasm ${QT_DEPENDENCIES})  
11 -  
12 -set_target_properties(stasm PROPERTIES  
13 - DEFINE_SYMBOL STASM_LIBRARY  
14 - VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}  
15 - SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}  
16 - LINK_INTERFACE_LIBRARIES ""  
17 - AUTOMOC TRUE)  
18 -  
19 -target_link_libraries(stasm ${OpenCV_LIBS} ${Qt5Core_QTMAIN_LIBRARIES})  
20 -  
21 -file(GLOB STASM_HEADERS stasm/include/*.h)  
22 -install(FILES ${STASM_HEADERS} DESTINATION include/stasm)  
23 -file(GLOB CLASSIC_HEADERS stasm/include/classic/*.mh)  
24 -install(FILES ${CLASSIC_HEADERS} DESTINATION include/stasm/classic)  
25 -file(GLOB HAT_HEADERS stasm/include/hat/*.mh)  
26 -install(FILES ${HAT_HEADERS} DESTINATION include/stasm/hat)  
27 -#file(GLOB TASM_HEADERS tasm/include/*.h)  
28 -#install(FILES ${TASM_HEADERS} DESTINATION include/tasm)  
29 -  
30 -install(TARGETS stasm RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)  
3rdparty/stasm/stasm/stasm/src/print.cpp deleted
1 -// print.cpp: printing and logging utilities for the Stasm library  
2 -//  
3 -// Copyright (C) 2005-2013, Stephen Milborrow  
4 -  
5 -#include "print.h"  
6 -#include "err.h"  
7 -#include "misc.h"  
8 -  
9 -#include <stdio.h>  
10 -#include <sys/stat.h>  
11 -#include <stdarg.h>  
12 -  
13 -namespace stasm  
14 -{  
15 -bool print_g; // true to allow output to stdout (but error msgs always printed)  
16 -  
17 -bool trace_g; // true to trace Stasm internal operation  
18 -  
19 -static FILE* logfile_g; // lprintfs go to this log file as well as stdout  
20 -  
21 -//-----------------------------------------------------------------------------  
22 -  
23 -// Open the log file. After this, when you call lprintf, you print to the log  
24 -// file (as well as to stdout). This inits the global variable logfile_g.  
25 -  
26 -void OpenLogFile( // also inits the global variable logfile_g  
27 - const char* path) // in: log file path, default is "stasm.log"  
28 -{  
29 - if (!logfile_g)  
30 - {  
31 - if (print_g)  
32 - printf("Generating %s\n", path);  
33 - logfile_g = fopen(path, "wb");  
34 - if (!logfile_g)  
35 - Err("Cannot open \"%s\"", path);  
36 - // check that we can write to the log file  
37 - if (fputs("log file\n", logfile_g) < 0)  
38 - Err("Cannot write to \"%s\"", path);  
39 - rewind(logfile_g); // rewind so above test msg is not in the log file  
40 - }  
41 -}  
42 -  
43 -// Like printf but only prints if print_g flag is set.  
44 -// Also prints to the log file if it is open (regardless of print_g).  
45 -  
46 -void lprintf(const char* format, ...) // args like printf  
47 -{  
48 - char s[SBIG];  
49 - va_list args;  
50 - va_start(args, format);  
51 - VSPRINTF(s, format, args);  
52 - va_end(args);  
53 - if (print_g)  
54 - {  
55 - printf("%s", s);  
56 - fflush(stdout); // flush so if there is a crash we can see what happened  
57 - }  
58 - if (logfile_g)  
59 - {  
60 - // we don't check fputs here, to prevent recursive calls and msgs  
61 - fputs(s, logfile_g);  
62 - fflush(logfile_g);  
63 - }  
64 -}  
65 -  
66 -// Like printf but prints to the log file only (and not to stdout).  
67 -// Used for detailed stuff that we don't usually want to see.  
68 -  
69 -void logprintf(const char* format, ...) // args like printf  
70 -{  
71 - if (logfile_g)  
72 - {  
73 - char s[SBIG];  
74 - va_list args;  
75 - va_start(args, format);  
76 - VSPRINTF(s, format, args);  
77 - va_end(args);  
78 - // we don't check fputs here, to prevent recursive calls and msgs  
79 - fputs(s, logfile_g);  
80 - fflush(logfile_g);  
81 - }  
82 -}  
83 -  
84 -// Like lprintf but always prints even if print_g is false.  
85 -  
86 -void lprintf_always(const char* format, ...)  
87 -{  
88 - char s[SBIG];  
89 - va_list args;  
90 - va_start(args, format);  
91 - VSPRINTF(s, format, args);  
92 - va_end(args);  
93 - printf("%s", s);  
94 - fflush(stdout); // flush so if there is a crash we can see what happened  
95 - if (logfile_g)  
96 - {  
97 - // we don't check fputs here, to prevent recursive calls and msgs  
98 - fputs(s, logfile_g);  
99 - fflush(logfile_g);  
100 - }  
101 -}  
102 -  
103 -// Like puts but prints to the log file as well if it is open,  
104 -// and does not append a newline.  
105 -  
106 -void lputs(const char* s)  
107 -{  
108 - printf("%s", s);  
109 - fflush(stdout); // flush so if there is a crash we can see what happened  
110 - logprintf("%s", s);  
111 -}  
112 -  
113 -// Print message only once on the screen, and only 100 times to the log file.  
114 -// This is used when similar messages could be printed many times and it  
115 -// suffices to let the user know just once. By convention the message is  
116 -// printed followed by "..." so the user knows that just the first message  
117 -// was printed. The user can look in the log file for further messages if  
118 -// necessary (but we print only 100 times to the log file --- else all the  
119 -// log prints make tasm slow).  
120 -  
121 -void PrintOnce(  
122 - int& printed, // io: zero=print, nonzero=no print  
123 - const char* format, ...) // in: args like printf  
124 -{  
125 - char s[SBIG];  
126 - va_list args;  
127 - va_start(args, format);  
128 - VSPRINTF(s, format, args);  
129 - va_end(args);  
130 - if (printed == 0 && print_g)  
131 - {  
132 - printed = 1;  
133 - printf("%s", s);  
134 - fflush(stdout); // flush so if there is a crash we can see what happened  
135 - }  
136 - if (printed < 100 && logfile_g)  
137 - {  
138 - fputs(s, logfile_g);  
139 - fflush(logfile_g);  
140 - printed++;  
141 - if (printed == 100)  
142 - logprintf("no more prints of the above message (printed == 100)\n");  
143 - }  
144 -}  
145 -  
146 -} // namespace stasm  
3rdparty/stasm4.0.0/stasm/MOD_1/facedet.cpp
@@ -47,28 +47,16 @@ void DetectFaces( // all face rects into detpars @@ -47,28 +47,16 @@ void DetectFaces( // all face rects into detpars
47 int minwidth, 47 int minwidth,
48 cv::CascadeClassifier cascade) // in: as percent of img width 48 cv::CascadeClassifier cascade) // in: as percent of img width
49 { 49 {
50 - int leftborder = 0, topborder = 0; // border size in pixels  
51 - Image bordered_img(BORDER_FRAC == 0?  
52 - img: EnborderImg(leftborder, topborder, img));  
53 -  
54 - // Detection results are very slightly better with equalization  
55 - // (tested on the MUCT images, which are not pre-equalized), and  
56 - // it's quick enough to equalize (roughly 10ms on a 1.6 GHz laptop).  
57 -  
58 - Image equalized_img; cv::equalizeHist(bordered_img, equalized_img);  
59 -  
60 CV_Assert(minwidth >= 1 && minwidth <= 100); 50 CV_Assert(minwidth >= 1 && minwidth <= 100);
61 51
62 - int minpix = MAX(100, cvRound(img.cols * minwidth / 100.));  
63 -  
64 // the params below are accurate but slow 52 // the params below are accurate but slow
65 - static const double SCALE_FACTOR = 1.1;  
66 - static const int MIN_NEIGHBORS = 3; 53 + static const double SCALE_FACTOR = 1.2;
  54 + static const int MIN_NEIGHBORS = 5;
67 static const int DETECTOR_FLAGS = 0; 55 static const int DETECTOR_FLAGS = 0;
68 56
69 vec_Rect facerects = // all face rects in image 57 vec_Rect facerects = // all face rects in image
70 - Detect(equalized_img, &cascade, NULL,  
71 - SCALE_FACTOR, MIN_NEIGHBORS, DETECTOR_FLAGS, minpix); 58 + Detect(img, &cascade, NULL,
  59 + SCALE_FACTOR, MIN_NEIGHBORS, DETECTOR_FLAGS, 64);
72 60
73 // copy face rects into the detpars vector 61 // copy face rects into the detpars vector
74 62
@@ -80,8 +68,6 @@ void DetectFaces( // all face rects into detpars @@ -80,8 +68,6 @@ void DetectFaces( // all face rects into detpars
80 // detpar.x and detpar.y is the center of the face rectangle 68 // detpar.x and detpar.y is the center of the face rectangle
81 detpar.x = facerect->x + facerect->width / 2.; 69 detpar.x = facerect->x + facerect->width / 2.;
82 detpar.y = facerect->y + facerect->height / 2.; 70 detpar.y = facerect->y + facerect->height / 2.;
83 - detpar.x -= leftborder; // discount the border we added earlier  
84 - detpar.y -= topborder;  
85 detpar.width = double(facerect->width); 71 detpar.width = double(facerect->width);
86 detpar.height = double(facerect->height); 72 detpar.height = double(facerect->height);
87 detpar.yaw = 0; // assume face has no yaw in this version of Stasm 73 detpar.yaw = 0; // assume face has no yaw in this version of Stasm
@@ -187,10 +173,10 @@ void FaceDet::DetectFaces_( // call once per image to find all the faces @@ -187,10 +173,10 @@ void FaceDet::DetectFaces_( // call once per image to find all the faces
187 //CV_Assert(!facedet_g.empty()); // check that OpenFaceDetector_ was called 173 //CV_Assert(!facedet_g.empty()); // check that OpenFaceDetector_ was called
188 174
189 DetectFaces(detpars_, img, minwidth, cascade); 175 DetectFaces(detpars_, img, minwidth, cascade);
190 - DiscardMissizedFaces(detpars_); 176 + //DiscardMissizedFaces(detpars_);
191 if (multiface) // order faces on increasing distance from left margin 177 if (multiface) // order faces on increasing distance from left margin
192 { 178 {
193 - sort(detpars_.begin(), detpars_.end(), IncreasingLeftMargin); 179 + sort(detpars_.begin(), detpars_.end(), DecreasingWidth);
194 } 180 }
195 else 181 else
196 { 182 {
3rdparty/stasm4.0.0/stasm/MOD_1/facedet.h
@@ -38,13 +38,11 @@ public: @@ -38,13 +38,11 @@ public:
38 38
39 FaceDet() {} // constructor 39 FaceDet() {} // constructor
40 40
41 -  
42 -private:  
43 - vector<DetPar> detpars_; // all the valid faces in the current image  
44 -  
45 int iface_; // index of current face for NextFace_ 41 int iface_; // index of current face for NextFace_
46 // indexes into detpars_ 42 // indexes into detpars_
  43 + vector<DetPar> detpars_; // all the valid faces in the current image
47 44
  45 +private:
48 DISALLOW_COPY_AND_ASSIGN(FaceDet); 46 DISALLOW_COPY_AND_ASSIGN(FaceDet);
49 47
50 }; // end class FaceDet 48 }; // end class FaceDet
3rdparty/stasm4.0.0/stasm/stasm_lib.cpp
@@ -160,7 +160,8 @@ int stasm_search_auto_ext( // extended version of stasm_search_auto @@ -160,7 +160,8 @@ int stasm_search_auto_ext( // extended version of stasm_search_auto
160 const char* data, 160 const char* data,
161 const int width, 161 const int width,
162 const int height, 162 const int height,
163 - StasmCascadeClassifier cascade) 163 + StasmCascadeClassifier cascade,
  164 + FaceDet &detection)
164 { 165 {
165 int returnval = 1; // assume success 166 int returnval = 1; // assume success
166 *foundface = 0; // but assume no face found 167 *foundface = 0; // but assume no face found
@@ -176,15 +177,14 @@ int stasm_search_auto_ext( // extended version of stasm_search_auto @@ -176,15 +177,14 @@ int stasm_search_auto_ext( // extended version of stasm_search_auto
176 // Allocate image 177 // Allocate image
177 Image img = Image(height, width,(unsigned char*)data); 178 Image img = Image(height, width,(unsigned char*)data);
178 179
179 - FaceDet facedet;  
180 -  
181 // call the face detector to detect the face rectangle(s) 180 // call the face detector to detect the face rectangle(s)
182 - facedet.DetectFaces_(img, NULL, false, 10, NULL, cascade.faceCascade); 181 + if (detection.detpars_.empty())
  182 + detection.DetectFaces_(img, NULL, true, 10, NULL, cascade.faceCascade);
183 183
184 // Get the start shape for the next face in the image, and the ROI around it. 184 // Get the start shape for the next face in the image, and the ROI around it.
185 // The shape will be wrt the ROI frame. 185 // The shape will be wrt the ROI frame.
186 if (NextStartShapeAndRoi(shape, face_roi, detpar_roi, detpar, 186 if (NextStartShapeAndRoi(shape, face_roi, detpar_roi, detpar,
187 - img, mods_g, facedet, cascade)) 187 + img, mods_g, detection, cascade))
188 { 188 {
189 // now working with maybe flipped ROI and start shape in ROI frame 189 // now working with maybe flipped ROI and start shape in ROI frame
190 *foundface = 1; 190 *foundface = 1;
@@ -219,9 +219,10 @@ int stasm_search_auto(// call repeatedly to find all faces @@ -219,9 +219,10 @@ int stasm_search_auto(// call repeatedly to find all faces
219 const char *data, 219 const char *data,
220 const int width, 220 const int width,
221 const int height, 221 const int height,
222 - StasmCascadeClassifier cascade) 222 + StasmCascadeClassifier cascade,
  223 + FaceDet &detection)
223 { 224 {
224 - return stasm_search_auto_ext(foundface, landmarks, NULL, data, width, height, cascade); 225 + return stasm_search_auto_ext(foundface, landmarks, NULL, data, width, height, cascade, detection);
225 } 226 }
226 227
227 int stasm_search_single( // wrapper for stasm_search_auto and friends 228 int stasm_search_single( // wrapper for stasm_search_auto and friends
@@ -237,7 +238,8 @@ int stasm_search_single( // wrapper for stasm_search_auto and friends @@ -237,7 +238,8 @@ int stasm_search_single( // wrapper for stasm_search_auto and friends
237 (void) datadir; 238 (void) datadir;
238 (void) imgpath; 239 (void) imgpath;
239 240
240 - return stasm_search_auto(foundface, landmarks, img, width, height, cascade); 241 + FaceDet detection;
  242 + return stasm_search_auto(foundface, landmarks, img, width, height, cascade, detection);
241 } 243 }
242 244
243 int stasm_search_pinned( // call after the user has pinned some points 245 int stasm_search_pinned( // call after the user has pinned some points
3rdparty/stasm4.0.0/stasm/stasm_lib.h
@@ -64,6 +64,10 @@ @@ -64,6 +64,10 @@
64 64
65 #include "stasmcascadeclassifier.h" 65 #include "stasmcascadeclassifier.h"
66 66
  67 +namespace stasm {
  68 + class FaceDet;
  69 +}
  70 +
67 static const int stasm_NLANDMARKS = 77; // number of landmarks 71 static const int stasm_NLANDMARKS = 77; // number of landmarks
68 72
69 extern const char* const stasm_VERSION; 73 extern const char* const stasm_VERSION;
@@ -89,7 +93,8 @@ int stasm_search_auto( // call repeatedly to find all faces @@ -89,7 +93,8 @@ int stasm_search_auto( // call repeatedly to find all faces
89 const char* data, 93 const char* data,
90 const int width, 94 const int width,
91 const int height, 95 const int height,
92 - StasmCascadeClassifier cascade); 96 + StasmCascadeClassifier cascade,
  97 + stasm::FaceDet &detection);
93 98
94 extern "C" 99 extern "C"
95 int stasm_search_single( // wrapper for stasm_search_auto and friends 100 int stasm_search_single( // wrapper for stasm_search_auto and friends
openbr/plugins/core/algorithms.cpp
@@ -39,7 +39,7 @@ class AlgorithmsInitializer : public Initializer @@ -39,7 +39,7 @@ class AlgorithmsInitializer : public Initializer
39 Globals->abbreviations.insert("FR_Mouth", "(CropFromLandmarks([59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76])+Resize(24,48))"); 39 Globals->abbreviations.insert("FR_Mouth", "(CropFromLandmarks([59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76])+Resize(24,48))");
40 Globals->abbreviations.insert("FR_Nose", "(CropFromLandmarks([16,17,18,19,20,21,22,23,24,25,26,27],padding=3)+Resize(36,36))"); 40 Globals->abbreviations.insert("FR_Nose", "(CropFromLandmarks([16,17,18,19,20,21,22,23,24,25,26,27],padding=3)+Resize(36,36))");
41 Globals->abbreviations.insert("FR_Face", "(Crop(24,24,88,88)+Resize(44,44))"); 41 Globals->abbreviations.insert("FR_Face", "(Crop(24,24,88,88)+Resize(44,44))");
42 - Globals->abbreviations.insert("FR_Detect", "(FaceDetection+Stasm+Rename(StasmLeftEye,Affine_1,true)+Rename(StasmRightEye,Affine_0,true)+Affine(136,136,0.35,0.35,warpPoints=true))"); 42 + Globals->abbreviations.insert("FR_Detect", "(Open+Cvt(Gray)+Cascade+Stasm+Rename(StasmLeftEye,Affine_1,true)+Rename(StasmRightEye,Affine_0,true)+Affine(136,136,0.35,0.35,warpPoints=true))");
43 Globals->abbreviations.insert("FR_Represent", "((DenseHOG/DenseLBP)+Cat+LDA(.98)+Normalize(L2))"); 43 Globals->abbreviations.insert("FR_Represent", "((DenseHOG/DenseLBP)+Cat+LDA(.98)+Normalize(L2))");
44 44
45 Globals->abbreviations.insert("GenderClassification", "FaceDetection+Expand+FaceClassificationRegistration+Expand+<FaceClassificationExtraction>+<GenderClassifier>+Discard"); 45 Globals->abbreviations.insert("GenderClassification", "FaceDetection+Expand+FaceClassificationRegistration+Expand+<FaceClassificationExtraction>+<GenderClassifier>+Discard");
openbr/plugins/metadata/stasm4.cpp
1 #include <QString> 1 #include <QString>
2 -#include <stasm_lib.h>  
3 #include <stasmcascadeclassifier.h> 2 #include <stasmcascadeclassifier.h>
  3 +#include <stasm_lib.h>
  4 +#include <stasm.h>
4 #include <opencv2/opencv.hpp> 5 #include <opencv2/opencv.hpp>
5 6
6 #include <openbr/plugins/openbr_internal.h> 7 #include <openbr/plugins/openbr_internal.h>
@@ -56,18 +57,18 @@ BR_REGISTER(Initializer, StasmInitializer) @@ -56,18 +57,18 @@ BR_REGISTER(Initializer, StasmInitializer)
56 * \brief Wraps STASM key point detector 57 * \brief Wraps STASM key point detector
57 * \author Scott Klum \cite sklum 58 * \author Scott Klum \cite sklum
58 */ 59 */
59 -class StasmTransform : public UntrainableTransform 60 +class StasmTransform : public UntrainableMetaTransform
60 { 61 {
61 Q_OBJECT 62 Q_OBJECT
62 63
63 Q_PROPERTY(bool stasm3Format READ get_stasm3Format WRITE set_stasm3Format RESET reset_stasm3Format STORED false) 64 Q_PROPERTY(bool stasm3Format READ get_stasm3Format WRITE set_stasm3Format RESET reset_stasm3Format STORED false)
64 BR_PROPERTY(bool, stasm3Format, false) 65 BR_PROPERTY(bool, stasm3Format, false)
65 - Q_PROPERTY(bool clearLandmarks READ get_clearLandmarks WRITE set_clearLandmarks RESET reset_clearLandmarks STORED false)  
66 - BR_PROPERTY(bool, clearLandmarks, false)  
67 Q_PROPERTY(QList<float> pinPoints READ get_pinPoints WRITE set_pinPoints RESET reset_pinPoints STORED false) 66 Q_PROPERTY(QList<float> pinPoints READ get_pinPoints WRITE set_pinPoints RESET reset_pinPoints STORED false)
68 BR_PROPERTY(QList<float>, pinPoints, QList<float>()) 67 BR_PROPERTY(QList<float>, pinPoints, QList<float>())
69 Q_PROPERTY(QStringList pinLabels READ get_pinLabels WRITE set_pinLabels RESET reset_pinLabels STORED false) 68 Q_PROPERTY(QStringList pinLabels READ get_pinLabels WRITE set_pinLabels RESET reset_pinLabels STORED false)
70 BR_PROPERTY(QStringList, pinLabels, QStringList()) 69 BR_PROPERTY(QStringList, pinLabels, QStringList())
  70 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
  71 + BR_PROPERTY(QString, inputVariable, "FrontalFace")
71 72
72 Resource<StasmCascadeClassifier> stasmCascadeResource; 73 Resource<StasmCascadeClassifier> stasmCascadeResource;
73 74
@@ -77,86 +78,119 @@ class StasmTransform : public UntrainableTransform @@ -77,86 +78,119 @@ class StasmTransform : public UntrainableTransform
77 stasmCascadeResource.setResourceMaker(new StasmResourceMaker()); 78 stasmCascadeResource.setResourceMaker(new StasmResourceMaker());
78 } 79 }
79 80
80 - void project(const Template &src, Template &dst) const 81 + QList<QPointF> convertLandmarks(int nLandmarks, float *landmarks) const
81 { 82 {
82 - Mat stasmSrc(src);  
83 - if (src.m().channels() == 3)  
84 - cvtColor(src, stasmSrc, CV_BGR2GRAY);  
85 - else if (src.m().channels() != 1)  
86 - qFatal("Stasm expects single channel matrices.");  
87 -  
88 - if (!stasmSrc.isContinuous())  
89 - qFatal("Stasm expects continuous matrix data.");  
90 - dst = src;  
91 -  
92 - int foundFace = 0;  
93 - int nLandmarks = stasm_NLANDMARKS;  
94 - float landmarks[2 * stasm_NLANDMARKS];  
95 -  
96 - bool searchPinned = false;  
97 -  
98 - QPointF rightEye, leftEye;  
99 - /* Two use cases are accounted for:  
100 - * 1. Pin eyes without normalization: in this case the string list should contain the KEYS for right then left eyes, respectively.  
101 - * 2. Pin eyes with normalization: in this case the string list should contain the COORDINATES of the right then left eyes, respectively.  
102 - * Currently, we only support normalization with a transformation such that the src file contains Affine_0 and Affine_1. Checking for  
103 - * these keys prevents us from pinning eyes on a face that wasn't actually transformed (see AffineTransform).  
104 - * If both cases fail, we default to stasm_search_single. */  
105 -  
106 - if (!pinPoints.isEmpty() && src.file.contains("Affine_0") && src.file.contains("Affine_1")) {  
107 - rightEye = QPointF(pinPoints.at(0), pinPoints.at(1));  
108 - leftEye = QPointF(pinPoints.at(2), pinPoints.at(3));  
109 - searchPinned = true;  
110 - } else if (!pinLabels.isEmpty()) {  
111 - rightEye = src.file.get<QPointF>(pinLabels.at(0), QPointF());  
112 - leftEye = src.file.get<QPointF>(pinLabels.at(1), QPointF());  
113 - searchPinned = true;  
114 - }  
115 -  
116 - if (searchPinned) {  
117 - float pins[2 * stasm_NLANDMARKS];  
118 -  
119 - for (int i = 0; i < nLandmarks; i++) {  
120 - if (i == 38) /* Stasm Right Eye */ { pins[2*i] = rightEye.x(); pins[2*i+1] = rightEye.y(); }  
121 - else if (i == 39) /* Stasm Left Eye */ { pins[2*i] = leftEye.x(); pins[2*i+1] = leftEye.y(); }  
122 - else { pins[2*i] = 0; pins[2*i+1] = 0; }  
123 - } 83 + if (stasm3Format)
  84 + stasm_convert_shape(landmarks, 76);
124 85
125 - stasm_search_pinned(landmarks, pins, reinterpret_cast<const char*>(stasmSrc.data), stasmSrc.cols, stasmSrc.rows, NULL);  
126 -  
127 - // The ASM in Stasm is guaranteed to converge in this case  
128 - foundFace = 1; 86 + QList<QPointF> points;
  87 + for (int i = 0; i < nLandmarks; i++) {
  88 + QPointF point(landmarks[2 * i], landmarks[2 * i + 1]);
  89 + points.append(point);
129 } 90 }
130 91
131 - if (!foundFace) {  
132 - StasmCascadeClassifier *stasmCascade = stasmCascadeResource.acquire();  
133 - stasm_search_single(&foundFace, landmarks, reinterpret_cast<const char*>(stasmSrc.data), stasmSrc.cols, stasmSrc.rows, *stasmCascade, NULL, NULL);  
134 - stasmCascadeResource.release(stasmCascade);  
135 - } 92 + return points;
  93 + }
136 94
137 - if (stasm3Format) {  
138 - nLandmarks = 76;  
139 - stasm_convert_shape(landmarks, nLandmarks);  
140 - } 95 + void project(const Template &src, Template &dst) const
  96 + {
  97 + TemplateList temp;
  98 + project(TemplateList() << src, temp);
  99 + if (!temp.isEmpty()) dst = temp.first();
  100 + }
141 101
142 - // For convenience, if these are the only points/rects we want to deal with as the algorithm progresses  
143 - if (clearLandmarks) {  
144 - dst.file.clearPoints();  
145 - dst.file.clearRects();  
146 - } 102 + void project(const TemplateList &src, TemplateList &dst) const
  103 + {
  104 + foreach (const Template &t, src) {
  105 + Mat stasmSrc(t);
  106 + if (t.m().channels() == 3)
  107 + cvtColor(t, stasmSrc, CV_BGR2GRAY);
  108 + else if (t.m().channels() != 1)
  109 + qFatal("Stasm expects single channel matrices.");
  110 +
  111 + if (!stasmSrc.isContinuous())
  112 + qFatal("Stasm expects continuous matrix data.");
  113 +
  114 + int foundFace = 0;
  115 + int nLandmarks = stasm_NLANDMARKS;
  116 + float landmarks[2 * stasm_NLANDMARKS];
  117 +
  118 + bool searchPinned = false;
  119 +
  120 + QPointF rightEye, leftEye;
  121 + /* Two use cases are accounted for:
  122 + * 1. Pin eyes without normalization: in this case the string list should contain the KEYS for right then left eyes, respectively.
  123 + * 2. Pin eyes with normalization: in this case the string list should contain the COORDINATES of the right then left eyes, respectively.
  124 + * Currently, we only support normalization with a transformation such that the src file contains Affine_0 and Affine_1. Checking for
  125 + * these keys prevents us from pinning eyes on a face that wasn't actually transformed (see AffineTransform).
  126 + * If both cases fail, we default to stasm_search_single. */
  127 +
  128 + if (!pinPoints.isEmpty() && t.file.contains("Affine_0") && t.file.contains("Affine_1")) {
  129 + rightEye = QPointF(pinPoints.at(0), pinPoints.at(1));
  130 + leftEye = QPointF(pinPoints.at(2), pinPoints.at(3));
  131 + searchPinned = true;
  132 + } else if (!pinLabels.isEmpty()) {
  133 + rightEye = t.file.get<QPointF>(pinLabels.at(0), QPointF());
  134 + leftEye = t.file.get<QPointF>(pinLabels.at(1), QPointF());
  135 + searchPinned = true;
  136 + }
  137 +
  138 + if (searchPinned) {
  139 + float pins[2 * stasm_NLANDMARKS];
  140 +
  141 + for (int i = 0; i < nLandmarks; i++) {
  142 + if (i == 38) /* Stasm Right Eye */ { pins[2*i] = rightEye.x(); pins[2*i+1] = rightEye.y(); }
  143 + else if (i == 39) /* Stasm Left Eye */ { pins[2*i] = leftEye.x(); pins[2*i+1] = leftEye.y(); }
  144 + else { pins[2*i] = 0; pins[2*i+1] = 0; }
  145 + }
  146 +
  147 + stasm_search_pinned(landmarks, pins, reinterpret_cast<const char*>(stasmSrc.data), stasmSrc.cols, stasmSrc.rows, NULL);
  148 +
  149 + // The ASM in Stasm is guaranteed to converge in this case
  150 + foundFace = 1;
  151 + Template u(t.file, t.m());
  152 + QList<QPointF> points = convertLandmarks(nLandmarks, landmarks);
  153 + u.file.set("StasmRightEye", points[38]);
  154 + u.file.set("StasmLeftEye", points[39]);
  155 + u.file.appendPoints(points);
  156 + dst.append(u);
  157 + }
147 158
148 - if (!foundFace) {  
149 - if (Globals->verbose) qWarning("No face found in %s.", qPrintable(src.file.fileName()));  
150 - dst.file.fte = true;  
151 - } else {  
152 - QList<QPointF> points;  
153 - for (int i = 0; i < nLandmarks; i++) {  
154 - QPointF point(landmarks[2 * i], landmarks[2 * i + 1]);  
155 - points.append(point); 159 + if (!foundFace) {
  160 + StasmCascadeClassifier *stasmCascade = stasmCascadeResource.acquire();
  161 + foundFace = 1;
  162 + stasm::FaceDet detection;
  163 +
  164 + QList<QRectF> rects = t.file.contains(inputVariable) ? QList<QRectF>() << t.file.get<QRectF>(inputVariable) : t.file.rects();
  165 + if (!rects.isEmpty()) detection.iface_ = 0;
  166 + for (int i=0; i<rects.size(); i++) {
  167 + Rect rect = OpenCVUtils::toRect(rects[i]);
  168 + stasm::DetPar detpar;
  169 + detpar.x = rect.x + rect.width / 2.;
  170 + detpar.y = rect.y + rect.height / 2.;
  171 + detpar.width = double(rect.width);
  172 + detpar.height = double(rect.height);
  173 + detpar.yaw = 0;
  174 + detpar.eyaw = stasm::EYAW00;
  175 + detection.detpars_.push_back(detpar);
  176 + }
  177 +
  178 + while (foundFace) {
  179 + stasm_search_auto(&foundFace, landmarks, reinterpret_cast<const char*>(stasmSrc.data), stasmSrc.cols, stasmSrc.rows, *stasmCascade, detection);
  180 + if (foundFace) {
  181 + Template u(t.file, t.m());
  182 + QList<QPointF> points = convertLandmarks(nLandmarks, landmarks);
  183 + u.file.set("StasmRightEye", points[38]);
  184 + u.file.set("StasmLeftEye", points[39]);
  185 + u.file.appendPoints(points);
  186 + dst.append(u);
  187 + }
  188 +
  189 + if (!Globals->enrollAll)
  190 + break;
  191 + }
  192 + stasmCascadeResource.release(stasmCascade);
156 } 193 }
157 - dst.file.set("StasmRightEye", points[38]);  
158 - dst.file.set("StasmLeftEye", points[39]);  
159 - dst.file.appendPoints(points);  
160 } 194 }
161 } 195 }
162 }; 196 };