单阶段目标检测:YOLOv5中的指标计算

这篇具有很好参考价值的文章主要介绍了单阶段目标检测:YOLOv5中的指标计算。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

个人觉得,单目标检测相比分割复杂的地方主要在于(1)样本分配策略(2)预测结果后处理以及指标计算。这次记录一下指标计算,下次有时间记录一下目标检测中的样本分配策略。
本文以YOLOv5 7.0的val代码为例子,解析单阶段目标检测是怎么计算指标的。这里只展示核心代码,完整代码见github。

数据集介绍

首先介绍一下我的数据集。我使用的数据集是txt格式的,一共是三个类别。我使用的权重是用YOLOv5在我的数据集上训练得到的。批量大小设置为8,标签读入后的张量为(标签格式为xywh,原本的标签中的坐标其实是归一化的,这里乘上了图像大小):
单阶段目标检测:YOLOv5中的指标计算
这个张量每行代表一个目标框,每一行从左到右代表1.标签所属图像的索引 2.标签的类别 3.中心点横坐标 4.中心点纵坐标 5.目标框宽度 6.目标框高度。

代码解析

1.创建Dataloader
dataloader = create_dataloader(data[task],    # 测试集的路径
                               imgsz,         # 推理时的图像size
                               batch_size,    # 批量大小
                               stride,        # 步距
                               single_cls,    # 数据集类别是否只有一类
                               pad=pad,       # 填充
                               rect=rect,     # 是否使用平方推理
                               workers=workers,  
                               prefix=colorstr(f'{task}: '))[0]
2.推理与非极大值抑制
conf_thres = args.conf_thres
iou_thres = args.iou_thres
pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT)

for batch_i, (im, targets, paths, shapes) in enumerate(pbar):    
    if cuda:  
        im = im.to(device, non_blocking=True)  
        targets = targets.to(device)  
    im = im.half() if half else im.float()  # uint8 to fp16/32  
    im /= 255  # 0 - 255 to 0.0 - 1.0  
    nb, _, height, width = im.shape  # batch size, channels, height, width  
  
    # Inference    
    preds = model(im, augment=augment)
  
    # NMS
    # txt格式标签默认是归一化的(0~1),这里把他还原到图像尺寸    
    targets[:, 2:] *= torch.tensor((width, height, width, height), device=device)  # to pixels (num_gt, 6)  
  
    preds = non_max_suppression(preds,  
                                conf_thres,  
                                iou_thres,  
                                labels=[],  
                                multi_label=True,  
                                agnostic=single_cls,  
                                max_det=max_det)

需要注意,这里默认的非极大值抑制的置信度阈值为0.001:
单阶段目标检测:YOLOv5中的指标计算
这意味着会保留一些预测置信度很低的预测框。同一张图片置信度阈值设置为0.3与0.001的对比:
单阶段目标检测:YOLOv5中的指标计算
单阶段目标检测:YOLOv5中的指标计算可以看出来在低置信度阈值下网络是有很多离谱预测的。这里为什么不把置信度阈值设高一点呢?作者是故意的还是不小心的?后面会有解释。
这里我们以0.001为置信度阈值,看看非极大值抑制之后的预测preds:
单阶段目标检测:YOLOv5中的指标计算
看看非极大值抑制之前preds:

单阶段目标检测:YOLOv5中的指标计算
可以看到,对于第一张图片而言,预测框的数量从3*(56*84+28*42+14*21)=18522减少到了15个。

3.对一个batch逐张图像计算指标
stats = []
# [0.5, 0.55, ..., 0.95]
iouv = torch.linspace(0.5, 0.95, 10, device=device) # iou阈值 0.5--0.95
niou = iouv.numel()  # 10

for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
    ...
    for si, pred in enumerate(preds):  # 此时pred对应的是该batch中每张图像的预测。
        labels = targets[targets[:, 0] == si, 1:]  # 取出这张图像的targets
        nl, npr = labels.shape[0], pred.shape[0]  # number of labels, predictions  
        path, shape = Path(paths[si]), shapes[si][0]  # 这张图片的绝对路径,与图像实际size  
        correct = torch.zeros(npr, niou, dtype=torch.bool, device=device)
        seen += 1  
      
        # Predictions  
        predn = pred.clone() 
        # 把预测框从从统一的size映射回图像原初始尺寸  
        scale_boxes(im[si].shape[1:], predn[:, :4], shape, shapes[si][1])
      
        # Evaluate    
        if nl:  
            tbox = xywh2xyxy(labels[:, 1:5])  # target boxes (num_gt, 4)  
            scale_boxes(im[si].shape[1:], tbox, shape, shapes[si][1])  # gt映射回原始图像尺寸  
            labelsn = torch.cat((labels[:, 0:1], tbox), 1)  # native-space labels  
            # 每个预测只对应一个gt,每个gt也只对应一个预测  
            correct = process_batch(predn, labelsn, iouv)  # (num_pred, 10)  
        stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0]))  # (correct, conf, pcls, tcls)

stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)]  # to numpy  
if len(stats) and stats[0].any():  
    tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)   
    ap50, ap = ap[:, 0], ap.mean(1)  # AP@0.5, AP@0.5:0.95  
    mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()  

这里主要介绍一下两个函数:process_batchap_per_class

process_batch
def process_batch(detections, labels, iouv):  
    """  
    Return correct prediction matrix  
    返回每个预测框在10个IoU阈值上是TP还是FP  
    Arguments:        
        detections (array[N, 6]), x1, y1, x2, y2, conf, class        
        labels (array[M, 5]), class, x1, y1, x2, y2    
    Returns:        
        correct (array[N, 10]), for 10 IoU levels    
    """     
    correct = np.zeros((detections.shape[0], iouv.shape[0])).astype(bool)  
    # 计算每个预测与每个gt的iou
    iou = box_iou(labels[:, 1:], detections[:, :4])  # (M, N)  
    # 对比每个预测与每个gt的类别,相同则为True,否则为False
    correct_class = labels[:, 0:1] == detections[:, 5]  # (M, N)  
    for i in range(len(iouv)):  
        # iou超过阈值而且类别正确,则为True,返回索引
        x = torch.where((iou >= iouv[i]) & correct_class)
        if x[0].shape[0]:  # 至少有一个TP  
            matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()  # [label, detect, iou]  
            if x[0].shape[0] > 1:  # TP数量大于1  
                # 按照iou从高到低排序
                matches = matches[matches[:, 2].argsort()[::-1]]  
                # 这两步是确保每个gt只对应一个预测,每个预测也只对应一个gt
                # 如果出现一对多的情况,则取iou最高的那个
                matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
                matches = matches[np.unique(matches[:, 0], return_index=True)[1]] 
            correct[matches[:, 1].astype(int), i] = True  # (N, 10)  
    return torch.tensor(correct, dtype=torch.bool, device=iouv.device)

假设对于某张图片,网络产生了20个预测框,我们认为设定了一系列的iou阈值记为iouv(这里就假设为[0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95]),则输入预测结果与真实标签gt,这个函数会返回一个形状为(20, 10)的张量(10是设定阈值的数量),记录在每个iou阈值下哪些预测框为真阳性(TP)。TP在这里的定义是:如果一个预测框与一个真实边界框的类别相同且iou大于阈值,则该预测框在此iou阈值下为真阳性。需要注意的是,这里预测框与gt是一一对应的,比如说(1)预测框A和B对于同一个gt来说都是TP,但是A与该gt的iou为0.9,而B与该gt的iou为0.8,那么最后只有A算TP,而B算FP。(2)预测框A对于gt1和gt2来说都是TP,但是A与gt1的iou为0.9,A与gt2的iou为0.8,那么A会被分配给gt1。

ap_per_class

先看一下ap_per_class的输入stats:
单阶段目标检测:YOLOv5中的指标计算
可以看到,stats是列表,其中包含4个numpy数组,这里的6947代表的是我的测试集(共400张)上网络产生的所有预测框,564代表测试集标签中的所有目标框。

  • 第一个数组代表测试集中的所有预测在10个iou阈值上是TP还是FP。
  • 第二个数组代表所有预测框的置信度
  • 第三个数组代表所有预测框的预测类别
  • 第四个数组代表标签的类别

代码解析:

def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='.', names=(), eps=1e-16, prefix=""):  
    """ Compute the average precision, given the recall and precision curves.  
    Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.    
    # Arguments 
        tp:  True positives (nparray, nx1 or nx10).        
        conf:  Objectness value from 0-1 (nparray).    
        pred_cls:  Predicted object classes (nparray).        
        target_cls:  True object classes (nparray).        
        plot:  Plot precision-recall curve at mAP@0.5        
        save_dir:  Plot save directory    
    # Returns        
        The average precision as computed in py-faster-rcnn.    
    """  
    # Sort by objectness  
    i = np.argsort(-conf)  
    tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]  # 从大到小排序  
  
    # Find unique classes  
    unique_classes, nt = np.unique(target_cls, return_counts=True)  
    nc = unique_classes.shape[0]  # number of classes, number of detections  
    # Create Precision-Recall curve and compute AP for each class    
    px, py = np.linspace(0, 1, 1000), []  # for plotting  
    ap, p, r = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000)) 
    # 计算每个类别的指标 
    for ci, c in enumerate(unique_classes):  
        i = pred_cls == c  
        n_l = nt[ci]  # number of labels  
        n_p = i.sum()  # number of predictions  
        if n_p == 0 or n_l == 0:  
            continue  
  
        # Accumulate FPs and TPs  
        # 统计随着预测目标的增多,TP与FP数量的变化
        fpc = (1 - tp[i]).cumsum(0)
        tpc = tp[i].cumsum(0)  

        # 统计精确率与召回率曲线,这里的曲线是指精确率与召回率随着样本数增加的变化
        # p与r是iou阈值为0.5的精确率与召回率曲线(所有类别的,每类对应一条曲线)
        # recall与precision则是所有iou阈值下的精确率与召回率曲线
        # Recall  
        recall = tpc / (n_l + eps)  # 计算召回率
        r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0)
        
        # Precision        
        precision = tpc / (tpc + fpc)  # 计算精确率
        p[ci] = np.interp(-px, -conf[i], precision[:, 0], left=1)
  
        # AP from recall-precision curve
        # 计算每个iou阈值下的ap-->(num_cls, niou)        
        for j in range(tp.shape[1]):  
            ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j])  
            if plot and j == 0:  
                py.append(np.interp(px, mrec, mpre))  # precision at mAP@0.5  
  
    # Compute F1 (harmonic mean of precision and recall)    
    f1 = 2 * p * r / (p + r + eps)  
    names = [v for k, v in names.items() if k in unique_classes]  # list: only classes that have data  
    names = dict(enumerate(names))  # to dict  
    # 绘制曲线
    if plot:  
        plot_pr_curve(px, py, ap, Path(save_dir) / f'{prefix}PR_curve.png', names)  
        plot_mc_curve(px, f1, Path(save_dir) / f'{prefix}F1_curve.png', names, ylabel='F1')  
        plot_mc_curve(px, p, Path(save_dir) / f'{prefix}P_curve.png', names, ylabel='Precision')  
        plot_mc_curve(px, r, Path(save_dir) / f'{prefix}R_curve.png', names, ylabel='Recall')
    i = smooth(f1.mean(0), 0.1).argmax()  # max F1 index  
    p, r, f1 = p[:, i], r[:, i], f1[:, i]  
    tp = (r * nt).round()  # true positives  
    fp = (tp / (p + eps) - tp).round()  # false positives  
    return tp, fp, p, r, f1, ap, unique_classes.astype(int)

需要注意的是,这里返回的p与r是使得所有类别的平均f1-score最大的p与r。例如:
单阶段目标检测:YOLOv5中的指标计算
从代码中我们可以得知,每个类别的p与r原本都是长为1000的变化曲线,图中的all classes 0.91 at 0.670代表在670这个点位时,我们的所有类别的平均score达到最大,所以最后返回的p与r也是这个点对应的p与r(代码里其实取的点其实会有一些差异,比如我在这次验证时实际取的是689。略有不同是因为代码里取值时还进行了一个平滑操作)。至于为什么这么返回,因为我们非极大值抑制的时候置信度阈值设得很低,这样肯定会导致我们的预测样本数量很多,所以最后精确率会很低,召回率会很高。实际算出的p和r是取决于置信度阈值的,所以取f1-score最高的点的p、r返回,其实也就代表着我们为模型的验证取了一个合适的置信度阈值。这也是为什么前面的置信度阈值不用设得高一点的原因

至此,整个val.py就已经分析完了,现在我们来看下计算完指标后打印的日志:
单阶段目标检测:YOLOv5中的指标计算
这个日志其实我觉得写的有点容易让人产生误解,其实只有all对应的这一行才是mAP,代表所有的类别的AP值的平均。这个日志写的容易让不了解AP值的人误以为单独每个类别都有一个mAP指标。all这一行打印的是平均AP值,剩下的打印的是每个类别自己的AP值。

AP计算

AP简单来说就是PR曲线与坐标轴围成的面积:
单阶段目标检测:YOLOv5中的指标计算在绘制PR曲线的时候,首先要弄清楚一个点:随着预测数量的增加,recall是递增的,而precision是递减的。也就是说随着recall的递增,precision是递减的。对于YOLOv5,只需要根据precision和recall绘制PR曲线然后计算面积就行。文章来源地址https://www.toymoban.com/news/detail-447239.html

到了这里,关于单阶段目标检测:YOLOv5中的指标计算的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【计算机视觉 | 目标检测】目标检测中的评价指标 mAP 理解及计算(含示例)

    在目标检测中,有几个常用的评价指标用于衡量算法的性能。以下是其中几个重要的评价指标: Precision(精确率):Precision 衡量了在所有被检测为正样本的样本中,有多少是真正的正样本。 Precision 的计算公式为:Precision = TP / (TP + FP),其中 TP 是真正的正样本数量,FP 是将负

    2024年01月19日
    浏览(50)
  • 【计算机视觉】目标检测—yolov5自定义模型的训练以及加载

    目标检测是计算机视觉主要应用方向之一。目标检测通常包括两方面的工作,首先是招到目标,然后就是识别目标。目标检测可以分为单物体检测和多物体检测。常用的目标检测方法分为两大流派:一步走(one_stage)算法:直接对输入的图像应用算法并输出类别和相应的定位

    2024年02月01日
    浏览(54)
  • 《一种改进的YOLOv5用于无人机捕获场景中的目标检测》论文笔记

           无人机图像处理中的目标检测逐渐成为近年来的研究热点。一般目标检测算法在应用于无人机场景时往往会显著下降。这是因为无人机图像是从高空拍摄的,分辨率高,小物体比例很大。为了在满足轻量化特性的同时提高无人机目标检测的精度,我们对YOLOv5s模型进行

    2024年02月02日
    浏览(42)
  • 计算机视觉的应用7-利用YOLOv5模型启动电脑摄像头进行目标检测

    大家好,我是微学AI,今天给大家介绍一下计算机视觉的应用7-利用YOLOv5模型启动电脑摄像头进行目标检测,本文将详细介绍YOLOv5模型的原理,YOLOv5模型的结构,并展示如何利用电脑摄像头进行目标检测。文章将提供样例代码,以帮助读者更好地理解和实践YOLOv5模型。 目录 引

    2024年02月10日
    浏览(54)
  • 【计算机视觉面经四】基于深度学习的目标检测算法面试必备(RCNN~YOLOv5)

    目标检测算法主要包括:【两阶段】目标检测算法、【多阶段】目标检测算法、【单阶段】目标检测算法。 什么是两阶段目标检测算法,与单阶段目标检测有什么区别? 两阶段目标检测算法因需要进行两阶段的处理:1)候选区域的获取,2)候选区域分类和回归,也称为基于

    2024年03月27日
    浏览(58)
  • 【YOLOv7/YOLOv5系列改进NO.53】融入CFPNet网络中的ECVBlock模块,提升小目标检测能力

    作为当前先进的深度学习目标检测算法YOLOv7,已经集合了大量的trick,但是还是有提高和改进的空间,针对具体应用场景下的检测难点,可以不同的改进方法。此后的系列文章,将重点对YOLOv7的如何改进进行详细的介绍,目的是为了给那些搞科研的同学需要创新点或者搞工程

    2024年02月09日
    浏览(50)
  • YOLOv8/YOLOv7/YOLOv5/YOLOv4/Faster-rcnn系列算法改进【NO.69】针对遥感图像目标检测中的小目标进行改进CATnet(ContextAggregation模块)

    前言 作为当前先进的深度学习目标检测算法YOLOv8,已经集合了大量的trick,但是还是有提高和改进的空间,针对具体应用场景下的检测难点,可以不同的改进方法。此后的系列文章,将重点对YOLOv8的如何改进进行详细的介绍,目的是为了给那些搞科研的同学需要创新点或者搞

    2024年02月11日
    浏览(54)
  • YOLO等目标检测模型的非极大值抑制NMS和评价指标(Acc, Precision, Recall, AP, mAP, RoI)、YOLOv5中mAP@0.5与mAP@0.5:0.95的含义

    YOLOv5正负样本定义 yolov5输出有3个预测分支,每个分支的每个网格有3个anchor与之对应。 没有采用IOU最大的匹配方法,而是通过计算该bounding-box和当前层的anchor的宽高比,如果最大比例大于4(设定阈值),则比例过大,则说明匹配度不高,将该bbox过滤,在当前层认为是背景

    2024年02月03日
    浏览(48)
  • YOLOv5获得大中小目标的AP和AR指标(自制数据集)

    本文简要介绍YOLOv5如何调用pycocotools得到 大中小目标的AP和AR指标 ,评价自制数据集。 代码版本-----YOLOv5_6.0版本。 数据集----Seaships7000数据集,共包含6类7000张船舶图片,其中测试集1400张。 模型-----自制模型。 话不多说,运行示例: 主要参考了以下三个案例,并根据Seaships数

    2024年02月01日
    浏览(136)
  • YOLOv5目标检测实验

    最近在用YOLOv5跑一些目标检测的东西,这是自己日常学习的一些总结!后期会继续更新!有问题也欢迎批评指正!如果雷同请见谅! 创建数据集是在detect.py里面的create_dataloader,并在主函数里面调用 yolov5在计算资源的调用上采用了torch.nn.parallel.DistributedDataParallel(DDP,多张显卡

    2024年02月07日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包