nms和P,R,map原理及在Yolov5代码中的解析

将非极大值抑制(nms)和map放在一块进行讲解分析,因为其都是通过IOU和置信度(score)来计算,但两者方式不一样,容易产生干扰,NMS通过IOU来过滤掉候选框,而map通过IOU来筛选正负样本。

目录

nms

所有类别nms

不同类别nms

准确率,召回率

F1和map

F1:

Ap:

Yolov5代码中P, R和Map解析

nms

目标检测推理过程会产生许多目标检测框,这些检测框宽高都不一致,且每个检测框都赋有一个置信度阈值,需要对这些目标框进行过滤,筛选出最优的目标框。首先,通过事先设定好的置信度阈值可以过滤掉部分检测框(即置信度小于该阈值的检测框被过滤),置信度有两种形式,一种是前景的概率(即包含有物体的概率),另一种是前景概率与类别概率的乘积。对于剩余检测框通过NMS进行过滤,最终仅保留一个与目标最匹配的检测框。

NMS有两种思路:

所有类别nms

伪代码算法简易步骤:

all_box = all_box.sort() ## 将所有检测出的box从大到小进行排序
for i in len(all_box): ## 根据置信度从大到小遍历所有的box
for j in len(all_box): ## 将置信度小于某个box的其他所有box与此box对比,计算IOU
if j > i :
判断all_box[i]和all_box[j]的IOU面积是否大于阈值,如果大于阈值则删除此box,否则保留此box,直到所有box被保存,即为整张图片被检测到的所有目标框。

其原理图示如下,图片源于网络 Tom Hardy博客

nms和P,R,map原理及在Yolov5代码中的解析

对所有类别进行nms,代码以python示例:


