detectron2(目标检测框架)无死角玩转-10:源码详解(6)-anchor的使用,loss计算

以下链接是个人关于detectron2(目标检测框架),所有见解,如有错误欢迎大家指出,我会第一时间纠正。有兴趣的朋友可以加微信:17575010159 相互讨论技术。若是帮助到了你什么,一定要记得点赞!因为这是对我最大的鼓励。 文末附带 \color{blue}{文末附带} 文末附带 公众号 − \color{blue}{公众号 -} 公众号 海量资源。 \color{blue}{ 海量资源}。 海量资源

detectron2(目标检测框架)无死角玩转-00:目录

前言

通过上一篇的博客,我们已经知道anchor是如何生成的了,这里再提一下,每个特征特的每个网格,都会对应生成多个anchor。下面我们看看这些anchor是如何使用的,或者说它有什么作用。

我们先进入detectron2\modeling\meta_arch\retinanet.py文件,找类class RetinaNet(nn.Module),然后找到def forward(self, batched_inputs):函数的如下内容:

        # 生成anchor
        anchors = self.anchor_generator(features)

        # 如果是训练,则结合 ground_truth 计算loss
        if self.training:
            gt_classes, gt_anchors_reg_deltas = self.get_ground_truth(anchors, gt_instances)
            return self.losses(gt_classes, gt_anchors_reg_deltas, box_cls, box_delta)

        # 如果是预测,则返回预测的结果
        else:
            results = self.inference(box_cls, box_delta, anchors, images.image_sizes)
            processed_results = []
            for results_per_image, input_per_image, image_size in zip(
                results, batched_inputs, images.image_sizes
            ):
                height = input_per_image.get("height", image_size[0])
                width = input_per_image.get("width", image_size[1])
                r = detector_postprocess(results_per_image, height, width)
                processed_results.append({"instances": r})
            return processed_results

可以看到无论是训练过程,还是预测过程,都使用到了生成的anchor。那么我们就先深入了解一下训练的anchor。

训练过程

从上面可以知道,训练过程只执行了如下代码:

        # 如果是训练,则结合 ground_truth 计算loss
        if self.training:
            gt_classes, gt_anchors_reg_deltas = self.get_ground_truth(anchors, gt_instances)
            return self.losses(gt_classes, gt_anchors_reg_deltas, box_cls, box_delta)

可以明显的知道,核心要点为self.get_ground_truth(anchors, gt_instances)与self.losses(gt_classes, gt_anchors_reg_deltas, box_cls, box_delta) 两个函数,先来看看self.get_ground_truth(),从函数的名字可以知道,该函数的主要作用是根据anchors,结合 gt_instances,去获得训练样本对应的ground truth。

在分析函数之前,我们先来回忆一下gt_instances是什么东西,本人做一份截图如下:
detectron2(目标检测框架)无死角玩转-10:源码详解(6)-anchor的使用,loss计算_第1张图片
从图示,我们可以看出,每个gt_instance包含了一张图片的大小尺寸,以及目标物体的box,和每个物体的类别。要注意的是,这些图片的大小尺寸,以及box都已经进行了预处理。并非最开始原图的boxs。那么下面我们就分析def get_ground_truth(self, anchors, targets)函数吧。

    def get_ground_truth(self, anchors, targets):
        """
		为了代码简洁好看,本人没有粘贴英文注释了,有兴趣的朋友可以在源码中查看
        """
        gt_classes = []
        gt_anchors_deltas = []
        anchors = [Boxes.cat(anchors_i) for anchors_i in anchors]
        # list[Tensor(R, 4)], one for each image

        # 循环处理每张图片,获得每张图片对应的gt_classe以及gt_anchors_delta
        for anchors_per_image, targets_per_image in zip(anchors, targets):
            # 用每张图片的gt_boxes的,和所有anchor进行匹配,计算出对应的iou值。
            # 如targets_per_image.gt_boxes包含了N个box,anchors_per_image包含了M个anchor
            # 那么得到 match_quality_matrix 的形状为[N,M],存储为匹配的IOU值
            match_quality_matrix = pairwise_iou(targets_per_image.gt_boxes, anchors_per_image)

            # 通过阈值进行筛选,把低于阈值的去除掉,并且以下标所以和对应的anchor进行表示
            gt_matched_idxs, anchor_labels = self.matcher(match_quality_matrix)

            has_gt = len(targets_per_image) > 0

            # 如果 has_gt>0 说明存在ground truth
            if has_gt:
                # ground truth box regression
                # 通过索引获得anchor对应的匹配到的gt_boxes
                matched_gt_boxes = targets_per_image.gt_boxes[gt_matched_idxs]

                # 根据gt_boxes 以及 anchor 计算他们的偏移值,同时这个偏移值就是网络需要学习的东西
                gt_anchors_reg_deltas_i = self.box2box_transform.get_deltas(
                    anchors_per_image.tensor, matched_gt_boxes.tensor
                )

                # 对每个匹配到的anchor 与 gt_boxes 进行类别标记
                gt_classes_i = targets_per_image.gt_classes[gt_matched_idxs]

                # Anchors with label 0 are treated as background.
                gt_classes_i[anchor_labels == 0] = self.num_classes

                # Anchors with label -1 are ignored.
                gt_classes_i[anchor_labels == -1] = -1

            # 如果 has_gt<=0 说明不存在ground truth,即图片中没有需要检测的目标,简单的设置成0值即可
            else:
                gt_classes_i = torch.zeros_like(gt_matched_idxs) + self.num_classes
                gt_anchors_reg_deltas_i = torch.zeros_like(anchors_per_image.tensor)

            # 把每张图片计算出来的gt_classes_i 以及 gt_anchors_reg_deltas_i 添加到一个batch之中
            gt_classes.append(gt_classes_i)
            gt_anchors_deltas.append(gt_anchors_reg_deltas_i)

        return torch.stack(gt_classes), torch.stack(gt_anchors_deltas)

