从零讲解目标检测的评价指标map及实现

2023-11-04

前言

map是**Mean Average Precision**的缩写,翻译过来是平均精度的平均。有点绕啊,在理解map之前,先问个为什么要引入map,在分类任务中,常使用精确率和召回率作为评价指标,也称查准率和查全率,这是一个简单直接的统计量。目标检测任务稍有不同的是,即使目标检测器检测到猫和狗,没有定位,这也是没有用的。所以我们评价这个目标检测器的性能,不仅要评价它检测的对不对,还要评价它定位的准确性,这里就引入了map这个指标。

map最早出现在信息检索系统,引入AP是为了衡量相关条目出现在不同位置时搜索结果的差异性。对于目标检测来说,其GT数量不定(一张图片可能有多类多个label),网络输出也不确定(经过NMS输出数量可能大不相同),在这种情况下如何去评价检测模型的性能?肯定不能用分类问题的Accuracy,最直观的想法就是计算PR曲线

map的定义

经过一系列的训练过后,怎么才能判断我们目标检测训练模型的效果呢,首先我们要有包含标签的验证集,没有标签就没办法评价;其次知道目标检测任务是用来干嘛的,如在植物大战僵尸中,坚果+地雷是绝佳配合。
在这里插入图片描述
但是坚果要放在地雷前面才有效果,我们需要检测地雷和坚果以及两者的位置。如下图。
在这里插入图片描述
评价目标模型的效果,是要评价出检测的准确性。预测框有三个信息:位置、类别、对应类别的置信度,首先ap计算是分类别计算的,先抽出某一类别的预测框和gt框,分两步:第一步预测框与gt框的匹配,第二步进行相关precision 、recall以及ap的计算

1.预测框与gt框的匹配

假设预测出的框如下(红色),先做单一类别的ap计算,抽出坚果的预测框(红色1,2,3,4, 5,6,7)和坚果的真实框。
在这里插入图片描述
框完全重合,但是考虑实际中像素级偏差可以忽略,人工标注gt的偏差,很难完全重合,这里使用的是IOU,两个框的交并比,关于IOU的理解可以参见这篇,当然也可以将IOU换成IOU系列的G-IOU,D-IOU等,这样的话就不是单纯的map了,不过可以试试用于自己项目中的评价图片。
先计算各预测框与gt框的IOU值,设IOU阈值为0.5,则过滤掉1、2、4和7框,1,5,7框因为IOU小于阈值,4框虽然和地刺的gt框重合,但是他检测的类别是坚果的,也是不行的,3,5和6均满足IOU值**,一个gt只能匹配到一个预测框(主要用于后面计算recall),**在一般的目标检测过程中,会先通过nms将预测框过滤掉后再计算map, 这里假设没有过滤完全,取score高的作为匹配框。则3和6框为匹配到的预测框。
匹配完坚果,以同样的方式匹配到地刺。

2. 指标计算

这里先了解下混淆矩阵,这块有点绕,对目标检测来说:
在这里插入图片描述
P和N是对应预测框被预测成正样本还是负样本,T和F是预测框预测的对不对,是否被正确的分类,
目标检测中,训练时通过人为定义正负样本,去让模型学习哪些是目标,哪些是背景,如与gt的IOU值高于阈值的图像区域为正样本,小于阈值的图像区域为负样本,有正负样本之分(TN)。在验证测试时,输入是整张图像,只有gt对应的真实正样本,没有真实负样本。先通过置信度阈值来区分预测框为预测正样本还是预测负样本,再对预测为正样本的框,判断是否预测正确。

所有大于置信度阈值的框均为预测正样本(P),与gt的iou高于iou阈值的为TP,反之为FP。没有预测出来的框都是N,目标检测一般不区分TN和FN。因为目标检测预测对应的框为背景是正确的似乎也没多大意义。因此,目标检测中,一般不考虑TN。

对于坚果类别:
True Positive (TP): 真的正样本,实际为正样本,检测为正样本,目标检测上是IOU>=阈值的检测框,这里的3和6框
False Positive (FP): 假的正样本,实际为负样本,检测为正样本,目标检测上:IOU<阈值的检测框和,或者是检测到同一个GT的多余检测框的数量,这里的1、2、4、5和7框
True Negative (TN): 真的负样本,实际为负样本,检测为负样本,忽略。
False Negative (FN): 假的负样本,实际为正样本,检测为负样本,最下面的那个坚果。
目标检测中的准确率和分类算法中的准确率相同:图片
召回率:
图片

