第一步:需要确定计算TP的准则
一般情况,我们设定Iou_threadhold 阈值∈[0.5,0.55,0.6,0.65,0.7,0.75,0.8,0.85,0.9,0.95],这基于IoU阈值的情况下,计算当前检测结果Dt 与 真值Gt之间的IoU,大于阈值,则算是TP。小于阈值否则视为FP。TP+FP = 全部Dt数量
也可以自定义相关TP的准则,例如我们要求模型需要输出confidence,需要输出位置,速度。confidence需要>0.3,位置与真值需要小于0.1米,速度需要小于0.5m/s,才认为是TP。
参考了:what-is-map-understanding-the-statistic-of-choice-for-comparing-object-detection-models
第二步骤,基于TP数量,基于检测到的数量,基于真值数量,计算FP、FN、TN
基于以下理解,
全部的真值标注框的数量 num_gt,全部检测的框数量为num_dt
True Positive (TP): 检测存在,真值存在,真值标注框与检测框的Iou大于阈值;
False Positive (FP): 检测存在,真值不存在,真值标注框与检测框的Iou小于阈值;
所以:fp_num = num_dt - tp_num
False Negative (FN): 检测不存在,真值是存在,
所以:fn_num = num_gt - tp_num
For calculating Recall, we need the count of Negatives. Since every part of the image where we didn’t predict an object is considered a negative, measuring “True” negatives is a bit futile. So we only measure “False” Negatives ie. the objects that our model has missed out.
True Negative (TN): 检测是不存在,真值不存在。在 mAP 评价指标中不会使用到。
Negative 和Positive 是相对于检测器的Dt而言,Dt输出的是Positive 或者 Negative。
用真值去考核Dt输出的结果为True或者False。
第三步骤:计算整体的Precision 和 Recall
Precision = tp_num / num_dt
Recall = tp_num / num_gt
等价
Precision = tp_num /(tp_num +fp_num )
Recall = TP/(tp_num +fn_num )
计算 AP
AP:在某个IoU阈值下,PR 曲线下面积。
mAP:Iou_threadhold 阈值∈[0.5,0.55,0.6,0.65,0.7,0.75,0.8,0.85,0.9,0.95] 的情况下,可以分别得到AP值,mean Average Precision,即各类别 AP 的平均值。
PR曲线的绘制方法
这篇目标检测中PR曲线和mAP文章,我个人认为有点错误。
我按照源代码的步骤:
假设:目前有101张图片,共有真值 num_gt 397;IoU阈值0.5-0.95,每0.05取一个,共10个;(通过NMS后)输出的检测目标框 2944;
1、每个框的置信度 scores 的尺寸1×2944
2、计算TP的情况:首先设定self.tps 尺寸 101 ×10 ×n,101是图片数量,n是每个图片detection的检测到的框的数目,10是0.5-0.95的Iou阈值。self.tps 里面只存储False或者True。
3、对self.tps 转换到 10 ×2944
tps = torch.cat(self.tps, dim=1)
4、对 score进行排序,
scores = torch.cat(self.scores, dim=0)
scores, sortidx = torch.sort(scores, dim=0, descending=True)# 全部的图片的全部检测的框的得分排序
5、对TP进行排序,按照score进行排序
tps = tps[:,sortidx]
返回结果:sortidx tensor([2902, 2922, 2703, ..., 1245, 1546, 2901])
6、tps 的尺寸还是 10 ×2944,10行,2944列
7、对tps 的每一列的2944个,累加计算 True 的数目
tp_sum = torch.cumsum(tps, dim=1)
print("tp_sum",tp_sum.shape[0],tp_sum.shape[1])
print("tp_sum0",tp_sum[0][0])
print("tp_sum100",tp_sum[0][100])
print("tp_sum1000",tp_sum[0][1000])
print("tp_sum1000",tp_sum[0][-1])
返回结果:
tp_sum 10 2944
tp_sum0 tensor(1.)
tp_sum100 tensor(48.)
tp_sum1000 tensor(126.)
tp_sum1000 tensor(144.)
这里特别说明下, torch.cumsum
返回维度dim中输入元素的累计和,维度保持不变,输出的第一个数据和输入一致,输出最后的数据为输入数据的全部的和.
例如
一维数据:
x1 = torch.arange(0, 6)
print(x1)
y1 = torch.cumsum(x1, dim=0)
print(y1)
结果分析:
y1(dim=0)的结果是:第一列不变,后面的列依次在上一列基础上加上自身的数。
第1列,值0,不变,和为本身,即:0;
第2列,值1,累加前一列值0,和为:0+1=1
第3列,值2,累加前一列值1,和为:1+2=3
第4列,值3,累加前一列值3,和为:3+3=6
第5列,值4,累加前一列值6,和为:6+4=10
第6列,值5,累加前一列值10,和为:10+5=15
8、计算fp的情况, tp+fp =2944,所以,直接可以取反得到fp数目
fps = ~tps
9、对 fps 的每一列的2944个,累加计算True的数目
print("tp_sum0",fp_sum[0][0])
print("tp_sum100",fp_sum[0][100])
print("tp_sum1000",fp_sum[0][1000])
print("tp_sum1000",fp_sum[0][-1])
返回结果
tp_sum0 tensor(0.)
tp_sum100 tensor(53.)
tp_sum1000 tensor(875.)
tp_sum1000 tensor(2800.)
10、计算precision 和 recall
precision = tp_sum / (tp_sum+fp_sum)
recall = tp_sum / num_gt
print("num_gt",num_gt)
print("precision",precision.shape[0],precision.shape[1])
返回结果
num_gt 397 precision 10 2944
11、初始化PR图片
self.rec_thres = 100
PRcurve = torch.zeros(len(self.iou_thres),len(self.rec_thres))
12、在PR 中填入precision 和 recall,但是要保证图片是单调递减的
for ti, (prec_T,rc_T) in enumerate(zip(precision, recall)):
# 保证了单调降低
for i in range(num_dt-1,0,-1):
if prec_T[i] > prec_T[i-1]:
prec_T[i-1] = prec_T[i]
# find the 100 recall points
idxs = np.searchsorted(rc_T, self.rec_thres, side='left')
# fill in the P-R curve
for ri,pi in enumerate(idxs):
if pi >= len(prec_T):
# reach the upper bound of Recall
break
PRcurve[ti,ri] = prec_T[pi]
13、返回AP的结果,尺寸10×1
APs = self.PRcurve.mean(dim=1)
# 将10× 100的PR表格,压缩到10×1
14、返回最佳阈值
f1 = 2 * (precision*recall) / (precision + recall)
self.best_thres = scores[torch.argmax(f1, dim=1)]
结束
代码,可以参考我的一份gitee代码
other
(1)交并比 - Intersection Over Union (IOU)
交并比(IOU)是度量两个检测框(对于目标检测来说)的交叠程度,公式如下:
B_gt 代表的是目标实际的边框(Ground Truth,GT),B_p 代表的是预测的边框,通过计算这两者的 IOU,可以判断预测的检测框是否符合条件,IOU 用图片展示如下: