Skip to content

Commit b918968

Browse files
Merge branch 'jpfleischer-feature/map-reporting-fixes-superclean' into v5.1
2 parents 6addc22 + 1829518 commit b918968

3 files changed

Lines changed: 144 additions & 98 deletions

File tree

src-lib/darknet_format_and_colour.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,42 @@ std::string Darknet::format_map_confusion_matrix_values(
225225
}
226226

227227

228+
std::string Darknet::format_map_ap_row_values(
229+
const int class_id,
230+
std::string name,
231+
const float &average_precision, // 0..1
232+
const int &tp,
233+
const int &fp,
234+
const int &fn,
235+
const int &gt,
236+
const float &diag_avg_iou // 0..1
237+
)
238+
{
239+
TAT(TATPARMS);
240+
241+
if (name.length() > 20)
242+
{
243+
name.erase(19);
244+
name += "+";
245+
}
246+
247+
// Note: format_in_colour(x,len) auto-colours by value.
248+
// It also treats values >1 as percentages (divides by 100 for scale),
249+
// so we pass AP*100 and IoU*100 to show nicely as percents with colour.
250+
const std::string output =
251+
" " +
252+
format_in_colour(class_id, EColour::kNormal, 2) + " " +
253+
format_in_colour(name, EColour::kBrightWhite, 20) + " " +
254+
format_in_colour(100.0f * average_precision, 9) + " " + // <- 9 so "100.0000" fits
255+
format_in_colour(tp, EColour::kNormal, 6) + " " +
256+
format_in_colour(fp, EColour::kNormal, 6) + " " +
257+
format_in_colour(fn, EColour::kNormal, 6) + " " +
258+
format_in_colour(gt, EColour::kNormal, 6) + " " +
259+
format_in_colour(100.0f * diag_avg_iou, 17);
260+
return output;
261+
}
262+
263+
228264
std::string Darknet::format_layer_summary(const size_t idx, const Darknet::CfgSection & section, const Darknet::Layer & l)
229265
{
230266
TAT(TATPARMS);

src-lib/darknet_format_and_colour.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ namespace Darknet
6363
const float & specificity,
6464
const float & false_pos_rate);
6565