3. ap 的计算

如何计算每个类别的ap呢,首先ap的定义是PR曲线围成的面积,所以我们需要先求PR曲线,对于PR曲线的采样点,voc10前后有两种不同的方式,voc08只选取Recall >= 0, 0.1, 0.2, …, 1共11个点时的Precision最大值,然后AP就是这11个Precision的平均值,称为Interplolated AP。如我们上面预测的坚果:
这个列表确实不好列,建议先把下面表列出来后,对着列此表。
在voc10年之后,按照score值从上往下排列,对应的准确率和召回率如下:这里需要注意rank5这个框虽然是score和IOU均满足,但是它因为和6框同属于预测同一个gt框,所以按照score值的大小,将其定义成负样本。
在这里插入图片描述
根据上述的PR列表,画图如下:
在这里插入图片描述
ap为PR曲线下围城的面积
在这里插入图片描述
1*0.333+(0.25+0.4)*0.333/2 =0.441

voc08是如下这样的图:
在这里插入图片描述
ap为:(41+30.4)/11=0.472,两者之间相差3个点,voc08计算ap时简单粗暴,但是有精度上的损失,特别是对样本稀疏的情况下。所以这种方法在2009年的Pascalvoc之后便不再采用了。在Pascal voc 2010之后,便开始采用第一种精度更高的方式。

4. map 计算

在计算完每个类别的ap后,对于整个数据集的map,采用对各类别ap的平均值,这里面有人考虑对整个数据的计算(GT, TP,FP)后,获得整个数据集的ap作为map,这里实际上是不合理的。

举个例子,数据集有A、B两个类,样本量分别占90%、10%;有两个待评估模型①和②,
在这里插入图片描述
如果选“加权平均”则会认为①更好,但是①在少数类(B类)的表现太差,所以还是②更好,所以类别的直接平均作为map是合理的。

同时在coco数据集上,使用的也是Interplolated AP的计算方式[1]。与Voc 2008不同的是,为了提高精度,在PR曲线上采样了**101个点**进行计算,

recall = 0.00, 0.01, 0.02 , ⋯, 1.00 时对应的最大precision值,然后计算这101个值得平均值,就得到了该验证集上某类的ap值。
在这里插入图片描述
在coco官网上,对map有以下几个评价指标:
在这里插入图片描述
第一个map为Iou的阈值从固定的0.5调整为在 0.5 - 0.95 的区间上每隔0.5计算一次AP的值,取所有结果的平均值作为最终的结果。

第二个map为不同尺寸的物体的mAP。包括小物体、中等物体、大物体,后面描述了物体对应的像素值的大小。

第三为平均召回率,和AP相似,但这个不太常用。

3. map的实现

map的实现网上有很多实现方式,这里使用简单的方法:

# 按照置信度降序排序
sorted_ind = np.argsort(-confidence)
BB = BB[sorted_ind, :]   # 预测框坐标
image_ids = [image_ids[x] for x in sorted_ind] # 各个预测框的对应图片id

# 便利预测框,并统计TPs和FPs
nd = len(image_ids)
tp = np.zeros(nd)
fp = np.zeros(nd)
for d in range(nd):
    R = class_recs[image_ids[d]]
    bb = BB[d, :].astype(float)
    ovmax = -np.inf
    BBGT = R['bbox'].astype(float)  # ground truth

    if BBGT.size > 0:
        # 计算IoU
        # intersection
        ixmin = np.maximum(BBGT[:, 0], bb[0])
        iymin = np.maximum(BBGT[:, 1], bb[1])
        ixmax = np.minimum(BBGT[:, 2], bb[2])
        iymax = np.minimum(BBGT[:, 3], bb[3])
        iw = np.maximum(ixmax - ixmin + 1., 0.)
        ih = np.maximum(iymax - iymin + 1., 0.)
        inters = iw * ih

        # union
        uni = ((bb[2] - bb[0] + 1.) * (bb[3] - bb[1] + 1.) +
               (BBGT[:, 2] - BBGT[:, 0] + 1.) *
               (BBGT[:, 3] - BBGT[:, 1] + 1.) - inters)

        overlaps = inters / uni
        ovmax = np.max(overlaps)
        jmax = np.argmax(overlaps)
    # 取最大的IoU
    if ovmax > ovthresh:  # 是否大于阈值
        if not R['difficult'][jmax]:  # 非difficult物体
            if not R['det'][jmax]:    # 未被检测
                tp[d] = 1.
                R['det'][jmax] = 1    # 标记已被检测
            else:
                fp[d] = 1.
    else:
        fp[d] = 1.

