YOLO的Grid Cells机制 VS. Faster RCNN的Anchor Boxes机制

转载:https://blog.csdn.net/zbgjhy88/article/details/86841525

转载:https://zhuanlan.zhihu.com/p/392976529

Grid Cells机制
虽然YOLO中设置了两个Predictor(这里记为Predictor A和Predictor B),但是YOLO并没有让一个Grid Cell去预测多个Object,它的机制是通过计算Grid Cell与不同Object的IOU,让这个Grid Cell去负责IOU最大的哪个Object,也就是说两个Predictor都去预测这个Object。
就以下图为例(请暂时忽略其中的两个紫色框,他们并非“车”与“人”的Ground Truth Box),“车”与“人”的中心都位于8号Grid Cell(图中的黑点,本例中二者重合)。

这时候需要分别计算8号Grid Cell与“车”Ground Truth Box的IOU和8号Grid Cell与“人”Ground Truth Box的IOU,如果8号Grid Cell与“车”Ground Truth Box的IOU > 8号Grid Cell与“人”Ground Truth Box的IOU,那么8号Grid Cell就负责预测“车”这个Object,Predictor A和Predictor B都不会再去管“人”这个Object,而是专注于将Predict Box回归到“车”的Ground Truth Box上。

Predict Box机制
在YOLOv1的文章中,作者提到了YOLO的一个缺陷,正是其无法对中心位于同一个Grid Cell上的多个Object进行预测。究其原因,正是因为Grid Cell机制舍弃了对多个Object的中心位于同一个Grid Cell上这种情况的考虑。因此,有些人提出了这样的想法,姑且称之为Predict Box机制:不计算Grid Cell与不同Object的IOU,而是分别计算两个Predict Box与两个Ground Truth Box的IOU,假设有结果:Predictor A的Predict Box与“车”Ground Truth Box的IOU > Predictor B的Predict Box与“车”Ground Truth Box的IOU;Predictor B的Predict Box与“人”Ground Truth Box的IOU > Predictor A的Predict Box与“人”Ground Truth Box的IOU,那么就由Predictor A来负责预测“车”,而由Predictor B来负责预测“人”。不能排除对不同Object的IOU,Predictor A都要大于Predictor B的可能性,这时候无法做出完美的决策;如果假设Predictor A的Predict Box与“车”Ground Truth Box的IOU > Predictor A的Predict Box与“人”Ground Truth Box的IOU,那么就使Predictor A预测“车”,Predictor B预测“人”。
乍看之下可能感觉Predict Box机制还不错,可以解决原有问题。可是仔细分析一下,就能发现问题了。
首先,由于Predict Box都是随机初始化的,其与Grid Cell并没有相关性,何谈用其来指定Predictor到底应该用于回归中心落于这个Grid Cell上的哪个Object呢?举个例子,Predictor A的初始化Predict Box可能是在1号Grid Cell,Predictor B的初始化Predict Box可能是在2号Grid Cell,用其来确定应该回归中心落于8号Grid Cell上的哪个Ground Truth Box显然是不妥的。
其次,从结果来看,这样得到的结果似乎与直接指定Predictor A预测Ground Truth Box A,Predictor B预测Ground Truth Box B没有差别,因为大家都是随机的。
或许有人想每一步都重新计算相应的IOU,然后调整更新方向。这个也显然是不合适的,一方面,重复计算正负样本,这是对计算资源的浪费;另一方面,前面Predictor A还在朝着“车”的Ground Truth Box优化loss function,后面如果又要求其朝着“人”的Ground Truth Box优化loss function,不断调整更新的方向极易造成收敛问题,至少也有延长了收敛时间的问题。

Anchor Boxes机制
我们回到图n的例子:现在我们知道,这两个紫色的框就是两个预设的Anchor Boxes,如果计算IOU,横的Anchor Box与“车”Ground Truth Box的IOU大于其与“人”Ground Truth Box的IOU,因此横的Anchor Box将用于预测车;竖的Anchor Box与“人”Ground Truth Box的IOU大于其与“车”Ground Truth Box的IOU,因此竖的Anchor Box将用于预测人。

小结
YOLO的Grid Cell与Faster RCNN的Anchor Box实际上作用都是相同的,其作用都是通过计算其与一个或者多个Object的Ground Truth Box的IOU,来确定相应的正负样本。

YOLOv5中的anchor Boxes机制

anchor是一种先验框,就是用先验知识所描绘的框,可以用聚类等无监督学习的方法求取,聚类求取的代码会放在最下面。

先说一下我对anchor的理解,在我眼里 anchors 就像是一类语义的集合,每个anchor都是对在无监督学习下的某一簇(类)的代表,如果数据的样本分布接近总样本分布的话,那么这个anchor就是对样本的先验知识了。先验知识对机器学习的作用是巨大的,作者认为,目前深度学习上的对模型结构的改造,或者是各种trick,本质上都是融入了样本的先验知识,例如CNN对图像分类的效果明显优于一个简单的神经网络,是因为卷积核学习可以很好的图像的边缘等知识,很多个卷积核又能加强总体的语义知识,而作者认为正是这种卷积核又相当于添加先验知识(边缘知识等)的作用,这样就能更好的分别出到底处于哪种模式了。

