精华内容
下载资源
问答
  • 2019-10-09 17:54:20

    一.yolov v3聚类出框 

    # -*- coding: utf-8 -*-
    import numpy as np
    import random
    import argparse
    import os
    
    # # 参数名称
    # parser = argparse.ArgumentParser(description='使用该脚本生成YOLO-V3的anchor boxes\n')
    # parser.add_argument('--input_annotation_txt_dir', required=True, type=str, help='输入存储图片的标注txt文件(注意不要有中文)')
    # parser.add_argument('--output_anchors_txt', required=True, type=str, help='输出的存储Anchor boxes的文本文件')
    # parser.add_argument('--input_num_anchors', required=True, default=6, type=int, help='输入要计算的聚类(Anchor boxes的个数)')
    # parser.add_argument('--input_cfg_width', required=True, type=int, help="配置文件中width")
    # parser.add_argument('--input_cfg_height', required=True, type=int, help="配置文件中height")
    # args = parser.parse_args()
    # print('args:', args)
    '''
    centroids 聚类点 尺寸是 numx2,类型是ndarray
    annotation_array 其中之一的标注框
    '''
    
    def IOU(annotation_array, centroids):
        #
        similarities = []
        # 其中一个标注框
        w, h = annotation_array
        for centroid in centroids:
            c_w, c_h = centroid
            if c_w >= w and c_h >= h:  # 第1中情况
                similarity = w * h / (c_w * c_h)
            elif c_w >= w and c_h <= h:  # 第2中情况
                similarity = w * c_h / (w * h + (c_w - w) * c_h)
            elif c_w <= w and c_h >= h:  # 第3种情况
                similarity = c_w * h / (w * h + (c_h - h) * c_w)
            else:  # 第3种情况
                similarity = (c_w * c_h) / (w * h)
            similarities.append(similarity)
        # 将列表转换为ndarray
        return np.array(similarities, np.float32)  # 返回的是一维数组,尺寸为(num,)
    
    '''
    k_means:k均值聚类
    annotations_array 所有的标注框的宽高,N个标注框,尺寸是Nx2,类型是ndarray
    centroids 聚类点 尺寸是 numx2,类型是ndarray
    '''
    
    def k_means(annotations_array, centroids, eps=0.00005, iterations=200000):
        #
        N = annotations_array.shape[0]  # C=2
        num = centroids.shape[0]
        # 损失函数
        distance_sum_pre = -1
        assignments_pre = -1 * np.ones(N, dtype=np.int64)
        #
        iteration = 0
        # 循环处理
        while (True):
            #
            iteration += 1
            #
            distances = []
            # 循环计算每一个标注框与所有的聚类点的距离(IOU)
            for i in range(N):
                distance = 1 - IOU(annotations_array[i], centroids)
                distances.append(distance)
            # 列表转换成ndarray
            distances_array = np.array(distances, np.float32)  # 该ndarray的尺寸为 Nxnum
            # 找出每一个标注框到当前聚类点最近的点
            assignments = np.argmin(distances_array, axis=1)  # 计算每一行的最小值的位置索引
            # 计算距离的总和,相当于k均值聚类的损失函数
            distances_sum = np.sum(distances_array)
            # 计算新的聚类点
            centroid_sums = np.zeros(centroids.shape, np.float32)
            for i in range(N):
                centroid_sums[assignments[i]] += annotations_array[i]  # 计算属于每一聚类类别的和
            for j in range(num):
                centroids[j] = centroid_sums[j] / (np.sum(assignments == j))
            # 前后两次的距离变化
            diff = abs(distances_sum - distance_sum_pre)
            # 打印结果
            print("iteration: {},distance: {}, diff: {}, avg_IOU: {}\n".format(iteration, distances_sum, diff,
                                                                               np.sum(1 - distances_array) / (N * num)))
            # 三种情况跳出while循环:1:循环20000次,2:eps计算平均的距离很小 3:以上的情况
            if (assignments == assignments_pre).all():
                print("按照前后两次的得到的聚类结果是否相同结束循环\n")
                break
            if diff < eps:
                print("按照eps结束循环\n")
                break
            if iteration > iterations:
                print("按照迭代次数结束循环\n")
                break
            # 记录上一次迭代
            distance_sum_pre = distances_sum
            assignments_pre = assignments.copy()
    
    
    if __name__ == '__main__':
        # 聚类点的个数,anchor boxes的个数
        num_clusters = 9#args.input_num_anchors
        # 索引出文件夹中的每一个标注文件的名字(.txt)
        names = [i for i in os.listdir('train_images_tif_txt') if 'txt' in i]#args.input_annotation_txt_dir)
        print('names:',names)
        # # 标注的框的宽和高
        annotations_w_h = []
        for name in names:
            txt_path = os.path.join('train_images_tif_txt', name)
            # 读取txt文件中的每一行
            f = open(txt_path, 'r')
            for line in f.readlines():
                line = line.rstrip('\n')
                w, h = line.split(' ')[3:]  # 这时读到的w,h是字符串类型
                # eval()函数用来将字符串转换为数值型
                annotations_w_h.append((eval(w), eval(h)))
            f.close()
            # 将列表annotations_w_h转换为numpy中的array,尺寸是(N,2),N代表多少框
            annotations_array = np.array(annotations_w_h, dtype=np.float32)
        N = annotations_array.shape[0]
        # 对于k-means聚类,随机初始化聚类点
        random_indices = [random.randrange(N) for i in range(num_clusters)]  # 产生随机数
        centroids = annotations_array[random_indices]
        # k-means聚类
        k_means(annotations_array, centroids, 0.00005, 200000)
        # 对centroids按照宽排序,并写入文件
        widths = centroids[:, 0]
        sorted_indices = np.argsort(widths)
        anchors = centroids[sorted_indices]
        print('anchors:',anchors)
        # # 将anchor写入文件并保存
        f_anchors = open('./anchors_txt.txt', 'w')
        # #
        for anchor in anchors:           #cfg_w train的时候用的宽度             #cfg_h train的时候用的高度
            f_anchors.write('%d,%d,' % (int(anchor[0] * 200), int(anchor[1] * 1800)))
            # f_anchors.write('\n')

    train_images_tif_txt下存放的是如下所示的标注txt文件.

    二.宽高比分析

    1.kmeans.py代码

    import numpy as np
    
    
    def iou(box, clusters):
        """
        Calculates the Intersection over Union (IoU) between a box and k clusters.
        :param box: tuple or array, shifted to the origin (i. e. width and height)
        :param clusters: numpy array of shape (k, 2) where k is the number of clusters
        :return: numpy array of shape (k, 0) where k is the number of clusters
        """
        x = np.minimum(clusters[:, 0], box[0])
        y = np.minimum(clusters[:, 1], box[1])
        if np.count_nonzero(x == 0) > 0 or np.count_nonzero(y == 0) > 0:
            raise ValueError("Box has no area")
    
        intersection = x * y
        box_area = box[0] * box[1]
        cluster_area = clusters[:, 0] * clusters[:, 1]
    
        iou_ = intersection / (box_area + cluster_area - intersection)
    
        return iou_
    
    
    def avg_iou(boxes, clusters):
        """
        Calculates the average Intersection over Union (IoU) between a numpy array of boxes and k clusters.
        :param boxes: numpy array of shape (r, 2), where r is the number of rows
        :param clusters: numpy array of shape (k, 2) where k is the number of clusters
        :return: average IoU as a single float
        """
        return np.mean([np.max(iou(boxes[i], clusters)) for i in range(boxes.shape[0])])
    
    
    def translate_boxes(boxes):
        """
        Translates all the boxes to the origin.
        :param boxes: numpy array of shape (r, 4)
        :return: numpy array of shape (r, 2)
        """
        new_boxes = boxes.copy()
        for row in range(new_boxes.shape[0]):
            new_boxes[row][2] = np.abs(new_boxes[row][2] - new_boxes[row][0])
            new_boxes[row][3] = np.abs(new_boxes[row][3] - new_boxes[row][1])
        return np.delete(new_boxes, [0, 1], axis=1)
    
    
    def kmeans(boxes, k, dist=np.median):
        """
        Calculates k-means clustering with the Intersection over Union (IoU) metric.
        :param boxes: numpy array of shape (r, 2), where r is the number of rows
        :param k: number of clusters
        :param dist: distance function
        :return: numpy array of shape (k, 2)
        """
        rows = boxes.shape[0]
    
        distances = np.empty((rows, k))
        last_clusters = np.zeros((rows,))
    
        np.random.seed()
    
        print('np.random.choice(rows, k, replace=False):',np.random.choice(rows, k))
        # the Forgy method will fail if the whole array contains the same rows
        clusters = boxes[np.random.choice(rows, k, replace=False)]
    
        while True:
            for row in range(rows):
                distances[row] = 1 - iou(boxes[row], clusters)
    
            nearest_clusters = np.argmin(distances, axis=1)
    
            if (last_clusters == nearest_clusters).all():
                break
    
            for cluster in range(k):
                clusters[cluster] = dist(boxes[nearest_clusters == cluster], axis=0)
    
            last_clusters = nearest_clusters
    
        return clusters
    

    2.example.py代码

    import glob
    import xml.etree.ElementTree as ET
    import cv2
    import os
    import numpy as np
    import matplotlib.pyplot as plt
    from kmeans import kmeans, avg_iou
    
    # ANNOTATIONS_PATH = "./data/pascalvoc07-annotations"
    ANNOTATIONS_PATH = "./data/widerface-annotations"
    CLUSTERS = 9
    # 相对原图是否归一化
    BBOX_NORMALIZE = True
    
    
    def show_cluster(data, cluster, max_points=2000):
        '''
        Display bouding box's size distribution and anchor generated in scatter.
        '''
        if len(data) > max_points:
            idx = np.random.choice(len(data), max_points)
            data = data[idx]
        plt.scatter(data[:, 0], data[:, 1], s=5, c='lavender')
        plt.scatter(cluster[:, 0], cluster[:, 1], c='red', s=100, marker="^")
        plt.xlabel("Width")
        plt.ylabel("Height")
        plt.title("Bounding and anchor distribution")
        plt.savefig("cluster.png")
        plt.show()
    
    
    def show_width_height(data, cluster, bins=50):
        '''
        Display bouding box distribution with histgram.
        '''
        if data.dtype != np.float32:
            data = data.astype(np.float32)
        width = data[:, 0]
        height = data[:, 1]
        ratio = height / width
    
        plt.figure(1, figsize=(20, 6))
        plt.subplot(131)
        plt.hist(width, bins=bins, color='green')
        plt.xlabel('width')
        plt.ylabel('number')
        plt.title('Distribution of Width')
    
        plt.subplot(132)
        plt.hist(height, bins=bins, color='blue')
        plt.xlabel('Height')
        plt.ylabel('Number')
        plt.title('Distribution of Height')
    
        plt.subplot(133)
        plt.hist(ratio, bins=bins, color='magenta')
        plt.xlabel('Height / Width')
        plt.ylabel('number')
        plt.title('Distribution of aspect ratio(Height / Width)')
        plt.savefig("shape-distribution.png")
        plt.show()
    
    
    def sort_cluster(cluster):
        '''
        Sort the cluster to with area small to big.
        '''
        if cluster.dtype != np.float32:
            cluster = cluster.astype(np.float32)
        area = cluster[:, 0] * cluster[:, 1]
        cluster = cluster[area.argsort()]
        ratio = cluster[:, 1:2] / cluster[:, 0:1]
        return np.concatenate([cluster, ratio], axis=-1)
    
    
    # def load_dataset(path, normalized=True):
    #     '''
    #     load dataset from pasvoc formatl xml files
    #     return [[w,h],[w,h]]
    #     '''
    #     dataset = []
    #     for xml_file in glob.glob("{}/*xml".format(path)):
    #         tree = ET.parse(xml_file)
    #
    #         height = int(tree.findtext("./size/height"))
    #         width = int(tree.findtext("./size/width"))
    #
    #         for obj in tree.iter("object"):
    #             if normalized:
    #                 xmin = int(obj.findtext("bndbox/xmin")) / float(width)
    #                 ymin = int(obj.findtext("bndbox/ymin")) / float(height)
    #                 xmax = int(obj.findtext("bndbox/xmax")) / float(width)
    #                 ymax = int(obj.findtext("bndbox/ymax")) / float(height)
    #             else:
    #                 xmin = int(obj.findtext("bndbox/xmin"))
    #                 ymin = int(obj.findtext("bndbox/ymin"))
    #                 xmax = int(obj.findtext("bndbox/xmax"))
    #                 ymax = int(obj.findtext("bndbox/ymax"))
    #             if (xmax - xmin) == 0 or (ymax - ymin) == 0:
    #                 continue  # to avoid divded by zero error.
    #             dataset.append([xmax - xmin, ymax - ymin])
    #
    #     return np.array(dataset)
    
    def load_dataset(path, normalized=True):
        '''
        load dataset from pasvoc formatl xml files
        return [[w,h],[w,h]]
        '''
        dataset = []
        names = [i for i in os.listdir(path) if 'txt' in i]  # args.input_annotation_txt_dir)
        # print('names:', names)
        # # 标注的框的宽和高
        # annotations_w_h = []
        for name in names:
            txt_path = os.path.join(path, name)
            img_path = txt_path.replace('.txt', '.jpg')
            img = cv2.imread(img_path)
            img_h, img_w, _ = img.shape
            # 读取txt文件中的每一行
            f = open(txt_path, 'r')
            for line in f.readlines():
                line = line.rstrip('\n')
                w, h = line.split(' ')[3:]  # 这时读到的w,h是字符串类型
                # eval()函数用来将字符串转换为数值型
                if normalized:
                    dataset.append((eval(w), eval(h)))
                else:
                    dataset.append((eval(w) * 200, eval(h) * 1800))
            f.close()
    
        return np.array(dataset)
    
    
    # print("Start to load data annotations on: %s" % ANNOTATIONS_PATH)
    # [[w, h], [w, h]]
    data = load_dataset(path='./train_img', normalized=BBOX_NORMALIZE)
    print(data[:3])
    print("Start to do kmeans, please wait for a moment.")
    out = kmeans(data, k=CLUSTERS)
    print('==out', out)
    out_sorted = sort_cluster(out)
    print("Accuracy: {:.2f}%".format(avg_iou(data, out) * 100))
    #
    show_cluster(data, out, max_points=2000)
    
    if out.dtype != np.float32:
        out = out.astype(np.float32)
    
    print("Recommanded aspect ratios(width/height)")
    print("Width    Height   Height/Width")
    for i in range(len(out_sorted)):
        print("%.3f      %.3f     %.1f" % (out_sorted[i, 0], out_sorted[i, 1], out_sorted[i, 2]))
    show_width_height(data, out, bins=50)
    

    txt是类别, cx,cy,w和h是归一化后的比例),下图是其分布,也就是输入如果是方形,anchor ratio比就用这个

     下图是乘以实际尺寸后的分布,也就是输入如果是图片等比例 anchor ratio比就用这个

    更多相关内容
  • yolov3.zip

    2020-03-31 17:18:54
    利用K-means聚类算法对目标候选框的个数和宽高比维度进行聚类分析。用改进的YOLO V3算法和原YOLO V3算法在VEDAI数据集上进行对比实验,结果表明改进后的YOLO V3算法能有效检测小目标,对小目标的召回率和检测的平均...
  • YOLOv3详解

    2022-05-21 13:31:04
    前言 yolo算法是一种one-stage的目标检测算法,与two-stage最大区别在于,YOLO系列算法将图片划分成若干个网格,再基于anchor机制生成...YOLOv4的论文于2020年发表在CVPR上,相对于YOLOv3的主要改动有以下几部分,mos.


    前言

    yolo算法是一种one-stage的目标检测算法,与two-stage目标检测算法最大区别在于运算速度上,YOLO系列算法将图片划分成若干个网格,再基于anchor机制生成先验框,只用一步就生成检测框,这种方法大大提升了算法的预测速度,今天我们主要学习的是YOLOv3算法的主要实现过程,YOLOv3的论文于2018年发表在CVPR上.

    ​​论文名称:YOLOv3: An Incremental Improvement

    论文下载地址: https://arxiv.org/abs/1804.02767

    一、YOLOv3网络模型分析

    YOLOv3网络结构大致可分为三个部分:BackbonePANetYolo Head,在YOLOv3-SPP结构中还引入了SPP结构,但与SPPnet的SPP略有不同。
    yolov3网络结构图

    1.Backbone: Darknet-53

    首先了解一下yolov3backbone部分,Darknet-53主体与resnet结构相似,堆叠了多个 残差模块,残差模块之间间隔了一个kernel_size=3x3stride=2卷积层,作用主要是downsample,53代表了整个backbone一共有52个卷积层和最后的connect层(全连接层),一共53层结构,下图是以输入图像256 x 256进行预训练来进行介绍的,常用的尺寸是416 x 416,都是32的倍数(经过stride后会变换特征图大小一共有5次,每次都是2倍所以确定img_size大小要是 2 5 = 32 2^5=32 25=32)。
    卷积的strides默认为1padding默认为same(补全),当strides2时padding为valid(丢弃).
    在这里插入图片描述
    第一个3x3的卷积核主要用来增加通道数,在不改变图片尺寸的条件下获得更多的有效特征图,同时扩大特征图感受野,第二个3x3的卷积核stride=2主要作用是downsample,减少计算过程中的参数量与计算量,再堆叠多个residual block,最后对得到的feature map进行平均池化。
    以上的Residual模块的残差连接方式与Resnet基本相同,输入特征图与输出特征图中间经过卷积核大小为1x13x3大小的两次卷积,再将输出的两个特征图add,Convolutional模块=conv+bn+leakrelu,如下图所示:
    Residual and Convlutional

    2.PANet

    PANet对应Neck模块,是FPN的改进版本,FPN主要方法是自顶向下对浅层大尺度特征图进行下采样与深层特征图进行特征融合,输出多个不同尺度的特征图进行预测,PANet在此基础之上增加了对深层特征图的上采样操作,将深层小尺度特征图上采样后concat在一起,提出了一种自顶向下+自底向上的特征融合方式。
    PANet可以被看作一个Multiple-input-multiple-output encoder.它对网络性能影响最大的主要有两个方面:
    (1) 多尺度特征融合.
    (2) 分而治之:简而言之就是输出多个不同尺度具有不同感受野特征图.
    对于这两个影响因素谁更重要,可以参考论文YOLOF(You Only Look One-level Feature ),该论文针对提出了一种single-input-single-outputencoder,通过在残差模块中堆叠膨胀卷积使得单级特征图具有更大的感受野,在保证精度的前提下减少了encoder的参数量.

    3.Yolo Head

    Yolo Head是一种decoder,它的主要结构是一个conv+bn+act模块与一个kernel_size =1x1卷积分类层,用1x1卷积代替全连接层进行分类的主要原因有两点:
    (1) 全连接层输入尺度被限定,而卷积层只用限定输入和输出channel
    (2)全连接层输出是一维或二维,特征图输入到全连接层时需要对其进行Flatten,这样在一定程度上破环了特征图的空间信息,而卷积层输出为三维(c,w,h),选择卷积层作为decoder,极大保留了特征图上对应原图的空间结构信息,如下图所示:
    更便于匹配到正样本时,输出空间上对应的channel的值.
    yolov3

    二、anchor网格偏移量预测

    由前一部分的讲解可知,Yolo Head输出是三维的w,h 的每一个点对应原图划分的网格,channel对应的是该anchor的预测值,那我们如何计算anchor的具体位置呢?
    yolo的思想是将一张图片划分为W*W个网格,每一个网格负责中心点落在该网格的目标.每个网格可以看作一个感兴趣区域,既然是区域,就需要计算预测anchor的具体坐标与bboxwh.
    c h a n n e l = t x + t y + t w + t h + o b j + n u m c l a s s e s channel = t_x+t_y+t_w+t_h+obj+num_{classes} channel=tx+ty+tw+th+obj+numclasses
    其中预测值tx,ty并不是anchor的坐标,而是anchor的偏移量,同样的 t w , t h t_w,t_h tw,th是先验框的缩放因子,先验框的大小由k-means聚类xml标签文件中保存的坐标位置得到,每一个anchor有三个不同大小的先验框,预测层Yolohead有三个,总共的先验框数量为划分总网格数的三倍.
    anchor 坐标预测
    上图表示了bbox的回归过程 c x , c y c_x,c_y cx,cy是网格左上角坐标,anchor向右下方偏移,为了防止anchor偏移量超出网格导致定位精度损失过高,yolov3使用了sigmoid函数将预测值tx,ty进行限制到[0,1](可以加速网络的收敛),最后得出anchor的预测坐标 b x , b y b_x,b_y bx,by.bbox的w与h由预测缩放因子 t w 与 t h t_w与t_h twth决定, p w 和 p h p_w和p_h pwphanchor模板映射到特征图上的宽和高,通过指数函数对 t w t_w tw t h t_h th进行放大在分别与 p w 和 p h p_w和p_h pwph相乘就得到了最终预测的w和h.
    PS:此处的x,y坐标点都是anchor映射到特征图的值,并不是原图上的坐标,在plot bbox应该将其转换到真实图片上的坐标再进行绘制.

    三、正负样本匹配规则

    yolov3论文中提到正负样本的匹配规则是:给每一个groundtrue box分配一个正样本,这个正样本是所有bbox中找一个与gt_box的重叠区域最大的一个预测框,也就是和该gt_box的iou最大的预测框.但是如果利用这个规则去寻找正样本,正样本的数量是很少的,这将使得网络难以训练.如果一个样本不是正样本,那么它既没有定位损失,也没有类别损失,只有置信度损失,在yolov3的论文中作者尝试用focal loss来缓解正负样本不均匀的问题,但是并没有取得很好的效果,原因就在于负样本值参与了置信度损失,对loss的影响占比很小.
    所以我们要去看作者在源码中是如何匹配正样本的.
    源码中定义了build_targets函数来匹配正样本.

    def build_targets(p, targets, model):
        """
        Build targets for compute_loss(), input targets(image,class,x,y,w,h)
        :param p: 预测框 由模型构建中的yolo_out返回的三个yolo层的输出
                  tensor格式 list列表 存放三个tensor 对应的是三个yolo层的输出
                  例如:[4, 3, 23, 23, 25]  [4, 3, 46, 46, 25]  [4, 3, 96, 96, 25]  (736x736尺度下)
                  [batch_size, anchor_num, grid, grid, xywh + obj + classes]
                  p[i].shape
        :param targets: 数据增强后一个batch的真实框 [21, 6] 21: num_object  6: batch中第几张图(0,1,2,3)+类别+x+y+w+h真实框
        :param model: 初始化的模型
        :return: tbox: append [m(正样本个数), x偏移量(中心点坐标相对中心所在grid_cell左上角的偏移量) + y偏移量 + w + h]
                       存放着当前batch中所有anchor的正样本 某个anchor的正样本指的是当前的target由这个anchor预测
                       另外,同一个target可能由多个anchor预测,所以通常 m>nt
                 indices: append [m(正样本个数), b + a + gj + gi]
                       b: 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)属于这个batch中的哪一张图片
                       a: 和tbox一一对应 存放着tbox中对应位置的target是属于哪个anchor(index)的正样本(由哪个anchor负责预测)
                       gj: 和tbox一一对应 存放着tbox中对应位置的target的中心点所在grid_cell的左上角的y坐标
                       gi: 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)的中心点所在grid_cell的左上角的x坐标
                 tcls: append [m] 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)所属的类别
                 anch: append [m, 2] 和tbox一一对应 存放着tbox中对应位置的target是属于哪个anchor(shape)的正样本(由哪个anchor负责预测)
        """
    
        nt = targets.shape[0]  # 当前batch真实框的数量 num of target
    
        # 定义一些变量
        # anch append [m, 2] 和tbox一一对应 存放着对应位置的target是属于哪个anchor(shape)的正样本(由哪个anchor负责预测)
        tcls, tbox, indices, anch = [], [], [], []
        gain = torch.ones(6, device=targets.device)  # normalized to gridspace gain tensor([1,1,1,1,1,1])
    
        multi_gpu = type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel)  # 一般False
        for i, j in enumerate(model.yolo_layers):  # [89, 101, 113]  i,j = 0, 89   1, 101   2, 113
            # 获取该yolo predictor对应的anchors的大小,不过这里是缩放到feature map上的  shape=[3, 2]
            # 而且它就是cfg文件中的anchors除以了缩放比例stride得到的
            # 比如:[3.6250, 2.8125] / 13 * 416 (缩放比例为32)= [116,90]  等于cfg文件中的anchor的大小
            anchors = model.module.module_list[j].anchor_vec if multi_gpu else model.module_list[j].anchor_vec
    
            # gain中存放的是feature map的尺寸信息
            # 在原图尺度为(736,736)情况下  p[0]=[4,3,23,23,25] p[1]=[4,3,46,46,25] p[2]=[4,3,92,92,25]
            # 如原图(736x736)  gain=Tensor([1,1,23,23,23,23]) 或 Tensor([1,1,46,46,46,46]) 或 Tensor([1,1,92,92,92,92])
            gain[2:] = torch.tensor(p[i].shape)[[3, 2, 3, 2]]  # xyxy gain
    
            na = anchors.shape[0]  # number of anchors  3个
            # [3] -> [3, 1] -> [3, nt]
            # anchor tensor, same as .repeat_interleave(nt)   at.shape=[3,21] 21个0, 1, 2
            at = torch.arange(na).view(na, 1).repeat(1, nt)
    
            # Match targets to anchors
            # t = targets * gain: 将box坐标(在box标签生成中,对box坐标进行了归一化,即除以图像的宽高)转换到当前yolo层输出的特征图上
            #                     通过将归一化的box乘以特征图尺度,从而将box坐标投影到特征图上
            # 广播原理 targets=[21,6]  gain=[6] => gain=[6,6] => t=[21,6]
            a, t, offsets = [], targets * gain, 0
    
            if nt:  # 如果存在target的话
                # 把yolo层的anchor在该feature map上对应的wh(anchors)和所有预测真实框在该feature map上对应的wh(t[4:6])做iou,
                #       若大于model.hyp['iou_t']=0.2, 则为正样本保留,否则则为负样本舍弃
                # anchors: [3, 2]: 当前yolO层的三个anchor(且都是相对416x416的, 不过初始框的wh多大都可以,反正最后都是做回归)
                # t[:, 4:6]: [nt, 2]: 所有target真是框的w和h, 且都是相对当前feature map的
                # j: [3, nt]
                j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t']  # iou(3,n) = wh_iou(anchors(3,2), gwh(n,2))
                # t.repeat(na, 1, 1): [nt, 6] -> [3, nt, 6]
                # 获取iou大于阈值的anchor与target对应信息
                # a=tensor[30]: anchor_index(0、1、2)  0表示是属于第一个anchor(包含4张图片)的正样本  同理第二。。。
                #               再解释下什么是正样本: 表示当前target可以由第i个anchor检测,就表示当前target是这个anchor的正样本
                # t=tensor[30,6]: 第0、1、2、3(4张图片)的target, class, x, y, w, h(相对于当前feature map尺度)
                #                 与a变量一一对应,a用来指示t中相对应的位置的target是属于哪一个anchor的正样本
                #                 注意:这里的同一个target是可能会同属于多个anchor的正样本的,由多个anchor计算同一个target
                #                      不然t个数也不会大于正样本数(30>21)
                a, t = at[j], t.repeat(na, 1, 1)[j]  # filter 选出所有anchor对应属于它们的正样本
    
            # Define
            # b: 对应图片的index 即当前target是属于哪张图片的
            # c: 当前target是属于哪个类
            # long等于to(torch.int64), 数值向下取整  这里都是整数,long()只起到了float->int的作用
            b, c = t[:, :2].long().T  # image, class
    
            gxy = t[:, 2:4]  # grid xy  对应于当前feature map的target的xy坐标
            gwh = t[:, 4:6]  # grid wh  对应于当前feature map的target的wh坐标
    
            # 匹配targets所在的grid cell左上角坐标
            # (gxy-0).long 向下取整  得到当前target的中心点所在左上角的坐标
            gij = (gxy - offsets).long()
            # grid xy indices 左上角x, y坐标
            gi, gj = gij.T
    
            # Append
            indices.append((b, a, gj, gi))  # image index, anchor, grid indices(x, y)
            tbox.append(torch.cat((gxy - gij, gwh), 1))  # gt box相对当前feature map的x,y偏移量以及w,h
            anch.append(anchors[a])  # anchors
            tcls.append(c)  # class
    
            if c.shape[0]:  # if any targets
                # 目标的标签数值不能大于给定的目标类别数
                assert c.max() < model.nc, 'Model accepts %g classes labeled from 0-%g, however you labelled a class %g. ' \
                                           'See https://github.com/ultralytics/yolov3/wiki/Train-Custom-Data' % (
                                               model.nc, model.nc - 1, c.max())
        return tcls, tbox, indices, anch
    
    
    # 这里是根据宽高(左上角对齐)来进行iou计算处理
    # 与普通的IoU,GIoU,那些不一样
    def wh_iou(wh1, wh2):
        """
        把yolo层的anchor在该feature map上对应的wh(anchors)和所有预测真实框在该feature map上对应的wh(t[4:6])做iou,
        若大于model.hyp['iou_t']=0.2, 则为正样本保留,否则则为负样本舍弃  筛选出符合该yolo层对应的正样本
        Args:
            wh1: anchors  [3, 2]
            wh2: target   [22,2]
        Returns:
            wh1 和 wh2 的iou [3, 22]
        """
        # Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2
        wh1 = wh1[:, None]  # [N,1,2]  [3, 1, 2]
        wh2 = wh2[None]  # [1,M,2]     [1, 22, 2]
        inter = torch.min(wh1, wh2).prod(2)  # [N,M]  [3, 22]
        return inter / (wh1.prod(2) + wh2.prod(2) - inter)  # iou = inter / (area1 + area2 - inter)
    
    

    通过对上面源码的阅读我们可以得出作者首先将bboxgr_box的左上角对齐,再计算出存在目标的anchorbboxgr_boxiou,并设定一个iou阈值,如果anchor templateiou大于阈值则归为正样本.

    四、损失函数

    损失分为三个部分:置信度损失,定位损失和类别损失.
    L = L c l s + L c o n f + L l o c L = L_{cls} + L_{conf} +L_{loc} L=Lcls+Lconf+Lloc
    在正负样本匹配部分我已经解释过只有正样本才有这三类损失,而负样本只有置信度损失,所以在损失计算的过程中我们需要将anchor template划分为正样本与负样本两种情况来计算loss.

    1.类别损失(只考虑正样本)

    L o s s c l s Loss_{cls} Losscls选用的是BCE损失函数:
    L c l s = − ∑ i ∈ p o s ∑ j ∈ c l s ( O i j l n ( C ^ i j ) ) + ( 1 − O i j ) l n ( 1 − C ^ i j ) L_{cls} =-\sum_{i\in pos}\sum_{j\in cls}(O_{ij}ln(\hat C_{ij}))+(1-O_{ij})ln(1-\hat C_{ij}) Lcls=iposjcls(Oijln(C^ij))+(1Oij)ln(1C^ij)
    C ^ i j = σ ( C i j ) \hat C_{ij} = \sigma (C_{ij}) C^ij=σ(Cij)
    O i j O_{ij} Oij代表第i个正样本中的第j个类别是否存在,若存在则为1,不存在为0.(根据自己打的标签来)
    C ^ i j \hat C_{ij} C^ij代表经过sigmoid函数分类后的预测值.
    yolov3论文片段
    上图是从原论文中截取的对分类损失函数的描述,作者的观点是获取好的分类性能并不一定要使用softmax,相反使用了使sigmoid函数分类和bce损失.
    读到这儿,平时我们一般都使用softmax进行多分类,那为什么使用sigmoid进行分类?
    independent logistic classifiers指的是sigmoid函数,个人的理解是:它的好处是将类别概率限制到[0,1]之间,且每个类别的概率值相互独立,互不影响,适用于多标签分类.softmax在进行分类时使所有类别概率总和为1,很多时候会出现“一家独大”的情况,即某一个类别占比较大,概率值较高,其他类别的概率值很低,在计算类别损失时无法达到一个好的效果,并不利于网络的收敛.
    总而言之,softmax函数不适用于多标签分类,预测值通过sigmoid函数后每一类的值相互独立互不影响,适用于同时预测多类目标,即多标签分类.

    2.置信度损失(考虑所有样本)

    置信度 L o s s c o n f Loss_{conf} Lossconf计算公式如下所示:
    L o s s c o n f = − ∑ i O i l n ( c ^ i ) + ( 1 − O i ) l n ( 1 − c ^ i ) Loss_{conf}=-\sum_iO_iln(\hat c_i)+(1-O_i)ln(1-\hat c_i) Lossconf=iOiln(c^i)+(1Oi)ln(1c^i)
    C ^ i = σ ( C i ) \hat C_{i} = \sigma (C_{i}) C^i=σ(Ci)
    O i O_i Oi代表预测目标边界框i中是否真实存在目标.若存在则为1,不存在为0.(根据自己打的标签来)
    C ^ i \hat C_{i} C^i代表表示预测目标矩形框i内是否存在目标的Sigmoid概率.
    Sigmoid函数作用主要是将预测值范围规定到[0,1]
    yolov3论文片段
    yolov3论文中提出confidence score的预测使用的是一种逻辑回归的方式,所以损失函数用的是BCE.

    3.定位损失(只考虑正样本)

    置信度 L o s s l o c Loss_{loc} Lossloc计算公式如下所示:
    L o s s l o c = ∑ i ∈ p o s ( t ^ ∗ − t ∗ ) 2 Loss_{loc}=\sum_{i\in pos}(\hat t_*-t_*)^2 Lossloc=ipos(t^t)2
    yolov3论文片段
    根据以上论文内容可知,计算 L o s s l o c Loss_{loc} Lossloc时使用的时误差平方和公式, t ^ ∗ \hat t_* t^为真实值, t ∗ t_* t为预测值
    所以 L o s s l o c Loss_{loc} Lossloc可写作如下形式
    L o s s l o c ( x , y ) = ∑ i ∈ p o s ∑ m ∈ x , y [ ( g m − c m ) − σ ( t m ) ] 2 Loss_{loc (x,y)} = \sum_{i\in pos}\sum_{m\in{x,y}}[ (g_m-c_m)-\sigma(t_m)]^2 Lossloc(x,y)=iposmx,y[(gmcm)σ(tm)]2
    其中 t ^ ∗ = g m − c m \hat t_* =g_m-c_m t^=gmcm t ∗ = σ ( t m ) t_* = \sigma(t_m) t=σ(tm)
    L o s s l o c ( w , h ) = ∑ i ∈ p o s ∑ n ∈ w , h ( l n g m i p n i − t n ) 2 Loss_{loc (w,h)} = \sum_{i\in pos}\sum_{n\in{w,h}}(ln\frac{g_m^i}{p_n^i}-t_n)^2 Lossloc(w,h)=iposnw,h(lnpnigmitn)2
    其中 t ^ ∗ = l n g m i p n i \hat t_* =ln\frac{g_m^i}{p_n^i} t^=lnpnigmi t ∗ = t n t_* = t_n t=tn
    L o s s l o c = L o s s l o c ( x , y ) + L o s s l o c ( w , h ) Loss_{loc}=Loss_{loc (x,y)} +Loss_{loc (w,h)} Lossloc=Lossloc(x,y)+Lossloc(w,h)
    yolov3中的定位损失采用SSE显然是不太合适的,SSE主要是对预测所得anchor中心偏移量与宽高缩放因子进行误差分析,是对点进行回归分析,并不能较好的反应预测框与真实框之间的误差关系,这个问题我们将在下一节yolov4的讲解中进行讨论.

    展开全文
  • 在上一篇博客:Yolov3-spp系列 | 训练Pascal voc格式的数据集的基础上,可以跑通之后,就可以一步步调试理解代码了。对于整个yolov3spp的训练阶段,最最重要的就是正负样本匹配的实现。所以,下面,我会记录一下...

    如有问题,恳请指出。


    在上一篇博客:Yolov3-spp系列 | 训练Pascal voc格式的数据集的基础上,可以跑通之后,就可以一步步调试理解代码了。对于整个yolov3spp的训练阶段,最最重要的就是正负样本匹配的实现。所以,下面,我会记录一下yolov3spp的正负样本匹配实现上。

    在yolov3-spp的正负样本匹配与损失计算中,主要是在train_one_epoch这个函数下的两个操作:

    • build_targets:负责正负样本匹配,传入的参数同样为predictions, targets, model
    • compute_loss:负责损失计算,传入的参数为predictions, targets, model

    在整个yolov3-spp的工程项目中,最关键的最重要的也就是这个部分,主要思路是首先进行build_targets的正负样本的匹配,然后再进行compute_loss损失计算。所以build_targets函数是被包含了在compute_loss函数中,下面分别对这另个函数给予注释。


    1. 正负样本匹配

    build_targets函数代码:作用是为YOLO Layer输出的3个feature map对应的每个anchor筛选正样本,也就是正负样本匹配,然后利用这些正样本来进行训练,计算损失。

    1.1 处理过程图

    最后会有分析过程
    在这里插入图片描述

    1.2 build_targets代码

    代码如下:

    def build_targets(p, targets, model):
        """
        Build targets for compute_loss(), input targets(image,class,x,y,w,h)
        :param p: 预测框 由模型构建中的yolo_out返回的三个yolo层的输出
                  tensor格式 list列表 存放三个tensor 对应的是三个yolo层的输出
                  例如:[4, 3, 23, 23, 25]  [4, 3, 46, 46, 25]  [4, 3, 96, 96, 25]  (736x736尺度下)
                  [batch_size, anchor_num, grid, grid, xywh + obj + classes]
                  p[i].shape
        :param targets: 数据增强后一个batch的真实框 [21, 6] 21: num_object  6: batch中第几张图(0,1,2,3)+类别+x+y+w+h真实框
        :param model: 初始化的模型
        :return: tbox: append [m(正样本个数), x偏移量(中心点坐标相对中心所在grid_cell左上角的偏移量) + y偏移量 + w + h]
                       存放着当前batch中所有anchor的正样本 某个anchor的正样本指的是当前的target由这个anchor预测
                       另外,同一个target可能由多个anchor预测,所以通常 m>nt
                 indices: append [m(正样本个数), b + a + gj + gi]
                       b: 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)属于这个batch中的哪一张图片
                       a: 和tbox一一对应 存放着tbox中对应位置的target是属于哪个anchor(index)的正样本(由哪个anchor负责预测)
                       gj: 和tbox一一对应 存放着tbox中对应位置的target的中心点所在grid_cell的左上角的y坐标
                       gi: 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)的中心点所在grid_cell的左上角的x坐标
                 tcls: append [m] 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)所属的类别
                 anch: append [m, 2] 和tbox一一对应 存放着tbox中对应位置的target是属于哪个anchor(shape)的正样本(由哪个anchor负责预测)
        """
    
        nt = targets.shape[0]  # 当前batch真实框的数量 num of target
    
        # 定义一些变量
        # anch append [m, 2] 和tbox一一对应 存放着对应位置的target是属于哪个anchor(shape)的正样本(由哪个anchor负责预测)
        tcls, tbox, indices, anch = [], [], [], []
        gain = torch.ones(6, device=targets.device)  # normalized to gridspace gain tensor([1,1,1,1,1,1])
    
        multi_gpu = type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel)  # 一般False
        for i, j in enumerate(model.yolo_layers):  # [89, 101, 113]  i,j = 0, 89   1, 101   2, 113
            # 获取该yolo predictor对应的anchors的大小,不过这里是缩放到feature map上的  shape=[3, 2]
            # 而且它就是cfg文件中的anchors除以了缩放比例stride得到的
            # 比如:[3.6250, 2.8125] / 13 * 416 (缩放比例为32)= [116,90]  等于cfg文件中的anchor的大小
            anchors = model.module.module_list[j].anchor_vec if multi_gpu else model.module_list[j].anchor_vec
    
            # gain中存放的是feature map的尺寸信息
            # 在原图尺度为(736,736)情况下  p[0]=[4,3,23,23,25] p[1]=[4,3,46,46,25] p[2]=[4,3,92,92,25]
            # 如原图(736x736)  gain=Tensor([1,1,23,23,23,23]) 或 Tensor([1,1,46,46,46,46]) 或 Tensor([1,1,92,92,92,92])
            gain[2:] = torch.tensor(p[i].shape)[[3, 2, 3, 2]]  # xyxy gain
    
            na = anchors.shape[0]  # number of anchors  3个
            # [3] -> [3, 1] -> [3, nt]
            # anchor tensor, same as .repeat_interleave(nt)   at.shape=[3,21] 21个0, 1, 2
            at = torch.arange(na).view(na, 1).repeat(1, nt)
    
            # Match targets to anchors
            # t = targets * gain: 将box坐标(在box标签生成中,对box坐标进行了归一化,即除以图像的宽高)转换到当前yolo层输出的特征图上
            #                     通过将归一化的box乘以特征图尺度,从而将box坐标投影到特征图上
            # 广播原理 targets=[21,6]  gain=[6] => gain=[6,6] => t=[21,6]
            a, t, offsets = [], targets * gain, 0
    
            if nt:  # 如果存在target的话
                # 把yolo层的anchor在该feature map上对应的wh(anchors)和所有预测真实框在该feature map上对应的wh(t[4:6])做iou,
                #       若大于model.hyp['iou_t']=0.2, 则为正样本保留,否则则为负样本舍弃
                # anchors: [3, 2]: 当前yolO层的三个anchor(且都是相对416x416的, 不过初始框的wh多大都可以,反正最后都是做回归)
                # t[:, 4:6]: [nt, 2]: 所有target真是框的w和h, 且都是相对当前feature map的
                # j: [3, nt]
                j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t']  # iou(3,n) = wh_iou(anchors(3,2), gwh(n,2))
                # t.repeat(na, 1, 1): [nt, 6] -> [3, nt, 6]
                # 获取iou大于阈值的anchor与target对应信息
                # a=tensor[30]: anchor_index(0、1、2)  0表示是属于第一个anchor(包含4张图片)的正样本  同理第二。。。
                #               再解释下什么是正样本: 表示当前target可以由第i个anchor检测,就表示当前target是这个anchor的正样本
                # t=tensor[30,6]: 第0、1、2、3(4张图片)的target, class, x, y, w, h(相对于当前feature map尺度)
                #                 与a变量一一对应,a用来指示t中相对应的位置的target是属于哪一个anchor的正样本
                #                 注意:这里的同一个target是可能会同属于多个anchor的正样本的,由多个anchor计算同一个target
                #                      不然t个数也不会大于正样本数(30>21)
                a, t = at[j], t.repeat(na, 1, 1)[j]  # filter 选出所有anchor对应属于它们的正样本
    
            # Define
            # b: 对应图片的index 即当前target是属于哪张图片的
            # c: 当前target是属于哪个类
            # long等于to(torch.int64), 数值向下取整  这里都是整数,long()只起到了float->int的作用
            b, c = t[:, :2].long().T  # image, class
    
            gxy = t[:, 2:4]  # grid xy  对应于当前feature map的target的xy坐标
            gwh = t[:, 4:6]  # grid wh  对应于当前feature map的target的wh坐标
    
            # 匹配targets所在的grid cell左上角坐标
            # (gxy-0).long 向下取整  得到当前target的中心点所在左上角的坐标
            gij = (gxy - offsets).long()
            # grid xy indices 左上角x, y坐标
            gi, gj = gij.T
    
            # Append
            indices.append((b, a, gj, gi))  # image index, anchor, grid indices(x, y)
            tbox.append(torch.cat((gxy - gij, gwh), 1))  # gt box相对当前feature map的x,y偏移量以及w,h
            anch.append(anchors[a])  # anchors
            tcls.append(c)  # class
    
            if c.shape[0]:  # if any targets
                # 目标的标签数值不能大于给定的目标类别数
                assert c.max() < model.nc, 'Model accepts %g classes labeled from 0-%g, however you labelled a class %g. ' \
                                           'See https://github.com/ultralytics/yolov3/wiki/Train-Custom-Data' % (
                                               model.nc, model.nc - 1, c.max())
        return tcls, tbox, indices, anch
    
    
    # 这里是根据宽高(左上角对齐)来进行iou计算处理
    # 与普通的IoU,GIoU,那些不一样
    def wh_iou(wh1, wh2):
        """
        把yolo层的anchor在该feature map上对应的wh(anchors)和所有预测真实框在该feature map上对应的wh(t[4:6])做iou,
        若大于model.hyp['iou_t']=0.2, 则为正样本保留,否则则为负样本舍弃  筛选出符合该yolo层对应的正样本
        Args:
            wh1: anchors  [3, 2]
            wh2: target   [22,2]
        Returns:
            wh1 和 wh2 的iou [3, 22]
        """
        # Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2
        wh1 = wh1[:, None]  # [N,1,2]  [3, 1, 2]
        wh2 = wh2[None]  # [1,M,2]     [1, 22, 2]
        inter = torch.min(wh1, wh2).prod(2)  # [N,M]  [3, 22]
        return inter / (wh1.prod(2) + wh2.prod(2) - inter)  # iou = inter / (area1 + area2 - inter)
    

    1.3 简要分析

    在build_targets这个函数中,主要是根据traget来获取类别信息,边界框信息,与gt匹配的anchor信息(anchor的宽高大小)以及其他信息(比如anhcor选择的类别,当前batch的第几张图片,还有对应grid cell的xy坐标),大致详细过程也可以从上图的处理过程图看见。

    这里比较重要的过程是,target[2:]中存储是0~1的相对坐标,那么可以利用当前的预测特征层p,来对target缩放到当前特征层的维度,也就是关键代码:a, t, offsets = [], targets * gain, 0。

    对每层的预测特征层进行遍历,每层都包含3种不同大小的anchor,这个设置在cfg文件中。对于当前特征层所设置的3种anchor,与刚刚缩放好的target信息进一个宽高的重叠率测试操作(wh_iou函数实现),筛选出那些大于阈值的target称为t与所匹配的anchor索引列表称为a。

    那么根据t基于可以获得target的中心点xy坐标t[:, 2:4],以及wh宽高t[:, 4:]。而由于处于xy坐标的网格就由那个点的anchor来进行预测,所以对中心点去小数,就得到了匹配targets所在的grid cell左上角坐标gij。所以,之后就会利用该grid cell坐标点中所匹配的anhcor来进行预测,而且刚刚所去掉的小数就是所需要预测的偏移量。网络所需要预测的就是这个偏移量以及宽高。

    总的来说,就是根据target返回了需要预测的类别信息与边界框的偏移量,还有所使用到的anchor与与预测特征层匹配的信息。


    2. 损失计算

    compute_loss函数代码:作用是计算损失

    2.1 处理过程图

    最后会有分析过程
    在这里插入图片描述

    2.2 compute_loss代码

    代码如下:

    def compute_loss(p, targets, model):
        """
        计算损失:所有anchor的正样本的损失
        Args:
            p: predictions  预测框 由模型构建中的yolo_out返回的三个yolo层的输出
               tensor格式 list列表 存放三个tensor 对应的是三个yolo层的输出
               例如:[4, 3, 23, 23, 25]  [4, 3, 46, 46, 25]  [4, 3, 96, 96, 25]
               [batch_size, anchor_num, grid, grid, xywh + obj + classes]
               可以看出来这里的预测值是三个yolo层每个grid_cell(每个grid_cell有三个预测值)的预测值,后面肯定要进行正样本筛选
            targets: 数据增强后的真实框 [21, 6] 21: num_object  6: batch中第几张图(0,1,2,3)+类别+x+y+w+h真实框 [22, 6]
            model: 初始化模型
        Returns: lbox: 位置损失  tensor([1])
                 obj_loss: 置信度损失 tensor([1])
                 class_loss: 分类损失 tensor([1])
        """
        device = p[0].device
        lcls = torch.zeros(1, device=device)  # Tensor([0.])
        lbox = torch.zeros(1, device=device)  # Tensor([0.])
        lobj = torch.zeros(1, device=device)  # Tensor([0.])
    
        '''
        Build targets for compute_loss(), input targets(image,class,x,y,w,h)
        tbox: append [m(正样本个数), x偏移量(中心点坐标相对中心所在grid_cell左上角的偏移量) + y偏移量 + w + h]
              存放着当前batch中所有anchor的正样本 某个anchor的正样本指的是当前的target由这个anchor预测
              另外,同一个target可能由多个anchor预测,所以通常 m>nt
        indices: append [m(正样本个数), b + a + gj + gi]
                 b: 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)属于这个batch中的哪一张图片
                 a: 和tbox一一对应 存放着tbox中对应位置的target是属于哪个anchor(index)的正样本(由哪个anchor负责预测)
                gj: 和tbox一一对应 存放着tbox中对应位置的target的中心点所在grid_cell的左上角的y坐标(真实框的x的整数部分)
                gi: 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)的中心点所在grid_cell的左上角的x坐标(真实框的y的整数部分)
        tcls: append [m] 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)所属的类别
        anch: append [m, 2] 和tbox一一对应 存放着tbox中对应位置的target是属于哪个anchor(shape)的正样本(由哪个anchor负责预测)
        '''
        tcls, tbox, indices, anchors = build_targets(p, targets, model)  # targets  得到所有anchor的正样本
    
        h = model.hyp  # hyperparameters
        red = 'mean'  # Loss reduction (sum or mean)
    
        # 初始化交叉熵损失函数:分类损失BCEcls 置信度BCEobj   如果不用Focal loss就用BCEWithLogitsLoss
        # BCEWithLogitsLoss = Sigmoid-BCELoss合成一步  先用对output进行sigmoid再对output和target进行交叉熵
        # pos_weight: 用于设置损失的class权重,用于缓解样本的不均衡问题
        # reduction: 设为"sum"表示对样本进行求损失和;设为"mean"表示对样本进行求损失的平均值;而设为"none"表示对样本逐个求损失,输出与输入的shape一样。
        BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['cls_pw']], device=device), reduction=red)
        BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['obj_pw']], device=device), reduction=red)
    
        # class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
        # 标签平滑  cp: positive label smoothing BCE targets     cn: negative label smoothing BCE targets
        cp, cn = smooth_BCE(eps=0)  # 平滑系数eps=0说明不采用标签平滑  要用通常为0.1
    
        # focal loss  这里暂时没用到 一般是用到分类损失中
        g = h['fl_gamma']  # focal loss gamma  hpy中g默认=0
        if g > 0:
            BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
    
        # 遍历每一个yolo层输出
        nt = 0  # targets
        # layer index(0,1,2), layer predictions [[batch_size, anchor_num, grid, grid, xywh + obj + classes]]
        for i, pi in enumerate(p):  # pi为第i层yolo层输出 [4,3,23,23,25]
            # 获得每一层的所有正样本(三个anchor)信息
            #  b: 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)属于这个batch中的哪一张图片
            #  a: 和tbox一一对应 存放着tbox中对应位置的target是属于哪个anchor(index)的正样本(由哪个anchor负责预测)
            # gj: 和tbox一一对应 存放着tbox中对应位置的target的中心点所在grid_cell的左上角的y坐标
            # gi: 和tbox一一对应 存放着tbox中对应位置的target(第a个anchor的正样本)的中心点所在grid_cell的左上角的x坐标
            b, a, gj, gi = indices[i]
            tobj = torch.zeros_like(pi[..., 0], device=device)  # 所有的target obj [4,3,23,23]
    
            nb = b.shape[0]  # target中的m 所有anchor的正样本数  并不是真实框的个数,应该大于真实框的个数,因为一个真实框可能在多个anchor中预测
            if nb:
                # 对应匹配到正样本的预测信息
                # pi=[batch_size, anchor_num, grid, grid, xywh + obj + classes]
                # ps=[m, xywh + obj + classes] 其中m是正样本的个数 其实对应的就是在target中所有正样本的个数
                # ps其实就是拿到在预测信息pi中的 相对target所有正样本位置 的值 之后方便作损失计算
                ps = pi[b, a, gj, gi]  # prediction subset corresponding to targets  [30,25]
    
                # lbox: 位置损失  GIoU Loss
                pxy = ps[:, :2].sigmoid()
                pwh = ps[:, 2:4].exp().clamp(max=1E3) * anchors[i]
                pbox = torch.cat((pxy, pwh), 1)  # predicted box
                giou = bbox_iou(pbox.t(), tbox[i], x1y1x2y2=False, GIoU=True)  # giou(prediction, target)
                lbox += (1.0 - giou).mean() if red == "mean" else (1.0 - giou).sum()  # giou loss
    
                # obj model.gr=1  置为giou(有物体的地方,置信度为giou)   model.gr在train.py中定义
                # model.gr: giou loss ratio (obj_loss = 1.0 or giou)
                # model.gr=1 obj_loss=giou;  model.gr=0, obj_loss=1
                tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * giou.detach().clamp(0).type(tobj.dtype)  # giou 公式
    
                # lcls: 所有正样本的分类损失
                if model.nc > 1:  # cls loss (only if multiple classes)
                    # cn: negative label(label smooth中的负样本值)  [m, 20] 全是cn
                    t = torch.full_like(ps[:, 5:], cn, device=device)  # targets
                    # nb: m  tcls[i]: 真实框的类别位置  cp: positive label(label smooth中的正样本值)
                    # 真实框的类别对应的位置就置为cp  其他类别位置就置为cn
                    t[range(nb), tcls[i]] = cp
                    # BCE class loss  如果打开focal loss ,这里会调用focal loss
                    lcls += BCEcls(ps[:, 5:], t)
    
                # Append targets to text file
                # with open('targets.txt', 'a') as file:
                #     [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)]
    
            # lobj: 置信度损失(有无物体) lobj是对所有prediction区域计算的 即包括正样本也包括负样本
            #       这样训练才能让负样本处预测值接近0,正样本处预测值接近1
            lobj += BCEobj(pi[..., 4], tobj)  # obj loss
    
        # 乘上每种损失的对应权重
        # 因为目标检测主要的loss来自位置损失,所有给lbox乘一个较小的权重 平衡各个损失,防止损失函数被个别损失掌控
        lbox *= h['giou']   # 3.54
        lobj *= h['obj']    # 102.88
        lcls *= h['cls']    # 9.35
    
        # loss = lbox + lobj + lcls
        return {"box_loss": lbox,
                "obj_loss": lobj,
                "class_loss": lcls}
    

    2.3 简要分析

    为什么build_traget函数会输出indice这个参数你呢?是因为与特征层的输出有关,某一层的预测特征层的输出结果就为:[batch, anchor, girdx, gridy, xywh+obj+cls],那么根据indices所包含的值,ps = pi[b, a, gj, gi]就可以在饥饿对应的匹配到正样本的预测信息。

    现在知道是用预测特征层的哪个点来进行预测了,需要做的就是利用该点的25维预测信息来做一个损失计算,而由于刚刚所输出的target是偏移量,所以这里 ps[:, :2]预测的也是xy的偏移量:pxy = ps[:, :2].sigmoid()。而且 ps[:, 2:4]预测是就是所使用的anchor的相对缩放大小,这里还会有exp函数对缩放系数进行处理来确保是大于0的:pwh = ps[:, 2:4].exp().clamp(max=1E3) * anchors[i]。

    而对于第五个值置信度的预测,这里是针对所有prediction区域计算的 即包括正样本也包括负样本,这样做的目的是希望让负样本处的预测值能接近于0,而正样本出的预测值接近于1,而且这里的置信度用匹配到点的giou值来表示。

    对于分类损失,比较简单这里就不再叙述了,只是代码的实现不一样而已,但思路是一样的。最后,所得到的损失结果边界框进行一个权重和相加就好了。需要注意,由于这里主要的损失是边界框回归损失,其次是分类损失,置信度损失数值比较小,所以在处理上,需要给置信度损失乘以一个相对比较大的数值,二队边界框损失乘以一个相对比较小的数值,这里yolo的设置权重为;{‘giou’: 3.54, ‘cls’: 9.35, ‘cls_pw’: 1.0, ‘obj’: 102.88,…}。

    三个损失值乘以权重前分别为:
    在这里插入图片描述

    三个损失值乘以权重后分别为:
    在这里插入图片描述


    参考资料:

    1. https://blog.csdn.net/qq_38253797/article/details/118046587(博主注释得非常详细,建议看看)
    2. https://www.bilibili.com/video/BV1t54y1C7ra?p=6(或者看b站的视频解析)
    展开全文
  • YOLOv3

    2021-07-16 17:14:37
    YOLOv3:An Incremental Improvement 做大做强,再创辉煌 速度和精度最均衡的目标检测网络。 YOLOv3的模型比之前的模型复杂了不少,可以通过改变模型结构的大小来权衡速度与精度。 YOLOv3在Pascal Titan X上处理...

    YOLOv3:An Incremental Improvement 做大做强,再创辉煌

    速度和精度最均衡的目标检测网络。

    YOLOv3的模型比之前的模型复杂了不少,可以通过改变模型结构的大小来权衡速度与精度。

    YOLOv3在Pascal Titan X上处理608x608图像速度达到20FPS,在 COCO test-dev 上 mAP@0.5 达到 57.9%,与RetinaNet(FocalLoss论文所提出的单阶段网络)的结果相近,并且速度快4倍.
     

    简而言之,YOLOv3 的先验检测(Prior detection)系统将分类器或定位器重新用于执行检测任务。他们将模型应用于图像的多个位置和尺度。而那些评分较高的区域就可以视为检测结果。此外,相对于其它目标检测方法,我们使用了完全不同的方法。我们将一个单神经网络应用于整张图像,该网络将图像划分为不同的区域,因而预测每一块区域的边界框和概率,这些边界框会通过预测的概率加权。我们的模型相比于基于分类器的系统有一些优势。它在测试时会查看整个图像,所以它的预测利用了图像中的全局信息。与需要数千张单一目标图像的 R-CNN 不同,它通过单一网络评估进行预测。这令 YOLOv3 非常快,一般它比 R-CNN 快 1000 倍、比 Fast R-CNN 快 100 倍。

    1、backbone:darknet53 更好的网络结构(类ResNet),精度更好。融合多种先进方法,改进yolov1/v2缺点,且效果更优。

    2、步骤:YOLOv3不过是把一张图片划分成不同的网格,每个网格点负责一个区域的预测,只要物体的中心点落在这个区域,这个物体就由这个网格点来确定。

    3、细节:

    • 将yolov2的darknet-19的模型-------> YOLOv3 的darknet-53(类似于Resnet引入残差结构)
    • 在图片上画出box框需要四个参数:分别是中心点的x轴、y轴坐标、框的高和宽。
    • 借鉴FPN金字塔特征,y1,y2,y3为多尺度,边长:13,26,52,深度:255,主要解决小目标检测。其中,输出通道数为255:3*(4+1+80)=255,3表示一个格子的锚有3个bounding box,4表示框的4个坐标信息,1表示有无物体,80位类别。S*S*B*(5+C)
    • 固定尺寸:416*416,为了防止失真,边缘加灰条。
    • 分类器不在使用softmax,分类损失采用binary cross-entropy loss(二分类交叉熵损失熵)

    4、bbox预测

    YOLOv3不使用softmax对每个框进行分类,主要考虑因素有两个:

    1. softmax使得每个框分配一个类别(score最大的一个),而对于open Images这种数据集,目标可能有重叠的类别标签,因此softmax不适用于多标签分类。
    2. softmax可被独立的logistics分类器替代,且准确率不会下降。

    为何使用sigmoid函数?

    YOLO不预测边界框中心的绝对坐标,它预测的是偏移量,预测的结果通过一个sigmoid函数,迫使输出的值在0~1之间。例如,若对中心的预测是(0.4,0.7),左上角坐标是(6,6),那么中心位于13×13特征地图上的(6.4,6.7)。若预测的x,y坐标大于1,比如(1.2,0.7),则中心位于(7.2,6.7)。注意现在中心位于图像的第7排第8列单元格,这打破了YOLO背后的理论,因为如果假设原区域负责预测某个目标,目标的中心必须位于这个区域中,而不是位于此区域旁边的其他网格里。为解决这个问题,输出是通过一个sigmoid函数传递的,该函数在0到1的范围内缩放输出,有效地将中心保持在预测的网格中。

    BCE 交叉熵

    MSE最小均方误差

    多类别分类multi-class 互斥 softmax  概率和为1

    sigmoid:

    \sigma\left (x_{i} \right ) = \frac{1}{1+e^{x_i}}

     torch.nn.BCEWithLogitsLoss()

    softmax:

    \sigma\left (x_{i} \right ) = \frac{e^{x_i}}{\sum e^{x_i}}

     torch.nn.CrossEntroploss()

    torch.nn.BCELoss()、torch.nn.BCEWithLogitsLoss() 用于二分类问题, torch.nn.CrossEntropyLoss() 既可用于二分类又可用于多分类

    预测推理阶段时,根据 tx,ty,tw,th这4个offsets,和 cx,cy,cw,ch(是已知的)及上图的回归公式倒推出最终得到的边框坐标值是 bx,by,bw,bh,即边界框bboxes相对于feature maps的位置和大小,是我们需要的预测输出坐标。最后再乘以stride再乘以原始图像的宽高就得到原图上的预测框。

    其实图像在输入之前是按照图像的长边缩放为416,短边根据比例缩放(图像不会变形扭曲),然后再对短边的两侧填充至416,这样就保证了输入图像是416*416的。

    注意点:loss计算时 anchor box与ground truth的匹配。

    ​ 为啥需要匹配呢?你是监督学习,那得知道网络预测的结果是啥呀?这样才能逼近真实的label,反过来就是我现在让他们匹配,给他分配好label,后面就让网络一直这样学习,最后就是以假乱真了,输出的结果无线接近正确结果了。​yolov3的输出prediction的shape为(num_samples, self.num_anchors*(self.num_classes + 5), grid_size, grid_size),为了计算loss,转换为(num_samples, self.num_anchors, grid_size, grid_size, 5+self.num_classes ), 其中self.num_anchors为3, 总共这么多boxes,哪些框可能有目标呢,而且一个cell对应有三个anchor boxes,究竟选择哪个anchor去匹配ground truth?

    将每个锚框(anchor boxes)视为一个训练样本,需要标记每个anchor box的标签,即类别标签和偏移量。所以我们只需要考虑有目标的anchor boxes,哪些有目标呢?ground truth的中心落在哪个cell,那对应这三个anchor boxes就有,所以计算ground truth与anchor boxeses的IOU(bbox_wh_iou(计算Gw,Gh与Pw,Ph的IOU)),其实只需要选取重叠度最高的anchor box就行,再将三个anchores通过torch.stack后max(0)下就知道选择三个中的哪个了,将这种方式匹配到的boxes视为有目标的box。

    YOLOv3在mAP@0.5及小目标APs上具有不错的结果,但随着IOU的增大,性能下降,说明YOLOv3不能很好地与ground truth切合。

    图为带有维度先验和定位预测的边界框,我们边界框的宽和高作为离聚类中心的位移,并使用sigmoid函数预测边界框相对于滤波器应用位置的中心坐标。 仍采用之前的logis,其中cx,cy是网格的坐标偏移量,pw,ph是预设的anchor box的边长.最终得到的边框坐标值是b*,而网络学习目标是t*,用sigmod函数、指数转换。

    第4行说明:loss分3部分组成:
    第1行代表geo_loss,S代表13,26,52,就是grid是几乘几的。B=5。
    第2行代表confidence_loss,和YOLO v2一模一样。
    第3行代表class_loss,和YOLO v2的区别是改成了交叉熵。

    5、总结:

    YOLOv3改进了小物体检测

    YOLOv4是在v3基础上的守正出新。

    网络结构图

    绘制网络结构图受到Yolov3另一位作者文章的启发,包括下面Yolov4的结构图,确实,从总体框架上先了解了Yolov3的流程。再针对去学习每一小块的知识点,会事半功倍。

    上图三个蓝色方框内表示Yolov3的三个基本组件

    1. CBL:Yolov3网络结构中的最小组件,由Conv+Bn+Leaky_relu激活函数三者组成。
    2. Res unit:借鉴Resnet网络中的残差结构,让网络可以构建的更深。
    3. ResX:由一个CBLX个残差组件构成,是Yolov3中的大组件。每个Res模块前面的CBL都起到下采样的作用,因此经过5次Res模块后,得到的特征图是608->304->152->76->38->19大小

    其他基础操作:

    1. Concat:张量拼接,会扩充两个张量的维度,例如26*26*256和26*26*512两个张量拼接,结果是26*26*768。Concat和cfg文件中的route功能一样。
    2. add:张量相加,张量直接相加,不会扩充维度,例如104*104*128和104*104*128相加,结果还是104*104*128。add和cfg文件中的shortcut功能一样。

    Backbone中卷积层的数量:

    每个ResX中包含1+2*X个卷积层,因此整个主干网络Backbone中一共包含1+(1+2*1)+(1+2*2)+(1+2*8)+(1+2*8)+(1+2*4)=52,再加上一个FC全连接层,即可以组成一个Darknet53分类网络。不过在目标检测Yolov3中,去掉FC层,不过为了方便称呼,仍然把Yolov3的主干网络叫做Darknet53结构


    yolov3-spp  和yolov4 map相差不大

    YOLOv1 2016 CVPR  45FPS 448*448  63.4mAP 

    YOLOv3输入尺寸问题 [参考](https://blog.csdn.net/pangxing6491/article/details/109822428)

    1、python代码

    https://github.com/ultralytics/yolov3

    2、C++代码

    https://github.com/AlexeyAB/darknet

    3、 python版本的Tensorrt代码

    除了算法研究外,实际项目中还需要将算法落地部署到工程上使用,比如GPU服务器使用时还需要对模型进行tensorrt加速。

    (1)Tensort中的加速案例

    强烈推荐tensort软件中,自带的Yolov3加速案例,路径位于tensorrt解压文件夹的TensortX/samples/python/Yolov3_onnx中

    针对案例中的代码,如果有不明白的,也可参照下方文章上的详细说明:

    代码地址:https://www.cnblogs.com/shouhuxianjian/p/10550262.html

    (2)Github上的tensorrt加速

    除了tensorrt软件中的代码, github上也有其他作者的开源代码

    代码地址:https://github.com/lewes6369/TensorRT-Yolov3

    4、 C++版本的Tensorrt代码

    项目的工程部署上,如果使用C++版本进行Tensorrt加速,一方面可以参照Alexey的github代码,另一方面也可以参照下面其他作者的开源代码

    代码地址:https://github.com/wang-xinyu/tensorrtx/tree/master/Yolov3

    参考

    睿智的目标检测26——Pytorch搭建yolo3目标检测平台_Bubbliiiing的学习小课堂-CSDN博客_睿智的目标检测26

    你一定从未看过如此通俗易懂的YOLO系列(从v1到v5)模型解读 (中) - 知乎

    3.1 YOLO系列理论合集(YOLOv1~v3)_哔哩哔哩_bilibili

    深入浅出Yolo系列之Yolov3&Yolov4&Yolov5&Yolox核心基础知识完整讲解 - 知乎

    展开全文
  • 因为工作原因,项目中经常遇到目标检测的任务,因此对目标检测算法会经常使用和关注,比如Yolov3、Yolov4算法。 当然,实际项目中很多的第一步,也都是先进行目标检测任务,比如人脸识别、多目标追踪、REID、客流...
  • yolov3详解

    千次阅读 2021-11-21 18:00:59
    title: yolov3算法理解 date: 2021-11-21 14:45:42 tags: toc: True 本贴记录自己学习完yolov3算法的后的个人理解 首先我们来了解一下yolov3算法物体检测的具体流程: 前向传播: 对数据集图片进行处理,...
  • 找预测图片的宽高 代码: for path, im, im0s, vid_cap, s in dataset: print("test!!!",s)# s: image 2/2 /root/AI-lab/yolov5-master/data/test/022_000022.jpg: # 这里打印了s字符串,可以看出是很长的一串 ...
  • YoloV3的整体框架介绍

    2022-05-13 14:51:19
    YOLOV3的三目标检测算法。假设我们眼里看到是这样一幅图,这是一只猫,你们一眼就可以知道这只猫在什么位置。但是计算机要怎么确定这个猫的位置呢?计算机在图片上画出这个框需要四个参数,分别是中心点的 X 轴、外...
  • Yolov3/Yolov4原理对比改进创新

    千次阅读 2021-02-27 22:15:46
    YoLoV3原理详解 Yolo的整个网络,吸取了Resnet、Densenet、FPN的精髓,可以说是融合了目标检测当前业界最有效的全部技巧。 一、backbone主网络 1、升级为Darknet-53 yolov3的backbone部分由Yolov2时期的Darknet-19...
  • Yolov3细节解释

    2020-01-08 14:28:54
    Yolov3日常使用比较多,因此对其中的一些细节进行了分析,简单记录,未完待续。 1. 卷积中的pad darknet在设置pad参数时和caffe不同,caffe中设置的pad值就是实际计算的值,而darknet中设置的pad只是使能padding的...
  • 首先,采用K-means算法对数据进行聚类分析,结合聚类结果和交通灯标签的统计结果,确定先验框的宽高比及其数量。然后,根据交通灯尺寸特点,精简网络结构,分别将8倍降采样信息、16倍降采样信息与高层语义信息进行融合,在...
  • YOLOv3论文

    千次阅读 2022-03-04 09:23:10
    【精读AI论文】YOLO V3目标检测(附YOLOV3代码复现)_哔哩哔哩_bilibili AP50的意思就是0.5IOU为阈值的mAP 是sigmoid函数,把坐标限定在一个grid cell内。 yolo的创新其实就在于把目标检测任务变成回归任务,用...
  • 文章目录1 总体介绍2 YOLOv3主干网络3 FPN特征融合4 利用Yolo Head获得预测结果5 不同尺度的先验框anchor box5.1 理论介绍5.2 代码读取6 YOLOv3整体网络结构代码理解7 感谢链接 1 总体介绍 YOLOv3网络主要包括两部分...
  • YOLOv3总结

    千次阅读 2022-02-23 23:49:42
    YOLOv3 的架构总结和损失函数理解以及正负样本分配
  • YOLOv3论文精读

    千次阅读 2021-11-15 19:44:12
    对于320*320分辨率的图像,YOLOv3对每一张图片运行只需要22ms,并且保持着28.2的mAP,这和SSD算法一样准确但是速度快了三倍。当我们使用这个旧的0.5为IoU阈值时的mAP检测指标时,YOLOv3是很好的。在Titan X显卡上...
  • YOLO系列:YOLOv1,YOLOv2,YOLOv3,YOLOv4,YOLOv5简介

    万次阅读 多人点赞 2020-09-21 09:09:36
    YOLO系列:YOLOv1,YOLOv2,YOLOv3,YOLOv4,YOLOv5简介YOLO系列是基于深度学习的回归方法。RCNN, Fast-RCNN,Faster-RCNN是基于深度学习的分类方法。YOLO官网:https://github.com/pjreddie/darknet​github.comYOLO v...
  • yolov3

    2021-01-29 17:03:54
    YOLOV3
  • YOLOv3 Loss】YOLOv3损失函数详解

    千次阅读 2022-04-22 13:22:25
    文章目录1 自己写类 要注意什么2 计算loss所需参数2.1 pred是什么2.2 target是什么3 loss的计算过程4 代码理解5 感谢链接 1 自己写类 要注意什么 Pytorch中,调用自己写的类,forward函数为什么可以直接被调用?比如...
  • YOLOV3介绍

    千次阅读 2019-09-26 16:32:31
    最近的发展方向把建议框去掉(free anchor) 基本原理 细节 数据 算出缩放后中心点,宽高 流程 首先通过特征提取网络对输入的图像提取特征,得到一定size的feature map,eg:13*13 遍历特征图中的每一个格子,判断...
  • 本篇是对目标检测网络YOLOv3的模型分析。
  • 使用yolov3进行目标检测

    千次阅读 2021-04-19 22:40:42
    但yolo系列的模型无疑是不能忽略的one-stage模型,尤其是以darknet53为backbone的yolov3将整体模型的效果提升到了一个新的水平。本文将介绍如何使用yolov3的pre-trained模型来对自己的数据进行训练。yolov3的源码请...
  • YOLO系列-YOLOv2与YOLOv3

    2019-12-27 09:18:57
    三、YOLOv2 为提高物体定位精准性和召回率,YOLO作者提出了 《YOLO9000: Better, Faster, Stronger》。相比v1提高了训练图像的分辨率;引入了Faster RCNN中anchor box的思想,对网络结构的设计进行了改进...
  • YOLOV4与YOLOV3的区别

    千次阅读 多人点赞 2021-01-29 11:02:28
    YOLOV4与YOLOV3的区别 20年的11月份的时候,正式开始接触v4。时间过去了小半年了,最近突然忘记了v4里面的所谓那些tricks,所以特地做一篇blog(怕过段时间又忘记了~)。 首先,先大概的说下二者之间的差别: 1.特征...
  • 前三篇博客我们从三个方向过了一遍yolov3框架结构,最后这篇来总结一下yolo的亮点和不足。以下就木有配图了,有兴趣的大家耐心过一下。首先聊聊它出彩的地方。1、yolo系列最让人激动的形式在于you only look once,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,264
精华内容 2,105
关键字:

yolov3宽高