# 计算precision recall
fp = np.cumsum(fp)
tp = np.cumsum(tp)
rec = tp / float(npos)
# avoid divide by zero in case the first detection matches a difficult
# ground truth
prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)

最终得到一系列的precision和recall值,并且这些值是按照置信度降低排列统计的,可以认为是取不同的置信度阈值(或者rank值)得到的。然后据此可以计算AP:

def voc_ap(rec, prec, use_07_metric=False):
    """Compute VOC AP given precision and recall. If use_07_metric is true, uses
    the VOC 07 11-point method (default:False).
    """
    if use_07_metric:  # 使用07年方法
        # 11 个点
        ap = 0.
        for t in np.arange(0., 1.1, 0.1):
            if np.sum(rec >= t) == 0:
                p = 0
            else:
                p = np.max(prec[rec >= t])  # 插值
            ap = ap + p / 11.
    else:  # 新方式,计算所有点
        # correct AP calculation
        # first append sentinel values at the end
        mrec = np.concatenate(([0.], rec, [1.]))
        mpre = np.concatenate(([0.], prec, [0.]))

        # compute the precision 曲线值(也用了插值)
        for i in range(mpre.size - 1, 0, -1):
            mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

        # to calculate area under PR curve, look for points
        # where X axis (recall) changes value
        i = np.where(mrec[1:] != mrec[:-1])[0]

        # and sum (\Delta recall) * prec
        ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
    return ap

结语

根据 mAP 的高低,我们只能较为概括地知道网络整体性能的好坏,但比较难分析问题具体在哪。举个例子:如果网络输出的框很贴合,选择合适的 Confidence 阈值时,检出和召回也较均衡,但是目标的类别判断错误较多。由于首先根据类别结果分类处理,只要类别错了,定位、检出和召回都很好,mAP 指标也不会高。但从结果观察,并不能很明确知道,问题出在类别判断上还是定位不准确上面。

mAP 指标关注的点,与实际应用时关注的点,并不完全吻合,mAP 会统计所有 Confidence 值下的 PR值,而实际使用时,会设定一个 Confidence 阈值,低于该阈值的目标会被丢弃,这部分目标在统计 mAP 时也会有一定的贡献。部分针对比赛刷榜的涨点技巧,会关注这部分检测结果对 mAP 的影响。

所以针对具体的目标检测项目需求,map仅仅只能大概的评估模型性能,还需要其他的评价指标。

参考:

参考:

[1] https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/cocoeval.py
[2] https://www.zhihu.com/question/53405779
[3] https://www.cnblogs.com/boligongzhu/p/15065738.html
[4] https://zhuanlan.zhihu.com/p/365840197
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从零讲解目标检测的评价指标map及实现 的相关文章

