"""
评价模型的核心函数:根据得到的正负样本,输出:P,R,map等
"""
#验证集或测试集都可以用
def evaluate(model, path, iou_thres, conf_thres, nms_thres, img_size, batch_size):
model.eval()
# Get dataloader
dataset = ListDataset(path, img_size=img_size, augment=False, multiscale=False)
dataloader = torch.utils.data.DataLoader(
dataset, batch_size=batch_size, shuffle=False, num_workers=2, collate_fn=dataset.collate_fn
)
Tensor = torch.cuda.FloatTensor if torch.cuda.is_available() else torch.FloatTensor
labels = []
sample_metrics = [] # List of tuples (TP, confs, pred)
for batch_i, (_, imgs, targets) in enumerate(tqdm.tqdm(dataloader, desc="Detecting objects")):
#将dataloader迭代器枚举出来,一个batch一个batch的操作
# Extract labels
labels += targets[:, 1].tolist()
# Rescale target,训练时图片有个固定尺寸,因此原图像的尺度标注也需要放缩
targets[:, 2:] = xywh2xyxy(targets[:, 2:])#不同数据标注方法的结果转换
targets[:, 2:] *= img_size
imgs = Variable(imgs.type(Tensor), requires_grad=False)
with torch.no_grad():
outputs = model(imgs)
outputs = non_max_suppression(outputs, conf_thres=conf_thres, nms_thres=nms_thres)#对每张图片作抑制,只在测试或验证时采用
#这里get_batch_statistics的输出是每个batch的推理统计结果,然后循环追加所有batch结果,完成整个验证集的统计结果
sample_metrics += get_batch_statistics(outputs, targets, iou_threshold=iou_thres)
# Concatenate sample statistics
true_positives, pred_scores, pred_labels = [np.concatenate(x, 0) for x in list(zip(*sample_metrics))]
#在axis = 0 方向拼接,得到了分离的true_positives, pred_scores, pred_labels
precision, recall, AP, f1, ap_class = ap_per_class(true_positives, pred_scores, pred_labels, labels)#这里的ap_per_class是关键评估函数。
#用模型对整个验证集有效预测的结果[np.concatenate(x, 0) for x in list(zip(*sample_metrics))] 和 labels 标签求指标
return precision, recall, AP, f1, ap_class#得到一个验证集的每个类别的P,R,ap,f1,ap的类别。其实就是nums = num_class张PR曲线。每个变量的第一维度都等于类别数
"""
推理的核心函数,
输入:nms抑制之后的结果
输出:模型预测得到的正样本和负样本()
"""
#outputs:模型的原始预测输出仅经过nms抑制后的结果(nms抑制中做了置信度阈值下界的过滤,认为预测得分小于0.5的不算前景),还没有经过最终的判别置信度阈值过滤
#经过get_batch_statistics后就统计出最终的正确检测数目(正样本)
#iou_threshold这个阈值是预测框和gt_box的交并比。和nms一定要分开。
#两个IOU计算对象不同,
def get_batch_statistics(outputs, targets, iou_threshold):#iou_threshold参数:用nms之后得到的每个样本与gt_box做IOU,超过此iou_threshold就认为是正样本;但是对于某gt_box只有与其具有最大IOU的正样本(P)才是真正例(TP)。
#这里是验证时结果与标签对比
""" Compute true positives, predicted scores and predicted labels per sample """
batch_metrics = []
for sample_i in range(len(outputs)):
#以下针对单张图片的操作,len(outputs)就是一次推理的样本(图片)数目
if outputs[sample_i] is None:
continue
#这里output是什么样的,很关键,明白每个数据的shape后面就简单了,outputs第0维大小就是batchsize。
output = outputs[sample_i]#这里output是单图片
pred_boxes = output[:, :4]#pred_boxes也是单图片
pred_scores = output[:, 4]
pred_labels = output[:, -1]
#true_positives指的正样本(目标)的数量
true_positives = np.zeros(pred_boxes.shape[0])#生成一个形状的0填充的数组,最终记录正确预测的box的索引,正确即为1
#targets是什么样的[],看这操作就是通过sample_i去对应targets的部分
annotations = targets[targets[:, 0] == sample_i][:, 1:]#有点像反查!生成标注的数组,排除掉了第一维,是什么???
target_labels = annotations[:, 0] if len(annotations) else []#从一张图片标注中提取 类别标签
#取样本标注的第一维,即类别标签
if len(annotations):
#一条annotation就是一个gt_box的标签信息
#如果这个样本是有标注的,即这样本有需要回归预测的object
detected_boxes = []
target_boxes = annotations[:, 1:]#从标注中提取 位置标签
#下面是个常用操作enumerate(zip(pred_boxes, pred_labels)),是不是应该自己写看看作用效果???
#分而治之
"""
对一张图片的pre_box遍历
"""
for pred_i, (pred_box, pred_label) in enumerate(zip(pred_boxes, pred_labels)):
# If targets are found break
if len(detected_boxes) == len(annotations):
break
#这里指,如果下面的break
# Ignore if label is not one of the target labels
if pred_label not in target_labels:#如果预测标签不属于这张图片标注的任何一个标签
continue#这里是假正例
#IOU等没必要再做了。
"""
以下就是要从正样本里确定真正例:与每个gt_box具有最大IOU且这个值>iou_threshold的才是真正例。因此每个pred_box都会标记一个与gt_box有最大IOU的gt_box的box_index,但是如果已经被更大的IOU的pred_box标记过了,则就是假正例。
iou >= iou_threshold:正样本
box_index not in detected_boxes:如果有多个正样本pre_box预测为同一个gt_box,那么与gt_box的IOU最大的是真正例,其余都是假正例。
"""
#这个tensor的max函数很重要,既能返回最大值,又能返回最大值的位置索引。这里都是同一个类别的。
iou, box_index = bbox_iou(pred_box.unsqueeze(0), target_boxes).max(0)#目的:找出一张图片中某个pred_box与哪个gt_box具有最大IOU,索引box_index就是gt_box的编号
#将此样本的预测pre_box与所有gt_box做IOU计算,得到bbox_iou(pred_box.unsqueeze(0), target_boxes),一个len==dets_nums的单图张量
if iou >= iou_threshold and box_index not in detected_boxes:#只有与gt_box交并比大于阈值,且此之前别的pre_box没预测到这个gt_box才算。
#nms之后,正负样本的划分只看与gt_box的IOU阈值置信度;如果超过某个阈值,即推理结果为真正例。在此基础上作PR曲线,做PR曲线用到的是推理置信度即那个score。
#而正样本中的真正例,还要看是不是与此gt_box具有最大IOU
true_positives[pred_i] = 1#模型认为这个预测是正样本中的真阳例。
detected_boxes += [box_index]#认为一个gt_box被正确detected到
batch_metrics.append([true_positives, pred_scores, pred_labels])#一个图片一个图片的追加,最终的shape:[3,batch_dets_nums]
#true_positives所有为1的对应正样本,其余负样本。这样一个batch的统计就弄完了。
#最终batch_metrics的里面第一维是Batch大小,即有Batch个[true_positives, pred_scores, pred_labels]元素,每一个元素代表一个样本:
# true_positives:预测正确(类别正确且大于置信度阈值,这里用IOU阈值衡量了)的位置就是1,如[0,0,1,1,0]
# pred_scores: 预测置信度
# pred_labels:预测标签
return batch_metrics#这里得到是有真正例的索引!!!