目标检测中常见且标准化的评价指标主要围绕 定位精度(Localization)分类正确性(Classification)整体检测性能 三个方面。
参考链接:目标检测的评价指标(七个)
机器学习分类中的四大指标:TP、TN、FP、FN

一、样本

首先说明一下样本的概念,样本分为正样本负样本。正样本即为需要被检测出的物体,负样本是不需要被检测出的物体。由此可衍生出真正例(TP)、假正例(FP)、假负例(FN)和真负例(TN)四个概念

真正例 TP

实际为正类的样本被正确地预测为正类的数量

假正例 FP

即负样本被检测为正样本的数量,俗称误报

假负例 FN

实际为正类的样本被错误地预测为负类,也称漏报

真负例 TN

实际为负类的样本被正确地预测为负类,无法计算

二、交并比(Intersection over Union, IoU)

用于判断预测框是否命中,顾名思义交并比的意思就是交集区域和并集区域的比值。通常会对IOU设置阈值进行预测标签筛选,常见阈值:IoU ≥ 0.5(VOC)、IoU ≥ 0.75(更严格)、 0.5:0.95(COCO,多阈值平均)

三、 Precision(精确率)

模型预测为“目标”的框中,有多少是真的,关注误检

四、Recall(召回率)

GT 中有多少目标被成功检测,关注漏检

五、Precision–Recall 曲线与 AP

PR Curve(Precision–Recall 曲线)

通过调整 confidence score 阈值得到不同的预测结果,对每一个阈值: 计算对应的 Precision 和 Recall并画在坐标系中(横轴:Recall 纵轴:Precision)
PR 曲线本质上反映的是: 曲线越靠右上角 → 模型越好 曲线越平滑、越高 → 模型稳定性越好

AP(Average Precision)

即 PR 曲线下的面积

mAP(mean Average Precision)

即多类别平均,计算出每个类别的AP以后,对于所有类别的AP取均值就得到mAP了

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#计算recall, precision和AP
class_recs = {}
npos = 0
for imagename in imagenames:
R = [obj for obj in recs[imagename] if obj['name'] == classname]
bbox = np.array([x['bbox'] for x in R])
difficult = np.array([x['difficult'] for x in R]).astype(np.bool)
det = [False] * len(R) #这个值是用来判断是否重复检测的
npos = npos + sum(~difficult)
class_recs[imagename] = {'bbox': bbox,
'difficult': difficult,
'det': det}

# read dets
detfile = detpath.format(classname)
with open(detfile, 'r') as f:
lines = f.readlines()

splitlines = [x.strip().split(' ') for x in lines]
image_ids = [x[0] for x in splitlines]
confidence = np.array([float(x[1]) for x in splitlines])
BB = np.array([[float(z) for z in x[2:]] for x in splitlines])

# sort by confidence
sorted_ind = np.argsort(-confidence)
BB = BB[sorted_ind, :]
image_ids = [image_ids[x] for x in sorted_ind]

# go down dets and mark TPs and FPs
nd = len(image_ids)
tp = np.zeros(nd)
fp = np.zeros(nd)
for d in range(nd):
R = class_recs[image_ids[d]]
bb = BB[d, :].astype(float)
ovmax = -np.inf
BBGT = R['bbox'].astype(float)

if BBGT.size > 0:
# compute overlaps
# intersection
ixmin = np.maximum(BBGT[:, 0], bb[0])
iymin = np.maximum(BBGT[:, 1], bb[1])
ixmax = np.minimum(BBGT[:, 2], bb[2])
iymax = np.minimum(BBGT[:, 3], bb[3])
iw = np.maximum(ixmax - ixmin + 1., 0.)
ih = np.maximum(iymax - iymin + 1., 0.)
inters = iw * ih

# union
uni = ((bb[2] - bb[0] + 1.) * (bb[3] - bb[1] + 1.) +
(BBGT[:, 2] - BBGT[:, 0] + 1.) *
(BBGT[:, 3] - BBGT[:, 1] + 1.) - inters)

overlaps = inters / uni
ovmax = np.max(overlaps)
jmax = np.argmax(overlaps)

if ovmax > ovthresh:
if not R['difficult'][jmax]:
if not R['det'][jmax]:
tp[d] = 1.
R['det'][jmax] = 1 #判断是否重复检测,检测过一次以后,值就从False变为1了
else:
fp[d] = 1.
else:
fp[d] = 1.

# compute precision recall
fp = np.cumsum(fp)
tp = np.cumsum(tp)
rec = tp / float(npos)
# avoid divide by zero in case the first detection matches a difficult
# ground truth
prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
ap = voc_ap(rec, prec, use_07_metric)

return rec, prec, ap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def voc_ap(rec, prec, use_07_metric=False):
"""Compute VOC AP given precision and recall. If use_07_metric is true, uses
the VOC 07 11-point method (default:False).
"""
if use_07_metric:
# 11 point metric
ap = 0.
for t in np.arange(0., 1.1, 0.1):
if np.sum(rec >= t) == 0:
p = 0
else:
p = np.max(prec[rec >= t])
ap = ap + p / 11.
else:
# correct AP calculation
# first append sentinel values at the end
mrec = np.concatenate(([0.], rec, [1.]))
mpre = np.concatenate(([0.], prec, [0.]))

# compute the precision envelope
for i in range(mpre.size - 1, 0, -1):
mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

i = np.where(mrec[1:] != mrec[:-1])[0]

# and sum (\Delta recall) * prec
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) #计算面积
return ap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def mAP():
detpath,annopath,imagesetfile,cachedir,class_path = get_dir('kitti')
ovthresh=0.3,
use_07_metric=False
rec = 0; prec = 0; mAP = 0
class_list = get_classlist(class_path)
for classname in class_list:
rec, prec, ap = voc_eval(detpath,
annopath,
imagesetfile,
classname,
cachedir,
ovthresh=0.5,
use_07_metric=False,
kitti=True)
print('on {}, the ap is {}, recall is {}, precision is {}'.format(classname, ap, rec[-1], prec[-1]))
mAP += ap
mAP = float(mAP) / len(class_list)
return mAP