Commit a4c49511de5a8792b0d3dbc623c0560639308f9c
1 parent
34953f33
Add the ability to match object labels in the detection evaluation
Showing
7 changed files
with
68 additions
and
21 deletions
app/br/br.cpp
| ... | ... | @@ -160,8 +160,8 @@ public: |
| 160 | 160 | check((parc >= 2) && (parc <= 5), "Incorrect parameter count for 'evalClustering'."); |
| 161 | 161 | br_eval_clustering(parv[0], parv[1], parc > 2 ? parv[2] : "", parc > 3 ? atoi(parv[3]) : 1, parc > 4 ? parv[4] : ""); |
| 162 | 162 | } else if (!strcmp(fun, "evalDetection")) { |
| 163 | - check((parc >= 2) && (parc <= 7), "Incorrect parameter count for 'evalDetection'."); | |
| 164 | - 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); | |
| 163 | + check((parc >= 2) && (parc <= 8), "Incorrect parameter count for 'evalDetection'."); | |
| 164 | + 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] : ""); | |
| 165 | 165 | } else if (!strcmp(fun, "evalLandmarking")) { |
| 166 | 166 | check((parc >= 2) && (parc <= 7), "Incorrect parameter count for 'evalLandmarking'."); |
| 167 | 167 | 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: |
| 290 | 290 | "-convert (Format|Gallery|Output) <input_file> {output_file}\n" |
| 291 | 291 | "-evalClassification <predicted_gallery> <truth_gallery> <predicted property name> <ground truth proprty name>\n" |
| 292 | 292 | "-evalClustering <clusters> <truth_gallery> [truth_property [cluster_csv [cluster_property]]]\n" |
| 293 | - "-evalDetection <predicted_gallery> <truth_gallery> [{csv}] [{normalize}] [{minSize}] [{maxSize}]\n" | |
| 293 | + "-evalDetection <predicted_gallery> <truth_gallery> [{csv}] [{normalize}] [{minSize}] [{maxSize}] [{label_filter}]\n" | |
| 294 | 294 | "-evalLandmarking <predicted_gallery> <truth_gallery> [{csv} [<normalization_index_a> <normalization_index_b>] [sample_index] [total_examples]]\n" |
| 295 | 295 | "-evalRegression <predicted_gallery> <truth_gallery> <predicted property name> <ground truth property name>\n" |
| 296 | 296 | "-evalKNN <knn_graph> <knn_truth> [{csv}]\n" | ... | ... |
openbr/core/eval.cpp
| ... | ... | @@ -769,7 +769,7 @@ void EvalClassification(const QString &predictedGallery, const QString &truthGal |
| 769 | 769 | qDebug("Overall Accuracy = %f", (float)tpc / (float)(tpc + fnc)); |
| 770 | 770 | } |
| 771 | 771 | |
| 772 | -float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv, bool normalize, int minSize, int maxSize, float relativeMinSize) | |
| 772 | +float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv, bool normalize, int minSize, int maxSize, float relativeMinSize, const QString &label) | |
| 773 | 773 | { |
| 774 | 774 | qDebug("Evaluating detection of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); |
| 775 | 775 | // Organized by file, QMap used to preserve order |
| ... | ... | @@ -789,6 +789,15 @@ float EvalDetection(const QString &predictedGallery, const QString &truthGallery |
| 789 | 789 | allDetections = filterDetections(allDetections, maxSize, false); |
| 790 | 790 | } |
| 791 | 791 | |
| 792 | + // Optionally, keep only detections with a specific label | |
| 793 | + if (!label.isEmpty()) { | |
| 794 | + if (Globals->verbose) | |
| 795 | + qDebug("Removing detections without label %s\n", qPrintable(label)); | |
| 796 | + allDetections = filterLabels(allDetections, label); | |
| 797 | + if (allDetections.isEmpty()) | |
| 798 | + qFatal("No detections left after filtering on label. Check your filter"); | |
| 799 | + } | |
| 800 | + | |
| 792 | 801 | QList<ResolvedDetection> resolvedDetections, falseNegativeDetections; |
| 793 | 802 | QRectF normalizations(0, 0, 0, 0); |
| 794 | 803 | ... | ... |
openbr/core/eval.h
| ... | ... | @@ -30,7 +30,7 @@ namespace br |
| 30 | 30 | float InplaceEval(const QString & simmat, const QString & target, const QString & query, const QString & csv = ""); |
| 31 | 31 | |
| 32 | 32 | void EvalClassification(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); |
| 33 | - 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 | |
| 33 | + 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 | |
| 34 | 34 | 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 |
| 35 | 35 | void EvalRegression(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); |
| 36 | 36 | void EvalKNN(const QString &knnGraph, const QString &knnTruth, const QString &csv = ""); | ... | ... |
openbr/core/evalutils.cpp
| ... | ... | @@ -47,28 +47,34 @@ DetectionKey EvalUtils::getDetectKey(const FileList &files) |
| 47 | 47 | // return a list of detections independent of the detection key format |
| 48 | 48 | QList<Detection> EvalUtils::getDetections(const DetectionKey &key, const File &f, bool isTruth) |
| 49 | 49 | { |
| 50 | - QString pose = f.get<QString>("Pose"); | |
| 50 | + QString pose = f.get<QString>("Pose", "Frontal"); | |
| 51 | 51 | if (pose.contains("Angle")) |
| 52 | 52 | pose = "Frontal"; |
| 53 | 53 | |
| 54 | + QString label = f.get<QString>("Label", ""); | |
| 55 | + | |
| 54 | 56 | const QString filePath = f.path() + "/" + f.fileName(); |
| 55 | 57 | QList<Detection> dets; |
| 56 | 58 | if (key.type == DetectionKey::RectList) { |
| 57 | 59 | QList<QRectF> rects = f.rects(); |
| 58 | 60 | QList<float> confidences = f.getList<float>("Confidences", QList<float>()); |
| 61 | + QList<QString> labels = f.getList<QString>("Labels", QList<QString>()); | |
| 59 | 62 | if (!isTruth && rects.size() != confidences.size()) |
| 60 | 63 | qFatal("You don't have enough confidence. I mean, your detections don't all have confidence measures."); |
| 64 | + if (!labels.empty() && rects.size() != labels.size()) | |
| 65 | + qFatal("Some of your rects have labels but not all, it's all or nothing I'm afraid"); | |
| 66 | + | |
| 61 | 67 | for (int i=0; i<rects.size(); i++) { |
| 62 | 68 | if (isTruth) |
| 63 | - dets.append(Detection(rects[i], filePath)); | |
| 69 | + dets.append(Detection(rects[i], filePath, -1, false, "Frontal", labels.empty() ? "" : labels[i])); | |
| 64 | 70 | else |
| 65 | - dets.append(Detection(rects[i], filePath, confidences[i])); | |
| 71 | + dets.append(Detection(rects[i], filePath, confidences[i], false, "Frontal", labels.empty() ? "" : labels[i])); | |
| 66 | 72 | } |
| 67 | 73 | } else if (key.type == DetectionKey::Rect) { |
| 68 | - dets.append(Detection(f.get<QRectF>(key), filePath, isTruth ? -1 : f.get<float>("Confidence", -1), f.get<bool>("Ignore", false), pose)); | |
| 74 | + dets.append(Detection(f.get<QRectF>(key), filePath, isTruth ? -1 : f.get<float>("Confidence", -1), f.get<bool>("Ignore", false), pose, label)); | |
| 69 | 75 | } else if (key.type == DetectionKey::XYWidthHeight) { |
| 70 | 76 | const QRectF rect(f.get<float>(key+"_X"), f.get<float>(key+"_Y"), f.get<float>(key+"_Width"), f.get<float>(key+"_Height")); |
| 71 | - dets.append(Detection(rect, filePath, isTruth ? -1 : f.get<float>("Confidence", -1), f.get<bool>("Ignore", false), pose)); | |
| 77 | + dets.append(Detection(rect, filePath, isTruth ? -1 : f.get<float>("Confidence", -1), f.get<bool>("Ignore", false), pose, label)); | |
| 72 | 78 | } |
| 73 | 79 | return dets; |
| 74 | 80 | } |
| ... | ... | @@ -128,6 +134,27 @@ QMap<QString, Detections> EvalUtils::filterDetections(const QMap<QString, Detect |
| 128 | 134 | } |
| 129 | 135 | if (!filteredDetections.truth.empty()) allFilteredDetections[key] = filteredDetections; |
| 130 | 136 | } |
| 137 | + | |
| 138 | + return allFilteredDetections; | |
| 139 | +} | |
| 140 | + | |
| 141 | +QMap<QString, Detections> EvalUtils::filterLabels(const QMap<QString, Detections> &allDetections, const QString &label) | |
| 142 | +{ | |
| 143 | + QMap<QString, Detections> allFilteredDetections; | |
| 144 | + foreach (QString key, allDetections.keys()) { | |
| 145 | + Detections detections = allDetections[key]; | |
| 146 | + Detections filteredDetections; | |
| 147 | + for (int i = 0; i < detections.predicted.size(); i++) { | |
| 148 | + if (detections.predicted[i].label == label) | |
| 149 | + filteredDetections.predicted.append(detections.predicted[i]); | |
| 150 | + } | |
| 151 | + for (int i = 0; i < detections.truth.size(); i++) { | |
| 152 | + if (detections.truth[i].label == label) | |
| 153 | + filteredDetections.truth.append(detections.truth[i]); | |
| 154 | + } | |
| 155 | + if (!filteredDetections.truth.empty()) allFilteredDetections[key] = filteredDetections; | |
| 156 | + } | |
| 157 | + | |
| 131 | 158 | return allFilteredDetections; |
| 132 | 159 | } |
| 133 | 160 | |
| ... | ... | @@ -147,6 +174,10 @@ int EvalUtils::associateGroundTruthDetections(QList<ResolvedDetection> &resolved |
| 147 | 174 | for (int p = 0; p < detections.predicted.size(); p++) { |
| 148 | 175 | Detection predicted = detections.predicted[p]; |
| 149 | 176 | |
| 177 | + // Only boxes of the same class can overlap | |
| 178 | + if (predicted.label != truth.label) | |
| 179 | + continue; | |
| 180 | + | |
| 150 | 181 | float predictedWidth = predicted.boundingBox.width(); |
| 151 | 182 | float x, y, width, height; |
| 152 | 183 | x = predicted.boundingBox.x() + offsets.x()*predictedWidth; |
| ... | ... | @@ -175,7 +206,7 @@ int EvalUtils::associateGroundTruthDetections(QList<ResolvedDetection> &resolved |
| 175 | 206 | const Detection predicted = detections.predicted[detection.predicted_idx]; |
| 176 | 207 | |
| 177 | 208 | if (!truth.ignore) |
| 178 | - resolved.append(ResolvedDetection(predicted.filePath, predicted.boundingBox, predicted.confidence, detection.overlap, truth.boundingBox, truth.pose == predicted.pose)); | |
| 209 | + resolved.append(ResolvedDetection(predicted.filePath, predicted.boundingBox, predicted.confidence, detection.overlap, truth.boundingBox, truth.pose == predicted.pose, truth.label)); | |
| 179 | 210 | |
| 180 | 211 | removedTruth.append(detection.truth_idx); |
| 181 | 212 | removedPredicted.append(detection.predicted_idx); |
| ... | ... | @@ -192,11 +223,11 @@ int EvalUtils::associateGroundTruthDetections(QList<ResolvedDetection> &resolved |
| 192 | 223 | |
| 193 | 224 | // False positive |
| 194 | 225 | for (int i = 0; i < detections.predicted.size(); i++) |
| 195 | - if (!removedPredicted.contains(i)) resolved.append(ResolvedDetection(detections.predicted[i].filePath, detections.predicted[i].boundingBox, detections.predicted[i].confidence, 0, QRectF(), false)); | |
| 226 | + 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)); | |
| 196 | 227 | |
| 197 | 228 | // False negative |
| 198 | 229 | for (int i = 0; i < detections.truth.size(); i++) |
| 199 | - if (!removedTruth.contains(i) && !detections.truth[i].ignore) falseNegative.append(ResolvedDetection(detections.truth[i].filePath, detections.truth[i].boundingBox, -std::numeric_limits<float>::max(), 0, QRectF(), false)); | |
| 230 | + if (!removedTruth.contains(i) && !detections.truth[i].ignore) falseNegative.append(ResolvedDetection(detections.truth[i].filePath, detections.truth[i].boundingBox, -std::numeric_limits<float>::max(), 0, QRectF(), false, detections.truth[i].label)); | |
| 200 | 231 | } |
| 201 | 232 | |
| 202 | 233 | if (offsets.x() == 0) { | ... | ... |
openbr/core/evalutils.h
| ... | ... | @@ -7,7 +7,8 @@ |
| 7 | 7 | |
| 8 | 8 | namespace EvalUtils |
| 9 | 9 | { |
| 10 | - struct Detection | |
| 10 | + | |
| 11 | +struct Detection | |
| 11 | 12 | { |
| 12 | 13 | QRectF boundingBox; |
| 13 | 14 | QString filePath; |
| ... | ... | @@ -18,14 +19,17 @@ namespace EvalUtils |
| 18 | 19 | // true negative, or false negative, it will simply be ignored. |
| 19 | 20 | bool ignore; |
| 20 | 21 | QString pose; |
| 22 | + // The label field can be used to distinguish between different object classes | |
| 23 | + QString label; | |
| 21 | 24 | |
| 22 | 25 | Detection() {} |
| 23 | - Detection(const QRectF &boundingBox, const QString &filePath = QString(), float confidence = -1, bool ignore = false, const QString &pose = "Frontal") : | |
| 26 | + Detection(const QRectF &boundingBox, const QString &filePath = QString(), float confidence = -1, bool ignore = false, const QString &pose = "Frontal", const QString &label = "") : | |
| 24 | 27 | boundingBox(boundingBox), |
| 25 | 28 | filePath(filePath), |
| 26 | 29 | confidence(confidence), |
| 27 | 30 | ignore(ignore), |
| 28 | - pose(pose) | |
| 31 | + pose(pose), | |
| 32 | + label(label) | |
| 29 | 33 | {} |
| 30 | 34 | |
| 31 | 35 | float overlap(const Detection &other) const |
| ... | ... | @@ -50,18 +54,20 @@ struct ResolvedDetection |
| 50 | 54 | QRectF boundingBox, groundTruthBoundingBox; |
| 51 | 55 | float confidence, overlap; |
| 52 | 56 | bool poseMatch; |
| 57 | + QString label; | |
| 53 | 58 | ResolvedDetection() : |
| 54 | 59 | confidence(-1), |
| 55 | 60 | overlap(-1) |
| 56 | 61 | {} |
| 57 | 62 | |
| 58 | -ResolvedDetection(const QString &filePath, const QRectF &boundingBox, float confidence, float overlap, const QRectF &groundTruthBoundingBox, bool poseMatch) : | |
| 63 | +ResolvedDetection(const QString &filePath, const QRectF &boundingBox, float confidence, float overlap, const QRectF &groundTruthBoundingBox, bool poseMatch, const QString &label) : | |
| 59 | 64 | filePath(filePath), |
| 60 | 65 | boundingBox(boundingBox), |
| 61 | 66 | groundTruthBoundingBox(groundTruthBoundingBox), |
| 62 | 67 | confidence(confidence), |
| 63 | 68 | overlap(overlap), |
| 64 | - poseMatch(poseMatch) | |
| 69 | + poseMatch(poseMatch), | |
| 70 | + label(label) | |
| 65 | 71 | {} |
| 66 | 72 | |
| 67 | 73 | inline bool operator<(const ResolvedDetection &other) const { return confidence > other.confidence; } |
| ... | ... | @@ -99,6 +105,7 @@ struct DetectionOperatingPoint |
| 99 | 105 | QList<Detection> getDetections(const DetectionKey &key, const br::File &f, bool isTruth); |
| 100 | 106 | QMap<QString, Detections> getDetections(const br::File &predictedGallery, const br::File &truthGallery); |
| 101 | 107 | QMap<QString, Detections> filterDetections(const QMap<QString, Detections> &allDetections, int threshold, bool useMin = true, float relativeThreshold = 0); |
| 108 | + QMap<QString, Detections> filterLabels(const QMap<QString, Detections> &allDetections, const QString &label); | |
| 102 | 109 | int associateGroundTruthDetections(QList<ResolvedDetection> &resolved, QList<ResolvedDetection> &falseNegative, QMap<QString, Detections> &all, QRectF &offsets); |
| 103 | 110 | QStringList computeDetectionResults(const QList<ResolvedDetection> &detections, int totalTrueDetections, int numImages, bool discrete, QList<DetectionOperatingPoint> &points); |
| 104 | 111 | inline int getNumberOfImages(const QMap<QString, Detections> detections) | ... | ... |
openbr/openbr.cpp
| ... | ... | @@ -126,9 +126,9 @@ void br_eval_clustering(const char *clusters, const char *truth_gallery, const c |
| 126 | 126 | EvalClustering(clusters, truth_gallery, truth_property, cluster_csv, cluster_property); |
| 127 | 127 | } |
| 128 | 128 | |
| 129 | -float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv, bool normalize, int minSize, int maxSize, float relativeMinSize) | |
| 129 | +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) | |
| 130 | 130 | { |
| 131 | - return EvalDetection(predicted_gallery, truth_gallery, csv, normalize, minSize, maxSize, relativeMinSize); | |
| 131 | + return EvalDetection(predicted_gallery, truth_gallery, csv, normalize, minSize, maxSize, relativeMinSize, label); | |
| 132 | 132 | } |
| 133 | 133 | |
| 134 | 134 | 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) | ... | ... |
openbr/openbr.h
| ... | ... | @@ -58,7 +58,7 @@ BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char |
| 58 | 58 | |
| 59 | 59 | 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 = ""); |
| 60 | 60 | |
| 61 | -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); | |
| 61 | +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 = ""); | |
| 62 | 62 | |
| 63 | 63 | 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); |
| 64 | 64 | ... | ... |