问题描述
在使用mmdetection做实验时,使用Pascal VOC数据集,模型为Faster RCNN,测试时一般使用的代码为:
bash tools/dist_test.sh \
configs/pascal_voc/faster_rcnn_r50_fpn_1x_voc0712.py \
checkpoints/faster_rcnn_r50_fpn_1x_voc0712_20220320_192712-54bef0f3.pth \
4 \
--eval mAP
在实验过程中想要观察recall(召回率),确认一些实验现象,按照代码里的提示,直接将eval改为recall,即:
...
--eval recall
此时会出现报错信息:
File "mmdetection/mmdet/core/evaluation/recall.py", line 90, in eval_recalls
if proposals[i].ndim == 2 and proposals[i].shape[1] == 5:
AttributeError: 'list' object has no attribute 'ndim'
mmdetection代码库里也有对应的issue:Find an error when evaluating recall in VOC Dataset #5792
可以看到,这就是mmdetection自己的bug:
def eval_recalls(gts,
proposals,
proposal_nums=None,
iou_thrs=0.5,
logger=None):
"""Calculate recalls.
Args:
gts (list[ndarray]): a list of arrays of shape (n, 4)
proposals (list[ndarray]): a list of arrays of shape (k, 4) or (k, 5)
proposal_nums (int | Sequence[int]): Top N proposals to be evaluated.
iou_thrs (float | Sequence[float]): IoU thresholds. Default: 0.5.
eval_recalls函数里的proposals期望接受的是list[nparray] 类型的数据,而实际传入的是list[list] 类型的数据。下面改正一些代码,修复这个bug。
问题解决
需要修改的地方为:mmdetection/mmdet/core/evaluation/recall.py中的eval_recalls函数,一共需要修改两个地方。
原本的proposals为4952*20的一个list,即4952张照片,每张照片有20个类别各自的proposal。list里面存储的是大小为k * 5的一个nparray,k为该图片该类别下具有的proposal的数量,如果为0的话,存储的就是空矩阵。
这里原始的每个proposals[i] 为长度为20,内容不固定的list;而函数希望的proposals[i] 的形式为:n * 5的一个nparray,n为无论什么类别、所有proposal的数量。因此这里对每个proposals[i] 做一个转换。代码修改如下:
def eval_recalls(gts,
proposals,
proposal_nums=None,
iou_thrs=0.5,
logger=None,
use_legacy_coordinate=False):
img_num = len(gts)
assert img_num == len(proposals)
proposal_nums, iou_thrs = set_recall_param(proposal_nums, iou_thrs)
all_ious = []
for i in range(img_num):
# modify 1
new_proposals = []
for j in range(len(proposals[i])):
idx = 0
while idx < proposals[i][j].shape[0]:
new_proposals.append(proposals[i][j][idx])
idx = idx + 1
proposals[i] = np.array(new_proposals)
# modify1
if proposals[i].ndim == 2 and proposals[i].shape[1] == 5:
scores = proposals[i][:, 4]
sort_idx = np.argsort(scores)[::-1]
img_proposal = proposals[i][sort_idx, :]
else:
img_proposal = proposals[i]
print(img_proposal.shape)
需要修改的第二个地方为:转化之后有的proposals[i] 可能是空的,即该张照片下没有一张预测出来的proposal,而函数里没有考虑这种情况,会导致bug,因此在if语句里加上判断:img_proposal.shape[0] == 0 来保证代码正常运行。代码如下:
prop_num = min(img_proposal.shape[0], proposal_nums[-1])
# modify2
if gts[i] is None or gts[i].shape[0] == 0 or img_proposal.shape[0] == 0:
# modify2
ious = np.zeros((0, img_proposal.shape[0]), dtype=np.float32)
else:
ious = bbox_overlaps(
gts[i],
img_proposal[:prop_num, :4],
use_legacy_coordinate=use_legacy_coordinate)
all_ious.append(ious)
all_ious = np.array(all_ious)
recalls = _recalls(all_ious, proposal_nums, iou_thrs)
print_recall_summary(recalls, proposal_nums, iou_thrs, logger=logger)
return recalls