def NMS(boxes,scores, thresholds):
    x1 = boxes[:,0]
    y1 = boxes[:,1]
    x2 = boxes[:,2]
    y2 = boxes[:,3]
    areas = (x2-x1)*(y2-y1)

    _,order = scores.sort(0,descending=True)
    keep = []
    while order.numel() > 0:
        i = order[0]
        keep.append(i)
        if order.numel() == 1:
            break
        xx1 = x1[order[1:]].clamp(min=x1[i])
        yy1 = y1[order[1:]].clamp(min=y1[i])
        xx2 = x2[order[1:]].clamp(max=x2[i])
        yy2 = y2[order[1:]].clamp(max=y2[i])

        w = (xx2-xx1).clamp(min=0)
        h = (yy2-yy1).clamp(min=0)
        inter = w*h

        ovr = inter/(areas[i] + areas[order[1:]] - inter)
        ids = (ovr

代码以nanodet的推理c++为例(仅部分):

void nanodet::nms(std::vector& input_boxes, float NMS_THRESH)
{
    std::sort(input_boxes.begin(), input_boxes.end(), [](BoxInfo a, BoxInfo b) { return a.score > b.score; }); // 对检测的box根据置信度排序
    std::vector vArea(input_boxes.size());
    for (int i = 0; i < int(input_boxes.size()); ++i)
    {
        vArea[i] = (input_boxes.at(i).x2 - input_boxes.at(i).x1 + 1)
            * (input_boxes.at(i).y2 - input_boxes.at(i).y1 + 1);
    }  // 获取所有检测出的box的面积
    for (int i = 0; i < int(input_boxes.size()); ++i)
    {
        for (int j = i + 1; j < int(input_boxes.size());)
        {
            float xx1 = (std::max)(input_boxes[i].x1, input_boxes[j].x1);
            float yy1 = (std::max)(input_boxes[i].y1, input_boxes[j].y1);
            float xx2 = (std::min)(input_boxes[i].x2, input_boxes[j].x2);
            float yy2 = (std::min)(input_boxes[i].y2, input_boxes[j].y2);
            float w = (std::max)(float(0), xx2 - xx1 + 1);
            float h = (std::max)(float(0), yy2 - yy1 + 1);
            float inter = w * h;
            float ovr = inter / (vArea[i] + vArea[j] - inter); // IOU
            if (ovr >= NMS_THRESH)  // 从vector begin开始,置信度最大的box与另一box对比,如果IOU大于阈值则删除此box,进而和下一个box对比,直到所有box都对比完
            {
                input_boxes.erase(input_boxes.begin() + j);
                vArea.erase(vArea.begin() + j);
            }
            else
            {
                j++;
            }
        }
    }

通过手动设置IOU阈值,容易产生两个主要问题,一是:当IOU阈值设置较大时,会有很多冗余的检测框不会被有效过滤;当IOU阈值设置较小,虽可有效过滤更多的检测框。但当有两个不同类别的物体相距很近时,另一个置信度较低的物体容易被过滤,从而无法被检测到;为了弥补这种缺陷往往会采用另一种方式。

不同类别nms

通过不同类别进行NMS,伪代码算法步骤为:

for label in all_labels:
a. 获取此类别(label)下所有box信息 ## 坐标位置,置信度,类别概率
b. 根据box置信度从高至低排序,保存且记录当前置信度最大box
c. 遍历b中置信度最大box以外的其他所有box,对比其他所有box与置信度最大box的IOU,删除IOU大于阈值的其他box
d. 对剩下box,重复循环b,c步骤
这种方法的缺陷为,当两个相同类别的物体相隔很近时,另一个被检测到置信度较低些的物体容易被过滤掉,结果仅保留此类别下的一种物体。图示如下,其中红框的犬只可以被有效检测到,但蓝框虚线犬只会被过滤,因为其IOU大于阈值。当然图示相隔还有一定距离,如果相隔更近,IOU就更大了,更加难以去除。

nms和P,R,map原理及在Yolov5代码中的解析
i = torchvision.ops.nms(boxes, scores, iou_thres)

对于多类别NMS的实现,通过对每个候选框坐标添加一个偏移量来实现,偏移量可以通过不同类别的索引来实现。源码如下:

max_coordinate = boxes.max()
offsets = idxs.to(boxes) * (max_coordinate + torch.tensor(1).to(boxes))
boxes_for_nms = boxes + offsets[:, None]
keep = nms(boxes_for_nms, scores, iou_threshold)
return keep

通过torchvision.ops.boxes.batched_nms(boxes, scores, classes, nms_thresh) 调用。

在yolov5中,实现代码:

        # Batched NMS
        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes 类别
        boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores

        ## 采用nms将框box数量过滤,IOU设置越小,框越少; i为经过nms后剩余框的索引
        i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS 将所有的框box,依据置信度scores得分进行过滤

通过设置agnostic来判定是否使用多类别NMS,当agnostic为True时,即对所有类别进行NMS,当其为False时,对每个类别分开单独进行NMS。max_wh为检测框的最大宽高(像素),yolov5中指定为4096。

这两种方式的缺陷:①IOU阈值设置过大,,单个目标物会出现多个检测框,IOU阈值设置过小,则相邻的同类物体会被过滤掉;②低于IOU阈值的,置信度设置为0,不够合理;③NMS只能在CPU上运行,影响性能。

现方法中除了nms外还有soft nms可以从原理上有效解决多个同类别物体相隔很近时的检测问题。此外对于IOU的演化,还有GIOU,CIOU,DIOU以及最新的SIOU,其将两个不同box之间的距离,重叠率,尺度,横纵比等多维度进行考量。这些方法的改进思路和方法很简单,这里就不再赘述。

准确率,召回率

对准确性和召回率,通过TP,FP,FN三者的关系对准确性和召回率进行计算,对TP,FP,FN的解析如下:

TP: 与真实框的IOU大于设定阈值的检测框,被视为模型正确识别的正样本;

FP:与真实框的IOU小于设定阈值的检测框,被视为模型错误识别的正样本;

FN:没有被模型识别为正样本的目标(即模型没有检测到)

准确率和召回率计算公式为:

准确率(Precision):

Precision=TP/(TP+FP)
表示模型预测的所有检测框中,预测正确的检测框(正样本数)所占的比例

召回率(Recall):

Recall=TP/(TP+FN)
表示模型预测的所有检测框中,预测正确的检测框与实际真实框的比例

计算过程为:首先模型对所有验证集图片进行检测,通过NMS后保留下所有验证集图片的目标检测框。再基于设定的置信度阈值,对大于此阈值的检测框进行统计分析。

以如下图示和表为例:红色框为GT框,蓝色框,黑色框和黄色虚线框为检测框,

① 假定置信度阈值为0.3,三个检测框都大于设定的置信度阈值。另假定IOU阈值为0.6,其中蓝色框,黑色框与真实框IOU大于设定阈值,黄色虚线框与真实框IOU小于设定阈值,则TP=2(即被模型识别正确的检测框——蓝色框和黑色框),FP=1(被模型识别错误的检测框——黄色虚线框),FN=2(漏检的,左图红框中的犬只与右图下面犬只未被检测到),故准确率:Precision=2/(2+1)=0.67,召回率:Recall=2/(2+2)=0.5.

②假定置信度阈值为0.7,则置信度小于0.7的检测框不纳入统计范畴,IOU依旧阈值为0.6,则蓝色框和黄色虚线框作为检测框,TP=1(蓝色框),FP=1(黄色虚线框),FN=3(四个GT框,仅蓝色框对应的GT框被检测到,其余三个为被检测到),则准确率:Precision=1/(1+1)=0.5,Recall=1/(1+3)=0.25.

③假定置信度阈值为0.7,IOU阈值为0.4,基于置信度阈值,ID为1,2的两个框被检测到并作为统计,则:TP=2(蓝色框和黄色虚线框),FP=0,FN=2,Precision=2/(2+0)=1,Recall=2/(2+2)=0.5.

nms和P,R,map原理及在Yolov5代码中的解析

基于置信度从高至低排序:

`
目标ID| 检测框 | 置信度 | IOU
2 | 黄色虚线框| 0.75 | 0.42
1 | Box1 | -0.95| True | 0.16 | 1
3 | Box3 | -0.82| False | 0.33 | 0.66
5 | Box5 | -0.05| True | 0.50 | 0.75

Original: https://blog.csdn.net/qq_40629612/article/details/126333695
Author: ywyErwin
Title: nms和P,R,map原理及在Yolov5代码中的解析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/785602/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球