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 | 411 | QString getDetectKey(const TemplateList &templates) |
| 412 | 412 | { |
| 413 | 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 | 416 | if (!f.get<QRectF>(key, QRectF()).isNull()) |
| 416 | 417 | return key; |
| 418 | + } | |
| 419 | + // and then multiple | |
| 420 | + if (!f.rects().empty()) | |
| 421 | + return "Rects"; | |
| 417 | 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 | 459 | // Figure out which metadata field contains a bounding box |
| 427 | 460 | QString truthDetectKey = getDetectKey(truth); |
| 428 | 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 | 463 | if (predictedDetectKey.isEmpty()) qFatal("No suitable predicted metadata key found."); |
| 433 | - | |
| 434 | 464 | qDebug("Using metadata key: %s%s", |
| 435 | 465 | qPrintable(predictedDetectKey), |
| 436 | 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 | 491 | QList<ResolvedDetection> resolvedDetections, falseNegativeDetections; |
| 445 | 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 | 343 | QString(" + theme(aspect.ratio=1)\n\n"))); |
| 344 | 344 | |
| 345 | 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 | 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 | 351 | return p.finalize(show); |
| 352 | 352 | } | ... | ... |
openbr/core/qtutils.cpp
| ... | ... | @@ -399,13 +399,33 @@ void showFile(const QString &file) |
| 399 | 399 | |
| 400 | 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 | 429 | return QString(); |
| 410 | 430 | } |
| 411 | 431 | ... | ... |
openbr/openbr_plugin.cpp
| ... | ... | @@ -19,6 +19,7 @@ |
| 19 | 19 | #include <QFutureSynchronizer> |
| 20 | 20 | #include <QMetaProperty> |
| 21 | 21 | #include <QPointF> |
| 22 | +#include <QProcess> | |
| 22 | 23 | #include <QRect> |
| 23 | 24 | #include <QRegExp> |
| 24 | 25 | #include <QThreadPool> |
| ... | ... | @@ -40,6 +41,12 @@ |
| 40 | 41 | using namespace br; |
| 41 | 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 | 50 | /* File - public methods */ |
| 44 | 51 | // Note that the convention for displaying metadata is as follows: |
| 45 | 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 | 893 | break; |
| 887 | 894 | } |
| 888 | 895 | } |
| 896 | + | |
| 897 | + qInstallMessageHandler(messageHandler); | |
| 898 | + | |
| 889 | 899 | // We take in argc as a reference due to: |
| 890 | 900 | // https://bugreports.qt-project.org/browse/QTBUG-5637 |
| 891 | 901 | // QApplication should be initialized before anything else. |
| 892 | 902 | // Since we can't ensure that it gets deleted last, we never delete it. |
| 893 | 903 | if (QCoreApplication::instance() == NULL) { |
| 894 | 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 | 918 | #else |
| 898 | 919 | application = new QCoreApplication(argc, argv); |
| 899 | 920 | #endif |
| ... | ... | @@ -919,7 +940,6 @@ void br::Context::initialize(int &argc, char *argv[], QString sdkPath, bool use_ |
| 919 | 940 | Globals->init(File()); |
| 920 | 941 | Globals->useGui = use_gui; |
| 921 | 942 | |
| 922 | - qInstallMessageHandler(messageHandler); | |
| 923 | 943 | |
| 924 | 944 | Common::seedRNG(); |
| 925 | 945 | |
| ... | ... | @@ -988,6 +1008,26 @@ void br::Context::messageHandler(QtMsgType type, const QMessageLogContext &conte |
| 988 | 1008 | static QMutex generalLock; |
| 989 | 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 | 1031 | QString txt; |
| 992 | 1032 | if (type == QtDebugMsg) { |
| 993 | 1033 | if (Globals->quiet) return; | ... | ... |
openbr/openbr_plugin.h
| ... | ... | @@ -41,6 +41,8 @@ |
| 41 | 41 | #include <QVector> |
| 42 | 42 | #include <opencv2/core/core.hpp> |
| 43 | 43 | #include <openbr/openbr.h> |
| 44 | +#include <openbr/core/qtutils.h> | |
| 45 | +#include <openbr/core/opencvutils.h> | |
| 44 | 46 | |
| 45 | 47 | /*! |
| 46 | 48 | * \defgroup cpp_plugin_sdk C++ Plugin SDK |
| ... | ... | @@ -215,6 +217,14 @@ struct BR_EXPORT File |
| 215 | 217 | static QVariant parse(const QString &value); /*!< \brief Try to convert the QString to a QPointF or QRectF if possible. */ |
| 216 | 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 | 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 | 228 | inline void remove(const QString &key) { m_metadata.remove(key); } /*!< \brief Remove the metadata key. */ |
| 219 | 229 | |
| 220 | 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 | 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 | 279 | /*!< \brief Returns the value for the specified key for every file in the list. */ |
| 257 | 280 | template<class U> |
| 258 | 281 | static QList<QVariant> values(const QList<U> &fileList, const QString &key) |
| ... | ... | @@ -292,9 +315,12 @@ struct BR_EXPORT File |
| 292 | 315 | QList<QRectF> namedRects() const; /*!< \brief Returns rects convertible from metadata values. */ |
| 293 | 316 | QList<QRectF> rects() const; /*!< \brief Returns the file's rects list. */ |
| 294 | 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 | 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 | 321 | inline void clearRects() { m_metadata["Rects"] = QList<QVariant>(); } /*!< \brief Clears the file's rect list. */ |
| 297 | 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 | 325 | private: |
| 300 | 326 | QMap<QString,QVariant> m_metadata; | ... | ... |
openbr/plugins/gallery.cpp
| ... | ... | @@ -857,18 +857,21 @@ class FDDBGallery : public Gallery |
| 857 | 857 | for (int i=0; i<numDetects; i++) { |
| 858 | 858 | const QStringList detect = lines.takeFirst().split(' '); |
| 859 | 859 | Template t(fileName); |
| 860 | + QList<QVariant> faceList; //to be consistent with slidingWindow | |
| 860 | 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 | 863 | t.file.set("Confidence", detect[4].toFloat()); |
| 863 | 864 | } else if (detect.size() == 6) { //ellipse |
| 864 | 865 | float x = detect[3].toFloat(), |
| 865 | 866 | y = detect[4].toFloat(), |
| 866 | 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 | 869 | t.file.set("Confidence", detect[5].toFloat()); |
| 869 | 870 | } else { |
| 870 | 871 | qFatal("Unknown FDDB annotation format."); |
| 871 | 872 | } |
| 873 | + t.file.set("Face", faceList); | |
| 874 | + t.file.set("Label",QString("face")); | |
| 872 | 875 | templates.append(t); |
| 873 | 876 | } |
| 874 | 877 | } | ... | ... |
openbr/plugins/landmarks.cpp
| ... | ... | @@ -113,7 +113,7 @@ class ProcrustesTransform : public Transform |
| 113 | 113 | // R(0,0), R(1,0), R(1,1), R(0,1), mean_x, mean_y, norm |
| 114 | 114 | QList<float> procrustesStats; |
| 115 | 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 | 118 | if (warp) { |
| 119 | 119 | Eigen::MatrixXf dstMat = srcMat*R; |
| ... | ... | @@ -273,7 +273,7 @@ class DelaunayTransform : public UntrainableTransform |
| 273 | 273 | dst.file.setRects(QList<QRectF>() << OpenCVUtils::fromRect(boundingBox)); |
| 274 | 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
openbr/plugins/slidingwindow.cpp
| 1 | 1 | #include "openbr_internal.h" |
| 2 | 2 | #include "openbr/core/opencvutils.h" |
| 3 | 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 | 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 | 14 | namespace br |
| 8 | 15 | { |
| 9 | 16 | |
| ... | ... | @@ -19,31 +26,65 @@ class SlidingWindowTransform : public Transform |
| 19 | 26 | Q_PROPERTY(br::Transform *transform READ get_transform WRITE set_transform RESET reset_transform STORED false) |
| 20 | 27 | Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) |
| 21 | 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 | 30 | Q_PROPERTY(bool takeLargestScale READ get_takeLargestScale WRITE set_takeLargestScale RESET reset_takeLargestScale STORED false) |
| 24 | 31 | Q_PROPERTY(bool negSamples READ get_negSamples WRITE set_negSamples RESET reset_negSamples STORED false) |
| 25 | 32 | Q_PROPERTY(int negToPosRatio READ get_negToPosRatio WRITE set_negToPosRatio RESET reset_negToPosRatio STORED false) |
| 26 | 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 | 36 | BR_PROPERTY(br::Transform *, transform, NULL) |
| 28 | 37 | BR_PROPERTY(int, minSize, 8) |
| 29 | 38 | BR_PROPERTY(double, scaleFactor, 0.75) |
| 30 | - BR_PROPERTY(double, stepSize, 1) | |
| 39 | + BR_PROPERTY(int, stepSize, 1) | |
| 31 | 40 | BR_PROPERTY(bool, takeLargestScale, true) |
| 32 | 41 | BR_PROPERTY(bool, negSamples, true) |
| 33 | 42 | BR_PROPERTY(int, negToPosRatio, 1) |
| 34 | 43 | BR_PROPERTY(double, maxOverlap, 0) |
| 44 | + BR_PROPERTY(float, aspectRatio, 1) | |
| 45 | + BR_PROPERTY(int, windowWidth, 24) | |
| 35 | 46 | |
| 36 | 47 | public: |
| 37 | 48 | SlidingWindowTransform() : Transform(false, true) {} |
| 38 | - | |
| 39 | 49 | private: |
| 50 | + | |
| 40 | 51 | void train(const TemplateList &data) |
| 41 | 52 | { |
| 42 | 53 | if (transform->trainable) { |
| 54 | + double tempRatio = 0; | |
| 55 | + int ratioCnt = 0; | |
| 43 | 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 | 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 | 88 | full += pos; |
| 48 | 89 | |
| 49 | 90 | // add random negative samples |
| ... | ... | @@ -57,9 +98,11 @@ private: |
| 57 | 98 | int maxSize = std::min(maxWidth, maxHeight); |
| 58 | 99 | int size = (maxSize <= minSize ? maxSize : Common::RandSample(1, maxSize, minSize)[0]); |
| 59 | 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 | 104 | continue; |
| 105 | + negRects.append(negRect); | |
| 63 | 106 | Template neg(tmpl.file, Mat(tmpl, negRect)); |
| 64 | 107 | neg.file.set("Label", QString("neg")); |
| 65 | 108 | full += neg; |
| ... | ... | @@ -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 | 128 | void project(const Template &src, Template &dst) const |
| 76 | 129 | { |
| 77 | 130 | dst = src; |
| ... | ... | @@ -79,17 +132,33 @@ private: |
| 79 | 132 | if (src.file.getBool("Train", false)) return; |
| 80 | 133 | |
| 81 | 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 | 153 | Template detect; |
| 89 | 154 | transform->project(windowMat, detect); |
| 90 | 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 | 162 | if (takeLargestScale) return; |
| 94 | 163 | } |
| 95 | 164 | } |
| ... | ... | @@ -100,6 +169,36 @@ private: |
| 100 | 169 | |
| 101 | 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 | 202 | } // namespace br |
| 104 | 203 | |
| 105 | 204 | #include "slidingwindow.moc" | ... | ... |
openbr/plugins/svm.cpp
| ... | ... | @@ -103,6 +103,7 @@ class SVMTransform : public Transform |
| 103 | 103 | Q_PROPERTY(float gamma READ get_gamma WRITE set_gamma RESET reset_gamma STORED false) |
| 104 | 104 | Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false) |
| 105 | 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 | 108 | public: |
| 108 | 109 | enum Kernel { Linear = CvSVM::LINEAR, |
| ... | ... | @@ -123,6 +124,7 @@ private: |
| 123 | 124 | BR_PROPERTY(float, gamma, -1) |
| 124 | 125 | BR_PROPERTY(QString, inputVariable, "") |
| 125 | 126 | BR_PROPERTY(QString, outputVariable, "") |
| 127 | + BR_PROPERTY(bool, returnDFVal, false) | |
| 126 | 128 | |
| 127 | 129 | |
| 128 | 130 | SVM svm; |
| ... | ... | @@ -149,8 +151,17 @@ private: |
| 149 | 151 | |
| 150 | 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 | 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 | 165 | if (type == EPS_SVR || type == NU_SVR) |
| 155 | 166 | dst.file.set(outputVariable, prediction); |
| 156 | 167 | else | ... | ... |