PV-RCNN:paper,code
配套实践篇:
PV-RCNN代码测试——TP,FP,TN,FN的计算(一)
PV-RCNN代码测试——TP,FP,TN,FN的计算(二)
后面的代码中涉及到目标检测中几个重要的定义:
True positives
: 正样本被正确识别为正样本,飞机的图片被正确的识别成飞机。
True negatives
: 负样本被正确识别为负样本,飞机的图片被正确地识别为不是大雁。
False positives
: 负样本被错误识别为正样本,飞机的图片被错误地识别成了大雁。
False negatives
: 正样本被错误识别为负样本,飞机的图片被错误地识别为不是飞机。
Precision
:检索出来的条目中有多大比例是我们需要的。
Recall
:我们需要的条目中有多大比例被检索出来了。
compute_statistics_jit
函数的调用
# pcdet/datasets/kitti/kitti_object_eval_python/eval.py
# 计算统计量tp, fp, fn, similarity, thresholds
rets = compute_statistics_jit(
overlaps[i], # 单个图像的iou值b/n gt和dt
gt_datas_list[i], # N x 5阵列
dt_datas_list[i], # N x 6阵列
ignored_gts[i], # 长度N数组,-1、0、1
ignored_dets[i], # 长度N数组,-1、0、1
dontcares[i], # 无关框数量x 4
metric, # 0, 1, 或 2 (bbox, bev, 3d)
min_overlap=min_overlap, # 浮动最小IOU阈值为正
thresh=0.0, # 忽略得分低于此值的dt。
compute_fp=False)
tp, fp, fn, similarity, thresholds, _ = rets
compute_statistics_jit
函数的定义
# pcdet/datasets/kitti/kitti_object_eval_python/eval.py
# 计算统计量tp, fp, fn, similarity, thresholds
def compute_statistics_jit(overlaps,
gt_datas,
dt_datas,
ignored_gt,
ignored_det,
dc_bboxes,
metric,
min_overlap,
thresh=0,
compute_fp=False, # 如果我们在计算召回阈值(recall thresholds),则将compute_fp设置为False。
compute_aos=False):
det_size = dt_datas.shape[0]
gt_size = gt_datas.shape[0]
dt_scores = dt_datas[:, -1]
dt_alphas = dt_datas[:, 4]
gt_alphas = gt_datas[:, 4]
dt_bboxes = dt_datas[:, :4]
gt_bboxes = gt_datas[:, :4]
assigned_detection = [False] * det_size # 存储是否每个检测都分配给了一个gt。
ignored_threshold = [False] * det_size # 如果检测分数低于阈值,则存储数组
if compute_fp:
for i in range(det_size):
if (dt_scores[i] < thresh):
ignored_threshold[i] = True
NO_DETECTION = -10000000
tp, fp, fn, similarity = 0, 0, 0, 0
# thresholds = [0.0]
# delta = [0.0]
thresholds = np.zeros((gt_size, ))
thresh_idx = 0 # 用于计算阈值
delta = np.zeros((gt_size, ))
delta_idx = 0
#! detection-toolbox的作者补充的代码 ----
extra = {
#! 对于每个gt框,存储它是否为-1 (忽略), 0 (false negative (不匹配)), 1 (true positive (匹配))
"gt_box_type": np.full((gt_size, ), -1),
#! 对于每个dt框,存储它是否为-1 (无关), 0 (false positive (不匹配)), 1 (true positive (匹配))
#! -1表示它在don't care范围内,属于其他类,等等
"dt_box_type": np.full((det_size, ), -1),
#! 存储匹配对象的idx
"gt_box_matched_idx": np.full((gt_size, ), -1),
"dt_box_matched_idx": np.full((det_size, ), -1)
}
#! 遍历gt框
for i in range(gt_size): #遍历gt
if ignored_gt[i] == -1: #! 不要匹配完全不相关的gt框
continue
det_idx = -1 #! 储存对此gt存储的最佳检测的idx
valid_detection = NO_DETECTION #! 存储到目前为止检测到的最大分数。
max_overlap = 0 #! 最佳检测的overlap。best是最高重叠
assigned_ignored_det = False
for j in range(det_size): #遍历dt
if (ignored_det[j] == -1): #! 与完全不相关的dt框不匹配
continue
if (assigned_detection[j]): #! 如果已经分配了dt,请跳过(分配给更好的gt)
continue
if (ignored_threshold[j]): #! 如果dt分数低于阈值,则跳过
continue
overlap = overlaps[j, i] #! 当前此dt和此gt之间的overlap
dt_score = dt_scores[j] #! 当前dt的分数
if (not compute_fp # compute_fp为false,则这是唯一重要的部分
and (overlap > min_overlap) # 找到足够的重叠
and dt_score > valid_detection): # 找最高分的检测
det_idx = j
valid_detection = dt_score
elif (compute_fp #! 当compute_fp为true时,我们基于overlap进行选择
and (overlap > min_overlap) #! compute_fp为true。 这意味着我们正在度量,而不是设置阈值。
and (overlap > max_overlap or assigned_ignored_det) #! 如果重叠足够(比以前的重叠好,或者我们不关心以前的重复)
and ignored_det[j] == 0): #而当前的需求是我们所关心的,
max_overlap = overlap # 分配
det_idx = j #更新重叠的det_idx
valid_detection = 1 #由于我们没有按分数排名,因此我们对有效检测进行了留一法检验。
assigned_ignored_det = False #用留一法来表明已经分配了关心的单位
elif (compute_fp #! compute_fp为true。
and (overlap > min_overlap) #! 如果重叠足够
and (valid_detection == NO_DETECTION) #尚未分配任何东西
and ignored_det[j] == 1): #是我们不关心的检测
det_idx = j #我们分配它,因为我们将max_overlap保留为默认设置,因此任何内容都可以覆盖它。
valid_detection = 1
assigned_ignored_det = True
#? 一个奇怪的事情是我们不关心的事物,如果我们分配第一个,则下一个无法覆盖它,因为valid_detection!= NO_DETECTION
if (valid_detection == NO_DETECTION) #! 如果我们无法将此gt与任何东西匹配
and ignored_gt[i] == 0: #而这是我们关心的事情
fn += 1 #那将是一个false negative
extra['gt_box_type'][i] = 0
elif ((valid_detection != NO_DETECTION) #! 如果我们确实将此gt与某些内容匹配
and (ignored_gt[i] == 1 or ignored_det[det_idx] == 1 )): #gt是我们不关心的东西,或者det是我们不关心的东西
assigned_detection[det_idx] = True #! 我们分配它
# 为什么? 可能是因为如果我们不分配它,那么以后它将是一个误报(未分配的det)
extra['gt_box_type'][i] = -1
extra['dt_box_type'][det_idx] = -1
elif valid_detection != NO_DETECTION: #! 如果我们确实将此gt与某些内容匹配,
#! 剩下的条件是:gt是我们关心的东西,det是我们关心的东西
#! true positive.
extra['gt_box_type'][i] = 1
extra['dt_box_type'][det_idx] = 1
extra['gt_box_matched_idx'][i] = det_idx
extra['dt_box_matched_idx'][det_idx] = i
tp += 1 # 仅在tp上添加阈值。
# thresholds.append(dt_scores[det_idx])
thresholds[thresh_idx] = dt_scores[det_idx] #! 在这里,我们还将det分数附加到阈值的末尾
thresh_idx += 1
if compute_aos:
# delta.append(gt_alphas[i] - dt_alphas[det_idx])
delta[delta_idx] = gt_alphas[i] - dt_alphas[det_idx]
delta_idx += 1
assigned_detection[det_idx] = True #! 然后,将检测分配为True
#! 在没有检测到结果并且gt是我们不在乎的时候
else:
extra['gt_box_type'][i] = -1
#? 到目前为止,我们还没有使用 dc boxes。 这是因为它们仅用于误报计算(false positive )因为我们还没有研究过不匹配的检测
#? 如果里面有一个不匹配的东西不在乎,我们就不算它为FP
if compute_fp:
for i in range(det_size): #! 遍历检测
if (not (assigned_detection[i] #未分配给gt
or ignored_det[i] == -1 #! 不同的类别
or ignored_det[i] == 1 #! 大小不同
or ignored_threshold[i])): #! 不低于分数阈值
fp += 1
extra['dt_box_type'][i] = 0 #! false positive!
#! 这是我们从无关区域中收集到的检测数量。 我们将从fp中减去它。
nstuff = 0
if metric == 0: #! Metric == 0 是 2d bbox
overlaps_dt_dc = image_box_overlap(dt_bboxes, dc_bboxes, 0) #! dt盒和dc盒之间的ious。
for i in range(dc_bboxes.shape[0]):
for j in range(det_size):
# 跳过上面没有添加到fp的内容
if (assigned_detection[j]):
continue
if (ignored_det[j] == -1 or ignored_det[j] == 1):
continue
if (ignored_threshold[j]):
continue
if overlaps_dt_dc[j, i] > min_overlap: #! 如果两者之间的重叠大于min_overlap
assigned_detection[j] = True # 将检测结果分配给dc
nstuff += 1 #并将其添加到我们从fp中获得的东西中。
extra['dt_box_type'][j] = -1
fp -= nstuff # 从FP减去nstuff
if compute_aos:
tmp = np.zeros((fp + delta_idx, ))
# tmp = [0] * fp
for i in range(delta_idx):
tmp[i + fp] = (1.0 + np.cos(delta[i])) / 2.0
# tmp.append((1.0 + np.cos(delta[i])) / 2.0)
# assert len(tmp) == fp + tp
# assert len(delta) == tp
if tp > 0 or fp > 0:
similarity = np.sum(tmp)
else:
similarity = -1
'''
! 我们在这里说明条件。
! if compute_fp:
! tp和fn在这里,fp和similarity就没有用
! thresholds[:thresh_idx] = thresholds
! 因此,基本上,我们必须使用工具来计算召回率和匹配的dts的得分。
'''
return tp, fp, fn, similarity, thresholds[:thresh_idx]
致谢:detection-toolbox的作者将代码中的注释写的太清晰了,对我理解代码起到了很大帮助。有需要的读者可以看英文原版。