Commit db2484c2419cc56e76afe506065366d4feb974ab

Authored by sklum
2 parents 823a873d fca1772a

Merge branch 'master' of https://github.com/biometrics/openbr

.gitignore
@@ -34,3 +34,6 @@ scripts/results @@ -34,3 +34,6 @@ scripts/results
34 34
35 ### vim ### 35 ### vim ###
36 *.swp 36 *.swp
  37 +
  38 +### autogenerated sigsets ###
  39 +data/INRIAPerson/sigset
3rdparty/stasm4.0.0/stasm/faceroi.cpp
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 // Copyright (C) 2005-2013, Stephen Milborrow 3 // Copyright (C) 2005-2013, Stephen Milborrow
4 4
5 #include "stasm.h" 5 #include "stasm.h"
  6 +#include <QDebug>
6 7
7 namespace stasm 8 namespace stasm
8 { 9 {
3rdparty/stasm4.0.0/stasm/pinstart.cpp
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 // Copyright (C) 2005-2013, Stephen Milborrow 3 // Copyright (C) 2005-2013, Stephen Milborrow
4 4
5 #include "stasm.h" 5 #include "stasm.h"
  6 +#include <QDebug>
6 7
7 namespace stasm 8 namespace stasm
8 { 9 {
@@ -267,8 +268,8 @@ void PinnedStartShapeAndRoi( // use the pinned landmarks to init the start sha @@ -267,8 +268,8 @@ void PinnedStartShapeAndRoi( // use the pinned landmarks to init the start sha
267 const Shape& pinned) // in: manually pinned landmarks 268 const Shape& pinned) // in: manually pinned landmarks
268 { 269 {
269 double rot, yaw; 270 double rot, yaw;
270 - EstRotAndYawFrom5PointShape(rot, yaw,  
271 - As5PointShape(pinned, mods[0]->MeanShape_())); 271 + EstRotAndYawFrom5PointShape(rot, yaw, As5PointShape(pinned, mods[0]->MeanShape_()));
  272 +
272 const EYAW eyaw = DegreesAsEyaw(yaw, NSIZE(mods)); 273 const EYAW eyaw = DegreesAsEyaw(yaw, NSIZE(mods));
273 const int imod = EyawAsModIndex(eyaw, mods); // select ASM model based on yaw 274 const int imod = EyawAsModIndex(eyaw, mods); // select ASM model based on yaw
274 if (trace_g) 275 if (trace_g)
3rdparty/stasm4.0.0/stasm/stasm_lib.cpp
@@ -244,24 +244,21 @@ int stasm_search_single( // wrapper for stasm_search_auto and friends @@ -244,24 +244,21 @@ int stasm_search_single( // wrapper for stasm_search_auto and friends
244 int stasm_search_pinned( // call after the user has pinned some points 244 int stasm_search_pinned( // call after the user has pinned some points
245 float* landmarks, // out: x0, y0, x1, y1, ..., caller must allocate 245 float* landmarks, // out: x0, y0, x1, y1, ..., caller must allocate
246 const float* pinned, // in: pinned landmarks (0,0 points not pinned) 246 const float* pinned, // in: pinned landmarks (0,0 points not pinned)
247 - const char* img, // in: gray image data, top left corner at 0,0 247 + const char* data, // in: gray image data, top left corner at 0,0
248 int width, // in: image width 248 int width, // in: image width
249 int height, // in: image height 249 int height, // in: image height
250 const char* imgpath) // in: image path, used only for err msgs and debug 250 const char* imgpath) // in: image path, used only for err msgs and debug
251 { 251 {
252 - (void) img;  
253 (void) width; 252 (void) width;
254 (void) height; 253 (void) height;
255 (void) imgpath; 254 (void) imgpath;
256 255
257 int returnval = 1; // assume success 256 int returnval = 1; // assume success
258 - CatchOpenCvErrs();  
259 try 257 try
260 { 258 {
261 - CV_Assert(imgpath && STRNLEN(imgpath, SLEN) < SLEN);  
262 CheckStasmInit(); 259 CheckStasmInit();
263 260
264 - //img_g = Image(height, width, (unsigned char*)img); 261 + Image img = Image(height, width,(unsigned char*)data);
265 262
266 const Shape pinnedshape(LandmarksAsShape(pinned)); 263 const Shape pinnedshape(LandmarksAsShape(pinned));
267 264
@@ -271,8 +268,7 @@ int stasm_search_pinned( // call after the user has pinned some points @@ -271,8 +268,7 @@ int stasm_search_pinned( // call after the user has pinned some points
271 DetPar detpar_roi; // detpar translated to ROI frame 268 DetPar detpar_roi; // detpar translated to ROI frame
272 DetPar detpar; // params returned by pseudo face det, in img frame 269 DetPar detpar; // params returned by pseudo face det, in img frame
273 270
274 - /*PinnedStartShapeAndRoi(shape, face_roi, detpar_roi, detpar, pinned_roi,  
275 - img_g, mods_g, pinnedshape);*/ 271 + PinnedStartShapeAndRoi(shape, face_roi, detpar_roi, detpar, pinned_roi, img, mods_g, pinnedshape);
276 272
277 // now working with maybe flipped ROI and start shape in ROI frame 273 // now working with maybe flipped ROI and start shape in ROI frame
278 const int imod = ABS(EyawAsModIndex(detpar.eyaw, mods_g)); 274 const int imod = ABS(EyawAsModIndex(detpar.eyaw, mods_g));
@@ -284,14 +280,11 @@ int stasm_search_pinned( // call after the user has pinned some points @@ -284,14 +280,11 @@ int stasm_search_pinned( // call after the user has pinned some points
284 RoundMat(shape); 280 RoundMat(shape);
285 ForcePinnedPoints(shape, pinnedshape); // undo above RoundMat on pinned points 281 ForcePinnedPoints(shape, pinnedshape); // undo above RoundMat on pinned points
286 ShapeToLandmarks(landmarks, shape); 282 ShapeToLandmarks(landmarks, shape);
287 - if (trace_g)  
288 - lprintf("\n");  
289 } 283 }
290 catch(...) 284 catch(...)
291 { 285 {
292 returnval = 0; // a call was made to Err or a CV_Assert failed 286 returnval = 0; // a call was made to Err or a CV_Assert failed
293 } 287 }
294 - UncatchOpenCvErrs();  
295 return returnval; 288 return returnval;
296 } 289 }
297 290
app/br/br.cpp
@@ -111,8 +111,8 @@ public: @@ -111,8 +111,8 @@ public:
111 111
112 // Secondary Tasks 112 // Secondary Tasks
113 else if (!strcmp(fun, "fuse")) { 113 else if (!strcmp(fun, "fuse")) {
114 - check(parc >= 5, "Insufficient parameter count for 'fuse'.");  
115 - br_fuse(parc-4, parv, parv[parc-4], parv[parc-3], parv[parc-2], parv[parc-1]); 114 + check(parc >= 4, "Insufficient parameter count for 'fuse'.");
  115 + br_fuse(parc-3, parv, parv[parc-3], parv[parc-2], parv[parc-1]);
116 } else if (!strcmp(fun, "cluster")) { 116 } else if (!strcmp(fun, "cluster")) {
117 check(parc >= 3, "Insufficient parameter count for 'cluster'."); 117 check(parc >= 3, "Insufficient parameter count for 'cluster'.");
118 br_cluster(parc-2, parv, atof(parv[parc-2]), parv[parc-1]); 118 br_cluster(parc-2, parv, atof(parv[parc-2]), parv[parc-1]);
@@ -215,7 +215,7 @@ private: @@ -215,7 +215,7 @@ private:
215 "-plot <file> ... <file> {destination}\n" 215 "-plot <file> ... <file> {destination}\n"
216 "\n" 216 "\n"
217 "==== Other Commands ====\n" 217 "==== Other Commands ====\n"
218 - "-fuse <simmat> ... <simmat> <mask> (None|MinMax|ZScore|WScore) (Min|Max|Sum[W1:W2:...:Wn]|Replace|Difference|None) {simmat}\n" 218 + "-fuse <simmat> ... <simmat> (None|MinMax|ZScore|WScore) (Min|Max|Sum[W1:W2:...:Wn]|Replace|Difference|None) {simmat}\n"
219 "-cluster <simmat> ... <simmat> <aggressiveness> {csv}\n" 219 "-cluster <simmat> ... <simmat> <aggressiveness> {csv}\n"
220 "-makeMask <target_gallery> <query_gallery> {mask}\n" 220 "-makeMask <target_gallery> <query_gallery> {mask}\n"
221 "-combineMasks <mask> ... <mask> {mask} (And|Or)\n" 221 "-combineMasks <mask> ... <mask> {mask} (And|Or)\n"
app/examples/face_recognition.cpp
@@ -45,7 +45,7 @@ int main(int argc, char *argv[]) @@ -45,7 +45,7 @@ int main(int argc, char *argv[])
45 45
46 // Initialize templates 46 // Initialize templates
47 br::Template queryA("../data/MEDS/img/S354-01-t10_01.jpg"); 47 br::Template queryA("../data/MEDS/img/S354-01-t10_01.jpg");
48 - br::Template queryB("../data/MEDS/img/S386-04-t10_01.jpg"); 48 + br::Template queryB("../data/MEDS/img/S382-08-t10_01.jpg");
49 br::Template target("../data/MEDS/img/S354-02-t10_01.jpg"); 49 br::Template target("../data/MEDS/img/S354-02-t10_01.jpg");
50 50
51 // Enroll templates 51 // Enroll templates
data/INRIAPerson/README.md 0 → 100644
  1 +## INRIA Person Database
  2 +Dataset for human detection in several formats: original positive and negative images with bounding box annotations and normalized positive images (just the bounding box).
  3 +* [Website](http://pascal.inrialpes.fr/data/human/)
openbr/core/bee.cpp
@@ -75,6 +75,21 @@ FileList BEE::readSigset(const File &amp;sigset, bool ignoreMetadata) @@ -75,6 +75,21 @@ FileList BEE::readSigset(const File &amp;sigset, bool ignoreMetadata)
75 else if (!ignoreMetadata) file.set(key, value); 75 else if (!ignoreMetadata) file.set(key, value);
76 } 76 }
77 77
  78 + // add bounding boxes, if they exist (will be child elements of <presentation>)
  79 + if (fileNode.hasChildNodes()) {
  80 + QList<QRectF> rects;
  81 + QDomNodeList bboxes = fileNode.childNodes();
  82 + for (int i=0; i<bboxes.length(); i++) {
  83 + QDomElement bbox = bboxes.at(i).toElement();
  84 + qreal x = bbox.attribute("x").toDouble();
  85 + qreal y = bbox.attribute("y").toDouble();
  86 + qreal width = bbox.attribute("width").toDouble();
  87 + qreal height = bbox.attribute("height").toDouble();
  88 + rects += QRectF(x, y, width, height);
  89 + }
  90 + file.setRects(rects);
  91 + }
  92 +
78 if (file.name.isEmpty()) qFatal("Missing file-name in %s.", qPrintable(sigset)); 93 if (file.name.isEmpty()) qFatal("Missing file-name in %s.", qPrintable(sigset));
79 fileList.append(file); 94 fileList.append(file);
80 95
@@ -112,7 +127,7 @@ void BEE::writeSigset(const QString &amp;sigset, const br::FileList &amp;files, bool ign @@ -112,7 +127,7 @@ void BEE::writeSigset(const QString &amp;sigset, const br::FileList &amp;files, bool ign
112 metadata.append("Rects=\"["+landmarks.join(",")+"]\""); 127 metadata.append("Rects=\"["+landmarks.join(",")+"]\"");
113 } 128 }
114 } 129 }
115 - lines.append("\t<biometric-signature name=\"" + file.get<QString>("Label",file.fileName()) +"\">"); 130 + lines.append("\t<biometric-signature name=\"" + file.get<QString>("Label",file.baseName()) +"\">");
116 lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>"); 131 lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>");
117 lines.append("\t</biometric-signature>"); 132 lines.append("\t</biometric-signature>");
118 } 133 }
@@ -302,7 +317,6 @@ cv::Mat BEE::makeMask(const br::FileList &amp;targets, const br::FileList &amp;queries, @@ -302,7 +317,6 @@ cv::Mat BEE::makeMask(const br::FileList &amp;targets, const br::FileList &amp;queries,
302 else if (partitionA != partition) val = DontCare; 317 else if (partitionA != partition) val = DontCare;
303 else if (partitionB == -1) val = NonMatch; 318 else if (partitionB == -1) val = NonMatch;
304 else if (partitionB != partition) val = DontCare; 319 else if (partitionB != partition) val = DontCare;
305 - else if (partitionA != partitionB) val = DontCare;  
306 else if (labelA == labelB) val = Match; 320 else if (labelA == labelB) val = Match;
307 else val = NonMatch; 321 else val = NonMatch;
308 mask.at<Mask_t>(i,j) = val; 322 mask.at<Mask_t>(i,j) = val;
openbr/core/core.cpp
@@ -43,6 +43,11 @@ struct AlgorithmCore @@ -43,6 +43,11 @@ struct AlgorithmCore
43 { 43 {
44 TemplateList data(TemplateList::fromGallery(input)); 44 TemplateList data(TemplateList::fromGallery(input));
45 45
  46 + // set the Train bool metadata, in case a Transform's project
  47 + // needs to know if it's called during train or enroll
  48 + for (int i=0; i<data.size(); i++)
  49 + data[i].file.set("Train", true);
  50 +
46 if (transform.isNull()) qFatal("Null transform."); 51 if (transform.isNull()) qFatal("Null transform.");
47 qDebug("%d training files", data.size()); 52 qDebug("%d training files", data.size());
48 53
openbr/core/fuse.cpp
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 16
17 #include <QList> 17 #include <QList>
18 #include <QStringList> 18 #include <QStringList>
  19 +#include "openbr/core/opencvutils.h"
19 #include <limits> 20 #include <limits>
20 #include <vector> 21 #include <vector>
21 #include <opencv2/core/core.hpp> 22 #include <opencv2/core/core.hpp>
@@ -36,8 +37,8 @@ static void normalizeMatrix(Mat &amp;matrix, const Mat &amp;mask, const QString &amp;method) @@ -36,8 +37,8 @@ static void normalizeMatrix(Mat &amp;matrix, const Mat &amp;mask, const QString &amp;method)
36 for (int j=0; j<matrix.cols; j++) { 37 for (int j=0; j<matrix.cols; j++) {
37 float val = matrix.at<float>(i,j); 38 float val = matrix.at<float>(i,j);
38 if ((mask.at<BEE::Mask_t>(i,j) == BEE::DontCare) || 39 if ((mask.at<BEE::Mask_t>(i,j) == BEE::DontCare) ||
39 - (val == -std::numeric_limits<float>::infinity()) ||  
40 - (val == std::numeric_limits<float>::infinity())) 40 + (val == -std::numeric_limits<float>::max()) ||
  41 + (val == std::numeric_limits<float>::max()))
41 continue; 42 continue;
42 vals.append(val); 43 vals.append(val);
43 } 44 }
@@ -53,20 +54,20 @@ static void normalizeMatrix(Mat &amp;matrix, const Mat &amp;mask, const QString &amp;method) @@ -53,20 +54,20 @@ static void normalizeMatrix(Mat &amp;matrix, const Mat &amp;mask, const QString &amp;method)
53 for (int j=0; j<matrix.cols; j++) { 54 for (int j=0; j<matrix.cols; j++) {
54 if (mask.at<BEE::Mask_t>(i,j) == BEE::DontCare) continue; 55 if (mask.at<BEE::Mask_t>(i,j) == BEE::DontCare) continue;
55 float &val = matrix.at<float>(i,j); 56 float &val = matrix.at<float>(i,j);
56 - if (val == -std::numeric_limits<float>::infinity()) val = 0;  
57 - else if (val == std::numeric_limits<float>::infinity()) val = 1; 57 + if (val == -std::numeric_limits<float>::max()) val = 0;
  58 + else if (val == std::numeric_limits<float>::max()) val = 1;
58 else val = (val - min) / (max - min); 59 else val = (val - min) / (max - min);
59 } 60 }
60 } 61 }
61 } else if (method == "ZScore") { 62 } else if (method == "ZScore") {
62 if (stddev == 0) qFatal("Stddev is 0."); 63 if (stddev == 0) qFatal("Stddev is 0.");
63 - for (int i=0; i<matrix.rows; i++) { 64 + for (int i=0; i<matrix.rows; i++) {
64 for (int j=0; j<matrix.cols; j++) { 65 for (int j=0; j<matrix.cols; j++) {
65 if (mask.at<BEE::Mask_t>(i,j) == BEE::DontCare) continue; 66 if (mask.at<BEE::Mask_t>(i,j) == BEE::DontCare) continue;
66 float &val = matrix.at<float>(i,j); 67 float &val = matrix.at<float>(i,j);
67 - if (val == -std::numeric_limits<float>::infinity()) val = (min - mean) / stddev;  
68 - else if (val == std::numeric_limits<float>::infinity()) val = (max - mean) / stddev;  
69 - else val = (val - mean) / stddev; 68 + if (val == -std::numeric_limits<float>::max()) val = (min - mean) / stddev;
  69 + else if (val == std::numeric_limits<float>::max()) val = (max - mean) / stddev;
  70 + else val = (val - mean) / stddev;
70 } 71 }
71 } 72 }
72 } else { 73 } else {
@@ -74,63 +75,90 @@ static void normalizeMatrix(Mat &amp;matrix, const Mat &amp;mask, const QString &amp;method) @@ -74,63 +75,90 @@ static void normalizeMatrix(Mat &amp;matrix, const Mat &amp;mask, const QString &amp;method)
74 } 75 }
75 } 76 }
76 77
77 -void br::Fuse(const QStringList &inputSimmats, File mask, const QString &normalization, const QString &fusion, const QString &outputSimmat) 78 +void br::Fuse(const QStringList &inputSimmats, const QString &normalization, const QString &fusion, const QString &outputSimmat)
78 { 79 {
79 qDebug("Fusing %d to %s", inputSimmats.size(), qPrintable(outputSimmat)); 80 qDebug("Fusing %d to %s", inputSimmats.size(), qPrintable(outputSimmat));
80 - QList<Mat> matrices;  
81 - foreach (const QString &simmat, inputSimmats)  
82 - matrices.append(BEE::readSimmat(simmat));  
83 - if ((matrices.size() < 2) && (fusion != "None")) qFatal("Expected at least two similarity matrices.");  
84 - if ((matrices.size() > 1) && (fusion == "None")) qFatal("Expected exactly one similarity matrix.");  
85 -  
86 - mask.set("rows", matrices.first().rows);  
87 - mask.set("columns", matrices.first().cols);  
88 - Mat matrix_mask = BEE::readMask(mask);  
89 -  
90 - for (int i=0; i<matrices.size(); i++)  
91 - normalizeMatrix(matrices[i], matrix_mask, normalization);  
92 -  
93 - Mat fused;  
94 - if (fusion == "Max") {  
95 - max(matrices[0], matrices[1], fused);  
96 - for (int i=2; i<matrices.size(); i++)  
97 - max(fused, matrices[i], fused);  
98 - } else if (fusion == "Min") {  
99 - min(matrices[0], matrices[1], fused);  
100 - for (int i=2; i<matrices.size(); i++)  
101 - min(fused, matrices[i], fused);  
102 - } else if (fusion.startsWith("Sum")) {  
103 - QList<float> weights;  
104 - QStringList words = fusion.right(fusion.size()-3).split(":", QString::SkipEmptyParts);  
105 - if (words.size() == 0) {  
106 - for (int k=0; k<matrices.size(); k++)  
107 - weights.append(1);  
108 - } else if (words.size() == matrices.size()) {  
109 - bool ok;  
110 - for (int k=0; k<matrices.size(); k++) {  
111 - float weight = words[k].toFloat(&ok);  
112 - if (!ok) qFatal("Non-numerical weight %s.", qPrintable(words[k]));  
113 - weights.append(weight); 81 +
  82 + QString target, query, previousTarget, previousQuery;
  83 + QList<Mat> originalMatrices;
  84 + foreach (const QString &simmat, inputSimmats) {
  85 + originalMatrices.append(BEE::readSimmat(simmat,&target,&query));
  86 + // Make we're fusing score matrices for the same set of targets and querys
  87 + if (!previousTarget.isEmpty() && !previousQuery.isEmpty() && (previousTarget != target || previousQuery != query))
  88 + qFatal("Target or query files are not the same across fused matrices.");
  89 + previousTarget = target; previousQuery = query;
  90 + }
  91 +
  92 + if ((originalMatrices.size() < 2) && (fusion != "None")) qFatal("Expected at least two similarity matrices.");
  93 + if ((originalMatrices.size() > 1) && (fusion == "None")) qFatal("Expected exactly one similarity matrix.");
  94 +
  95 + const FileList targetFiles = TemplateList::fromGallery(target).files();
  96 + const FileList queryFiles = TemplateList::fromGallery(query).files();
  97 +
  98 + int partition = 0;
  99 + int crossValidate = Globals->crossValidate;
  100 + Mat buffer = Mat::zeros(originalMatrices.last().size(),originalMatrices.last().type());
  101 +
  102 + do {
  103 + QList<Mat> matrices;
  104 + foreach (const Mat& matrix, originalMatrices)
  105 + matrices.append(matrix.clone());
  106 +
  107 + Mat matrix_mask = BEE::makeMask(targetFiles,queryFiles,partition);
  108 + for (int i=0; i<matrices.size(); i++)
  109 + normalizeMatrix(matrices[i], matrix_mask, normalization);
  110 +
  111 + Mat fused;
  112 + if (fusion == "Max") {
  113 + max(matrices[0], matrices[1], fused);
  114 + for (int i=2; i<matrices.size(); i++)
  115 + max(fused, matrices[i], fused);
  116 + } else if (fusion == "Min") {
  117 + min(matrices[0], matrices[1], fused);
  118 + for (int i=2; i<matrices.size(); i++)
  119 + min(fused, matrices[i], fused);
  120 + } else if (fusion.startsWith("Sum")) {
  121 + QList<float> weights;
  122 + QStringList words = fusion.right(fusion.size()-3).split(":", QString::SkipEmptyParts);
  123 + if (words.size() == 0) {
  124 + for (int k=0; k<matrices.size(); k++)
  125 + weights.append(1);
  126 + } else if (words.size() == matrices.size()) {
  127 + bool ok;
  128 + for (int k=0; k<matrices.size(); k++) {
  129 + float weight = words[k].toFloat(&ok);
  130 + if (!ok) qFatal("Non-numerical weight %s.", qPrintable(words[k]));
  131 + weights.append(weight);
  132 + }
  133 + } else {
  134 + qFatal("Number of weights does not match number of similarity matrices.");
114 } 135 }
  136 +
  137 + addWeighted(matrices[0], weights[0], matrices[1], weights[1], 0, fused);
  138 + for (int i=2; i<matrices.size(); i++)
  139 + addWeighted(fused, 1, matrices[i], weights[i], 0, fused);
  140 + } else if (fusion == "Replace") {
  141 + if (matrices.size() != 2) qFatal("Replace fusion requires exactly two matrices.");
  142 + fused = matrices.first().clone();
  143 + matrices.last().copyTo(fused, matrix_mask != BEE::DontCare);
  144 + } else if (fusion == "Difference") {
  145 + if (matrices.size() != 2) qFatal("Difference fusion requires exactly two matrices.");
  146 + subtract(matrices[0], matrices[1], fused);
  147 + } else if (fusion == "None") {
  148 + fused = matrices[0];
115 } else { 149 } else {
116 - qFatal("Number of weights does not match number of similarity matrices."); 150 + qFatal("Invalid fusion method %s.", qPrintable(fusion));
117 } 151 }
118 152
119 - addWeighted(matrices[0], weights[0], matrices[1], weights[1], 0, fused);  
120 - for (int i=2; i<matrices.size(); i++)  
121 - addWeighted(fused, 1, matrices[i], weights[i], 0, fused);  
122 - } else if (fusion == "Replace") {  
123 - if (matrices.size() != 2) qFatal("Replace fusion requires exactly two matrices.");  
124 - fused = matrices.first().clone();  
125 - matrices.last().copyTo(fused, matrix_mask != BEE::DontCare);  
126 - } else if (fusion == "Difference") {  
127 - if (matrices.size() != 2) qFatal("Difference fusion requires exactly two matrices.");  
128 - subtract(matrices[0], matrices[1], fused);  
129 - } else if (fusion == "None") {  
130 - fused = matrices[0];  
131 - } else {  
132 - qFatal("Invalid fusion method %s.", qPrintable(fusion));  
133 - } 153 + // We don't want to add scores where the mask says we shouldn't care
  154 + Mat buffer_mask = Mat::ones(matrix_mask.size(),CV_8UC1);
  155 + buffer_mask.setTo(0,matrix_mask==BEE::DontCare);
  156 +
  157 + add(buffer,fused,buffer,buffer_mask);
  158 +
  159 + partition++;
  160 +
  161 + } while (partition < crossValidate);
134 162
135 - BEE::writeSimmat(fused, outputSimmat); 163 + BEE::writeSimmat(buffer, outputSimmat);
136 } 164 }
openbr/core/fuse.h
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 22
23 namespace br 23 namespace br
24 { 24 {
25 - void Fuse(const QStringList &inputSimmats, File mask, const QString &normalization, const QString &fusion, const QString &outputSimmat); 25 + void Fuse(const QStringList &inputSimmats, const QString &normalization, const QString &fusion, const QString &outputSimmat);
26 } 26 }
27 27
28 #endif // BR_FUSE_H 28 #endif // BR_FUSE_H
openbr/openbr.cpp
@@ -102,10 +102,10 @@ void br_finalize() @@ -102,10 +102,10 @@ void br_finalize()
102 Context::finalize(); 102 Context::finalize();
103 } 103 }
104 104
105 -void br_fuse(int num_input_simmats, const char *input_simmats[], const char *mask, 105 +void br_fuse(int num_input_simmats, const char *input_simmats[],
106 const char *normalization, const char *fusion, const char *output_simmat) 106 const char *normalization, const char *fusion, const char *output_simmat)
107 { 107 {
108 - Fuse(QtUtils::toStringList(num_input_simmats, input_simmats), mask, normalization, fusion, output_simmat); 108 + Fuse(QtUtils::toStringList(num_input_simmats, input_simmats), normalization, fusion, output_simmat);
109 } 109 }
110 110
111 void br_initialize(int &argc, char *argv[], const char *sdk_path) 111 void br_initialize(int &argc, char *argv[], const char *sdk_path)
openbr/openbr.h
@@ -199,7 +199,7 @@ BR_EXPORT void br_finalize(); @@ -199,7 +199,7 @@ BR_EXPORT void br_finalize();
199 * - \c Replace - Replaces scores in the first matrix with scores in the second matrix when the mask is set. 199 * - \c Replace - Replaces scores in the first matrix with scores in the second matrix when the mask is set.
200 * \param output_simmat \ref simmat to contain the fused scores. 200 * \param output_simmat \ref simmat to contain the fused scores.
201 */ 201 */
202 -BR_EXPORT void br_fuse(int num_input_simmats, const char *input_simmats[], const char *mask, 202 +BR_EXPORT void br_fuse(int num_input_simmats, const char *input_simmats[],
203 const char *normalization, const char *fusion, const char *output_simmat); 203 const char *normalization, const char *fusion, const char *output_simmat);
204 204
205 /*! 205 /*!
openbr/openbr_export.cpp
@@ -29,7 +29,8 @@ @@ -29,7 +29,8 @@
29 * \section get_started Get Started 29 * \section get_started Get Started
30 * - \ref introduction - A high-level technical overview of OpenBR. 30 * - \ref introduction - A high-level technical overview of OpenBR.
31 * - \ref installation - A hacker's guide to building, editing, and running OpenBR. 31 * - \ref installation - A hacker's guide to building, editing, and running OpenBR.
32 - * - \ref qmake_integration - Add OpenBR to your Qt <tt>.pro</tt> project. 32 + * - \ref examples - Source code illustrating common use cases.
  33 + * - \ref help - Talk to the experts.
33 * 34 *
34 * \section learn_more Learn More 35 * \section learn_more Learn More
35 * - \ref algorithm_grammar - How algorithms are constructed from string descriptions. 36 * - \ref algorithm_grammar - How algorithms are constructed from string descriptions.
@@ -37,6 +38,7 @@ @@ -37,6 +38,7 @@
37 * - \ref c_sdk - High-level API for running algorithms and evaluating results. 38 * - \ref c_sdk - High-level API for running algorithms and evaluating results.
38 * - \ref cpp_plugin_sdk - Plugin API for extending OpenBR functionality. 39 * - \ref cpp_plugin_sdk - Plugin API for extending OpenBR functionality.
39 * - \ref bee - A <a href="http://www.nist.gov/index.html">NIST</a> standard for evaluating biometric algorithms. 40 * - \ref bee - A <a href="http://www.nist.gov/index.html">NIST</a> standard for evaluating biometric algorithms.
  41 + * - \ref qmake_integration - Add OpenBR to your Qt <tt>.pro</tt> project.
40 */ 42 */
41 43
42 /*! 44 /*!
@@ -397,6 +399,12 @@ $ br -help @@ -397,6 +399,12 @@ $ br -help
397 * \endcode 399 * \endcode
398 */ 400 */
399 401
  402 + /*!
  403 + * \page help Help
  404 + * - Developer mailing list: <a href="https://groups.google.com/forum/?fromgroups#!forum/openbr-dev">openbr-dev@googlegroups.com</a>
  405 + * - IRC Channel: <a href="http://webchat.freenode.net/">irc.freenode.net#openbr</a>
  406 + */
  407 +
400 /*! 408 /*!
401 * \page qmake_integration QMake Integration 409 * \page qmake_integration QMake Integration
402 * \brief Add OpenBR to your Qt <tt>.pro</tt> project. 410 * \brief Add OpenBR to your Qt <tt>.pro</tt> project.
openbr/openbr_plugin.cpp
@@ -441,10 +441,10 @@ TemplateList TemplateList::fromGallery(const br::File &amp;gallery) @@ -441,10 +441,10 @@ TemplateList TemplateList::fromGallery(const br::File &amp;gallery)
441 // of target images to every partition 441 // of target images to every partition
442 newTemplates[i].file.set("Partition", -1); 442 newTemplates[i].file.set("Partition", -1);
443 } else { 443 } else {
444 - // Direct use of "Label" is not general -cao  
445 - const QByteArray md5 = QCryptographicHash::hash(newTemplates[i].file.get<QString>("Label").toLatin1(), QCryptographicHash::Md5);  
446 - // Select the right 8 hex characters so that it can be represented as a 64 bit integer without overflow  
447 - newTemplates[i].file.set("Partition", md5.toHex().right(8).toULongLong(0, 16) % crossValidate); 444 + // Direct use of "Label" is not general -cao
  445 + const QByteArray md5 = QCryptographicHash::hash(newTemplates[i].file.get<QString>("Label").toLatin1(), QCryptographicHash::Md5);
  446 + // Select the right 8 hex characters so that it can be represented as a 64 bit integer without overflow
  447 + newTemplates[i].file.set("Partition", md5.toHex().right(8).toULongLong(0, 16) % crossValidate);
448 } 448 }
449 } 449 }
450 } 450 }
@@ -996,9 +996,9 @@ void br::Context::messageHandler(QtMsgType type, const QMessageLogContext &amp;conte @@ -996,9 +996,9 @@ void br::Context::messageHandler(QtMsgType type, const QMessageLogContext &amp;conte
996 switch (type) { 996 switch (type) {
997 case QtWarningMsg: txt = QString("Warning: %1\n" ).arg(msg); break; 997 case QtWarningMsg: txt = QString("Warning: %1\n" ).arg(msg); break;
998 case QtCriticalMsg: txt = QString("Critical: %1\n").arg(msg); break; 998 case QtCriticalMsg: txt = QString("Critical: %1\n").arg(msg); break;
999 - default: txt = QString("Fatal: %1\n" ).arg(msg); 999 + default: txt = QString("Fatal: %1\n" ).arg(msg); break;
1000 } 1000 }
1001 - txt += " File: " + QString(context.file) + "\n Function: " + QString(context.function) + "\n Line: " + QString::number(context.line) + "\n"; 1001 + txt += " SDK Path: " + Globals->sdkPath + "\n File: " + QString(context.file) + "\n Function: " + QString(context.function) + "\n Line: " + QString::number(context.line) + "\n";
1002 } 1002 }
1003 1003
1004 std::cerr << txt.toStdString(); 1004 std::cerr << txt.toStdString();
openbr/openbr_plugin.h
@@ -162,6 +162,7 @@ void reset_##NAME() { NAME = DEFAULT; } @@ -162,6 +162,7 @@ void reset_##NAME() { NAME = DEFAULT; }
162 * Rects | QList<Rect> | List of unnamed rects 162 * Rects | QList<Rect> | List of unnamed rects
163 * Age | float | Age used for demographic filtering 163 * Age | float | Age used for demographic filtering
164 * Gender | QString | Subject gender 164 * Gender | QString | Subject gender
  165 + * Train | bool | The data is for training, as opposed to enrollment
165 * _* | * | Reserved for internal use 166 * _* | * | Reserved for internal use
166 */ 167 */
167 struct BR_EXPORT File 168 struct BR_EXPORT File
openbr/plugins/crop.cpp
@@ -60,8 +60,13 @@ class ROITransform : public UntrainableTransform @@ -60,8 +60,13 @@ class ROITransform : public UntrainableTransform
60 60
61 void project(const Template &src, Template &dst) const 61 void project(const Template &src, Template &dst) const
62 { 62 {
63 - foreach (const QRectF &rect, src.file.rects())  
64 - dst += src.m()(OpenCVUtils::toRect(rect)); 63 + if (src.file.rects().empty()) {
  64 + dst = src;
  65 + qWarning("No rects present in file.");
  66 + }
  67 + else
  68 + foreach (const QRectF &rect, src.file.rects())
  69 + dst += src.m()(OpenCVUtils::toRect(rect));
65 } 70 }
66 }; 71 };
67 72
openbr/plugins/distance.cpp
@@ -310,7 +310,7 @@ class OnlineDistance : public Distance @@ -310,7 +310,7 @@ class OnlineDistance : public Distance
310 Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false) 310 Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false)
311 Q_PROPERTY(float alpha READ get_alpha WRITE set_alpha RESET reset_alpha STORED false) 311 Q_PROPERTY(float alpha READ get_alpha WRITE set_alpha RESET reset_alpha STORED false)
312 BR_PROPERTY(br::Distance*, distance, NULL) 312 BR_PROPERTY(br::Distance*, distance, NULL)
313 - BR_PROPERTY(float, alpha, 0.1f); 313 + BR_PROPERTY(float, alpha, 0.1f)
314 314
315 mutable QHash<QString,float> scoreHash; 315 mutable QHash<QString,float> scoreHash;
316 mutable QMutex mutex; 316 mutable QMutex mutex;
openbr/plugins/filter.cpp
@@ -139,10 +139,8 @@ class CSDNTransform : public UntrainableTransform @@ -139,10 +139,8 @@ class CSDNTransform : public UntrainableTransform
139 139
140 const int surround = s/2; 140 const int surround = s/2;
141 141
142 - for ( int i = 0; i < nRows; i++ )  
143 - {  
144 - for ( int j = 0; j < nCols; j++ )  
145 - { 142 + for ( int i = 0; i < nRows; i++ ) {
  143 + for ( int j = 0; j < nCols; j++ ) {
146 int width = min( j+surround, nCols ) - max( 0, j-surround ); 144 int width = min( j+surround, nCols ) - max( 0, j-surround );
147 int height = min( i+surround, nRows ) - max( 0, i-surround ); 145 int height = min( i+surround, nRows ) - max( 0, i-surround );
148 146
@@ -154,7 +152,7 @@ class CSDNTransform : public UntrainableTransform @@ -154,7 +152,7 @@ class CSDNTransform : public UntrainableTransform
154 } 152 }
155 } 153 }
156 154
157 - dst = m; 155 + dst = m;
158 156
159 } 157 }
160 }; 158 };
openbr/plugins/gallery.cpp
@@ -119,6 +119,7 @@ class galGallery : public Gallery @@ -119,6 +119,7 @@ class galGallery : public Gallery
119 { 119 {
120 if (t.isEmpty() && t.file.isNull()) 120 if (t.isEmpty() && t.file.isNull())
121 return; 121 return;
  122 +
122 stream << t; 123 stream << t;
123 } 124 }
124 }; 125 };
@@ -856,8 +857,18 @@ class FDDBGallery : public Gallery @@ -856,8 +857,18 @@ class FDDBGallery : public Gallery
856 for (int i=0; i<numDetects; i++) { 857 for (int i=0; i<numDetects; i++) {
857 const QStringList detect = lines.takeFirst().split(' '); 858 const QStringList detect = lines.takeFirst().split(' ');
858 Template t(fileName); 859 Template t(fileName);
859 - t.file.set("Face", QRectF(detect[0].toFloat(), detect[1].toFloat(), detect[2].toFloat(), detect[3].toFloat()));  
860 - t.file.set("Confidence", detect[4].toFloat()); 860 + if (detect.size() == 5) { //rectangle
  861 + t.file.set("Face", QRectF(detect[0].toFloat(), detect[1].toFloat(), detect[2].toFloat(), detect[3].toFloat()));
  862 + t.file.set("Confidence", detect[4].toFloat());
  863 + } else if (detect.size() == 6) { //ellipse
  864 + float x = detect[3].toFloat(),
  865 + y = detect[4].toFloat(),
  866 + radius = detect[1].toFloat();
  867 + t.file.set("Face", QRectF(x - radius,y - radius,radius * 2.0, radius * 2.0));
  868 + t.file.set("Confidence", detect[5].toFloat());
  869 + } else {
  870 + qFatal("Unknown FDDB annotation format.");
  871 + }
861 templates.append(t); 872 templates.append(t);
862 } 873 }
863 } 874 }
openbr/plugins/keypoint.cpp
@@ -97,6 +97,7 @@ class KeyPointDescriptorTransform : public UntrainableTransform @@ -97,6 +97,7 @@ class KeyPointDescriptorTransform : public UntrainableTransform
97 else 97 else
98 foreach (const QPointF &landmark, src.file.points()) 98 foreach (const QPointF &landmark, src.file.points())
99 keyPoints.push_back(KeyPoint(landmark.x(), landmark.y(), size)); 99 keyPoints.push_back(KeyPoint(landmark.x(), landmark.y(), size));
  100 + if (keyPoints.empty()) return;
