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 34  
35 35 ### vim ###
36 36 *.swp
  37 +
  38 +### autogenerated sigsets ###
  39 +data/INRIAPerson/sigset
... ...
3rdparty/stasm4.0.0/stasm/faceroi.cpp
... ... @@ -3,6 +3,7 @@
3 3 // Copyright (C) 2005-2013, Stephen Milborrow
4 4  
5 5 #include "stasm.h"
  6 +#include <QDebug>
6 7  
7 8 namespace stasm
8 9 {
... ...
3rdparty/stasm4.0.0/stasm/pinstart.cpp
... ... @@ -3,6 +3,7 @@
3 3 // Copyright (C) 2005-2013, Stephen Milborrow
4 4  
5 5 #include "stasm.h"
  6 +#include <QDebug>
6 7  
7 8 namespace stasm
8 9 {
... ... @@ -267,8 +268,8 @@ void PinnedStartShapeAndRoi( // use the pinned landmarks to init the start sha
267 268 const Shape& pinned) // in: manually pinned landmarks
268 269 {
269 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 273 const EYAW eyaw = DegreesAsEyaw(yaw, NSIZE(mods));
273 274 const int imod = EyawAsModIndex(eyaw, mods); // select ASM model based on yaw
274 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 244 int stasm_search_pinned( // call after the user has pinned some points
245 245 float* landmarks, // out: x0, y0, x1, y1, ..., caller must allocate
246 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 248 int width, // in: image width
249 249 int height, // in: image height
250 250 const char* imgpath) // in: image path, used only for err msgs and debug
251 251 {
252   - (void) img;
253 252 (void) width;
254 253 (void) height;
255 254 (void) imgpath;
256 255  
257 256 int returnval = 1; // assume success
258   - CatchOpenCvErrs();
259 257 try
260 258 {
261   - CV_Assert(imgpath && STRNLEN(imgpath, SLEN) < SLEN);
262 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 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 268 DetPar detpar_roi; // detpar translated to ROI frame
272 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 273 // now working with maybe flipped ROI and start shape in ROI frame
278 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 280 RoundMat(shape);
285 281 ForcePinnedPoints(shape, pinnedshape); // undo above RoundMat on pinned points
286 282 ShapeToLandmarks(landmarks, shape);
287   - if (trace_g)
288   - lprintf("\n");
289 283 }
290 284 catch(...)
291 285 {
292 286 returnval = 0; // a call was made to Err or a CV_Assert failed
293 287 }
294   - UncatchOpenCvErrs();
295 288 return returnval;
296 289 }
297 290  
... ...
app/br/br.cpp
... ... @@ -111,8 +111,8 @@ public:
111 111  
112 112 // Secondary Tasks
113 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 116 } else if (!strcmp(fun, "cluster")) {
117 117 check(parc >= 3, "Insufficient parameter count for 'cluster'.");
118 118 br_cluster(parc-2, parv, atof(parv[parc-2]), parv[parc-1]);
... ... @@ -215,7 +215,7 @@ private:
215 215 "-plot <file> ... <file> {destination}\n"
216 216 "\n"
217 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 219 "-cluster <simmat> ... <simmat> <aggressiveness> {csv}\n"
220 220 "-makeMask <target_gallery> <query_gallery> {mask}\n"
221 221 "-combineMasks <mask> ... <mask> {mask} (And|Or)\n"
... ...
app/examples/face_recognition.cpp
... ... @@ -45,7 +45,7 @@ int main(int argc, char *argv[])
45 45  
46 46 // Initialize templates
47 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 49 br::Template target("../data/MEDS/img/S354-02-t10_01.jpg");
50 50  
51 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 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 93 if (file.name.isEmpty()) qFatal("Missing file-name in %s.", qPrintable(sigset));
79 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 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 131 lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>");
117 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 317 else if (partitionA != partition) val = DontCare;
303 318 else if (partitionB == -1) val = NonMatch;
304 319 else if (partitionB != partition) val = DontCare;
305   - else if (partitionA != partitionB) val = DontCare;
306 320 else if (labelA == labelB) val = Match;
307 321 else val = NonMatch;
308 322 mask.at<Mask_t>(i,j) = val;
... ...
openbr/core/core.cpp
... ... @@ -43,6 +43,11 @@ struct AlgorithmCore
43 43 {
44 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 51 if (transform.isNull()) qFatal("Null transform.");
47 52 qDebug("%d training files", data.size());
48 53  
... ...
openbr/core/fuse.cpp
... ... @@ -16,6 +16,7 @@
16 16  
17 17 #include <QList>
18 18 #include <QStringList>
  19 +#include "openbr/core/opencvutils.h"
19 20 #include <limits>
20 21 #include <vector>
21 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 37 for (int j=0; j<matrix.cols; j++) {
37 38 float val = matrix.at<float>(i,j);
38 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 42 continue;
42 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 54 for (int j=0; j<matrix.cols; j++) {
54 55 if (mask.at<BEE::Mask_t>(i,j) == BEE::DontCare) continue;
55 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 59 else val = (val - min) / (max - min);
59 60 }
60 61 }
61 62 } else if (method == "ZScore") {
62 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 65 for (int j=0; j<matrix.cols; j++) {
65 66 if (mask.at<BEE::Mask_t>(i,j) == BEE::DontCare) continue;
66 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 73 } else {
... ... @@ -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 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 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 22  
23 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 28 #endif // BR_FUSE_H
... ...
openbr/openbr.cpp
... ... @@ -102,10 +102,10 @@ void br_finalize()
102 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 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 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 199 * - \c Replace - Replaces scores in the first matrix with scores in the second matrix when the mask is set.
200 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 203 const char *normalization, const char *fusion, const char *output_simmat);
204 204  
205 205 /*!
... ...
openbr/openbr_export.cpp
... ... @@ -29,7 +29,8 @@
29 29 * \section get_started Get Started
30 30 * - \ref introduction - A high-level technical overview of OpenBR.
31 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 35 * \section learn_more Learn More
35 36 * - \ref algorithm_grammar - How algorithms are constructed from string descriptions.
... ... @@ -37,6 +38,7 @@
37 38 * - \ref c_sdk - High-level API for running algorithms and evaluating results.
38 39 * - \ref cpp_plugin_sdk - Plugin API for extending OpenBR functionality.
39 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 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 409 * \page qmake_integration QMake Integration
402 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 441 // of target images to every partition
442 442 newTemplates[i].file.set("Partition", -1);
443 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 996 switch (type) {
997 997 case QtWarningMsg: txt = QString("Warning: %1\n" ).arg(msg); break;
998 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 1004 std::cerr << txt.toStdString();
... ...
openbr/openbr_plugin.h
... ... @@ -162,6 +162,7 @@ void reset_##NAME() { NAME = DEFAULT; }
162 162 * Rects | QList<Rect> | List of unnamed rects
163 163 * Age | float | Age used for demographic filtering
164 164 * Gender | QString | Subject gender
  165 + * Train | bool | The data is for training, as opposed to enrollment
165 166 * _* | * | Reserved for internal use
166 167 */
167 168 struct BR_EXPORT File
... ...
openbr/plugins/crop.cpp
... ... @@ -60,8 +60,13 @@ class ROITransform : public UntrainableTransform
60 60  
61 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 310 Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false)
311 311 Q_PROPERTY(float alpha READ get_alpha WRITE set_alpha RESET reset_alpha STORED false)
312 312 BR_PROPERTY(br::Distance*, distance, NULL)
313   - BR_PROPERTY(float, alpha, 0.1f);
  313 + BR_PROPERTY(float, alpha, 0.1f)
314 314  
315 315 mutable QHash<QString,float> scoreHash;
316 316 mutable QMutex mutex;
... ...
openbr/plugins/filter.cpp
... ... @@ -139,10 +139,8 @@ class CSDNTransform : public UntrainableTransform
139 139  
140 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 144 int width = min( j+surround, nCols ) - max( 0, j-surround );
147 145 int height = min( i+surround, nRows ) - max( 0, i-surround );
148 146  
... ... @@ -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 119 {
120 120 if (t.isEmpty() && t.file.isNull())
121 121 return;
  122 +
122 123 stream << t;
123 124 }
124 125 };
... ... @@ -856,8 +857,18 @@ class FDDBGallery : public Gallery
856 857 for (int i=0; i<numDetects; i++) {
857 858 const QStringList detect = lines.takeFirst().split(' ');
858 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 872 templates.append(t);
862 873 }
863 874 }
... ...
openbr/plugins/keypoint.cpp
... ... @@ -97,6 +97,7 @@ class KeyPointDescriptorTransform : public UntrainableTransform
97 97 else
98 98 foreach (const QPointF &landmark, src.file.points())
99 99 keyPoints.push_back(KeyPoint(landmark.x(), landmark.y(), size));
  100 + if (keyPoints.empty()) return;
100 101 descriptorExtractor->compute(src, keyPoints, dst);
101 102 }
102 103 };
... ...
openbr/plugins/landmarks.cpp
... ... @@ -269,22 +269,14 @@ class DelaunayTransform : public UntrainableTransform
269 269  
270 270 Mat output(src.m().rows,src.m().cols,src.m().type());
271 271  
272   - // Optimization needed
273 272 if (i > 0) {
274 273 Mat overlap;
275 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 278 bitwise_and(buffer,mask,output);
286 279  
287   -
288 280 dst.m() += output;
289 281 }
290 282  
... ... @@ -297,53 +289,6 @@ class DelaunayTransform : public UntrainableTransform
297 289  
298 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 292 } // namespace br
348 293  
349 294 #include "landmarks.moc"
... ...
openbr/plugins/misc.cpp
... ... @@ -382,53 +382,6 @@ BR_REGISTER(Transform, RegexPropertyTransform)
382 382  
383 383 /*!
384 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 385 * \brief Store the last matrix of the input template as a metadata key with input property name.
433 386 * \author Charles Otto \cite caotto
434 387 */
... ...
openbr/plugins/output.cpp
... ... @@ -249,7 +249,8 @@ BR_REGISTER(Output, mtxOutput)
249 249 /*!
250 250 * \ingroup outputs
251 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 255 class rrOutput : public MatrixOutput
255 256 {
... ... @@ -267,15 +268,17 @@ class rrOutput : public MatrixOutput
267 268  
268 269 for (int i=0; i<queryFiles.size(); i++) {
269 270 QStringList files;
270   - if (!byLine) files.append(queryFiles[i]);
  271 + files.append(queryFiles[i]);
271 272  
272 273 typedef QPair<float,int> Pair;
273 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 283 lines.append(files.join(byLine ? "\n" : ","));
281 284 }
... ... @@ -428,7 +431,7 @@ class rankOutput : public MatrixOutput
428 431 typedef QPair<float,int> Pair;
429 432 int rank = 1;
430 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 435 if (QString(targetFiles[pair.second]) != QString(queryFiles[i])) {
433 436 if (targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) {
434 437 ranks.append(rank);
... ...
openbr/plugins/pp5.cpp
... ... @@ -154,10 +154,10 @@ struct PP5Context
154 154  
155 155 ppr_face_attributes_type face_attributes;
156 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 161 metadata.insert("PP5_Face_Confidence", face_attributes.confidence);
162 162 metadata.insert("PP5_Face_Roll", face_attributes.rotation.roll);
163 163 metadata.insert("PP5_Face_Pitch", face_attributes.rotation.pitch);
... ... @@ -240,7 +240,8 @@ class PP5EnrollTransform : public UntrainableMetaTransform
240 240  
241 241 PP5Context *context = contexts.acquire();
242 242  
243   - foreach(const Template & src, srcList) {
  243 + foreach (const Template &src, srcList) {
  244 + bool foundFace = false;
244 245 if (!src.isEmpty()) {
245 246 ppr_raw_image_type raw_image;
246 247 PP5Context::createRawImage(src, raw_image);
... ... @@ -254,6 +255,7 @@ class PP5EnrollTransform : public UntrainableMetaTransform
254 255 int extractable;
255 256 TRY(ppr_is_template_extractable(context->context, face, &extractable))
256 257 if (!extractable && !detectOnly) continue;
  258 + else foundFace = true;
257 259  
258 260 cv::Mat m;
259 261 if (detectOnly) {
... ... @@ -272,20 +274,18 @@ class PP5EnrollTransform : public UntrainableMetaTransform
272 274 // Found a face, nothing else to do (if we aren't trying to find multiple faces).
273 275 if (!Globals->enrollAll)
274 276 break;
275   - }
  277 + }
276 278  
277 279 ppr_free_face_list(face_list);
278 280 ppr_free_image(image);
279 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 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 56  
57 57 Q_PROPERTY(bool stasm3Format READ get_stasm3Format WRITE set_stasm3Format RESET reset_stasm3Format STORED false)
58 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 64 Resource<StasmCascadeClassifier> stasmCascadeResource;
61 65  
... ... @@ -69,12 +73,49 @@ class StasmTransform : public UntrainableTransform
69 73 {
70 74 if (src.m().channels() != 1) qFatal("Stasm expects single channel matrices.");
71 75  
  76 + dst = src;
  77 +
72 78 StasmCascadeClassifier *stasmCascade = stasmCascadeResource.acquire();
73 79  
74 80 int foundface;
75 81 int nLandmarks = stasm_NLANDMARKS;
76 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 120 if (stasm3Format) {
80 121 nLandmarks = 76;
... ... @@ -83,13 +124,22 @@ class StasmTransform : public UntrainableTransform
83 124  
84 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 1 #include "openbr_internal.h"
  2 +#include <QRegularExpression>
2 3  
3 4 namespace br
4 5 {
... ... @@ -25,6 +26,30 @@ class RetainTransform : public UntrainableTransform
25 26  
26 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 53 } // namespace br
29 54  
30 55 #include "template.moc"
... ...
openbr/plugins/validate.cpp
... ... @@ -75,6 +75,7 @@ class CrossValidateTransform : public MetaTransform
75 75 if (subjectIndices.size() > 1 && subjectIndices.size() <= i) {
76 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 79 else if (partitionsBuffer[j] == i) {
79 80 removed.append(j);
80 81 }
... ... @@ -87,7 +88,12 @@ class CrossValidateTransform : public MetaTransform
87 88 } else {
88 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 95 } else if (partitions[j] == i) {
  96 + // Remove data, it's designated for testing
91 97 partitionedData.removeAt(j);
92 98 j--;
93 99 } else j--;
... ... @@ -99,7 +105,8 @@ class CrossValidateTransform : public MetaTransform
99 105 }
100 106  
101 107 void project(const Template &src, Template &dst) const
102   - {
  108 + {
  109 + qDebug() << src.file.get<int>("Partition", 0);
103 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 35 rm *.eye description.txt BioID-FaceDatabase-V1.2.zip
36 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 56 # KTH
39 57 if [ ! -d ../data/KTH/vid ]; then
40 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>'
... ...