Commit cfa577b513e8d43b8b73473fdaa7f5be09d6e439
Removed debug statement in core
Showing
54 changed files
with
1120 additions
and
516 deletions
.gitignore
CHANGELOG.md
README.md
| 1 | -www.openbiometrics.org | |
| 1 | +**www.openbiometrics.org** | |
| 2 | 2 | |
| 3 | 3 | $ git clone https://github.com/biometrics/openbr.git |
| 4 | 4 | $ cd openbr |
| 5 | 5 | $ git submodule init |
| 6 | 6 | $ git submodule update |
| 7 | + | |
| 8 | +To optionally check out a particular [tagged release](https://github.com/biometrics/openbr/releases): | |
| 7 | 9 | |
| 8 | -[Build Instructions](http://openbiometrics.org/doxygen/latest/installation.html) | |
| 10 | + $ git checkout <tag> | |
| 11 | + $ git submodule update | |
| 12 | + | |
| 13 | +**[Build Instructions](http://openbiometrics.org/doxygen/latest/installation.html)** | ... | ... |
app/br/br.cpp
| ... | ... | @@ -129,17 +129,20 @@ public: |
| 129 | 129 | check(parc == 3, "Incorrect parameter count for 'convert'."); |
| 130 | 130 | br_convert(parv[0], parv[1], parv[2]); |
| 131 | 131 | } else if (!strcmp(fun, "evalClassification")) { |
| 132 | - check(parc == 2, "Incorrect parameter count for 'evalClassification'."); | |
| 133 | - br_eval_classification(parv[0], parv[1]); | |
| 132 | + check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalClassification'."); | |
| 133 | + br_eval_classification(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); | |
| 134 | 134 | } else if (!strcmp(fun, "evalClustering")) { |
| 135 | 135 | check(parc == 2, "Incorrect parameter count for 'evalClustering'."); |
| 136 | 136 | br_eval_clustering(parv[0], parv[1]); |
| 137 | 137 | } else if (!strcmp(fun, "evalDetection")) { |
| 138 | 138 | check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalDetection'."); |
| 139 | 139 | br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : ""); |
| 140 | + } else if (!strcmp(fun, "evalLandmarking")) { | |
| 141 | + check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalLandmarking'."); | |
| 142 | + br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : ""); | |
| 140 | 143 | } else if (!strcmp(fun, "evalRegression")) { |
| 141 | - check(parc == 2, "Incorrect parameter count for 'evalRegression'."); | |
| 142 | - br_eval_regression(parv[0], parv[1]); | |
| 144 | + check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'."); | |
| 145 | + br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); | |
| 143 | 146 | } else if (!strcmp(fun, "plotDetection")) { |
| 144 | 147 | check(parc >= 2, "Incorrect parameter count for 'plotDetection'."); |
| 145 | 148 | br_plot_detection(parc-1, parv, parv[parc-1], true); |
| ... | ... | @@ -215,10 +218,11 @@ private: |
| 215 | 218 | "-combineMasks <mask> ... <mask> {mask} (And|Or)\n" |
| 216 | 219 | "-cat <gallery> ... <gallery> {gallery}\n" |
| 217 | 220 | "-convert (Format|Gallery|Output) <input_file> {output_file}\n" |
| 218 | - "-evalClassification <predicted_gallery> <truth_gallery>\n" | |
| 221 | + "-evalClassification <predicted_gallery> <truth_gallery> <predicted property name> <ground truth proprty name>\n" | |
| 219 | 222 | "-evalClustering <clusters> <gallery>\n" |
| 220 | 223 | "-evalDetection <predicted_gallery> <truth_gallery> [{csv}]\n" |
| 221 | - "-evalRegression <predicted_gallery> <truth_gallery>\n" | |
| 224 | + "-evalLandmarking <predicted_gallery> <truth_gallery> [{csv}]\n" | |
| 225 | + "-evalRegression <predicted_gallery> <truth_gallery> <predicted property name> <ground truth property name>\n" | |
| 222 | 226 | "-plotDetection <file> ... <file> {destination}\n" |
| 223 | 227 | "-plotMetadata <file> ... <file> <columns>\n" |
| 224 | 228 | "-getHeader <matrix>\n" | ... | ... |
app/examples/age_estimation.cpp
| ... | ... | @@ -29,7 +29,7 @@ |
| 29 | 29 | |
| 30 | 30 | static void printTemplate(const br::Template &t) |
| 31 | 31 | { |
| 32 | - printf("%s age: %d\n", qPrintable(t.file.fileName()), int(t.file.get<float>("Subject"))); | |
| 32 | + printf("%s age: %d\n", qPrintable(t.file.fileName()), int(t.file.get<float>("Age"))); | |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | 35 | int main(int argc, char *argv[]) | ... | ... |
app/examples/face_recognition_evaluation.cpp
| ... | ... | @@ -25,8 +25,7 @@ |
| 25 | 25 | * -compare target.gal query.gal scores.mtx \ |
| 26 | 26 | * -makeMask ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml MEDS.mask \ |
| 27 | 27 | * -eval scores.mtx MEDS.mask Algorithm_Dataset/FaceRecognition_MEDS.csv \ |
| 28 | - * -eval ../data/MEDS/simmat/COTS_MEDS.mtx MEDS.mask Algorithm_Dataset/COTS_MEDS.csv \ | |
| 29 | - * -plot Algorithm_Dataset/FaceRecognition_MEDS.csv Algorithm_Dataset/COTS_MEDS.csv MEDS | |
| 28 | + * -plot Algorithm_Dataset/FaceRecognition_MEDS.csv MEDS | |
| 30 | 29 | * \endcode |
| 31 | 30 | */ |
| 32 | 31 | ... | ... |
app/examples/gender_estimation.cpp
| ... | ... | @@ -29,7 +29,7 @@ |
| 29 | 29 | |
| 30 | 30 | static void printTemplate(const br::Template &t) |
| 31 | 31 | { |
| 32 | - printf("%s gender: %s\n", qPrintable(t.file.fileName()), qPrintable(t.file.get<QString>("Subject"))); | |
| 32 | + printf("%s gender: %s\n", qPrintable(t.file.fileName()), qPrintable(t.file.get<QString>("Gender"))); | |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | 35 | int main(int argc, char *argv[]) | ... | ... |
openbr/core/bee.cpp
| ... | ... | @@ -99,10 +99,10 @@ void BEE::writeSigset(const QString &sigset, const br::FileList &files, bool ign |
| 99 | 99 | QStringList metadata; |
| 100 | 100 | if (!ignoreMetadata) |
| 101 | 101 | foreach (const QString &key, file.localKeys()) { |
| 102 | - if ((key == "Index") || (key == "Subject")) continue; | |
| 102 | + if ((key == "Index") || (key == "Label")) continue; | |
| 103 | 103 | metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\""); |
| 104 | 104 | } |
| 105 | - lines.append("\t<biometric-signature name=\"" + file.get<QString>("Subject",file.fileName()) +"\">"); | |
| 105 | + lines.append("\t<biometric-signature name=\"" + file.get<QString>("Label",file.fileName()) +"\">"); | |
| 106 | 106 | lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>"); |
| 107 | 107 | lines.append("\t</biometric-signature>"); |
| 108 | 108 | } |
| ... | ... | @@ -266,10 +266,11 @@ void BEE::makeMask(const QString &targetInput, const QString &queryInput, const |
| 266 | 266 | |
| 267 | 267 | cv::Mat BEE::makeMask(const br::FileList &targets, const br::FileList &queries, int partition) |
| 268 | 268 | { |
| 269 | - // Would like to use indexProperty for this, but didn't make a version of that for Filelist yet | |
| 270 | - // -cao | |
| 271 | - QList<QString> targetLabels = File::get<QString>(targets, "Subject", "-1"); | |
| 272 | - QList<QString> queryLabels = File::get<QString>(queries, "Subject", "-1"); | |
| 269 | + // Direct use of "Label" isn't general, also would prefer to use indexProperty, rather than | |
| 270 | + // doing string comparisons (but that isn't implemented yet for FileList) -cao | |
| 271 | + QList<QString> targetLabels = File::get<QString>(targets, "Label", "-1"); | |
| 272 | + QList<QString> queryLabels = File::get<QString>(queries, "Label", "-1"); | |
| 273 | + | |
| 273 | 274 | QList<int> targetPartitions = targets.crossValidationPartitions(); |
| 274 | 275 | QList<int> queryPartitions = queries.crossValidationPartitions(); |
| 275 | 276 | ... | ... |
openbr/core/cluster.cpp
| ... | ... | @@ -279,8 +279,8 @@ void br::EvalClustering(const QString &csv, const QString &input) |
| 279 | 279 | qDebug("Evaluating %s against %s", qPrintable(csv), qPrintable(input)); |
| 280 | 280 | |
| 281 | 281 | // We assume clustering algorithms store assigned cluster labels as integers (since the clusters are |
| 282 | - // not named). | |
| 283 | - QList<int> labels = File::get<int>(TemplateList::fromGallery(input), "Subject"); | |
| 282 | + // not named). Direct use of ClusterID is not general -cao | |
| 283 | + QList<int> labels = File::get<int>(TemplateList::fromGallery(input), "ClusterID"); | |
| 284 | 284 | |
| 285 | 285 | QHash<int, int> labelToIndex; |
| 286 | 286 | int nClusters = 0; | ... | ... |
openbr/core/common.cpp
| ... | ... | @@ -15,11 +15,15 @@ |
| 15 | 15 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
| 16 | 16 | |
| 17 | 17 | #include "common.h" |
| 18 | +#include <QMutex> | |
| 18 | 19 | |
| 19 | 20 | using namespace std; |
| 20 | 21 | |
| 21 | 22 | /**** GLOBAL ****/ |
| 22 | 23 | void Common::seedRNG() { |
| 24 | + static QMutex seedControl; | |
| 25 | + QMutexLocker lock(&seedControl); | |
| 26 | + | |
| 23 | 27 | static bool seeded = false; |
| 24 | 28 | if (!seeded) { |
| 25 | 29 | srand(0); // We seed with 0 instead of time(NULL) to have reproducible randomness |
| ... | ... | @@ -29,8 +33,6 @@ void Common::seedRNG() { |
| 29 | 33 | |
| 30 | 34 | QList<int> Common::RandSample(int n, int max, int min, bool unique) |
| 31 | 35 | { |
| 32 | - seedRNG(); | |
| 33 | - | |
| 34 | 36 | QList<int> samples; samples.reserve(n); |
| 35 | 37 | int range = max-min; |
| 36 | 38 | if (range <= 0) qFatal("Non-positive range."); |
| ... | ... | @@ -50,8 +52,6 @@ QList<int> Common::RandSample(int n, int max, int min, bool unique) |
| 50 | 52 | |
| 51 | 53 | QList<int> Common::RandSample(int n, const QSet<int> &values, bool unique) |
| 52 | 54 | { |
| 53 | - seedRNG(); | |
| 54 | - | |
| 55 | 55 | QList<int> valueList = values.toList(); |
| 56 | 56 | if (unique && (values.size() <= n)) return valueList; |
| 57 | 57 | ... | ... |
openbr/core/core.cpp
| ... | ... | @@ -147,13 +147,7 @@ struct AlgorithmCore |
| 147 | 147 | data.removeAt(i); |
| 148 | 148 | const int numFiles = data.size(); |
| 149 | 149 | |
| 150 | - if (Globals->backProject) { | |
| 151 | - TemplateList backProjectedData; | |
| 152 | - transform->backProject(data, backProjectedData); | |
| 153 | - data = backProjectedData; | |
| 154 | - } else { | |
| 155 | - data >> *transform; | |
| 156 | - } | |
| 150 | + data >> *transform; | |
| 157 | 151 | |
| 158 | 152 | g->writeBlock(data); |
| 159 | 153 | const FileList newFiles = data.files(); |
| ... | ... | @@ -394,7 +388,6 @@ void br::Convert(const File &fileType, const File &inputFile, const File &output |
| 394 | 388 | QSharedPointer<Output> o(Factory<Output>::make(outputFile)); |
| 395 | 389 | o->initialize(targetFiles, queryFiles); |
| 396 | 390 | |
| 397 | - qDebug() << m.rows << m.cols << targetFiles.size() << queryFiles.size(); | |
| 398 | 391 | for (int i=0; i<queryFiles.size(); i++) |
| 399 | 392 | for (int j=0; j<targetFiles.size(); j++) |
| 400 | 393 | o->setRelative(m.at<float>(i,j), i, j); | ... | ... |
openbr/core/eval.cpp
| ... | ... | @@ -255,9 +255,20 @@ struct Counter |
| 255 | 255 | } |
| 256 | 256 | }; |
| 257 | 257 | |
| 258 | -void EvalClassification(const QString &predictedInput, const QString &truthInput) | |
| 258 | +void EvalClassification(const QString &predictedInput, const QString &truthInput, QString predictedProperty, QString truthProperty) | |
| 259 | 259 | { |
| 260 | 260 | qDebug("Evaluating classification of %s against %s", qPrintable(predictedInput), qPrintable(truthInput)); |
| 261 | + | |
| 262 | + if (predictedProperty.isEmpty()) | |
| 263 | + predictedProperty = "Label"; | |
| 264 | + // If predictedProperty is specified, but truthProperty isn't, copy over the value from | |
| 265 | + // predicted property | |
| 266 | + else if (truthProperty.isEmpty()) | |
| 267 | + truthProperty = predictedProperty; | |
| 268 | + | |
| 269 | + if (truthProperty.isEmpty()) | |
| 270 | + truthProperty = "Label"; | |
| 271 | + | |
| 261 | 272 | TemplateList predicted(TemplateList::fromGallery(predictedInput)); |
| 262 | 273 | TemplateList truth(TemplateList::fromGallery(truthInput)); |
| 263 | 274 | if (predicted.size() != truth.size()) qFatal("Input size mismatch."); |
| ... | ... | @@ -267,9 +278,8 @@ void EvalClassification(const QString &predictedInput, const QString &truthInput |
| 267 | 278 | if (predicted[i].file.name != truth[i].file.name) |
| 268 | 279 | qFatal("Input order mismatch."); |
| 269 | 280 | |
| 270 | - // Typically these lists will be of length one, but this generalization allows measuring multi-class labeling accuracy. | |
| 271 | - QString predictedSubject = predicted[i].file.get<QString>("Subject"); | |
| 272 | - QString trueSubject = truth[i].file.get<QString>("Subject"); | |
| 281 | + QString predictedSubject = predicted[i].file.get<QString>(predictedProperty); | |
| 282 | + QString trueSubject = truth[i].file.get<QString>(truthProperty); | |
| 273 | 283 | |
| 274 | 284 | QStringList predictedSubjects(predictedSubject); |
| 275 | 285 | QStringList trueSubjects(trueSubject); |
| ... | ... | @@ -373,6 +383,8 @@ static QStringList computeDetectionResults(const QList<ResolvedDetection> &detec |
| 373 | 383 | } |
| 374 | 384 | |
| 375 | 385 | const int keep = qMin(points.size(), Max_Points); |
| 386 | + if (keep < 2) qFatal("Insufficient points."); | |
| 387 | + | |
| 376 | 388 | QStringList lines; lines.reserve(keep); |
| 377 | 389 | for (int i=0; i<keep; i++) { |
| 378 | 390 | const DetectionOperatingPoint &point = points[double(i) / double(keep-1) * double(points.size()-1)]; |
| ... | ... | @@ -382,6 +394,15 @@ static QStringList computeDetectionResults(const QList<ResolvedDetection> &detec |
| 382 | 394 | return lines; |
| 383 | 395 | } |
| 384 | 396 | |
| 397 | +QString getDetectKey(const TemplateList &templates) | |
| 398 | +{ | |
| 399 | + const File &f = templates.first().file; | |
| 400 | + foreach (const QString &key, f.localKeys()) | |
| 401 | + if (!f.get<QRectF>(key, QRectF()).isNull()) | |
| 402 | + return key; | |
| 403 | + return ""; | |
| 404 | +} | |
| 405 | + | |
| 385 | 406 | float EvalDetection(const QString &predictedInput, const QString &truthInput, const QString &csv) |
| 386 | 407 | { |
| 387 | 408 | qDebug("Evaluating detection of %s against %s", qPrintable(predictedInput), qPrintable(truthInput)); |
| ... | ... | @@ -389,20 +410,22 @@ float EvalDetection(const QString &predictedInput, const QString &truthInput, co |
| 389 | 410 | const TemplateList truth(TemplateList::fromGallery(truthInput)); |
| 390 | 411 | |
| 391 | 412 | // Figure out which metadata field contains a bounding box |
| 392 | - QString detectKey; | |
| 393 | - foreach (const QString &key, truth.first().file.localKeys()) | |
| 394 | - if (!truth.first().file.get<QRectF>(key, QRectF()).isNull()) { | |
| 395 | - detectKey = key; | |
| 396 | - break; | |
| 397 | - } | |
| 398 | - if (detectKey.isNull()) qFatal("No suitable metadata key found."); | |
| 399 | - else qDebug("Using metadata key: %s", qPrintable(detectKey)); | |
| 413 | + QString truthDetectKey = getDetectKey(truth); | |
| 414 | + if (truthDetectKey.isEmpty()) qFatal("No suitable ground truth metadata key found."); | |
| 415 | + QString predictedDetectKey = truthDetectKey; | |
| 416 | + if (predicted.first().file.get<QRectF>(predictedDetectKey, QRectF()).isNull()) | |
| 417 | + predictedDetectKey = getDetectKey(predicted); | |
| 418 | + if (predictedDetectKey.isEmpty()) qFatal("No suitable predicted metadata key found."); | |
| 419 | + | |
| 420 | + qDebug("Using metadata key: %s%s", | |
| 421 | + qPrintable(predictedDetectKey), | |
| 422 | + qPrintable(predictedDetectKey == truthDetectKey ? QString() : "/"+truthDetectKey)); | |
| 400 | 423 | |
| 401 | 424 | QMap<QString, Detections> allDetections; // Organized by file, QMap used to preserve order |
| 402 | 425 | foreach (const Template &t, predicted) |
| 403 | - allDetections[t.file.baseName()].predicted.append(Detection(t.file.get<QRectF>(detectKey), t.file.get<float>("Confidence", -1))); | |
| 426 | + allDetections[t.file.baseName()].predicted.append(Detection(t.file.get<QRectF>(predictedDetectKey), t.file.get<float>("Confidence", -1))); | |
| 404 | 427 | foreach (const Template &t, truth) |
| 405 | - allDetections[t.file.baseName()].truth.append(Detection(t.file.get<QRectF>(detectKey))); | |
| 428 | + allDetections[t.file.baseName()].truth.append(Detection(t.file.get<QRectF>(truthDetectKey))); | |
| 406 | 429 | |
| 407 | 430 | QList<ResolvedDetection> resolvedDetections, falseNegativeDetections; |
| 408 | 431 | foreach (Detections detections, allDetections.values()) { |
| ... | ... | @@ -453,21 +476,44 @@ float EvalDetection(const QString &predictedInput, const QString &truthInput, co |
| 453 | 476 | return averageOverlap; |
| 454 | 477 | } |
| 455 | 478 | |
| 456 | -void EvalRegression(const QString &predictedInput, const QString &truthInput) | |
| 479 | +void EvalLandmarking(const QString &predictedInput, const QString &truthInput, const QString &csv) | |
| 480 | +{ | |
| 481 | + (void) predictedInput; | |
| 482 | + (void) truthInput; | |
| 483 | + (void) csv; | |
| 484 | +} | |
| 485 | + | |
| 486 | +void EvalRegression(const QString &predictedInput, const QString &truthInput, QString predictedProperty, QString truthProperty) | |
| 457 | 487 | { |
| 458 | 488 | qDebug("Evaluating regression of %s against %s", qPrintable(predictedInput), qPrintable(truthInput)); |
| 489 | + | |
| 490 | + if (predictedProperty.isEmpty()) | |
| 491 | + predictedProperty = "Regressor"; | |
| 492 | + // If predictedProperty is specified, but truthProperty isn't, copy the value over | |
| 493 | + // rather than using the default for truthProperty | |
| 494 | + else if (truthProperty.isEmpty()) | |
| 495 | + truthProperty = predictedProperty; | |
| 496 | + | |
| 497 | + if (truthProperty.isEmpty()) | |
| 498 | + predictedProperty = "Regressand"; | |
| 499 | + | |
| 459 | 500 | const TemplateList predicted(TemplateList::fromGallery(predictedInput)); |
| 460 | 501 | const TemplateList truth(TemplateList::fromGallery(truthInput)); |
| 461 | 502 | if (predicted.size() != truth.size()) qFatal("Input size mismatch."); |
| 462 | 503 | |
| 463 | 504 | float rmsError = 0; |
| 505 | + float maeError = 0; | |
| 464 | 506 | QStringList truthValues, predictedValues; |
| 465 | 507 | for (int i=0; i<predicted.size(); i++) { |
| 466 | 508 | if (predicted[i].file.name != truth[i].file.name) |
| 467 | 509 | qFatal("Input order mismatch."); |
| 468 | - rmsError += pow(predicted[i].file.get<float>("Subject")-truth[i].file.get<float>("Subject"), 2.f); | |
| 469 | - truthValues.append(QString::number(truth[i].file.get<float>("Subject"))); | |
| 470 | - predictedValues.append(QString::number(predicted[i].file.get<float>("Subject"))); | |
| 510 | + | |
| 511 | + float difference = predicted[i].file.get<float>(predictedProperty) - truth[i].file.get<float>(truthProperty); | |
| 512 | + | |
| 513 | + rmsError += pow(difference, 2.f); | |
| 514 | + maeError += fabsf(difference); | |
| 515 | + truthValues.append(QString::number(truth[i].file.get<float>(truthProperty))); | |
| 516 | + predictedValues.append(QString::number(predicted[i].file.get<float>(predictedProperty))); | |
| 471 | 517 | } |
| 472 | 518 | |
| 473 | 519 | QStringList rSource; |
| ... | ... | @@ -487,6 +533,7 @@ void EvalRegression(const QString &predictedInput, const QString &truthInput) |
| 487 | 533 | if (success) QtUtils::showFile("EvalRegression.pdf"); |
| 488 | 534 | |
| 489 | 535 | qDebug("RMS Error = %f", sqrt(rmsError/predicted.size())); |
| 536 | + qDebug("MAE = %f", maeError/predicted.size()); | |
| 490 | 537 | } |
| 491 | 538 | |
| 492 | 539 | } // namespace br | ... | ... |
openbr/core/eval.h
| ... | ... | @@ -26,9 +26,10 @@ namespace br |
| 26 | 26 | float Evaluate(const QString &simmat, const QString &mask = "", const QString &csv = ""); // Returns TAR @ FAR = 0.001 |
| 27 | 27 | float Evaluate(const cv::Mat &scores, const FileList &target, const FileList &query, const QString &csv = "", int parition = 0); |
| 28 | 28 | float Evaluate(const cv::Mat &scores, const cv::Mat &masks, const QString &csv = ""); |
| 29 | - void EvalClassification(const QString &predictedInput, const QString &truthInput); | |
| 29 | + void EvalClassification(const QString &predictedInput, const QString &truthInput, QString predictedProperty="", QString truthProperty=""); | |
| 30 | 30 | float EvalDetection(const QString &predictedInput, const QString &truthInput, const QString &csv = ""); // Return average overlap |
| 31 | - void EvalRegression(const QString &predictedInput, const QString &truthInput); | |
| 31 | + void EvalLandmarking(const QString &predictedInput, const QString &truthInput, const QString &csv = ""); | |
| 32 | + void EvalRegression(const QString &predictedInput, const QString &truthInput, QString predictedProperty="", QString truthProperty=""); | |
| 32 | 33 | } |
| 33 | 34 | |
| 34 | 35 | #endif // __EVAL_H | ... | ... |
openbr/core/qtutils.cpp
| ... | ... | @@ -334,8 +334,8 @@ QRectF QtUtils::toRect(const QString &string, bool *ok) |
| 334 | 334 | bool okX, okY, okWidth, okHeight; |
| 335 | 335 | x = words[0].toFloat(&okX); |
| 336 | 336 | y = words[1].toFloat(&okY); |
| 337 | - width = words[0].toFloat(&okWidth); | |
| 338 | - height = words[1].toFloat(&okHeight); | |
| 337 | + width = words[2].toFloat(&okWidth); | |
| 338 | + height = words[3].toFloat(&okHeight); | |
| 339 | 339 | if (okX && okY && okWidth && okHeight) { |
| 340 | 340 | if (ok) *ok = true; |
| 341 | 341 | return QRectF(x, y, width, height); | ... | ... |
openbr/core/resource.h
| ... | ... | @@ -24,6 +24,7 @@ |
| 24 | 24 | #include <QSharedPointer> |
| 25 | 25 | #include <QString> |
| 26 | 26 | #include <QThread> |
| 27 | +#include <openbr/openbr_plugin.h> | |
| 27 | 28 | |
| 28 | 29 | template <typename T> |
| 29 | 30 | class ResourceMaker |
| ... | ... | @@ -52,7 +53,7 @@ public: |
| 52 | 53 | : resourceMaker(rm) |
| 53 | 54 | , availableResources(new QList<T*>()) |
| 54 | 55 | , lock(new QMutex()) |
| 55 | - , totalResources(new QSemaphore(QThread::idealThreadCount())) | |
| 56 | + , totalResources(new QSemaphore(br::Globals->parallelism)) | |
| 56 | 57 | {} |
| 57 | 58 | |
| 58 | 59 | ~Resource() | ... | ... |
openbr/frvt2012.cpp
| ... | ... | @@ -132,7 +132,7 @@ int32_t SdkEstimator::estimate_age(const ONEFACE &input_face, int32_t &age) |
| 132 | 132 | TemplateList templates; |
| 133 | 133 | templates.append(templateFromONEFACE(input_face)); |
| 134 | 134 | templates >> *frvt2012_age_transform.data(); |
| 135 | - age = templates.first().file.get<float>("Subject"); | |
| 135 | + age = templates.first().file.get<float>("Age"); | |
| 136 | 136 | return templates.first().file.failed() ? 4 : 0; |
| 137 | 137 | } |
| 138 | 138 | |
| ... | ... | @@ -141,6 +141,6 @@ int32_t SdkEstimator::estimate_gender(const ONEFACE &input_face, int8_t &gender, |
| 141 | 141 | TemplateList templates; |
| 142 | 142 | templates.append(templateFromONEFACE(input_face)); |
| 143 | 143 | templates >> *frvt2012_gender_transform.data(); |
| 144 | - mf = gender = templates.first().file.get<QString>("Subject") == "Male" ? 0 : 1; | |
| 144 | + mf = gender = templates.first().file.get<QString>("Gender") == "Male" ? 0 : 1; | |
| 145 | 145 | return templates.first().file.failed() ? 4 : 0; |
| 146 | 146 | } | ... | ... |
openbr/gui/classifier.cpp
| ... | ... | @@ -39,19 +39,23 @@ void Classifier::_classify(File file) |
| 39 | 39 | { |
| 40 | 40 | QString key, value; |
| 41 | 41 | foreach (const File &f, Enroll(file.flat(), File("[algorithm=" + algorithm + "]"))) { |
| 42 | - if (!f.contains("Label")) | |
| 43 | - continue; | |
| 44 | 42 | |
| 45 | 43 | if (algorithm == "GenderClassification") { |
| 46 | 44 | key = "Gender"; |
| 47 | - value = (f.get<QString>("Subject")); | |
| 48 | 45 | } else if (algorithm == "AgeRegression") { |
| 49 | 46 | key = "Age"; |
| 50 | - value = QString::number(int(f.get<float>("Subject")+0.5)) + " Years"; | |
| 51 | 47 | } else { |
| 52 | 48 | key = algorithm; |
| 53 | - value = f.get<QString>("Subject"); | |
| 54 | 49 | } |
| 50 | + | |
| 51 | + if (!f.contains(key)) | |
| 52 | + continue; | |
| 53 | + | |
| 54 | + if (algorithm == "AgeRegression") | |
| 55 | + value = QString::number(int(f.get<float>(key)+0.5)) + " Years"; | |
| 56 | + else | |
| 57 | + value = f.get<QString>(key); | |
| 58 | + | |
| 55 | 59 | break; |
| 56 | 60 | } |
| 57 | 61 | ... | ... |
openbr/openbr.cpp
| ... | ... | @@ -72,9 +72,9 @@ float br_eval(const char *simmat, const char *mask, const char *csv) |
| 72 | 72 | return Evaluate(simmat, mask, csv); |
| 73 | 73 | } |
| 74 | 74 | |
| 75 | -void br_eval_classification(const char *predicted_gallery, const char *truth_gallery) | |
| 75 | +void br_eval_classification(const char *predicted_gallery, const char *truth_gallery, const char *predicted_property, const char * truth_property) | |
| 76 | 76 | { |
| 77 | - EvalClassification(predicted_gallery, truth_gallery); | |
| 77 | + EvalClassification(predicted_gallery, truth_gallery, predicted_property, truth_property); | |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | 80 | void br_eval_clustering(const char *csv, const char *gallery) |
| ... | ... | @@ -87,9 +87,14 @@ float br_eval_detection(const char *predicted_gallery, const char *truth_gallery |
| 87 | 87 | return EvalDetection(predicted_gallery, truth_gallery, csv); |
| 88 | 88 | } |
| 89 | 89 | |
| 90 | -void br_eval_regression(const char *predicted_gallery, const char *truth_gallery) | |
| 90 | +void br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv) | |
| 91 | 91 | { |
| 92 | - EvalRegression(predicted_gallery, truth_gallery); | |
| 92 | + return EvalLandmarking(predicted_gallery, truth_gallery, csv); | |
| 93 | +} | |
| 94 | + | |
| 95 | +void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char * predicted_property, const char * truth_property) | |
| 96 | +{ | |
| 97 | + EvalRegression(predicted_gallery, truth_gallery, predicted_property, truth_property); | |
| 93 | 98 | } |
| 94 | 99 | |
| 95 | 100 | void br_finalize() | ... | ... |
openbr/openbr.h
| ... | ... | @@ -149,7 +149,7 @@ BR_EXPORT float br_eval(const char *simmat, const char *mask, const char *csv = |
| 149 | 149 | * \param predicted_gallery The predicted br::Gallery. |
| 150 | 150 | * \param truth_gallery The ground truth br::Gallery. |
| 151 | 151 | */ |
| 152 | -BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char *truth_gallery); | |
| 152 | +BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char *truth_gallery, const char * predicted_property="", const char * truth_property=""); | |
| 153 | 153 | |
| 154 | 154 | /*! |
| 155 | 155 | * \brief Evaluates and prints clustering accuracy to the terminal. |
| ... | ... | @@ -169,11 +169,19 @@ BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery); |
| 169 | 169 | BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv = ""); |
| 170 | 170 | |
| 171 | 171 | /*! |
| 172 | + * \brief Evaluates and prints landmarking accuracy to terminal. | |
| 173 | + * \param predicted_gallery The predicted br::Gallery. | |
| 174 | + * \param truth_gallery The ground truth br::Gallery. | |
| 175 | + * \param csv Optional \c .csv file to contain performance metrics. | |
| 176 | + */ | |
| 177 | +BR_EXPORT void br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv = ""); | |
| 178 | + | |
| 179 | +/*! | |
| 172 | 180 | * \brief Evaluates regression accuracy to disk. |
| 173 | 181 | * \param predicted_gallery The predicted br::Gallery. |
| 174 | 182 | * \param truth_gallery The ground truth br::Gallery. |
| 175 | 183 | */ |
| 176 | -BR_EXPORT void br_eval_regression(const char *predicted_gallery, const char *truth_gallery); | |
| 184 | +BR_EXPORT void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char * predicted_property="", const char * truth_property=""); | |
| 177 | 185 | |
| 178 | 186 | /*! |
| 179 | 187 | * \brief Wraps br::Context::finalize() | ... | ... |
openbr/openbr_export.cpp
| ... | ... | @@ -69,7 +69,6 @@ |
| 69 | 69 | $ cd bin |
| 70 | 70 | $ export LD_LIBRARY_PATH=../lib:${LD_LIBRARY_PATH} |
| 71 | 71 | $ sudo ldconfig |
| 72 | -$ sudo cp ../share/openbr/70-yubikey.rules /etc/udev/rules.d # Only needed if you were given a license dongle. | |
| 73 | 72 | \endverbatim |
| 74 | 73 | * \par OS X |
| 75 | 74 | \verbatim |
| ... | ... | @@ -80,10 +79,6 @@ $ export DYLD_FRAMEWORK_PATH=../lib:${DYLD_FRAMEWORK_PATH} |
| 80 | 79 | * \par Windows |
| 81 | 80 | * No configuration is necessary! |
| 82 | 81 | * |
| 83 | - * \section installation_license_dongle License Dongle | |
| 84 | - * In the unlikely event that you were given a USB License Dongle, then dongle must be in the computer in order to use the SDK. | |
| 85 | - * No configuration of the dongle is needed. | |
| 86 | - * | |
| 87 | 82 | * \section installation_done Start Working |
| 88 | 83 | * To test for successful installation: |
| 89 | 84 | \verbatim | ... | ... |
openbr/openbr_plugin.cpp
| ... | ... | @@ -437,6 +437,18 @@ TemplateList TemplateList::fromGallery(const br::File &gallery) |
| 437 | 437 | // Select the right 8 hex characters so that it can be represented as a 64 bit integer without overflow |
| 438 | 438 | newTemplates[i].file.set("Partition", md5.toHex().right(8).toULongLong(0, 16) % crossValidate); |
| 439 | 439 | } |
| 440 | +<<<<<<< HEAD | |
| 441 | +======= | |
| 442 | + } else if (newTemplates[i].file.getBool("allPartitions")) { | |
| 443 | + // The allPartitions flag is used to add an extended set | |
| 444 | + // of target images to every partition | |
| 445 | + newTemplates[i].file.set("Partition", -1); | |
| 446 | + } else { | |
| 447 | + // Direct use of "Label" is not general -cao | |
| 448 | + const QByteArray md5 = QCryptographicHash::hash(newTemplates[i].file.get<QString>("Label").toLatin1(), QCryptographicHash::Md5); | |
| 449 | + // Select the right 8 hex characters so that it can be represented as a 64 bit integer without overflow | |
| 450 | + newTemplates[i].file.set("Partition", md5.toHex().right(8).toULongLong(0, 16) % crossValidate); | |
| 451 | +>>>>>>> c2b1835e05d3b229db72d8829fb9ebf7e3cf31d8 | |
| 440 | 452 | } |
| 441 | 453 | } |
| 442 | 454 | } |
| ... | ... | @@ -834,7 +846,7 @@ float br::Context::progress() const |
| 834 | 846 | |
| 835 | 847 | void br::Context::setProperty(const QString &key, const QString &value) |
| 836 | 848 | { |
| 837 | - Object::setProperty(key, value); | |
| 849 | + Object::setProperty(key, value.isEmpty() ? QVariant() : value); | |
| 838 | 850 | qDebug("Set %s%s", qPrintable(key), value.isEmpty() ? "" : qPrintable(" to " + value)); |
| 839 | 851 | |
| 840 | 852 | if (key == "parallelism") { |
| ... | ... | @@ -912,6 +924,8 @@ void br::Context::initialize(int &argc, char *argv[], QString sdkPath, bool use_ |
| 912 | 924 | |
| 913 | 925 | qInstallMessageHandler(messageHandler); |
| 914 | 926 | |
| 927 | + Common::seedRNG(); | |
| 928 | + | |
| 915 | 929 | // Search for SDK |
| 916 | 930 | if (sdkPath.isEmpty()) { |
| 917 | 931 | QStringList checkPaths; checkPaths << QDir::currentPath() << QCoreApplication::applicationDirPath(); |
| ... | ... | @@ -1104,9 +1118,6 @@ Transform::Transform(bool _independent, bool _trainable) |
| 1104 | 1118 | { |
| 1105 | 1119 | independent = _independent; |
| 1106 | 1120 | trainable = _trainable; |
| 1107 | - classes = std::numeric_limits<int>::max(); | |
| 1108 | - instances = std::numeric_limits<int>::max(); | |
| 1109 | - fraction = 1; | |
| 1110 | 1121 | } |
| 1111 | 1122 | |
| 1112 | 1123 | Transform *Transform::make(QString str, QObject *parent) |
| ... | ... | @@ -1162,9 +1173,6 @@ Transform *Transform::make(QString str, QObject *parent) |
| 1162 | 1173 | Transform *Transform::clone() const |
| 1163 | 1174 | { |
| 1164 | 1175 | Transform *clone = Factory<Transform>::make(file.flat()); |
| 1165 | - clone->classes = classes; | |
| 1166 | - clone->instances = instances; | |
| 1167 | - clone->fraction = fraction; | |
| 1168 | 1176 | return clone; |
| 1169 | 1177 | } |
| 1170 | 1178 | |
| ... | ... | @@ -1193,28 +1201,6 @@ void Transform::project(const TemplateList &src, TemplateList &dst) const |
| 1193 | 1201 | futures.waitForFinished(); |
| 1194 | 1202 | } |
| 1195 | 1203 | |
| 1196 | -static void _backProject(const Transform *transform, const Template *dst, Template *src) | |
| 1197 | -{ | |
| 1198 | - try { | |
| 1199 | - transform->backProject(*dst, *src); | |
| 1200 | - } catch (...) { | |
| 1201 | - qWarning("Exception triggered when processing %s with transform %s", qPrintable(src->file.flat()), qPrintable(transform->objectName())); | |
| 1202 | - *src = Template(dst->file); | |
| 1203 | - src->file.set("FTE", true); | |
| 1204 | - } | |
| 1205 | -} | |
| 1206 | - | |
| 1207 | -void Transform::backProject(const TemplateList &dst, TemplateList &src) const | |
| 1208 | -{ | |
| 1209 | - src.reserve(dst.size()); | |
| 1210 | - for (int i=0; i<dst.size(); i++) src.append(Template()); | |
| 1211 | - | |
| 1212 | - QFutureSynchronizer<void> futures; | |
| 1213 | - for (int i=0; i<dst.size(); i++) | |
| 1214 | - futures.addFuture(QtConcurrent::run(_backProject, this, &dst[i], &src[i])); | |
| 1215 | - futures.waitForFinished(); | |
| 1216 | -} | |
| 1217 | - | |
| 1218 | 1204 | QList<Transform *> Transform::getChildren() const |
| 1219 | 1205 | { |
| 1220 | 1206 | QList<Transform *> output; | ... | ... |
openbr/openbr_plugin.h
| ... | ... | @@ -130,13 +130,6 @@ void reset_##NAME() { NAME = DEFAULT; } |
| 130 | 130 | * -# If the value is convertable to a floating point number then it is represented with \c float. |
| 131 | 131 | * -# Otherwise, it is represented with \c QString. |
| 132 | 132 | * |
| 133 | - * The metadata keys \c Subject and \c Label have special significance in the system. | |
| 134 | - * \c Subject is a string specifying a unique identifier used to determine ground truth match/non-match. | |
| 135 | - * \c Label is a floating point value used for supervised learning. | |
| 136 | - * When the system needs labels for training, but only subjects are provided in the file metadata, the rule for generating labels is as follows. | |
| 137 | - * If the subject value can be converted to a float then do so and consider that the label. | |
| 138 | - * Otherwise, generate a unique integer ID for the string starting from zero and incrementing by one everytime another ID is needed. | |
| 139 | - * | |
| 140 | 133 | * Metadata keys fall into one of two categories: |
| 141 | 134 | * - \c camelCaseKeys are inputs that specify how to process the file. |
| 142 | 135 | * - \c Capitalized_Underscored_Keys are outputs computed from processing the file. |
| ... | ... | @@ -147,8 +140,6 @@ void reset_##NAME() { NAME = DEFAULT; } |
| 147 | 140 | * --- | ---- | ----------- |
| 148 | 141 | * separator | QString | Seperate #name into multiple files |
| 149 | 142 | * Index | int | Index of a template in a template list |
| 150 | - * Subject | QString | Class name | |
| 151 | - * Label | float | Class value | |
| 152 | 143 | * Confidence | float | Classification/Regression quality |
| 153 | 144 | * FTE | bool | Failure to enroll |
| 154 | 145 | * FTO | bool | Failure to open |
| ... | ... | @@ -157,13 +148,15 @@ void reset_##NAME() { NAME = DEFAULT; } |
| 157 | 148 | * *_Width | float | Size |
| 158 | 149 | * *_Height | float | Size |
| 159 | 150 | * *_Radius | float | Size |
| 151 | + * Label | QString | Class label | |
| 160 | 152 | * Theta | float | Pose |
| 161 | 153 | * Roll | float | Pose |
| 162 | 154 | * Pitch | float | Pose |
| 163 | 155 | * Yaw | float | Pose |
| 164 | 156 | * Points | QList<QPointF> | List of unnamed points |
| 165 | 157 | * Rects | QList<Rect> | List of unnamed rects |
| 166 | - * Age | QString | Age used for demographic filtering | |
| 158 | + * Age | float | Age used for demographic filtering | |
| 159 | + * Gender | QString | Subject gender | |
| 167 | 160 | * _* | * | Reserved for internal use |
| 168 | 161 | */ |
| 169 | 162 | struct BR_EXPORT File |
| ... | ... | @@ -172,7 +165,7 @@ struct BR_EXPORT File |
| 172 | 165 | |
| 173 | 166 | File() {} |
| 174 | 167 | File(const QString &file) { init(file); } /*!< \brief Construct a file from a string. */ |
| 175 | - File(const QString &file, const QVariant &subject) { init(file); set("Subject", subject); } /*!< \brief Construct a file from a string and assign a label. */ | |
| 168 | + File(const QString &file, const QVariant &label) { init(file); set("Label", label); } /*!< \brief Construct a file from a string and assign a label. */ | |
| 176 | 169 | File(const char *file) { init(file); } /*!< \brief Construct a file from a c-style string. */ |
| 177 | 170 | inline operator QString() const { return name; } /*!< \brief Returns #name. */ |
| 178 | 171 | QString flat() const; /*!< \brief A stringified version of the file with metadata. */ |
| ... | ... | @@ -616,13 +609,6 @@ public: |
| 616 | 609 | BR_PROPERTY(int, blockSize, parallelism * ((sizeof(void*) == 4) ? 128 : 1024)) |
| 617 | 610 | |
| 618 | 611 | /*! |
| 619 | - * \brief true if backProject should be used instead of project (the algorithm should be inverted) | |
| 620 | - */ | |
| 621 | - Q_PROPERTY(bool backProject READ get_backProject WRITE set_backProject RESET reset_backProject) | |
| 622 | - BR_PROPERTY(bool, backProject, false) | |
| 623 | - | |
| 624 | - | |
| 625 | - /*! | |
| 626 | 612 | * \brief If \c true no messages will be sent to the terminal, \c false by default. |
| 627 | 613 | */ |
| 628 | 614 | Q_PROPERTY(bool quiet READ get_quiet WRITE set_quiet RESET reset_quiet) |
| ... | ... | @@ -1048,6 +1034,10 @@ private: |
| 1048 | 1034 | * @{ |
| 1049 | 1035 | */ |
| 1050 | 1036 | |
| 1037 | +/*! | |
| 1038 | + * \brief For asynchronous events during template projection. | |
| 1039 | + * \see #Transform::getEvent | |
| 1040 | + */ | |
| 1051 | 1041 | class TemplateEvent : public QObject |
| 1052 | 1042 | { |
| 1053 | 1043 | Q_OBJECT |
| ... | ... | @@ -1062,7 +1052,6 @@ signals: |
| 1062 | 1052 | void theSignal(const Template & output) const; |
| 1063 | 1053 | }; |
| 1064 | 1054 | |
| 1065 | - | |
| 1066 | 1055 | /*! |
| 1067 | 1056 | * \brief Plugin base class for processing a template. |
| 1068 | 1057 | * |
| ... | ... | @@ -1076,12 +1065,6 @@ class BR_EXPORT Transform : public Object |
| 1076 | 1065 | Q_OBJECT |
| 1077 | 1066 | |
| 1078 | 1067 | public: |
| 1079 | - Q_PROPERTY(int classes READ get_classes WRITE set_classes RESET reset_classes STORED false) | |
| 1080 | - Q_PROPERTY(int instances READ get_instances WRITE set_instances RESET reset_instances STORED false) | |
| 1081 | - Q_PROPERTY(float fraction READ get_fraction WRITE set_fraction RESET reset_fraction STORED false) | |
| 1082 | - BR_PROPERTY(int, classes, std::numeric_limits<int>::max()) | |
| 1083 | - BR_PROPERTY(int, instances, std::numeric_limits<int>::max()) | |
| 1084 | - BR_PROPERTY(float, fraction, 1) | |
| 1085 | 1068 | bool independent, trainable; |
| 1086 | 1069 | |
| 1087 | 1070 | virtual ~Transform() {} |
| ... | ... | @@ -1092,8 +1075,6 @@ public: |
| 1092 | 1075 | virtual void train(const TemplateList &data) = 0; /*!< \brief Train the transform. */ |
| 1093 | 1076 | virtual void project(const Template &src, Template &dst) const = 0; /*!< \brief Apply the transform. */ |
| 1094 | 1077 | virtual void project(const TemplateList &src, TemplateList &dst) const; /*!< \brief Apply the transform. */ |
| 1095 | - virtual void backProject(const Template &dst, Template &src) const { src = dst; } /*!< \brief Invert the transform. */ | |
| 1096 | - virtual void backProject(const TemplateList &dst, TemplateList &src) const; /*!< \brief Invert the transform. */ | |
| 1097 | 1078 | |
| 1098 | 1079 | /*!< \brief Apply the transform, may update the transform's internal state */ |
| 1099 | 1080 | virtual void projectUpdate(const Template &src, Template &dst) | ... | ... |
openbr/plugins/algorithms.cpp
| ... | ... | @@ -46,9 +46,12 @@ class AlgorithmsInitializer : public Initializer |
| 46 | 46 | Globals->abbreviations.insert("CropFace", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.25,0.35)"); |
| 47 | 47 | |
| 48 | 48 | // Video |
| 49 | - Globals->abbreviations.insert("DisplayVideo", "Stream([Show(false,[FrameNumber])+Discard])"); | |
| 49 | + Globals->abbreviations.insert("DisplayVideo", "Stream([FPSLimit(30)+Show(false,[FrameNumber])+Discard])"); | |
| 50 | 50 | Globals->abbreviations.insert("PerFrameDetection", "Stream([SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+RestoreMat(original)+Draw(inPlace=true),Show(false,[FrameNumber])+Discard])"); |
| 51 | - Globals->abbreviations.insert("AgeGenderDemo", "Stream([SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+<FaceClassificationRegistration>+<FaceClassificationExtraction>+(<AgeRegressor>+Rename(Subject,Age)+Discard)/(<GenderClassifier>+Rename(Subject,Gender)+Discard)+RestoreMat(original)+Draw(inPlace=true)+DrawPropertiesPoint([Age,Gender],Affine_0,inPlace=true)+SaveMat(original)+Discard+Contract,RestoreMat(original)+FPSCalc+Show(false,[AvgFPS,Age,Gender])+Discard])"); | |
| 51 | + Globals->abbreviations.insert("AgeGenderDemo", "Stream([SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+<FaceClassificationRegistration>+<FaceClassificationExtraction>+<AgeRegressor>/<GenderClassifier>+Discard+RestoreMat(original)+Draw(inPlace=true)+DrawPropertiesPoint([Age,Gender],Affine_0,inPlace=true)+SaveMat(original)+Discard+Contract,RestoreMat(original)+FPSCalc+Show(false,[AvgFPS,Age,Gender])+Discard])"); | |
| 52 | + Globals->abbreviations.insert("HOG", "Stream([KeyPointDetector(SIFT)+ROI+Expand+Resize(32,32)+Gradient+RectRegions+Bin(0,360,8)+Hist(8)+Cat])+Contract+CatRows+KMeans(500)+Hist(500)+SVM"); | |
| 53 | + Globals->abbreviations.insert("HOF", "Stream([KeyPointDetector(SIFT),AggregateFrames(2)+OpticalFlow+Gradient+Bin(0,360,8)+ROI+Hist(8)])+Contract+CatRows+KMeans(500)+Hist(500)"); | |
| 54 | + Globals->abbreviations.insert("HOGHOF", "Stream([Cvt(Gray),KeyPointDetector(SIFT),AggregateFrames(2),(OpticalFlow+Gradient+Bin(0,360,8)+ROI+Hist(8))/(First+Gradient+Bin(0,360,8)+ROI+Hist(8)),CatCols])+Contract+CatRows+KMeans(500)+Hist(500)"); | |
| 52 | 55 | |
| 53 | 56 | // Generic Image Processing |
| 54 | 57 | Globals->abbreviations.insert("SIFT", "Open+KeyPointDetector(SIFT)+KeyPointDescriptor(SIFT):KeyPointMatcher(BruteForce)"); |
| ... | ... | @@ -74,14 +77,14 @@ class AlgorithmsInitializer : public Initializer |
| 74 | 77 | Globals->abbreviations.insert("FaceDetection", "(Open+Cvt(Gray)+Cascade(FrontalFace))"); |
| 75 | 78 | Globals->abbreviations.insert("DenseLBP", "(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))"); |
| 76 | 79 | Globals->abbreviations.insert("DenseSIFT", "(Grid(10,10)+SIFTDescriptor(12)+ByRow)"); |
| 77 | - Globals->abbreviations.insert("FaceRecognitionRegistration", "(ASEFEyes+Affine(88,88,0.25,0.35)+FTE(DFFS,instances=1))"); | |
| 78 | - Globals->abbreviations.insert("FaceRecognitionExtraction", "(Mask+DenseSIFT/DenseLBP+PCA(0.95,instances=1)+Normalize(L2)+Cat)"); | |
| 79 | - Globals->abbreviations.insert("FaceRecognitionEmbedding", "(Dup(12)+RndSubspace(0.05,1)+LDA(0.98,instances=-2)+Cat+PCA(768,instances=1))"); | |
| 80 | + Globals->abbreviations.insert("FaceRecognitionRegistration", "(ASEFEyes+Affine(88,88,0.25,0.35)+DownsampleTraining(FTE(DFFS),instances=1))"); | |
| 81 | + Globals->abbreviations.insert("FaceRecognitionExtraction", "(Mask+DenseSIFT/DenseLBP+DownsampleTraining(PCA(0.95),instances=1)+Normalize(L2)+Cat)"); | |
| 82 | + Globals->abbreviations.insert("FaceRecognitionEmbedding", "(Dup(12)+RndSubspace(0.05,1)+DownsampleTraining(LDA(0.98),instances=-2)+Cat+DownsampleTraining(PCA(768),instances=1))"); | |
| 80 | 83 | Globals->abbreviations.insert("FaceRecognitionQuantization", "(Normalize(L1)+Quantize)"); |
| 81 | 84 | Globals->abbreviations.insert("FaceClassificationRegistration", "(ASEFEyes+Affine(56,72,0.33,0.45)+FTE(DFFS))"); |
| 82 | - Globals->abbreviations.insert("FaceClassificationExtraction", "((Grid(7,7)+SIFTDescriptor(8)+ByRow)/DenseLBP+PCA(0.95,instances=-1)+Cat)"); | |
| 83 | - Globals->abbreviations.insert("AgeRegressor", "Center(Range,instances=-1)+SVM(RBF,EPS_SVR,instances=100)"); | |
| 84 | - Globals->abbreviations.insert("GenderClassifier", "Center(Range,instances=-1)+SVM(RBF,C_SVC,instances=4000)"); | |
| 85 | + Globals->abbreviations.insert("FaceClassificationExtraction", "((Grid(7,7)+SIFTDescriptor(8)+ByRow)/DenseLBP+DownsampleTraining(PCA(0.95),instances=-1, inputVariable=Gender)+Cat)"); | |
| 86 | + Globals->abbreviations.insert("AgeRegressor", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Age)+DownsampleTraining(SVM(RBF,EPS_SVR,inputVariable=Age),instances=100, inputVariable=Age)"); | |
| 87 | + Globals->abbreviations.insert("GenderClassifier", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Gender)+DownsampleTraining(SVM(RBF,C_SVC,inputVariable=Gender),instances=4000, inputVariable=Gender)"); | |
| 85 | 88 | Globals->abbreviations.insert("UCharL1", "Unit(ByteL1)"); |
| 86 | 89 | } |
| 87 | 90 | }; | ... | ... |
openbr/plugins/cascade.cpp
| ... | ... | @@ -49,19 +49,20 @@ private: |
| 49 | 49 | } |
| 50 | 50 | }; |
| 51 | 51 | |
| 52 | - | |
| 53 | 52 | /*! |
| 54 | 53 | * \ingroup transforms |
| 55 | 54 | * \brief Wraps OpenCV cascade classifier |
| 56 | 55 | * \author Josh Klontz \cite jklontz |
| 57 | 56 | */ |
| 58 | -class CascadeTransform : public UntrainableTransform | |
| 57 | +class CascadeTransform : public UntrainableMetaTransform | |
| 59 | 58 | { |
| 60 | 59 | Q_OBJECT |
| 61 | 60 | Q_PROPERTY(QString model READ get_model WRITE set_model RESET reset_model STORED false) |
| 62 | 61 | Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) |
| 62 | + Q_PROPERTY(bool ROCMode READ get_ROCMode WRITE set_ROCMode RESET reset_ROCMode STORED false) | |
| 63 | 63 | BR_PROPERTY(QString, model, "FrontalFace") |
| 64 | 64 | BR_PROPERTY(int, minSize, 64) |
| 65 | + BR_PROPERTY(bool, ROCMode, false) | |
| 65 | 66 | |
| 66 | 67 | Resource<CascadeClassifier> cascadeResource; |
| 67 | 68 | |
| ... | ... | @@ -72,18 +73,54 @@ class CascadeTransform : public UntrainableTransform |
| 72 | 73 | |
| 73 | 74 | void project(const Template &src, Template &dst) const |
| 74 | 75 | { |
| 76 | + TemplateList temp; | |
| 77 | + project(TemplateList() << src, temp); | |
| 78 | + if (!temp.isEmpty()) dst = temp.first(); | |
| 79 | + } | |
| 80 | + | |
| 81 | + void project(const TemplateList &src, TemplateList &dst) const | |
| 82 | + { | |
| 75 | 83 | CascadeClassifier *cascade = cascadeResource.acquire(); |
| 76 | - vector<Rect> rects; | |
| 77 | - cascade->detectMultiScale(src, rects, 1.2, 5, src.file.get<bool>("enrollAll", false) ? 0 : CV_HAAR_FIND_BIGGEST_OBJECT, Size(minSize, minSize)); | |
| 84 | + foreach (const Template &t, src) { | |
| 85 | + const bool enrollAll = t.file.getBool("enrollAll"); | |
| 86 | + | |
| 87 | + for (int i=0; i<t.size(); i++) { | |
| 88 | + const Mat &m = t[i]; | |
| 89 | + vector<Rect> rects; | |
| 90 | + vector<int> rejectLevels; | |
| 91 | + vector<double> levelWeights; | |
| 92 | + if (ROCMode) cascade->detectMultiScale(m, rects, rejectLevels, levelWeights, 1.2, 5, (enrollAll ? 0 : CV_HAAR_FIND_BIGGEST_OBJECT) | CV_HAAR_SCALE_IMAGE, Size(minSize, minSize), Size(), true); | |
| 93 | + else cascade->detectMultiScale(m, rects, 1.2, 5, enrollAll ? 0 : CV_HAAR_FIND_BIGGEST_OBJECT, Size(minSize, minSize)); | |
| 94 | + | |
| 95 | + if (!enrollAll && rects.empty()) | |
| 96 | + rects.push_back(Rect(0, 0, m.cols, m.rows)); | |
| 97 | + | |
| 98 | + for (size_t j=0; j<rects.size(); j++) { | |
| 99 | + Template u(t.file, m); | |
| 100 | + if (rejectLevels.size() > j) | |
| 101 | + u.file.set("Confidence", rejectLevels[j]*levelWeights[j]); | |
| 102 | + const QRectF rect = OpenCVUtils::fromRect(rects[j]); | |
| 103 | + u.file.appendRect(rect); | |
| 104 | + u.file.set(model, rect); | |
| 105 | + dst.append(u); | |
| 106 | + } | |
| 107 | + } | |
| 108 | + } | |
| 109 | + | |
| 78 | 110 | cascadeResource.release(cascade); |
| 111 | + } | |
| 79 | 112 | |
| 80 | - if (!src.file.get<bool>("enrollAll", false) && rects.empty()) | |
| 81 | - rects.push_back(Rect(0, 0, src.m().cols, src.m().rows)); | |
| 113 | + // TODO: Remove this code when ready to break binary compatibility | |
| 114 | + void store(QDataStream &stream) const | |
| 115 | + { | |
| 116 | + int size = 1; | |
| 117 | + stream << size; | |
| 118 | + } | |
| 82 | 119 | |
| 83 | - foreach (const Rect &rect, rects) { | |
| 84 | - dst += src; | |
| 85 | - dst.file.appendRect(OpenCVUtils::fromRect(rect)); | |
| 86 | - } | |
| 120 | + void load(QDataStream &stream) | |
| 121 | + { | |
| 122 | + int size; | |
| 123 | + stream >> size; | |
| 87 | 124 | } |
| 88 | 125 | }; |
| 89 | 126 | ... | ... |
openbr/plugins/cluster.cpp
| ... | ... | @@ -89,10 +89,14 @@ class KNNTransform : public Transform |
| 89 | 89 | Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance STORED false) |
| 90 | 90 | Q_PROPERTY(bool weighted READ get_weighted WRITE set_weighted RESET reset_weighted STORED false) |
| 91 | 91 | Q_PROPERTY(int numSubjects READ get_numSubjects WRITE set_numSubjects RESET reset_numSubjects STORED false) |
| 92 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 93 | + Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false) | |
| 92 | 94 | BR_PROPERTY(int, k, 1) |
| 93 | 95 | BR_PROPERTY(br::Distance*, distance, NULL) |
| 94 | 96 | BR_PROPERTY(bool, weighted, false) |
| 95 | 97 | BR_PROPERTY(int, numSubjects, 1) |
| 98 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 99 | + BR_PROPERTY(QString, outputVariable, "KNN") | |
| 96 | 100 | |
| 97 | 101 | TemplateList gallery; |
| 98 | 102 | |
| ... | ... | @@ -111,17 +115,17 @@ class KNNTransform : public Transform |
| 111 | 115 | QHash<QString, float> votes; |
| 112 | 116 | const int max = (k < 1) ? sortedScores.size() : std::min(k, sortedScores.size()); |
| 113 | 117 | for (int j=0; j<max; j++) |
| 114 | - votes[gallery[sortedScores[j].second].file.get<QString>("Subject")] += (weighted ? sortedScores[j].first : 1); | |
| 118 | + votes[gallery[sortedScores[j].second].file.get<QString>(inputVariable)] += (weighted ? sortedScores[j].first : 1); | |
| 115 | 119 | subjects.append(votes.keys()[votes.values().indexOf(Common::Max(votes.values()))]); |
| 116 | 120 | |
| 117 | 121 | // Remove subject from consideration |
| 118 | 122 | if (subjects.size() < numSubjects) |
| 119 | 123 | for (int j=sortedScores.size()-1; j>=0; j--) |
| 120 | - if (gallery[sortedScores[j].second].file.get<QString>("Subject") == subjects.last()) | |
| 124 | + if (gallery[sortedScores[j].second].file.get<QString>(inputVariable) == subjects.last()) | |
| 121 | 125 | sortedScores.removeAt(j); |
| 122 | 126 | } |
| 123 | 127 | |
| 124 | - dst.file.set("KNN", subjects.size() > 1 ? "[" + subjects.join(",") + "]" : subjects.first()); | |
| 128 | + dst.file.set(outputVariable, subjects.size() > 1 ? "[" + subjects.join(",") + "]" : subjects.first()); | |
| 125 | 129 | } |
| 126 | 130 | |
| 127 | 131 | void store(QDataStream &stream) const | ... | ... |
openbr/plugins/eigen3.cpp
| ... | ... | @@ -59,21 +59,6 @@ public: |
| 59 | 59 | PCATransform() : keep(0.95), drop(0), whiten(false) {} |
| 60 | 60 | |
| 61 | 61 | private: |
| 62 | - /* | |
| 63 | - void backProject(const Template &src, Template &dst) const | |
| 64 | - { | |
| 65 | - const cv::Mat &m = src; | |
| 66 | - dst = cv::Mat(originalRows, m.rows*m.cols/originalRows, CV_32FC1); | |
| 67 | - | |
| 68 | - // Map Eigen into OpenCV | |
| 69 | - Eigen::Map<const Eigen::MatrixXf> inMap(m.ptr<float>(), keep, 1); | |
| 70 | - Eigen::Map<Eigen::MatrixXf> outMap(dst.m().ptr<float>(), m.rows*m.cols, 1); | |
| 71 | - | |
| 72 | - // Do projection | |
| 73 | - outMap = (eVecs * inMap) + mean; | |
| 74 | - } | |
| 75 | - */ | |
| 76 | - | |
| 77 | 62 | double residualReconstructionError(const Template &src) const |
| 78 | 63 | { |
| 79 | 64 | Template proj; |
| ... | ... | @@ -318,10 +303,12 @@ class LDATransform : public Transform |
| 318 | 303 | Q_PROPERTY(bool pcaWhiten READ get_pcaWhiten WRITE set_pcaWhiten RESET reset_pcaWhiten STORED false) |
| 319 | 304 | Q_PROPERTY(int directLDA READ get_directLDA WRITE set_directLDA RESET reset_directLDA STORED false) |
| 320 | 305 | Q_PROPERTY(float directDrop READ get_directDrop WRITE set_directDrop RESET reset_directDrop STORED false) |
| 306 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 321 | 307 | BR_PROPERTY(float, pcaKeep, 0.98) |
| 322 | 308 | BR_PROPERTY(bool, pcaWhiten, false) |
| 323 | 309 | BR_PROPERTY(int, directLDA, 0) |
| 324 | 310 | BR_PROPERTY(float, directDrop, 0.1) |
| 311 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 325 | 312 | |
| 326 | 313 | int dimsOut; |
| 327 | 314 | Eigen::VectorXf mean; |
| ... | ... | @@ -330,7 +317,7 @@ class LDATransform : public Transform |
| 330 | 317 | void train(const TemplateList &_trainingSet) |
| 331 | 318 | { |
| 332 | 319 | // creates "Label" |
| 333 | - TemplateList trainingSet = TemplateList::relabel(_trainingSet, "Subject"); | |
| 320 | + TemplateList trainingSet = TemplateList::relabel(_trainingSet, inputVariable); | |
| 334 | 321 | |
| 335 | 322 | int instances = trainingSet.size(); |
| 336 | 323 | ... | ... |
openbr/plugins/eyes.cpp
| ... | ... | @@ -186,7 +186,6 @@ private: |
| 186 | 186 | //dst.file.appendPoint(QPointF(second_eye_x, second_eye_y)); |
| 187 | 187 | dst.file.set("First_Eye", QPointF(first_eye_x, first_eye_y)); |
| 188 | 188 | dst.file.set("Second_Eye", QPointF(second_eye_x, second_eye_y)); |
| 189 | - dst.file.set("Face", QRect(roi.x, roi.y, roi.width, roi.height)); | |
| 190 | 189 | } |
| 191 | 190 | }; |
| 192 | 191 | ... | ... |
openbr/plugins/gallery.cpp
| ... | ... | @@ -71,7 +71,7 @@ class arffGallery : public Gallery |
| 71 | 71 | } |
| 72 | 72 | |
| 73 | 73 | arffFile.write(qPrintable(OpenCVUtils::matrixToStringList(t).join(','))); |
| 74 | - arffFile.write(qPrintable(",'" + t.file.get<QString>("Subject") + "'\n")); | |
| 74 | + arffFile.write(qPrintable(",'" + t.file.get<QString>("Label") + "'\n")); | |
| 75 | 75 | } |
| 76 | 76 | }; |
| 77 | 77 | |
| ... | ... | @@ -643,11 +643,16 @@ class dbGallery : public Gallery |
| 643 | 643 | query = query.mid(1, query.size()-2); |
| 644 | 644 | if (!q.exec(query)) |
| 645 | 645 | qFatal("%s.", qPrintable(q.lastError().text())); |
| 646 | + | |
| 646 | 647 | if ((q.record().count() == 0) || (q.record().count() > 3)) |
| 647 | 648 | qFatal("Query record expected one to three fields, got %d.", q.record().count()); |
| 648 | 649 | const bool hasMetadata = (q.record().count() >= 2); |
| 649 | 650 | const bool hasFilter = (q.record().count() >= 3); |
| 650 | 651 | |
| 652 | + QString labelName = "Label"; | |
| 653 | + if (q.record().count() >= 2) | |
| 654 | + labelName = q.record().fieldName(1); | |
| 655 | + | |
| 651 | 656 | // subset = seed:subjectMaxSize:numSubjects:subjectMinSize or |
| 652 | 657 | // subset = seed:{Metadata,...,Metadata}:numSubjects |
| 653 | 658 | int seed = 0, subjectMaxSize = std::numeric_limits<int>::max(), numSubjects = std::numeric_limits<int>::max(), subjectMinSize = 0; |
| ... | ... | @@ -673,6 +678,7 @@ class dbGallery : public Gallery |
| 673 | 678 | QHash<QString, QList<Entry> > entries; // QHash<Label, QList<Entry> > |
| 674 | 679 | while (q.next()) { |
| 675 | 680 | if (hasFilter && (seed >= 0) && (qHash(q.value(2).toString()) % 2 != (uint)seed % 2)) continue; // Ensures training and testing filters don't overlap |
| 681 | + | |
| 676 | 682 | if (metadataFields.isEmpty()) |
| 677 | 683 | entries[hasMetadata ? q.value(1).toString() : ""].append(QPair<QString,QString>(q.value(0).toString(), hasFilter ? q.value(2).toString() : "")); |
| 678 | 684 | else |
| ... | ... | @@ -707,8 +713,10 @@ class dbGallery : public Gallery |
| 707 | 713 | |
| 708 | 714 | if (entryList.size() > subjectMaxSize) |
| 709 | 715 | std::random_shuffle(entryList.begin(), entryList.end()); |
| 710 | - foreach (const Entry &entry, entryList.mid(0, subjectMaxSize)) | |
| 711 | - templates.append(File(entry.first, label)); | |
| 716 | + foreach (const Entry &entry, entryList.mid(0, subjectMaxSize)) { | |
| 717 | + templates.append(File(entry.first)); | |
| 718 | + templates.last().file.set(labelName, label); | |
| 719 | + } | |
| 712 | 720 | numSubjects--; |
| 713 | 721 | } |
| 714 | 722 | } |
| ... | ... | @@ -816,7 +824,7 @@ class statGallery : public Gallery |
| 816 | 824 | |
| 817 | 825 | void write(const Template &t) |
| 818 | 826 | { |
| 819 | - subjects.insert(t.file.get<QString>("Subject")); | |
| 827 | + subjects.insert(t.file.get<QString>("Label")); | |
| 820 | 828 | bytes.append(t.bytes()); |
| 821 | 829 | } |
| 822 | 830 | }; | ... | ... |
openbr/plugins/gui.cpp
| ... | ... | @@ -52,26 +52,33 @@ QImage toQImage(const Mat &mat) |
| 52 | 52 | return QImage(mat8uc3.data, mat8uc3.cols, mat8uc3.rows, 3*mat8uc3.cols, QImage::Format_RGB888).copy(); |
| 53 | 53 | } |
| 54 | 54 | |
| 55 | - | |
| 56 | -// Provides slots for manipulating a QLabel, but does not inherit from QWidget. | |
| 57 | -// Therefore, it can be moved to the main thread if not created there initially | |
| 58 | -// since god forbid you create a QWidget subclass in not the main thread. | |
| 59 | -class GUIProxy : public QObject | |
| 55 | +class DisplayWindow : public QLabel | |
| 60 | 56 | { |
| 61 | 57 | Q_OBJECT |
| 58 | + | |
| 59 | +protected: | |
| 62 | 60 | QMutex lock; |
| 63 | 61 | QWaitCondition wait; |
| 62 | + QPixmap pixmap; | |
| 64 | 63 | |
| 65 | 64 | public: |
| 66 | 65 | |
| 67 | - QLabel *window; | |
| 68 | - QPixmap pixmap; | |
| 66 | + DisplayWindow(QWidget * parent = NULL) : QLabel(parent) | |
| 67 | + { | |
| 68 | + QApplication::instance()->installEventFilter(this); | |
| 69 | + } | |
| 69 | 70 | |
| 70 | - GUIProxy() | |
| 71 | +public slots: | |
| 72 | + void showImage(const QPixmap & input) | |
| 71 | 73 | { |
| 72 | - window = NULL; | |
| 74 | + pixmap = input; | |
| 75 | + | |
| 76 | + show(); | |
| 77 | + setPixmap(pixmap); | |
| 78 | + setFixedSize(input.size()); | |
| 73 | 79 | } |
| 74 | 80 | |
| 81 | + | |
| 75 | 82 | bool eventFilter(QObject * obj, QEvent * event) |
| 76 | 83 | { |
| 77 | 84 | if (event->type() == QEvent::KeyPress) |
| ... | ... | @@ -92,41 +99,10 @@ public: |
| 92 | 99 | |
| 93 | 100 | return QList<QPointF>(); |
| 94 | 101 | } |
| 95 | - | |
| 96 | -public slots: | |
| 97 | - | |
| 98 | - void showImage(const QPixmap & input) | |
| 99 | - { | |
| 100 | - pixmap = input; | |
| 101 | - | |
| 102 | - window->show(); | |
| 103 | - window->setPixmap(pixmap); | |
| 104 | - window->setFixedSize(input.size()); | |
| 105 | - } | |
| 106 | - | |
| 107 | - void createWindow() | |
| 108 | - { | |
| 109 | - delete window; | |
| 110 | - QApplication::instance()->removeEventFilter(this); | |
| 111 | - | |
| 112 | - window = new QLabel(); | |
| 113 | - window->setVisible(true); | |
| 114 | - | |
| 115 | - QApplication::instance()->installEventFilter(this); | |
| 116 | - Qt::WindowFlags flags = window->windowFlags(); | |
| 117 | - | |
| 118 | - flags = flags & ~Qt::WindowCloseButtonHint; | |
| 119 | - window->setWindowFlags(flags); | |
| 120 | - } | |
| 121 | - | |
| 122 | 102 | }; |
| 123 | 103 | |
| 124 | -class LandmarkProxy : public GUIProxy | |
| 104 | +class PointMarkingWindow : public DisplayWindow | |
| 125 | 105 | { |
| 126 | - Q_OBJECT | |
| 127 | - | |
| 128 | -public: | |
| 129 | - | |
| 130 | 106 | bool eventFilter(QObject *obj, QEvent *event) |
| 131 | 107 | { |
| 132 | 108 | if (event->type() == QEvent::MouseButtonPress) |
| ... | ... | @@ -144,11 +120,11 @@ public: |
| 144 | 120 | painter.setBrush(Qt::red); |
| 145 | 121 | foreach(const QPointF &point, points) painter.drawEllipse(point, 4, 4); |
| 146 | 122 | |
| 147 | - window->setPixmap(pixmapBuffer); | |
| 123 | + setPixmap(pixmapBuffer); | |
| 148 | 124 | |
| 149 | 125 | return true; |
| 150 | 126 | } else { |
| 151 | - return GUIProxy::eventFilter(obj, event); | |
| 127 | + return DisplayWindow::eventFilter(obj, event); | |
| 152 | 128 | } |
| 153 | 129 | } |
| 154 | 130 | |
| ... | ... | @@ -156,14 +132,85 @@ public: |
| 156 | 132 | { |
| 157 | 133 | points.clear(); |
| 158 | 134 | |
| 159 | - GUIProxy::waitForKey(); | |
| 135 | + DisplayWindow::waitForKey(); | |
| 160 | 136 | |
| 161 | 137 | return points; |
| 162 | 138 | } |
| 163 | 139 | |
| 164 | 140 | private: |
| 165 | - | |
| 166 | 141 | QList<QPointF> points; |
| 142 | + | |
| 143 | + | |
| 144 | +}; | |
| 145 | + | |
| 146 | + | |
| 147 | +// I want a template class that doesn't look like a template class | |
| 148 | +class NominalCreation | |
| 149 | +{ | |
| 150 | +public: | |
| 151 | + virtual ~NominalCreation() {} | |
| 152 | + virtual void creation()=0; | |
| 153 | +}; | |
| 154 | + | |
| 155 | +// Putting the template on a subclass means we can maintain a pointer that | |
| 156 | +// doesn't include T in its type. | |
| 157 | +template<typename T> | |
| 158 | +class ActualCreation : public NominalCreation | |
| 159 | +{ | |
| 160 | +public: | |
| 161 | + T * basis; | |
| 162 | + | |
| 163 | + void creation() | |
| 164 | + { | |
| 165 | + basis = new T(); | |
| 166 | + } | |
| 167 | +}; | |
| 168 | + | |
| 169 | +// We want to create a QLabel subclass on the main thread, but are running in another thread. | |
| 170 | +// We cannot move QWidget subclasses to a different thread (obviously that would be crazy), but | |
| 171 | +// we can create one of these, and move it to the main thread, and then use it to create the object | |
| 172 | +// we want. | |
| 173 | +// Additional fact: QObject subclasses cannot be template classes. | |
| 174 | +class MainThreadCreator : public QObject | |
| 175 | +{ | |
| 176 | + Q_OBJECT | |
| 177 | +public: | |
| 178 | + | |
| 179 | + MainThreadCreator() | |
| 180 | + { | |
| 181 | + this->moveToThread(QApplication::instance()->thread()); | |
| 182 | + | |
| 183 | + connect(this, SIGNAL(needCreation()), this, SLOT(createThing()), Qt::BlockingQueuedConnection); | |
| 184 | + } | |
| 185 | + | |
| 186 | + // While this cannot be a template class, it can still have a template method. | |
| 187 | + template<typename T> | |
| 188 | + T * getItem() | |
| 189 | + { | |
| 190 | + if (QThread::currentThread() == QApplication::instance()->thread()) | |
| 191 | + return new T(); | |
| 192 | + | |
| 193 | + ActualCreation<T> * actualWorker; | |
| 194 | + actualWorker = new ActualCreation<T> (); | |
| 195 | + worker = actualWorker; | |
| 196 | + | |
| 197 | + emit needCreation(); | |
| 198 | + | |
| 199 | + T * output = actualWorker->basis; | |
| 200 | + delete actualWorker; | |
| 201 | + return output; | |
| 202 | + } | |
| 203 | + | |
| 204 | + NominalCreation * worker; | |
| 205 | + | |
| 206 | +signals: | |
| 207 | + void needCreation(); | |
| 208 | + | |
| 209 | +public slots: | |
| 210 | + void createThing() | |
| 211 | + { | |
| 212 | + worker->creation(); | |
| 213 | + } | |
| 167 | 214 | }; |
| 168 | 215 | |
| 169 | 216 | /*! |
| ... | ... | @@ -184,14 +231,14 @@ public: |
| 184 | 231 | |
| 185 | 232 | ShowTransform() : TimeVaryingTransform(false, false) |
| 186 | 233 | { |
| 187 | - gui = NULL; | |
| 188 | 234 | displayBuffer = NULL; |
| 235 | + window = NULL; | |
| 189 | 236 | } |
| 190 | 237 | |
| 191 | 238 | ~ShowTransform() |
| 192 | 239 | { |
| 193 | - delete gui; | |
| 194 | 240 | delete displayBuffer; |
| 241 | + delete window; | |
| 195 | 242 | } |
| 196 | 243 | |
| 197 | 244 | void train(const TemplateList &data) { (void) data; } |
| ... | ... | @@ -235,7 +282,7 @@ public: |
| 235 | 282 | |
| 236 | 283 | // Blocking wait for a key-press |
| 237 | 284 | if (this->waitInput) |
| 238 | - gui->waitForKey(); | |
| 285 | + window->waitForKey(); | |
| 239 | 286 | |
| 240 | 287 | } |
| 241 | 288 | } |
| ... | ... | @@ -249,33 +296,36 @@ public: |
| 249 | 296 | |
| 250 | 297 | void init() |
| 251 | 298 | { |
| 299 | + initActual<DisplayWindow>(); | |
| 300 | + } | |
| 301 | + | |
| 302 | + template<typename WindowType> | |
| 303 | + void initActual() | |
| 304 | + { | |
| 252 | 305 | if (!Globals->useGui) |
| 253 | 306 | return; |
| 254 | 307 | |
| 308 | + if (displayBuffer) | |
| 309 | + delete displayBuffer; | |
| 255 | 310 | displayBuffer = new QPixmap(); |
| 256 | 311 | |
| 257 | - // Create our GUI proxy | |
| 258 | - gui = new GUIProxy(); | |
| 259 | - // Move it to the main thread, this means signals we send to it will | |
| 260 | - // be run in the main thread, which is hopefully in an event loop | |
| 261 | - gui->moveToThread(QApplication::instance()->thread()); | |
| 262 | - | |
| 263 | - // Connect our signals to the proxy's slots | |
| 264 | - connect(this, SIGNAL(needWindow()), gui, SLOT(createWindow()), Qt::BlockingQueuedConnection); | |
| 265 | - connect(this, SIGNAL(updateImage(QPixmap)), gui,SLOT(showImage(QPixmap))); | |
| 312 | + if (window) | |
| 313 | + delete window; | |
| 266 | 314 | |
| 267 | - emit needWindow(); | |
| 268 | - connect(this, SIGNAL(changeTitle(QString)), gui->window, SLOT(setWindowTitle(QString))); | |
| 269 | - connect(this, SIGNAL(hideWindow()), gui->window, SLOT(hide())); | |
| 315 | + window = creator.getItem<WindowType>(); | |
| 316 | + // Connect our signals to the window's slots | |
| 317 | + connect(this, SIGNAL(updateImage(QPixmap)), window,SLOT(showImage(QPixmap))); | |
| 318 | + connect(this, SIGNAL(changeTitle(QString)), window, SLOT(setWindowTitle(QString))); | |
| 319 | + connect(this, SIGNAL(hideWindow()), window, SLOT(hide())); | |
| 270 | 320 | } |
| 271 | 321 | |
| 272 | 322 | protected: |
| 273 | - GUIProxy * gui; | |
| 323 | + MainThreadCreator creator; | |
| 324 | + DisplayWindow * window; | |
| 274 | 325 | QImage qImageBuffer; |
| 275 | 326 | QPixmap * displayBuffer; |
| 276 | 327 | |
| 277 | 328 | signals: |
| 278 | - void needWindow(); | |
| 279 | 329 | void updateImage(const QPixmap & input); |
| 280 | 330 | void changeTitle(const QString & input); |
| 281 | 331 | void hideWindow(); |
| ... | ... | @@ -312,7 +362,7 @@ public: |
| 312 | 362 | |
| 313 | 363 | // Blocking wait for a key-press |
| 314 | 364 | if (this->waitInput) { |
| 315 | - QList<QPointF> points = gui->waitForKey(); | |
| 365 | + QList<QPointF> points = window->waitForKey(); | |
| 316 | 366 | if (keys.isEmpty()) dst[i].file.appendPoints(points); |
| 317 | 367 | else { |
| 318 | 368 | if (keys.size() == points.size()) |
| ... | ... | @@ -326,24 +376,7 @@ public: |
| 326 | 376 | |
| 327 | 377 | void init() |
| 328 | 378 | { |
| 329 | - if (!Globals->useGui) | |
| 330 | - return; | |
| 331 | - | |
| 332 | - displayBuffer = new QPixmap(); | |
| 333 | - | |
| 334 | - // Create our GUI proxy | |
| 335 | - gui = new LandmarkProxy(); | |
| 336 | - | |
| 337 | - // Move it to the main thread, this means signals we send to it will | |
| 338 | - // be run in the main thread, which is hopefully in an event loop | |
| 339 | - gui->moveToThread(QApplication::instance()->thread()); | |
| 340 | - | |
| 341 | - // Connect our signals to the proxy's slots | |
| 342 | - connect(this, SIGNAL(needWindow()), gui, SLOT(createWindow()), Qt::BlockingQueuedConnection); | |
| 343 | - connect(this, SIGNAL(updateImage(QPixmap)), gui,SLOT(showImage(QPixmap))); | |
| 344 | - | |
| 345 | - emit needWindow(); | |
| 346 | - connect(this, SIGNAL(hideWindow()), gui->window, SLOT(hide())); | |
| 379 | + initActual<PointMarkingWindow>(); | |
| 347 | 380 | } |
| 348 | 381 | }; |
| 349 | 382 | ... | ... |
openbr/plugins/hist.cpp
| ... | ... | @@ -52,8 +52,11 @@ class HistTransform : public UntrainableTransform |
| 52 | 52 | int histSize[] = {dims}; |
| 53 | 53 | float range[] = {min, max}; |
| 54 | 54 | const float* ranges[] = {range}; |
| 55 | - Mat hist; | |
| 56 | - calcHist(&mv[i], 1, channels, Mat(), hist, 1, histSize, ranges); | |
| 55 | + Mat hist, chan = mv[i]; | |
| 56 | + // calcHist requires F or U, might as well convert just in case | |
| 57 | + if (mv[i].depth() != CV_8U || mv[i].depth() == CV_32F) | |
| 58 | + mv[i].convertTo(chan, CV_32F); | |
| 59 | + calcHist(&chan, 1, channels, Mat(), hist, 1, histSize, ranges); | |
| 57 | 60 | memcpy(m.ptr(i), hist.ptr(), dims * sizeof(float)); |
| 58 | 61 | } |
| 59 | 62 | ... | ... |
openbr/plugins/independent.cpp
| ... | ... | @@ -9,37 +9,36 @@ using namespace cv; |
| 9 | 9 | namespace br |
| 10 | 10 | { |
| 11 | 11 | |
| 12 | -static TemplateList Downsample(const TemplateList &templates, const Transform *transform) | |
| 12 | +static TemplateList Downsample(const TemplateList &templates, int classes, int instances, float fraction, const QString & inputVariable) | |
| 13 | 13 | { |
| 14 | 14 | // Return early when no downsampling is required |
| 15 | - if ((transform->classes == std::numeric_limits<int>::max()) && | |
| 16 | - (transform->instances == std::numeric_limits<int>::max()) && | |
| 17 | - (transform->fraction >= 1)) | |
| 15 | + if ((classes == std::numeric_limits<int>::max()) && | |
| 16 | + (instances == std::numeric_limits<int>::max()) && | |
| 17 | + (fraction >= 1)) | |
| 18 | 18 | return templates; |
| 19 | 19 | |
| 20 | - const bool atLeast = transform->instances < 0; | |
| 21 | - const int instances = abs(transform->instances); | |
| 20 | + const bool atLeast = instances < 0; | |
| 21 | + instances = abs(instances); | |
| 22 | 22 | |
| 23 | - QList<QString> allLabels = File::get<QString>(templates, "Subject"); | |
| 23 | + QList<QString> allLabels = File::get<QString>(templates, inputVariable); | |
| 24 | 24 | QList<QString> uniqueLabels = allLabels.toSet().toList(); |
| 25 | 25 | qSort(uniqueLabels); |
| 26 | 26 | |
| 27 | - QMap<QString,int> counts = templates.countValues<QString>("Subject", instances != std::numeric_limits<int>::max()); | |
| 27 | + QMap<QString,int> counts = templates.countValues<QString>(inputVariable, instances != std::numeric_limits<int>::max()); | |
| 28 | 28 | |
| 29 | - if ((instances != std::numeric_limits<int>::max()) && (transform->classes != std::numeric_limits<int>::max())) | |
| 29 | + if ((instances != std::numeric_limits<int>::max()) && (classes != std::numeric_limits<int>::max())) | |
| 30 | 30 | foreach (const QString & label, counts.keys()) |
| 31 | 31 | if (counts[label] < instances) |
| 32 | 32 | counts.remove(label); |
| 33 | 33 | |
| 34 | 34 | uniqueLabels = counts.keys(); |
| 35 | - if ((transform->classes != std::numeric_limits<int>::max()) && (uniqueLabels.size() < transform->classes)) | |
| 36 | - qWarning("Downsample requested %d classes but only %d are available.", transform->classes, uniqueLabels.size()); | |
| 35 | + if ((classes != std::numeric_limits<int>::max()) && (uniqueLabels.size() < classes)) | |
| 36 | + qWarning("Downsample requested %d classes but only %d are available.", classes, uniqueLabels.size()); | |
| 37 | 37 | |
| 38 | - Common::seedRNG(); | |
| 39 | 38 | QList<QString> selectedLabels = uniqueLabels; |
| 40 | - if (transform->classes < uniqueLabels.size()) { | |
| 39 | + if (classes < uniqueLabels.size()) { | |
| 41 | 40 | std::random_shuffle(selectedLabels.begin(), selectedLabels.end()); |
| 42 | - selectedLabels = selectedLabels.mid(0, transform->classes); | |
| 41 | + selectedLabels = selectedLabels.mid(0, classes); | |
| 43 | 42 | } |
| 44 | 43 | |
| 45 | 44 | TemplateList downsample; |
| ... | ... | @@ -56,14 +55,45 @@ static TemplateList Downsample(const TemplateList &templates, const Transform *t |
| 56 | 55 | downsample.append(templates.value(indices[j])); |
| 57 | 56 | } |
| 58 | 57 | |
| 59 | - if (transform->fraction < 1) { | |
| 58 | + if (fraction < 1) { | |
| 60 | 59 | std::random_shuffle(downsample.begin(), downsample.end()); |
| 61 | - downsample = downsample.mid(0, downsample.size()*transform->fraction); | |
| 60 | + downsample = downsample.mid(0, downsample.size()*fraction); | |
| 62 | 61 | } |
| 63 | 62 | |
| 64 | 63 | return downsample; |
| 65 | 64 | } |
| 66 | 65 | |
| 66 | +class DownsampleTrainingTransform : public Transform | |
| 67 | +{ | |
| 68 | + Q_OBJECT | |
| 69 | + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform STORED true) | |
| 70 | + Q_PROPERTY(int classes READ get_classes WRITE set_classes RESET reset_classes STORED false) | |
| 71 | + Q_PROPERTY(int instances READ get_instances WRITE set_instances RESET reset_instances STORED false) | |
| 72 | + Q_PROPERTY(float fraction READ get_fraction WRITE set_fraction RESET reset_fraction STORED false) | |
| 73 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 74 | + BR_PROPERTY(br::Transform*, transform, NULL) | |
| 75 | + BR_PROPERTY(int, classes, std::numeric_limits<int>::max()) | |
| 76 | + BR_PROPERTY(int, instances, std::numeric_limits<int>::max()) | |
| 77 | + BR_PROPERTY(float, fraction, 1) | |
| 78 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 79 | + | |
| 80 | + void project(const Template & src, Template & dst) const | |
| 81 | + { | |
| 82 | + transform->project(src,dst); | |
| 83 | + } | |
| 84 | + | |
| 85 | + | |
| 86 | + void train(const TemplateList &data) | |
| 87 | + { | |
| 88 | + if (!transform || !transform->trainable) | |
| 89 | + return; | |
| 90 | + | |
| 91 | + TemplateList downsampled = Downsample(data, classes, instances, fraction, inputVariable); | |
| 92 | + transform->train(downsampled); | |
| 93 | + } | |
| 94 | +}; | |
| 95 | +BR_REGISTER(Transform, DownsampleTrainingTransform) | |
| 96 | + | |
| 67 | 97 | /*! |
| 68 | 98 | * \ingroup transforms |
| 69 | 99 | * \brief Clones the transform so that it can be applied independently. |
| ... | ... | @@ -124,13 +154,10 @@ class IndependentTransform : public MetaTransform |
| 124 | 154 | while (transforms.size() < templatesList.size()) |
| 125 | 155 | transforms.append(transform->clone()); |
| 126 | 156 | |
| 127 | - for (int i=0; i<templatesList.size(); i++) | |
| 128 | - templatesList[i] = Downsample(templatesList[i], transforms[i]); | |
| 129 | - | |
| 130 | 157 | QFutureSynchronizer<void> futures; |
| 131 | 158 | for (int i=0; i<templatesList.size(); i++) |
| 132 | - futures.addFuture(QtConcurrent::run(_train, transforms[i], &templatesList[i])); | |
| 133 | - futures.waitForFinished(); | |
| 159 | + futures.addFuture(QtConcurrent::run(_train, transforms[i], &templatesList[i])); | |
| 160 | + futures.waitForFinished(); | |
| 134 | 161 | } |
| 135 | 162 | |
| 136 | 163 | void project(const Template &src, Template &dst) const | ... | ... |
openbr/plugins/keypoint.cpp
| ... | ... | @@ -15,6 +15,7 @@ |
| 15 | 15 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
| 16 | 16 | |
| 17 | 17 | #include <opencv2/features2d/features2d.hpp> |
| 18 | +#include <opencv2/objdetect/objdetect.hpp> | |
| 18 | 19 | #include <opencv2/nonfree/nonfree.hpp> |
| 19 | 20 | #include "openbr_internal.h" |
| 20 | 21 | #include "openbr/core/opencvutils.h" |
| ... | ... | @@ -178,6 +179,33 @@ BR_REGISTER(Transform, SIFTDescriptorTransform) |
| 178 | 179 | |
| 179 | 180 | /*! |
| 180 | 181 | * \ingroup transforms |
| 182 | + * \brief OpenCV HOGDescriptor wrapper | |
| 183 | + * \author Austin Blanton \cite imaus10 | |
| 184 | + */ | |
| 185 | +class HoGDescriptorTransform : public UntrainableTransform | |
| 186 | +{ | |
| 187 | + Q_OBJECT | |
| 188 | + | |
| 189 | + HOGDescriptor hog; | |
| 190 | + | |
| 191 | + void project(const Template &src, Template &dst) const | |
| 192 | + { | |
| 193 | + std::vector<float> descriptorVals; | |
| 194 | + std::vector<Point> locations; | |
| 195 | + Size winStride = Size(0,0); | |
| 196 | + Size padding = Size(0,0); | |
| 197 | + foreach (const Mat &rect, src) { | |
| 198 | + hog.compute(rect, descriptorVals, winStride, padding, locations); | |
| 199 | + Mat HoGFeats(descriptorVals, true); | |
| 200 | + dst += HoGFeats; | |
| 201 | + } | |
| 202 | + } | |
| 203 | +}; | |
| 204 | + | |
| 205 | +BR_REGISTER(Transform, HoGDescriptorTransform) | |
| 206 | + | |
| 207 | +/*! | |
| 208 | + * \ingroup transforms | |
| 181 | 209 | * \brief Add landmarks to the template in a grid layout |
| 182 | 210 | * \author Josh Klontz \cite jklontz |
| 183 | 211 | */ | ... | ... |
openbr/plugins/mask.cpp
| ... | ... | @@ -158,6 +158,9 @@ class LargestConvexAreaTransform : public UntrainableTransform |
| 158 | 158 | { |
| 159 | 159 | Q_OBJECT |
| 160 | 160 | |
| 161 | + Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false) | |
| 162 | + BR_PROPERTY(QString, outputVariable, "Label") | |
| 163 | + | |
| 161 | 164 | void project(const Template &src, Template &dst) const |
| 162 | 165 | { |
| 163 | 166 | std::vector< std::vector<Point> > contours; |
| ... | ... | @@ -171,7 +174,7 @@ class LargestConvexAreaTransform : public UntrainableTransform |
| 171 | 174 | if (area / hullArea > 0.98) |
| 172 | 175 | maxArea = std::max(maxArea, area); |
| 173 | 176 | } |
| 174 | - dst.file.set("Label", maxArea); | |
| 177 | + dst.file.set(outputVariable, maxArea); | |
| 175 | 178 | } |
| 176 | 179 | }; |
| 177 | 180 | ... | ... |
openbr/plugins/meta.cpp
| ... | ... | @@ -105,6 +105,24 @@ class PipeTransform : public CompositeTransform |
| 105 | 105 | transforms[i]->train(copy); |
| 106 | 106 | } |
| 107 | 107 | |
| 108 | + // if the transform is time varying, we can't project it in parallel | |
| 109 | + if (transforms[i]->timeVarying()) { | |
| 110 | + fprintf(stderr, "\n%s projecting...", qPrintable(transforms[i]->objectName())); | |
| 111 | + for (int j=0; j < singleItemLists.size();j++) | |
| 112 | + transforms[i]->projectUpdate(singleItemLists[j], singleItemLists[j]); | |
| 113 | + | |
| 114 | + // advance i since we already projected for this stage. | |
| 115 | + i++; | |
| 116 | + | |
| 117 | + // set up copy again | |
| 118 | + copy.clear(); | |
| 119 | + for (int j=0; j < singleItemLists.size(); j++) | |
| 120 | + copy.append(singleItemLists[j]); | |
| 121 | + | |
| 122 | + // the next stage might be trainable, so continue to evaluate it. | |
| 123 | + continue; | |
| 124 | + } | |
| 125 | + | |
| 108 | 126 | // We project through any subsequent untrainable transforms at once |
| 109 | 127 | // as a memory optimization in case any of these intermediate |
| 110 | 128 | // transforms allocate a lot of memory (like OpenTransform) |
| ... | ... | @@ -112,7 +130,8 @@ class PipeTransform : public CompositeTransform |
| 112 | 130 | // by that transform at once if we can avoid it. |
| 113 | 131 | int nextTrainableTransform = i+1; |
| 114 | 132 | while ((nextTrainableTransform < transforms.size()) && |
| 115 | - !transforms[nextTrainableTransform]->trainable) | |
| 133 | + !transforms[nextTrainableTransform]->trainable && | |
| 134 | + !transforms[nextTrainableTransform]->timeVarying()) | |
| 116 | 135 | nextTrainableTransform++; |
| 117 | 136 | |
| 118 | 137 | fprintf(stderr, " projecting..."); |
| ... | ... | @@ -129,26 +148,6 @@ class PipeTransform : public CompositeTransform |
| 129 | 148 | } |
| 130 | 149 | } |
| 131 | 150 | |
| 132 | - void backProject(const Template &dst, Template &src) const | |
| 133 | - { | |
| 134 | - // Backprojecting a time-varying transform is probably not going to work. | |
| 135 | - if (timeVarying()) qFatal("No backProject defined for time-varying transform"); | |
| 136 | - | |
| 137 | - src = dst; | |
| 138 | - // Reverse order in which transforms are processed | |
| 139 | - int length = transforms.length(); | |
| 140 | - for (int i=length-1; i>=0; i--) { | |
| 141 | - Transform *f = transforms.at(i); | |
| 142 | - try { | |
| 143 | - src >> *f; | |
| 144 | - } catch (...) { | |
| 145 | - qWarning("Exception triggered when processing %s with transform %s", qPrintable(dst.file.flat()), qPrintable(f->objectName())); | |
| 146 | - src = Template(src.file); | |
| 147 | - src.file.set("FTE", true); | |
| 148 | - } | |
| 149 | - } | |
| 150 | - } | |
| 151 | - | |
| 152 | 151 | void projectUpdate(const Template &src, Template &dst) |
| 153 | 152 | { |
| 154 | 153 | dst = src; |
| ... | ... | @@ -306,8 +305,6 @@ class ForkTransform : public CompositeTransform |
| 306 | 305 | futures.waitForFinished(); |
| 307 | 306 | } |
| 308 | 307 | |
| 309 | - void backProject(const Template &dst, Template &src) const {Transform::backProject(dst, src);} | |
| 310 | - | |
| 311 | 308 | // same as _project, but calls projectUpdate on sub-transforms |
| 312 | 309 | void projectupdate(const Template & src, Template & dst) |
| 313 | 310 | { |
| ... | ... | @@ -645,17 +642,28 @@ public: |
| 645 | 642 | QList<TemplateList> input_buffer; |
| 646 | 643 | input_buffer.reserve(src.size()); |
| 647 | 644 | |
| 645 | + QFutureSynchronizer<void> futures; | |
| 646 | + | |
| 648 | 647 | for (int i =0; i < src.size();i++) { |
| 649 | 648 | input_buffer.append(TemplateList()); |
| 650 | 649 | output_buffer.append(TemplateList()); |
| 651 | 650 | } |
| 652 | - | |
| 653 | - QFutureSynchronizer<void> futures; | |
| 651 | + QList<QFuture<void> > temp; | |
| 652 | + temp.reserve(src.size()); | |
| 654 | 653 | for (int i=0; i<src.size(); i++) { |
| 655 | 654 | input_buffer[i].append(src[i]); |
| 656 | - if (Globals->parallelism) futures.addFuture(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i])); | |
| 657 | - else _projectList( transform, &input_buffer[i], &output_buffer[i]); | |
| 655 | + | |
| 656 | + if (Globals->parallelism > 1) temp.append(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i])); | |
| 657 | + else _projectList(transform, &input_buffer[i], &output_buffer[i]); | |
| 658 | 658 | } |
| 659 | + // We add the futures in reverse order, since in Qt 5.1 at least the | |
| 660 | + // waiting thread will wait on them in the order added (which for uniform priority | |
| 661 | + // threads is the order of execution), and we want the waiting thread to go in the opposite order | |
| 662 | + // so that it can steal runnables and do something besides wait. | |
| 663 | + for (int i = temp.size() - 1; i >= 0; i--) { | |
| 664 | + futures.addFuture(temp[i]); | |
| 665 | + } | |
| 666 | + | |
| 659 | 667 | futures.waitForFinished(); |
| 660 | 668 | |
| 661 | 669 | for (int i=0; i<src.size(); i++) dst.append(output_buffer[i]); | ... | ... |
openbr/plugins/misc.cpp
| ... | ... | @@ -179,6 +179,10 @@ class FirstTransform : public UntrainableMetaTransform |
| 179 | 179 | |
| 180 | 180 | void project(const Template &src, Template &dst) const |
| 181 | 181 | { |
| 182 | + // AggregateFrames will leave the Template empty | |
| 183 | + // if it hasn't filled up the buffer | |
| 184 | + // so we gotta anticipate an empty Template | |
| 185 | + if (src.empty()) return; | |
| 182 | 186 | dst.file = src.file; |
| 183 | 187 | dst = src.m(); |
| 184 | 188 | } | ... | ... |
openbr/plugins/normalize.cpp
| ... | ... | @@ -97,6 +97,7 @@ class CenterTransform : public Transform |
| 97 | 97 | Q_OBJECT |
| 98 | 98 | Q_ENUMS(Method) |
| 99 | 99 | Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false) |
| 100 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 100 | 101 | |
| 101 | 102 | public: |
| 102 | 103 | /*!< */ |
| ... | ... | @@ -107,6 +108,7 @@ public: |
| 107 | 108 | |
| 108 | 109 | private: |
| 109 | 110 | BR_PROPERTY(Method, method, Mean) |
| 111 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 110 | 112 | |
| 111 | 113 | Mat a, b; // dst = (src - b) / a |
| 112 | 114 | |
| ... | ... | @@ -127,7 +129,7 @@ private: |
| 127 | 129 | Mat m; |
| 128 | 130 | OpenCVUtils::toMat(data.data()).convertTo(m, CV_64F); |
| 129 | 131 | |
| 130 | - const QList<int> labels = data.indexProperty("Subject"); | |
| 132 | + const QList<int> labels = data.indexProperty(inputVariable); | |
| 131 | 133 | const int dims = m.cols; |
| 132 | 134 | |
| 133 | 135 | vector<Mat> mv, av, bv; | ... | ... |
openbr/plugins/openbr_internal.h
| ... | ... | @@ -170,14 +170,8 @@ public: |
| 170 | 170 | virtual void project(const TemplateList &src, TemplateList &dst) const |
| 171 | 171 | { |
| 172 | 172 | if (timeVarying()) { |
| 173 | - if (!this->timeInvariantAlias) { | |
| 174 | - QMutexLocker lock(&aliasLock); | |
| 175 | - CompositeTransform * non_const = const_cast<CompositeTransform *>(this); | |
| 176 | - non_const->timeInvariantAlias = non_const->smartCopy(); | |
| 177 | - non_const->timeInvariantAlias->setParent(non_const); | |
| 178 | - lock.unlock(); | |
| 179 | - } | |
| 180 | - timeInvariantAlias->projectUpdate(src,dst); | |
| 173 | + CompositeTransform * non_const = const_cast<CompositeTransform *>(this); | |
| 174 | + non_const->projectUpdate(src,dst); | |
| 181 | 175 | return; |
| 182 | 176 | } |
| 183 | 177 | _project(src, dst); |
| ... | ... | @@ -225,10 +219,6 @@ public: |
| 225 | 219 | } |
| 226 | 220 | |
| 227 | 221 | output->file = this->file; |
| 228 | - output->classes = classes; | |
| 229 | - output->instances = instances; | |
| 230 | - output->fraction = fraction; | |
| 231 | - | |
| 232 | 222 | output->init(); |
| 233 | 223 | |
| 234 | 224 | return output; |
| ... | ... | @@ -237,13 +227,10 @@ public: |
| 237 | 227 | protected: |
| 238 | 228 | bool isTimeVarying; |
| 239 | 229 | |
| 240 | - mutable QMutex aliasLock; | |
| 241 | - Transform * timeInvariantAlias; | |
| 242 | - | |
| 243 | 230 | virtual void _project(const Template & src, Template & dst) const = 0; |
| 244 | 231 | virtual void _project(const TemplateList & src, TemplateList & dst) const = 0; |
| 245 | 232 | |
| 246 | - CompositeTransform() : TimeVaryingTransform(false) { timeInvariantAlias = NULL; } | |
| 233 | + CompositeTransform() : TimeVaryingTransform(false) {} | |
| 247 | 234 | }; |
| 248 | 235 | |
| 249 | 236 | } | ... | ... |
openbr/plugins/opticalflow.cpp
0 โ 100644
| 1 | +#include <opencv2/video/tracking.hpp> | |
| 2 | +#include "openbr_internal.h" | |
| 3 | +#include "openbr/core/opencvutils.h" | |
| 4 | + | |
| 5 | +using namespace cv; | |
| 6 | + | |
| 7 | +namespace br | |
| 8 | +{ | |
| 9 | + | |
| 10 | +/*! | |
| 11 | + * \ingroup transforms | |
| 12 | + * \brief Gets a one-channel dense optical flow from two images | |
| 13 | + * \author Austin Blanton \cite imaus10 | |
| 14 | + */ | |
| 15 | +class OpticalFlowTransform : public UntrainableMetaTransform | |
| 16 | +{ | |
| 17 | + Q_OBJECT | |
| 18 | + Q_PROPERTY(double pyr_scale READ get_pyr_scale WRITE set_pyr_scale RESET reset_pyr_scale STORED false) | |
| 19 | + Q_PROPERTY(int levels READ get_levels WRITE set_levels RESET reset_levels STORED false) | |
| 20 | + Q_PROPERTY(int winsize READ get_winsize WRITE set_winsize RESET reset_winsize STORED false) | |
| 21 | + Q_PROPERTY(int iterations READ get_iterations WRITE set_iterations RESET reset_iterations STORED false) | |
| 22 | + Q_PROPERTY(int poly_n READ get_poly_n WRITE set_poly_n RESET reset_poly_n STORED false) | |
| 23 | + Q_PROPERTY(double poly_sigma READ get_poly_sigma WRITE set_poly_sigma RESET reset_poly_sigma STORED false) | |
| 24 | + Q_PROPERTY(int flags READ get_flags WRITE set_flags RESET reset_flags STORED false) | |
| 25 | + // these defaults are optimized for KTH | |
| 26 | + BR_PROPERTY(double, pyr_scale, 0.1) | |
| 27 | + BR_PROPERTY(int, levels, 1) | |
| 28 | + BR_PROPERTY(int, winsize, 5) | |
| 29 | + BR_PROPERTY(int, iterations, 10) | |
| 30 | + BR_PROPERTY(int, poly_n, 7) | |
| 31 | + BR_PROPERTY(double, poly_sigma, 1.1) | |
| 32 | + BR_PROPERTY(int, flags, 0) | |
| 33 | + | |
| 34 | + void project(const Template &src, Template &dst) const | |
| 35 | + { | |
| 36 | + // get the two images put there by AggregateFrames | |
| 37 | + if (src.size() < 2) return; | |
| 38 | + Mat prevImg = src[0], nextImg = src[1], flow, flowOneCh; | |
| 39 | + if (src[0].channels() != 1) OpenCVUtils::cvtGray(src[0], prevImg); | |
| 40 | + if (src[1].channels() != 1) OpenCVUtils::cvtGray(src[1], nextImg); | |
| 41 | + calcOpticalFlowFarneback(prevImg, nextImg, flow, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags); | |
| 42 | + | |
| 43 | + // the result is two channels | |
| 44 | + std::vector<Mat> channels(2); | |
| 45 | + split(flow, channels); | |
| 46 | + magnitude(channels[0], channels[1], flowOneCh); | |
| 47 | + | |
| 48 | + dst += flowOneCh; | |
| 49 | + dst.file = src.file; | |
| 50 | + } | |
| 51 | +}; | |
| 52 | + | |
| 53 | +BR_REGISTER(Transform, OpticalFlowTransform) | |
| 54 | + | |
| 55 | +} // namespace br | |
| 56 | + | |
| 57 | +#include "opticalflow.moc" | ... | ... |
openbr/plugins/output.cpp
| ... | ... | @@ -146,8 +146,8 @@ class meltOutput : public MatrixOutput |
| 146 | 146 | QStringList lines; |
| 147 | 147 | if (file.baseName() != "terminal") lines.append(QString("Query,Target,Mask,Similarity%1").arg(keys)); |
| 148 | 148 | |
| 149 | - QList<QString> queryLabels = File::get<QString>(queryFiles, "Subject"); | |
| 150 | - QList<QString> targetLabels = File::get<QString>(targetFiles, "Subject"); | |
| 149 | + QList<QString> queryLabels = File::get<QString>(queryFiles, "Label"); | |
| 150 | + QList<QString> targetLabels = File::get<QString>(targetFiles, "Label"); | |
| 151 | 151 | |
| 152 | 152 | for (int i=0; i<queryFiles.size(); i++) { |
| 153 | 153 | for (int j=(selfSimilar ? i+1 : 0); j<targetFiles.size(); j++) { |
| ... | ... | @@ -300,7 +300,7 @@ class txtOutput : public MatrixOutput |
| 300 | 300 | if (file.isNull() || targetFiles.isEmpty() || queryFiles.isEmpty()) return; |
| 301 | 301 | QStringList lines; |
| 302 | 302 | foreach (const File &file, queryFiles) |
| 303 | - lines.append(file.name + " " + file.get<QString>("Subject")); | |
| 303 | + lines.append(file.name + " " + file.get<QString>("Label")); | |
| 304 | 304 | QtUtils::writeFile(file, lines); |
| 305 | 305 | } |
| 306 | 306 | }; |
| ... | ... | @@ -433,7 +433,7 @@ class rankOutput : public MatrixOutput |
| 433 | 433 | foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) { |
| 434 | 434 | if (Globals->crossValidate > 0 ? (targetFiles[pair.second].get<int>("Partition",-1) == queryFiles[i].get<int>("Partition",-1)) : true) { |
| 435 | 435 | if (QString(targetFiles[pair.second]) != QString(queryFiles[i])) { |
| 436 | - if (targetFiles[pair.second].get<QString>("Subject") == queryFiles[i].get<QString>("Subject")) { | |
| 436 | + if (targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) { | |
| 437 | 437 | ranks.append(rank); |
| 438 | 438 | positions.append(pair.second); |
| 439 | 439 | scores.append(pair.first); | ... | ... |
openbr/plugins/quality.cpp
| ... | ... | @@ -19,17 +19,20 @@ class ImpostorUniquenessMeasureTransform : public Transform |
| 19 | 19 | Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false) |
| 20 | 20 | Q_PROPERTY(double mean READ get_mean WRITE set_mean RESET reset_mean) |
| 21 | 21 | Q_PROPERTY(double stddev READ get_stddev WRITE set_stddev RESET reset_stddev) |
| 22 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 22 | 23 | BR_PROPERTY(br::Distance*, distance, Distance::make("Dist(L2)", this)) |
| 23 | 24 | BR_PROPERTY(double, mean, 0) |
| 24 | 25 | BR_PROPERTY(double, stddev, 1) |
| 26 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 27 | + | |
| 25 | 28 | TemplateList impostors; |
| 26 | 29 | |
| 27 | 30 | float calculateIUM(const Template &probe, const TemplateList &gallery) const |
| 28 | 31 | { |
| 29 | - const QString probeLabel = probe.file.get<QString>("Subject"); | |
| 32 | + const QString probeLabel = probe.file.get<QString>(inputVariable); | |
| 30 | 33 | TemplateList subset = gallery; |
| 31 | 34 | for (int j=subset.size()-1; j>=0; j--) |
| 32 | - if (subset[j].file.get<QString>("Subject") == probeLabel) | |
| 35 | + if (subset[j].file.get<QString>(inputVariable) == probeLabel) | |
| 33 | 36 | subset.removeAt(j); |
| 34 | 37 | |
| 35 | 38 | QList<float> scores = distance->compare(subset, probe); |
| ... | ... | @@ -151,6 +154,7 @@ class MatchProbabilityDistance : public Distance |
| 151 | 154 | Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false) |
| 152 | 155 | Q_PROPERTY(bool gaussian READ get_gaussian WRITE set_gaussian RESET reset_gaussian STORED false) |
| 153 | 156 | Q_PROPERTY(bool crossModality READ get_crossModality WRITE set_crossModality RESET reset_crossModality STORED false) |
| 157 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 154 | 158 | |
| 155 | 159 | MP mp; |
| 156 | 160 | |
| ... | ... | @@ -158,7 +162,7 @@ class MatchProbabilityDistance : public Distance |
| 158 | 162 | { |
| 159 | 163 | distance->train(src); |
| 160 | 164 | |
| 161 | - const QList<int> labels = src.indexProperty("Subject"); | |
| 165 | + const QList<int> labels = src.indexProperty(inputVariable); | |
| 162 | 166 | QScopedPointer<MatrixOutput> matrixOutput(MatrixOutput::make(FileList(src.size()), FileList(src.size()))); |
| 163 | 167 | distance->compare(src, src, matrixOutput.data()); |
| 164 | 168 | |
| ... | ... | @@ -201,6 +205,7 @@ protected: |
| 201 | 205 | BR_PROPERTY(br::Distance*, distance, make("Dist(L2)")) |
| 202 | 206 | BR_PROPERTY(bool, gaussian, true) |
| 203 | 207 | BR_PROPERTY(bool, crossModality, false) |
| 208 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 204 | 209 | }; |
| 205 | 210 | |
| 206 | 211 | BR_REGISTER(Distance, MatchProbabilityDistance) |
| ... | ... | @@ -217,10 +222,12 @@ class HeatMapDistance : public Distance |
| 217 | 222 | Q_PROPERTY(bool gaussian READ get_gaussian WRITE set_gaussian RESET reset_gaussian STORED false) |
| 218 | 223 | Q_PROPERTY(bool crossModality READ get_crossModality WRITE set_crossModality RESET reset_crossModality STORED false) |
| 219 | 224 | Q_PROPERTY(int step READ get_step WRITE set_step RESET reset_step STORED false) |
| 225 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 220 | 226 | BR_PROPERTY(br::Distance*, distance, make("Dist(L2)")) |
| 221 | 227 | BR_PROPERTY(bool, gaussian, true) |
| 222 | 228 | BR_PROPERTY(bool, crossModality, false) |
| 223 | 229 | BR_PROPERTY(int, step, 1) |
| 230 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 224 | 231 | |
| 225 | 232 | QList<MP> mp; |
| 226 | 233 | |
| ... | ... | @@ -228,7 +235,7 @@ class HeatMapDistance : public Distance |
| 228 | 235 | { |
| 229 | 236 | distance->train(src); |
| 230 | 237 | |
| 231 | - const QList<int> labels = src.indexProperty("Subject"); | |
| 238 | + const QList<int> labels = src.indexProperty(inputVariable); | |
| 232 | 239 | |
| 233 | 240 | QList<TemplateList> patches; |
| 234 | 241 | |
| ... | ... | @@ -307,14 +314,16 @@ class UnitDistance : public Distance |
| 307 | 314 | Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance) |
| 308 | 315 | Q_PROPERTY(float a READ get_a WRITE set_a RESET reset_a) |
| 309 | 316 | Q_PROPERTY(float b READ get_b WRITE set_b RESET reset_b) |
| 317 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 310 | 318 | BR_PROPERTY(br::Distance*, distance, make("Dist(L2)")) |
| 311 | 319 | BR_PROPERTY(float, a, 1) |
| 312 | 320 | BR_PROPERTY(float, b, 0) |
| 321 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 313 | 322 | |
| 314 | 323 | void train(const TemplateList &templates) |
| 315 | 324 | { |
| 316 | 325 | const TemplateList samples = templates.mid(0, 2000); |
| 317 | - const QList<int> sampleLabels = samples.indexProperty("Subject"); | |
| 326 | + const QList<int> sampleLabels = samples.indexProperty(inputVariable); | |
| 318 | 327 | QScopedPointer<MatrixOutput> matrixOutput(MatrixOutput::make(FileList(samples.size()), FileList(samples.size()))); |
| 319 | 328 | Distance::compare(samples, samples, matrixOutput.data()); |
| 320 | 329 | ... | ... |
openbr/plugins/quantize.cpp
| ... | ... | @@ -120,6 +120,10 @@ BR_REGISTER(Transform, HistEqQuantizationTransform) |
| 120 | 120 | class BayesianQuantizationDistance : public Distance |
| 121 | 121 | { |
| 122 | 122 | Q_OBJECT |
| 123 | + | |
| 124 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 125 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 126 | + | |
| 123 | 127 | QVector<float> loglikelihoods; |
| 124 | 128 | |
| 125 | 129 | static void computeLogLikelihood(const Mat &data, const QList<int> &labels, float *loglikelihood) |
| ... | ... | @@ -150,7 +154,7 @@ class BayesianQuantizationDistance : public Distance |
| 150 | 154 | qFatal("Expected sigle matrix templates of type CV_8UC1!"); |
| 151 | 155 | |
| 152 | 156 | const Mat data = OpenCVUtils::toMat(src.data()); |
| 153 | - const QList<int> templateLabels = src.indexProperty("Subject"); | |
| 157 | + const QList<int> templateLabels = src.indexProperty(inputVariable); | |
| 154 | 158 | loglikelihoods = QVector<float>(data.cols*256, 0); |
| 155 | 159 | |
| 156 | 160 | QFutureSynchronizer<void> futures; |
| ... | ... | @@ -343,9 +347,11 @@ class ProductQuantizationTransform : public Transform |
| 343 | 347 | Q_PROPERTY(int n READ get_n WRITE set_n RESET reset_n STORED false) |
| 344 | 348 | Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance STORED false) |
| 345 | 349 | Q_PROPERTY(bool bayesian READ get_bayesian WRITE set_bayesian RESET reset_bayesian STORED false) |
| 350 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 346 | 351 | BR_PROPERTY(int, n, 2) |
| 347 | 352 | BR_PROPERTY(br::Distance*, distance, Distance::make("L2", this)) |
| 348 | 353 | BR_PROPERTY(bool, bayesian, false) |
| 354 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 349 | 355 | |
| 350 | 356 | quint16 index; |
| 351 | 357 | QList<Mat> centers; |
| ... | ... | @@ -474,7 +480,7 @@ private: |
| 474 | 480 | Mat data = OpenCVUtils::toMat(src.data()); |
| 475 | 481 | const int step = getStep(data.cols); |
| 476 | 482 | |
| 477 | - const QList<int> labels = src.indexProperty("Subject"); | |
| 483 | + const QList<int> labels = src.indexProperty(inputVariable); | |
| 478 | 484 | |
| 479 | 485 | Mat &lut = ProductQuantizationLUTs[index]; |
| 480 | 486 | lut = Mat(getDims(data.cols), 256*(256+1)/2, CV_32FC1); | ... | ... |
openbr/plugins/quantize2.cpp
| ... | ... | @@ -19,6 +19,10 @@ namespace br |
| 19 | 19 | class BayesianQuantizationTransform : public Transform |
| 20 | 20 | { |
| 21 | 21 | Q_OBJECT |
| 22 | + | |
| 23 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 24 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 25 | + | |
| 22 | 26 | QVector<float> thresholds; |
| 23 | 27 | |
| 24 | 28 | static void computeThresholdsRecursive(const QVector<int> &cumulativeGenuines, const QVector<int> &cumulativeImpostors, |
| ... | ... | @@ -77,7 +81,7 @@ class BayesianQuantizationTransform : public Transform |
| 77 | 81 | void train(const TemplateList &src) |
| 78 | 82 | { |
| 79 | 83 | const Mat data = OpenCVUtils::toMat(src.data()); |
| 80 | - const QList<int> labels = src.indexProperty("Subject"); | |
| 84 | + const QList<int> labels = src.indexProperty(inputVariable); | |
| 81 | 85 | |
| 82 | 86 | thresholds = QVector<float>(256*data.cols); |
| 83 | 87 | ... | ... |
openbr/plugins/regions.cpp
| ... | ... | @@ -114,7 +114,7 @@ BR_REGISTER(Transform, CatTransform) |
| 114 | 114 | /*! |
| 115 | 115 | * \ingroup transforms |
| 116 | 116 | * \brief Concatenates all input matrices by row into a single matrix. |
| 117 | - * All matricies must have the same row counts. | |
| 117 | + * All matricies must have the same column counts. | |
| 118 | 118 | * \author Josh Klontz \cite jklontz |
| 119 | 119 | */ |
| 120 | 120 | class CatRowsTransform : public UntrainableMetaTransform |
| ... | ... | @@ -131,6 +131,31 @@ BR_REGISTER(Transform, CatRowsTransform) |
| 131 | 131 | |
| 132 | 132 | /*! |
| 133 | 133 | * \ingroup transforms |
| 134 | + * \brief Concatenates all input matrices by column into a single matrix. | |
| 135 | + * Use after a fork to concatenate two feature matrices by column. | |
| 136 | + * \author Austin Blanton \cite imaus10 | |
| 137 | + */ | |
| 138 | +class CatColsTransform : public UntrainableMetaTransform | |
| 139 | +{ | |
| 140 | + Q_OBJECT | |
| 141 | + | |
| 142 | + void project(const Template &src, Template &dst) const | |
| 143 | + { | |
| 144 | + if (src.empty()) return; | |
| 145 | + dst.file = src.file; | |
| 146 | + Mat m = OpenCVUtils::toMatByRow(src); | |
| 147 | + // right now this just splits src in half and joins them horizontally | |
| 148 | + // TODO: add partitions parameter for more than a single split | |
| 149 | + Mat first = m.rowRange(Range(0, m.rows/2)); | |
| 150 | + Mat second = m.rowRange(Range(m.rows/2, m.rows)); | |
| 151 | + hconcat(first, second, dst); | |
| 152 | + } | |
| 153 | +}; | |
| 154 | + | |
| 155 | +BR_REGISTER(Transform, CatColsTransform) | |
| 156 | + | |
| 157 | +/*! | |
| 158 | + * \ingroup transforms | |
| 134 | 159 | * \brief Reshape the each matrix to the specified number of rows. |
| 135 | 160 | * \author Josh Klontz \cite jklontz |
| 136 | 161 | */ | ... | ... |
openbr/plugins/stream.cpp
| ... | ... | @@ -31,8 +31,10 @@ public: |
| 31 | 31 | virtual ~SharedBuffer() {} |
| 32 | 32 | |
| 33 | 33 | virtual void addItem(FrameData * input)=0; |
| 34 | + virtual void reset()=0; | |
| 34 | 35 | |
| 35 | 36 | virtual FrameData * tryGetItem()=0; |
| 37 | + virtual int size()=0; | |
| 36 | 38 | }; |
| 37 | 39 | |
| 38 | 40 | // for n - 1 boundaries, multiple threads call addItem, the frames are |
| ... | ... | @@ -74,6 +76,21 @@ public: |
| 74 | 76 | return output; |
| 75 | 77 | } |
| 76 | 78 | |
| 79 | + virtual int size() | |
| 80 | + { | |
| 81 | + QMutexLocker lock(&bufferGuard); | |
| 82 | + return buffer.size(); | |
| 83 | + } | |
| 84 | + virtual void reset() | |
| 85 | + { | |
| 86 | + if (size() != 0) | |
| 87 | + qDebug("Sequencing buffer has non-zero size during reset!"); | |
| 88 | + | |
| 89 | + QMutexLocker lock(&bufferGuard); | |
| 90 | + next_target = 0; | |
| 91 | + } | |
| 92 | + | |
| 93 | + | |
| 77 | 94 | private: |
| 78 | 95 | QMutex bufferGuard; |
| 79 | 96 | int next_target; |
| ... | ... | @@ -95,6 +112,11 @@ public: |
| 95 | 112 | outputBuffer = &buffer2; |
| 96 | 113 | } |
| 97 | 114 | |
| 115 | + int size() | |
| 116 | + { | |
| 117 | + QReadLocker readLock(&bufferGuard); | |
| 118 | + return inputBuffer->size() + outputBuffer->size(); | |
| 119 | + } | |
| 98 | 120 | |
| 99 | 121 | // called from the producer thread |
| 100 | 122 | void addItem(FrameData * input) |
| ... | ... | @@ -133,6 +155,13 @@ public: |
| 133 | 155 | return output; |
| 134 | 156 | } |
| 135 | 157 | |
| 158 | + virtual void reset() | |
| 159 | + { | |
| 160 | + if (this->size() != 0) | |
| 161 | + qDebug("Shared buffer has non-zero size during reset!"); | |
| 162 | + } | |
| 163 | + | |
| 164 | + | |
| 136 | 165 | private: |
| 137 | 166 | // The read-write lock. The thread adding to this buffer can add |
| 138 | 167 | // to the current input buffer if it has a read lock. The thread |
| ... | ... | @@ -160,9 +189,8 @@ class DataSource |
| 160 | 189 | public: |
| 161 | 190 | DataSource(int maxFrames=500) |
| 162 | 191 | { |
| 192 | + // The sequence number of the last frame | |
| 163 | 193 | final_frame = -1; |
| 164 | - last_issued = -2; | |
| 165 | - last_received = -3; | |
| 166 | 194 | for (int i=0; i < maxFrames;i++) |
| 167 | 195 | { |
| 168 | 196 | allFrames.addItem(new FrameData()); |
| ... | ... | @@ -181,52 +209,67 @@ public: |
| 181 | 209 | } |
| 182 | 210 | |
| 183 | 211 | // non-blocking version of getFrame |
| 184 | - FrameData * tryGetFrame() | |
| 212 | + // Returns a NULL FrameData if too many frames are out, or the | |
| 213 | + // data source is broken. Sets last_frame to true iff the FrameData | |
| 214 | + // returned is the last valid frame, and the data source is now broken. | |
| 215 | + FrameData * tryGetFrame(bool & last_frame) | |
| 185 | 216 | { |
| 217 | + last_frame = false; | |
| 218 | + | |
| 219 | + if (is_broken) { | |
| 220 | + return NULL; | |
| 221 | + } | |
| 222 | + | |
| 223 | + // Try to get a FrameData from the pool, if we can't it means too many | |
| 224 | + // frames are already out, and we will return NULL to indicate failure | |
| 186 | 225 | FrameData * aFrame = allFrames.tryGetItem(); |
| 187 | 226 | if (aFrame == NULL) |
| 188 | 227 | return NULL; |
| 189 | 228 | |
| 190 | - aFrame->data.clear(); | |
| 191 | - aFrame->sequenceNumber = -1; | |
| 192 | - | |
| 229 | + // Try to actually read a frame, if this returns false the data source is broken | |
| 193 | 230 | bool res = getNext(*aFrame); |
| 194 | 231 | |
| 195 | - // The datasource broke. | |
| 196 | - if (!res) { | |
| 197 | - allFrames.addItem(aFrame); | |
| 198 | - | |
| 232 | + // The datasource broke, update final_frame | |
| 233 | + if (!res) | |
| 234 | + { | |
| 199 | 235 | QMutexLocker lock(&last_frame_update); |
| 200 | - // Did we already receive the last frame? | |
| 201 | - final_frame = last_issued; | |
| 236 | + final_frame = lookAhead.back()->sequenceNumber; | |
| 237 | + allFrames.addItem(aFrame); | |
| 238 | + } | |
| 239 | + else { | |
| 240 | + lookAhead.push_back(aFrame); | |
| 241 | + } | |
| 202 | 242 | |
| 203 | - // We got the last frame before the data source broke, | |
| 204 | - // better pulse lastReturned | |
| 205 | - if (final_frame == last_received) { | |
| 206 | - lastReturned.wakeAll(); | |
| 207 | - } | |
| 208 | - else if (final_frame < last_received) | |
| 209 | - std::cout << "Bad last frame " << final_frame << " but received " << last_received << std::endl; | |
| 243 | + // we will return the first frame on the lookAhead buffer | |
| 244 | + FrameData * rVal = lookAhead.first(); | |
| 245 | + lookAhead.pop_front(); | |
| 210 | 246 | |
| 211 | - return NULL; | |
| 247 | + // If this is the last frame, say so | |
| 248 | + if (rVal->sequenceNumber == final_frame) { | |
| 249 | + last_frame = true; | |
| 250 | + is_broken = true; | |
| 212 | 251 | } |
| 213 | - last_issued = aFrame->sequenceNumber; | |
| 214 | - return aFrame; | |
| 252 | + | |
| 253 | + return rVal; | |
| 215 | 254 | } |
| 216 | 255 | |
| 217 | - // Returns true if the frame returned was the last | |
| 256 | + // Return a frame to the pool, returns true if the frame returned was the last | |
| 218 | 257 | // frame issued, false otherwise |
| 219 | 258 | bool returnFrame(FrameData * inputFrame) |
| 220 | 259 | { |
| 260 | + int frameNumber = inputFrame->sequenceNumber; | |
| 261 | + | |
| 262 | + inputFrame->data.clear(); | |
| 263 | + inputFrame->sequenceNumber = -1; | |
| 221 | 264 | allFrames.addItem(inputFrame); |
| 222 | 265 | |
| 223 | 266 | bool rval = false; |
| 224 | 267 | |
| 225 | 268 | QMutexLocker lock(&last_frame_update); |
| 226 | - last_received = inputFrame->sequenceNumber; | |
| 227 | 269 | |
| 228 | - if (inputFrame->sequenceNumber == final_frame) { | |
| 270 | + if (frameNumber == final_frame) { | |
| 229 | 271 | // We just received the last frame, better pulse |
| 272 | + allReturned = true; | |
| 230 | 273 | lastReturned.wakeAll(); |
| 231 | 274 | rval = true; |
| 232 | 275 | } |
| ... | ... | @@ -234,23 +277,88 @@ public: |
| 234 | 277 | return rval; |
| 235 | 278 | } |
| 236 | 279 | |
| 237 | - void waitLast() | |
| 280 | + bool waitLast() | |
| 238 | 281 | { |
| 239 | 282 | QMutexLocker lock(&last_frame_update); |
| 240 | - lastReturned.wait(&last_frame_update); | |
| 283 | + | |
| 284 | + while (!allReturned) | |
| 285 | + { | |
| 286 | + // This would be a safer wait if we used a timeout, but | |
| 287 | + // theoretically that should never matter. | |
| 288 | + lastReturned.wait(&last_frame_update); | |
| 289 | + } | |
| 290 | + return true; | |
| 241 | 291 | } |
| 242 | 292 | |
| 243 | - virtual void close() = 0; | |
| 244 | - virtual bool open(Template & output, int start_index=0) = 0; | |
| 245 | - virtual bool isOpen() = 0; | |
| 293 | + bool open(Template & output, int start_index = 0) | |
| 294 | + { | |
| 295 | + is_broken = false; | |
| 296 | + allReturned = false; | |
| 297 | + | |
| 298 | + // The last frame isn't initialized yet | |
| 299 | + final_frame = -1; | |
| 300 | + // Start our sequence numbers from the input index | |
| 301 | + next_sequence_number = start_index; | |
| 302 | + | |
| 303 | + // Actually open the data source | |
| 304 | + bool open_res = concreteOpen(output); | |
| 305 | + | |
| 306 | + // We couldn't open the data source | |
| 307 | + if (!open_res) { | |
| 308 | + is_broken = true; | |
| 309 | + return false; | |
| 310 | + } | |
| 311 | + | |
| 312 | + // Try to get a frame from the global pool | |
| 313 | + FrameData * firstFrame = allFrames.tryGetItem(); | |
| 314 | + | |
| 315 | + // If this fails, things have gone pretty badly. | |
| 316 | + if (firstFrame == NULL) { | |
| 317 | + is_broken = true; | |
| 318 | + return false; | |
| 319 | + } | |
| 320 | + | |
| 321 | + // Read a frame from the video source | |
| 322 | + bool res = getNext(*firstFrame); | |
| 246 | 323 | |
| 324 | + // the data source broke already, we couldn't even get one frame | |
| 325 | + // from it even though it claimed to have opened successfully. | |
| 326 | + if (!res) { | |
| 327 | + is_broken = true; | |
| 328 | + return false; | |
| 329 | + } | |
| 330 | + | |
| 331 | + // We read one frame ahead of the last one returned, this allows | |
| 332 | + // us to know which frame is the final frame when we return it. | |
| 333 | + lookAhead.append(firstFrame); | |
| 334 | + return true; | |
| 335 | + } | |
| 336 | + | |
| 337 | + /* | |
| 338 | + * Pure virtual methods | |
| 339 | + */ | |
| 340 | + | |
| 341 | + // isOpen doesn't appear to particularly work when used on opencv | |
| 342 | + // VideoCaptures, so we don't use it for anything important. | |
| 343 | + virtual bool isOpen()=0; | |
| 344 | + // Called from open, open the data source specified by the input | |
| 345 | + // template, don't worry about setting any of the state variables | |
| 346 | + // set in open. | |
| 347 | + virtual bool concreteOpen(Template & output) = 0; | |
| 348 | + // Get the next frame from the data source, store the results in | |
| 349 | + // FrameData (including the actual frame and appropriate sequence | |
| 350 | + // number). | |
| 247 | 351 | virtual bool getNext(FrameData & input) = 0; |
| 352 | + // close the currently open data source. | |
| 353 | + virtual void close() = 0; | |
| 248 | 354 | |
| 355 | + int next_sequence_number; | |
| 249 | 356 | protected: |
| 250 | 357 | DoubleBuffer allFrames; |
| 251 | 358 | int final_frame; |
| 252 | - int last_issued; | |
| 253 | - int last_received; | |
| 359 | + bool is_broken; | |
| 360 | + bool allReturned; | |
| 361 | + QList<FrameData *> lookAhead; | |
| 254 | 362 | |
| 255 | 363 | QWaitCondition lastReturned; |
| 256 | 364 | QMutex last_frame_update; |
| ... | ... | @@ -262,14 +370,14 @@ class VideoDataSource : public DataSource |
| 262 | 370 | public: |
| 263 | 371 | VideoDataSource(int maxFrames) : DataSource(maxFrames) {} |
| 264 | 372 | |
| 265 | - bool open(Template &input, int start_index=0) | |
| 373 | + bool concreteOpen(Template &input) | |
| 266 | 374 | { |
| 267 | - final_frame = -1; | |
| 268 | - last_issued = -2; | |
| 269 | - last_received = -3; | |
| 270 | - | |
| 271 | - next_idx = start_index; | |
| 272 | 375 | basis = input; |
| 376 | + | |
| 377 | + // We can open either files (well actually this includes addresses of ip cameras | |
| 378 | + // through ffmpeg), or webcams. Webcam VideoCaptures are created through a separate | |
| 379 | + // overload of open that takes an integer, not a string. | |
| 380 | + // So, does this look like an integer? | |
| 273 | 381 | bool is_int = false; |
| 274 | 382 | int anInt = input.file.name.toInt(&is_int); |
| 275 | 383 | if (is_int) |
| ... | ... | @@ -287,7 +395,8 @@ public: |
| 287 | 395 | } else { |
| 288 | 396 | // Yes, we should specify absolute path: |
| 289 | 397 | // http://stackoverflow.com/questions/9396459/loading-a-video-in-opencv-in-python |
| 290 | - video.open(QFileInfo(input.file.name).absoluteFilePath().toStdString()); | |
| 398 | + QString fileName = (Globals->path.isEmpty() ? "" : Globals->path + "/") + input.file.name; | |
| 399 | + video.open(QFileInfo(fileName).absoluteFilePath().toStdString()); | |
| 291 | 400 | } |
| 292 | 401 | |
| 293 | 402 | return video.isOpened(); |
| ... | ... | @@ -302,29 +411,38 @@ public: |
| 302 | 411 | private: |
| 303 | 412 | bool getNext(FrameData & output) |
| 304 | 413 | { |
| 305 | - if (!isOpen()) | |
| 414 | + if (!isOpen()) { | |
| 415 | + qDebug("video source is not open"); | |
| 306 | 416 | return false; |
| 417 | + } | |
| 307 | 418 | |
| 308 | 419 | output.data.append(Template(basis.file)); |
| 309 | - output.data.last().append(cv::Mat()); | |
| 420 | + output.data.last().m() = cv::Mat(); | |
| 310 | 421 | |
| 311 | - output.sequenceNumber = next_idx; | |
| 312 | - next_idx++; | |
| 422 | + output.sequenceNumber = next_sequence_number; | |
| 423 | + next_sequence_number++; | |
| 313 | 424 | |
| 314 | - bool res = video.read(output.data.last().last()); | |
| 315 | - output.data.last().last() = output.data.last().last().clone(); | |
| 425 | + cv::Mat temp; | |
| 426 | + bool res = video.read(temp); | |
| 316 | 427 | |
| 317 | 428 | if (!res) { |
| 429 | + // The video capture broke, return false. | |
| 430 | + output.data.last().m() = cv::Mat(); | |
| 318 | 431 | close(); |
| 319 | 432 | return false; |
| 320 | 433 | } |
| 434 | + | |
| 435 | + // This clone is critical, if we don't do it then the matrix will | |
| 436 | + // be an alias of an internal buffer of the video source, leading | |
| 437 | + // to various problems later. | |
| 438 | + output.data.last().m() = temp.clone(); | |
| 439 | + | |
| 321 | 440 | output.data.last().file.set("FrameNumber", output.sequenceNumber); |
| 322 | 441 | return true; |
| 323 | 442 | } |
| 324 | 443 | |
| 325 | 444 | cv::VideoCapture video; |
| 326 | 445 | Template basis; |
| 327 | - int next_idx; | |
| 328 | 446 | }; |
| 329 | 447 | |
| 330 | 448 | // Given a template as input, return its matrices one by one on subsequent calls |
| ... | ... | @@ -334,21 +452,18 @@ class TemplateDataSource : public DataSource |
| 334 | 452 | public: |
| 335 | 453 | TemplateDataSource(int maxFrames) : DataSource(maxFrames) |
| 336 | 454 | { |
| 337 | - current_idx = INT_MAX; | |
| 455 | + current_matrix_idx = INT_MAX; | |
| 338 | 456 | data_ok = false; |
| 339 | 457 | } |
| 340 | - bool data_ok; | |
| 341 | 458 | |
| 342 | - bool open(Template &input, int start_index=0) | |
| 459 | + // To "open" it we just set appropriate indices, we assume that if this | |
| 460 | + // is an image, it is already loaded into memory. | |
| 461 | + bool concreteOpen(Template &input) | |
| 343 | 462 | { |
| 344 | 463 | basis = input; |
| 345 | - current_idx = 0; | |
| 346 | - next_sequence = start_index; | |
| 347 | - final_frame = -1; | |
| 348 | - last_issued = -2; | |
| 349 | - last_received = -3; | |
| 464 | + current_matrix_idx = 0; | |
| 350 | 465 | |
| 351 | - data_ok = current_idx < basis.size(); | |
| 466 | + data_ok = current_matrix_idx < basis.size(); | |
| 352 | 467 | return data_ok; |
| 353 | 468 | } |
| 354 | 469 | |
| ... | ... | @@ -358,39 +473,41 @@ public: |
| 358 | 473 | |
| 359 | 474 | void close() |
| 360 | 475 | { |
| 361 | - current_idx = INT_MAX; | |
| 476 | + current_matrix_idx = INT_MAX; | |
| 362 | 477 | basis.clear(); |
| 363 | 478 | } |
| 364 | 479 | |
| 365 | 480 | private: |
| 366 | 481 | bool getNext(FrameData & output) |
| 367 | 482 | { |
| 368 | - data_ok = current_idx < basis.size(); | |
| 483 | + data_ok = current_matrix_idx < basis.size(); | |
| 369 | 484 | if (!data_ok) |
| 370 | 485 | return false; |
| 371 | 486 | |
| 372 | - output.data.append(basis[current_idx]); | |
| 373 | - current_idx++; | |
| 487 | + output.data.append(basis[current_matrix_idx]); | |
| 488 | + current_matrix_idx++; | |
| 374 | 489 | |
| 375 | - output.sequenceNumber = next_sequence; | |
| 376 | - next_sequence++; | |
| 490 | + output.sequenceNumber = next_sequence_number; | |
| 491 | + next_sequence_number++; | |
| 377 | 492 | |
| 378 | 493 | output.data.last().file.set("FrameNumber", output.sequenceNumber); |
| 379 | 494 | return true; |
| 380 | 495 | } |
| 381 | 496 | |
| 382 | 497 | Template basis; |
| 383 | - int current_idx; | |
| 384 | - int next_sequence; | |
| 498 | + // Index of the next matrix to output from the template | |
| 499 | + int current_matrix_idx; | |
| 500 | + | |
| 501 | + // is current_matrix_idx in bounds? | |
| 502 | + bool data_ok; | |
| 385 | 503 | }; |
| 386 | 504 | |
| 387 | -// Given a template as input, create a VideoDataSource or a TemplateDataSource | |
| 388 | -// depending on whether or not it looks like the input template has already | |
| 389 | -// loaded frames into memory. | |
| 505 | +// Given a templatelist as input, create appropriate data source for each | |
| 506 | +// individual template | |
| 390 | 507 | class DataSourceManager : public DataSource |
| 391 | 508 | { |
| 392 | 509 | public: |
| 393 | - DataSourceManager() | |
| 510 | + DataSourceManager(int activeFrames=100) : DataSource(activeFrames) | |
| 394 | 511 | { |
| 395 | 512 | actualSource = NULL; |
| 396 | 513 | } |
| ... | ... | @@ -400,6 +517,11 @@ public: |
| 400 | 517 | close(); |
| 401 | 518 | } |
| 402 | 519 | |
| 520 | + int size() | |
| 521 | + { | |
| 522 | + return this->allFrames.size(); | |
| 523 | + } | |
| 524 | + | |
| 403 | 525 | void close() |
| 404 | 526 | { |
| 405 | 527 | if (actualSource) { |
| ... | ... | @@ -409,33 +531,40 @@ public: |
| 409 | 531 | } |
| 410 | 532 | } |
| 411 | 533 | |
| 534 | + // We are used through a call to open(TemplateList) | |
| 412 | 535 | bool open(TemplateList & input) |
| 413 | 536 | { |
| 414 | - currentIdx = 0; | |
| 537 | + // Set up variables specific to us | |
| 538 | + current_template_idx = 0; | |
| 415 | 539 | templates = input; |
| 416 | 540 | |
| 417 | - return open(templates[currentIdx]); | |
| 541 | + // Call datasourece::open on the first template to set up | |
| 542 | + // state variables | |
| 543 | + return DataSource::open(templates[current_template_idx]); | |
| 418 | 544 | } |
| 419 | 545 | |
| 420 | - bool open(Template & input, int start_index=0) | |
| 546 | + // Create an actual data source of appropriate type for this template | |
| 547 | + // (initially called via the call to DataSource::open, called later | |
| 548 | + // as we run out of frames on our templates). | |
| 549 | + bool concreteOpen(Template & input) | |
| 421 | 550 | { |
| 422 | 551 | close(); |
| 423 | - final_frame = -1; | |
| 424 | - last_issued = -2; | |
| 425 | - last_received = -3; | |
| 426 | - next_frame = start_index; | |
| 427 | 552 | |
| 553 | + bool open_res = false; | |
| 428 | 554 | // Input has no matrices? Its probably a video that hasn't been loaded yet |
| 429 | 555 | if (input.empty()) { |
| 430 | 556 | actualSource = new VideoDataSource(0); |
| 431 | - actualSource->open(input, next_frame); | |
| 557 | + open_res = actualSource->concreteOpen(input); | |
| 432 | 558 | } |
| 559 | + // If the input is not empty, we assume it is a set of frames already | |
| 560 | + // in memory. | |
| 433 | 561 | else { |
| 434 | - // create frame dealer | |
| 435 | 562 | actualSource = new TemplateDataSource(0); |
| 436 | - actualSource->open(input, next_frame); | |
| 563 | + open_res = actualSource->concreteOpen(input); | |
| 437 | 564 | } |
| 438 | - if (!isOpen()) { | |
| 565 | + | |
| 566 | + // The data source failed to open | |
| 567 | + if (!open_res) { | |
| 439 | 568 | delete actualSource; |
| 440 | 569 | actualSource = NULL; |
| 441 | 570 | return false; |
| ... | ... | @@ -446,30 +575,55 @@ public: |
| 446 | 575 | bool isOpen() { return !actualSource ? false : actualSource->isOpen(); } |
| 447 | 576 | |
| 448 | 577 | protected: |
| 449 | - int currentIdx; | |
| 450 | - int next_frame; | |
| 578 | + // Index of the template in the templatelist we are currently reading from | |
| 579 | + int current_template_idx; | |
| 580 | + | |
| 451 | 581 | TemplateList templates; |
| 452 | 582 | DataSource * actualSource; |
| 583 | + // Get the next frame, if we run out of frames on the current template | |
| 584 | + // move on to the next one. | |
| 453 | 585 | bool getNext(FrameData & output) |
| 454 | 586 | { |
| 455 | 587 | bool res = actualSource->getNext(output); |
| 588 | + output.sequenceNumber = next_sequence_number; | |
| 589 | + | |
| 590 | + // OK we got a frame | |
| 456 | 591 | if (res) { |
| 457 | - next_frame = output.sequenceNumber+1; | |
| 592 | + // Override the sequence number set by actualSource | |
| 593 | + output.data.last().file.set("FrameNumber", output.sequenceNumber); | |
| 594 | + next_sequence_number++; | |
| 595 | + if (output.data.last().last().empty()) | |
| 596 | + qDebug("broken matrix"); | |
| 458 | 597 | return true; |
| 459 | 598 | } |
| 460 | 599 | |
| 600 | + // We didn't get a frame, try to move on to the next template. | |
| 461 | 601 | while(!res) { |
| 462 | - currentIdx++; | |
| 602 | + output.data.clear(); | |
| 603 | + current_template_idx++; | |
| 463 | 604 | |
| 464 | - if (currentIdx >= templates.size()) | |
| 605 | + // No more templates? We're done | |
| 606 | + if (current_template_idx >= templates.size()) | |
| 465 | 607 | return false; |
| 466 | - bool open_res = open(templates[currentIdx], next_frame); | |
| 608 | + | |
| 609 | + // open the next data source | |
| 610 | + bool open_res = concreteOpen(templates[current_template_idx]); | |
| 611 | + // We couldn't open it, give up? We could maybe continue here | |
| 612 | + // but don't currently. | |
| 467 | 613 | if (!open_res) |
| 468 | 614 | return false; |
| 615 | + | |
| 616 | + // get a frame from the newly opened data source, if that fails | |
| 617 | + // we continue to open the next one. | |
| 469 | 618 | res = actualSource->getNext(output); |
| 470 | 619 | } |
| 620 | + // Finally, set the sequence number for the frame we actually return. | |
| 621 | + output.sequenceNumber = next_sequence_number++; | |
| 622 | + output.data.last().file.set("FrameNumber", output.sequenceNumber); | |
| 623 | + | |
| 624 | + if (output.data.last().last().empty()) | |
| 625 | + qDebug("broken matrix"); | |
| 471 | 626 | |
| 472 | - next_frame = output.sequenceNumber+1; | |
| 473 | 627 | return res; |
| 474 | 628 | } |
| 475 | 629 | |
| ... | ... | @@ -477,9 +631,14 @@ protected: |
| 477 | 631 | |
| 478 | 632 | class ProcessingStage; |
| 479 | 633 | |
| 480 | -class BasicLoop : public QRunnable | |
| 634 | +class BasicLoop : public QRunnable, public QFutureInterface<void> | |
| 481 | 635 | { |
| 482 | 636 | public: |
| 637 | + BasicLoop() | |
| 638 | + { | |
| 639 | + this->reportStarted(); | |
| 640 | + } | |
| 641 | + | |
| 483 | 642 | void run(); |
| 484 | 643 | |
| 485 | 644 | QList<ProcessingStage *> * stages; |
| ... | ... | @@ -506,12 +665,15 @@ public: |
| 506 | 665 | |
| 507 | 666 | virtual void reset()=0; |
| 508 | 667 | |
| 668 | + virtual void status()=0; | |
| 669 | + | |
| 509 | 670 | protected: |
| 510 | 671 | int thread_count; |
| 511 | 672 | |
| 512 | 673 | SharedBuffer * inputBuffer; |
| 513 | 674 | ProcessingStage * nextStage; |
| 514 | 675 | QList<ProcessingStage *> * stages; |
| 676 | + QThreadPool * threads; | |
| 515 | 677 | Transform * transform; |
| 516 | 678 | |
| 517 | 679 | }; |
| ... | ... | @@ -530,6 +692,7 @@ void BasicLoop::run() |
| 530 | 692 | current_idx++; |
| 531 | 693 | current_idx = current_idx % stages->size(); |
| 532 | 694 | } |
| 695 | + this->reportFinished(); | |
| 533 | 696 | } |
| 534 | 697 | |
| 535 | 698 | class MultiThreadStage : public ProcessingStage |
| ... | ... | @@ -537,7 +700,8 @@ class MultiThreadStage : public ProcessingStage |
| 537 | 700 | public: |
| 538 | 701 | MultiThreadStage(int _input) : ProcessingStage(_input) {} |
| 539 | 702 | |
| 540 | - | |
| 703 | + // Not much to worry about here, we will project the input | |
| 704 | + // and try to continue to the next stage. | |
| 541 | 705 | FrameData * run(FrameData * input, bool & should_continue) |
| 542 | 706 | { |
| 543 | 707 | if (input == NULL) { |
| ... | ... | @@ -551,7 +715,8 @@ public: |
| 551 | 715 | return input; |
| 552 | 716 | } |
| 553 | 717 | |
| 554 | - // Called from a different thread than run | |
| 718 | + // Called from a different thread than run. Nothing to worry about | |
| 719 | + // we offer no restrictions on when loops may enter this stage. | |
| 555 | 720 | virtual bool tryAcquireNextStage(FrameData *& input) |
| 556 | 721 | { |
| 557 | 722 | (void) input; |
| ... | ... | @@ -562,9 +727,11 @@ public: |
| 562 | 727 | { |
| 563 | 728 | // nothing to do. |
| 564 | 729 | } |
| 730 | + void status(){ | |
| 731 | + qDebug("multi thread stage %d, nothing to worry about", this->stage_id); | |
| 732 | + } | |
| 565 | 733 | }; |
| 566 | 734 | |
| 567 | - | |
| 568 | 735 | class SingleThreadStage : public ProcessingStage |
| 569 | 736 | { |
| 570 | 737 | public: |
| ... | ... | @@ -572,13 +739,18 @@ public: |
| 572 | 739 | { |
| 573 | 740 | currentStatus = STOPPING; |
| 574 | 741 | next_target = 0; |
| 742 | + // If the previous stage is single-threaded, queued inputs | |
| 743 | + // are stored in a double buffer | |
| 575 | 744 | if (input_variance) { |
| 576 | 745 | this->inputBuffer = new DoubleBuffer(); |
| 577 | 746 | } |
| 747 | + // If it's multi-threaded we need to put the inputs back in order | |
| 748 | + // before we can use them, so we use a sequencing buffer. | |
| 578 | 749 | else { |
| 579 | 750 | this->inputBuffer = new SequencingBuffer(); |
| 580 | 751 | } |
| 581 | 752 | } |
| 753 | + | |
| 582 | 754 | ~SingleThreadStage() |
| 583 | 755 | { |
| 584 | 756 | delete inputBuffer; |
| ... | ... | @@ -589,6 +761,7 @@ public: |
| 589 | 761 | QWriteLocker writeLock(&statusLock); |
| 590 | 762 | currentStatus = STOPPING; |
| 591 | 763 | next_target = 0; |
| 764 | + inputBuffer->reset(); | |
| 592 | 765 | } |
| 593 | 766 | |
| 594 | 767 | |
| ... | ... | @@ -627,18 +800,26 @@ public: |
| 627 | 800 | lock.unlock(); |
| 628 | 801 | |
| 629 | 802 | if (newItem) |
| 630 | - { | |
| 631 | - BasicLoop * next = new BasicLoop(); | |
| 632 | - next->stages = stages; | |
| 633 | - next->start_idx = this->stage_id; | |
| 634 | - next->startItem = newItem; | |
| 635 | - | |
| 636 | - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); | |
| 637 | - } | |
| 803 | + startThread(newItem); | |
| 638 | 804 | |
| 639 | 805 | return input; |
| 640 | 806 | } |
| 641 | 807 | |
| 808 | + void startThread(br::FrameData * newItem) | |
| 809 | + { | |
| 810 | + BasicLoop * next = new BasicLoop(); | |
| 811 | + next->stages = stages; | |
| 812 | + next->start_idx = this->stage_id; | |
| 813 | + next->startItem = newItem; | |
| 814 | + | |
| 815 | + // We start threads with priority equal to their stage id | |
| 816 | + // This is intended to ensure progression, we do queued late stage | |
| 817 | + // jobs before queued early stage jobs, and so tend to finish frames | |
| 818 | + // rather than go stage by stage. In Qt 5.1, priorities are priorities | |
| 819 | + // so we use the stage_id directly. | |
| 820 | + this->threads->start(next, stage_id); | |
| 821 | + } | |
| 822 | + | |
| 642 | 823 | |
| 643 | 824 | // Calledfrom a different thread than run. |
| 644 | 825 | bool tryAcquireNextStage(FrameData *& input) |
| ... | ... | @@ -671,79 +852,109 @@ public: |
| 671 | 852 | |
| 672 | 853 | return true; |
| 673 | 854 | } |
| 855 | + | |
| 856 | + void status(){ | |
| 857 | + qDebug("single thread stage %d, status starting? %d, next %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->inputBuffer->size()); | |
| 858 | + } | |
| 859 | + | |
| 674 | 860 | }; |
| 675 | 861 | |
| 676 | -// No input buffer, instead we draw templates from some data source | |
| 677 | -// Will be operated by the main thread for the stream | |
| 862 | +// This stage reads new frames from the data source. | |
| 678 | 863 | class FirstStage : public SingleThreadStage |
| 679 | 864 | { |
| 680 | 865 | public: |
| 681 | - FirstStage() : SingleThreadStage(true) {} | |
| 866 | + FirstStage(int activeFrames = 100) : SingleThreadStage(true), dataSource(activeFrames){ } | |
| 682 | 867 | |
| 683 | 868 | DataSourceManager dataSource; |
| 684 | 869 | |
| 870 | + void reset() | |
| 871 | + { | |
| 872 | + dataSource.close(); | |
| 873 | + SingleThreadStage::reset(); | |
| 874 | + } | |
| 875 | + | |
| 685 | 876 | FrameData * run(FrameData * input, bool & should_continue) |
| 686 | 877 | { |
| 687 | - // Is there anything on our input buffer? If so we should start a thread with that. | |
| 688 | - QWriteLocker lock(&statusLock); | |
| 689 | - input = dataSource.tryGetFrame(); | |
| 690 | - // Datasource broke? | |
| 691 | - if (!input) | |
| 692 | - { | |
| 693 | - currentStatus = STOPPING; | |
| 694 | - should_continue = false; | |
| 695 | - return NULL; | |
| 696 | - } | |
| 697 | - lock.unlock(); | |
| 878 | + if (input == NULL) | |
| 879 | + qFatal("NULL frame in input stage"); | |
| 698 | 880 | |
| 881 | + // Can we enter the next stage? | |
| 699 | 882 | should_continue = nextStage->tryAcquireNextStage(input); |
| 700 | 883 | |
| 701 | - BasicLoop * next = new BasicLoop(); | |
| 702 | - next->stages = stages; | |
| 703 | - next->start_idx = this->stage_id; | |
| 704 | - next->startItem = NULL; | |
| 884 | + // Try to get a frame from the datasource, we keep working on | |
| 885 | + // the frame we have, but we will queue another job for the next | |
| 886 | + // frame if a frame is currently available. | |
| 887 | + QWriteLocker lock(&statusLock); | |
| 888 | + bool last_frame = false; | |
| 889 | + FrameData * newFrame = dataSource.tryGetFrame(last_frame); | |
| 705 | 890 | |
| 706 | - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); | |
| 891 | + // Were we able to get a frame? | |
| 892 | + if (newFrame) startThread(newFrame); | |
| 893 | + // If not this stage will enter a stopped state. | |
| 894 | + else { | |
| 895 | + currentStatus = STOPPING; | |
| 896 | + } | |
| 897 | + | |
| 898 | + lock.unlock(); | |
| 707 | 899 | |
| 708 | 900 | return input; |
| 709 | 901 | } |
| 710 | 902 | |
| 711 | - // Calledfrom a different thread than run. | |
| 903 | + // The last stage, trying to access the first stage | |
| 712 | 904 | bool tryAcquireNextStage(FrameData *& input) |
| 713 | 905 | { |
| 906 | + // Return the frame, was it the last one? | |
| 714 | 907 | bool was_last = dataSource.returnFrame(input); |
| 715 | 908 | input = NULL; |
| 909 | + | |
| 910 | + // OK we won't continue. | |
| 716 | 911 | if (was_last) { |
| 717 | 912 | return false; |
| 718 | 913 | } |
| 719 | 914 | |
| 720 | - if (!dataSource.isOpen()) | |
| 721 | - return false; | |
| 722 | - | |
| 723 | 915 | QReadLocker lock(&statusLock); |
| 724 | - // Thread is already running, we should just return | |
| 916 | + // If the first stage is already active we will just end. | |
| 725 | 917 | if (currentStatus == STARTING) |
| 726 | 918 | { |
| 727 | 919 | return false; |
| 728 | 920 | } |
| 729 | - // Have to change to a write lock to modify currentStatus | |
| 921 | + | |
| 922 | + // Otherwise we will try to continue, but to do so we have to | |
| 923 | + // escalate the lock, and sadly there is no way to do so without | |
| 924 | + // releasing the read-mode lock, and getting a new write-mode lock. | |
| 730 | 925 | lock.unlock(); |
| 731 | 926 | |
| 732 | 927 | QWriteLocker writeLock(&statusLock); |
| 733 | - // But someone else might have started a thread in the meantime | |
| 928 | + // currentStatus might have changed in the gap between releasing the read | |
| 929 | + // lock and getting the write lock. | |
| 734 | 930 | if (currentStatus == STARTING) |
| 735 | 931 | { |
| 736 | 932 | return false; |
| 737 | 933 | } |
| 738 | - // Ok we'll start a thread | |
| 934 | + | |
| 935 | + bool last_frame = false; | |
| 936 | + // Try to get a frame from the data source, if we get one we will | |
| 937 | + // continue to the first stage. | |
| 938 | + input = dataSource.tryGetFrame(last_frame); | |
| 939 | + | |
| 940 | + if (!input) { | |
| 941 | + return false; | |
| 942 | + } | |
| 943 | + | |
| 739 | 944 | currentStatus = STARTING; |
| 740 | 945 | |
| 741 | - // We always start a readstage thread with null input, so nothing to do here | |
| 742 | 946 | return true; |
| 743 | 947 | } |
| 744 | 948 | |
| 949 | + void status(){ | |
| 950 | + qDebug("Read stage %d, status starting? %d, next frame %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->dataSource.size()); | |
| 951 | + } | |
| 952 | + | |
| 953 | + | |
| 745 | 954 | }; |
| 746 | 955 | |
| 956 | +// Appened to the end of a Stream's transform sequence. Collects the output | |
| 957 | +// from each frame on a single templatelist | |
| 747 | 958 | class LastStage : public SingleThreadStage |
| 748 | 959 | { |
| 749 | 960 | public: |
| ... | ... | @@ -756,6 +967,7 @@ public: |
| 756 | 967 | private: |
| 757 | 968 | TemplateList collectedOutput; |
| 758 | 969 | public: |
| 970 | + | |
| 759 | 971 | void reset() |
| 760 | 972 | { |
| 761 | 973 | collectedOutput.clear(); |
| ... | ... | @@ -774,11 +986,14 @@ public: |
| 774 | 986 | } |
| 775 | 987 | next_target = input->sequenceNumber + 1; |
| 776 | 988 | |
| 989 | + // add the item to our output buffer | |
| 777 | 990 | collectedOutput.append(input->data); |
| 778 | 991 | |
| 992 | + // Can we enter the read stage? | |
| 779 | 993 | should_continue = nextStage->tryAcquireNextStage(input); |
| 780 | 994 | |
| 781 | - // Is there anything on our input buffer? If so we should start a thread with that. | |
| 995 | + // Is there anything on our input buffer? If so we should start a thread | |
| 996 | + // in this stage to process that frame. | |
| 782 | 997 | QWriteLocker lock(&statusLock); |
| 783 | 998 | FrameData * newItem = inputBuffer->tryGetItem(); |
| 784 | 999 | if (!newItem) |
| ... | ... | @@ -788,23 +1003,25 @@ public: |
| 788 | 1003 | lock.unlock(); |
| 789 | 1004 | |
| 790 | 1005 | if (newItem) |
| 791 | - { | |
| 792 | - BasicLoop * next = new BasicLoop(); | |
| 793 | - next->stages = stages; | |
| 794 | - next->start_idx = this->stage_id; | |
| 795 | - next->startItem = newItem; | |
| 796 | - | |
| 797 | - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); | |
| 798 | - } | |
| 1006 | + startThread(newItem); | |
| 799 | 1007 | |
| 800 | 1008 | return input; |
| 801 | 1009 | } |
| 1010 | + | |
| 1011 | + void status(){ | |
| 1012 | + qDebug("Collection stage %d, status starting? %d, next %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->inputBuffer->size()); | |
| 1013 | + } | |
| 1014 | + | |
| 802 | 1015 | }; |
| 803 | 1016 | |
| 1017 | + | |
| 804 | 1018 | class StreamTransform : public CompositeTransform |
| 805 | 1019 | { |
| 806 | 1020 | Q_OBJECT |
| 807 | 1021 | public: |
| 1022 | + Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames) | |
| 1023 | + BR_PROPERTY(int, activeFrames, 100) | |
| 1024 | + | |
| 808 | 1025 | void train(const TemplateList & data) |
| 809 | 1026 | { |
| 810 | 1027 | foreach(Transform * transform, transforms) { |
| ... | ... | @@ -826,7 +1043,8 @@ public: |
| 826 | 1043 | qFatal("whatever"); |
| 827 | 1044 | } |
| 828 | 1045 | |
| 829 | - // start processing | |
| 1046 | + // start processing, consider all templates in src a continuous | |
| 1047 | + // 'video' | |
| 830 | 1048 | void projectUpdate(const TemplateList & src, TemplateList & dst) |
| 831 | 1049 | { |
| 832 | 1050 | dst = src; |
| ... | ... | @@ -834,21 +1052,27 @@ public: |
| 834 | 1052 | bool res = readStage->dataSource.open(dst); |
| 835 | 1053 | if (!res) return; |
| 836 | 1054 | |
| 837 | - QThreadPool::globalInstance()->releaseThread(); | |
| 1055 | + // Start the first thread in the stream. | |
| 1056 | + QWriteLocker lock(&readStage->statusLock); | |
| 838 | 1057 | readStage->currentStatus = SingleThreadStage::STARTING; |
| 839 | 1058 | |
| 840 | - BasicLoop loop; | |
| 841 | - loop.stages = &this->processingStages; | |
| 842 | - loop.start_idx = 0; | |
| 843 | - loop.startItem = NULL; | |
| 844 | - loop.setAutoDelete(false); | |
| 1059 | + // We have to get a frame before starting the thread | |
| 1060 | + bool last_frame = false; | |
| 1061 | + FrameData * firstFrame = readStage->dataSource.tryGetFrame(last_frame); | |
| 1062 | + if (firstFrame == NULL) | |
| 1063 | + qFatal("Failed to read first frame of video"); | |
| 845 | 1064 | |
| 846 | - QThreadPool::globalInstance()->start(&loop, processingStages.size() - processingStages[0]->stage_id); | |
| 1065 | + readStage->startThread(firstFrame); | |
| 1066 | + lock.unlock(); | |
| 847 | 1067 | |
| 848 | - // Wait for the end. | |
| 849 | - readStage->dataSource.waitLast(); | |
| 850 | - QThreadPool::globalInstance()->reserveThread(); | |
| 1068 | + // Wait for the stream to process the last frame available from | |
| 1069 | + // the data source. | |
| 1070 | + bool wait_res = false; | |
| 1071 | + wait_res = readStage->dataSource.waitLast(); | |
| 851 | 1072 | |
| 1073 | + // Now that there are no more incoming frames, call finalize | |
| 1074 | + // on each transform in turn to collect any last templates | |
| 1075 | + // they wish to issue. | |
| 852 | 1076 | TemplateList final_output; |
| 853 | 1077 | |
| 854 | 1078 | // Push finalize through the stages |
| ... | ... | @@ -864,7 +1088,8 @@ public: |
| 864 | 1088 | final_output.append(output_set); |
| 865 | 1089 | } |
| 866 | 1090 | |
| 867 | - // dst is set to all output received by the final stage | |
| 1091 | + // dst is set to all output received by the final stage, along | |
| 1092 | + // with anything output via the calls to finalize. | |
| 868 | 1093 | dst = collectionStage->getOutput(); |
| 869 | 1094 | dst.append(final_output); |
| 870 | 1095 | |
| ... | ... | @@ -876,7 +1101,8 @@ public: |
| 876 | 1101 | virtual void finalize(TemplateList & output) |
| 877 | 1102 | { |
| 878 | 1103 | (void) output; |
| 879 | - // Not handling this yet -cao | |
| 1104 | + // Nothing in particular to do here, stream calls finalize | |
| 1105 | + // on all child transforms as part of projectUpdate | |
| 880 | 1106 | } |
| 881 | 1107 | |
| 882 | 1108 | // Create and link stages |
| ... | ... | @@ -884,26 +1110,49 @@ public: |
| 884 | 1110 | { |
| 885 | 1111 | if (transforms.isEmpty()) return; |
| 886 | 1112 | |
| 1113 | + // call CompositeTransform::init so that trainable is set | |
| 1114 | + // correctly. | |
| 1115 | + CompositeTransform::init(); | |
| 1116 | + | |
| 1117 | + // We share a thread pool across streams attached to the same | |
| 1118 | + // parent tranform, retrieve or create a thread pool based | |
| 1119 | + // on our parent transform. | |
| 1120 | + QMutexLocker poolLock(&poolsAccess); | |
| 1121 | + QHash<QObject *, QThreadPool *>::Iterator it; | |
| 1122 | + if (!pools.contains(this->parent())) { | |
| 1123 | + it = pools.insert(this->parent(), new QThreadPool(this)); | |
| 1124 | + it.value()->setMaxThreadCount(Globals->parallelism); | |
| 1125 | + } | |
| 1126 | + else it = pools.find(this->parent()); | |
| 1127 | + threads = it.value(); | |
| 1128 | + poolLock.unlock(); | |
| 1129 | + | |
| 1130 | + // Are our children time varying or not? This decides whether | |
| 1131 | + // we run them in single threaded or multi threaded stages | |
| 887 | 1132 | stage_variance.reserve(transforms.size()); |
| 888 | 1133 | foreach (const br::Transform *transform, transforms) { |
| 889 | 1134 | stage_variance.append(transform->timeVarying()); |
| 890 | 1135 | } |
| 891 | 1136 | |
| 892 | - readStage = new FirstStage(); | |
| 1137 | + // Additionally, we have a separate stage responsible for reading | |
| 1138 | + // frames from the data source | |
| 1139 | + readStage = new FirstStage(activeFrames); | |
| 893 | 1140 | |
| 894 | 1141 | processingStages.push_back(readStage); |
| 895 | 1142 | readStage->stage_id = 0; |
| 896 | 1143 | readStage->stages = &this->processingStages; |
| 1144 | + readStage->threads = this->threads; | |
| 897 | 1145 | |
| 1146 | + // Initialize and link a processing stage for each of our child | |
| 1147 | + // transforms. | |
| 898 | 1148 | int next_stage_id = 1; |
| 899 | - | |
| 900 | 1149 | bool prev_stage_variance = true; |
| 901 | 1150 | for (int i =0; i < transforms.size(); i++) |
| 902 | 1151 | { |
| 903 | 1152 | if (stage_variance[i]) |
| 904 | - { | |
| 1153 | + // Whether or not the previous stage is multi-threaded controls | |
| 1154 | + // the type of input buffer we need in a single threaded stage. | |
| 905 | 1155 | processingStages.append(new SingleThreadStage(prev_stage_variance)); |
| 906 | - } | |
| 907 | 1156 | else |
| 908 | 1157 | processingStages.append(new MultiThreadStage(Globals->parallelism)); |
| 909 | 1158 | |
| ... | ... | @@ -914,24 +1163,31 @@ public: |
| 914 | 1163 | processingStages[i]->nextStage = processingStages[i+1]; |
| 915 | 1164 | |
| 916 | 1165 | processingStages.last()->stages = &this->processingStages; |
| 1166 | + processingStages.last()->threads = this->threads; | |
| 917 | 1167 | |
| 918 | 1168 | processingStages.last()->transform = transforms[i]; |
| 919 | 1169 | prev_stage_variance = stage_variance[i]; |
| 920 | 1170 | } |
| 921 | 1171 | |
| 1172 | + // We also have the last stage, which just puts the output of the | |
| 1173 | + // previous stages on a template list. | |
| 922 | 1174 | collectionStage = new LastStage(prev_stage_variance); |
| 923 | 1175 | processingStages.append(collectionStage); |
| 924 | 1176 | collectionStage->stage_id = next_stage_id; |
| 925 | 1177 | collectionStage->stages = &this->processingStages; |
| 1178 | + collectionStage->threads = this->threads; | |
| 926 | 1179 | |
| 1180 | + // the last transform stage points to collection stage | |
| 927 | 1181 | processingStages[processingStages.size() - 2]->nextStage = collectionStage; |
| 928 | 1182 | |
| 929 | - // It's a ring buffer, get it? | |
| 1183 | + // And the collection stage points to the read stage, because this is | |
| 1184 | + // a ring buffer. | |
| 930 | 1185 | collectionStage->nextStage = readStage; |
| 931 | 1186 | } |
| 932 | 1187 | |
| 933 | 1188 | ~StreamTransform() |
| 934 | 1189 | { |
| 1190 | + // Delete all the stages | |
| 935 | 1191 | for (int i = 0; i < processingStages.size(); i++) { |
| 936 | 1192 | delete processingStages[i]; |
| 937 | 1193 | } |
| ... | ... | @@ -945,6 +1201,25 @@ protected: |
| 945 | 1201 | |
| 946 | 1202 | QList<ProcessingStage *> processingStages; |
| 947 | 1203 | |
| 1204 | + // This is a map from parent transforms (of Streams) to thread pools. Rather | |
| 1205 | + // than starting threads on the global thread pool, Stream uses separate thread pools | |
| 1206 | + // keyed on their parent transform. This is necessary because stream's project starts | |
| 1207 | + // threads, then enters an indefinite wait for them to finish. Since we are starting | |
| 1208 | + // threads using thread pools, threads themselves are a limited resource. Therefore, | |
| 1209 | + // the type of hold and wait done by stream project can lead to deadlock unless | |
| 1210 | + // resources are ordered in such a way that a circular wait will not occur. The points | |
| 1211 | + // of this hash is to introduce a resource ordering (on threads) that mirrors the structure | |
| 1212 | + // of the algorithm. So, as long as the structure of the algorithm is a DAG, the wait done | |
| 1213 | + // by stream project will not be circular, since every thread in stream project is waiting | |
| 1214 | + // for threads at a lower level to do the work. | |
| 1215 | + // This issue doesn't come up in distribute, since a thread waiting on a QFutureSynchronizer | |
| 1216 | + // will steal work from those jobs, so in that sense distribute isn't doing a hold and wait. | |
| 1217 | + // Waiting for a QFutureSynchronzier isn't really possible here since stream runs an indeteriminate | |
| 1218 | + // number of jobs. | |
| 1219 | + static QHash<QObject *, QThreadPool *> pools; | |
| 1220 | + static QMutex poolsAccess; | |
| 1221 | + QThreadPool * threads; | |
| 1222 | + | |
| 948 | 1223 | void _project(const Template &src, Template &dst) const |
| 949 | 1224 | { |
| 950 | 1225 | (void) src; (void) dst; |
| ... | ... | @@ -957,6 +1232,9 @@ protected: |
| 957 | 1232 | } |
| 958 | 1233 | }; |
| 959 | 1234 | |
| 1235 | +QHash<QObject *, QThreadPool *> StreamTransform::pools; | |
| 1236 | +QMutex StreamTransform::poolsAccess; | |
| 1237 | + | |
| 960 | 1238 | BR_REGISTER(Transform, StreamTransform) |
| 961 | 1239 | |
| 962 | 1240 | ... | ... |
openbr/plugins/svm.cpp
| ... | ... | @@ -101,6 +101,8 @@ class SVMTransform : public Transform |
| 101 | 101 | Q_PROPERTY(Type type READ get_type WRITE set_type RESET reset_type STORED false) |
| 102 | 102 | Q_PROPERTY(float C READ get_C WRITE set_C RESET reset_C STORED false) |
| 103 | 103 | Q_PROPERTY(float gamma READ get_gamma WRITE set_gamma RESET reset_gamma STORED false) |
| 104 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 105 | + Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false) | |
| 104 | 106 | |
| 105 | 107 | public: |
| 106 | 108 | enum Kernel { Linear = CvSVM::LINEAR, |
| ... | ... | @@ -119,6 +121,9 @@ private: |
| 119 | 121 | BR_PROPERTY(Type, type, C_SVC) |
| 120 | 122 | BR_PROPERTY(float, C, -1) |
| 121 | 123 | BR_PROPERTY(float, gamma, -1) |
| 124 | + BR_PROPERTY(QString, inputVariable, "") | |
| 125 | + BR_PROPERTY(QString, outputVariable, "") | |
| 126 | + | |
| 122 | 127 | |
| 123 | 128 | SVM svm; |
| 124 | 129 | QHash<QString, int> labelMap; |
| ... | ... | @@ -128,14 +133,15 @@ private: |
| 128 | 133 | { |
| 129 | 134 | Mat data = OpenCVUtils::toMat(_data.data()); |
| 130 | 135 | Mat lab; |
| 131 | - // If we are doing regression, assume subject has float values | |
| 136 | + // If we are doing regression, the input variable should have float | |
| 137 | + // values | |
| 132 | 138 | if (type == EPS_SVR || type == NU_SVR) { |
| 133 | - lab = OpenCVUtils::toMat(File::get<float>(_data, "Subject")); | |
| 139 | + lab = OpenCVUtils::toMat(File::get<float>(_data, inputVariable)); | |
| 134 | 140 | } |
| 135 | - // If we are doing classification, assume subject has discrete values, map them | |
| 136 | - // and store the mapping data | |
| 141 | + // If we are doing classification, we should be dealing with discrete | |
| 142 | + // values. Map them and store the mapping data | |
| 137 | 143 | else { |
| 138 | - QList<int> dataLabels = _data.indexProperty("Subject", labelMap, reverseLookup); | |
| 144 | + QList<int> dataLabels = _data.indexProperty(inputVariable, labelMap, reverseLookup); | |
| 139 | 145 | lab = OpenCVUtils::toMat(dataLabels); |
| 140 | 146 | } |
| 141 | 147 | trainSVM(svm, data, lab, kernel, type, C, gamma); |
| ... | ... | @@ -146,9 +152,9 @@ private: |
| 146 | 152 | dst = src; |
| 147 | 153 | float prediction = svm.predict(src.m().reshape(1, 1)); |
| 148 | 154 | if (type == EPS_SVR || type == NU_SVR) |
| 149 | - dst.file.set("Subject", prediction); | |
| 155 | + dst.file.set(outputVariable, prediction); | |
| 150 | 156 | else |
| 151 | - dst.file.set("Subject", reverseLookup[prediction]); | |
| 157 | + dst.file.set(outputVariable, reverseLookup[prediction]); | |
| 152 | 158 | } |
| 153 | 159 | |
| 154 | 160 | void store(QDataStream &stream) const |
| ... | ... | @@ -162,6 +168,24 @@ private: |
| 162 | 168 | loadSVM(svm, stream); |
| 163 | 169 | stream >> labelMap >> reverseLookup; |
| 164 | 170 | } |
| 171 | + | |
| 172 | + void init() | |
| 173 | + { | |
| 174 | + // Since SVM can do regression or classification, we have to check the problem type before | |
| 175 | + // specifying target variable names | |
| 176 | + if (inputVariable.isEmpty()) | |
| 177 | + { | |
| 178 | + if (type == EPS_SVR || type == NU_SVR) { | |
| 179 | + inputVariable = "Regressor"; | |
| 180 | + if (outputVariable.isEmpty()) | |
| 181 | + outputVariable = "Regressand"; | |
| 182 | + } | |
| 183 | + else | |
| 184 | + inputVariable = "Label"; | |
| 185 | + } | |
| 186 | + if (outputVariable.isEmpty()) | |
| 187 | + outputVariable = inputVariable; | |
| 188 | + } | |
| 165 | 189 | }; |
| 166 | 190 | |
| 167 | 191 | BR_REGISTER(Transform, SVMTransform) |
| ... | ... | @@ -178,6 +202,8 @@ class SVMDistance : public Distance |
| 178 | 202 | Q_ENUMS(Type) |
| 179 | 203 | Q_PROPERTY(Kernel kernel READ get_kernel WRITE set_kernel RESET reset_kernel STORED false) |
| 180 | 204 | Q_PROPERTY(Type type READ get_type WRITE set_type RESET reset_type STORED false) |
| 205 | + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | |
| 206 | + | |
| 181 | 207 | |
| 182 | 208 | public: |
| 183 | 209 | enum Kernel { Linear = CvSVM::LINEAR, |
| ... | ... | @@ -194,13 +220,14 @@ public: |
| 194 | 220 | private: |
| 195 | 221 | BR_PROPERTY(Kernel, kernel, Linear) |
| 196 | 222 | BR_PROPERTY(Type, type, EPS_SVR) |
| 223 | + BR_PROPERTY(QString, inputVariable, "Label") | |
| 197 | 224 | |
| 198 | 225 | SVM svm; |
| 199 | 226 | |
| 200 | 227 | void train(const TemplateList &src) |
| 201 | 228 | { |
| 202 | 229 | const Mat data = OpenCVUtils::toMat(src.data()); |
| 203 | - const QList<int> lab = src.indexProperty("Subject"); | |
| 230 | + const QList<int> lab = src.indexProperty(inputVariable); | |
| 204 | 231 | |
| 205 | 232 | const int instances = data.rows * (data.rows+1) / 2; |
| 206 | 233 | Mat deltaData(instances, data.cols, data.type()); | ... | ... |
openbr/plugins/validate.cpp
| ... | ... | @@ -159,26 +159,29 @@ class MetadataDistance : public Distance |
| 159 | 159 | float compare(const Template &a, const Template &b) const |
| 160 | 160 | { |
| 161 | 161 | foreach (const QString &key, filters) { |
| 162 | + QString aValue = a.file.get<QString>(key, QString()); | |
| 163 | + QString bValue = b.file.get<QString>(key, QString()); | |
| 162 | 164 | |
| 163 | - const QString aValue = a.file.get<QString>(key, ""); | |
| 164 | - const QString bValue = b.file.get<QString>(key, ""); | |
| 165 | + // The query value may be a range. Let's check. | |
| 166 | + if (bValue.isEmpty()) bValue = QtUtils::toString(b.file.get<QPointF>(key, QPointF())); | |
| 165 | 167 | |
| 166 | 168 | if (aValue.isEmpty() || bValue.isEmpty()) continue; |
| 167 | 169 | |
| 168 | 170 | bool keep = false; |
| 171 | + bool ok; | |
| 169 | 172 | |
| 170 | - if (aValue[0] == '(') /* Range */ { | |
| 171 | - QStringList values = aValue.split(','); | |
| 173 | + QPointF range = QtUtils::toPoint(bValue,&ok); | |
| 172 | 174 | |
| 173 | - int value = values[0].mid(1).toInt(); | |
| 174 | - values[1].chop(1); | |
| 175 | - int upperBound = values[1].toInt(); | |
| 175 | + if (ok) /* Range */ { | |
| 176 | + int value = range.x(); | |
| 177 | + int upperBound = range.y(); | |
| 176 | 178 | |
| 177 | 179 | while (value <= upperBound) { |
| 178 | - if (aValue == bValue) { | |
| 180 | + if (aValue == QString::number(value)) { | |
| 179 | 181 | keep = true; |
| 180 | 182 | break; |
| 181 | 183 | } |
| 184 | + value++; | |
| 182 | 185 | } |
| 183 | 186 | } |
| 184 | 187 | else if (aValue == bValue) keep = true; | ... | ... |
scripts/downloadDatasets.sh
| ... | ... | @@ -64,6 +64,5 @@ if [ ! -d ../data/KTH/vid ]; then |
| 64 | 64 | rm ${vidclass}.zip |
| 65 | 65 | done |
| 66 | 66 | # this file is corrupted |
| 67 | - chmod +w ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi | |
| 68 | - rm ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi | |
| 67 | + rm -f ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi | |
| 69 | 68 | fi | ... | ... |
scripts/evalAgeRegression-PCSO.sh
| ... | ... | @@ -4,8 +4,12 @@ if [ ! -f evalAgeRegression-PCSO.sh ]; then |
| 4 | 4 | exit |
| 5 | 5 | fi |
| 6 | 6 | |
| 7 | +export BR="../build/app/br/br -useGui 0" | |
| 8 | +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/ | |
| 9 | +export ageAlg=AgeRegression | |
| 10 | + | |
| 7 | 11 | # Create a file list by querying the database |
| 8 | -br -quiet -algorithm Identity -enroll "../data/PCSO/PCSO.db[query='SELECT File,Age,PersonID FROM PCSO WHERE Age >= 15 AND AGE <= 75', subset=1:200]" terminal.txt > Input.txt | |
| 12 | +$BR -quiet -algorithm Identity -enroll "$PCSO_DIR/PCSO.db[query='SELECT File,Age,PersonID FROM PCSO WHERE Age >= 17 AND AGE <= 68', subset=1:200]" terminal.txt > Input.txt | |
| 9 | 13 | |
| 10 | 14 | # Enroll the file list and evaluate performance |
| 11 | -br -algorithm AgeRegression -path ../data/PCSO/img -enroll Input.txt Output.txt -evalRegression Output.txt Input.txt | |
| 15 | +$BR -algorithm $ageAlg -path $PCSO_DIR/Images -enroll Input.txt Output.txt -evalRegression Output.txt Input.txt Age | ... | ... |
scripts/evalFaceRecognition-MEDS.sh
| ... | ... | @@ -20,11 +20,11 @@ if [ ! -e Algorithm_Dataset ]; then |
| 20 | 20 | fi |
| 21 | 21 | |
| 22 | 22 | if [ ! -e MEDS.mask ]; then |
| 23 | - br -makeMask ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml MEDS.mask | |
| 23 | + br -useGui 0 -makeMask ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml MEDS.mask | |
| 24 | 24 | fi |
| 25 | 25 | |
| 26 | 26 | # Run Algorithm on MEDS |
| 27 | -br -algorithm ${ALGORITHM} -path ../data/MEDS/img -compare ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml ${ALGORITHM}_MEDS.mtx -eval ${ALGORITHM}_MEDS.mtx MEDS.mask Algorithm_Dataset/${ALGORITHM}_MEDS.csv | |
| 27 | +br -useGui 0 -algorithm ${ALGORITHM} -path ../data/MEDS/img -compare ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml ${ALGORITHM}_MEDS.mtx -eval ${ALGORITHM}_MEDS.mtx MEDS.mask Algorithm_Dataset/${ALGORITHM}_MEDS.csv | |
| 28 | 28 | |
| 29 | 29 | # Plot results |
| 30 | -br -plot Algorithm_Dataset/*_MEDS.csv MEDS | |
| 30 | +br -useGui 0 -plot Algorithm_Dataset/*_MEDS.csv MEDS | ... | ... |
scripts/evalGenderClassification-PCSO.sh
| ... | ... | @@ -4,8 +4,13 @@ if [ ! -f evalGenderClassification-PCSO.sh ]; then |
| 4 | 4 | exit |
| 5 | 5 | fi |
| 6 | 6 | |
| 7 | +export BR=../build/app/br/br | |
| 8 | +export genderAlg=GenderClassification | |
| 9 | + | |
| 10 | +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/ | |
| 11 | + | |
| 7 | 12 | # Create a file list by querying the database |
| 8 | -br -quiet -algorithm Identity -enroll "../data/PCSO/PCSO.db[query='SELECT File,Gender,PersonID FROM PCSO', subset=1:8000]" terminal.txt > Input.txt | |
| 13 | +$BR -useGui 0 -quiet -algorithm Identity -enroll "$PCSO_DIR/PCSO.db[query='SELECT File,Gender,PersonID FROM PCSO', subset=1:8000]" terminal.txt > Input.txt | |
| 9 | 14 | |
| 10 | 15 | # Enroll the file list and evaluate performance |
| 11 | -br -algorithm GenderClassification -path ../data/PCSO/img -enroll Input.txt Output.txt -evalClassification Output.txt Input.txt | |
| 16 | +$BR -useGui 0 -algorithm $genderAlg -path $PCSO_DIR/Images -enroll Input.txt Output.txt -evalClassification Output.txt Input.txt Gender | |
| 12 | 17 | \ No newline at end of file | ... | ... |
scripts/trainAgeRegression-PCSO.sh
| ... | ... | @@ -6,6 +6,11 @@ fi |
| 6 | 6 | |
| 7 | 7 | #rm -f ../share/openbr/models/features/FaceClassificationRegistration |
| 8 | 8 | #rm -f ../share/openbr/models/features/FaceClassificationExtraction |
| 9 | -rm -f ../share/openbr/models/algorithms/AgeRegression | |
| 9 | +#rm -f ../share/openbr/models/algorithms/AgeRegression | |
| 10 | 10 | |
| 11 | -br -algorithm AgeRegression -path ../data/PCSO/Images -train "../data/PCSO/PCSO.db[query='SELECT File,Age,PersonID FROM PCSO WHERE Age >= 15 AND AGE <= 75', subset=0:200]" ../share/openbr/models/algorithms/AgeRegression | |
| 11 | +export BR=../build/app/br/br | |
| 12 | +export ageAlg=AgeRegression | |
| 13 | + | |
| 14 | +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/ | |
| 15 | + | |
| 16 | +$BR -useGui 0 -algorithm $ageAlg -path $PCSO_DIR/Images -train "$PCSO_DIR/PCSO.db[query='SELECT File,Age,PersonID FROM PCSO WHERE Age >= 17 AND AGE <= 68', subset=0:200]" ../share/openbr/models/algorithms/AgeRegression | ... | ... |
scripts/trainFaceRecognition-PCSO.sh
| ... | ... | @@ -8,6 +8,13 @@ fi |
| 8 | 8 | #rm -f ../share/openbr/models/features/FaceRecognitionExtraction |
| 9 | 9 | #rm -f ../share/openbr/models/features/FaceRecognitionEmbedding |
| 10 | 10 | #rm -f ../share/openbr/models/features/FaceRecognitionQuantization |
| 11 | -rm -f ../share/openbr/models/algorithms/FaceRecognition | |
| 11 | +#rm -f ../share/openbr/models/algorithms/FaceRecognition | |
| 12 | + | |
| 13 | +export BR=../build/app/br/br | |
| 14 | + | |
| 15 | +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/ | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | +$BR -useGui 0 -algorithm FaceRecognition -path "$PCSO_DIR/Images/" -train "$PCSO_DIR/PCSO.db[query='SELECT File,PersonID as Label,PersonID FROM PCSO', subset=0:5:6000]" ../share/openbr/models/algorithms/FaceRecognition | |
| 12 | 20 | |
| 13 | -br -algorithm FaceRecognition -path ../data/PCSO/img -train "../data/PCSO/PCSO.db[query='SELECT File,'S'||PersonID,PersonID FROM PCSO', subset=0:5:6000]" ../share/openbr/models/algorithms/FaceRecognition | ... | ... |
scripts/trainGenderClassification-PCSO.sh
| ... | ... | @@ -6,6 +6,11 @@ fi |
| 6 | 6 | |
| 7 | 7 | #rm -f ../share/openbr/models/features/FaceClassificationRegistration |
| 8 | 8 | #rm -f ../share/openbr/models/features/FaceClassificationExtraction |
| 9 | -rm -f ../share/openbr/models/algorithms/GenderClassification | |
| 9 | +#rm -f ../share/openbr/models/algorithms/GenderClassification | |
| 10 | 10 | |
| 11 | -br -algorithm GenderClassification -path ../data/PCSO/Images -train "../data/PCSO/PCSO.db[query='SELECT File,Gender,PersonID FROM PCSO', subset=0:8000]" ../share/openbr/models/algorithms/GenderClassification | |
| 11 | +export BR=../build/app/br/br | |
| 12 | +export genderAlg=GenderClassification | |
| 13 | + | |
| 14 | +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/ | |
| 15 | + | |
| 16 | +$BR -useGui 0 -algorithm $genderAlg -path $PCSO_DIR/Images -train "$PCSO_DIR/PCSO.db[query='SELECT File,Gender,PersonID FROM PCSO', subset=0:8000]" ../share/openbr/models/algorithms/GenderClassification | ... | ... |
share/openbr/openbr.bib
| ... | ... | @@ -34,6 +34,11 @@ |
| 34 | 34 | Howpublished = {https://github.com/lbestrowden}, |
| 35 | 35 | Title = {bestrow1 at msu.edu}} |
| 36 | 36 | |
| 37 | +@misc{imaus10, | |
| 38 | + Author = {Austin Van Blanton}, | |
| 39 | + Howpublished = {https://github.com/imaus10}, | |
| 40 | + Title = {imaus10 at gmail.com}} | |
| 41 | + | |
| 37 | 42 | % Software |
| 38 | 43 | @misc{libface, |
| 39 | 44 | Howpublished = {http://libface.sourceforge.net/file/Home.html}, | ... | ... |