Commit 289023d827674ec228b6cdcbcb5e4b46cf7e1fff
Merge branch 'master' of https://github.com/biometrics/openbr into specificity
Resolved conflicts: app/br/br.cpp openbr/core/bee.cpp openbr/plugins/output.cpp
Showing
24 changed files
with
494 additions
and
242 deletions
CHANGELOG.md
app/br/br.cpp
| @@ -140,6 +140,9 @@ public: | @@ -140,6 +140,9 @@ public: | ||
| 140 | } else if (!strcmp(fun, "evalRegression")) { | 140 | } else if (!strcmp(fun, "evalRegression")) { |
| 141 | check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'."); | 141 | check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'."); |
| 142 | br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); | 142 | br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); |
| 143 | + } else if (!strcmp(fun, "plotDetection")) { | ||
| 144 | + check(parc >= 2, "Incorrect parameter count for 'plotDetection'."); | ||
| 145 | + br_plot_detection(parc-1, parv, parv[parc-1], true); | ||
| 143 | } else if (!strcmp(fun, "plotMetadata")) { | 146 | } else if (!strcmp(fun, "plotMetadata")) { |
| 144 | check(parc >= 2, "Incorrect parameter count for 'plotMetadata'."); | 147 | check(parc >= 2, "Incorrect parameter count for 'plotMetadata'."); |
| 145 | br_plot_metadata(parc-1, parv, parv[parc-1], true); | 148 | br_plot_metadata(parc-1, parv, parv[parc-1], true); |
| @@ -216,6 +219,7 @@ private: | @@ -216,6 +219,7 @@ private: | ||
| 216 | "-evalClustering <clusters> <gallery>\n" | 219 | "-evalClustering <clusters> <gallery>\n" |
| 217 | "-evalDetection <predicted_gallery> <truth_gallery> [{csv}]\n" | 220 | "-evalDetection <predicted_gallery> <truth_gallery> [{csv}]\n" |
| 218 | "-evalRegression <predicted_gallery> <truth_gallery>\n" | 221 | "-evalRegression <predicted_gallery> <truth_gallery>\n" |
| 222 | + "-plotDetection <file> ... <file> {destination}\n" | ||
| 219 | "-plotMetadata <file> ... <file> <columns>\n" | 223 | "-plotMetadata <file> ... <file> <columns>\n" |
| 220 | "-getHeader <matrix>\n" | 224 | "-getHeader <matrix>\n" |
| 221 | "-setHeader {<matrix>} <target_gallery> <query_gallery>\n" | 225 | "-setHeader {<matrix>} <target_gallery> <query_gallery>\n" |
openbr/core/bee.cpp
| @@ -102,7 +102,7 @@ void BEE::writeSigset(const QString &sigset, const br::FileList &files, bool ign | @@ -102,7 +102,7 @@ void BEE::writeSigset(const QString &sigset, const br::FileList &files, bool ign | ||
| 102 | if ((key == "Index") || (key == "Label")) continue; | 102 | if ((key == "Index") || (key == "Label")) continue; |
| 103 | metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\""); | 103 | metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\""); |
| 104 | } | 104 | } |
| 105 | - lines.append("\t<biometric-signature name=\"" + file.get<QString>("Label") +"\">"); | 105 | + lines.append("\t<biometric-signature name=\"" + file.get<QString>("Label",file.fileName()) +"\">"); |
| 106 | lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>"); | 106 | lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>"); |
| 107 | lines.append("\t</biometric-signature>"); | 107 | lines.append("\t</biometric-signature>"); |
| 108 | } | 108 | } |
openbr/core/eval.cpp
| @@ -387,7 +387,7 @@ static QStringList computeDetectionResults(const QList<ResolvedDetection> &detec | @@ -387,7 +387,7 @@ static QStringList computeDetectionResults(const QList<ResolvedDetection> &detec | ||
| 387 | for (int i=0; i<keep; i++) { | 387 | for (int i=0; i<keep; i++) { |
| 388 | const DetectionOperatingPoint &point = points[double(i) / double(keep-1) * double(points.size()-1)]; | 388 | const DetectionOperatingPoint &point = points[double(i) / double(keep-1) * double(points.size()-1)]; |
| 389 | lines.append(QString("%1ROC, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.FalsePositives), QString::number(point.Recall))); | 389 | lines.append(QString("%1ROC, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.FalsePositives), QString::number(point.Recall))); |
| 390 | - lines.append(QString("%1PR, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.Precision), QString::number(point.Recall))); | 390 | + lines.append(QString("%1PR, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.Recall), QString::number(point.Precision))); |
| 391 | } | 391 | } |
| 392 | return lines; | 392 | return lines; |
| 393 | } | 393 | } |
openbr/core/plot.cpp
| @@ -79,7 +79,7 @@ struct RPlot | @@ -79,7 +79,7 @@ struct RPlot | ||
| 79 | 79 | ||
| 80 | Pivot major, minor; | 80 | Pivot major, minor; |
| 81 | 81 | ||
| 82 | - RPlot(QStringList files, const br::File &destination, bool isEvalFormat = true) | 82 | + RPlot(QStringList files, const File &destination, bool isEvalFormat = true) |
| 83 | { | 83 | { |
| 84 | if (files.isEmpty()) qFatal("Empty file list."); | 84 | if (files.isEmpty()) qFatal("Empty file list."); |
| 85 | qSort(files.begin(), files.end(), sortFiles); | 85 | qSort(files.begin(), files.end(), sortFiles); |
| @@ -214,7 +214,7 @@ struct RPlot | @@ -214,7 +214,7 @@ struct RPlot | ||
| 214 | }; | 214 | }; |
| 215 | 215 | ||
| 216 | // Does not work if dataset folder starts with a number | 216 | // Does not work if dataset folder starts with a number |
| 217 | -bool Plot(const QStringList &files, const br::File &destination, bool show) | 217 | +bool Plot(const QStringList &files, const File &destination, bool show) |
| 218 | { | 218 | { |
| 219 | qDebug("Plotting %d file(s) to %s", files.size(), qPrintable(destination)); | 219 | qDebug("Plotting %d file(s) to %s", files.size(), qPrintable(destination)); |
| 220 | 220 | ||
| @@ -238,7 +238,6 @@ bool Plot(const QStringList &files, const br::File &destination, bool show) | @@ -238,7 +238,6 @@ bool Plot(const QStringList &files, const br::File &destination, bool show) | ||
| 238 | (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) + | 238 | (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) + |
| 239 | QString(" + scale_x_log10(labels=percent) + scale_y_log10(labels=percent) + annotation_logticks()\n\n"))); | 239 | QString(" + scale_x_log10(labels=percent) + scale_y_log10(labels=percent) + annotation_logticks()\n\n"))); |
| 240 | 240 | ||
| 241 | - | ||
| 242 | p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") + | 241 | p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") + |
| 243 | QString(", xlab=\"Score%1\"").arg((p.flip ? p.major.size : p.minor.size) > 1 ? " / " + (p.flip ? p.major.header : p.minor.header) : QString()) + | 242 | QString(", xlab=\"Score%1\"").arg((p.flip ? p.major.size : p.minor.size) > 1 ? " / " + (p.flip ? p.major.header : p.minor.header) : QString()) + |
| 244 | QString(", ylab=\"Frequency%1\"").arg((p.flip ? p.minor.size : p.major.size) > 1 ? " / " + (p.flip ? p.minor.header : p.major.header) : QString()) + | 243 | QString(", ylab=\"Frequency%1\"").arg((p.flip ? p.minor.size : p.major.size) > 1 ? " / " + (p.flip ? p.minor.header : p.major.header) : QString()) + |
| @@ -272,6 +271,48 @@ bool Plot(const QStringList &files, const br::File &destination, bool show) | @@ -272,6 +271,48 @@ bool Plot(const QStringList &files, const br::File &destination, bool show) | ||
| 272 | return p.finalize(show); | 271 | return p.finalize(show); |
| 273 | } | 272 | } |
| 274 | 273 | ||
| 274 | +bool PlotDetection(const QStringList &files, const File &destination, bool show) | ||
| 275 | +{ | ||
| 276 | + qDebug("Plotting %d detection file(s) to %s", files.size(), qPrintable(destination)); | ||
| 277 | + | ||
| 278 | + RPlot p(files, destination, false); | ||
| 279 | + | ||
| 280 | + p.file.write("# Split data into individual plots\n" | ||
| 281 | + "plot_index = which(names(data)==\"Plot\")\n" | ||
| 282 | + "DiscreteROC <- data[grep(\"DiscreteROC\",data$Plot),-c(1)]\n" | ||
| 283 | + "ContinuousROC <- data[grep(\"ContinuousROC\",data$Plot),-c(1)]\n" | ||
| 284 | + "DiscretePR <- data[grep(\"DiscretePR\",data$Plot),-c(1)]\n" | ||
| 285 | + "ContinuousPR <- data[grep(\"ContinuousPR\",data$Plot),-c(1)]\n" | ||
| 286 | + "Overlap <- data[grep(\"Overlap\",data$Plot),-c(1)]\n" | ||
| 287 | + "rm(data)\n" | ||
| 288 | + "\n"); | ||
| 289 | + | ||
| 290 | + foreach (const QString &type, QStringList() << "Discrete" << "Continuous") | ||
| 291 | + 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\"") + | ||
| 292 | + (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) + | ||
| 293 | + (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) + | ||
| 294 | + QString(", xlab=\"False Accepts\", ylab=\"True Accept Rate\") + theme_minimal()") + | ||
| 295 | + (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) + | ||
| 296 | + (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) + | ||
| 297 | + QString(" + scale_x_log10() + scale_y_continuous(labels=percent) + annotation_logticks(sides=\"b\") + ggtitle(\"%1\")\n\n").arg(type))); | ||
| 298 | + | ||
| 299 | + foreach (const QString &type, QStringList() << "Discrete" << "Continuous") | ||
| 300 | + 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\"") + | ||
| 301 | + (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) + | ||
| 302 | + (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) + | ||
| 303 | + QString(", xlab=\"Recall\", ylab=\"Precision\") + theme_minimal()") + | ||
| 304 | + (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) + | ||
| 305 | + (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) + | ||
| 306 | + QString(" + scale_x_continuous(labels=percent) + scale_y_continuous(labels=percent) + ggtitle(\"%1\")\n\n").arg(type))); | ||
| 307 | + | ||
| 308 | + p.file.write(qPrintable(QString("qplot(X, data=Overlap, geom=\"histogram\", position=\"identity\", xlab=\"Overlap\", ylab=\"Frequency\")") + | ||
| 309 | + QString(" + theme_minimal() + scale_x_continuous(minor_breaks=NULL) + scale_y_continuous(minor_breaks=NULL) + theme(axis.text.y=element_blank(), axis.ticks=element_blank(), axis.text.x=element_text(angle=-90, hjust=0))") + | ||
| 310 | + (p.major.size > 1 ? (p.minor.size > 1 ? QString(" + facet_grid(%2 ~ %1, scales=\"free\")").arg(p.minor.header, p.major.header) : QString(" + facet_wrap(~ %1, scales = \"free\")").arg(p.major.header)) : QString()) + | ||
| 311 | + QString(" + theme(aspect.ratio=1)\n\n"))); | ||
| 312 | + | ||
| 313 | + return p.finalize(show); | ||
| 314 | +} | ||
| 315 | + | ||
| 275 | bool PlotMetadata(const QStringList &files, const QString &columns, bool show) | 316 | bool PlotMetadata(const QStringList &files, const QString &columns, bool show) |
| 276 | { | 317 | { |
| 277 | qDebug("Plotting %d metadata file(s) for columns %s", files.size(), qPrintable(columns)); | 318 | qDebug("Plotting %d metadata file(s) for columns %s", files.size(), qPrintable(columns)); |
openbr/core/plot.h
| @@ -24,7 +24,8 @@ | @@ -24,7 +24,8 @@ | ||
| 24 | 24 | ||
| 25 | namespace br | 25 | namespace br |
| 26 | { | 26 | { |
| 27 | - bool Plot(const QStringList &files, const br::File &destination, bool show = false); | 27 | + bool Plot(const QStringList &files, const File &destination, bool show = false); |
| 28 | + bool PlotDetection(const QStringList &files, const File &destination, bool show = false); | ||
| 28 | bool PlotMetadata(const QStringList &files, const QString &destination, bool show = false); | 29 | bool PlotMetadata(const QStringList &files, const QString &destination, bool show = false); |
| 29 | } | 30 | } |
| 30 | 31 |
openbr/core/resource.h
| @@ -24,6 +24,7 @@ | @@ -24,6 +24,7 @@ | ||
| 24 | #include <QSharedPointer> | 24 | #include <QSharedPointer> |
| 25 | #include <QString> | 25 | #include <QString> |
| 26 | #include <QThread> | 26 | #include <QThread> |
| 27 | +#include <openbr/openbr_plugin.h> | ||
| 27 | 28 | ||
| 28 | template <typename T> | 29 | template <typename T> |
| 29 | class ResourceMaker | 30 | class ResourceMaker |
| @@ -52,7 +53,7 @@ public: | @@ -52,7 +53,7 @@ public: | ||
| 52 | : resourceMaker(rm) | 53 | : resourceMaker(rm) |
| 53 | , availableResources(new QList<T*>()) | 54 | , availableResources(new QList<T*>()) |
| 54 | , lock(new QMutex()) | 55 | , lock(new QMutex()) |
| 55 | - , totalResources(new QSemaphore(QThread::idealThreadCount())) | 56 | + , totalResources(new QSemaphore(br::Globals->parallelism)) |
| 56 | {} | 57 | {} |
| 57 | 58 | ||
| 58 | ~Resource() | 59 | ~Resource() |
openbr/openbr.cpp
| @@ -172,6 +172,11 @@ bool br_plot(int num_files, const char *files[], const char *destination, bool s | @@ -172,6 +172,11 @@ bool br_plot(int num_files, const char *files[], const char *destination, bool s | ||
| 172 | return Plot(QtUtils::toStringList(num_files, files), destination, show); | 172 | return Plot(QtUtils::toStringList(num_files, files), destination, show); |
| 173 | } | 173 | } |
| 174 | 174 | ||
| 175 | +bool br_plot_detection(int num_files, const char *files[], const char *destination, bool show) | ||
| 176 | +{ | ||
| 177 | + return PlotDetection(QtUtils::toStringList(num_files, files), destination, show); | ||
| 178 | +} | ||
| 179 | + | ||
| 175 | bool br_plot_metadata(int num_files, const char *files[], const char *columns, bool show) | 180 | bool br_plot_metadata(int num_files, const char *files[], const char *columns, bool show) |
| 176 | { | 181 | { |
| 177 | return PlotMetadata(QtUtils::toStringList(num_files, files), columns, show); | 182 | return PlotMetadata(QtUtils::toStringList(num_files, files), columns, show); |
openbr/openbr.h
| @@ -240,7 +240,7 @@ BR_EXPORT const char *br_most_recent_message(); | @@ -240,7 +240,7 @@ BR_EXPORT const char *br_most_recent_message(); | ||
| 240 | BR_EXPORT const char *br_objects(const char *abstractions = ".*", const char *implementations = ".*", bool parameters = true); | 240 | BR_EXPORT const char *br_objects(const char *abstractions = ".*", const char *implementations = ".*", bool parameters = true); |
| 241 | 241 | ||
| 242 | /*! | 242 | /*! |
| 243 | - * \brief Renders performance figures for a set of <tt>.csv</tt> files. | 243 | + * \brief Renders recognition performance figures for a set of <tt>.csv</tt> files created by \ref br_eval. |
| 244 | * | 244 | * |
| 245 | * In order of their output, the figures are: | 245 | * In order of their output, the figures are: |
| 246 | * -# Metadata table | 246 | * -# Metadata table |
| @@ -262,11 +262,29 @@ BR_EXPORT const char *br_objects(const char *abstractions = ".*", const char *im | @@ -262,11 +262,29 @@ BR_EXPORT const char *br_objects(const char *abstractions = ".*", const char *im | ||
| 262 | * \return Returns \c true on success. Returns false on a failure to compile the figures due to a missing, out of date, or incomplete \c R installation. | 262 | * \return Returns \c true on success. Returns false on a failure to compile the figures due to a missing, out of date, or incomplete \c R installation. |
| 263 | * \note This function requires a current <a href="http://www.r-project.org/">R</a> installation with the following packages: | 263 | * \note This function requires a current <a href="http://www.r-project.org/">R</a> installation with the following packages: |
| 264 | * \code install.packages(c("ggplot2", "gplots", "reshape", "scales")) \endcode | 264 | * \code install.packages(c("ggplot2", "gplots", "reshape", "scales")) \endcode |
| 265 | - * \see br_plot_metadata | 265 | + * \see br_eval |
| 266 | */ | 266 | */ |
| 267 | BR_EXPORT bool br_plot(int num_files, const char *files[], const char *destination, bool show = false); | 267 | BR_EXPORT bool br_plot(int num_files, const char *files[], const char *destination, bool show = false); |
| 268 | 268 | ||
| 269 | /*! | 269 | /*! |
| 270 | + * \brief Renders detection performance figures for a set of <tt>.csv</tt> files created by \ref br_eval_detection. | ||
| 271 | + * | ||
| 272 | + * In order of their output, the figures are: | ||
| 273 | + * -# Discrete Receiver Operating Characteristic (DiscreteROC) | ||
| 274 | + * -# Continuous Receiver Operating Characteristic (ContinuousROC) | ||
| 275 | + * -# Discrete Precision Recall (DiscretePR) | ||
| 276 | + * -# Continuous Precision Recall (ContinuousPR) | ||
| 277 | + * -# Bounding Box Overlap Histogram (Overlap) | ||
| 278 | + * | ||
| 279 | + * Detection accuracy is measured with <i>overlap fraction = bounding box intersection / union</i>. | ||
| 280 | + * When computing <i>discrete</i> curves, an overlap >= 0.5 is considered a true positive, otherwise it is considered a false negative. | ||
| 281 | + * When computing <i>continuous</i> curves, true positives and false negatives are measured fractionally as <i>overlap</i> and <i>1-overlap</i> respectively. | ||
| 282 | + * | ||
| 283 | + * \see br_plot | ||
| 284 | + */ | ||
| 285 | +BR_EXPORT bool br_plot_detection(int num_files, const char *files[], const char *destination, bool show = false); | ||
| 286 | + | ||
| 287 | +/*! | ||
| 270 | * \brief Renders metadata figures for a set of <tt>.csv</tt> files with specified columns. | 288 | * \brief Renders metadata figures for a set of <tt>.csv</tt> files with specified columns. |
| 271 | * | 289 | * |
| 272 | * Several files will be created: | 290 | * Several files will be created: |
openbr/openbr_plugin.cpp
| @@ -813,7 +813,7 @@ float br::Context::progress() const | @@ -813,7 +813,7 @@ float br::Context::progress() const | ||
| 813 | 813 | ||
| 814 | void br::Context::setProperty(const QString &key, const QString &value) | 814 | void br::Context::setProperty(const QString &key, const QString &value) |
| 815 | { | 815 | { |
| 816 | - Object::setProperty(key, value); | 816 | + Object::setProperty(key, value.isEmpty() ? QVariant() : value); |
| 817 | qDebug("Set %s%s", qPrintable(key), value.isEmpty() ? "" : qPrintable(" to " + value)); | 817 | qDebug("Set %s%s", qPrintable(key), value.isEmpty() ? "" : qPrintable(" to " + value)); |
| 818 | 818 | ||
| 819 | if (key == "parallelism") { | 819 | if (key == "parallelism") { |
| @@ -1163,7 +1163,8 @@ void Transform::project(const TemplateList &src, TemplateList &dst) const | @@ -1163,7 +1163,8 @@ void Transform::project(const TemplateList &src, TemplateList &dst) const | ||
| 1163 | dst.append(Template()); | 1163 | dst.append(Template()); |
| 1164 | QFutureSynchronizer<void> futures; | 1164 | QFutureSynchronizer<void> futures; |
| 1165 | for (int i=0; i<dst.size(); i++) | 1165 | for (int i=0; i<dst.size(); i++) |
| 1166 | - futures.addFuture(QtConcurrent::run(_project, this, &src[i], &dst[i])); | 1166 | + if (Globals->parallelism > 1) futures.addFuture(QtConcurrent::run(_project, this, &src[i], &dst[i])); |
| 1167 | + else _project(this, &src[i], &dst[i]); | ||
| 1167 | futures.waitForFinished(); | 1168 | futures.waitForFinished(); |
| 1168 | } | 1169 | } |
| 1169 | 1170 |
openbr/plugins/cascade.cpp
| @@ -49,19 +49,20 @@ private: | @@ -49,19 +49,20 @@ private: | ||
| 49 | } | 49 | } |
| 50 | }; | 50 | }; |
| 51 | 51 | ||
| 52 | - | ||
| 53 | /*! | 52 | /*! |
| 54 | * \ingroup transforms | 53 | * \ingroup transforms |
| 55 | * \brief Wraps OpenCV cascade classifier | 54 | * \brief Wraps OpenCV cascade classifier |
| 56 | * \author Josh Klontz \cite jklontz | 55 | * \author Josh Klontz \cite jklontz |
| 57 | */ | 56 | */ |
| 58 | -class CascadeTransform : public UntrainableTransform | 57 | +class CascadeTransform : public UntrainableMetaTransform |
| 59 | { | 58 | { |
| 60 | Q_OBJECT | 59 | Q_OBJECT |
| 61 | Q_PROPERTY(QString model READ get_model WRITE set_model RESET reset_model STORED false) | 60 | Q_PROPERTY(QString model READ get_model WRITE set_model RESET reset_model STORED false) |
| 62 | Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) | 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 | BR_PROPERTY(QString, model, "FrontalFace") | 63 | BR_PROPERTY(QString, model, "FrontalFace") |
| 64 | BR_PROPERTY(int, minSize, 64) | 64 | BR_PROPERTY(int, minSize, 64) |
| 65 | + BR_PROPERTY(bool, ROCMode, false) | ||
| 65 | 66 | ||
| 66 | Resource<CascadeClassifier> cascadeResource; | 67 | Resource<CascadeClassifier> cascadeResource; |
| 67 | 68 | ||
| @@ -72,18 +73,54 @@ class CascadeTransform : public UntrainableTransform | @@ -72,18 +73,54 @@ class CascadeTransform : public UntrainableTransform | ||
| 72 | 73 | ||
| 73 | void project(const Template &src, Template &dst) const | 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 | CascadeClassifier *cascade = cascadeResource.acquire(); | 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]*1000.0 + levelWeights[j]*1.0); | ||
| 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 | cascadeResource.release(cascade); | 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/eyes.cpp
| @@ -186,7 +186,6 @@ private: | @@ -186,7 +186,6 @@ private: | ||
| 186 | dst.file.appendPoint(QPointF(second_eye_x, second_eye_y)); | 186 | dst.file.appendPoint(QPointF(second_eye_x, second_eye_y)); |
| 187 | dst.file.set("First_Eye", QPointF(first_eye_x, first_eye_y)); | 187 | dst.file.set("First_Eye", QPointF(first_eye_x, first_eye_y)); |
| 188 | dst.file.set("Second_Eye", QPointF(second_eye_x, second_eye_y)); | 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/frames.cpp
0 โ 100644
| 1 | +#include "openbr_internal.h" | ||
| 2 | + | ||
| 3 | +namespace br | ||
| 4 | +{ | ||
| 5 | + | ||
| 6 | +/*! | ||
| 7 | + * \ingroup transforms | ||
| 8 | + * \brief Passes along n sequential frames to the next transform. | ||
| 9 | + * \author Josh Klontz \cite jklontz | ||
| 10 | + * | ||
| 11 | + * For a video with m frames, AggregateFrames would create a total of m-n+1 sequences ([0,n] ... [m-n+1, m]). | ||
| 12 | + */ | ||
| 13 | +class AggregateFrames : public TimeVaryingTransform | ||
| 14 | +{ | ||
| 15 | + Q_OBJECT | ||
| 16 | + Q_PROPERTY(int n READ get_n WRITE set_n RESET reset_n STORED false) | ||
| 17 | + BR_PROPERTY(int, n, 1) | ||
| 18 | + | ||
| 19 | + TemplateList buffer; | ||
| 20 | + | ||
| 21 | +public: | ||
| 22 | + AggregateFrames() : TimeVaryingTransform(false) {} | ||
| 23 | + | ||
| 24 | +private: | ||
| 25 | + void train(const TemplateList &data) | ||
| 26 | + { | ||
| 27 | + (void) data; | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + void projectUpdate(const Template &src, Template &dst) | ||
| 31 | + { | ||
| 32 | + buffer.append(src); | ||
| 33 | + if (buffer.size() < n) return; | ||
| 34 | + foreach (const Template &t, buffer) dst.append(t); | ||
| 35 | + dst.file = buffer.takeFirst().file; | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + void store(QDataStream &stream) const | ||
| 39 | + { | ||
| 40 | + (void) stream; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + void load(QDataStream &stream) | ||
| 44 | + { | ||
| 45 | + (void) stream; | ||
| 46 | + } | ||
| 47 | +}; | ||
| 48 | + | ||
| 49 | +BR_REGISTER(Transform, AggregateFrames) | ||
| 50 | + | ||
| 51 | +} // namespace br | ||
| 52 | + | ||
| 53 | +#include "frames.moc" |
openbr/plugins/gallery.cpp
| @@ -165,10 +165,13 @@ class EmptyGallery : public Gallery | @@ -165,10 +165,13 @@ class EmptyGallery : public Gallery | ||
| 165 | templates.append(File(fileName, dir.dirName())); | 165 | templates.append(File(fileName, dir.dirName())); |
| 166 | 166 | ||
| 167 | if (!regexp.isEmpty()) { | 167 | if (!regexp.isEmpty()) { |
| 168 | - const QRegularExpression re(regexp); | ||
| 169 | - for (int i=templates.size()-1; i>=0; i--) | ||
| 170 | - if (!re.match(templates[i].file.suffix()).hasMatch()) | 168 | + QRegExp re(regexp); |
| 169 | + re.setPatternSyntax(QRegExp::Wildcard); | ||
| 170 | + for (int i=templates.size()-1; i>=0; i--) { | ||
| 171 | + if (!re.exactMatch(templates[i].file.fileName())) { | ||
| 171 | templates.removeAt(i); | 172 | templates.removeAt(i); |
| 173 | + } | ||
| 174 | + } | ||
| 172 | } | 175 | } |
| 173 | 176 | ||
| 174 | return templates; | 177 | return templates; |
| @@ -269,67 +272,6 @@ class matrixGallery : public Gallery | @@ -269,67 +272,6 @@ class matrixGallery : public Gallery | ||
| 269 | BR_REGISTER(Gallery, matrixGallery) | 272 | BR_REGISTER(Gallery, matrixGallery) |
| 270 | 273 | ||
| 271 | /*! | 274 | /*! |
| 272 | - * \ingroup galleries | ||
| 273 | - * \brief Treat a video as a gallery, making a single template from each frame | ||
| 274 | - * \author Charles Otto \cite caotto | ||
| 275 | - */ | ||
| 276 | -class aviGallery : public Gallery | ||
| 277 | -{ | ||
| 278 | - Q_OBJECT | ||
| 279 | - | ||
| 280 | - TemplateList output_set; | ||
| 281 | - QScopedPointer<cv::VideoWriter> videoOut; | ||
| 282 | - | ||
| 283 | - ~aviGallery() | ||
| 284 | - { | ||
| 285 | - if (videoOut && videoOut->isOpened()) videoOut->release(); | ||
| 286 | - } | ||
| 287 | - | ||
| 288 | - TemplateList readBlock(bool * done) | ||
| 289 | - { | ||
| 290 | - std::string fname = file.name.toStdString(); | ||
| 291 | - *done = true; | ||
| 292 | - | ||
| 293 | - TemplateList output; | ||
| 294 | - if (!file.exists()) | ||
| 295 | - return output; | ||
| 296 | - | ||
| 297 | - cv::VideoCapture videoReader(file.name.toStdString()); | ||
| 298 | - | ||
| 299 | - bool open = videoReader.isOpened(); | ||
| 300 | - | ||
| 301 | - while (open) { | ||
| 302 | - cv::Mat frame; | ||
| 303 | - | ||
| 304 | - open = videoReader.read(frame); | ||
| 305 | - if (!open) break; | ||
| 306 | - output.append(Template()); | ||
| 307 | - output.back() = frame.clone(); | ||
| 308 | - } | ||
| 309 | - | ||
| 310 | - return TemplateList(); | ||
| 311 | - } | ||
| 312 | - | ||
| 313 | - void write(const Template & t) | ||
| 314 | - { | ||
| 315 | - if (videoOut.isNull() || !videoOut->isOpened()) { | ||
| 316 | - int fourcc = OpenCVUtils::getFourcc(); | ||
| 317 | - videoOut.reset(new cv::VideoWriter(qPrintable(file.name), fourcc, 30, t.m().size())); | ||
| 318 | - } | ||
| 319 | - | ||
| 320 | - if (!videoOut->isOpened()) { | ||
| 321 | - qWarning("Failed to open %s for writing\n", qPrintable(file.name)); | ||
| 322 | - return; | ||
| 323 | - } | ||
| 324 | - | ||
| 325 | - foreach(const cv::Mat & m, t) { | ||
| 326 | - videoOut->write(m); | ||
| 327 | - } | ||
| 328 | - } | ||
| 329 | -}; | ||
| 330 | -BR_REGISTER(Gallery, aviGallery) | ||
| 331 | - | ||
| 332 | -/*! | ||
| 333 | * \ingroup initializers | 275 | * \ingroup initializers |
| 334 | * \brief Initialization support for memGallery. | 276 | * \brief Initialization support for memGallery. |
| 335 | * \author Josh Klontz \cite jklontz | 277 | * \author Josh Klontz \cite jklontz |
openbr/plugins/gui.cpp
| @@ -212,8 +212,11 @@ public: | @@ -212,8 +212,11 @@ public: | ||
| 212 | foreach (const Template & t, src) { | 212 | foreach (const Template & t, src) { |
| 213 | // build label | 213 | // build label |
| 214 | QString newTitle; | 214 | QString newTitle; |
| 215 | + | ||
| 215 | foreach (const QString & s, keys) { | 216 | foreach (const QString & s, keys) { |
| 216 | - if (t.file.contains(s)) { | 217 | + if (s.compare("name", Qt::CaseInsensitive) == 0) { |
| 218 | + newTitle = newTitle + s + ": " + t.file.fileName() + " "; | ||
| 219 | + } else if (t.file.contains(s)) { | ||
| 217 | QString out = t.file.get<QString>(s); | 220 | QString out = t.file.get<QString>(s); |
| 218 | newTitle = newTitle + s + ": " + out + " "; | 221 | newTitle = newTitle + s + ": " + out + " "; |
| 219 | } | 222 | } |
openbr/plugins/independent.cpp
| @@ -129,6 +129,8 @@ class IndependentTransform : public MetaTransform | @@ -129,6 +129,8 @@ class IndependentTransform : public MetaTransform | ||
| 129 | return independentTransform; | 129 | return independentTransform; |
| 130 | } | 130 | } |
| 131 | 131 | ||
| 132 | + bool timeVarying() const { return transform->timeVarying(); } | ||
| 133 | + | ||
| 132 | static void _train(Transform *transform, const TemplateList *data) | 134 | static void _train(Transform *transform, const TemplateList *data) |
| 133 | { | 135 | { |
| 134 | transform->train(*data); | 136 | transform->train(*data); |
| @@ -170,6 +172,27 @@ class IndependentTransform : public MetaTransform | @@ -170,6 +172,27 @@ class IndependentTransform : public MetaTransform | ||
| 170 | dst.append(mats); | 172 | dst.append(mats); |
| 171 | } | 173 | } |
| 172 | 174 | ||
| 175 | + void projectUpdate(const Template &src, Template &dst) | ||
| 176 | + { | ||
| 177 | + dst.file = src.file; | ||
| 178 | + QList<Mat> mats; | ||
| 179 | + for (int i=0; i<src.size(); i++) { | ||
| 180 | + transforms[i%transforms.size()]->projectUpdate(Template(src.file, src[i]), dst); | ||
| 181 | + mats.append(dst); | ||
| 182 | + dst.clear(); | ||
| 183 | + } | ||
| 184 | + dst.append(mats); | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + void projectUpdate(const TemplateList &src, TemplateList &dst) | ||
| 188 | + { | ||
| 189 | + dst.reserve(src.size()); | ||
| 190 | + foreach (const Template &t, src) { | ||
| 191 | + dst.append(Template()); | ||
| 192 | + projectUpdate(t, dst.last()); | ||
| 193 | + } | ||
| 194 | + } | ||
| 195 | + | ||
| 173 | void store(QDataStream &stream) const | 196 | void store(QDataStream &stream) const |
| 174 | { | 197 | { |
| 175 | const int size = transforms.size(); | 198 | const int size = transforms.size(); |
openbr/plugins/landmarks.cpp
| @@ -230,6 +230,8 @@ class DelaunayTransform : public UntrainableTransform | @@ -230,6 +230,8 @@ class DelaunayTransform : public UntrainableTransform | ||
| 230 | 230 | ||
| 231 | QList<Point2f> mappedPoints; | 231 | QList<Point2f> mappedPoints; |
| 232 | 232 | ||
| 233 | + dst.file = src.file; | ||
| 234 | + | ||
| 233 | for (int i = 0; i < validTriangles.size(); i++) { | 235 | for (int i = 0; i < validTriangles.size(); i++) { |
| 234 | Eigen::MatrixXf srcMat(validTriangles[i].size(), 2); | 236 | Eigen::MatrixXf srcMat(validTriangles[i].size(), 2); |
| 235 | 237 | ||
| @@ -272,8 +274,8 @@ class DelaunayTransform : public UntrainableTransform | @@ -272,8 +274,8 @@ class DelaunayTransform : public UntrainableTransform | ||
| 272 | bitwise_and(dst.m(),mask,overlap); | 274 | bitwise_and(dst.m(),mask,overlap); |
| 273 | for (int j = 0; j < overlap.rows; j++) { | 275 | for (int j = 0; j < overlap.rows; j++) { |
| 274 | for (int k = 0; k < overlap.cols; k++) { | 276 | for (int k = 0; k < overlap.cols; k++) { |
| 275 | - if (overlap.at<uchar>(k,j) != 0) { | ||
| 276 | - mask.at<uchar>(k,j) = 0; | 277 | + if (overlap.at<uchar>(j,k) != 0) { |
| 278 | + mask.at<uchar>(j,k) = 0; | ||
| 277 | } | 279 | } |
| 278 | } | 280 | } |
| 279 | } | 281 | } |
| @@ -281,11 +283,13 @@ class DelaunayTransform : public UntrainableTransform | @@ -281,11 +283,13 @@ class DelaunayTransform : public UntrainableTransform | ||
| 281 | 283 | ||
| 282 | bitwise_and(buffer,mask,output); | 284 | bitwise_and(buffer,mask,output); |
| 283 | 285 | ||
| 286 | + | ||
| 284 | dst.m() += output; | 287 | dst.m() += output; |
| 285 | } | 288 | } |
| 286 | 289 | ||
| 290 | + // Overwrite any rects | ||
| 287 | Rect boundingBox = boundingRect(mappedPoints.toVector().toStdVector()); | 291 | Rect boundingBox = boundingRect(mappedPoints.toVector().toStdVector()); |
| 288 | - dst.file.appendRect(OpenCVUtils::fromRect(boundingBox)); | 292 | + dst.file.setRects(QList<QRectF>() << OpenCVUtils::fromRect(boundingBox)); |
| 289 | } | 293 | } |
| 290 | } | 294 | } |
| 291 | 295 |
openbr/plugins/meta.cpp
| @@ -645,17 +645,28 @@ public: | @@ -645,17 +645,28 @@ public: | ||
| 645 | QList<TemplateList> input_buffer; | 645 | QList<TemplateList> input_buffer; |
| 646 | input_buffer.reserve(src.size()); | 646 | input_buffer.reserve(src.size()); |
| 647 | 647 | ||
| 648 | + QFutureSynchronizer<void> futures; | ||
| 649 | + | ||
| 648 | for (int i =0; i < src.size();i++) { | 650 | for (int i =0; i < src.size();i++) { |
| 649 | input_buffer.append(TemplateList()); | 651 | input_buffer.append(TemplateList()); |
| 650 | output_buffer.append(TemplateList()); | 652 | output_buffer.append(TemplateList()); |
| 651 | } | 653 | } |
| 652 | - | ||
| 653 | - QFutureSynchronizer<void> futures; | 654 | + QList<QFuture<void> > temp; |
| 655 | + temp.reserve(src.size()); | ||
| 654 | for (int i=0; i<src.size(); i++) { | 656 | for (int i=0; i<src.size(); i++) { |
| 655 | input_buffer[i].append(src[i]); | 657 | 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]); | 658 | + |
| 659 | + if (Globals->parallelism > 1) temp.append(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i])); | ||
| 660 | + else _projectList(transform, &input_buffer[i], &output_buffer[i]); | ||
| 661 | + } | ||
| 662 | + // We add the futures in reverse order, since in Qt 5.1 at least the | ||
| 663 | + // waiting thread will wait on them in the order added (which for uniform priority | ||
| 664 | + // threads is the order of execution), and we want the waiting thread to go in the opposite order | ||
| 665 | + // so that it can steal runnables and do something besides wait. | ||
| 666 | + for (int i = temp.size() - 1; i > 0; i--) { | ||
| 667 | + futures.addFuture(temp[i]); | ||
| 658 | } | 668 | } |
| 669 | + | ||
| 659 | futures.waitForFinished(); | 670 | futures.waitForFinished(); |
| 660 | 671 | ||
| 661 | for (int i=0; i<src.size(); i++) dst.append(output_buffer[i]); | 672 | for (int i=0; i<src.size(); i++) dst.append(output_buffer[i]); |
openbr/plugins/output.cpp
| @@ -98,7 +98,7 @@ class heatOutput : public MatrixOutput | @@ -98,7 +98,7 @@ class heatOutput : public MatrixOutput | ||
| 98 | { | 98 | { |
| 99 | Q_OBJECT | 99 | Q_OBJECT |
| 100 | Q_PROPERTY(int patches READ get_patches WRITE set_patches RESET reset_patches STORED false) | 100 | Q_PROPERTY(int patches READ get_patches WRITE set_patches RESET reset_patches STORED false) |
| 101 | - BR_PROPERTY(int, patches, -1); | 101 | + BR_PROPERTY(int, patches, -1) |
| 102 | 102 | ||
| 103 | ~heatOutput() | 103 | ~heatOutput() |
| 104 | { | 104 | { |
| @@ -426,20 +426,26 @@ class rankOutput : public MatrixOutput | @@ -426,20 +426,26 @@ class rankOutput : public MatrixOutput | ||
| 426 | typedef QPair<float,int> Pair; | 426 | typedef QPair<float,int> Pair; |
| 427 | int rank = 1; | 427 | int rank = 1; |
| 428 | foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) { | 428 | foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) { |
| 429 | - if (targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) { | ||
| 430 | - ranks.append(rank); | ||
| 431 | - positions.append(pair.second); | ||
| 432 | - scores.append(pair.first); | ||
| 433 | - break; | 429 | + if (Globals->crossValidate > 0 ? (targetFiles[pair.second].get<int>("Partition",-1) == queryFiles[i].get<int>("Partition",-1)) : true) { |
| 430 | + if (QString(targetFiles[pair.second]) != QString(queryFiles[i])) { | ||
| 431 | + if (targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) { | ||
| 432 | + ranks.append(rank); | ||
| 433 | + positions.append(pair.second); | ||
| 434 | + scores.append(pair.first); | ||
| 435 | + break; | ||
| 436 | + } | ||
| 437 | + rank++; | ||
| 438 | + } | ||
| 434 | } | 439 | } |
| 435 | - rank++; | ||
| 436 | } | 440 | } |
| 437 | } | 441 | } |
| 438 | 442 | ||
| 439 | typedef QPair<int,int> RankPair; | 443 | typedef QPair<int,int> RankPair; |
| 440 | foreach (const RankPair &pair, Common::Sort(ranks, false)) | 444 | foreach (const RankPair &pair, Common::Sort(ranks, false)) |
| 445 | + // pair.first == rank retrieved, pair.second == original position | ||
| 441 | lines.append(queryFiles[pair.second].name + " " + QString::number(pair.first) + " " + QString::number(scores[pair.second]) + " " + targetFiles[positions[pair.second]].name); | 446 | lines.append(queryFiles[pair.second].name + " " + QString::number(pair.first) + " " + QString::number(scores[pair.second]) + " " + targetFiles[positions[pair.second]].name); |
| 442 | 447 | ||
| 448 | + | ||
| 443 | QtUtils::writeFile(file, lines); | 449 | QtUtils::writeFile(file, lines); |
| 444 | } | 450 | } |
| 445 | }; | 451 | }; |
openbr/plugins/regions.cpp
| @@ -252,16 +252,12 @@ class CropRectTransform : public UntrainableTransform | @@ -252,16 +252,12 @@ class CropRectTransform : public UntrainableTransform | ||
| 252 | void project(const Template &src, Template &dst) const | 252 | void project(const Template &src, Template &dst) const |
| 253 | { | 253 | { |
| 254 | dst = src; | 254 | dst = src; |
| 255 | - QList<QRectF> rects = dst.file.rects(); | 255 | + QList<QRectF> rects = src.file.rects(); |
| 256 | for (int i=0;i < rects.size(); i++) { | 256 | for (int i=0;i < rects.size(); i++) { |
| 257 | - QRectF rect = rects[i]; | ||
| 258 | - | ||
| 259 | - rect.setX(rect.x() + rect.width() * QtUtils::toPoint(widthCrop).x()); | ||
| 260 | - rect.setY(rect.y() + rect.height() * QtUtils::toPoint(heightCrop).x()); | ||
| 261 | - rect.setWidth(rect.width() * (1-QtUtils::toPoint(widthCrop).y())); | ||
| 262 | - rect.setHeight(rect.height() * (1-QtUtils::toPoint(heightCrop).y())); | ||
| 263 | - | ||
| 264 | - dst.m() = Mat(dst.m(), OpenCVUtils::toRect(rect)); | 257 | + rects[i].setX(rects[i].x() + rects[i].width() * QtUtils::toPoint(widthCrop).x()); |
| 258 | + rects[i].setY(rects[i].y() + rects[i].height() * QtUtils::toPoint(heightCrop).x()); | ||
| 259 | + rects[i].setWidth(rects[i].width() * (1-QtUtils::toPoint(widthCrop).y())); | ||
| 260 | + rects[i].setHeight(rects[i].height() * (1-QtUtils::toPoint(heightCrop).y())); | ||
| 265 | } | 261 | } |
| 266 | dst.file.setRects(rects); | 262 | dst.file.setRects(rects); |
| 267 | } | 263 | } |
openbr/plugins/stream.cpp
| @@ -160,9 +160,8 @@ class DataSource | @@ -160,9 +160,8 @@ class DataSource | ||
| 160 | public: | 160 | public: |
| 161 | DataSource(int maxFrames=500) | 161 | DataSource(int maxFrames=500) |
| 162 | { | 162 | { |
| 163 | + // The sequence number of the last frame | ||
| 163 | final_frame = -1; | 164 | final_frame = -1; |
| 164 | - last_issued = -2; | ||
| 165 | - last_received = -3; | ||
| 166 | for (int i=0; i < maxFrames;i++) | 165 | for (int i=0; i < maxFrames;i++) |
| 167 | { | 166 | { |
| 168 | allFrames.addItem(new FrameData()); | 167 | allFrames.addItem(new FrameData()); |
| @@ -181,51 +180,64 @@ public: | @@ -181,51 +180,64 @@ public: | ||
| 181 | } | 180 | } |
| 182 | 181 | ||
| 183 | // non-blocking version of getFrame | 182 | // non-blocking version of getFrame |
| 184 | - FrameData * tryGetFrame() | 183 | + // Returns a NULL FrameData if too many frames are out, or the |
| 184 | + // data source is broken. Sets last_frame to true iff the FrameData | ||
| 185 | + // returned is the last valid frame, and the data source is now broken. | ||
| 186 | + FrameData * tryGetFrame(bool & last_frame) | ||
| 185 | { | 187 | { |
| 188 | + last_frame = false; | ||
| 189 | + | ||
| 190 | + if (is_broken) { | ||
| 191 | + return NULL; | ||
| 192 | + } | ||
| 193 | + | ||
| 194 | + // Try to get a FrameData from the pool, if we can't it means too many | ||
| 195 | + // frames are already out, and we will return NULL to indicate failure | ||
| 186 | FrameData * aFrame = allFrames.tryGetItem(); | 196 | FrameData * aFrame = allFrames.tryGetItem(); |
| 187 | - if (aFrame == NULL) | 197 | + if (aFrame == NULL) { |
| 188 | return NULL; | 198 | return NULL; |
| 199 | + } | ||
| 189 | 200 | ||
| 190 | aFrame->data.clear(); | 201 | aFrame->data.clear(); |
| 191 | aFrame->sequenceNumber = -1; | 202 | aFrame->sequenceNumber = -1; |
| 192 | 203 | ||
| 204 | + // Try to read a frame, if this returns false the data source is broken | ||
| 193 | bool res = getNext(*aFrame); | 205 | bool res = getNext(*aFrame); |
| 194 | 206 | ||
| 195 | - // The datasource broke. | ||
| 196 | - if (!res) { | 207 | + // The datasource broke, update final_frame |
| 208 | + if (!res) | ||
| 209 | + { | ||
| 210 | + QMutexLocker lock(&last_frame_update); | ||
| 211 | + final_frame = lookAhead.back()->sequenceNumber; | ||
| 197 | allFrames.addItem(aFrame); | 212 | allFrames.addItem(aFrame); |
| 213 | + } | ||
| 214 | + else lookAhead.push_back(aFrame); | ||
| 198 | 215 | ||
| 199 | - QMutexLocker lock(&last_frame_update); | ||
| 200 | - // Did we already receive the last frame? | ||
| 201 | - final_frame = last_issued; | 216 | + FrameData * rVal = lookAhead.first(); |
| 217 | + lookAhead.pop_front(); | ||
| 202 | 218 | ||
| 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; | ||
| 210 | 219 | ||
| 211 | - return NULL; | 220 | + if (rVal->sequenceNumber == final_frame) { |
| 221 | + last_frame = true; | ||
| 222 | + is_broken = true; | ||
| 212 | } | 223 | } |
| 213 | - last_issued = aFrame->sequenceNumber; | ||
| 214 | - return aFrame; | 224 | + |
| 225 | + return rVal; | ||
| 215 | } | 226 | } |
| 216 | 227 | ||
| 217 | - // Returns true if the frame returned was the last | 228 | + // Return a frame to the pool, returns true if the frame returned was the last |
| 218 | // frame issued, false otherwise | 229 | // frame issued, false otherwise |
| 219 | bool returnFrame(FrameData * inputFrame) | 230 | bool returnFrame(FrameData * inputFrame) |
| 220 | { | 231 | { |
| 232 | + int frameNumber = inputFrame->sequenceNumber; | ||
| 233 | + | ||
| 221 | allFrames.addItem(inputFrame); | 234 | allFrames.addItem(inputFrame); |
| 222 | 235 | ||
| 223 | bool rval = false; | 236 | bool rval = false; |
| 224 | 237 | ||
| 225 | QMutexLocker lock(&last_frame_update); | 238 | QMutexLocker lock(&last_frame_update); |
| 226 | - last_received = inputFrame->sequenceNumber; | ||
| 227 | 239 | ||
| 228 | - if (inputFrame->sequenceNumber == final_frame) { | 240 | + if (frameNumber == final_frame) { |
| 229 | // We just received the last frame, better pulse | 241 | // We just received the last frame, better pulse |
| 230 | lastReturned.wakeAll(); | 242 | lastReturned.wakeAll(); |
| 231 | rval = true; | 243 | rval = true; |
| @@ -240,17 +252,57 @@ public: | @@ -240,17 +252,57 @@ public: | ||
| 240 | lastReturned.wait(&last_frame_update); | 252 | lastReturned.wait(&last_frame_update); |
| 241 | } | 253 | } |
| 242 | 254 | ||
| 243 | - virtual void close() = 0; | ||
| 244 | - virtual bool open(Template & output, int start_index=0) = 0; | ||
| 245 | - virtual bool isOpen() = 0; | 255 | + bool open(Template & output, int start_index = 0) |
| 256 | + { | ||
| 257 | + is_broken = false; | ||
| 258 | + // The last frame isn't initialized yet | ||
| 259 | + final_frame = -1; | ||
| 260 | + // Start our sequence numbers from the input index | ||
| 261 | + next_sequence_number = start_index; | ||
| 262 | + | ||
| 263 | + // Actually open the data source | ||
| 264 | + bool open_res = concreteOpen(output); | ||
| 265 | + | ||
| 266 | + // We couldn't open the data source | ||
| 267 | + if (!open_res) { | ||
| 268 | + is_broken = true; | ||
| 269 | + return false; | ||
| 270 | + } | ||
| 271 | + | ||
| 272 | + // Try to get a frame from the global pool | ||
| 273 | + FrameData * firstFrame = allFrames.tryGetItem(); | ||
| 274 | + | ||
| 275 | + // If this fails, things have gone pretty badly. | ||
| 276 | + if (firstFrame == NULL) { | ||
| 277 | + is_broken = true; | ||
| 278 | + return false; | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + // Read a frame from the video source | ||
| 282 | + bool res = getNext(*firstFrame); | ||
| 283 | + | ||
| 284 | + // the data source broke already, we couldn't even get one frame | ||
| 285 | + // from it. | ||
| 286 | + if (!res) { | ||
| 287 | + is_broken = true; | ||
| 288 | + return false; | ||
| 289 | + } | ||
| 246 | 290 | ||
| 291 | + lookAhead.append(firstFrame); | ||
| 292 | + return true; | ||
| 293 | + } | ||
| 294 | + | ||
| 295 | + virtual bool isOpen()=0; | ||
| 296 | + virtual bool concreteOpen(Template & output) = 0; | ||
| 247 | virtual bool getNext(FrameData & input) = 0; | 297 | virtual bool getNext(FrameData & input) = 0; |
| 298 | + virtual void close() = 0; | ||
| 248 | 299 | ||
| 300 | + int next_sequence_number; | ||
| 249 | protected: | 301 | protected: |
| 250 | DoubleBuffer allFrames; | 302 | DoubleBuffer allFrames; |
| 251 | int final_frame; | 303 | int final_frame; |
| 252 | - int last_issued; | ||
| 253 | - int last_received; | 304 | + bool is_broken; |
| 305 | + QList<FrameData *> lookAhead; | ||
| 254 | 306 | ||
| 255 | QWaitCondition lastReturned; | 307 | QWaitCondition lastReturned; |
| 256 | QMutex last_frame_update; | 308 | QMutex last_frame_update; |
| @@ -262,13 +314,8 @@ class VideoDataSource : public DataSource | @@ -262,13 +314,8 @@ class VideoDataSource : public DataSource | ||
| 262 | public: | 314 | public: |
| 263 | VideoDataSource(int maxFrames) : DataSource(maxFrames) {} | 315 | VideoDataSource(int maxFrames) : DataSource(maxFrames) {} |
| 264 | 316 | ||
| 265 | - bool open(Template &input, int start_index=0) | 317 | + bool concreteOpen(Template &input) |
| 266 | { | 318 | { |
| 267 | - final_frame = -1; | ||
| 268 | - last_issued = -2; | ||
| 269 | - last_received = -3; | ||
| 270 | - | ||
| 271 | - next_idx = start_index; | ||
| 272 | basis = input; | 319 | basis = input; |
| 273 | bool is_int = false; | 320 | bool is_int = false; |
| 274 | int anInt = input.file.name.toInt(&is_int); | 321 | int anInt = input.file.name.toInt(&is_int); |
| @@ -284,8 +331,11 @@ public: | @@ -284,8 +331,11 @@ public: | ||
| 284 | { | 331 | { |
| 285 | qDebug("Video not open!"); | 332 | qDebug("Video not open!"); |
| 286 | } | 333 | } |
| 334 | + } else { | ||
| 335 | + // Yes, we should specify absolute path: | ||
| 336 | + // http://stackoverflow.com/questions/9396459/loading-a-video-in-opencv-in-python | ||
| 337 | + video.open(QFileInfo(input.file.name).absoluteFilePath().toStdString()); | ||
| 287 | } | 338 | } |
| 288 | - else video.open(input.file.name.toStdString()); | ||
| 289 | 339 | ||
| 290 | return video.isOpened(); | 340 | return video.isOpened(); |
| 291 | } | 341 | } |
| @@ -303,25 +353,31 @@ private: | @@ -303,25 +353,31 @@ private: | ||
| 303 | return false; | 353 | return false; |
| 304 | 354 | ||
| 305 | output.data.append(Template(basis.file)); | 355 | output.data.append(Template(basis.file)); |
| 306 | - output.data.last().append(cv::Mat()); | 356 | + output.data.last().m() = cv::Mat(); |
| 307 | 357 | ||
| 308 | - output.sequenceNumber = next_idx; | ||
| 309 | - next_idx++; | 358 | + output.sequenceNumber = next_sequence_number; |
| 359 | + next_sequence_number++; | ||
| 310 | 360 | ||
| 311 | - bool res = video.read(output.data.last().last()); | ||
| 312 | - output.data.last().last() = output.data.last().last().clone(); | 361 | + cv::Mat temp; |
| 362 | + bool res = video.read(temp); | ||
| 313 | 363 | ||
| 314 | if (!res) { | 364 | if (!res) { |
| 365 | + output.data.last().m() = cv::Mat(); | ||
| 315 | close(); | 366 | close(); |
| 316 | return false; | 367 | return false; |
| 317 | } | 368 | } |
| 369 | + | ||
| 370 | + // This clone is critical, if we don't do it then the matrix will | ||
| 371 | + // be an alias of an internal buffer of the video source, leading | ||
| 372 | + // to various problems later. | ||
| 373 | + output.data.last().m() = temp.clone(); | ||
| 374 | + | ||
| 318 | output.data.last().file.set("FrameNumber", output.sequenceNumber); | 375 | output.data.last().file.set("FrameNumber", output.sequenceNumber); |
| 319 | return true; | 376 | return true; |
| 320 | } | 377 | } |
| 321 | 378 | ||
| 322 | cv::VideoCapture video; | 379 | cv::VideoCapture video; |
| 323 | Template basis; | 380 | Template basis; |
| 324 | - int next_idx; | ||
| 325 | }; | 381 | }; |
| 326 | 382 | ||
| 327 | // Given a template as input, return its matrices one by one on subsequent calls | 383 | // Given a template as input, return its matrices one by one on subsequent calls |
| @@ -331,21 +387,16 @@ class TemplateDataSource : public DataSource | @@ -331,21 +387,16 @@ class TemplateDataSource : public DataSource | ||
| 331 | public: | 387 | public: |
| 332 | TemplateDataSource(int maxFrames) : DataSource(maxFrames) | 388 | TemplateDataSource(int maxFrames) : DataSource(maxFrames) |
| 333 | { | 389 | { |
| 334 | - current_idx = INT_MAX; | 390 | + current_matrix_idx = INT_MAX; |
| 335 | data_ok = false; | 391 | data_ok = false; |
| 336 | } | 392 | } |
| 337 | - bool data_ok; | ||
| 338 | 393 | ||
| 339 | - bool open(Template &input, int start_index=0) | 394 | + bool concreteOpen(Template &input) |
| 340 | { | 395 | { |
| 341 | basis = input; | 396 | basis = input; |
| 342 | - current_idx = 0; | ||
| 343 | - next_sequence = start_index; | ||
| 344 | - final_frame = -1; | ||
| 345 | - last_issued = -2; | ||
| 346 | - last_received = -3; | 397 | + current_matrix_idx = 0; |
| 347 | 398 | ||
| 348 | - data_ok = current_idx < basis.size(); | 399 | + data_ok = current_matrix_idx < basis.size(); |
| 349 | return data_ok; | 400 | return data_ok; |
| 350 | } | 401 | } |
| 351 | 402 | ||
| @@ -355,39 +406,41 @@ public: | @@ -355,39 +406,41 @@ public: | ||
| 355 | 406 | ||
| 356 | void close() | 407 | void close() |
| 357 | { | 408 | { |
| 358 | - current_idx = INT_MAX; | 409 | + current_matrix_idx = INT_MAX; |
| 359 | basis.clear(); | 410 | basis.clear(); |
| 360 | } | 411 | } |
| 361 | 412 | ||
| 362 | private: | 413 | private: |
| 363 | bool getNext(FrameData & output) | 414 | bool getNext(FrameData & output) |
| 364 | { | 415 | { |
| 365 | - data_ok = current_idx < basis.size(); | 416 | + data_ok = current_matrix_idx < basis.size(); |
| 366 | if (!data_ok) | 417 | if (!data_ok) |
| 367 | return false; | 418 | return false; |
| 368 | 419 | ||
| 369 | - output.data.append(basis[current_idx]); | ||
| 370 | - current_idx++; | 420 | + output.data.append(basis[current_matrix_idx]); |
| 421 | + current_matrix_idx++; | ||
| 371 | 422 | ||
| 372 | - output.sequenceNumber = next_sequence; | ||
| 373 | - next_sequence++; | 423 | + output.sequenceNumber = next_sequence_number; |
| 424 | + next_sequence_number++; | ||
| 374 | 425 | ||
| 375 | output.data.last().file.set("FrameNumber", output.sequenceNumber); | 426 | output.data.last().file.set("FrameNumber", output.sequenceNumber); |
| 376 | return true; | 427 | return true; |
| 377 | } | 428 | } |
| 378 | 429 | ||
| 379 | Template basis; | 430 | Template basis; |
| 380 | - int current_idx; | ||
| 381 | - int next_sequence; | 431 | + // Index of the next matrix to output from the template |
| 432 | + int current_matrix_idx; | ||
| 433 | + | ||
| 434 | + // is current_matrix_idx in bounds? | ||
| 435 | + bool data_ok; | ||
| 382 | }; | 436 | }; |
| 383 | 437 | ||
| 384 | -// Given a template as input, create a VideoDataSource or a TemplateDataSource | ||
| 385 | -// depending on whether or not it looks like the input template has already | ||
| 386 | -// loaded frames into memory. | 438 | +// Given a templatelist as input, create appropriate data source for each |
| 439 | +// individual template | ||
| 387 | class DataSourceManager : public DataSource | 440 | class DataSourceManager : public DataSource |
| 388 | { | 441 | { |
| 389 | public: | 442 | public: |
| 390 | - DataSourceManager() | 443 | + DataSourceManager() : DataSource(500) |
| 391 | { | 444 | { |
| 392 | actualSource = NULL; | 445 | actualSource = NULL; |
| 393 | } | 446 | } |
| @@ -408,29 +461,25 @@ public: | @@ -408,29 +461,25 @@ public: | ||
| 408 | 461 | ||
| 409 | bool open(TemplateList & input) | 462 | bool open(TemplateList & input) |
| 410 | { | 463 | { |
| 411 | - currentIdx = 0; | 464 | + current_template_idx = 0; |
| 412 | templates = input; | 465 | templates = input; |
| 413 | 466 | ||
| 414 | - return open(templates[currentIdx]); | 467 | + return DataSource::open(templates[current_template_idx]); |
| 415 | } | 468 | } |
| 416 | 469 | ||
| 417 | - bool open(Template & input, int start_index=0) | 470 | + bool concreteOpen(Template & input) |
| 418 | { | 471 | { |
| 419 | close(); | 472 | close(); |
| 420 | - final_frame = -1; | ||
| 421 | - last_issued = -2; | ||
| 422 | - last_received = -3; | ||
| 423 | - next_frame = start_index; | ||
| 424 | 473 | ||
| 425 | // Input has no matrices? Its probably a video that hasn't been loaded yet | 474 | // Input has no matrices? Its probably a video that hasn't been loaded yet |
| 426 | if (input.empty()) { | 475 | if (input.empty()) { |
| 427 | actualSource = new VideoDataSource(0); | 476 | actualSource = new VideoDataSource(0); |
| 428 | - actualSource->open(input, next_frame); | 477 | + actualSource->concreteOpen(input); |
| 429 | } | 478 | } |
| 430 | else { | 479 | else { |
| 431 | // create frame dealer | 480 | // create frame dealer |
| 432 | actualSource = new TemplateDataSource(0); | 481 | actualSource = new TemplateDataSource(0); |
| 433 | - actualSource->open(input, next_frame); | 482 | + actualSource->concreteOpen(input); |
| 434 | } | 483 | } |
| 435 | if (!isOpen()) { | 484 | if (!isOpen()) { |
| 436 | delete actualSource; | 485 | delete actualSource; |
| @@ -443,30 +492,47 @@ public: | @@ -443,30 +492,47 @@ public: | ||
| 443 | bool isOpen() { return !actualSource ? false : actualSource->isOpen(); } | 492 | bool isOpen() { return !actualSource ? false : actualSource->isOpen(); } |
| 444 | 493 | ||
| 445 | protected: | 494 | protected: |
| 446 | - int currentIdx; | ||
| 447 | - int next_frame; | 495 | + // Index of the template in the templatelist we are currently reading from |
| 496 | + int current_template_idx; | ||
| 497 | + | ||
| 448 | TemplateList templates; | 498 | TemplateList templates; |
| 449 | DataSource * actualSource; | 499 | DataSource * actualSource; |
| 450 | bool getNext(FrameData & output) | 500 | bool getNext(FrameData & output) |
| 451 | { | 501 | { |
| 452 | bool res = actualSource->getNext(output); | 502 | bool res = actualSource->getNext(output); |
| 503 | + output.sequenceNumber = next_sequence_number; | ||
| 504 | + | ||
| 453 | if (res) { | 505 | if (res) { |
| 454 | - next_frame = output.sequenceNumber+1; | 506 | + output.data.last().file.set("FrameNumber", output.sequenceNumber); |
| 507 | + next_sequence_number++; | ||
| 508 | + if (output.data.last().last().empty()) | ||
| 509 | + qDebug("broken matrix"); | ||
| 455 | return true; | 510 | return true; |
| 456 | } | 511 | } |
| 457 | 512 | ||
| 513 | + | ||
| 458 | while(!res) { | 514 | while(!res) { |
| 459 | - currentIdx++; | 515 | + output.data.clear(); |
| 516 | + current_template_idx++; | ||
| 460 | 517 | ||
| 461 | - if (currentIdx >= templates.size()) | 518 | + // No more templates? We're done |
| 519 | + if (current_template_idx >= templates.size()) | ||
| 462 | return false; | 520 | return false; |
| 463 | - bool open_res = open(templates[currentIdx], next_frame); | 521 | + |
| 522 | + // open the next data source | ||
| 523 | + bool open_res = concreteOpen(templates[current_template_idx]); | ||
| 464 | if (!open_res) | 524 | if (!open_res) |
| 465 | return false; | 525 | return false; |
| 526 | + | ||
| 527 | + // get a frame from it | ||
| 466 | res = actualSource->getNext(output); | 528 | res = actualSource->getNext(output); |
| 467 | } | 529 | } |
| 530 | + output.sequenceNumber = next_sequence_number++; | ||
| 531 | + output.data.last().file.set("FrameNumber", output.sequenceNumber); | ||
| 532 | + | ||
| 533 | + if (output.data.last().last().empty()) | ||
| 534 | + qDebug("broken matrix"); | ||
| 468 | 535 | ||
| 469 | - next_frame = output.sequenceNumber+1; | ||
| 470 | return res; | 536 | return res; |
| 471 | } | 537 | } |
| 472 | 538 | ||
| @@ -474,9 +540,14 @@ protected: | @@ -474,9 +540,14 @@ protected: | ||
| 474 | 540 | ||
| 475 | class ProcessingStage; | 541 | class ProcessingStage; |
| 476 | 542 | ||
| 477 | -class BasicLoop : public QRunnable | 543 | +class BasicLoop : public QRunnable, public QFutureInterface<void> |
| 478 | { | 544 | { |
| 479 | public: | 545 | public: |
| 546 | + BasicLoop() | ||
| 547 | + { | ||
| 548 | + this->reportStarted(); | ||
| 549 | + } | ||
| 550 | + | ||
| 480 | void run(); | 551 | void run(); |
| 481 | 552 | ||
| 482 | QList<ProcessingStage *> * stages; | 553 | QList<ProcessingStage *> * stages; |
| @@ -502,13 +573,13 @@ public: | @@ -502,13 +573,13 @@ public: | ||
| 502 | int stage_id; | 573 | int stage_id; |
| 503 | 574 | ||
| 504 | virtual void reset()=0; | 575 | virtual void reset()=0; |
| 505 | - | ||
| 506 | protected: | 576 | protected: |
| 507 | int thread_count; | 577 | int thread_count; |
| 508 | 578 | ||
| 509 | SharedBuffer * inputBuffer; | 579 | SharedBuffer * inputBuffer; |
| 510 | ProcessingStage * nextStage; | 580 | ProcessingStage * nextStage; |
| 511 | QList<ProcessingStage *> * stages; | 581 | QList<ProcessingStage *> * stages; |
| 582 | + QThreadPool * threads; | ||
| 512 | Transform * transform; | 583 | Transform * transform; |
| 513 | 584 | ||
| 514 | }; | 585 | }; |
| @@ -527,6 +598,7 @@ void BasicLoop::run() | @@ -527,6 +598,7 @@ void BasicLoop::run() | ||
| 527 | current_idx++; | 598 | current_idx++; |
| 528 | current_idx = current_idx % stages->size(); | 599 | current_idx = current_idx % stages->size(); |
| 529 | } | 600 | } |
| 601 | + this->reportFinished(); | ||
| 530 | } | 602 | } |
| 531 | 603 | ||
| 532 | class MultiThreadStage : public ProcessingStage | 604 | class MultiThreadStage : public ProcessingStage |
| @@ -561,7 +633,6 @@ public: | @@ -561,7 +633,6 @@ public: | ||
| 561 | } | 633 | } |
| 562 | }; | 634 | }; |
| 563 | 635 | ||
| 564 | - | ||
| 565 | class SingleThreadStage : public ProcessingStage | 636 | class SingleThreadStage : public ProcessingStage |
| 566 | { | 637 | { |
| 567 | public: | 638 | public: |
| @@ -624,18 +695,20 @@ public: | @@ -624,18 +695,20 @@ public: | ||
| 624 | lock.unlock(); | 695 | lock.unlock(); |
| 625 | 696 | ||
| 626 | if (newItem) | 697 | if (newItem) |
| 627 | - { | ||
| 628 | - BasicLoop * next = new BasicLoop(); | ||
| 629 | - next->stages = stages; | ||
| 630 | - next->start_idx = this->stage_id; | ||
| 631 | - next->startItem = newItem; | ||
| 632 | - | ||
| 633 | - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); | ||
| 634 | - } | 698 | + startThread(newItem); |
| 635 | 699 | ||
| 636 | return input; | 700 | return input; |
| 637 | } | 701 | } |
| 638 | 702 | ||
| 703 | + void startThread(br::FrameData * newItem) | ||
| 704 | + { | ||
| 705 | + BasicLoop * next = new BasicLoop(); | ||
| 706 | + next->stages = stages; | ||
| 707 | + next->start_idx = this->stage_id; | ||
| 708 | + next->startItem = newItem; | ||
| 709 | + this->threads->start(next, stages->size() - stage_id); | ||
| 710 | + } | ||
| 711 | + | ||
| 639 | 712 | ||
| 640 | // Calledfrom a different thread than run. | 713 | // Calledfrom a different thread than run. |
| 641 | bool tryAcquireNextStage(FrameData *& input) | 714 | bool tryAcquireNextStage(FrameData *& input) |
| @@ -671,7 +744,7 @@ public: | @@ -671,7 +744,7 @@ public: | ||
| 671 | }; | 744 | }; |
| 672 | 745 | ||
| 673 | // No input buffer, instead we draw templates from some data source | 746 | // No input buffer, instead we draw templates from some data source |
| 674 | -// Will be operated by the main thread for the stream | 747 | +// Will be operated by the main thread for the stream. starts threads |
| 675 | class FirstStage : public SingleThreadStage | 748 | class FirstStage : public SingleThreadStage |
| 676 | { | 749 | { |
| 677 | public: | 750 | public: |
| @@ -681,44 +754,51 @@ public: | @@ -681,44 +754,51 @@ public: | ||
| 681 | 754 | ||
| 682 | FrameData * run(FrameData * input, bool & should_continue) | 755 | FrameData * run(FrameData * input, bool & should_continue) |
| 683 | { | 756 | { |
| 684 | - // Is there anything on our input buffer? If so we should start a thread with that. | 757 | + // Try to get a frame from the datasource |
| 685 | QWriteLocker lock(&statusLock); | 758 | QWriteLocker lock(&statusLock); |
| 686 | - input = dataSource.tryGetFrame(); | ||
| 687 | - // Datasource broke? | ||
| 688 | - if (!input) | 759 | + bool last_frame = false; |
| 760 | + input = dataSource.tryGetFrame(last_frame); | ||
| 761 | + | ||
| 762 | + // Datasource broke, or is currently out of frames? | ||
| 763 | + if (!input || last_frame) | ||
| 689 | { | 764 | { |
| 765 | + // We will just stop and not continue. | ||
| 690 | currentStatus = STOPPING; | 766 | currentStatus = STOPPING; |
| 691 | - should_continue = false; | ||
| 692 | - return NULL; | 767 | + if (!input) { |
| 768 | + should_continue = false; | ||
| 769 | + return NULL; | ||
| 770 | + } | ||
| 693 | } | 771 | } |
| 694 | lock.unlock(); | 772 | lock.unlock(); |
| 695 | - | 773 | + // Can we enter the next stage? |
| 696 | should_continue = nextStage->tryAcquireNextStage(input); | 774 | should_continue = nextStage->tryAcquireNextStage(input); |
| 697 | 775 | ||
| 698 | - BasicLoop * next = new BasicLoop(); | ||
| 699 | - next->stages = stages; | ||
| 700 | - next->start_idx = this->stage_id; | ||
| 701 | - next->startItem = NULL; | ||
| 702 | - | ||
| 703 | - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); | 776 | + // We are exiting leaving this stage, should we start another |
| 777 | + // thread here? Normally we will always re-queue a thread on | ||
| 778 | + // the first stage, but if we received the last frame there is | ||
| 779 | + // no need to. | ||
| 780 | + if (!last_frame) { | ||
| 781 | + startThread(NULL); | ||
| 782 | + } | ||
| 704 | 783 | ||
| 705 | return input; | 784 | return input; |
| 706 | } | 785 | } |
| 707 | 786 | ||
| 708 | - // Calledfrom a different thread than run. | 787 | + // The last stage, trying to access the first stage |
| 709 | bool tryAcquireNextStage(FrameData *& input) | 788 | bool tryAcquireNextStage(FrameData *& input) |
| 710 | { | 789 | { |
| 790 | + // Return the frame, was it the last one? | ||
| 711 | bool was_last = dataSource.returnFrame(input); | 791 | bool was_last = dataSource.returnFrame(input); |
| 712 | input = NULL; | 792 | input = NULL; |
| 793 | + | ||
| 794 | + // OK we won't continue. | ||
| 713 | if (was_last) { | 795 | if (was_last) { |
| 714 | return false; | 796 | return false; |
| 715 | } | 797 | } |
| 716 | 798 | ||
| 717 | - if (!dataSource.isOpen()) | ||
| 718 | - return false; | ||
| 719 | - | ||
| 720 | QReadLocker lock(&statusLock); | 799 | QReadLocker lock(&statusLock); |
| 721 | - // Thread is already running, we should just return | 800 | + // A thread is already in the first stage, |
| 801 | + // we should just return | ||
| 722 | if (currentStatus == STARTING) | 802 | if (currentStatus == STARTING) |
| 723 | { | 803 | { |
| 724 | return false; | 804 | return false; |
| @@ -741,6 +821,7 @@ public: | @@ -741,6 +821,7 @@ public: | ||
| 741 | 821 | ||
| 742 | }; | 822 | }; |
| 743 | 823 | ||
| 824 | +// starts threads | ||
| 744 | class LastStage : public SingleThreadStage | 825 | class LastStage : public SingleThreadStage |
| 745 | { | 826 | { |
| 746 | public: | 827 | public: |
| @@ -771,11 +852,14 @@ public: | @@ -771,11 +852,14 @@ public: | ||
| 771 | } | 852 | } |
| 772 | next_target = input->sequenceNumber + 1; | 853 | next_target = input->sequenceNumber + 1; |
| 773 | 854 | ||
| 855 | + // add the item to our output buffer | ||
| 774 | collectedOutput.append(input->data); | 856 | collectedOutput.append(input->data); |
| 775 | 857 | ||
| 858 | + // Can we enter the read stage? | ||
| 776 | should_continue = nextStage->tryAcquireNextStage(input); | 859 | should_continue = nextStage->tryAcquireNextStage(input); |
| 777 | 860 | ||
| 778 | - // Is there anything on our input buffer? If so we should start a thread with that. | 861 | + // Is there anything on our input buffer? If so we should start a thread |
| 862 | + // in this stage to process that frame. | ||
| 779 | QWriteLocker lock(&statusLock); | 863 | QWriteLocker lock(&statusLock); |
| 780 | FrameData * newItem = inputBuffer->tryGetItem(); | 864 | FrameData * newItem = inputBuffer->tryGetItem(); |
| 781 | if (!newItem) | 865 | if (!newItem) |
| @@ -785,23 +869,18 @@ public: | @@ -785,23 +869,18 @@ public: | ||
| 785 | lock.unlock(); | 869 | lock.unlock(); |
| 786 | 870 | ||
| 787 | if (newItem) | 871 | if (newItem) |
| 788 | - { | ||
| 789 | - BasicLoop * next = new BasicLoop(); | ||
| 790 | - next->stages = stages; | ||
| 791 | - next->start_idx = this->stage_id; | ||
| 792 | - next->startItem = newItem; | ||
| 793 | - | ||
| 794 | - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); | ||
| 795 | - } | 872 | + startThread(newItem); |
| 796 | 873 | ||
| 797 | return input; | 874 | return input; |
| 798 | } | 875 | } |
| 799 | }; | 876 | }; |
| 800 | 877 | ||
| 878 | + | ||
| 801 | class StreamTransform : public CompositeTransform | 879 | class StreamTransform : public CompositeTransform |
| 802 | { | 880 | { |
| 803 | Q_OBJECT | 881 | Q_OBJECT |
| 804 | public: | 882 | public: |
| 883 | + | ||
| 805 | void train(const TemplateList & data) | 884 | void train(const TemplateList & data) |
| 806 | { | 885 | { |
| 807 | foreach(Transform * transform, transforms) { | 886 | foreach(Transform * transform, transforms) { |
| @@ -831,21 +910,17 @@ public: | @@ -831,21 +910,17 @@ public: | ||
| 831 | bool res = readStage->dataSource.open(dst); | 910 | bool res = readStage->dataSource.open(dst); |
| 832 | if (!res) return; | 911 | if (!res) return; |
| 833 | 912 | ||
| 834 | - QThreadPool::globalInstance()->releaseThread(); | 913 | + // Start the first thread in the stream. |
| 835 | readStage->currentStatus = SingleThreadStage::STARTING; | 914 | readStage->currentStatus = SingleThreadStage::STARTING; |
| 915 | + readStage->startThread(NULL); | ||
| 836 | 916 | ||
| 837 | - BasicLoop loop; | ||
| 838 | - loop.stages = &this->processingStages; | ||
| 839 | - loop.start_idx = 0; | ||
| 840 | - loop.startItem = NULL; | ||
| 841 | - loop.setAutoDelete(false); | ||
| 842 | - | ||
| 843 | - QThreadPool::globalInstance()->start(&loop, processingStages.size() - processingStages[0]->stage_id); | ||
| 844 | - | ||
| 845 | - // Wait for the end. | 917 | + // Wait for the stream to reach the last frame available from |
| 918 | + // the data source. | ||
| 846 | readStage->dataSource.waitLast(); | 919 | readStage->dataSource.waitLast(); |
| 847 | - QThreadPool::globalInstance()->reserveThread(); | ||
| 848 | 920 | ||
| 921 | + // Now that there are no more incoming frames, call finalize | ||
| 922 | + // on each transform in turn to collect any last templates | ||
| 923 | + // they wish to issue. | ||
| 849 | TemplateList final_output; | 924 | TemplateList final_output; |
| 850 | 925 | ||
| 851 | // Push finalize through the stages | 926 | // Push finalize through the stages |
| @@ -861,7 +936,8 @@ public: | @@ -861,7 +936,8 @@ public: | ||
| 861 | final_output.append(output_set); | 936 | final_output.append(output_set); |
| 862 | } | 937 | } |
| 863 | 938 | ||
| 864 | - // dst is set to all output received by the final stage | 939 | + // dst is set to all output received by the final stage, along |
| 940 | + // with anything output via the calls to finalize. | ||
| 865 | dst = collectionStage->getOutput(); | 941 | dst = collectionStage->getOutput(); |
| 866 | dst.append(final_output); | 942 | dst.append(final_output); |
| 867 | 943 | ||
| @@ -873,7 +949,8 @@ public: | @@ -873,7 +949,8 @@ public: | ||
| 873 | virtual void finalize(TemplateList & output) | 949 | virtual void finalize(TemplateList & output) |
| 874 | { | 950 | { |
| 875 | (void) output; | 951 | (void) output; |
| 876 | - // Not handling this yet -cao | 952 | + // Nothing in particular to do here, stream calls finalize |
| 953 | + // on all child transforms as part of projectUpdate | ||
| 877 | } | 954 | } |
| 878 | 955 | ||
| 879 | // Create and link stages | 956 | // Create and link stages |
| @@ -881,6 +958,19 @@ public: | @@ -881,6 +958,19 @@ public: | ||
| 881 | { | 958 | { |
| 882 | if (transforms.isEmpty()) return; | 959 | if (transforms.isEmpty()) return; |
| 883 | 960 | ||
| 961 | + // We share a thread pool across streams attached to the same | ||
| 962 | + // parent tranform, retrieve or create a thread pool based | ||
| 963 | + // on our parent transform. | ||
| 964 | + QMutexLocker poolLock(&poolsAccess); | ||
| 965 | + QHash<QObject *, QThreadPool *>::Iterator it; | ||
| 966 | + if (!pools.contains(this->parent())) { | ||
| 967 | + it = pools.insert(this->parent(), new QThreadPool(this)); | ||
| 968 | + it.value()->setMaxThreadCount(Globals->parallelism); | ||
| 969 | + } | ||
| 970 | + else it = pools.find(this->parent()); | ||
| 971 | + threads = it.value(); | ||
| 972 | + poolLock.unlock(); | ||
| 973 | + | ||
| 884 | stage_variance.reserve(transforms.size()); | 974 | stage_variance.reserve(transforms.size()); |
| 885 | foreach (const br::Transform *transform, transforms) { | 975 | foreach (const br::Transform *transform, transforms) { |
| 886 | stage_variance.append(transform->timeVarying()); | 976 | stage_variance.append(transform->timeVarying()); |
| @@ -891,6 +981,7 @@ public: | @@ -891,6 +981,7 @@ public: | ||
| 891 | processingStages.push_back(readStage); | 981 | processingStages.push_back(readStage); |
| 892 | readStage->stage_id = 0; | 982 | readStage->stage_id = 0; |
| 893 | readStage->stages = &this->processingStages; | 983 | readStage->stages = &this->processingStages; |
| 984 | + readStage->threads = this->threads; | ||
| 894 | 985 | ||
| 895 | int next_stage_id = 1; | 986 | int next_stage_id = 1; |
| 896 | 987 | ||
| @@ -898,9 +989,7 @@ public: | @@ -898,9 +989,7 @@ public: | ||
| 898 | for (int i =0; i < transforms.size(); i++) | 989 | for (int i =0; i < transforms.size(); i++) |
| 899 | { | 990 | { |
| 900 | if (stage_variance[i]) | 991 | if (stage_variance[i]) |
| 901 | - { | ||
| 902 | processingStages.append(new SingleThreadStage(prev_stage_variance)); | 992 | processingStages.append(new SingleThreadStage(prev_stage_variance)); |
| 903 | - } | ||
| 904 | else | 993 | else |
| 905 | processingStages.append(new MultiThreadStage(Globals->parallelism)); | 994 | processingStages.append(new MultiThreadStage(Globals->parallelism)); |
| 906 | 995 | ||
| @@ -911,6 +1000,7 @@ public: | @@ -911,6 +1000,7 @@ public: | ||
| 911 | processingStages[i]->nextStage = processingStages[i+1]; | 1000 | processingStages[i]->nextStage = processingStages[i+1]; |
| 912 | 1001 | ||
| 913 | processingStages.last()->stages = &this->processingStages; | 1002 | processingStages.last()->stages = &this->processingStages; |
| 1003 | + processingStages.last()->threads = this->threads; | ||
| 914 | 1004 | ||
| 915 | processingStages.last()->transform = transforms[i]; | 1005 | processingStages.last()->transform = transforms[i]; |
| 916 | prev_stage_variance = stage_variance[i]; | 1006 | prev_stage_variance = stage_variance[i]; |
| @@ -920,6 +1010,7 @@ public: | @@ -920,6 +1010,7 @@ public: | ||
| 920 | processingStages.append(collectionStage); | 1010 | processingStages.append(collectionStage); |
| 921 | collectionStage->stage_id = next_stage_id; | 1011 | collectionStage->stage_id = next_stage_id; |
| 922 | collectionStage->stages = &this->processingStages; | 1012 | collectionStage->stages = &this->processingStages; |
| 1013 | + collectionStage->threads = this->threads; | ||
| 923 | 1014 | ||
| 924 | processingStages[processingStages.size() - 2]->nextStage = collectionStage; | 1015 | processingStages[processingStages.size() - 2]->nextStage = collectionStage; |
| 925 | 1016 | ||
| @@ -942,6 +1033,10 @@ protected: | @@ -942,6 +1033,10 @@ protected: | ||
| 942 | 1033 | ||
| 943 | QList<ProcessingStage *> processingStages; | 1034 | QList<ProcessingStage *> processingStages; |
| 944 | 1035 | ||
| 1036 | + static QHash<QObject *, QThreadPool *> pools; | ||
| 1037 | + static QMutex poolsAccess; | ||
| 1038 | + QThreadPool * threads; | ||
| 1039 | + | ||
| 945 | void _project(const Template &src, Template &dst) const | 1040 | void _project(const Template &src, Template &dst) const |
| 946 | { | 1041 | { |
| 947 | (void) src; (void) dst; | 1042 | (void) src; (void) dst; |
| @@ -954,6 +1049,9 @@ protected: | @@ -954,6 +1049,9 @@ protected: | ||
| 954 | } | 1049 | } |
| 955 | }; | 1050 | }; |
| 956 | 1051 | ||
| 1052 | +QHash<QObject *, QThreadPool *> StreamTransform::pools; | ||
| 1053 | +QMutex StreamTransform::poolsAccess; | ||
| 1054 | + | ||
| 957 | BR_REGISTER(Transform, StreamTransform) | 1055 | BR_REGISTER(Transform, StreamTransform) |
| 958 | 1056 | ||
| 959 | 1057 |
openbr/plugins/validate.cpp
| @@ -116,7 +116,8 @@ class FilterDistance : public Distance | @@ -116,7 +116,8 @@ class FilterDistance : public Distance | ||
| 116 | foreach (const QString &key, Globals->filters.keys()) { | 116 | foreach (const QString &key, Globals->filters.keys()) { |
| 117 | bool keep = false; | 117 | bool keep = false; |
| 118 | const QString metadata = a.file.get<QString>(key, ""); | 118 | const QString metadata = a.file.get<QString>(key, ""); |
| 119 | - if (metadata.isEmpty() || Globals->filters[key].isEmpty()) continue; | 119 | + if (Globals->filters[key].isEmpty()) continue; |
| 120 | + if (metadata.isEmpty()) return -std::numeric_limits<float>::max(); | ||
| 120 | foreach (const QString &value, Globals->filters[key]) { | 121 | foreach (const QString &value, Globals->filters[key]) { |
| 121 | if (metadata == value) { | 122 | if (metadata == value) { |
| 122 | keep = true; | 123 | keep = true; |
scripts/downloadDatasets.sh
| @@ -63,4 +63,6 @@ if [ ! -d ../data/KTH/vid ]; then | @@ -63,4 +63,6 @@ if [ ! -d ../data/KTH/vid ]; then | ||
| 63 | unzip ${vidclass}.zip -d ../data/KTH/vid/${vidclass} | 63 | unzip ${vidclass}.zip -d ../data/KTH/vid/${vidclass} |
| 64 | rm ${vidclass}.zip | 64 | rm ${vidclass}.zip |
| 65 | done | 65 | done |
| 66 | + # this file is corrupted | ||
| 67 | + rm -f ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi | ||
| 66 | fi | 68 | fi |
share/openbr/openbr.bib
| @@ -34,6 +34,11 @@ | @@ -34,6 +34,11 @@ | ||
| 34 | Howpublished = {https://github.com/lbestrowden}, | 34 | Howpublished = {https://github.com/lbestrowden}, |
| 35 | Title = {bestrow1 at msu.edu}} | 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 | % Software | 42 | % Software |
| 38 | @misc{libface, | 43 | @misc{libface, |
| 39 | Howpublished = {http://libface.sourceforge.net/file/Home.html}, | 44 | Howpublished = {http://libface.sourceforge.net/file/Home.html}, |