随机推荐

  • js-JavaScript 对象

    1 对象定义 JavaScript 对象是拥有属性和方法的数据 2 格式 键值对 var person firstName John lastName Smith age 50 eyeColor blue move function ale
  • 后端Long型数据传给前端精度丢失问题 分布式id 解决方案

    Long型数据id传给前端精度丢失问题 数据库数据类型bigint 今天将文章类ArticleVo的数据传给前端时 发现前端接收的数据id不一样 如前端获得的id 1405916999732707300 但数据库里是id 140591699
  • springboot国际化message配置

    常常在需求中遇到国际化的要求 而国际化最常见的就是中英文切换 除了前端 后端也需要进行国际化处理 这里来记录一下我实现国际化的步骤代码 1 在resource下的i18n下建立messages properties messages en
  • CycleGAN和Conditional GAN(cGAN)

    当谈到CycleGAN和Conditional GAN cGAN 时 我们涉及到生成对抗网络 GAN 的不同变体 让我逐步介绍它们的原理和应用 CycleGAN CycleGAN是一种无监督的图像转换模型 它可以在两个不同的图像域之间进行转
  • Ubuntu Source Insight 4.0安装后首次打开报错

    系统为中文会出现 Unable to open or create 中文路径 sidb 参照网上修改regedit方法 发现没用 直接修改 wine drive c users server 我的文档 为 wine drive c user
  • C++ 中 map 容器的内存释放机制及内存碎片管理

    C 中 map 容器的内存释放机制及内存碎片管理 C 中的容器很好用 比如 vector map 等 可以动态扩容 自己管理内存 不用用户关心 但是在某些极端情况下 如果内存比较紧张的情况下 可能用户对于这些容器自己的管理规则 主要是释放规
  • Linux read命令

    读取n个字符存入变量 不用按回车 输入到第n个自动结束 student myhost read n 3 a 123 student myhost echo a 123 无回显方式读取密码 student myhost read s pass
  • windows spacemacs实现org-mode转latex,然后生成pdf

    spacemacs默认英文字体做如下修改 安装了完整版ctex套装 spacemacs增加layer gt latex 增加windows的path路径 解决org mode里中英文等宽问题 latex编译命令 pdf预览等 实现了org转
  • 什么是文件目录,文件目录项的主要内容是什么?

    文件目录是记录系统中所有文件的名字及其存放地址的目录表 表中还包括关于文件的说明信息和控制信息 主要内容如下 1 文件名 文件名分为文件的符号名和内部标识符 id号 2 文件的逻辑结构 说明该文件是否是定长 记录长度及记录个数等 3 文件的
  • USB如何布局走线

    1 先上图 USB分为2 0和3 0 2 USB布局走线需要注意的地方 静电防护 阻抗匹配 同组等长
  • cv::Mat遍历赋值的几种方式

    cv Mat赋值的几种方式 1 前言 2 Mat简介 3 遍历Mat赋值方式 方式一 方式二 方式三 4 测试 5 参考文献 1 前言 背景 获取传感器数据后需要保存成图片 有时需要对里面的元素进行操作 因为是自己开发 不能直接得到图片 所
  • CopyOnWriteArrayList部分源码分析

    CopyOnWriteArrayList部分源码分析 我们都知道ArrayList是基于数组实现的可动态扩容的集合 但是他实际上也是线程不安全的 而在JUC java util concurrent 下有个线程安全的数组集合 就是CopyO
  • django开发电子商城(四)django分页进阶和列表长度控制

    1 在list html中增加bootstrap分页代码 2 增加样式 使分页列表居中 3 修改views py文件 将分页数据传到前端 4 修改list html 根据传到前端的分页数据进行渲染分页条
  • 1035 插入与归并

    1035 插入与归并 根据维基百科的定义 插入排序是迭代算法 逐一获得输入数据 逐步产生有序的输出序列 每步迭代中 算法从输入序列中取出一元素 将之插入有序序列中正确的位置 如此迭代直到全部元素有序 归并排序进行如下迭代操作 首先将原始序列
  • python循环语句for 循环十次_python循环10次写法以及实例代码

    python循环10次怎么写 Python for循环可以遍历任何序列的项目 如一个列表或者一个字符串 语法 for循环的语法格式如下 for iterating var in sequence statements s 例子 for i
  • PHP 实现网页爬虫

    方法一 通过fopen和stream get contents获取html内容 从给定的url获取html内容 爬虫程序 原型 从给定的url获取html内容 通过fopen和stream get contents获取html内容 para
  • vue+typescript与UE4像素流通信笔记

    一 文件准备 1 从搭建好的UE4项目下找到scripts文件夹 拷贝该文件夹 重点文件为app js与webRtcPlayer js 到vue项目下的静态资源文件夹 默认public文件夹 下 如下图 在vue项目的index html下
  • Cocos2d-x的SprideMonkey的JavaScript与C++的交互(二) - 单个全局变量操作

    哇塞最近发现转帖的真多呀 这星期五写的东东 现在满大街都是了 哥哥也不问功名利禄 感觉挺好的 JS火起来我也很开心 呵呵 当时快下班了 这篇写的有些仓促 现在加精一些 首先来理解一下sc gt getGlobalObject 这个同学们就问
  • 第六章 一山不容二虎

    第六章 一山不容二虎 收购规则 6 在同一行业里 同一重量级的收购不会取得成功 一座山上不能同时容纳两只老虎 为什么呢 因为老虎是百兽之王 两只老虎都想称王 最后无法协商 只好用牙齿和爪子较量一番 战败者一瘸一拐地离 去 战胜者舔着伤口在山
  • 从零讲解目标检测的评价指标map及实现

    前言 map是 Mean Average Precision 的缩写 翻译过来是平均精度的平均 有点绕啊 在理解map之前 先问个为什么要引入map 在分类任务中 常使用精确率和召回率作为评价指标 也称查准率和查全率 这是一个简单直接的统计