Commit e3b533a3541db3043b4a8a8ffc5a7d176397d5db
Merge branch 'master' of https://github.com/biometrics/openbr
Showing
10 changed files
with
292 additions
and
47 deletions
openbr/core/eval.cpp
| @@ -411,35 +411,82 @@ static QStringList computeDetectionResults(const QList<ResolvedDetection> &detec | @@ -411,35 +411,82 @@ static QStringList computeDetectionResults(const QList<ResolvedDetection> &detec | ||
| 411 | QString getDetectKey(const TemplateList &templates) | 411 | QString getDetectKey(const TemplateList &templates) |
| 412 | { | 412 | { |
| 413 | const File &f = templates.first().file; | 413 | const File &f = templates.first().file; |
| 414 | - foreach (const QString &key, f.localKeys()) | 414 | + foreach (const QString &key, f.localKeys()) { |
| 415 | + // first check for single detections | ||
| 415 | if (!f.get<QRectF>(key, QRectF()).isNull()) | 416 | if (!f.get<QRectF>(key, QRectF()).isNull()) |
| 416 | return key; | 417 | return key; |
| 418 | + } | ||
| 419 | + // and then multiple | ||
| 420 | + if (!f.rects().empty()) | ||
| 421 | + return "Rects"; | ||
| 417 | return ""; | 422 | return ""; |
| 418 | } | 423 | } |
| 419 | 424 | ||
| 420 | -float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv) | 425 | +bool detectKeyIsList(QString key, const TemplateList &templates) |
| 421 | { | 426 | { |
| 422 | - qDebug("Evaluating detection of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); | ||
| 423 | - const TemplateList predicted(TemplateList::fromGallery(predictedGallery)); | ||
| 424 | - const TemplateList truth(TemplateList::fromGallery(truthGallery)); | 427 | + return templates.first().file.get<QRectF>(key, QRectF()).isNull(); |
| 428 | +} | ||
| 429 | + | ||
| 430 | +// return a list of detections whether the template holds | ||
| 431 | +// multiple detections or a single detection | ||
| 432 | +QList<Detection> getDetections(QString key, const Template &t, bool isList, bool isTruth) | ||
| 433 | +{ | ||
| 434 | + File f = t.file; | ||
| 435 | + QList<Detection> dets; | ||
| 436 | + if (isList) { | ||
| 437 | + QList<QRectF> rects = f.rects(); | ||
| 438 | + QList<float> confidences = f.getList<float>("Confidences", QList<float>()); | ||
| 439 | + if (!isTruth && rects.size() != confidences.size()) | ||
| 440 | + qFatal("You don't have enough confidence. I mean, your detections don't all have confidence measures."); | ||
| 441 | + for (int i=0; i<rects.size(); i++) { | ||
| 442 | + if (isTruth) | ||
| 443 | + dets.append(Detection(rects.at(i))); | ||
| 444 | + else | ||
| 445 | + dets.append(Detection(rects.at(i), confidences.at(i))); | ||
| 446 | + } | ||
| 447 | + } else { | ||
| 448 | + if (isTruth) { | ||
| 449 | + dets.append(Detection(f.get<QRectF>(key))); | ||
| 450 | + } else { | ||
| 451 | + dets.append(Detection(f.get<QRectF>(key), f.get<float>("Confidence", -1))); | ||
| 452 | + } | ||
| 453 | + } | ||
| 454 | + return dets; | ||
| 455 | +} | ||
| 425 | 456 | ||
| 457 | +QMap<QString, Detections> getDetections(const TemplateList &predicted, const TemplateList &truth) | ||
| 458 | +{ | ||
| 426 | // Figure out which metadata field contains a bounding box | 459 | // Figure out which metadata field contains a bounding box |
| 427 | QString truthDetectKey = getDetectKey(truth); | 460 | QString truthDetectKey = getDetectKey(truth); |
| 428 | if (truthDetectKey.isEmpty()) qFatal("No suitable ground truth metadata key found."); | 461 | if (truthDetectKey.isEmpty()) qFatal("No suitable ground truth metadata key found."); |
| 429 | - QString predictedDetectKey = truthDetectKey; | ||
| 430 | - if (predicted.first().file.get<QRectF>(predictedDetectKey, QRectF()).isNull()) | ||
| 431 | - predictedDetectKey = getDetectKey(predicted); | 462 | + QString predictedDetectKey = getDetectKey(predicted); |
| 432 | if (predictedDetectKey.isEmpty()) qFatal("No suitable predicted metadata key found."); | 463 | if (predictedDetectKey.isEmpty()) qFatal("No suitable predicted metadata key found."); |
| 433 | - | ||
| 434 | qDebug("Using metadata key: %s%s", | 464 | qDebug("Using metadata key: %s%s", |
| 435 | qPrintable(predictedDetectKey), | 465 | qPrintable(predictedDetectKey), |
| 436 | qPrintable(predictedDetectKey == truthDetectKey ? QString() : "/"+truthDetectKey)); | 466 | qPrintable(predictedDetectKey == truthDetectKey ? QString() : "/"+truthDetectKey)); |
| 437 | 467 | ||
| 438 | - QMap<QString, Detections> allDetections; // Organized by file, QMap used to preserve order | ||
| 439 | - foreach (const Template &t, predicted) | ||
| 440 | - allDetections[t.file.baseName()].predicted.append(Detection(t.file.get<QRectF>(predictedDetectKey), t.file.get<float>("Confidence", -1))); | ||
| 441 | - foreach (const Template &t, truth) | ||
| 442 | - allDetections[t.file.baseName()].truth.append(Detection(t.file.get<QRectF>(truthDetectKey))); | 468 | + QMap<QString, Detections> allDetections; |
| 469 | + bool predKeyIsList = detectKeyIsList(predictedDetectKey, predicted); | ||
| 470 | + bool truthKeyIsList = detectKeyIsList(truthDetectKey, truth); | ||
| 471 | + foreach (const Template &t, predicted) { | ||
| 472 | + QList<Detection> dets = getDetections(predictedDetectKey, t, predKeyIsList, false); | ||
| 473 | + allDetections[t.file.baseName()].predicted.append(dets); | ||
| 474 | + } | ||
| 475 | + foreach (const Template &t, truth) { | ||
| 476 | + QList<Detection> dets = getDetections(truthDetectKey, t, truthKeyIsList, true); | ||
| 477 | + allDetections[t.file.baseName()].truth.append(dets); | ||
| 478 | + } | ||
| 479 | + return allDetections; | ||
| 480 | +} | ||
| 481 | + | ||
| 482 | +float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv) | ||
| 483 | +{ | ||
| 484 | + qDebug("Evaluating detection of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); | ||
| 485 | + const TemplateList predicted(TemplateList::fromGallery(predictedGallery)); | ||
| 486 | + const TemplateList truth(TemplateList::fromGallery(truthGallery)); | ||
| 487 | + | ||
| 488 | + // Organized by file, QMap used to preserve order | ||
| 489 | + QMap<QString, Detections> allDetections = getDetections(predicted, truth); | ||
| 443 | 490 | ||
| 444 | QList<ResolvedDetection> resolvedDetections, falseNegativeDetections; | 491 | QList<ResolvedDetection> resolvedDetections, falseNegativeDetections; |
| 445 | foreach (Detections detections, allDetections.values()) { | 492 | foreach (Detections detections, allDetections.values()) { |
openbr/core/plot.cpp
| @@ -343,10 +343,10 @@ bool PlotDetection(const QStringList &files, const File &destination, bool show) | @@ -343,10 +343,10 @@ bool PlotDetection(const QStringList &files, const File &destination, bool show) | ||
| 343 | QString(" + theme(aspect.ratio=1)\n\n"))); | 343 | QString(" + theme(aspect.ratio=1)\n\n"))); |
| 344 | 344 | ||
| 345 | p.file.write(qPrintable(QString("ggplot(AverageOverlap, aes(x=%1, y=%2, label=round(X,3)), main=\"Average Overlap\") + geom_text() + theme_minimal()").arg(p.minor.size > 1 ? p.minor.header : "'X'", p.major.size > 1 ? p.major.header : "'Y'") + | 345 | p.file.write(qPrintable(QString("ggplot(AverageOverlap, aes(x=%1, y=%2, label=round(X,3)), main=\"Average Overlap\") + geom_text() + theme_minimal()").arg(p.minor.size > 1 ? p.minor.header : "'X'", p.major.size > 1 ? p.major.header : "'Y'") + |
| 346 | - QString("%1%2\n\n").arg(p.minor.size > 1 ? "" : " + xlab(NULL)", p.major.size > 1 ? "" : "ylab(NULL)"))); | 346 | + QString("%1%2\n\n").arg(p.minor.size > 1 ? "" : " + xlab(NULL)", p.major.size > 1 ? "" : " + ylab(NULL)"))); |
| 347 | 347 | ||
| 348 | p.file.write(qPrintable(QString("ggplot(AverageOverlap, aes(x=%1, y=%2, fill=X)) + geom_tile() + scale_fill_continuous(\"Average Overlap\") + theme_minimal()").arg(p.minor.size > 1 ? p.minor.header : "'X'", p.major.size > 1 ? p.major.header : "'Y'") + | 348 | p.file.write(qPrintable(QString("ggplot(AverageOverlap, aes(x=%1, y=%2, fill=X)) + geom_tile() + scale_fill_continuous(\"Average Overlap\") + theme_minimal()").arg(p.minor.size > 1 ? p.minor.header : "'X'", p.major.size > 1 ? p.major.header : "'Y'") + |
| 349 | - QString("%1%2\n\n").arg(p.minor.size > 1 ? "" : " + xlab(NULL)", p.major.size > 1 ? "" : "ylab(NULL)"))); | 349 | + QString("%1%2\n\n").arg(p.minor.size > 1 ? "" : " + xlab(NULL)", p.major.size > 1 ? "" : " + ylab(NULL)"))); |
| 350 | 350 | ||
| 351 | return p.finalize(show); | 351 | return p.finalize(show); |
| 352 | } | 352 | } |
openbr/core/qtutils.cpp
| @@ -399,13 +399,33 @@ void showFile(const QString &file) | @@ -399,13 +399,33 @@ void showFile(const QString &file) | ||
| 399 | 399 | ||
| 400 | QString toString(const QVariant &variant) | 400 | QString toString(const QVariant &variant) |
| 401 | { | 401 | { |
| 402 | - if (variant.canConvert(QVariant::String)) return variant.toString(); | ||
| 403 | - else if(variant.canConvert(QVariant::PointF)) return QString("(%1,%2)").arg(QString::number(qvariant_cast<QPointF>(variant).x()), | ||
| 404 | - QString::number(qvariant_cast<QPointF>(variant).y())); | ||
| 405 | - else if (variant.canConvert(QVariant::RectF)) return QString("(%1,%2,%3,%4)").arg(QString::number(qvariant_cast<QRectF>(variant).x()), | ||
| 406 | - QString::number(qvariant_cast<QRectF>(variant).y()), | ||
| 407 | - QString::number(qvariant_cast<QRectF>(variant).width()), | ||
| 408 | - QString::number(qvariant_cast<QRectF>(variant).height())); | 402 | + if (variant.canConvert(QVariant::String)) |
| 403 | + return variant.toString(); | ||
| 404 | + else if(variant.canConvert(QVariant::PointF)) { | ||
| 405 | + QPointF pt = qvariant_cast<QPointF>(variant); | ||
| 406 | + return QString("(%1,%2)").arg(QString::number(pt.x()), | ||
| 407 | + QString::number(pt.y())); | ||
| 408 | + } | ||
| 409 | + else if (variant.canConvert(QVariant::RectF)) { | ||
| 410 | + QRectF rect = qvariant_cast<QRectF>(variant); | ||
| 411 | + return QString("(%1,%2,%3,%4)").arg(QString::number(rect.x()), | ||
| 412 | + QString::number(rect.y()), | ||
| 413 | + QString::number(rect.width()), | ||
| 414 | + QString::number(rect.height())); | ||
| 415 | + } | ||
| 416 | + else if (variant.canConvert(QVariant::List)) { | ||
| 417 | + QString ret = QString("["); | ||
| 418 | + bool first = true; | ||
| 419 | + foreach (const QVariant &i, variant.toList()) { | ||
| 420 | + if (!first) | ||
| 421 | + ret += ","; | ||
| 422 | + else | ||
| 423 | + first = false; | ||
| 424 | + ret += toString(i); | ||
| 425 | + } | ||
| 426 | + ret += "]"; | ||
| 427 | + return ret; | ||
| 428 | + } | ||
| 409 | return QString(); | 429 | return QString(); |
| 410 | } | 430 | } |
| 411 | 431 |
openbr/openbr_plugin.cpp
| @@ -19,6 +19,7 @@ | @@ -19,6 +19,7 @@ | ||
| 19 | #include <QFutureSynchronizer> | 19 | #include <QFutureSynchronizer> |
| 20 | #include <QMetaProperty> | 20 | #include <QMetaProperty> |
| 21 | #include <QPointF> | 21 | #include <QPointF> |
| 22 | +#include <QProcess> | ||
| 22 | #include <QRect> | 23 | #include <QRect> |
| 23 | #include <QRegExp> | 24 | #include <QRegExp> |
| 24 | #include <QThreadPool> | 25 | #include <QThreadPool> |
| @@ -40,6 +41,12 @@ | @@ -40,6 +41,12 @@ | ||
| 40 | using namespace br; | 41 | using namespace br; |
| 41 | using namespace cv; | 42 | using namespace cv; |
| 42 | 43 | ||
| 44 | +// Some globals used to transfer data to Context::messageHandler so that | ||
| 45 | +// we can restart the process if we try and fail to create a QApplication. | ||
| 46 | +static bool creating_qapp = false; | ||
| 47 | +static int * argc_ptr = NULL; | ||
| 48 | +static char ** argv_ptr = NULL; | ||
| 49 | + | ||
| 43 | /* File - public methods */ | 50 | /* File - public methods */ |
| 44 | // Note that the convention for displaying metadata is as follows: | 51 | // Note that the convention for displaying metadata is as follows: |
| 45 | // [] for lists in which argument order does not matter (e.g. [FTO=false, Index=0]), | 52 | // [] for lists in which argument order does not matter (e.g. [FTO=false, Index=0]), |
| @@ -886,14 +893,28 @@ void br::Context::initialize(int &argc, char *argv[], QString sdkPath, bool use_ | @@ -886,14 +893,28 @@ void br::Context::initialize(int &argc, char *argv[], QString sdkPath, bool use_ | ||
| 886 | break; | 893 | break; |
| 887 | } | 894 | } |
| 888 | } | 895 | } |
| 896 | + | ||
| 897 | + qInstallMessageHandler(messageHandler); | ||
| 898 | + | ||
| 889 | // We take in argc as a reference due to: | 899 | // We take in argc as a reference due to: |
| 890 | // https://bugreports.qt-project.org/browse/QTBUG-5637 | 900 | // https://bugreports.qt-project.org/browse/QTBUG-5637 |
| 891 | // QApplication should be initialized before anything else. | 901 | // QApplication should be initialized before anything else. |
| 892 | // Since we can't ensure that it gets deleted last, we never delete it. | 902 | // Since we can't ensure that it gets deleted last, we never delete it. |
| 893 | if (QCoreApplication::instance() == NULL) { | 903 | if (QCoreApplication::instance() == NULL) { |
| 894 | #ifndef BR_EMBEDDED | 904 | #ifndef BR_EMBEDDED |
| 895 | - if (use_gui) application = new QApplication(argc, argv); | ||
| 896 | - else application = new QCoreApplication(argc, argv); | 905 | + if (use_gui) { |
| 906 | + // Set up variables to be used in the message handler if this fails. | ||
| 907 | + // Just so you know, we | ||
| 908 | + creating_qapp = true; | ||
| 909 | + argc_ptr = &argc; | ||
| 910 | + argv_ptr = argv; | ||
| 911 | + | ||
| 912 | + application = new QApplication(argc, argv); | ||
| 913 | + creating_qapp = false; | ||
| 914 | + } | ||
| 915 | + else { | ||
| 916 | + application = new QCoreApplication(argc, argv); | ||
| 917 | + } | ||
| 897 | #else | 918 | #else |
| 898 | application = new QCoreApplication(argc, argv); | 919 | application = new QCoreApplication(argc, argv); |
| 899 | #endif | 920 | #endif |
| @@ -919,7 +940,6 @@ void br::Context::initialize(int &argc, char *argv[], QString sdkPath, bool use_ | @@ -919,7 +940,6 @@ void br::Context::initialize(int &argc, char *argv[], QString sdkPath, bool use_ | ||
| 919 | Globals->init(File()); | 940 | Globals->init(File()); |
| 920 | Globals->useGui = use_gui; | 941 | Globals->useGui = use_gui; |
| 921 | 942 | ||
| 922 | - qInstallMessageHandler(messageHandler); | ||
| 923 | 943 | ||
| 924 | Common::seedRNG(); | 944 | Common::seedRNG(); |
| 925 | 945 | ||
| @@ -988,6 +1008,26 @@ void br::Context::messageHandler(QtMsgType type, const QMessageLogContext &conte | @@ -988,6 +1008,26 @@ void br::Context::messageHandler(QtMsgType type, const QMessageLogContext &conte | ||
| 988 | static QMutex generalLock; | 1008 | static QMutex generalLock; |
| 989 | QMutexLocker locker(&generalLock); | 1009 | QMutexLocker locker(&generalLock); |
| 990 | 1010 | ||
| 1011 | + // If we are trying to create a QApplication, and get a fatal, then restart the process | ||
| 1012 | + // with useGui set to 0. | ||
| 1013 | + if (creating_qapp && type == QtFatalMsg) | ||
| 1014 | + { | ||
| 1015 | + // re-launch process with useGui = 0 | ||
| 1016 | + std::cout << "Failed to initialize gui, restarting with -useGui 0" << std::endl; | ||
| 1017 | + QStringList arguments; | ||
| 1018 | + arguments.append("-useGui"); | ||
| 1019 | + arguments.append("0"); | ||
| 1020 | + for (int i=1; i < *argc_ptr; i++) | ||
| 1021 | + { | ||
| 1022 | + arguments.append(argv_ptr[i]); | ||
| 1023 | + } | ||
| 1024 | + // QProcess::execute blocks until the other process completes. | ||
| 1025 | + QProcess::execute(argv_ptr[0], arguments); | ||
| 1026 | + // have to unlock this for some reason | ||
| 1027 | + locker.unlock(); | ||
| 1028 | + std::exit(0); | ||
| 1029 | + } | ||
| 1030 | + | ||
| 991 | QString txt; | 1031 | QString txt; |
| 992 | if (type == QtDebugMsg) { | 1032 | if (type == QtDebugMsg) { |
| 993 | if (Globals->quiet) return; | 1033 | if (Globals->quiet) return; |
openbr/openbr_plugin.h
| @@ -41,6 +41,8 @@ | @@ -41,6 +41,8 @@ | ||
| 41 | #include <QVector> | 41 | #include <QVector> |
| 42 | #include <opencv2/core/core.hpp> | 42 | #include <opencv2/core/core.hpp> |
| 43 | #include <openbr/openbr.h> | 43 | #include <openbr/openbr.h> |
| 44 | +#include <openbr/core/qtutils.h> | ||
| 45 | +#include <openbr/core/opencvutils.h> | ||
| 44 | 46 | ||
| 45 | /*! | 47 | /*! |
| 46 | * \defgroup cpp_plugin_sdk C++ Plugin SDK | 48 | * \defgroup cpp_plugin_sdk C++ Plugin SDK |
| @@ -215,6 +217,14 @@ struct BR_EXPORT File | @@ -215,6 +217,14 @@ struct BR_EXPORT File | ||
| 215 | static QVariant parse(const QString &value); /*!< \brief Try to convert the QString to a QPointF or QRectF if possible. */ | 217 | static QVariant parse(const QString &value); /*!< \brief Try to convert the QString to a QPointF or QRectF if possible. */ |
| 216 | inline void set(const QString &key, const QVariant &value) { m_metadata.insert(key, value); } /*!< \brief Insert or overwrite the metadata key with the specified value. */ | 218 | inline void set(const QString &key, const QVariant &value) { m_metadata.insert(key, value); } /*!< \brief Insert or overwrite the metadata key with the specified value. */ |
| 217 | void set(const QString &key, const QString &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ | 219 | void set(const QString &key, const QString &value); /*!< \brief Insert or overwrite the metadata key with the specified value. */ |
| 220 | + | ||
| 221 | + /*!< \brief Specialization for list type. Insert or overwrite the metadata key with the specified value. */ | ||
| 222 | + template <typename T> | ||
| 223 | + void setList(const QString &key, const QList<T> &value) | ||
| 224 | + { | ||
| 225 | + set(key, QtUtils::toVariantList(value)); | ||
| 226 | + } | ||
| 227 | + | ||
| 218 | inline void remove(const QString &key) { m_metadata.remove(key); } /*!< \brief Remove the metadata key. */ | 228 | inline void remove(const QString &key) { m_metadata.remove(key); } /*!< \brief Remove the metadata key. */ |
| 219 | 229 | ||
| 220 | /*!< \brief Returns a value for the key, throwing an error if the key does not exist. */ | 230 | /*!< \brief Returns a value for the key, throwing an error if the key does not exist. */ |
| @@ -253,6 +263,19 @@ struct BR_EXPORT File | @@ -253,6 +263,19 @@ struct BR_EXPORT File | ||
| 253 | return list; | 263 | return list; |
| 254 | } | 264 | } |
| 255 | 265 | ||
| 266 | + /*!< \brief Specialization for list type. Returns a list of type T for the key, returning \em defaultValue if the key does not exist or can't be converted. */ | ||
| 267 | + template <typename T> | ||
| 268 | + QList<T> getList(const QString &key, const QList<T> defaultValue) const | ||
| 269 | + { | ||
| 270 | + if (!contains(key)) return defaultValue; | ||
| 271 | + QList<T> list; | ||
| 272 | + foreach (const QVariant &item, m_metadata[key].toList()) { | ||
| 273 | + if (item.canConvert<T>()) list.append(item.value<T>()); | ||
| 274 | + else return defaultValue; | ||
| 275 | + } | ||
| 276 | + return list; | ||
| 277 | + } | ||
| 278 | + | ||
| 256 | /*!< \brief Returns the value for the specified key for every file in the list. */ | 279 | /*!< \brief Returns the value for the specified key for every file in the list. */ |
| 257 | template<class U> | 280 | template<class U> |
| 258 | static QList<QVariant> values(const QList<U> &fileList, const QString &key) | 281 | static QList<QVariant> values(const QList<U> &fileList, const QString &key) |
| @@ -292,9 +315,12 @@ struct BR_EXPORT File | @@ -292,9 +315,12 @@ struct BR_EXPORT File | ||
| 292 | QList<QRectF> namedRects() const; /*!< \brief Returns rects convertible from metadata values. */ | 315 | QList<QRectF> namedRects() const; /*!< \brief Returns rects convertible from metadata values. */ |
| 293 | QList<QRectF> rects() const; /*!< \brief Returns the file's rects list. */ | 316 | QList<QRectF> rects() const; /*!< \brief Returns the file's rects list. */ |
| 294 | void appendRect(const QRectF &rect); /*!< \brief Adds a rect to the file's rect list. */ | 317 | void appendRect(const QRectF &rect); /*!< \brief Adds a rect to the file's rect list. */ |
| 318 | + void appendRect(const cv::Rect &rect) { appendRect(OpenCVUtils::fromRect(rect)); } /*!< \brief Adds a rect to the file's rect list. */ | ||
| 295 | void appendRects(const QList<QRectF> &rects); /*!< \brief Adds rects to the file's rect list. */ | 319 | void appendRects(const QList<QRectF> &rects); /*!< \brief Adds rects to the file's rect list. */ |
| 320 | + void appendRects(const QList<cv::Rect> &rects) { appendRects(OpenCVUtils::fromRects(rects)); } /*!< \brief Adds rects to the file's rect list. */ | ||
| 296 | inline void clearRects() { m_metadata["Rects"] = QList<QVariant>(); } /*!< \brief Clears the file's rect list. */ | 321 | inline void clearRects() { m_metadata["Rects"] = QList<QVariant>(); } /*!< \brief Clears the file's rect list. */ |
| 297 | inline void setRects(const QList<QRectF> &rects) { clearRects(); appendRects(rects); } /*!< \brief Overwrites the file's rect list. */ | 322 | inline void setRects(const QList<QRectF> &rects) { clearRects(); appendRects(rects); } /*!< \brief Overwrites the file's rect list. */ |
| 323 | + inline void setRects(const QList<cv::Rect> &rects) { clearRects(); appendRects(rects); } /*!< \brief Overwrites the file's rect list. */ | ||
| 298 | 324 | ||
| 299 | private: | 325 | private: |
| 300 | QMap<QString,QVariant> m_metadata; | 326 | QMap<QString,QVariant> m_metadata; |
openbr/plugins/gallery.cpp
| @@ -857,18 +857,21 @@ class FDDBGallery : public Gallery | @@ -857,18 +857,21 @@ class FDDBGallery : public Gallery | ||
| 857 | for (int i=0; i<numDetects; i++) { | 857 | for (int i=0; i<numDetects; i++) { |
| 858 | const QStringList detect = lines.takeFirst().split(' '); | 858 | const QStringList detect = lines.takeFirst().split(' '); |
| 859 | Template t(fileName); | 859 | Template t(fileName); |
| 860 | + QList<QVariant> faceList; //to be consistent with slidingWindow | ||
| 860 | if (detect.size() == 5) { //rectangle | 861 | if (detect.size() == 5) { //rectangle |
| 861 | - t.file.set("Face", QRectF(detect[0].toFloat(), detect[1].toFloat(), detect[2].toFloat(), detect[3].toFloat())); | 862 | + faceList.append(QRectF(detect[0].toFloat(), detect[1].toFloat(), detect[2].toFloat(), detect[3].toFloat())); |
| 862 | t.file.set("Confidence", detect[4].toFloat()); | 863 | t.file.set("Confidence", detect[4].toFloat()); |
| 863 | } else if (detect.size() == 6) { //ellipse | 864 | } else if (detect.size() == 6) { //ellipse |
| 864 | float x = detect[3].toFloat(), | 865 | float x = detect[3].toFloat(), |
| 865 | y = detect[4].toFloat(), | 866 | y = detect[4].toFloat(), |
| 866 | radius = detect[1].toFloat(); | 867 | radius = detect[1].toFloat(); |
| 867 | - t.file.set("Face", QRectF(x - radius,y - radius,radius * 2.0, radius * 2.0)); | 868 | + faceList.append(QRectF(x - radius,y - radius,radius * 2.0, radius * 2.0)); |
| 868 | t.file.set("Confidence", detect[5].toFloat()); | 869 | t.file.set("Confidence", detect[5].toFloat()); |
| 869 | } else { | 870 | } else { |
| 870 | qFatal("Unknown FDDB annotation format."); | 871 | qFatal("Unknown FDDB annotation format."); |
| 871 | } | 872 | } |
| 873 | + t.file.set("Face", faceList); | ||
| 874 | + t.file.set("Label",QString("face")); | ||
| 872 | templates.append(t); | 875 | templates.append(t); |
| 873 | } | 876 | } |
| 874 | } | 877 | } |
openbr/plugins/landmarks.cpp
| @@ -113,7 +113,7 @@ class ProcrustesTransform : public Transform | @@ -113,7 +113,7 @@ class ProcrustesTransform : public Transform | ||
| 113 | // R(0,0), R(1,0), R(1,1), R(0,1), mean_x, mean_y, norm | 113 | // R(0,0), R(1,0), R(1,1), R(0,1), mean_x, mean_y, norm |
| 114 | QList<float> procrustesStats; | 114 | QList<float> procrustesStats; |
| 115 | procrustesStats << R(0,0) << R(1,0) << R(1,1) << R(0,1) << mean[0] << mean[1] << norm; | 115 | procrustesStats << R(0,0) << R(1,0) << R(1,1) << R(0,1) << mean[0] << mean[1] << norm; |
| 116 | - dst.file.set("ProcrustesStats",QtUtils::toVariantList(procrustesStats)); | 116 | + dst.file.setList<float>("ProcrustesStats",procrustesStats); |
| 117 | 117 | ||
| 118 | if (warp) { | 118 | if (warp) { |
| 119 | Eigen::MatrixXf dstMat = srcMat*R; | 119 | Eigen::MatrixXf dstMat = srcMat*R; |
| @@ -273,7 +273,7 @@ class DelaunayTransform : public UntrainableTransform | @@ -273,7 +273,7 @@ class DelaunayTransform : public UntrainableTransform | ||
| 273 | dst.file.setRects(QList<QRectF>() << OpenCVUtils::fromRect(boundingBox)); | 273 | dst.file.setRects(QList<QRectF>() << OpenCVUtils::fromRect(boundingBox)); |
| 274 | } else dst = src; | 274 | } else dst = src; |
| 275 | 275 | ||
| 276 | - dst.file.set("DelaunayTriangles", QtUtils::toVariantList(validTriangles)); | 276 | + dst.file.setList<QPointF>("DelaunayTriangles", validTriangles); |
| 277 | } | 277 | } |
| 278 | }; | 278 | }; |
| 279 | 279 |
openbr/plugins/regions.cpp
| @@ -53,7 +53,6 @@ class RectRegionsTransform : public UntrainableTransform | @@ -53,7 +53,6 @@ class RectRegionsTransform : public UntrainableTransform | ||
| 53 | dst += m(Rect(x, y, width, height)); | 53 | dst += m(Rect(x, y, width, height)); |
| 54 | } | 54 | } |
| 55 | }; | 55 | }; |
| 56 | - | ||
| 57 | BR_REGISTER(Transform, RectRegionsTransform) | 56 | BR_REGISTER(Transform, RectRegionsTransform) |
| 58 | 57 | ||
| 59 | /*! | 58 | /*! |
openbr/plugins/slidingwindow.cpp
| 1 | #include "openbr_internal.h" | 1 | #include "openbr_internal.h" |
| 2 | #include "openbr/core/opencvutils.h" | 2 | #include "openbr/core/opencvutils.h" |
| 3 | #include "openbr/core/common.h" | 3 | #include "openbr/core/common.h" |
| 4 | +#include "openbr/core/qtutils.h" | ||
| 5 | +#include <opencv2/objdetect/objdetect.hpp> | ||
| 6 | +#include <opencv2/imgproc/imgproc.hpp> | ||
| 7 | +#include <opencv2/highgui/highgui.hpp> | ||
| 4 | 8 | ||
| 5 | using namespace cv; | 9 | using namespace cv; |
| 6 | 10 | ||
| 11 | +// Because MSVC doesn't provide a round() function in math.h | ||
| 12 | +static int round(float x) { return (floor(x + 0.5)); } | ||
| 13 | + | ||
| 7 | namespace br | 14 | namespace br |
| 8 | { | 15 | { |
| 9 | 16 | ||
| @@ -19,31 +26,65 @@ class SlidingWindowTransform : public Transform | @@ -19,31 +26,65 @@ class SlidingWindowTransform : public Transform | ||
| 19 | Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false) | 26 | Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false) |
| 20 | Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) | 27 | Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) |
| 21 | Q_PROPERTY(double scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) | 28 | Q_PROPERTY(double scaleFactor READ get_scaleFactor WRITE set_scaleFactor RESET reset_scaleFactor STORED false) |
| 22 | - Q_PROPERTY(double stepSize READ get_stepSize WRITE set_stepSize RESET reset_stepSize STORED false) | 29 | + Q_PROPERTY(int stepSize READ get_stepSize WRITE set_stepSize RESET reset_stepSize STORED false) |
| 23 | Q_PROPERTY(bool takeLargestScale READ get_takeLargestScale WRITE set_takeLargestScale RESET reset_takeLargestScale STORED false) | 30 | Q_PROPERTY(bool takeLargestScale READ get_takeLargestScale WRITE set_takeLargestScale RESET reset_takeLargestScale STORED false) |
| 24 | Q_PROPERTY(bool negSamples READ get_negSamples WRITE set_negSamples RESET reset_negSamples STORED false) | 31 | Q_PROPERTY(bool negSamples READ get_negSamples WRITE set_negSamples RESET reset_negSamples STORED false) |
| 25 | Q_PROPERTY(int negToPosRatio READ get_negToPosRatio WRITE set_negToPosRatio RESET reset_negToPosRatio STORED false) | 32 | Q_PROPERTY(int negToPosRatio READ get_negToPosRatio WRITE set_negToPosRatio RESET reset_negToPosRatio STORED false) |
| 26 | Q_PROPERTY(double maxOverlap READ get_maxOverlap WRITE set_maxOverlap RESET reset_maxOverlap STORED false) | 33 | Q_PROPERTY(double maxOverlap READ get_maxOverlap WRITE set_maxOverlap RESET reset_maxOverlap STORED false) |
| 34 | + Q_PROPERTY(float aspectRatio READ get_aspectRatio WRITE set_aspectRatio RESET reset_aspectRatio STORED true) | ||
| 35 | + Q_PROPERTY(int windowWidth READ get_windowWidth WRITE set_windowWidth RESET reset_windowWidth STORED false) | ||
| 27 | BR_PROPERTY(br::Transform *, transform, NULL) | 36 | BR_PROPERTY(br::Transform *, transform, NULL) |
| 28 | BR_PROPERTY(int, minSize, 8) | 37 | BR_PROPERTY(int, minSize, 8) |
| 29 | BR_PROPERTY(double, scaleFactor, 0.75) | 38 | BR_PROPERTY(double, scaleFactor, 0.75) |
| 30 | - BR_PROPERTY(double, stepSize, 1) | 39 | + BR_PROPERTY(int, stepSize, 1) |
| 31 | BR_PROPERTY(bool, takeLargestScale, true) | 40 | BR_PROPERTY(bool, takeLargestScale, true) |
| 32 | BR_PROPERTY(bool, negSamples, true) | 41 | BR_PROPERTY(bool, negSamples, true) |
| 33 | BR_PROPERTY(int, negToPosRatio, 1) | 42 | BR_PROPERTY(int, negToPosRatio, 1) |
| 34 | BR_PROPERTY(double, maxOverlap, 0) | 43 | BR_PROPERTY(double, maxOverlap, 0) |
| 44 | + BR_PROPERTY(float, aspectRatio, 1) | ||
| 45 | + BR_PROPERTY(int, windowWidth, 24) | ||
| 35 | 46 | ||
| 36 | public: | 47 | public: |
| 37 | SlidingWindowTransform() : Transform(false, true) {} | 48 | SlidingWindowTransform() : Transform(false, true) {} |
| 38 | - | ||
| 39 | private: | 49 | private: |
| 50 | + | ||
| 40 | void train(const TemplateList &data) | 51 | void train(const TemplateList &data) |
| 41 | { | 52 | { |
| 42 | if (transform->trainable) { | 53 | if (transform->trainable) { |
| 54 | + double tempRatio = 0; | ||
| 55 | + int ratioCnt = 0; | ||
| 43 | TemplateList full; | 56 | TemplateList full; |
| 57 | + | ||
| 58 | + //First find avg aspect ratio | ||
| 59 | + foreach (const Template &tmpl, data) { | ||
| 60 | + QList<Rect> posRects = OpenCVUtils::toRects(tmpl.file.rects()); | ||
| 61 | + foreach (const Rect &posRect, posRects) { | ||
| 62 | + if (posRect.x + posRect.width >= tmpl.m().cols || posRect.y + posRect.height >= tmpl.m().rows || posRect.x < 0 || posRect.y < 0) { | ||
| 63 | + continue; | ||
| 64 | + } | ||
| 65 | + tempRatio += (float)posRect.width / (float)posRect.height; | ||
| 66 | + ratioCnt += 1; | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | + aspectRatio = tempRatio / (double)ratioCnt; | ||
| 70 | + | ||
| 44 | foreach (const Template &tmpl, data) { | 71 | foreach (const Template &tmpl, data) { |
| 45 | - foreach (const Rect &rect, OpenCVUtils::toRects(tmpl.file.rects())) { | ||
| 46 | - Template pos(tmpl.file, Mat(tmpl, rect)); | 72 | + QList<Rect> posRects = OpenCVUtils::toRects(tmpl.file.rects()); |
| 73 | + QList<Rect> negRects; | ||
| 74 | + foreach (Rect posRect, posRects) { | ||
| 75 | + | ||
| 76 | + //Adjust for training samples that have different aspect ratios | ||
| 77 | + int diff = posRect.width - (int)((float) posRect.height * aspectRatio); | ||
| 78 | + posRect.x += diff / 2; | ||
| 79 | + posRect.width += diff; | ||
| 80 | + | ||
| 81 | + if (posRect.x + posRect.width >= tmpl.m().cols || posRect.y + posRect.height >= tmpl.m().rows || posRect.x < 0 || posRect.y < 0) { | ||
| 82 | + continue; | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + Mat scaledImg; | ||
| 86 | + resize(Mat(tmpl, posRect), scaledImg, Size(windowWidth,round(windowWidth / aspectRatio))); | ||
| 87 | + Template pos(tmpl.file, scaledImg); | ||
| 47 | full += pos; | 88 | full += pos; |
| 48 | 89 | ||
| 49 | // add random negative samples | 90 | // add random negative samples |
| @@ -57,9 +98,11 @@ private: | @@ -57,9 +98,11 @@ private: | ||
| 57 | int maxSize = std::min(maxWidth, maxHeight); | 98 | int maxSize = std::min(maxWidth, maxHeight); |
| 58 | int size = (maxSize <= minSize ? maxSize : Common::RandSample(1, maxSize, minSize)[0]); | 99 | int size = (maxSize <= minSize ? maxSize : Common::RandSample(1, maxSize, minSize)[0]); |
| 59 | Rect negRect(x, y, size, size); | 100 | Rect negRect(x, y, size, size); |
| 60 | - Rect intersect = negRect & rect; | ||
| 61 | - if (intersect.area() > maxOverlap*rect.area()) | 101 | + // the negative samples cannot overlap the positive at all |
| 102 | + // but they may overlap with other negatives | ||
| 103 | + if (overlaps(posRects, negRect, 0) || overlaps(negRects, negRect, maxOverlap)) | ||
| 62 | continue; | 104 | continue; |
| 105 | + negRects.append(negRect); | ||
| 63 | Template neg(tmpl.file, Mat(tmpl, negRect)); | 106 | Template neg(tmpl.file, Mat(tmpl, negRect)); |
| 64 | neg.file.set("Label", QString("neg")); | 107 | neg.file.set("Label", QString("neg")); |
| 65 | full += neg; | 108 | full += neg; |
| @@ -72,6 +115,16 @@ private: | @@ -72,6 +115,16 @@ private: | ||
| 72 | } | 115 | } |
| 73 | } | 116 | } |
| 74 | 117 | ||
| 118 | + bool overlaps(QList<Rect> posRects, Rect negRect, double overlap) | ||
| 119 | + { | ||
| 120 | + foreach (const Rect posRect, posRects) { | ||
| 121 | + Rect intersect = negRect & posRect; | ||
| 122 | + if (intersect.area() > overlap*posRect.area()) | ||
| 123 | + return true; | ||
| 124 | + } | ||
| 125 | + return false; | ||
| 126 | + } | ||
| 127 | + | ||
| 75 | void project(const Template &src, Template &dst) const | 128 | void project(const Template &src, Template &dst) const |
| 76 | { | 129 | { |
| 77 | dst = src; | 130 | dst = src; |
| @@ -79,17 +132,33 @@ private: | @@ -79,17 +132,33 @@ private: | ||
| 79 | if (src.file.getBool("Train", false)) return; | 132 | if (src.file.getBool("Train", false)) return; |
| 80 | 133 | ||
| 81 | dst.file.clearRects(); | 134 | dst.file.clearRects(); |
| 82 | - int rows = src.m().rows, cols = src.m().cols; | ||
| 83 | - for (double size=std::min(rows, cols); size>=minSize; size*=scaleFactor) { | ||
| 84 | - for (double y=0; y+size<rows; y+=(size*stepSize)) { | ||
| 85 | - for (double x=0; x+size<cols; x+=(size*stepSize)) { | ||
| 86 | - Rect window(x, y, size, size); | ||
| 87 | - Template windowMat(src.file, Mat(src.m(), window)); | 135 | + int rows = src.m().rows; |
| 136 | + int cols = src.m().cols; | ||
| 137 | + int windowHeight = (int) round((float) windowWidth / aspectRatio); | ||
| 138 | + float startScale; | ||
| 139 | + if ((cols / rows) > aspectRatio) | ||
| 140 | + startScale = round((float) rows / (float) windowHeight); | ||
| 141 | + else | ||
| 142 | + startScale = round((float) cols / (float) windowWidth); | ||
| 143 | + | ||
| 144 | + for (float scale = startScale; scale >= 1.0; scale -= (1.0 - scaleFactor)) { | ||
| 145 | + Mat scaleImg; | ||
| 146 | + resize(src, scaleImg, Size(round(cols / scale), round(rows / scale))); | ||
| 147 | + | ||
| 148 | + for (double y = 0; y + windowHeight < scaleImg.rows; y += stepSize) { | ||
| 149 | + for (double x = 0; x + windowWidth < scaleImg.cols; x += stepSize) { | ||
| 150 | +qDebug() << "x=" << x << "\ty=" << y; | ||
| 151 | + Rect window(x, y, windowWidth, windowHeight); | ||
| 152 | + Template windowMat(src.file, Mat(scaleImg, window)); | ||
| 88 | Template detect; | 153 | Template detect; |
| 89 | transform->project(windowMat, detect); | 154 | transform->project(windowMat, detect); |
| 90 | // the result will be in the Label | 155 | // the result will be in the Label |
| 91 | - if (detect.file.get<QString>(QString("Label")) == "pos") { | ||
| 92 | - dst.file.appendRect(OpenCVUtils::fromRect(window)); | 156 | + if (detect.file.get<QString>("Label") == "pos") { |
| 157 | + dst.file.appendRect(QRectF((float) x * scale, (float) y * scale, (float) windowWidth * scale, (float) windowHeight * scale)); | ||
| 158 | + float confidence = detect.file.get<float>("Dist"); | ||
| 159 | + QList<float> confidences = dst.file.getList<float>("Confidences", QList<float>()); | ||
| 160 | + confidences.append(confidence); | ||
| 161 | + dst.file.setList<float>("Confidences", confidences); | ||
| 93 | if (takeLargestScale) return; | 162 | if (takeLargestScale) return; |
| 94 | } | 163 | } |
| 95 | } | 164 | } |
| @@ -100,6 +169,36 @@ private: | @@ -100,6 +169,36 @@ private: | ||
| 100 | 169 | ||
| 101 | BR_REGISTER(Transform, SlidingWindowTransform) | 170 | BR_REGISTER(Transform, SlidingWindowTransform) |
| 102 | 171 | ||
| 172 | +/*! | ||
| 173 | + * \ingroup transforms | ||
| 174 | + * \brief Detects objects with OpenCV's built-in HOG detection. | ||
| 175 | + * \author Austin Blanton \cite imaus10 | ||
| 176 | + */ | ||
| 177 | +class HOGDetectTransform : public UntrainableTransform | ||
| 178 | +{ | ||
| 179 | + Q_OBJECT | ||
| 180 | + | ||
| 181 | + HOGDescriptor hog; | ||
| 182 | + | ||
| 183 | + void init() | ||
| 184 | + { | ||
| 185 | + hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector()); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + void project(const Template &src, Template &dst) const | ||
| 189 | + { | ||
| 190 | + dst = src; | ||
| 191 | + std::vector<Rect> objLocs; | ||
| 192 | + QList<Rect> rects; | ||
| 193 | + hog.detectMultiScale(src, objLocs); | ||
| 194 | + foreach (const Rect &obj, objLocs) | ||
| 195 | + rects.append(obj); | ||
| 196 | + dst.file.setRects(rects); | ||
| 197 | + } | ||
| 198 | +}; | ||
| 199 | + | ||
| 200 | +BR_REGISTER(Transform, HOGDetectTransform) | ||
| 201 | + | ||
| 103 | } // namespace br | 202 | } // namespace br |
| 104 | 203 | ||
| 105 | #include "slidingwindow.moc" | 204 | #include "slidingwindow.moc" |
openbr/plugins/svm.cpp
| @@ -103,6 +103,7 @@ class SVMTransform : public Transform | @@ -103,6 +103,7 @@ class SVMTransform : public Transform | ||
| 103 | Q_PROPERTY(float gamma READ get_gamma WRITE set_gamma RESET reset_gamma STORED false) | 103 | Q_PROPERTY(float gamma READ get_gamma WRITE set_gamma RESET reset_gamma STORED false) |
| 104 | Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) | 104 | Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) |
| 105 | Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false) | 105 | Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false) |
| 106 | + Q_PROPERTY(bool returnDFVal READ get_returnDFVal WRITE set_returnDFVal RESET reset_returnDFVal STORED false) | ||
| 106 | 107 | ||
| 107 | public: | 108 | public: |
| 108 | enum Kernel { Linear = CvSVM::LINEAR, | 109 | enum Kernel { Linear = CvSVM::LINEAR, |
| @@ -123,6 +124,7 @@ private: | @@ -123,6 +124,7 @@ private: | ||
| 123 | BR_PROPERTY(float, gamma, -1) | 124 | BR_PROPERTY(float, gamma, -1) |
| 124 | BR_PROPERTY(QString, inputVariable, "") | 125 | BR_PROPERTY(QString, inputVariable, "") |
| 125 | BR_PROPERTY(QString, outputVariable, "") | 126 | BR_PROPERTY(QString, outputVariable, "") |
| 127 | + BR_PROPERTY(bool, returnDFVal, false) | ||
| 126 | 128 | ||
| 127 | 129 | ||
| 128 | SVM svm; | 130 | SVM svm; |
| @@ -149,8 +151,17 @@ private: | @@ -149,8 +151,17 @@ private: | ||
| 149 | 151 | ||
| 150 | void project(const Template &src, Template &dst) const | 152 | void project(const Template &src, Template &dst) const |
| 151 | { | 153 | { |
| 154 | + if (returnDFVal && reverseLookup.size() > 2) | ||
| 155 | + qFatal("Decision function for multiclass classification not implemented."); | ||
| 156 | + | ||
| 152 | dst = src; | 157 | dst = src; |
| 153 | - float prediction = svm.predict(src.m().reshape(1, 1)); | 158 | + float prediction = svm.predict(src.m().reshape(1, 1), returnDFVal); |
| 159 | + if (returnDFVal) { | ||
| 160 | + dst.file.set("Dist", prediction); | ||
| 161 | + // positive values ==> first class | ||
| 162 | + // negative values ==> second class | ||
| 163 | + prediction = prediction > 0 ? 0 : 1; | ||
| 164 | + } | ||
| 154 | if (type == EPS_SVR || type == NU_SVR) | 165 | if (type == EPS_SVR || type == NU_SVR) |
| 155 | dst.file.set(outputVariable, prediction); | 166 | dst.file.set(outputVariable, prediction); |
| 156 | else | 167 | else |