diff --git a/app/br/br.cpp b/app/br/br.cpp index da50621..87797fb 100644 --- a/app/br/br.cpp +++ b/app/br/br.cpp @@ -160,8 +160,8 @@ public: check((parc >= 2) && (parc <= 5), "Incorrect parameter count for 'evalClustering'."); br_eval_clustering(parv[0], parv[1], parc > 2 ? parv[2] : "", parc > 3 ? atoi(parv[3]) : 1, parc > 4 ? parv[4] : ""); } else if (!strcmp(fun, "evalDetection")) { - check((parc >= 2) && (parc <= 7), "Incorrect parameter count for 'evalDetection'."); - br_eval_detection(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 0, parc >= 6 ? atoi(parv[5]) : 0, parc >= 7 ? atof(parv[6]) : 0); + check((parc >= 2) && (parc <= 8), "Incorrect parameter count for 'evalDetection'."); + br_eval_detection(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 0, parc >= 6 ? atoi(parv[5]) : 0, parc >= 7 ? atof(parv[6]) : 0, parc >= 8 ? parv[7] : ""); } else if (!strcmp(fun, "evalLandmarking")) { check((parc >= 2) && (parc <= 7), "Incorrect parameter count for 'evalLandmarking'."); br_eval_landmarking(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 1, parc >= 6 ? atoi(parv[5]) : 0, parc >= 7 ? atoi(parv[6]) : 5); @@ -290,7 +290,7 @@ private: "-convert (Format|Gallery|Output) {output_file}\n" "-evalClassification \n" "-evalClustering [truth_property [cluster_csv [cluster_property]]]\n" - "-evalDetection [{csv}] [{normalize}] [{minSize}] [{maxSize}]\n" + "-evalDetection [{csv}] [{normalize}] [{minSize}] [{maxSize}] [{label_filter}]\n" "-evalLandmarking [{csv} [ ] [sample_index] [total_examples]]\n" "-evalRegression \n" "-evalKNN [{csv}]\n" diff --git a/openbr/core/eval.cpp b/openbr/core/eval.cpp index 4c8eba2..6bac685 100755 --- a/openbr/core/eval.cpp +++ b/openbr/core/eval.cpp @@ -769,7 +769,7 @@ void EvalClassification(const QString &predictedGallery, const QString &truthGal qDebug("Overall Accuracy = %f", (float)tpc / (float)(tpc + fnc)); } -float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv, bool normalize, int minSize, int maxSize, float relativeMinSize) +float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv, bool normalize, int minSize, int maxSize, float relativeMinSize, const QString &label) { qDebug("Evaluating detection of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); // Organized by file, QMap used to preserve order @@ -789,6 +789,15 @@ float EvalDetection(const QString &predictedGallery, const QString &truthGallery allDetections = filterDetections(allDetections, maxSize, false); } + // Optionally, keep only detections with a specific label + if (!label.isEmpty()) { + if (Globals->verbose) + qDebug("Removing detections without label %s\n", qPrintable(label)); + allDetections = filterLabels(allDetections, label); + if (allDetections.isEmpty()) + qFatal("No detections left after filtering on label. Check your filter"); + } + QList resolvedDetections, falseNegativeDetections; QRectF normalizations(0, 0, 0, 0); diff --git a/openbr/core/eval.h b/openbr/core/eval.h index 927a45d..86b7bb6 100644 --- a/openbr/core/eval.h +++ b/openbr/core/eval.h @@ -30,7 +30,7 @@ namespace br float InplaceEval(const QString & simmat, const QString & target, const QString & query, const QString & csv = ""); void EvalClassification(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); - float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", bool normalize = false, int minSize = 0, int maxSize = 0, float relativeMinSize = 0); // Return average overlap + float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", bool normalize = false, int minSize = 0, int maxSize = 0, float relativeMinSize = 0, const QString &label = ""); // Return average overlap float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", int normalizationIndexA = 0, int normalizationIndexB = 1, int sampleIndex = 0, int totalExamples = 5); // Return average error void EvalRegression(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); void EvalKNN(const QString &knnGraph, const QString &knnTruth, const QString &csv = ""); diff --git a/openbr/core/evalutils.cpp b/openbr/core/evalutils.cpp index 1cbb9b0..5b84663 100644 --- a/openbr/core/evalutils.cpp +++ b/openbr/core/evalutils.cpp @@ -47,28 +47,34 @@ DetectionKey EvalUtils::getDetectKey(const FileList &files) // return a list of detections independent of the detection key format QList EvalUtils::getDetections(const DetectionKey &key, const File &f, bool isTruth) { - QString pose = f.get("Pose"); + QString pose = f.get("Pose", "Frontal"); if (pose.contains("Angle")) pose = "Frontal"; + QString label = f.get("Label", ""); + const QString filePath = f.path() + "/" + f.fileName(); QList dets; if (key.type == DetectionKey::RectList) { QList rects = f.rects(); QList confidences = f.getList("Confidences", QList()); + QList labels = f.getList("Labels", QList()); if (!isTruth && rects.size() != confidences.size()) qFatal("You don't have enough confidence. I mean, your detections don't all have confidence measures."); + if (!labels.empty() && rects.size() != labels.size()) + qFatal("Some of your rects have labels but not all, it's all or nothing I'm afraid"); + for (int i=0; i(key), filePath, isTruth ? -1 : f.get("Confidence", -1), f.get("Ignore", false), pose)); + dets.append(Detection(f.get(key), filePath, isTruth ? -1 : f.get("Confidence", -1), f.get("Ignore", false), pose, label)); } else if (key.type == DetectionKey::XYWidthHeight) { const QRectF rect(f.get(key+"_X"), f.get(key+"_Y"), f.get(key+"_Width"), f.get(key+"_Height")); - dets.append(Detection(rect, filePath, isTruth ? -1 : f.get("Confidence", -1), f.get("Ignore", false), pose)); + dets.append(Detection(rect, filePath, isTruth ? -1 : f.get("Confidence", -1), f.get("Ignore", false), pose, label)); } return dets; } @@ -128,6 +134,27 @@ QMap EvalUtils::filterDetections(const QMap EvalUtils::filterLabels(const QMap &allDetections, const QString &label) +{ + QMap allFilteredDetections; + foreach (QString key, allDetections.keys()) { + Detections detections = allDetections[key]; + Detections filteredDetections; + for (int i = 0; i < detections.predicted.size(); i++) { + if (detections.predicted[i].label == label) + filteredDetections.predicted.append(detections.predicted[i]); + } + for (int i = 0; i < detections.truth.size(); i++) { + if (detections.truth[i].label == label) + filteredDetections.truth.append(detections.truth[i]); + } + if (!filteredDetections.truth.empty()) allFilteredDetections[key] = filteredDetections; + } + return allFilteredDetections; } @@ -147,6 +174,10 @@ int EvalUtils::associateGroundTruthDetections(QList &resolved for (int p = 0; p < detections.predicted.size(); p++) { Detection predicted = detections.predicted[p]; + // Only boxes of the same class can overlap + if (predicted.label != truth.label) + continue; + float predictedWidth = predicted.boundingBox.width(); float x, y, width, height; x = predicted.boundingBox.x() + offsets.x()*predictedWidth; @@ -175,7 +206,7 @@ int EvalUtils::associateGroundTruthDetections(QList &resolved const Detection predicted = detections.predicted[detection.predicted_idx]; if (!truth.ignore) - resolved.append(ResolvedDetection(predicted.filePath, predicted.boundingBox, predicted.confidence, detection.overlap, truth.boundingBox, truth.pose == predicted.pose)); + resolved.append(ResolvedDetection(predicted.filePath, predicted.boundingBox, predicted.confidence, detection.overlap, truth.boundingBox, truth.pose == predicted.pose, truth.label)); removedTruth.append(detection.truth_idx); removedPredicted.append(detection.predicted_idx); @@ -192,11 +223,11 @@ int EvalUtils::associateGroundTruthDetections(QList &resolved // False positive for (int i = 0; i < detections.predicted.size(); i++) - if (!removedPredicted.contains(i)) resolved.append(ResolvedDetection(detections.predicted[i].filePath, detections.predicted[i].boundingBox, detections.predicted[i].confidence, 0, QRectF(), false)); + if (!removedPredicted.contains(i)) resolved.append(ResolvedDetection(detections.predicted[i].filePath, detections.predicted[i].boundingBox, detections.predicted[i].confidence, 0, QRectF(), false, detections.predicted[i].label)); // False negative for (int i = 0; i < detections.truth.size(); i++) - if (!removedTruth.contains(i) && !detections.truth[i].ignore) falseNegative.append(ResolvedDetection(detections.truth[i].filePath, detections.truth[i].boundingBox, -std::numeric_limits::max(), 0, QRectF(), false)); + if (!removedTruth.contains(i) && !detections.truth[i].ignore) falseNegative.append(ResolvedDetection(detections.truth[i].filePath, detections.truth[i].boundingBox, -std::numeric_limits::max(), 0, QRectF(), false, detections.truth[i].label)); } if (offsets.x() == 0) { diff --git a/openbr/core/evalutils.h b/openbr/core/evalutils.h index 8fbeebe..387076c 100644 --- a/openbr/core/evalutils.h +++ b/openbr/core/evalutils.h @@ -7,7 +7,8 @@ namespace EvalUtils { - struct Detection + +struct Detection { QRectF boundingBox; QString filePath; @@ -18,14 +19,17 @@ namespace EvalUtils // true negative, or false negative, it will simply be ignored. bool ignore; QString pose; + // The label field can be used to distinguish between different object classes + QString label; Detection() {} - Detection(const QRectF &boundingBox, const QString &filePath = QString(), float confidence = -1, bool ignore = false, const QString &pose = "Frontal") : + Detection(const QRectF &boundingBox, const QString &filePath = QString(), float confidence = -1, bool ignore = false, const QString &pose = "Frontal", const QString &label = "") : boundingBox(boundingBox), filePath(filePath), confidence(confidence), ignore(ignore), - pose(pose) + pose(pose), + label(label) {} float overlap(const Detection &other) const @@ -50,18 +54,20 @@ struct ResolvedDetection QRectF boundingBox, groundTruthBoundingBox; float confidence, overlap; bool poseMatch; + QString label; ResolvedDetection() : confidence(-1), overlap(-1) {} -ResolvedDetection(const QString &filePath, const QRectF &boundingBox, float confidence, float overlap, const QRectF &groundTruthBoundingBox, bool poseMatch) : +ResolvedDetection(const QString &filePath, const QRectF &boundingBox, float confidence, float overlap, const QRectF &groundTruthBoundingBox, bool poseMatch, const QString &label) : filePath(filePath), boundingBox(boundingBox), groundTruthBoundingBox(groundTruthBoundingBox), confidence(confidence), overlap(overlap), - poseMatch(poseMatch) + poseMatch(poseMatch), + label(label) {} inline bool operator<(const ResolvedDetection &other) const { return confidence > other.confidence; } @@ -99,6 +105,7 @@ struct DetectionOperatingPoint QList getDetections(const DetectionKey &key, const br::File &f, bool isTruth); QMap getDetections(const br::File &predictedGallery, const br::File &truthGallery); QMap filterDetections(const QMap &allDetections, int threshold, bool useMin = true, float relativeThreshold = 0); + QMap filterLabels(const QMap &allDetections, const QString &label); int associateGroundTruthDetections(QList &resolved, QList &falseNegative, QMap &all, QRectF &offsets); QStringList computeDetectionResults(const QList &detections, int totalTrueDetections, int numImages, bool discrete, QList &points); inline int getNumberOfImages(const QMap detections) diff --git a/openbr/openbr.cpp b/openbr/openbr.cpp index 2c86df1..30caf4b 100644 --- a/openbr/openbr.cpp +++ b/openbr/openbr.cpp @@ -126,9 +126,9 @@ void br_eval_clustering(const char *clusters, const char *truth_gallery, const c EvalClustering(clusters, truth_gallery, truth_property, cluster_csv, cluster_property); } -float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv, bool normalize, int minSize, int maxSize, float relativeMinSize) +float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv, bool normalize, int minSize, int maxSize, float relativeMinSize, const char* label) { - return EvalDetection(predicted_gallery, truth_gallery, csv, normalize, minSize, maxSize, relativeMinSize); + return EvalDetection(predicted_gallery, truth_gallery, csv, normalize, minSize, maxSize, relativeMinSize, label); } float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv, int normalization_index_a, int normalization_index_b, int sample_index, int total_examples) diff --git a/openbr/openbr.h b/openbr/openbr.h index b46a880..9b2ae45 100644 --- a/openbr/openbr.h +++ b/openbr/openbr.h @@ -58,7 +58,7 @@ BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char BR_EXPORT void br_eval_clustering(const char *clusters, const char *truth_gallery, const char *truth_property = "", bool cluster_csv = true, const char *cluster_property = ""); -BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv = "", bool normalize = false, int minSize = 0, int maxSize = 0, float relativeMinSize = 0); +BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv = "", bool normalize = false, int minSize = 0, int maxSize = 0, float relativeMinSize = 0, const char* label = ""); BR_EXPORT float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv = "", int normalization_index_a = 0, int normalization_index_b = 1, int sample_index = 0, int total_examples = 5);