66+
std::string format_map_ap_row_values(
67+
const int class_id,
68+
std::string name,
69+
const float &average_precision, // 0..1
70+
const int &tp,
71+
const int &fp,
72+
const int &fn,
73+
const int &gt,
74+
const float &diag_avg_iou // 0..1 (diagnostic)
75+
);
76+
6677
std::string format_layer_summary(
6778
const size_t idx,
6879
const Darknet::CfgSection & section,

src-lib/detector.cpp

Lines changed: 97 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,12 +1342,12 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
13421342

13431343
struct box_prob
13441344
{
1345-
Darknet::Box b; // bounding box
1346-
float p; // probability
1345+
Darknet::Box b; // bounding box
1346+
float p; // probability (score)
13471347
int class_id;
1348-
int image_index; // ?
1349-
int truth_flag; // ?
1350-
int unique_truth_index; // ?
1348+
int image_index;
1349+
int truth_flag; // 1 if matched to some GT (greedy best-IoU of same class), else 0
1350+
int unique_truth_index; // global index of matched GT, to prevent double counting
13511351
};
13521352

13531353
list *options = read_data_cfg(datacfg);
@@ -1434,28 +1434,20 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
14341434
args.h = net.h;
14351435
args.c = net.c;
14361436
letter_box = net.letter_box;
1437-
if (letter_box)
1438-
{
1439-
args.type = LETTERBOX_DATA;
1440-
}
1441-
else
1442-
{
1443-
args.type = IMAGE_DATA;
1444-
}
1437+
args.type = letter_box ? LETTERBOX_DATA : IMAGE_DATA;
14451438

1446-
//const float thresh_calc_avg_iou = 0.24;
1447-
float avg_iou = 0;
1448-
int tp_for_thresh = 0;
1449-
int fp_for_thresh = 0;
1439+
float avg_iou = 0.f; // diagnostic IoU at thresh_calc_avg_iou across (TP+FP) — not used for AP
1440+
int tp_for_thresh = 0; // diagnostic TP at thresh_calc_avg_iou (across all classes)
1441+
int fp_for_thresh = 0; // diagnostic FP at thresh_calc_avg_iou (across all classes)
14501442

14511443
box_prob* detections = (box_prob*)xcalloc(1, sizeof(box_prob));
14521444
int detections_count = 0;
14531445
int unique_truth_count = 0;
14541446

1455-
/// @todo I think this is TP + FN (where the object actually exists, and we either found it, or missed it)
1447+
// counts of GT per class
14561448
int* truth_classes_count = (int*)xcalloc(classes, sizeof(int));
14571449

1458-
// For multi-class precision and recall computation
1450+
// Per-class diagnostics for the chosen confidence threshold
14591451
float *avg_iou_per_class = (float*)xcalloc(classes, sizeof(float));
14601452
int *tp_for_thresh_per_class = (int*)xcalloc(classes, sizeof(int));
14611453
int *fp_for_thresh_per_class = (int*)xcalloc(classes, sizeof(int));
@@ -1652,10 +1644,9 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
16521644

16531645
for (int class_id = 0; class_id < classes; class_id++)
16541646
{
1655-
if ((tp_for_thresh_per_class[class_id] + fp_for_thresh_per_class[class_id]) > 0)
1656-
{
1657-
avg_iou_per_class[class_id] = avg_iou_per_class[class_id] / (tp_for_thresh_per_class[class_id] + fp_for_thresh_per_class[class_id]);
1658-
}
1647+
const int denom = tp_for_thresh_per_class[class_id] + fp_for_thresh_per_class[class_id];
1648+
if (denom > 0)
1649+
avg_iou_per_class[class_id] = avg_iou_per_class[class_id] / denom;
16591650
}
16601651

16611652
// Sort the array from high probability to low probability.
@@ -1673,21 +1664,19 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
16731664

16741665
struct pr_t
16751666
{
1676-
double prob;
1677-
double precision;
1678-
double recall;
1679-
int tp;
1680-
int fp;
1681-
int fn;
1667+
double prob = 0.0;
1668+
double precision = 0.0;
1669+
double recall = 0.0;
1670+
int tp = 0;
1671+
int fp = 0;
1672+
int fn = 0;
16821673
};
16831674

16841675
// for PR-curve
16851676
// Note this is a pointer-to-a-pointer. We don't have just 1 of these per class, but these exist for every detections_count.
16861677
pr_t** pr = (pr_t**)xcalloc(classes, sizeof(pr_t*));
16871678
for (int i = 0; i < classes; ++i)
1688-
{
1689-
pr[i] = (pr_t*)xcalloc(detections_count, sizeof(pr_t));
1690-
}
1679+
pr[i] = (pr_t*)xcalloc(std::max(1, detections_count), sizeof(pr_t)); // allocate at least 1 to avoid nullptr deref
16911680

16921681
*cfg_and_state.output << "detections_count=" << detections_count << ", unique_truth_count=" << unique_truth_count << std::endl;
16931682

@@ -1697,8 +1686,9 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
16971686
detection_per_class_count[detections[j].class_id]++;
16981687
}
16991688

1700-
int* truth_flags = (int*)xcalloc(unique_truth_count, sizeof(int));
1689+
int *truth_flags = (int*)xcalloc(std::max(1, unique_truth_count), sizeof(int));
17011690

