@@ -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
19601959typedef struct {
0 commit comments