Commit a4c49511de5a8792b0d3dbc623c0560639308f9c

Authored by Jordan Cheney
1 parent 34953f33

Add the ability to match object labels in the detection evaluation

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 &amp;predictedGallery, const QString &amp;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 &amp;predictedGallery, const QString &amp;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 &amp;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&lt;QString, Detections&gt; EvalUtils::filterDetections(const QMap&lt;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&lt;ResolvedDetection&gt; &amp;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&lt;ResolvedDetection&gt; &amp;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&lt;ResolvedDetection&gt; &amp;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  
... ...