100 descriptorExtractor->compute(src, keyPoints, dst); 101 descriptorExtractor->compute(src, keyPoints, dst);
101 } 102 }
102 }; 103 };
openbr/plugins/landmarks.cpp
@@ -269,22 +269,14 @@ class DelaunayTransform : public UntrainableTransform @@ -269,22 +269,14 @@ class DelaunayTransform : public UntrainableTransform
269 269
270 Mat output(src.m().rows,src.m().cols,src.m().type()); 270 Mat output(src.m().rows,src.m().cols,src.m().type());
271 271
272 - // Optimization needed  
273 if (i > 0) { 272 if (i > 0) {
274 Mat overlap; 273 Mat overlap;
275 bitwise_and(dst.m(),mask,overlap); 274 bitwise_and(dst.m(),mask,overlap);
276 - for (int j = 0; j < overlap.rows; j++) {  
277 - for (int k = 0; k < overlap.cols; k++) {  
278 - if (overlap.at<uchar>(j,k) != 0) {  
279 - mask.at<uchar>(j,k) = 0;  
280 - }  
281 - }  
282 - } 275 + mask.setTo(0, overlap!=0);
283 } 276 }
284 277
285 bitwise_and(buffer,mask,output); 278 bitwise_and(buffer,mask,output);
286 279
287 -  
288 dst.m() += output; 280 dst.m() += output;
289 } 281 }
290 282
@@ -297,53 +289,6 @@ class DelaunayTransform : public UntrainableTransform @@ -297,53 +289,6 @@ class DelaunayTransform : public UntrainableTransform
297 289
298 BR_REGISTER(Transform, DelaunayTransform) 290 BR_REGISTER(Transform, DelaunayTransform)
299 291
300 -/*!  
301 - * \ingroup transforms  
302 - * \brief Loads a set of fiduciary points from a .dat file  
303 - * \author Scott Klum \cite sklum  
304 - */  
305 -class LoadLandmarksTransform : public UntrainableTransform  
306 -{  
307 - Q_OBJECT  
308 -  
309 - Q_PROPERTY(QString filePath READ get_filePath WRITE set_filePath RESET reset_filePath STORED false)  
310 - BR_PROPERTY(QString, filePath, QString())  
311 -  
312 - void project(const Template &src, Template &dst) const  
313 - {  
314 - dst = src;  
315 -  
316 - // Assume the fiduciary file has the same basename as src  
317 - QString path = filePath + "/" + src.file.baseName() + ".dat";  
318 -  
319 - QFile f(path);  
320 - if (!f.open(QIODevice::ReadOnly)) qFatal("Unable to open %s for reading.", qPrintable(path));  
321 -  
322 - QList<QPointF> landmarks;  
323 - while(!f.atEnd()) {  
324 - QByteArray line = f.readLine();  
325 - QString pointSet(line);  
326 - pointSet = pointSet.simplified();  
327 - if (!pointSet.isEmpty()) {  
328 - QStringList points = pointSet.split(" ");  
329 - landmarks.append(QPointF(points[0].toFloat(),points[1].toFloat()));  
330 - }  
331 - }  
332 -  
333 - if (landmarks.size() < 35) qFatal("Unrecognized landmark set format.");  
334 -  
335 - dst.file.set("rightEye", landmarks[16]);  
336 - dst.file.set("leftEye", landmarks[18]);  
337 -  
338 - landmarks.removeAt(18);  
339 - landmarks.removeAt(16);  
340 -  
341 - dst.file.appendPoints(landmarks);  
342 - }  
343 -};  
344 -  
345 -BR_REGISTER(Transform, LoadLandmarksTransform)  
346 -  
347 } // namespace br 292 } // namespace br
348 293
349 #include "landmarks.moc" 294 #include "landmarks.moc"
openbr/plugins/misc.cpp
@@ -382,53 +382,6 @@ BR_REGISTER(Transform, RegexPropertyTransform) @@ -382,53 +382,6 @@ BR_REGISTER(Transform, RegexPropertyTransform)
382 382
383 /*! 383 /*!
384 * \ingroup transforms 384 * \ingroup transforms
385 - * \brief Remove templates with the specified file extension or metadata value.  
386 - * \author Josh Klontz \cite jklontz  
387 - */  
388 -class RemoveTemplatesTransform : public UntrainableMetaTransform  
389 -{  
390 - Q_OBJECT  
391 - Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false)  
392 - Q_PROPERTY(QString key READ get_key WRITE set_key RESET reset_key STORED false)  
393 - BR_PROPERTY(QString, regexp, "")  
394 - BR_PROPERTY(QString, key, "")  
395 -  
396 - void project(const Template &src, Template &dst) const  
397 - {  
398 - const QRegularExpression re(regexp);  
399 - const QRegularExpressionMatch match = re.match(key.isEmpty() ? src.file.suffix() : src.file.get<QString>(key));  
400 - if (match.hasMatch()) dst = Template();  
401 - else dst = src;  
402 - }  
403 -};  
404 -  
405 -BR_REGISTER(Transform, RemoveTemplatesTransform)  
406 -  
407 -/*!  
408 - * \ingroup transforms  
409 - * \brief Remove template metadata with the specified key(s).  
410 - * \author Josh Klontz \cite jklontz  
411 - */  
412 -class RemoveMetadataTransform : public UntrainableMetaTransform  
413 -{  
414 - Q_OBJECT  
415 - Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false)  
416 - BR_PROPERTY(QString, regexp, "")  
417 -  
418 - void project(const Template &src, Template &dst) const  
419 - {  
420 - dst = src;  
421 - const QRegularExpression re(regexp);  
422 - foreach (const QString &key, dst.file.localKeys())  
423 - if (re.match(key).hasMatch())  
424 - dst.file.remove(key);  
425 - }  
426 -};  
427 -  
428 -BR_REGISTER(Transform, RemoveMetadataTransform)  
429 -  
430 -/*!  
431 - * \ingroup transforms  
432 * \brief Store the last matrix of the input template as a metadata key with input property name. 385 * \brief Store the last matrix of the input template as a metadata key with input property name.
433 * \author Charles Otto \cite caotto 386 * \author Charles Otto \cite caotto
434 */ 387 */
openbr/plugins/output.cpp
@@ -249,7 +249,8 @@ BR_REGISTER(Output, mtxOutput) @@ -249,7 +249,8 @@ BR_REGISTER(Output, mtxOutput)
249 /*! 249 /*!
250 * \ingroup outputs 250 * \ingroup outputs
251 * \brief Rank retrieval output. 251 * \brief Rank retrieval output.
252 - * \author Josh Klontz \cite jklontz Scott Klum \cite sklum 252 + * \author Josh Klontz \cite jklontz
  253 + * \author Scott Klum \cite sklum
253 */ 254 */
254 class rrOutput : public MatrixOutput 255 class rrOutput : public MatrixOutput
255 { 256 {
@@ -267,15 +268,17 @@ class rrOutput : public MatrixOutput @@ -267,15 +268,17 @@ class rrOutput : public MatrixOutput
267 268
268 for (int i=0; i<queryFiles.size(); i++) { 269 for (int i=0; i<queryFiles.size(); i++) {
269 QStringList files; 270 QStringList files;
270 - if (!byLine) files.append(queryFiles[i]); 271 + files.append(queryFiles[i]);
271 272
272 typedef QPair<float,int> Pair; 273 typedef QPair<float,int> Pair;
273 foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true, limit)) { 274 foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true, limit)) {
274 - if (pair.first < threshold) break;  
275 - File target = targetFiles[pair.second];  
276 - target.set("Score", QString::number(pair.first));  
277 - if (simple) files.append(target.baseName() + " " + QString::number(pair.first));  
278 - else files.append(target.flat()); 275 + if (Globals->crossValidate > 0 ? (targetFiles[pair.second].get<int>("Partition",-1) == -1 || targetFiles[pair.second].get<int>("Partition",-1) == queryFiles[i].get<int>("Partition",-1)) : true) {
  276 + if (pair.first < threshold) break;
  277 + File target = targetFiles[pair.second];
  278 + target.set("Score", QString::number(pair.first));
  279 + if (simple) files.append(target.baseName() + " " + QString::number(pair.first));
  280 + else files.append(target.flat());
  281 + }
279 } 282 }
280 lines.append(files.join(byLine ? "\n" : ",")); 283 lines.append(files.join(byLine ? "\n" : ","));
281 } 284 }
@@ -428,7 +431,7 @@ class rankOutput : public MatrixOutput @@ -428,7 +431,7 @@ class rankOutput : public MatrixOutput
428 typedef QPair<float,int> Pair; 431 typedef QPair<float,int> Pair;
429 int rank = 1; 432 int rank = 1;
430 foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) { 433 foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) {
431 - if (Globals->crossValidate > 0 ? (targetFiles[pair.second].get<int>("Partition",-1) == queryFiles[i].get<int>("Partition",-1)) : true) { 434 + if (Globals->crossValidate > 0 ? (targetFiles[pair.second].get<int>("Partition",-1) == -1 || targetFiles[pair.second].get<int>("Partition",-1) == queryFiles[i].get<int>("Partition",-1)) : true) {
432 if (QString(targetFiles[pair.second]) != QString(queryFiles[i])) { 435 if (QString(targetFiles[pair.second]) != QString(queryFiles[i])) {
433 if (targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) { 436 if (targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) {
434 ranks.append(rank); 437 ranks.append(rank);
openbr/plugins/pp5.cpp
@@ -154,10 +154,10 @@ struct PP5Context @@ -154,10 +154,10 @@ struct PP5Context
154 154
155 ppr_face_attributes_type face_attributes; 155 ppr_face_attributes_type face_attributes;
156 ppr_get_face_attributes(face, &face_attributes); 156 ppr_get_face_attributes(face, &face_attributes);
157 - metadata.insert("Face", QRectF(face_attributes.position.x - face_attributes.dimensions.width/2,  
158 - face_attributes.position.y - face_attributes.dimensions.height/2,  
159 - face_attributes.dimensions.width,  
160 - face_attributes.dimensions.height)); 157 + metadata.insert("FrontalFace", QRectF(face_attributes.position.x - face_attributes.dimensions.width/2,
  158 + face_attributes.position.y - face_attributes.dimensions.height/2,
  159 + face_attributes.dimensions.width,
  160 + face_attributes.dimensions.height));
161 metadata.insert("PP5_Face_Confidence", face_attributes.confidence); 161 metadata.insert("PP5_Face_Confidence", face_attributes.confidence);
162 metadata.insert("PP5_Face_Roll", face_attributes.rotation.roll); 162 metadata.insert("PP5_Face_Roll", face_attributes.rotation.roll);
163 metadata.insert("PP5_Face_Pitch", face_attributes.rotation.pitch); 163 metadata.insert("PP5_Face_Pitch", face_attributes.rotation.pitch);
@@ -240,7 +240,8 @@ class PP5EnrollTransform : public UntrainableMetaTransform @@ -240,7 +240,8 @@ class PP5EnrollTransform : public UntrainableMetaTransform
240 240
241 PP5Context *context = contexts.acquire(); 241 PP5Context *context = contexts.acquire();
242 242
243 - foreach(const Template & src, srcList) { 243 + foreach (const Template &src, srcList) {
  244 + bool foundFace = false;
244 if (!src.isEmpty()) { 245 if (!src.isEmpty()) {
245 ppr_raw_image_type raw_image; 246 ppr_raw_image_type raw_image;
246 PP5Context::createRawImage(src, raw_image); 247 PP5Context::createRawImage(src, raw_image);
@@ -254,6 +255,7 @@ class PP5EnrollTransform : public UntrainableMetaTransform @@ -254,6 +255,7 @@ class PP5EnrollTransform : public UntrainableMetaTransform
254 int extractable; 255 int extractable;
255 TRY(ppr_is_template_extractable(context->context, face, &extractable)) 256 TRY(ppr_is_template_extractable(context->context, face, &extractable))
256 if (!extractable && !detectOnly) continue; 257 if (!extractable && !detectOnly) continue;
  258 + else foundFace = true;
257 259
258 cv::Mat m; 260 cv::Mat m;
259 if (detectOnly) { 261 if (detectOnly) {
@@ -272,20 +274,18 @@ class PP5EnrollTransform : public UntrainableMetaTransform @@ -272,20 +274,18 @@ class PP5EnrollTransform : public UntrainableMetaTransform
272 // Found a face, nothing else to do (if we aren't trying to find multiple faces). 274 // Found a face, nothing else to do (if we aren't trying to find multiple faces).
273 if (!Globals->enrollAll) 275 if (!Globals->enrollAll)
274 break; 276 break;
275 - } 277 + }
276 278
277 ppr_free_face_list(face_list); 279 ppr_free_face_list(face_list);
278 ppr_free_image(image); 280 ppr_free_image(image);
279 ppr_raw_image_free(raw_image); 281 ppr_raw_image_free(raw_image);
280 } 282 }
281 - }  
282 283
283 - // No faces were detected, output something with FTE set.  
284 - if (dstList.empty()) {  
285 - dstList.append(srcList.first());  
286 - dstList.first().file.set("FTE",true);  
287 - if (!detectOnly)  
288 - dstList.first().m() = cv::Mat(); 284 + // No faces were detected when we were expecting one, output something with FTE set.
  285 + if (!foundFace && !Globals->enrollAll) {
  286 + dstList.append(Template(src.file, detectOnly ? src.m() : cv::Mat()));
  287 + dstList.last().file.set("FTE", true);
  288 + }
289 } 289 }
290 290
291 contexts.release(context); 291 contexts.release(context);
openbr/plugins/slidingwindow.cpp 0 → 100644
  1 +#include "openbr_internal.h"
  2 +#include "openbr/core/opencvutils.h"
  3 +#include "openbr/core/common.h"
  4 +
  5 +using namespace cv;
  6 +
  7 +namespace br
  8 +{
  9 +
  10 +/*!
  11 + * \ingroup transforms
  12 + * \brief Applies a transform to a sliding window.
  13 + * Discards negative detections.
  14 + * \author Austin Blanton \cite imaus10
  15 + */
  16 +class SlidingWindowTransform : public Transform
  17 +{
  18 + Q_OBJECT
  19 + Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false)
  20 + Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false)
  21 + Q_PROPERTY(double scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false)
  22 + Q_PROPERTY(double stepSize READ get_stepSize WRITE set_stepSize RESET reset_stepSize STORED false)
  23 + Q_PROPERTY(bool takeLargestScale READ get_takeLargestScale WRITE set_takeLargestScale RESET reset_takeLargestScale STORED false)
  24 + Q_PROPERTY(bool negSamples READ get_negSamples WRITE set_negSamples RESET reset_negSamples STORED false)
  25 + Q_PROPERTY(int negToPosRatio READ get_negToPosRatio WRITE set_negToPosRatio RESET reset_negToPosRatio STORED false)
  26 + Q_PROPERTY(double maxOverlap READ get_maxOverlap WRITE set_maxOverlap RESET reset_maxOverlap STORED false)
  27 + BR_PROPERTY(br::Transform *, transform, NULL)
  28 + BR_PROPERTY(int, minSize, 8)
  29 + BR_PROPERTY(double, scaleFactor, 0.75)
  30 + BR_PROPERTY(double, stepSize, 1)
  31 + BR_PROPERTY(bool, takeLargestScale, true)
  32 + BR_PROPERTY(bool, negSamples, true)
  33 + BR_PROPERTY(int, negToPosRatio, 1)
  34 + BR_PROPERTY(double, maxOverlap, 0)
  35 +
  36 +public:
  37 + SlidingWindowTransform() : Transform(false, true) {}
  38 +
  39 +private:
  40 + void train(const TemplateList &data)
  41 + {
  42 + if (transform->trainable) {
  43 + TemplateList full;
  44 + foreach (const Template &tmpl, data) {
  45 + foreach (const Rect &rect, OpenCVUtils::toRects(tmpl.file.rects())) {
  46 + Template pos(tmpl.file, Mat(tmpl, rect));
  47 + full += pos;
  48 +
  49 + // add random negative samples
  50 + if (negSamples) {
  51 + Mat m = tmpl.m();
  52 + int sample = 0;
  53 + while (sample < negToPosRatio) {
  54 + int x = Common::RandSample(1, m.cols)[0];
  55 + int y = Common::RandSample(1, m.rows)[0];
  56 + int maxWidth = m.cols - x, maxHeight = m.rows - y;
  57 + int maxSize = std::min(maxWidth, maxHeight);
  58 + int size = (maxSize <= minSize ? maxSize : Common::RandSample(1, maxSize, minSize)[0]);
  59 + Rect negRect(x, y, size, size);
  60 + Rect intersect = negRect & rect;
  61 + if (intersect.area() > maxOverlap*rect.area())
  62 + continue;
  63 + Template neg(tmpl.file, Mat(tmpl, negRect));
  64 + neg.file.set("Label", QString("neg"));
  65 + full += neg;
  66 + sample++;
  67 + }
  68 + }
  69 + }
  70 + }
  71 + transform->train(full);
  72 + }
  73 + }
  74 +
  75 + void project(const Template &src, Template &dst) const
  76 + {
  77 + dst = src;
  78 + // no need to slide a window over ground truth data
  79 + if (src.file.getBool("Train", false)) return;
  80 +
  81 + dst.file.clearRects();
  82 + int rows = src.m().rows, cols = src.m().cols;
  83 + for (double size=std::min(rows, cols); size>=minSize; size*=scaleFactor) {
  84 + for (double y=0; y+size<rows; y+=(size*stepSize)) {
  85 + for (double x=0; x+size<cols; x+=(size*stepSize)) {
  86 + Rect window(x, y, size, size);
  87 + Template windowMat(src.file, Mat(src.m(), window));
  88 + Template detect;
  89 + transform->project(windowMat, detect);
  90 + // the result will be in the Label
  91 + if (detect.file.get<QString>(QString("Label")) == "pos") {
  92 + dst.file.appendRect(OpenCVUtils::fromRect(window));
  93 + if (takeLargestScale) return;
  94 + }
  95 + }
  96 + }
  97 + }
  98 + }
  99 +};
  100 +
  101 +BR_REGISTER(Transform, SlidingWindowTransform)
  102 +
  103 +} // namespace br
  104 +
  105 +#include "slidingwindow.moc"
openbr/plugins/stasm4.cpp
@@ -56,6 +56,10 @@ class StasmTransform : public UntrainableTransform @@ -56,6 +56,10 @@ class StasmTransform : public UntrainableTransform
56 56
57 Q_PROPERTY(bool stasm3Format READ get_stasm3Format WRITE set_stasm3Format RESET reset_stasm3Format STORED false) 57 Q_PROPERTY(bool stasm3Format READ get_stasm3Format WRITE set_stasm3Format RESET reset_stasm3Format STORED false)
58 BR_PROPERTY(bool, stasm3Format, false) 58 BR_PROPERTY(bool, stasm3Format, false)
  59 + Q_PROPERTY(bool clearLandmarks READ get_clearLandmarks WRITE set_clearLandmarks RESET reset_clearLandmarks STORED false)
  60 + BR_PROPERTY(bool, clearLandmarks, false)
  61 + Q_PROPERTY(QStringList pinEyes READ get_pinEyes WRITE set_pinEyes RESET reset_pinEyes STORED false)
  62 + BR_PROPERTY(QStringList, pinEyes, QStringList())
59 63
60 Resource<StasmCascadeClassifier> stasmCascadeResource; 64 Resource<StasmCascadeClassifier> stasmCascadeResource;
61 65
@@ -69,12 +73,49 @@ class StasmTransform : public UntrainableTransform @@ -69,12 +73,49 @@ class StasmTransform : public UntrainableTransform
69 { 73 {
70 if (src.m().channels() != 1) qFatal("Stasm expects single channel matrices."); 74 if (src.m().channels() != 1) qFatal("Stasm expects single channel matrices.");
71 75
  76 + dst = src;
  77 +
72 StasmCascadeClassifier *stasmCascade = stasmCascadeResource.acquire(); 78 StasmCascadeClassifier *stasmCascade = stasmCascadeResource.acquire();
73 79
74 int foundface; 80 int foundface;
75 int nLandmarks = stasm_NLANDMARKS; 81 int nLandmarks = stasm_NLANDMARKS;
76 float landmarks[2 * stasm_NLANDMARKS]; 82 float landmarks[2 * stasm_NLANDMARKS];
77 - stasm_search_single(&foundface, landmarks, reinterpret_cast<const char*>(src.m().data), src.m().cols, src.m().rows, *stasmCascade, NULL, NULL); 83 +
  84 + if (!pinEyes.isEmpty()) {
  85 + // Two use cases are accounted for:
  86 + // 1. Pin eyes without normalization: in this case the string list should contain the KEYS for right then left eyes, respectively.
  87 + // 2. Pin eyes with normalization: in this case the string list should contain the COORDINATES of the right then left eyes, respectively.
  88 + // Note that for case 2, if Affine_0 and Affine_1 are not present (indicating no normalization has taken place), we default to stasm_search_single.
  89 +
  90 + bool ok = false;
  91 + QPointF rightEye;
  92 + QPointF leftEye;
  93 +
  94 + if (src.file.contains("Affine_0") && src.file.contains("Affine_1")) {
  95 + rightEye = QtUtils::toPoint(pinEyes.at(0),&ok);
  96 + leftEye = QtUtils::toPoint(pinEyes.at(1),&ok);
  97 + }
  98 +
  99 + if (!ok) {
  100 + rightEye = QtUtils::toPoint(src.file.get<QString>(pinEyes.at(0), QString()),&ok);
  101 + leftEye = QtUtils::toPoint(src.file.get<QString>(pinEyes.at(1), QString()),&ok);
  102 + }
  103 +
  104 + float eyes[2 * stasm_NLANDMARKS];
  105 +
  106 + if (ok) {
  107 + for (int i = 0; i < nLandmarks; i++) {
  108 + if (i == 38) /*Stasm Right Eye*/ { eyes[2*i] = rightEye.x(); eyes[2*i+1] = rightEye.y(); }
  109 + else if (i == 39) /*Stasm Left Eye*/ { eyes[2*i] = leftEye.x(); eyes[2*i+1] = leftEye.y(); }
  110 + else { eyes[2*i] = 0; eyes[2*i+1] = 0; }
  111 + }
  112 + } else qFatal("Unable to interpret pinned eyes.");
  113 +
  114 + stasm_search_pinned(landmarks, eyes, reinterpret_cast<const char*>(src.m().data), src.m().cols, src.m().rows, NULL);
  115 +
  116 + // The ASM in Stasm is guaranteed to converge in this case
  117 + foundface = 1;
  118 + } stasm_search_single(&foundface, landmarks, reinterpret_cast<const char*>(src.m().data), src.m().cols, src.m().rows, *stasmCascade, NULL, NULL);
78 119
79 if (stasm3Format) { 120 if (stasm3Format) {
80 nLandmarks = 76; 121 nLandmarks = 76;
@@ -83,13 +124,22 @@ class StasmTransform : public UntrainableTransform @@ -83,13 +124,22 @@ class StasmTransform : public UntrainableTransform
83 124
84 stasmCascadeResource.release(stasmCascade); 125 stasmCascadeResource.release(stasmCascade);
85 126
86 - if (!foundface) qWarning("No face found in %s", qPrintable(src.file.fileName()));  
87 - else {  
88 - for (int i = 0; i < nLandmarks; i++)  
89 - dst.file.appendPoint(QPointF(landmarks[2 * i], landmarks[2 * i + 1])); 127 + // For convenience, if these are the only points/rects we want to deal with as the algorithm progresses
  128 + if (clearLandmarks) {
  129 + dst.file.clearPoints();
  130 + dst.file.clearRects();
90 } 131 }
91 132
92 - dst.m() = src.m(); 133 + if (!foundface) {
  134 + qWarning("No face found in %s", qPrintable(src.file.fileName()));
  135 + } else {
  136 + for (int i = 0; i < nLandmarks; i++) {
  137 + QPointF point(landmarks[2 * i], landmarks[2 * i + 1]);
  138 + dst.file.appendPoint(point);
  139 + if (i == 38) dst.file.set("StasmRightEye",point);
  140 + else if (i == 39) dst.file.set("StasmLeftEye", point);
  141 + }
  142 + }
93 } 143 }
94 }; 144 };
95 145
openbr/plugins/template.cpp
1 #include "openbr_internal.h" 1 #include "openbr_internal.h"
  2 +#include <QRegularExpression>
2 3
3 namespace br 4 namespace br
4 { 5 {
@@ -25,6 +26,30 @@ class RetainTransform : public UntrainableTransform @@ -25,6 +26,30 @@ class RetainTransform : public UntrainableTransform
25 26
26 BR_REGISTER(Transform, RetainTransform) 27 BR_REGISTER(Transform, RetainTransform)
27 28
  29 +/*!
  30 + * \ingroup transforms
  31 + * \brief Remove templates with the specified file extension or metadata value.
  32 + * \author Josh Klontz \cite jklontz
  33 + */
  34 +class RemoveTemplatesTransform : public UntrainableMetaTransform
  35 +{
  36 + Q_OBJECT
  37 + Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false)
  38 + Q_PROPERTY(QString key READ get_key WRITE set_key RESET reset_key STORED false)
  39 + BR_PROPERTY(QString, regexp, "")
  40 + BR_PROPERTY(QString, key, "")
  41 +
  42 + void project(const Template &src, Template &dst) const
  43 + {
  44 + const QRegularExpression re(regexp);
  45 + const QRegularExpressionMatch match = re.match(key.isEmpty() ? src.file.suffix() : src.file.get<QString>(key));
  46 + if (match.hasMatch()) dst = Template();
  47 + else dst = src;
  48 + }
  49 +};
  50 +
  51 +BR_REGISTER(Transform, RemoveTemplatesTransform)
  52 +
28 } // namespace br 53 } // namespace br
29 54
30 #include "template.moc" 55 #include "template.moc"
openbr/plugins/validate.cpp
@@ -75,6 +75,7 @@ class CrossValidateTransform : public MetaTransform @@ -75,6 +75,7 @@ class CrossValidateTransform : public MetaTransform
75 if (subjectIndices.size() > 1 && subjectIndices.size() <= i) { 75 if (subjectIndices.size() > 1 && subjectIndices.size() <= i) {
76 removed.append(subjectIndices[i%subjectIndices.size()]); 76 removed.append(subjectIndices[i%subjectIndices.size()]);
77 } 77 }
  78 + // For the time being, we don't support addition training data added to every fold in the case of leaveOneImageOut
78 else if (partitionsBuffer[j] == i) { 79 else if (partitionsBuffer[j] == i) {
79 removed.append(j); 80 removed.append(j);
80 } 81 }
@@ -87,7 +88,12 @@ class CrossValidateTransform : public MetaTransform @@ -87,7 +88,12 @@ class CrossValidateTransform : public MetaTransform
87 } else { 88 } else {
88 j--; 89 j--;
89 } 90 }
  91 + } else if (partitions[j] == -1) {
  92 + // Keep data for training, but modify the partition so we project into the correct space
  93 + partitionedData[j].file.set("Partition",i);
  94 + j--;
90 } else if (partitions[j] == i) { 95 } else if (partitions[j] == i) {
  96 + // Remove data, it's designated for testing
91 partitionedData.removeAt(j); 97 partitionedData.removeAt(j);
92 j--; 98 j--;
93 } else j--; 99 } else j--;
@@ -99,7 +105,8 @@ class CrossValidateTransform : public MetaTransform @@ -99,7 +105,8 @@ class CrossValidateTransform : public MetaTransform
99 } 105 }
100 106
101 void project(const Template &src, Template &dst) const 107 void project(const Template &src, Template &dst) const
102 - { 108 + {
  109 + qDebug() << src.file.get<int>("Partition", 0);
103 transforms[src.file.get<int>("Partition", 0)]->project(src, dst); 110 transforms[src.file.get<int>("Partition", 0)]->project(src, dst);
104 } 111 }
105 112
scripts/downloadDatasets.sh
@@ -35,6 +35,24 @@ if [ ! -d ../data/BioID/img ]; then @@ -35,6 +35,24 @@ if [ ! -d ../data/BioID/img ]; then
35 rm *.eye description.txt BioID-FaceDatabase-V1.2.zip 35 rm *.eye description.txt BioID-FaceDatabase-V1.2.zip
36 fi 36 fi
37 37
  38 +# INRIA person
  39 +if [ ! -d ../data/INRIAPerson/img ]; then
  40 + echo "Downloading INRIA person dataset..."
  41 + if hash curl 2>/dev/null; then
  42 + curl -OL http://pascal.inrialpes.fr/data/human/INRIAPerson.tar
  43 + else
  44 + wget http://pascal.inrialpes.fr/data/human/INRIAPerson.tar
  45 + fi
  46 + tar -xf INRIAPerson.tar
  47 + mkdir ../data/INRIAPerson/img ../data/INRIAPerson/sigset
  48 + ./writeINRIAPersonSigset.sh Train > ../data/INRIAPerson/sigset/train.xml
  49 + ./writeINRIAPersonSigset.sh Test > ../data/INRIAPerson/sigset/test.xml
  50 + ./writeINRIAPersonSigset.sh train_64x128_H96 > ../data/INRIAPerson/sigset/train_normalized.xml
  51 + ./writeINRIAPersonSigset.sh test_64x128_H96 > ../data/INRIAPerson/sigset/test_normalized.xml
  52 + mv INRIAPerson/* ../data/INRIAPerson/img
  53 + rm -r INRIAPerson*
  54 +fi
  55 +
38 # KTH 56 # KTH
39 if [ ! -d ../data/KTH/vid ]; then 57 if [ ! -d ../data/KTH/vid ]; then
40 echo "Downloading KTH..." 58 echo "Downloading KTH..."
scripts/writeINRIAPersonSigset.sh 0 → 100755
  1 +#!/bin/bash
  2 +
  3 +# prints out a <data:bbox> element from rectangle coordinates
  4 +# (from the ViPER standard: http://viper-toolkit.sourceforge.net/docs/file/)
  5 +printBBox()
  6 +{
  7 + width=$(($3-$1))
  8 + height=$(($4-$2))
  9 + echo -e "\t\t\t<data:bbox height=\"$height\" width=\"$width\" x=\"$1\" y=\"$2\" />"
  10 +}
  11 +# export printBBox so xargs can call it using bash -c below
  12 +export -f printBBox
  13 +SEDREGEX='s/.*(\([0-9]*\), \([0-9]*\)) - (\([0-9]*\), \([0-9]*\))/printBBox \1 \2 \3 \4/'
  14 +
  15 +echo '<?xml version="1.0" encoding="UTF-8"?>'
  16 +echo '<biometric-signature-set>'
  17 +
  18 +# print out the positive image sigs
  19 +for fullpath in INRIAPerson/$1/pos/*; do
  20 + # get just the filename, minus the path
  21 + filename=$(basename "$fullpath")
  22 + echo -e "\t<biometric-signature name=\"${filename%.*}\">"
  23 +
  24 + # if this folder has annotations, add bounding boxes
  25 + echo -en "\t\t<presentation Label=\"pos\" file-name=\"$1/pos/$filename\""
  26 + if [ -d INRIAPerson/$1/annotations ]; then
  27 + echo ">"
  28 + annotation="INRIAPerson/$1/annotations/${filename%.*}.txt"
  29 + grep 'Bounding box' $annotation | sed "$SEDREGEX" | xargs -n 5 bash -c 'printBBox $@'
  30 + echo -e "\t\t</presentation>"
  31 + # otherwise, just end the presentation
  32 + else
  33 + echo " />"
  34 + fi
  35 +
  36 + echo -e '\t</biometric-signature>'
  37 +done
  38 +
  39 +# print out the negative image sigs
  40 +for fullpath in INRIAPerson/$1/neg/*; do
  41 + filename=$(basename "$fullpath")
  42 + echo -e "\t<biometric-signature name=\"${filename%.*}\">"
  43 + echo -e "\t\t<presentation Label=\"neg\" file-name=\"$1/neg/$filename\" />"
  44 + echo -e '\t</biometric-signature>'
  45 +done
  46 +
  47 +echo '</biometric-signature-set>'