总得来说,get_ground_truth()这个函数主要根据anchors 以及 targets,返回了box相对anchor的偏移值,以及其对应的类别。我们依旧回到调用get_ground_truth()函数的部分如下:

        # 如果是训练,则结合 ground_truth 计算loss
        if self.training:
            gt_classes, gt_anchors_reg_deltas = self.get_ground_truth(anchors, gt_instances)
            return self.losses(gt_classes, gt_anchors_reg_deltas, box_cls, box_delta)

现在我们来看看其是如何根据gt_anchors_reg_deltas 去计算loss的。self.losses实现过程如下:

......

是的你没有看错,这里我就做解释了,因为过程很简单的。loss主要分为两个部分:
1.物体分类损失
2.box回归loss
具体的细节,有兴趣的朋友可以自己去分析一下。下面我们来看看预测过程又是怎样的。

预测过程

首先,我们回到之前的代码如下:

        # 如果是预测,则返回预测的结果
        else:
            # anchors包含了整个batch的anchor,image_sizes包含了整个batch(原图)的大小
            # 得到每张图片包含物体位置的结果
            results = self.inference(box_cls, box_delta, anchors, images.image_sizes)
            processed_results = []
            
            #对结果进行一些简单的处理,如分离出图片结果的height,width,instances
            for results_per_image, input_per_image, image_size in zip(
                results, batched_inputs, images.image_sizes
            ):
                height = input_per_image.get("height", image_size[0])
                width = input_per_image.get("width", image_size[1])
                r = detector_postprocess(results_per_image, height, width)
                processed_results.append({"instances": r})
            return processed_results

可以看到,其核心函数在于

            # 得到每张图片包含物体位置的结果
            results = self.inference(box_cls, box_delta, anchors, images.image_sizes)

的实现,实现过程如下:

    def inference(self, box_cls, box_delta, anchors, image_sizes):
        """
        Arguments:
            box_cls, box_delta: Same as the output of :meth:`RetinaNetHead.forward`
            anchors (list[list[Boxes]]): a list of #images elements. Each is a
                list of #feature level Boxes. The Boxes contain anchors of this
                image on the specific feature level.
            image_sizes (List[torch.Size]): the input image sizes

        Returns:
            results (List[Instances]): a list of #images elements.
        """
        # 验证其是否都为一个batch_size大小
        assert len(anchors) == len(image_sizes)
        results = []

        # 改变形状为[N, HxWxA, K]
        box_cls = [permute_to_N_HWA_K(x, self.num_classes) for x in box_cls]
        # 改变形状为[N, HxWxA, K]
        box_delta = [permute_to_N_HWA_K(x, 4) for x in box_delta]
        # list[Tensor], one per level, each has shape (N, Hi x Wi x A, K or 4)

        # 循环对每张图片的anchor进行处理
        for img_idx, anchors_per_image in enumerate(anchors):
            # 得到该张处理图像的大小
            image_size = image_sizes[img_idx]

            # 改变box_cls,以及box_delta的形状
            box_cls_per_image = [box_cls_per_level[img_idx] for box_cls_per_level in box_cls]
            box_reg_per_image = [box_reg_per_level[img_idx] for box_reg_per_level in box_delta]

            # 根基box_delta,集合图像大小以及anchor推断出目标box的位置(为最终结果)
            results_per_image = self.inference_single_image(
                box_cls_per_image, box_reg_per_image, anchors_per_image, tuple(image_size)
            )
            results.append(results_per_image)
        return results

其实预测的过程也是很简单的。首先根据网络的前向传播得到box_delta。有了box_delta之后,再结合anchor,以及原图的大小,就能计算出目标物体在原图中的坐标了。

到这里,可以说,把detectron2算是分析完成了。或许还有更加精彩的内容,敬请期待。

在这里插入图片描述

你可能感兴趣的:(detectron2,目标检测,pytorch,深度学习,神经网络)