Commit e6d1d7b442f289c71965cf1ed5a72df453e8d2b8
1 parent
d5677d9b
Added flag for normalizing predicted bounding boxes based on average distances f…
…rom ground truth bounding boxes.
Showing
5 changed files
with
75 additions
and
20 deletions
app/br/br.cpp
| @@ -144,8 +144,8 @@ public: | @@ -144,8 +144,8 @@ public: | ||
| 144 | check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalClustering'."); | 144 | check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalClustering'."); |
| 145 | br_eval_clustering(parv[0], parv[1], parc == 3 ? parv[2] : ""); | 145 | br_eval_clustering(parv[0], parv[1], parc == 3 ? parv[2] : ""); |
| 146 | } else if (!strcmp(fun, "evalDetection")) { | 146 | } else if (!strcmp(fun, "evalDetection")) { |
| 147 | - check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalDetection'."); | ||
| 148 | - br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : ""); | 147 | + check((parc >= 2) && (parc <= 4), "Incorrect parameter count for 'evalDetection'."); |
| 148 | + br_eval_detection(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc == 4 ? atoi(parv[3]) : 0); | ||
| 149 | } else if (!strcmp(fun, "evalLandmarking")) { | 149 | } else if (!strcmp(fun, "evalLandmarking")) { |
| 150 | check((parc >= 2) && (parc <= 5), "Incorrect parameter count for 'evalLandmarking'."); | 150 | check((parc >= 2) && (parc <= 5), "Incorrect parameter count for 'evalLandmarking'."); |
| 151 | br_eval_landmarking(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 1); | 151 | br_eval_landmarking(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 1); |
openbr/core/eval.cpp
| @@ -752,18 +752,13 @@ static QMap<QString, Detections> getDetections(const File &predictedGallery, con | @@ -752,18 +752,13 @@ static QMap<QString, Detections> getDetections(const File &predictedGallery, con | ||
| 752 | return allDetections; | 752 | return allDetections; |
| 753 | } | 753 | } |
| 754 | 754 | ||
| 755 | -float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv) | 755 | +static int associateGroundTruthDetections(QList<ResolvedDetection> &resolved, QList<ResolvedDetection> &falseNegative, QMap<QString, Detections> &all, QRectF &offsets) |
| 756 | { | 756 | { |
| 757 | - qDebug("Evaluating detection of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); | 757 | + float dLeftTotal = 0.0, dRightTotal = 0.0, dTopTotal = 0.0, dBottomTotal = 0.0; |
| 758 | + int count = 0, totalTrueDetections = 0; | ||
| 758 | 759 | ||
| 759 | - // Organized by file, QMap used to preserve order | ||
| 760 | - QMap<QString, Detections> allDetections = getDetections(predictedGallery, truthGallery); | ||
| 761 | - | ||
| 762 | - QList<ResolvedDetection> resolvedDetections, falseNegativeDetections; | ||
| 763 | - int totalTrueDetections = 0; | ||
| 764 | - foreach (Detections detections, allDetections.values()) { // For every file | 760 | + foreach (Detections detections, all.values()) { |
| 765 | totalTrueDetections += detections.truth.size(); | 761 | totalTrueDetections += detections.truth.size(); |
| 766 | - | ||
| 767 | // Try to associate ground truth detections with predicted detections | 762 | // Try to associate ground truth detections with predicted detections |
| 768 | while (!detections.truth.isEmpty() && !detections.predicted.isEmpty()) { | 763 | while (!detections.truth.isEmpty() && !detections.predicted.isEmpty()) { |
| 769 | const Detection truth = detections.truth.takeFirst(); // Take removes the detection | 764 | const Detection truth = detections.truth.takeFirst(); // Take removes the detection |
| @@ -771,7 +766,16 @@ float EvalDetection(const QString &predictedGallery, const QString &truthGallery | @@ -771,7 +766,16 @@ float EvalDetection(const QString &predictedGallery, const QString &truthGallery | ||
| 771 | float bestOverlap = -std::numeric_limits<float>::max(); | 766 | float bestOverlap = -std::numeric_limits<float>::max(); |
| 772 | // Find the nearest predicted detection to this ground truth detection | 767 | // Find the nearest predicted detection to this ground truth detection |
| 773 | for (int i=0; i<detections.predicted.size(); i++) { | 768 | for (int i=0; i<detections.predicted.size(); i++) { |
| 774 | - const float overlap = truth.overlap(detections.predicted[i]); | 769 | + Detection predicted = detections.predicted[i]; |
| 770 | + float predictedWidth = predicted.boundingBox.width(); | ||
| 771 | + float x, y, width, height; | ||
| 772 | + x = predicted.boundingBox.x() + offsets.x()*predictedWidth; | ||
| 773 | + y = predicted.boundingBox.y() + offsets.y()*predictedWidth; | ||
| 774 | + width = predicted.boundingBox.width() - offsets.width()*predictedWidth; | ||
| 775 | + height = predicted.boundingBox.height() - offsets.height()*predictedWidth; | ||
| 776 | + Detection newPredicted(QRectF(x, y, width, height), 0.0); | ||
| 777 | + | ||
| 778 | + const float overlap = truth.overlap(newPredicted); | ||
| 775 | if (overlap > bestOverlap) { | 779 | if (overlap > bestOverlap) { |
| 776 | bestOverlap = overlap; | 780 | bestOverlap = overlap; |
| 777 | bestIndex = i; | 781 | bestIndex = i; |
| @@ -781,17 +785,67 @@ float EvalDetection(const QString &predictedGallery, const QString &truthGallery | @@ -781,17 +785,67 @@ float EvalDetection(const QString &predictedGallery, const QString &truthGallery | ||
| 781 | // We don't want to associate two ground truth detections with the | 785 | // We don't want to associate two ground truth detections with the |
| 782 | // same prediction, over vice versa. | 786 | // same prediction, over vice versa. |
| 783 | const Detection predicted = detections.predicted.takeAt(bestIndex); | 787 | const Detection predicted = detections.predicted.takeAt(bestIndex); |
| 784 | - resolvedDetections.append(ResolvedDetection(predicted.confidence, bestOverlap)); | 788 | + resolved.append(ResolvedDetection(predicted.confidence, bestOverlap)); |
| 789 | + | ||
| 790 | + if (offsets.x() == 0) { | ||
| 791 | + // Add side differences to total only for pairs that meet the overlap threshold. | ||
| 792 | + if (bestOverlap > 0.3) { | ||
| 793 | + count++; | ||
| 794 | + float width = predicted.boundingBox.width(); | ||
| 795 | + dLeftTotal += (truth.boundingBox.left() - predicted.boundingBox.left()) / width; | ||
| 796 | + dRightTotal += (truth.boundingBox.right() - predicted.boundingBox.right()) / width; | ||
| 797 | + dTopTotal += (truth.boundingBox.top() - predicted.boundingBox.top()) / width; | ||
| 798 | + dBottomTotal += (truth.boundingBox.bottom() - predicted.boundingBox.bottom()) / width; | ||
| 799 | + } | ||
| 800 | + } | ||
| 785 | } | 801 | } |
| 786 | 802 | ||
| 787 | foreach (const Detection &detection, detections.predicted) | 803 | foreach (const Detection &detection, detections.predicted) |
| 788 | - resolvedDetections.append(ResolvedDetection(detection.confidence, 0)); | 804 | + resolved.append(ResolvedDetection(detection.confidence, 0)); |
| 789 | for (int i=0; i<detections.truth.size(); i++) | 805 | for (int i=0; i<detections.truth.size(); i++) |
| 790 | - falseNegativeDetections.append(ResolvedDetection(-std::numeric_limits<float>::max(), 0)); | 806 | + falseNegative.append(ResolvedDetection(-std::numeric_limits<float>::max(), 0)); |
| 791 | } | 807 | } |
| 808 | + if (offsets.x() == 0) { | ||
| 809 | + // Calculate average differences in each direction | ||
| 810 | + float dRight = dRightTotal / count; | ||
| 811 | + float dBottom = dBottomTotal / count; | ||
| 812 | + float dX = dLeftTotal / count; | ||
| 813 | + float dY = dTopTotal / count; | ||
| 814 | + float dWidth = dX - dRight; | ||
| 815 | + float dHeight = dY - dBottom; | ||
| 816 | + | ||
| 817 | + offsets.setX(dX); | ||
| 818 | + offsets.setY(dY); | ||
| 819 | + offsets.setWidth(dWidth); | ||
| 820 | + offsets.setHeight(dHeight); | ||
| 821 | + } | ||
| 822 | + return totalTrueDetections; | ||
| 823 | +} | ||
| 792 | 824 | ||
| 793 | - std::sort(resolvedDetections.begin(), resolvedDetections.end()); | 825 | +float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv, bool normalize) |
| 826 | +{ | ||
| 827 | + qDebug("Evaluating detection of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); | ||
| 828 | + // Organized by file, QMap used to preserve order | ||
| 829 | + QMap<QString, Detections> allDetections = getDetections(predictedGallery, truthGallery); | ||
| 794 | 830 | ||
| 831 | + QList<ResolvedDetection> resolvedDetections, falseNegativeDetections; | ||
| 832 | + QRectF normalizations(0, 0, 0, 0); | ||
| 833 | + | ||
| 834 | + // Associate predictions to ground truth | ||
| 835 | + int totalTrueDetections = associateGroundTruthDetections(resolvedDetections, falseNegativeDetections, allDetections, normalizations); | ||
| 836 | + | ||
| 837 | + // Redo association of ground truth to predictions with boundingBoxes | ||
| 838 | + // resized based on the average differences on each side. | ||
| 839 | + if (normalize) { | ||
| 840 | + qDebug("dX = %.3f", normalizations.x()); | ||
| 841 | + qDebug("dY = %.3f", normalizations.y()); | ||
| 842 | + qDebug("dWidth = %.3f", normalizations.width()); | ||
| 843 | + qDebug("dHeight = %.3f", normalizations.height()); | ||
| 844 | + resolvedDetections.clear(); | ||
| 845 | + falseNegativeDetections.clear(); | ||
| 846 | + totalTrueDetections = associateGroundTruthDetections(resolvedDetections, falseNegativeDetections, allDetections, normalizations); | ||
| 847 | + } | ||
| 848 | + std::sort(resolvedDetections.begin(), resolvedDetections.end()); | ||
| 795 | QStringList lines; | 849 | QStringList lines; |
| 796 | lines.append("Plot, X, Y"); | 850 | lines.append("Plot, X, Y"); |
| 797 | lines.append(computeDetectionResults(resolvedDetections, totalTrueDetections, true)); | 851 | lines.append(computeDetectionResults(resolvedDetections, totalTrueDetections, true)); |
openbr/core/eval.h
| @@ -29,7 +29,7 @@ namespace br | @@ -29,7 +29,7 @@ namespace br | ||
| 29 | float InplaceEval(const QString & simmat, const QString & target, const QString & query, const QString & csv = ""); | 29 | float InplaceEval(const QString & simmat, const QString & target, const QString & query, const QString & csv = ""); |
| 30 | 30 | ||
| 31 | void EvalClassification(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); | 31 | void EvalClassification(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); |
| 32 | - float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv = ""); // Return average overlap | 32 | + float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", bool normalize = false); // Return average overlap |
| 33 | float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", int normalizationIndexA = 0, int normalizationIndexB = 1); // Return average error | 33 | float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", int normalizationIndexA = 0, int normalizationIndexB = 1); // Return average error |
| 34 | void EvalRegression(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); | 34 | void EvalRegression(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); |
| 35 | } | 35 | } |
openbr/openbr.cpp
| @@ -124,9 +124,9 @@ void br_eval_clustering(const char *csv, const char *gallery, const char * truth | @@ -124,9 +124,9 @@ void br_eval_clustering(const char *csv, const char *gallery, const char * truth | ||
| 124 | EvalClustering(csv, gallery, truth_property); | 124 | EvalClustering(csv, gallery, truth_property); |
| 125 | } | 125 | } |
| 126 | 126 | ||
| 127 | -float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv) | 127 | +float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv, bool normalize) |
| 128 | { | 128 | { |
| 129 | - return EvalDetection(predicted_gallery, truth_gallery, csv); | 129 | + return EvalDetection(predicted_gallery, truth_gallery, csv, normalize); |
| 130 | } | 130 | } |
| 131 | 131 | ||
| 132 | float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv, int normalization_index_a, int normalization_index_b) | 132 | float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv, int normalization_index_a, int normalization_index_b) |
openbr/openbr.h
| @@ -193,9 +193,10 @@ BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery, const ch | @@ -193,9 +193,10 @@ BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery, const ch | ||
| 193 | * \param predicted_gallery The predicted br::Gallery. | 193 | * \param predicted_gallery The predicted br::Gallery. |
| 194 | * \param truth_gallery The ground truth br::Gallery. | 194 | * \param truth_gallery The ground truth br::Gallery. |
| 195 | * \param csv Optional \c .csv file to contain performance metrics. | 195 | * \param csv Optional \c .csv file to contain performance metrics. |
| 196 | + * \param normalize Optional \c bool flag to normalize predicted bounding boxes for improved detection. | ||
| 196 | * \return Average detection bounding box overlap. | 197 | * \return Average detection bounding box overlap. |
| 197 | */ | 198 | */ |
| 198 | -BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv = ""); | 199 | +BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv = "", bool normalize = false); |
| 199 | 200 | ||
| 200 | /*! | 201 | /*! |
| 201 | * \brief Evaluates and prints landmarking accuracy to terminal. | 202 | * \brief Evaluates and prints landmarking accuracy to terminal. |