本文记录计算目标检测AP(average precision) 的方法和c++实现
目标检测的输出也就是AP计算的输入
// 每张测图有多个目标物体
std::vector<std::vector<cv::RotatedRect>> pred_bbox; // predicted rotated bbox
std::vector<std::vector<float>> pred_score; // prediction score
std::vector<std::vector<cv::RotatedRect>> gt_bbox; // ground truth object rotated bbox
assert(pred_rbox.size() == pred_score.size())
计算过程
先计算得到precision和recall (也就是得到PR曲线)
void CalcPRCurve(float thresh_val, const std::vector<std::vector<cv::RotatedRect>>& pred_bbox, const std::vector<std::vector<float>>& pred_score, const std::vector<std::vector<cv::RotatedRect>>& gt_bbox, std::vector<float>& precision, std::vector<float>& recall)
{
assert(!pred_bbox.empty());
assert(!pred_score.empty());
assert(!gt_bbox.empty());
assert(pred_bbox.size() == pred_score.size());
assert(pred_score.size() == gt_bbox.size());
precision = std::vector<float>();
recall = std::vector<float>();
// Step 1. Check each prediction positive or negative
std::vector<float> pred_score_all;
std::vector<int> positive_flag;
int gt_num = 0;
for (int i = 0; i < pred_bbox.size(); ++i) {
if (pred_score[i].size() != pred_bbox[i].size()) {
std::cerr << i << "-th image prediction input dimension wrong.\n";
return 0;
}
std::vector<bool> gt_bbox_check_flags(gt_bbox.size(), true);
for (int j = 0; j < pred_bbox[i].size(); ++j) {
pred_score_all.push_back(pred_score[i][j]);
positive_flag.push_back(IsPositive(thresh_val, pred_bbox[i][j], gt_bbox[i], gt_bbox_check_flags));
}
gt_num += gt_bbox[i].size();
}
// Step 2. Sort predictions by score from high to low
SortByScore(pred_score_all, positive_flag);
// Step 3. Calculate precision and recall
int positive_cnt = 0;
for (int i = 0; i < pred_score_all.size(); ++i) {
if (positive_flag[i]) {
positive_cnt++;
}
precision.push_back(float(positive_cnt) / float(i + 1));
recall.push_back(float(positive_cnt) / float(gt_num));
}
}
其中,判断检测物是否为真,以及排序的实现如下:
// A prediction is positive if IoU ≥ thresh_val
// If multiple detections of the same object are detected, it counts the first one (highest score) as positive while the rest as negative.
int IsPositive(float thresh_val, const cv::RotatedRect& rrt, const std::vector<cv::RotatedRect>& gt_bbox, std::vector<bool>& gt_bbox_check_flags)
{
assert(gt_bbox.size() == gt_bbox_check_flags.size());
for (int i = 0; i < gt_bbox.size(); ++i) {
if (!gt_bbox_check_flags[i]) { continue; }
const cv::RotatedRect> = gt_bbox[i];
cv::Mat intersect_region;
if (cv::rotatedRectangleIntersection(gt, rrt, intersect_region) >= 1) {
float area = cv::contourArea(intersect_region);
float gt_area = gt.size.area();
float pred_area = rrt.size.area();
float IoU = area / (gt_area + pred_area - area);
if (IoU >= thresh_val) {
gt_bbox_check_flags[i] = false;
return 1;
}
}
}
return 0;
}
// Sort both pred_score_all and positive_flag by pred_score_all
void SortByScore(std::vector<float>& pred_score_all, std::vector<int>& positive_flag)
{
assert(pred_score_all.size() == positive_flag.size());
std::vector<std::pair<float, int>> pairs;
for (size_t i = 0; i < pred_score_all.size(); ++i) {
pairs.push_back(std::make_pair(pred_score_all[i], positive_flag[i]));
}
// Sort the combined vector in descending order of scores
std::sort(pairs.begin(), pairs.end(), [](const std::pair<float, int>& a, const std::pair<float, int>& b) { return a.first > b.first; });
// Separate the sorted scores and flags back into separate vectors
for (size_t i = 0; i < pairs.size(); ++i) {
pred_score_all[i] = pairs[i].first;
positive_flag[i] = pairs[i].second;
}
}
最后计算AP
// Area Under Curve (AUC) method
float CalcAP(const std::vector<float>& precision, const std::vector<float>& recall)
{
assert(!precision.empty());
assert(!recall.empty());
assert(precision.size() == recall.size());
float last_r = 0;
float auc = 0;
for (int i = 1; i < precision.size(); ++i) {
if (precision[i] < precision[i-1]) {
auc += (recall[i - 1] - last_r) * precision[i - 1];
last_r = recall[i - 1];
}
}
auc += (recall.back() - last_r)*precision.back();
return auc * 100; // in percentage
}
计算mAP
上述实现针对一个类别的物体检测的AP值,当有多个类别(n个),则针对每个类别的检测结果,用上述方法计算一个对应的AP值,然后求n个AP值得平均
计算AP50, AP75
AP50, AP75以及类似指标,是指AP值分别对应IoU计算的threshold = 0.5 和0.75 的情况,可以修改CalcPRCurve的第一个参数thresh_val,设为0.5.0.75,计算相应的AP即可