1691+
// Accumulate PR for each rank
17021692
for (int rank = 0; rank < detections_count; ++rank)
17031693
{
17041694
if (rank % 100 == 0)
@@ -1720,13 +1710,15 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
17201710

17211711
if (d.truth_flag == 1)
17221712
{
1723-
if (truth_flags[d.unique_truth_index] == 0)
1713+
if (d.unique_truth_index >= 0 && d.unique_truth_index < unique_truth_count &&
1714+
truth_flags[d.unique_truth_index] == 0)
17241715
{
17251716
truth_flags[d.unique_truth_index] = 1;
1726-
pr[d.class_id][rank].tp++; // true-positive
1727-
} else
1717+
pr[d.class_id][rank].tp++; // true positive
1718+
}
1719+
else
17281720
{
1729-
pr[d.class_id][rank].fp++;
1721+
pr[d.class_id][rank].fp++; // duplicate hit on same GT
17301722
}
17311723
}
17321724
else
@@ -1738,26 +1730,11 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
17381730
{
17391731
const int tp = pr[i][rank].tp;
17401732
const int fp = pr[i][rank].fp;
1741-
const int fn = truth_classes_count[i] - tp; // false-negative = objects - true-positive
1733+
const int fn = truth_classes_count[i] - tp; // remaining GT are false negatives
17421734
pr[i][rank].fn = fn;
17431735

1744-
if ((tp + fp) > 0)
1745-
{
1746-
pr[i][rank].precision = (double)tp / (double)(tp + fp);
1747-
}
1748-
else
1749-
{
1750-
pr[i][rank].precision = 0;
1751-
}
1752-
1753-
if ((tp + fn) > 0)
1754-
{
1755-
pr[i][rank].recall = (double)tp / (double)(tp + fn);
1756-
}
1757-
else
1758-
{
1759-
pr[i][rank].recall = 0;
1760-
}
1736+
pr[i][rank].precision = (tp + fp) > 0 ? (double)tp / (double)(tp + fp) : 0.0;
1737+
pr[i][rank].recall = (tp + fn) > 0 ? (double)tp / (double)(tp + fn) : 0.0;
17611738

17621739
if (rank == (detections_count - 1) && detection_per_class_count[i] != (tp + fp))
17631740
{
@@ -1777,6 +1754,7 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
17771754

17781755
double mean_average_precision = 0.0;
17791756

1757+
// ---- Per-class AP + reporting (no TN/accuracy/specificity) ----
17801758
for (int i = 0; i < classes; ++i)
17811759
{
17821760
double avg_precision = 0.0;
@@ -1787,8 +1765,15 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
17871765
// ImageNet - uses Area-Under-Curve on PR-chart.
17881766

17891767
// correct mAP calculation: ImageNet, PascalVOC 2010-2012
1790-
if (map_points == 0)
1768+
const int gt_i = truth_classes_count[i];
1769+
1770+
if (detections_count == 0)
1771+
{
1772+
// No detections at all -> AP remains 0 (unless you prefer to skip classes with gt_i==0)
1773+
}
1774+
else if (map_points == 0)
17911775
{
1776+
// VOC2010 / AUC of the precision envelope
17921777
double last_recall = pr[i][detections_count - 1].recall;
17931778
double last_precision = pr[i][detections_count - 1].precision;
17941779
for (int rank = detections_count - 2; rank >= 0; --rank)
@@ -1804,75 +1789,89 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
18041789
avg_precision += delta_recall * last_precision;
18051790
}
18061791
//add remaining area of PR curve when recall isn't 0 at rank-1
1807-
double delta_recall = last_recall - 0;
1792+
double delta_recall = last_recall - 0.0;
18081793
avg_precision += delta_recall * last_precision;
18091794
}
1810-
// MSCOCO - 101 Recall-points, PascalVOC - 11 Recall-points
18111795
else
18121796
{
1813-
int point;
1814-
for (point = 0; point < map_points; ++point)
1797+
// Sampled AP (VOC2007 11-pt, or COCO-style 101-pt sampling at a SINGLE IoU)
1798+
if (map_points < 2)
18151799
{
1816-
double cur_recall = point * 1.0 / (map_points-1);
1817-
double cur_precision = 0;
1818-
//double cur_prob = 0;
1800+
darknet_fatal_error(DARKNET_LOC, "map_points must be >= 2 (e.g., 11 or 101).");
1801+
}
1802+
1803+
for (int point = 0; point < map_points; ++point)
1804+
{
1805+
double cur_recall = (map_points == 1) ? 0.0 : (point * 1.0 / (map_points - 1));
1806+
double cur_precision = 0.0;
18191807
for (int rank = 0; rank < detections_count; ++rank)
18201808
{
1821-
if (pr[i][rank].recall >= cur_recall)
1809+
if (pr[i][rank].recall >= cur_recall &&
1810+
pr[i][rank].precision > cur_precision)
18221811
{
1823-
// > or >=
1824-
if (pr[i][rank].precision > cur_precision)
1825-
{
1826-
cur_precision = pr[i][rank].precision;
1827-
//cur_prob = pr[i][rank].prob;
1828-
}
1812+
cur_precision = pr[i][rank].precision;
18291813
}
18301814
}
1831-
18321815
avg_precision += cur_precision;
18331816
}
18341817
avg_precision = avg_precision / map_points;
18351818
}
18361819

1837-
// Accuracy: all correct / all = (TP + TN) / (TP + TN + FP + FN)
1838-
// Misclassification (error rate): all incorrect / all = (FP + FN) / (TP + TN + FP + FN)
1839-
// Precision: TP / predicted positives = TP / (TP + FP)
1840-
// Sensitivity aka recall: TP / all positives = TP / (TP + FN)
1841-
// Specificity (true negative rate): TN / all negatives = TN / (TN + FP)
1842-
// False positive rate: FP / all negatives = FP / (TN + FP)
1843-
1844-
const int all_detections = detection_per_class_count[i];
1845-
const int tp = tp_for_thresh_per_class[i];
1846-
const int fn = truth_classes_count[i] - tp;
1847-
const int fp = fp_for_thresh_per_class[i];
1848-
const int tn = all_detections - tp - fn - fp;
1849-
const float accuracy = static_cast<float>(tp + tn) / static_cast<float>(all_detections);
1850-
const float error_rate = static_cast<float>(fp + fn) / static_cast<float>(all_detections);
1851-
const float precision = static_cast<float>(tp) / static_cast<float>(tp + fp);
1852-
const float recall = static_cast<float>(tp) / static_cast<float>(tp + fn);
1853-
const float specificity = static_cast<float>(tn) / static_cast<float>(tn + fp);
1854-
const float false_pos_rate = static_cast<float>(fp) / static_cast<float>(tn + fp);
1820+
// Final (threshold-free) counts at last rank
1821+
int tp_final = 0, fp_final = 0;
1822+
if (detections_count > 0)
1823+
{
1824+
tp_final = pr[i][detections_count - 1].tp;
1825+
fp_final = pr[i][detections_count - 1].fp;
1826+
}
1827+
const int fn_final = std::max(0, gt_i - tp_final);
18551828

1829+
// Optional diagnostic IoU at the chosen conf threshold
1830+
const float diag_avg_iou_at_thresh =
1831+
(tp_for_thresh_per_class[i] + fp_for_thresh_per_class[i]) > 0 ? (avg_iou_per_class[i]) : 0.0f;
1832+
1833+
// Header (once)
18561834
if (i == 0)
18571835
{
18581836
*cfg_and_state.output
18591837
<< std::endl
18601838
<< std::endl
1861-
<< " Id Name AvgPrecision TP FN FP TN Accuracy ErrorRate Precision Recall Specificity FalsePosRate" << std::endl
1862-
<< " -- ---- ------------ ------ ------ ------ ------ -------- --------- --------- ------ ----------- ------------" << std::endl;
1839+
<< " Id Name AP(%) TP FP FN GT AvgIoU@conf(%)"
1840+
<< std::endl
1841+
<< " -- -------------------- --------- ------ ------ ------ ------ -----------------"
1842+
<< std::endl;
18631843
}
18641844

1865-
*cfg_and_state.output << Darknet::format_map_confusion_matrix_values(i, net.details->class_names[i], avg_precision, tp, fn, fp, tn, accuracy, error_rate, precision, recall, specificity, false_pos_rate) << std::endl;
1845+
// Colored row
1846+
*cfg_and_state.output
1847+
<< Darknet::format_map_ap_row_values(
1848+
/*class_id*/ i,
1849+
/*name*/ net.details->class_names[i],
1850+
/*AP*/ (float)avg_precision,
1851+
/*TP*/ tp_final,
1852+
/*FP*/ fp_final,
1853+
/*FN*/ fn_final,
1854+
/*GT*/ gt_i,
1855+
/*diag IoU*/ diag_avg_iou_at_thresh)
1856+
<< std::endl;
18661857

18671858
// send the result of this class to the C++ side of things so we can include it the right chart
1868-
Darknet::update_accuracy_in_new_charts(i, avg_precision);
1859+
Darknet::update_accuracy_in_new_charts(i, (float)avg_precision);
18691860

18701861
mean_average_precision += avg_precision;
18711862
}
18721863

1873-
const float cur_precision = (float)tp_for_thresh / ((float)tp_for_thresh + (float)fp_for_thresh);
1874-
const float cur_recall = (float)tp_for_thresh / ((float)tp_for_thresh + (float)(unique_truth_count - tp_for_thresh));
1875-
const float f1_score = 2.F * cur_precision * cur_recall / (cur_precision + cur_recall);
1864+
// Diagnostic summary (guard divisions)
1865+
float cur_precision = 0.f, cur_recall = 0.f, f1_score = 0.f;
1866+
const int det_denom = tp_for_thresh + fp_for_thresh;
1867+
if (det_denom > 0)
1868+
cur_precision = (float)tp_for_thresh / det_denom;
1869+
1870+
if (unique_truth_count > 0)
1871+
cur_recall = (float)tp_for_thresh / (float)unique_truth_count;
1872+
1873+
if ((cur_precision + cur_recall) > 0.f)
1874+
f1_score = 2.f * cur_precision * cur_recall / (cur_precision + cur_recall);
18761875

18771876
*cfg_and_state.output
18781877
<< std::endl
@@ -1898,7 +1897,7 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
18981897
*cfg_and_state.output << "used area-under-curve for each unique recall" << std::endl;
18991898
}
19001899

1901-
mean_average_precision = mean_average_precision / classes;
1900+
mean_average_precision = (classes > 0) ? (mean_average_precision / classes) : 0.0;
19021901
*cfg_and_state.output
19031902
<< "mean average precision (mAP@" << std::setprecision(2) << iou_thresh << ")="
19041903
<< Darknet::format_map_accuracy(mean_average_precision)
@@ -1954,7 +1953,7 @@ float validate_detector_map(const char * datacfg, const char * cfgfile, const ch
19541953
free(buf);
19551954
free(buf_resized);
19561955

1957-
return mean_average_precision;
1956+
return (float)mean_average_precision;
19581957
}
19591958

19601959
typedef struct {

0 commit comments

Comments
 (0)