Commit c743cdf51dfedf4d3ab12c269879b40f84cb80f3
Merge branch 'master' of https://github.com/biometrics/openbr
Showing
12 changed files
with
175 additions
and
45 deletions
openbr/core/bee.cpp
| ... | ... | @@ -287,8 +287,8 @@ cv::Mat BEE::makeMask(const br::FileList &targets, const br::FileList &queries, |
| 287 | 287 | |
| 288 | 288 | Mask_t val; |
| 289 | 289 | if (fileA == fileB) val = DontCare; |
| 290 | - else if (labelA == "-1") val = DontCare; | |
| 291 | - else if (labelB == "-1") val = DontCare; | |
| 290 | + else if (labelA == "-1") val = DontCare; | |
| 291 | + else if (labelB == "-1") val = DontCare; | |
| 292 | 292 | else if (partitionA != partition) val = DontCare; |
| 293 | 293 | else if (partitionB == -1) val = NonMatch; |
| 294 | 294 | else if (partitionB != partition) val = DontCare; | ... | ... |
openbr/openbr_plugin.cpp
| ... | ... | @@ -386,36 +386,66 @@ TemplateList TemplateList::fromGallery(const br::File &gallery) |
| 386 | 386 | newTemplates = newTemplates.reduced(); |
| 387 | 387 | |
| 388 | 388 | const int crossValidate = gallery.get<int>("crossValidate"); |
| 389 | - if (crossValidate > 0) srand(0); | |
| 390 | - | |
| 391 | - for (int i=newTemplates.size()-1; i>=0; i--) { | |
| 392 | - newTemplates[i].file.set("Index", i+templates.size()); | |
| 393 | - newTemplates[i].file.set("Gallery", gallery.name); | |
| 394 | - | |
| 395 | - if (crossValidate > 0) { | |
| 396 | - if (newTemplates[i].file.getBool("duplicatePartitions")) { | |
| 397 | - // The duplicatePartitions flag is used to add target images | |
| 398 | - // crossValidate times to the simmat/mask | |
| 399 | - // when multiple training sets are being used | |
| 400 | - | |
| 401 | - // Set template to the first parition | |
| 402 | - newTemplates[i].file.set("Partition", QVariant(0)); | |
| 403 | - | |
| 404 | - // Insert templates for all the other partitions | |
| 405 | - for (int j=crossValidate-1; j>=1; j--) { | |
| 406 | - Template allPartitionTemplate = newTemplates[i]; | |
| 407 | - allPartitionTemplate.file.set("Partition", j); | |
| 408 | - newTemplates.insert(i+1, allPartitionTemplate); | |
| 389 | + | |
| 390 | + if (gallery.getBool("leaveOneOut")) { | |
| 391 | + QStringList labels; | |
| 392 | + for (int i=newTemplates.size()-1; i>=0; i--) { | |
| 393 | + newTemplates[i].file.set("Index", i+templates.size()); | |
| 394 | + newTemplates[i].file.set("Gallery", gallery.name); | |
| 395 | + | |
| 396 | + QString label = newTemplates.at(i).file.get<QString>("Label"); | |
| 397 | + // Have we seen this subject before? | |
| 398 | + if (!labels.contains(label)) { | |
| 399 | + labels.append(label); | |
| 400 | + // Get indices belonging to this subject | |
| 401 | + QList<int> labelIndices = newTemplates.find("Label",label); | |
| 402 | + for (int j = 0; j < labelIndices.size(); j++) { | |
| 403 | + // Set subject partitions | |
| 404 | + newTemplates[labelIndices[j]].file.set("Partition",j%crossValidate); | |
| 405 | + } | |
| 406 | + // Extend the gallery for each partition | |
| 407 | + for (int j=0; j<labelIndices.size(); j++) { | |
| 408 | + for (int k=0; k<crossValidate; k++) { | |
| 409 | + Template leaveOneOutTemplate = newTemplates[labelIndices[j]]; | |
| 410 | + if (k!=leaveOneOutTemplate.file.get<int>("Partition")) { | |
| 411 | + leaveOneOutTemplate.file.set("Partition", k); | |
| 412 | + leaveOneOutTemplate.file.set("testOnly", true); | |
| 413 | + newTemplates.insert(i+1,leaveOneOutTemplate); | |
| 414 | + } | |
| 415 | + } | |
| 409 | 416 | } |
| 410 | - } else if (newTemplates[i].file.getBool("allPartitions")) { | |
| 411 | - // The allPartitions flag is used to add an extended set | |
| 412 | - // of target images to every partition | |
| 413 | - newTemplates[i].file.set("Partition", -1); | |
| 414 | - } else { | |
| 417 | + } | |
| 418 | + } | |
| 419 | + } else { | |
| 420 | + for (int i=newTemplates.size()-1; i>=0; i--) { | |
| 421 | + newTemplates[i].file.set("Index", i+templates.size()); | |
| 422 | + newTemplates[i].file.set("Gallery", gallery.name); | |
| 423 | + | |
| 424 | + if (crossValidate > 0) { | |
| 425 | + if (newTemplates[i].file.getBool("duplicatePartitions")) { | |
| 426 | + // The duplicatePartitions flag is used to add target images | |
| 427 | + // crossValidate times to the simmat/mask | |
| 428 | + // when multiple training sets are being used | |
| 429 | + | |
| 430 | + // Set template to the first parition | |
| 431 | + newTemplates[i].file.set("Partition", QVariant(0)); | |
| 432 | + | |
| 433 | + // Insert templates for all the other partitions | |
| 434 | + for (int j=crossValidate-1; j>0; j--) { | |
| 435 | + Template duplicatePartitionsTemplate = newTemplates[i]; | |
| 436 | + duplicatePartitionsTemplate.file.set("Partition", j); | |
| 437 | + newTemplates.insert(i+1, duplicatePartitionsTemplate); | |
| 438 | + } | |
| 439 | + } else if (newTemplates[i].file.getBool("allPartitions")) { | |
| 440 | + // The allPartitions flag is used to add an extended set | |
| 441 | + // of target images to every partition | |
| 442 | + newTemplates[i].file.set("Partition", -1); | |
| 443 | + } else { | |
| 415 | 444 | // Direct use of "Label" is not general -cao |
| 416 | 445 | const QByteArray md5 = QCryptographicHash::hash(newTemplates[i].file.get<QString>("Label").toLatin1(), QCryptographicHash::Md5); |
| 417 | 446 | // Select the right 8 hex characters so that it can be represented as a 64 bit integer without overflow |
| 418 | 447 | newTemplates[i].file.set("Partition", md5.toHex().right(8).toULongLong(0, 16) % crossValidate); |
| 448 | + } | |
| 419 | 449 | } |
| 420 | 450 | } |
| 421 | 451 | } | ... | ... |
openbr/openbr_plugin.h
| ... | ... | @@ -503,6 +503,20 @@ struct TemplateList : public QList<Template> |
| 503 | 503 | reduced.merge(t); |
| 504 | 504 | return TemplateList() << reduced; |
| 505 | 505 | } |
| 506 | + | |
| 507 | + /*! | |
| 508 | + * \brief Find the indices of templates with specified key, value pairs. | |
| 509 | + */ | |
| 510 | + template<typename T> | |
| 511 | + QList<int> find(const QString& key, const T& value) | |
| 512 | + { | |
| 513 | + QList<int> indices; | |
| 514 | + for (int i=0; i<size(); i++) | |
| 515 | + if (at(i).file.contains(key)) | |
| 516 | + if (at(i).file.get<T>(key) == value) | |
| 517 | + indices.append(i); | |
| 518 | + return indices; | |
| 519 | + } | |
| 506 | 520 | }; |
| 507 | 521 | |
| 508 | 522 | /*! | ... | ... |
openbr/plugins/format.cpp
openbr/plugins/gallery.cpp
| ... | ... | @@ -135,7 +135,7 @@ class EmptyGallery : public Gallery |
| 135 | 135 | { |
| 136 | 136 | Q_OBJECT |
| 137 | 137 | Q_PROPERTY(QString regexp READ get_regexp WRITE set_regexp RESET reset_regexp STORED false) |
| 138 | - BR_PROPERTY(QString, regexp, "") | |
| 138 | + BR_PROPERTY(QString, regexp, QString()) | |
| 139 | 139 | |
| 140 | 140 | void init() |
| 141 | 141 | { |
| ... | ... | @@ -184,7 +184,10 @@ class EmptyGallery : public Gallery |
| 184 | 184 | // Enrolling a null file is used as an idiom to initialize an algorithm |
| 185 | 185 | if (file.name.isEmpty()) return; |
| 186 | 186 | |
| 187 | - const QString destination = file.name + "/" + (file.getBool("preservePath") ? t.file.name : t.file.fileName()); | |
| 187 | + const QString newFormat = file.get<QString>("newFormat",QString()); | |
| 188 | + QString destination = file.name + "/" + (file.getBool("preservePath") ? t.file.path()+"/" : QString()); | |
| 189 | + destination += (newFormat.isEmpty() ? t.file.fileName() : t.file.baseName()+newFormat); | |
| 190 | + | |
| 188 | 191 | QMutexLocker diskLocker(&diskLock); // Windows prefers to crash when writing to disk in parallel |
| 189 | 192 | if (t.isNull()) { |
| 190 | 193 | QtUtils::copyFile(t.file.resolved(), destination); | ... | ... |
openbr/plugins/landmarks.cpp
| ... | ... | @@ -164,6 +164,7 @@ class DelaunayTransform : public UntrainableTransform |
| 164 | 164 | |
| 165 | 165 | if (points.empty() || rects.empty()) { |
| 166 | 166 | dst = src; |
| 167 | + dst.file.clearRects(); | |
| 167 | 168 | qWarning("Delauney triangulation failed because points or rects are empty."); |
| 168 | 169 | return; |
| 169 | 170 | } |
| ... | ... | @@ -292,11 +293,57 @@ class DelaunayTransform : public UntrainableTransform |
| 292 | 293 | dst.file.setRects(QList<QRectF>() << OpenCVUtils::fromRect(boundingBox)); |
| 293 | 294 | } |
| 294 | 295 | } |
| 295 | - | |
| 296 | 296 | }; |
| 297 | 297 | |
| 298 | 298 | BR_REGISTER(Transform, DelaunayTransform) |
| 299 | 299 | |
| 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 | + | |
| 300 | 347 | } // namespace br |
| 301 | 348 | |
| 302 | 349 | #include "landmarks.moc" | ... | ... |
openbr/plugins/output.cpp
| ... | ... | @@ -259,7 +259,8 @@ class rrOutput : public MatrixOutput |
| 259 | 259 | { |
| 260 | 260 | if (file.isNull() || targetFiles.isEmpty() || queryFiles.isEmpty()) return; |
| 261 | 261 | const int limit = file.get<int>("limit", 20); |
| 262 | - const bool byLine = file.get<bool>("byLine", false); | |
| 262 | + const bool byLine = file.getBool("byLine"); | |
| 263 | + const bool simple = file.getBool("simple"); | |
| 263 | 264 | const float threshold = file.get<float>("threshold", -std::numeric_limits<float>::max()); |
| 264 | 265 | |
| 265 | 266 | QStringList lines; |
| ... | ... | @@ -273,7 +274,8 @@ class rrOutput : public MatrixOutput |
| 273 | 274 | if (pair.first < threshold) break; |
| 274 | 275 | File target = targetFiles[pair.second]; |
| 275 | 276 | target.set("Score", QString::number(pair.first)); |
| 276 | - files.append(target.flat()); | |
| 277 | + if (simple) files.append(target.baseName() + " " + QString::number(pair.first)); | |
| 278 | + else files.append(target.flat()); | |
| 277 | 279 | } |
| 278 | 280 | lines.append(files.join(byLine ? "\n" : ",")); |
| 279 | 281 | } | ... | ... |
openbr/plugins/pp5.cpp
| ... | ... | @@ -299,6 +299,7 @@ BR_REGISTER(Transform, PP5EnrollTransform) |
| 299 | 299 | * \brief Compare templates with PP5 |
| 300 | 300 | * \author Josh Klontz \cite jklontz |
| 301 | 301 | * \author E. Taborsky \cite mmtaborsky |
| 302 | + * \note PP5 distance is known to be asymmetric | |
| 302 | 303 | */ |
| 303 | 304 | class PP5CompareDistance : public Distance |
| 304 | 305 | , public PP5Context | ... | ... |
openbr/plugins/stasm4.cpp
| ... | ... | @@ -36,8 +36,8 @@ class StasmInitializer : public Initializer |
| 36 | 36 | |
| 37 | 37 | void initialize() const |
| 38 | 38 | { |
| 39 | - Globals->abbreviations.insert("RectFromStasmEyes","RectFromPoints([29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],0.125,6.0)+Resize(44,164)"); | |
| 40 | - Globals->abbreviations.insert("RectFromStasmBrow","RectFromPoints([17, 18, 19, 20, 21, 22, 23, 24],0.15,6)+Resize(28,132)"); | |
| 39 | + Globals->abbreviations.insert("RectFromStasmEyes","RectFromPoints([29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47],0.125,6.0)"); | |
| 40 | + Globals->abbreviations.insert("RectFromStasmBrow","RectFromPoints([16,17,18,19,20,21,22,23,24,25,26,27],0.15,5)"); | |
| 41 | 41 | Globals->abbreviations.insert("RectFromStasmNose","RectFromPoints([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],0.15,1.25)"); |
| 42 | 42 | Globals->abbreviations.insert("RectFromStasmMouth","RectFromPoints([59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76],0.3,2.5)"); |
| 43 | 43 | } | ... | ... |
openbr/plugins/stream.cpp
| ... | ... | @@ -1261,7 +1261,7 @@ public: |
| 1261 | 1261 | { |
| 1262 | 1262 | } |
| 1263 | 1263 | |
| 1264 | - Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform STORED false) | |
| 1264 | + Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false) | |
| 1265 | 1265 | Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames) |
| 1266 | 1266 | BR_PROPERTY(br::Transform *, transform, NULL) |
| 1267 | 1267 | BR_PROPERTY(int, activeFrames, 100) | ... | ... |
openbr/plugins/validate.cpp
| 1 | 1 | #include <QFutureSynchronizer> |
| 2 | 2 | #include <QtConcurrentRun> |
| 3 | 3 | #include "openbr_internal.h" |
| 4 | +#include "openbr/core/common.h" | |
| 4 | 5 | #include <openbr/core/qtutils.h> |
| 5 | 6 | |
| 6 | 7 | namespace br |
| ... | ... | @@ -10,6 +11,7 @@ namespace br |
| 10 | 11 | * \ingroup transforms |
| 11 | 12 | * \brief Cross validate a trainable transform. |
| 12 | 13 | * \author Josh Klontz \cite jklontz |
| 14 | + * \author Scott Klum \cite sklum | |
| 13 | 15 | * \note To use an extended gallery, add an allPartitions="true" flag to the gallery sigset for those images that should be compared |
| 14 | 16 | * against for all testing partitions. |
| 15 | 17 | */ |
| ... | ... | @@ -17,7 +19,9 @@ class CrossValidateTransform : public MetaTransform |
| 17 | 19 | { |
| 18 | 20 | Q_OBJECT |
| 19 | 21 | Q_PROPERTY(QString description READ get_description WRITE set_description RESET reset_description STORED false) |
| 22 | + Q_PROPERTY(bool leaveOneOut READ get_leaveOneOut WRITE set_leaveOneOut RESET reset_leaveOneOut STORED false) | |
| 20 | 23 | BR_PROPERTY(QString, description, "Identity") |
| 24 | + BR_PROPERTY(bool, leaveOneOut, false) | |
| 21 | 25 | |
| 22 | 26 | QList<br::Transform*> transforms; |
| 23 | 27 | |
| ... | ... | @@ -40,11 +44,44 @@ class CrossValidateTransform : public MetaTransform |
| 40 | 44 | |
| 41 | 45 | QFutureSynchronizer<void> futures; |
| 42 | 46 | for (int i=0; i<numPartitions; i++) { |
| 47 | + QList<int> partitionsBuffer = partitions; | |
| 43 | 48 | TemplateList partitionedData = data; |
| 44 | - for (int j=partitionedData.size()-1; j>=0; j--) | |
| 45 | - // Remove all templates from partition i | |
| 46 | - if (partitions[j] == i) | |
| 49 | + int j = partitionedData.size()-1; | |
| 50 | + while (j>=0) { | |
| 51 | + // Remove all templates belonging to partition i | |
| 52 | + // if leaveOneOut is true, | |
| 53 | + // and i is greater than the number of images for a particular subject | |
| 54 | + // even if the partitions are different | |
| 55 | + if (leaveOneOut) { | |
| 56 | + const QString label = partitionedData.at(j).file.get<QString>("Label"); | |
| 57 | + QList<int> subjectIndices = partitionedData.find("Label",label); | |
| 58 | + QList<int> removed; | |
| 59 | + // Remove test only data | |
| 60 | + for (int k=subjectIndices.size()-1; k>=0; k--) | |
| 61 | + if (partitionedData[subjectIndices[k]].file.getBool("testOnly")) { | |
| 62 | + removed.append(subjectIndices[k]); | |
| 63 | + subjectIndices.removeAt(k); | |
| 64 | + } | |
| 65 | + // Remove template that was repeated to make the testOnly template | |
| 66 | + if (subjectIndices.size() > 1 && subjectIndices.size() <= i) { | |
| 67 | + removed.append(subjectIndices[i%subjectIndices.size()]); | |
| 68 | + } | |
| 69 | + else if (partitionsBuffer[j] == i) { | |
| 70 | + removed.append(j); | |
| 71 | + } | |
| 72 | + | |
| 73 | + if (!removed.empty()) { | |
| 74 | + typedef QPair<int,int> Pair; | |
| 75 | + foreach (Pair pair, Common::Sort(removed,true)) { | |
| 76 | + partitionedData.removeAt(pair.first); partitionsBuffer.removeAt(pair.first); j--; | |
| 77 | + } | |
| 78 | + } else { | |
| 79 | + j--; | |
| 80 | + } | |
| 81 | + } else if (partitions[j] == i) { | |
| 47 | 82 | partitionedData.removeAt(j); |
| 83 | + } else j--; | |
| 84 | + } | |
| 48 | 85 | // Train on the remaining templates |
| 49 | 86 | futures.addFuture(QtConcurrent::run(transforms[i], &Transform::train, partitionedData)); |
| 50 | 87 | } |
| ... | ... | @@ -53,12 +90,7 @@ class CrossValidateTransform : public MetaTransform |
| 53 | 90 | |
| 54 | 91 | void project(const Template &src, Template &dst) const |
| 55 | 92 | { |
| 56 | - // If the src partition is greater than the number of training partitions, | |
| 57 | - // assume that projection should be done using the same training data for all partitions. | |
| 58 | - int partition = src.file.get<int>("Partition", 0); | |
| 59 | - if (partition >= transforms.size()) partition = 0; | |
| 60 | - | |
| 61 | - transforms[partition]->project(src, dst); | |
| 93 | + transforms[src.file.get<int>("Partition", 0)]->project(src, dst); | |
| 62 | 94 | } |
| 63 | 95 | |
| 64 | 96 | void store(QDataStream &stream) const | ... | ... |