zoukankan      html  css  js  c++  java
  • How to implement a YOLO (v3) object detector from scratch in PyTorch: Part 4翻译与总结

    对Ayoosh Kathuria的YOLOv3实现进行翻译和总结,原文链接如下:

    https://blog.paperspace.com/how-to-implement-a-yolo-v3-object-detector-from-scratch-in-pytorch-part-4/

    *首先翻译遵循不删不改的原则有一说一,对容易起到歧义的中文采取保留英文的方式。其中对原文没有删减但是略有扩充,其中某些阐释是我一句话的总结,如有错误请大家在留言区指出扶正。

    这是从头开始实现YOLO v3检测器的教程的第4部分。 在上一部分中,我们实现了网络的前向传递。 在这一部分中,我们先通过目标置信度然后进行非最大抑制来对检测设立阈值。

    本教程的代码在Python 3.5和PyTorch 0.4上运行。在这个Github repo中可以完整地找到它。

    Part 1 : Understanding How YOLO works
    Part 2 : Creating the layers of the network architecture
    Part 3: Implementing the forward pass of the network
    Part 4(This one) : Objectness score thresholding and Non-maximum suppression
    Part 5 : Designing the input and the output pipelines

    1.先决条件

    • 本教程的第1到3部分。
    • PyTorch的基本知识,包括如何使用nn.Module,nn.Sequential和torch.nn.parameter类创建自定义架构。
    • 关于numpy的基础知识

    如果你有任何先决知识的储备不足,你可以在下方找到一些相关知识的链接。

    在前面的部分中,我们建立了一个模型,该模型在给定输入图像的情况下输出几个目标检测。 确切地说,我们的输出是形状为B x 22743 x 85的张量。B是批处理中的图像数量,22743是每个图像预测的边界框的数量,而85是边界框属性的数量。

    但是,如第1部分中所述,我们必须使输出经过目标评分阈值化和非最大抑制,才能获得本文其余部分所说的真实检测结果。 为此,我们将在文件util.py中创建一个称为write_results的函数。

    def write_results(prediction, confidence, num_classes, nms_conf = 0.4):

    该函数将预测值,置信度(目标分数阈值),num_classes(在我们的情况下为80)和nms_conf(NMS IoU阈值)作为输入。

    2.Object Confidence Thresholding

    我们的预测张量包含有关B x 22743边界框的信息。 对于目标分数低于阈值的每个边界框,我们将其每个属性(代表边界框的整个行)的值设置为零。

        conf_mask = (prediction[:,:,4] > confidence).float().unsqueeze(2)
        prediction = prediction*conf_mask

    3.Performing Non-maximum Suppression

    现在,我们通过中心坐标和边界框的高度与宽度来描述边界框的属性。 但是,使用每个框的两个对角线的坐标来计算两个框的IoU更容易。 因此,我们将盒子的(中心x,中心y,高度,宽度)属性转换为(左上角x,左上角y,右下角x,右下角y)。

    每个图像中的真实检测次数可能会有所不同。 例如,批大小为3的图像1、2和3分别具有5、2、4个真实检测。 因此,必须一次对每一张图像进行置信度阈值和NMS。 这意味着,我们无法向量化所涉及的操作,而必须在预测的第一维上循环(包含批处理中的图像索引)。

        batch_size = prediction.size(0)
    
        write = False
    
        for ind in range(batch_size):
            image_pred = prediction[ind]          #image Tensor
               #confidence threshholding 
               #NMS

    如前所述,write标志用于指示我们尚未初始化输出,我们将使用张量来收集整个批次中的真实检测结果。

    一旦进入循环,让我们稍微整理一下。 注意,每个边界框行都有85个属性,其中80个是类分数。 在这一点上,我们只关心具有最高分的类分数。 因此,我们从每行中删除80个类分数,并且添加具有最大值的类索引以及该类的类分数。

            max_conf, max_conf_score = torch.max(image_pred[:,5:5 + num_classes], 1)
            max_conf = max_conf.float().unsqueeze(1)
            max_conf_score = max_conf_score.float().unsqueeze(1)
            seq = (image_pred[:,:5], max_conf, max_conf_score)
            image_pred = torch.cat(seq, 1)

    还记得我们已经将对象置信度小于阈值的边界框行设置为零吗? 让我们摆脱它们。

            non_zero_ind =  (torch.nonzero(image_pred[:,4]))
            try:
                image_pred_ = image_pred[non_zero_ind.squeeze(),:].view(-1,7)
            except:
                continue
            
            #For PyTorch 0.4 compatibility
            #Since the above code with not raise exception for no detection 
            #as scalars are supported in PyTorch 0.4
            if image_pred_.shape[0] == 0:
                continue         

    try-except块用于处理无法检测到的情况。 在这种情况下,我们使用continue跳过该图像的其余循环体。

    现在,让我们获取图像中检测到的类。

            #Get the various classes detected in the image
            img_classes = unique(image_pred_[:,-1]) # -1 index holds the class index

    由于可以对同一类进行多次真实检测,因此我们使用一个名为unique的函数来获取任何给定图像中存在的类。

    def unique(tensor):
        tensor_np = tensor.cpu().numpy()
        unique_np = np.unique(tensor_np)
        unique_tensor = torch.from_numpy(unique_np)
        
        tensor_res = tensor.new(unique_tensor.shape)
        tensor_res.copy_(unique_tensor)
        return tensor_res

    之后,我们执行NMS类监测

            for cls in img_classes:
                #perform NMS

    一旦进入循环,我们要做的第一件事就是提取特定类的检测值(用变量cls表示)。

                #get the detections with one particular class
                cls_mask = image_pred_*(image_pred_[:,-1] == cls).float().unsqueeze(1)
                class_mask_ind = torch.nonzero(cls_mask[:,-2]).squeeze()
                image_pred_class = image_pred_[class_mask_ind].view(-1,7)
                
                #sort the detections such that the entry with the maximum objectness
                #confidence is at the top
                conf_sort_index = torch.sort(image_pred_class[:,4], descending = True )[1]
                image_pred_class = image_pred_class[conf_sort_index]
                idx = image_pred_class.size(0)   #Number of detections

    现在,执行NMS

                for i in range(idx):
                    #Get the IOUs of all boxes that come after the one we are looking at 
                    #in the loop
                    try:
                        ious = bbox_iou(image_pred_class[i].unsqueeze(0), image_pred_class[i+1:])
                    except ValueError:
                        break
    
                    except IndexError:
                        break
    
                    #Zero out all the detections that have IoU > treshhold
                    iou_mask = (ious < nms_conf).float().unsqueeze(1)
                    image_pred_class[i+1:] *= iou_mask       
    
                    #Remove the non-zero entries
                    non_zero_ind = torch.nonzero(image_pred_class[:,4]).squeeze()
                    image_pred_class = image_pred_class[non_zero_ind].view(-1,7)

    在这里,我们使用一个函数bbox_iou。 第一个输入是边界框的行,该行由循环中的变量i索引。

    bbox_iou的第二个输入是边界框的多行张量。 函数bbox_iou的输出是一个张量,该张量包含第一个输入边界框和第二个输入中包含每个边界框的IOU。

     如果我们有两个相同类别的边界框,它们的IoU都大于阈值,那么将消除具有较低类别置信度的边界框。 我们已经筛选出边界框,顶部具有较高的置信度。

    在循环的主体中,以下几行给出了盒子的IoU,以i索引,所有边界框的索引都高于i。

    ious = bbox_iou(image_pred_class[i].unsqueeze(0), image_pred_class[i+1:])

    每次迭代,如果索引大于i的任何边界框的IoU(以i索引的框)都大于阈值nms_thresh,则将消除该特定框。

    #Zero out all the detections that have IoU > treshhold
    iou_mask = (ious < nms_conf).float().unsqueeze(1)
    image_pred_class[i+1:] *= iou_mask       
    
    #Remove the non-zero entries
    non_zero_ind = torch.nonzero(image_pred_class[:,4]).squeeze()
    image_pred_class = image_pred_class[non_zero_ind]     

    还要注意,我们将代码行用于在try-catch块中计算ious。 这是因为循环旨在运行idx迭代(image_pred_class中的行数)。 但是,随着循环的进行,可能会从image_pred_class中删除许多边界框。 这意味着,即使从image_pred_class中删除了一个值,我们也无法进行idx迭代。 因此,我们可能尝试索引超出范围的值(IndexError),或者切片image_pred_class [i + 1:]可能会返回一个空张量,并分配该张量来触发ValueError。 到那时,我们可以确定NMS无法进一步删除任何边界框,所以我们跳出了循环。

    4.Calculating the IoU

    这是bbox_iou函数

    def bbox_iou(box1, box2):
        """
        Returns the IoU of two bounding boxes 
        
        
        """
        #Get the coordinates of bounding boxes
        b1_x1, b1_y1, b1_x2, b1_y2 = box1[:,0], box1[:,1], box1[:,2], box1[:,3]
        b2_x1, b2_y1, b2_x2, b2_y2 = box2[:,0], box2[:,1], box2[:,2], box2[:,3]
        
        #get the corrdinates of the intersection rectangle
        inter_rect_x1 =  torch.max(b1_x1, b2_x1)
        inter_rect_y1 =  torch.max(b1_y1, b2_y1)
        inter_rect_x2 =  torch.min(b1_x2, b2_x2)
        inter_rect_y2 =  torch.min(b1_y2, b2_y2)
        
        #Intersection area
        inter_area = torch.clamp(inter_rect_x2 - inter_rect_x1 + 1, min=0) * torch.clamp(inter_rect_y2 - inter_rect_y1 + 1, min=0)
     
        #Union Area
        b1_area = (b1_x2 - b1_x1 + 1)*(b1_y2 - b1_y1 + 1)
        b2_area = (b2_x2 - b2_x1 + 1)*(b2_y2 - b2_y1 + 1)
        
        iou = inter_area / (b1_area + b2_area - inter_area)
        
        return iou

    5.Writing the predictions

    函数write_results输出形状为D x 8的张量。这里D是所有图像中的真实检测,每个检测由一行表示。 检测具有8个属性,即该检测所属的批次中的图像索引,4个角坐标,目标分数,具有最大置信度的类别的分数以及该类别的索引。

    和之前一样,除非我们有检测要分配给它,否则我们不会初始化输出张量。 初始化完成后,我们会将随后的检测连接起来。 我们使用write标志来指示张量是否已初始化。 在遍历类的循环的最后,我们将结果检测添加到张量来输出。

                batch_ind = image_pred_class.new(image_pred_class.size(0), 1).fill_(ind)      
                #Repeat the batch_id for as many detections of the class cls in the image
                seq = batch_ind, image_pred_class
    
                if not write:
                    output = torch.cat(seq,1)
                    write = True
                else:
                    out = torch.cat(seq,1)
                    output = torch.cat((output,out))

    在函数的结尾,我们检查输出是否已全部初始化。 如果尚未检测到,则表示该批次的任何图像中都没有检测到一个。 在这种情况下,我们返回0。

        try:
            return output
        except:
            return 0

    在本文的最后,我们终于有了张量形式的预测,该预测列出了每个预测的行。 现在剩下的唯一事情就是创建一个输入管道,以从磁盘读取图像,计算预测,在图像上绘制边界框,然后显示/写入这些图像。 这是我们在下一部分中将要做的。

    Further Reading

    1. PyTorch tutorial
    2. IoU
    3. Non maximum suppresion
    4. Non-maximum Suppression
  • 相关阅读:
    常见RGB透明度对照表在这
    Android节假日图标动态替换方案
    用两个栈实现队列
    从头到尾打印链表 (链表/栈)
    MySQL常用函数
    找出数组中重复的数字
    两数之和
    java的list的几种排序写法整理(sort的用法)
    Java知识目录
    通过关键词来推荐话题
  • 原文地址:https://www.cnblogs.com/NWNU-LHY/p/12175950.html
Copyright © 2011-2022 走看看