我们再来看yolov5中的anchor,是由wh宽高组成,用的是原图的像素尺寸,表示数据样本中样本的大概形状,有三种大小的anchor,而featureMap又分成了3个不同尺寸的通道,总共有9个不一样的anchor。在分类前的最后一层featureMap中,每一个像素都有3个anchor负责识别,每个anchor负责一类的识别,通常这3个anchor都会被训练成识别同一类,所以实在不得不考虑一下只用一个anchor行不行。 使用anchor可以认为模型一开始就记住了数据样本的形状了,这样就能加快正负样本的学习,比如你的相机是640 * 10的像素(比如),然后你用来拍一根长木棍和一台电视机,相机的拍摄尺寸正好能把木棍完全拍进去,而不能把一台电视机完全拍进去,除非隔得非常远,这样我们能够识别我们拍出来的木棍,但是拍出来的电视机因为只拍了一部分,所以识别错误的几率就大大增加了。所以现在明白了anchor的作用了吧。

Yolov5中对xywh的学习都是经过处理的,为的是加速收敛,学习得更充分,我们来看看yolov5中的代码:

y = x[i].sigmoid()
y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xy
y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh
z.append(y.view(bs, -1, self.no))

其中x[i]是模型的输出就是预测出来的值,处理如下,其中 anchor_grid是缩放系数默认为1.

tx = sigmoid(gtx) * 2 - 0.5

ty = sigmoid(gty) * 2 - 0.5

tw = (sigmoid(gtw) * 2) ** 2

th = (sigmoid(gth) * 2) ** 2

gtx:ground truth x, tx:transfer x,通过了这种转换,

让模型输出的xy控制在[-0.5, 1.5], wh控制在[0, 4],xy变成了相对于anchor中心点的偏移量,wh变成了相对于anchor的wh的缩放系数,通过学习这样的xywh来学习bbox,这就是在yolov5中anchor的真实面目,包括训练推理预测的anchor都是以这格式进行传输。

而预测出来的xywh也是要通过上面式子的反式子推出来就是预测的bbox了。

Anchor让模型用了一个形状分类器。通过目标检测中的anchor这一功能,让我感受到了算法重在设计。

更好的数据,对数据结构更好的设计,才能让算法的功能更好地实现。

用聚类求anchor的代码如下,其中使用labelImg标注出的xml文件,其实就是得出样本的宽和高,如果用yolo的标签,那么直接用其已经归一化的wh进行聚类就好了。

# -*- coding: utf-8 -*-得出
# 根据标签文件求先验框
import os
import numpy as np
import xml.etree.cElementTree as et
from kmeans import kmeans, avg_iou

FILE_ROOT = "../rubbish_data/"     # 根路径
ANNOTATION_ROOT = "Annotations"  # 数据集标签文件夹路径
ANNOTATION_PATH = FILE_ROOT + ANNOTATION_ROOT

ANCHORS_TXT_PATH = "../yolov5-master/data/boxbag-anchors.txt"  # 存放结果路径

CLUSTERS = 9  # yolov5中为 9
ImageSize = 640  # 要训练的图像大小
CLASS_NAMES = ['Plastic box', 'Plastic bag']  # 自己的类别


def load_data(anno_dir, class_names):
    xml_names = os.listdir(anno_dir)
    boxes = []
    for xml_name in xml_names:
        xml_pth = os.path.join(anno_dir, xml_name)
        tree = et.parse(xml_pth)

        width = float(tree.findtext("./size/width"))
        height = float(tree.findtext("./size/height"))

        for obj in tree.findall("./object"):
            cls_name = obj.findtext("name")
            if cls_name in class_names:
                xmin = float(obj.findtext("bndbox/xmin")) / width
                ymin = float(obj.findtext("bndbox/ymin")) / height
                xmax = float(obj.findtext("bndbox/xmax")) / width
                ymax = float(obj.findtext("bndbox/ymax")) / height

                box = [xmax - xmin, ymax - ymin]
                print(box)
                boxes.append(box)
            else:
                continue
    return np.array(boxes)


if __name__ == '__main__':

    anchors_txt = open(ANCHORS_TXT_PATH, "w")

    train_boxes = load_data(ANNOTATION_PATH, CLASS_NAMES)
    count = 1
    best_accuracy = 0
    best_anchors = []
    best_ratios = []

    for i in range(10):      # 可以修改,不要太大,否则时间很长
        anchors_tmp = []
        clusters = kmeans(train_boxes, k=CLUSTERS)
        idx = clusters[:, 0].argsort()
        clusters = clusters[idx]

        for j in range(CLUSTERS):
            anchor = [round(clusters[j][0] * ImageSize, 2), round(clusters[j][1] * ImageSize, 2)]
            anchors_tmp.append(anchor)
            print(f"Anchors:{anchor}")

        temp_accuracy = avg_iou(train_boxes, clusters) * 100
        print("Train_Accuracy:{:.2f}%".format(temp_accuracy))

        ratios = np.around(clusters[:, 0] / clusters[:, 1], decimals=2).tolist()
        ratios.sort()
        print("Ratios:{}".format(ratios))
        print(20 * "*" + " {} ".format(count) + 20 * "*")

        count += 1

        if temp_accuracy > best_accuracy:
            best_accuracy = temp_accuracy
            best_anchors = anchors_tmp
            best_ratios = ratios

    anchors_txt.write("Best Accuracy = " + str(round(best_accuracy, 2)) + '%' + "\r\n")
    anchors_txt.write("Best Anchors = " + str(best_anchors) + "\r\n")
    anchors_txt.write("Best Ratios = " + str(best_ratios))
    anchors_txt.close()

 

posted on 2022-03-28 17:16  Sanny.Liu-CV&&ML  阅读(379)  评论(0编辑  收藏  举报

导航