diff --git a/app/br/br.cpp b/app/br/br.cpp index f3e1208..c7ecdc1 100644 --- a/app/br/br.cpp +++ b/app/br/br.cpp @@ -138,8 +138,8 @@ public: check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalDetection'."); br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : ""); } else if (!strcmp(fun, "evalLandmarking")) { - check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalLandmarking'."); - br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : ""); + check((parc >= 2) && (parc <= 5), "Incorrect parameter count for 'evalLandmarking'."); + br_eval_landmarking(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 1); } else if (!strcmp(fun, "evalRegression")) { check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'."); br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); @@ -221,7 +221,7 @@ private: "-evalClassification \n" "-evalClustering \n" "-evalDetection [{csv}]\n" - "-evalLandmarking [{csv}]\n" + "-evalLandmarking [{csv} [ ]]\n" "-evalRegression \n" "-plotDetection ... {destination}\n" "-plotMetadata ... \n" diff --git a/data/KTH/test_9ppl.xml b/data/KTH/test_9ppl.xml new file mode 100644 index 0000000..c211650 --- /dev/null +++ b/data/KTH/test_9ppl.xml @@ -0,0 +1,651 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/KTH/train_16ppl.xml b/data/KTH/train_16ppl.xml new file mode 100644 index 0000000..c4bdc71 --- /dev/null +++ b/data/KTH/train_16ppl.xml @@ -0,0 +1,1149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openbr/core/eval.cpp b/openbr/core/eval.cpp index 567c99d..2dcf4cc 100644 --- a/openbr/core/eval.cpp +++ b/openbr/core/eval.cpp @@ -16,6 +16,7 @@ #include "bee.h" #include "eval.h" +#include "openbr/core/common.h" #include "openbr/core/qtutils.h" using namespace cv; @@ -255,9 +256,9 @@ struct Counter } }; -void EvalClassification(const QString &predictedInput, const QString &truthInput, QString predictedProperty, QString truthProperty) +void EvalClassification(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty, QString truthProperty) { - qDebug("Evaluating classification of %s against %s", qPrintable(predictedInput), qPrintable(truthInput)); + qDebug("Evaluating classification of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); if (predictedProperty.isEmpty()) predictedProperty = "Label"; @@ -269,8 +270,8 @@ void EvalClassification(const QString &predictedInput, const QString &truthInput if (truthProperty.isEmpty()) truthProperty = "Label"; - TemplateList predicted(TemplateList::fromGallery(predictedInput)); - TemplateList truth(TemplateList::fromGallery(truthInput)); + TemplateList predicted(TemplateList::fromGallery(predictedGallery)); + TemplateList truth(TemplateList::fromGallery(truthGallery)); if (predicted.size() != truth.size()) qFatal("Input size mismatch."); QHash counters; @@ -383,13 +384,19 @@ static QStringList computeDetectionResults(const QList &detec } const int keep = qMin(points.size(), Max_Points); - if (keep < 2) qFatal("Insufficient points."); + if (keep < 1) qFatal("Insufficient points."); QStringList lines; lines.reserve(keep); - for (int i=0; i(predicted, "name"); + const QStringList truthNames = File::get(truth, "name"); + + QList< QList > pointErrors; + for (int i=0; i predictedPoints = predicted[i].file.points(); + const QList truthPoints = truth[truthIndex].file.points(); + if (predictedPoints.size() != truthPoints.size()) qFatal("Points size mismatch for file: %s", qPrintable(predictedName)); + while (pointErrors.size() < predictedPoints.size()) + pointErrors.append(QList()); + if (normalizationIndexA >= truthPoints.size()) qFatal("Normalization index A is out of range."); + if (normalizationIndexB >= truthPoints.size()) qFatal("Normalization index B is out of range."); + const float normalizedLength = QtUtils::euclideanLength(truthPoints[normalizationIndexB] - truthPoints[normalizationIndexA]); + for (int j=0; j averagePointErrors; averagePointErrors.reserve(pointErrors.size()); + for (int i=0; i &pointError = pointErrors[i]; + const int keep = qMin(Max_Points, pointError.size()); + for (int j=0; j 1) + return false; + } + + return true; +} + +//Check all files to see if any single file has only have one ROC point +bool filesHaveSinglePoint(const QStringList &files) { + foreach (const File &file, files) + if (fileHasSinglePoint(file)) + return true; + return false; +} + bool PlotDetection(const QStringList &files, const File &destination, bool show) { qDebug("Plotting %d detection file(s) to %s", files.size(), qPrintable(destination)); @@ -287,8 +315,12 @@ bool PlotDetection(const QStringList &files, const File &destination, bool show) "rm(data)\n" "\n"); + QString plotType("line"); + if (filesHaveSinglePoint(files)) + plotType = QString("point"); + foreach (const QString &type, QStringList() << "Discrete" << "Continuous") - p.file.write(qPrintable(QString("qplot(X, Y, data=%1ROC%2").arg(type, (p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : ", geom=\"line\"") + + p.file.write(qPrintable(QString("qplot(X, Y, data=%1ROC%2").arg(type, (p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : QString(", geom=\"%1\"").arg(plotType)) + (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) + (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) + QString(", xlab=\"False Accepts\", ylab=\"True Accept Rate\") + theme_minimal()") + @@ -297,7 +329,7 @@ bool PlotDetection(const QStringList &files, const File &destination, bool show) QString(" + scale_x_log10() + scale_y_continuous(labels=percent) + annotation_logticks(sides=\"b\") + ggtitle(\"%1\")\n\n").arg(type))); foreach (const QString &type, QStringList() << "Discrete" << "Continuous") - p.file.write(qPrintable(QString("qplot(X, Y, data=%1PR%2").arg(type, (p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : ", geom=\"line\"") + + p.file.write(qPrintable(QString("qplot(X, Y, data=%1PR%2").arg(type, (p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : QString(", geom=\"%1\"").arg(plotType)) + (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) + (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) + QString(", xlab=\"Recall\", ylab=\"Precision\") + theme_minimal()") + diff --git a/openbr/core/qtutils.cpp b/openbr/core/qtutils.cpp index fe295cf..1d3f748 100644 --- a/openbr/core/qtutils.cpp +++ b/openbr/core/qtutils.cpp @@ -33,7 +33,10 @@ using namespace br; -QStringList QtUtils::getFiles(QDir dir, bool recursive) +namespace QtUtils +{ + +QStringList getFiles(QDir dir, bool recursive) { dir = QDir(dir.canonicalPath()); @@ -51,7 +54,7 @@ QStringList QtUtils::getFiles(QDir dir, bool recursive) return files; } -QStringList QtUtils::getFiles(const QString ®exp) +QStringList getFiles(const QString ®exp) { QFileInfo fileInfo(regexp); QDir dir(fileInfo.dir()); @@ -65,14 +68,14 @@ QStringList QtUtils::getFiles(const QString ®exp) return files; } -QStringList QtUtils::readLines(const QString &file) +QStringList readLines(const QString &file) { QStringList lines; readFile(file, lines); return lines; } -void QtUtils::readFile(const QString &file, QStringList &lines) +void readFile(const QString &file, QStringList &lines) { QByteArray data; readFile(file, data); @@ -81,7 +84,7 @@ void QtUtils::readFile(const QString &file, QStringList &lines) lines[i] = lines[i].simplified(); } -void QtUtils::readFile(const QString &file, QByteArray &data, bool uncompress) +void readFile(const QString &file, QByteArray &data, bool uncompress) { QFile f(file); if (!f.open(QFile::ReadOnly)) { @@ -93,17 +96,17 @@ void QtUtils::readFile(const QString &file, QByteArray &data, bool uncompress) f.close(); } -void QtUtils::writeFile(const QString &file, const QStringList &lines) +void writeFile(const QString &file, const QStringList &lines) { writeFile(file, lines.join("\n")); } -void QtUtils::writeFile(const QString &file, const QString &data) +void writeFile(const QString &file, const QString &data) { writeFile(file, data.toLocal8Bit()); } -void QtUtils::writeFile(const QString &file, const QByteArray &data, int compression) +void writeFile(const QString &file, const QByteArray &data, int compression) { if (file.isEmpty()) return; const QString baseName = QFileInfo(file).baseName(); @@ -122,7 +125,7 @@ void QtUtils::writeFile(const QString &file, const QByteArray &data, int compres } } -void QtUtils::copyFile(const QString &src, const QString &dst) +void copyFile(const QString &src, const QString &dst) { touchDir(QFileInfo(dst)); if (!QFile::copy(src, dst)) { @@ -131,24 +134,24 @@ void QtUtils::copyFile(const QString &src, const QString &dst) } } -void QtUtils::touchDir(const QDir &dir) +void touchDir(const QDir &dir) { if (dir.exists(".")) return; if (!dir.mkpath(".")) qFatal("Unable to create path to dir %s", qPrintable(dir.absolutePath())); } -void QtUtils::touchDir(const QFile &file) +void touchDir(const QFile &file) { touchDir(QFileInfo(file)); } -void QtUtils::touchDir(const QFileInfo &fileInfo) +void touchDir(const QFileInfo &fileInfo) { touchDir(fileInfo.dir()); } -void QtUtils::emptyDir(QDir &dir) +void emptyDir(QDir &dir) { foreach (const QString &folder, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks)) { QDir subdir(dir); @@ -166,13 +169,13 @@ void QtUtils::emptyDir(QDir &dir) dir.remove(symlink); } -void QtUtils::deleteDir(QDir &dir) +void deleteDir(QDir &dir) { emptyDir(dir); dir.rmdir("."); } -QString QtUtils::find(const QString &file, const QString &alt) +QString find(const QString &file, const QString &alt) { if (QFileInfo(file).exists()) return file; if (QFileInfo(alt).exists()) return alt; @@ -180,28 +183,28 @@ QString QtUtils::find(const QString &file, const QString &alt) return ""; } -bool QtUtils::toBool(const QString &string) +bool toBool(const QString &string) { bool ok; bool result = (bool)string.toInt(&ok); if (!ok) qFatal("Expected integer value, got %s.", qPrintable(string)); return result; } -int QtUtils::toInt(const QString &string) +int toInt(const QString &string) { bool ok; int result = string.toInt(&ok); if (!ok) qFatal("Expected integer value, got %s.", qPrintable(string)); return result; } -float QtUtils::toFloat(const QString &string) +float toFloat(const QString &string) { bool ok; float result = string.toFloat(&ok); if (!ok) qFatal("Expected floating point value, got %s.", qPrintable(string)); return result; } -QList QtUtils::toFloats(const QStringList &strings) +QList toFloats(const QStringList &strings) { QList floats; bool ok; @@ -212,7 +215,7 @@ QList QtUtils::toFloats(const QStringList &strings) return floats; } -QStringList QtUtils::toStringList(const QList &values) +QStringList toStringList(const QList &values) { QStringList result; result.reserve(values.size()); foreach (float value, values) @@ -220,7 +223,7 @@ QStringList QtUtils::toStringList(const QList &values) return result; } -QStringList QtUtils::toStringList(const std::vector &string_list) +QStringList toStringList(const std::vector &string_list) { QStringList result; foreach (const std::string &string, string_list) @@ -228,7 +231,7 @@ QStringList QtUtils::toStringList(const std::vector &string_list) return result; } -QStringList QtUtils::toStringList(int num_strings, const char *strings[]) +QStringList toStringList(int num_strings, const char *strings[]) { QStringList result; for (int i=0; i&]")); return QString(QCryptographicHash::hash(qPrintable(string), QCryptographicHash::Md5).toBase64()).remove(QRegExp("[^a-zA-Z1-9]")).left(6); } -QStringList QtUtils::parse(QString args, char split, bool *ok) +QStringList parse(QString args, char split, bool *ok) { if (args.isEmpty()) return QStringList(); @@ -295,7 +298,7 @@ QStringList QtUtils::parse(QString args, char split, bool *ok) return words; } -void QtUtils::checkArgsSize(const QString &name, const QStringList &args, int min, int max) +void checkArgsSize(const QString &name, const QStringList &args, int min, int max) { if (max == -1) max = std::numeric_limits::max(); if (max == 0) max = min; @@ -303,7 +306,7 @@ void QtUtils::checkArgsSize(const QString &name, const QStringList &args, int mi if (args.size() > max) qFatal("%s expects no more than %d arguments, got %d", qPrintable(name), max, args.size()); } -QPointF QtUtils::toPoint(const QString &string, bool *ok) +QPointF toPoint(const QString &string, bool *ok) { if (string.startsWith('(') && string.endsWith(')')) { bool okParse; @@ -324,7 +327,7 @@ QPointF QtUtils::toPoint(const QString &string, bool *ok) return QPointF(); } -QRectF QtUtils::toRect(const QString &string, bool *ok) +QRectF toRect(const QString &string, bool *ok) { if (string.startsWith('(') && string.endsWith(')')) { bool okParse; @@ -347,7 +350,7 @@ QRectF QtUtils::toRect(const QString &string, bool *ok) return QRectF(); } -QStringList QtUtils::naturalSort(const QStringList &strings) +QStringList naturalSort(const QStringList &strings) { QList stdStrings; stdStrings.reserve(strings.size()); foreach (const QString &string, strings) @@ -362,7 +365,7 @@ QStringList QtUtils::naturalSort(const QStringList &strings) return result; } -bool QtUtils::runRScript(const QString &file) +bool runRScript(const QString &file) { QProcess RScript; RScript.start("Rscript", QStringList() << file); @@ -374,7 +377,7 @@ bool QtUtils::runRScript(const QString &file) return result; } -bool QtUtils::runDot(const QString &file) +bool runDot(const QString &file) { QProcess dot; dot.start("dot -Tpdf -O " + file); @@ -382,7 +385,7 @@ bool QtUtils::runDot(const QString &file) return ((dot.exitCode() == 0) && (dot.error() == QProcess::UnknownError)); } -void QtUtils::showFile(const QString &file) +void showFile(const QString &file) { #ifndef BR_EMBEDDED (void) file; @@ -393,7 +396,7 @@ void QtUtils::showFile(const QString &file) #endif // BR_EMBEDDED } -QString QtUtils::toString(const QVariant &variant) +QString toString(const QVariant &variant) { if (variant.canConvert(QVariant::String)) return variant.toString(); else if(variant.canConvert(QVariant::PointF)) return QString("(%1,%2)").arg(QString::number(qvariant_cast(variant).x()), @@ -404,3 +407,11 @@ QString QtUtils::toString(const QVariant &variant) QString::number(qvariant_cast(variant).height())); return QString(); } + +float euclideanLength(const QPointF &point) +{ + return sqrt(pow(point.x(), 2) + pow(point.y(), 2)); +} + +} // namespace QtUtils + diff --git a/openbr/core/qtutils.h b/openbr/core/qtutils.h index effe385..7fa743a 100644 --- a/openbr/core/qtutils.h +++ b/openbr/core/qtutils.h @@ -73,6 +73,9 @@ namespace QtUtils /**** Variant Utilities ****/ QString toString(const QVariant &variant); + + /**** Point Utilities ****/ + float euclideanLength(const QPointF &point); } #endif // __QTUTILS_H diff --git a/openbr/openbr.cpp b/openbr/openbr.cpp index 491b91d..9092d6b 100644 --- a/openbr/openbr.cpp +++ b/openbr/openbr.cpp @@ -87,9 +87,9 @@ float br_eval_detection(const char *predicted_gallery, const char *truth_gallery return EvalDetection(predicted_gallery, truth_gallery, csv); } -void br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv) +float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv, int normalization_index_a, int normalization_index_b) { - return EvalLandmarking(predicted_gallery, truth_gallery, csv); + return EvalLandmarking(predicted_gallery, truth_gallery, csv, normalization_index_a, normalization_index_b); } void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char * predicted_property, const char * truth_property) diff --git a/openbr/openbr.h b/openbr/openbr.h index 2fc5c2b..9165932 100644 --- a/openbr/openbr.h +++ b/openbr/openbr.h @@ -173,8 +173,10 @@ BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *tru * \param predicted_gallery The predicted br::Gallery. * \param truth_gallery The ground truth br::Gallery. * \param csv Optional \c .csv file to contain performance metrics. + * \param normalization_index_a Optional first index in the list of points to use for normalization. + * \param normalization_index_b Optional second index in the list of points to use for normalization. */ -BR_EXPORT void br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv = ""); +BR_EXPORT float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv = "", int normalization_index_a = 0, int normalization_index_b = 1); /*! * \brief Evaluates regression accuracy to disk. diff --git a/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index d66b5d7..565a2cd 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -127,12 +127,12 @@ QString File::resolved() const bool File::contains(const QString &key) const { - return m_metadata.contains(key) || Globals->contains(key); + return m_metadata.contains(key) || Globals->contains(key) || key == "name"; } QVariant File::value(const QString &key) const { - return m_metadata.contains(key) ? m_metadata.value(key) : Globals->property(qPrintable(key)); + return m_metadata.contains(key) ? m_metadata.value(key) : (key == "name" ? name : Globals->property(qPrintable(key))); } QVariant File::parse(const QString &value) diff --git a/openbr/openbr_plugin.h b/openbr/openbr_plugin.h index c50be94..aa808c6 100644 --- a/openbr/openbr_plugin.h +++ b/openbr/openbr_plugin.h @@ -138,6 +138,7 @@ void reset_##NAME() { NAME = DEFAULT; } * * Key | Value | Description * --- | ---- | ----------- + * name | QString | Contents of #name * separator | QString | Seperate #name into multiple files * Index | int | Index of a template in a template list * Confidence | float | Classification/Regression quality diff --git a/openbr/plugins/algorithms.cpp b/openbr/plugins/algorithms.cpp index 523b234..1d1e2fb 100644 --- a/openbr/plugins/algorithms.cpp +++ b/openbr/plugins/algorithms.cpp @@ -46,12 +46,13 @@ class AlgorithmsInitializer : public Initializer Globals->abbreviations.insert("CropFace", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.25,0.35)"); // Video - Globals->abbreviations.insert("DisplayVideo", "Stream([FPSLimit(30)+Show(false,[FrameNumber])+Discard])"); - Globals->abbreviations.insert("PerFrameDetection", "Stream([SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+RestoreMat(original)+Draw(inPlace=true),Show(false,[FrameNumber])+Discard])"); - Globals->abbreviations.insert("AgeGenderDemo", "Stream([SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+++/+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])"); - 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"); - Globals->abbreviations.insert("HOF", "Stream([KeyPointDetector(SIFT),AggregateFrames(2)+OpticalFlow+Gradient+Bin(0,360,8)+ROI+Hist(8)])+Contract+CatRows+KMeans(500)+Hist(500)"); - 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)"); + Globals->abbreviations.insert("DisplayVideo", "Stream(FPSLimit(30)+Show(false,[FrameNumber])+Discard)"); + Globals->abbreviations.insert("PerFrameDetection", "Stream(SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+RestoreMat(original)+Draw(inPlace=true)+Show(false,[FrameNumber])+Discard)"); + Globals->abbreviations.insert("AgeGenderDemo", "Stream(SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+++/+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)"); + + Globals->abbreviations.insert("HOG", "Stream(DropFrames(5)+Cvt(Gray)+KeyPointDetector(SIFT)+ROI+Expand+Resize(32,32)+Gradient+RectRegions+Bin(0,360,8)+Hist(8)+Cat)+Contract+CatRows+KMeans(500)+Hist(500)+SVM"); + Globals->abbreviations.insert("HOF", "Stream(DropFrames(5)+KeyPointDetector(SIFT)+AggregateFrames(2)+OpticalFlow+ROI+Expand+Resize(32,32)+Gradient+RectRegions+Bin(0,360,8)+Hist(8)+Cat)+Contract+CatRows+KMeans(500)+Hist(500)"); + Globals->abbreviations.insert("HOGHOF", "Stream(DropFrames(5)+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)"); // Generic Image Processing Globals->abbreviations.insert("SIFT", "Open+KeyPointDetector(SIFT)+KeyPointDescriptor(SIFT):KeyPointMatcher(BruteForce)"); diff --git a/openbr/plugins/cascade.cpp b/openbr/plugins/cascade.cpp index e05d9d2..2abb3c6 100644 --- a/openbr/plugins/cascade.cpp +++ b/openbr/plugins/cascade.cpp @@ -99,6 +99,8 @@ class CascadeTransform : public UntrainableMetaTransform Template u(t.file, m); if (rejectLevels.size() > j) u.file.set("Confidence", rejectLevels[j]*levelWeights[j]); + else + u.file.set("Confidence", 1); const QRectF rect = OpenCVUtils::fromRect(rects[j]); u.file.appendRect(rect); u.file.set(model, rect); diff --git a/openbr/plugins/frames.cpp b/openbr/plugins/frames.cpp index 440d36f..f5f8609 100644 --- a/openbr/plugins/frames.cpp +++ b/openbr/plugins/frames.cpp @@ -19,7 +19,7 @@ class AggregateFrames : public TimeVaryingTransform TemplateList buffer; public: - AggregateFrames() : TimeVaryingTransform(false) {} + AggregateFrames() : TimeVaryingTransform(false, false) {} private: void train(const TemplateList &data) @@ -29,15 +29,18 @@ private: void projectUpdate(const Template &src, Template &dst) { - // DropFrames will pass on empty Templates - // but we only want to use non-dropped frames - if (src.empty()) return; buffer.append(src); if (buffer.size() < n) return; foreach (const Template &t, buffer) dst.append(t); dst.file = buffer.takeFirst().file; } + void finalize(TemplateList & output) + { + (void) output; + buffer.clear(); + } + void store(QDataStream &stream) const { (void) stream; @@ -58,35 +61,21 @@ BR_REGISTER(Transform, AggregateFrames) * * For a video with m frames, DropFrames will pass on m/n frames. */ -class DropFrames : public TimeVaryingTransform +class DropFrames : public UntrainableMetaTransform { Q_OBJECT Q_PROPERTY(int n READ get_n WRITE set_n RESET reset_n STORED false) BR_PROPERTY(int, n, 1) -public: - DropFrames() : TimeVaryingTransform(false) {} - -private: - void train(const TemplateList &data) + void project(const TemplateList &src, TemplateList &dst) const { - (void) data; - } - - void projectUpdate(const Template &src, Template &dst) - { - if (src.file.get("FrameNumber") % n != 0) return; + if (src.first().file.get("FrameNumber") % n != 0) return; dst = src; } - void store(QDataStream &stream) const + void project(const Template &src, Template &dst) const { - (void) stream; - } - - void load(QDataStream &stream) - { - (void) stream; + (void) src; (void) dst; qFatal("shouldn't be here"); } }; diff --git a/openbr/plugins/gallery.cpp b/openbr/plugins/gallery.cpp index a2699ba..7a1f59d 100644 --- a/openbr/plugins/gallery.cpp +++ b/openbr/plugins/gallery.cpp @@ -873,6 +873,50 @@ class FDDBGallery : public Gallery BR_REGISTER(Gallery, FDDBGallery) +/*! + * \ingroup galleries + * \brief Text format for associating anonymous landmarks with images. + * \author Josh Klontz \cite jklontz + * + * \code + * file_name:x1,y1,x2,y2,...,xn,yn + * file_name:x1,y1,x2,y2,...,xn,yn + * ... + * file_name:x1,y1,x2,y2,...,xn,yn + * \endcode + */ +class landmarksGallery : public Gallery +{ + Q_OBJECT + + TemplateList readBlock(bool *done) + { + *done = true; + TemplateList templates; + foreach (const QString &line, QtUtils::readLines(file)) { + const QStringList words = line.split(':'); + if (words.size() != 2) qFatal("Expected exactly one ':' in: %s.", qPrintable(line)); + File file(words[0]); + const QList vals = QtUtils::toFloats(words[1].split(',')); + if (vals.size() % 2 != 0) qFatal("Expected an even number of comma-separated values."); + QList points; points.reserve(vals.size()/2); + for (int i=0; itype() == QEvent::KeyPress) + { + event->accept(); + + QKeyEvent * key_event = dynamic_cast (event); + if (key_event == NULL) { + qDebug("failed to donwcast key event"); + return true; + } + + QString text = key_event->text(); + + text =text.toLower(); + if (text == "y" || text == "n") + { + gotString = key_event->text(); + wait.wakeAll(); + } + else qDebug("Please answer y/n"); + + return true; + } else { + return QObject::eventFilter(obj, event); + } + } + +public: + QString waitForKeyPress() + { + QMutexLocker locker(&lock); + wait.wait(&lock); + + return gotString; + } + +private: + QString gotString; + + +}; + // I want a template class that doesn't look like a template class class NominalCreation @@ -382,6 +427,68 @@ public: BR_REGISTER(Transform, ManualTransform) + +/*! + * \ingroup transforms + * \brief Display an image, and asks a yes/no question about it + * \author Charles Otto \cite caotto + */ +class SurveyTransform : public ShowTransform +{ + Q_OBJECT + +public: + Q_PROPERTY(QString question READ get_question WRITE set_question RESET reset_question STORED false) + BR_PROPERTY(QString, question, "Yes/No") + + Q_PROPERTY(QString propertyName READ get_propertyName WRITE set_propertyName RESET reset_propertyName STORED false) + BR_PROPERTY(QString, propertyName, "answer") + + + void projectUpdate(const TemplateList &src, TemplateList &dst) + { + if (Globals->parallelism > 1) + qFatal("SurveyTransform cannot execute in parallel."); + + dst = src; + + if (src.empty()) + return; + + for (int i = 0; i < dst.size(); i++) { + foreach(const cv::Mat &m, dst[i]) { + qImageBuffer = toQImage(m); + displayBuffer->convertFromImage(qImageBuffer); + + emit updateImage(displayBuffer->copy(displayBuffer->rect())); + + // Blocking wait for a key-press + if (this->waitInput) { + QString answer = p_window->waitForKeyPress(); + + dst[i].file.set(this->propertyName, answer); + } + } + } + } + PromptWindow * p_window; + + + void init() + { + if (!Globals->useGui) + return; + + initActual(); + p_window = (PromptWindow *) window; + + emit changeTitle(this->question); + } +}; + +BR_REGISTER(Transform, SurveyTransform) + + class FPSLimit : public TimeVaryingTransform { Q_OBJECT diff --git a/openbr/plugins/meta.cpp b/openbr/plugins/meta.cpp index b4cc865..5905c31 100644 --- a/openbr/plugins/meta.cpp +++ b/openbr/plugins/meta.cpp @@ -614,6 +614,11 @@ public: void train(const TemplateList &data) { + if (!transform->trainable) { + qWarning("Attempted to train untrainable transform, nothing will happen."); + return; + } + transform->train(data); } @@ -628,7 +633,6 @@ public: else dst = output[0]; } - // For each input template, form a single element TemplateList, push all those // lists through transform, and form dst by concatenating the results. // Process the single elemnt templates in parallel if parallelism is enabled. @@ -677,7 +681,6 @@ public: void init() { - if (transform && transform->timeVarying()) transform = new br::TimeInvariantWrapperTransform(transform); } diff --git a/openbr/plugins/openbr_internal.h b/openbr/plugins/openbr_internal.h index 27136da..ffe5cbf 100644 --- a/openbr/plugins/openbr_internal.h +++ b/openbr/plugins/openbr_internal.h @@ -122,7 +122,10 @@ public: TimeInvariantWrapperTransform(Transform * basis) : transformSource(new TransformCopier(basis)) { + if (!basis) + qFatal("TimeInvariantWrapper created with NULL transform"); baseTransform = basis; + trainable = basis->trainable; } virtual void project(const Template &src, Template &dst) const @@ -132,7 +135,6 @@ public: transformSource.release(aTransform); } - void project(const TemplateList &src, TemplateList &dst) const { Transform * aTransform = transformSource.acquire(); diff --git a/openbr/plugins/stream.cpp b/openbr/plugins/stream.cpp index 298b822..573169a 100644 --- a/openbr/plugins/stream.cpp +++ b/openbr/plugins/stream.cpp @@ -649,7 +649,7 @@ public: class ProcessingStage { public: - friend class StreamTransform; + friend class DirectStreamTransform; public: ProcessingStage(int nThreads = 1) { @@ -1015,15 +1015,23 @@ public: }; -class StreamTransform : public CompositeTransform + +class DirectStreamTransform : public CompositeTransform { Q_OBJECT public: Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames) BR_PROPERTY(int, activeFrames, 100) + friend class StreamTransfrom; + void train(const TemplateList & data) { + if (!trainable) { + qWarning("Attempted to train untrainable transform, nothing will happen."); + return; + } + qFatal("Stream train is currently not implemented."); foreach(Transform * transform, transforms) { transform->train(data); } @@ -1110,6 +1118,10 @@ public: { if (transforms.isEmpty()) return; + for (int i=0; i < processingStages.size();i++) + delete processingStages[i]; + processingStages.clear(); + // call CompositeTransform::init so that trainable is set // correctly. CompositeTransform::init(); @@ -1185,12 +1197,13 @@ public: collectionStage->nextStage = readStage; } - ~StreamTransform() + ~DirectStreamTransform() { // Delete all the stages for (int i = 0; i < processingStages.size(); i++) { delete processingStages[i]; } + processingStages.clear(); } protected: @@ -1232,12 +1245,150 @@ protected: } }; -QHash StreamTransform::pools; -QMutex StreamTransform::poolsAccess; +QHash DirectStreamTransform::pools; +QMutex DirectStreamTransform::poolsAccess; + +BR_REGISTER(Transform, DirectStreamTransform) + +; + +class StreamTransform : public TimeVaryingTransform +{ + Q_OBJECT + +public: + StreamTransform() : TimeVaryingTransform(false) + { + } + + Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false) + Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames) + BR_PROPERTY(br::Transform *, transform, NULL) + BR_PROPERTY(int, activeFrames, 100) + + bool timeVarying() const { return true; } + + void project(const Template &src, Template &dst) const + { + basis.project(src,dst); + } + + void projectUpdate(const Template &src, Template &dst) + { + basis.projectUpdate(src,dst); + } + void projectUpdate(const TemplateList & src, TemplateList & dst) + { + basis.projectUpdate(src,dst); + } + + + void train(const TemplateList & data) + { + basis.train(data); + } + + virtual void finalize(TemplateList & output) + { + (void) output; + // Nothing in particular to do here, stream calls finalize + // on all child transforms as part of projectUpdate + } + + // reinterpret transform, set up the actual stream. We can only reinterpret pipes + void init() + { + if (!transform) + return; + trainable = transform->trainable; + + basis.setParent(this->parent()); + basis.transforms.clear(); + basis.activeFrames = this->activeFrames; + + // We need at least a CompositeTransform * to acess transform's children. + CompositeTransform * downcast = dynamic_cast (transform); + + // If this isn't even a composite transform, or it's not a pipe, just set up + // basis with 1 stage. + if (!downcast || QString(transform->metaObject()->className()) != "br::PipeTransform") + { + basis.transforms.append(transform); + basis.init(); + return; + } + if (downcast->transforms.empty()) + { + qWarning("Trying to set up empty stream"); + basis.init(); + return; + } + + // OK now we will regroup downcast's children + QList > sets; + sets.append(QList ()); + sets.last().append(downcast->transforms[0]); + if (downcast->transforms[0]->timeVarying()) + sets.append(QList ()); + + for (int i=1;i < downcast->transforms.size(); i++) { + // If this is time varying it becomse its own stage + if (downcast->transforms[i]->timeVarying()) { + // If a set was already active, we add another one + if (!sets.last().empty()) { + sets.append(QList()); + } + // add the item + sets.last().append(downcast->transforms[i]); + // Add another set to indicate separation. + sets.append(QList()); + } + // otherwise, we can combine non time-varying stages + else { + sets.last().append(downcast->transforms[i]); + } + + } + if (sets.last().empty()) + sets.removeLast(); + + QList transform_set; + transform_set.reserve(sets.size()); + for (int i=0; i < sets.size(); i++) { + // If this is a single transform set, we add that to the list + if (sets[i].size() == 1 ) { + transform_set.append(sets[i].at(0)); + } + //otherwise we build a pipe + else { + CompositeTransform * pipe = dynamic_cast(Transform::make("Pipe([])", this)); + pipe->transforms = sets[i]; + pipe->init(); + transform_set.append(pipe); + } + } + + basis.transforms = transform_set; + basis.init(); + } + + Transform * smartCopy() + { + // We just want the DirectStream to begin with, so just return a copy of that. + DirectStreamTransform * res = (DirectStreamTransform *) basis.smartCopy(); + res->activeFrames = this->activeFrames; + return res; + } + + +private: + DirectStreamTransform basis; +}; BR_REGISTER(Transform, StreamTransform) + } // namespace br #include "stream.moc"