【目标检测】Faster RCNN代码实现——(2)

一、项目总览

在这里插入图片描述

二、model文件夹

在这里插入图片描述

2.1 faster_rcnn.py

  本py文件用于构建Faster RCNN模型的基类,子类重写该类中的方法或新增方法,来定义想要的模型。
在这里插入图片描述

from __future__ import  absolute_import
from __future__ import division
import torch as t
import numpy as np
from utils import array_tool as at
from model.utils.bbox_tools import loc2bbox
from torchvision.ops import nms
from torch import nn
from data.dataset import preprocess
from torch.nn import functional as F
from utils.config import opt
'''本py文件为Faster RCNN模型的基类,其他子类对该类的方法进行重写'''
def nograd(f):
    def new_f(*args,**kwargs):
        with t.no_grad():
           return f(*args,**kwargs)
    return new_f
'''Faster RCNN类:所有类(Faster R-CNNvgg16)的基类。一些基础属性和方法的定义,子类重写这些方法'''
class FasterRCNN(nn.Module):
    """
    3个网络阶段构成了Faster R-CNN.
        1.Feature extraction: 输入图像提取共享的特征图
        2.Region Proposal Networks: 使用共享特征图,产生目标的ROI
        3.Localization and Classification Heads:使用共享特征图及提案ROI进行目标的分类和位置精修
    Args:
        extractor (nn.Module): 一个获取BCHW图像数组并返回特征图的模块。
        rpn (nn.Module): 具有与以下接口相同的模块`model.region_proposal_network.RegionProposalNetwork`
        head (nn.Module): 一个采用BCHW变量,RoI和RoI的批次索引的模块。 这将返回与类相关的位置和类分数。
        loc_normalize_mean (tuple of four floats):位置估计值的平均值。
        loc_normalize_std (tupler of four floats): 位置预测值的标准差。
    """
    '''__init__()函数:初始化函数'''
    def __init__(self, extractor, rpn, head,loc_normalize_mean = (0., 0., 0., 0.),loc_normalize_std = (0.1, 0.1, 0.2, 0.2)):

        super(FasterRCNN, self).__init__()
        '''(1)特征提取器'''
        self.extractor = extractor
        '''(2)RPN网络'''
        self.rpn = rpn
        '''(3)头部网络'''
        self.head = head

        '''4.位置预测的均值和标准差设定'''
        self.loc_normalize_mean = loc_normalize_mean#loc_normalize_mean = (0., 0., 0., 0.)
        self.loc_normalize_std = loc_normalize_std#loc_normalize_std = (0.1, 0.1, 0.2, 0.2)

        '''5.nms_thresh、score_thresh阈值的设定'''
        self.use_preset('evaluate')

    '''n_class()函数:总类别的数量(包含背景类)'''
    @property
    def n_class(self):
        return self.head.n_class
    '''forward()函数:FasterRCNN的前向传播,输入为调整后的图像,返回roi_cls_locs(头部预测), roi_scores(头部预测), rois(RPN生成), roi_indices(RPN生成)'''
    def forward(self, x, scale=1.):
        """
        `scale`:RPN使用scale来确定选择小目标的阈值,这无论他们的置信度如何,都会被拒绝
        `N`:批量的大小  Classes=背景+`L`类
        Args:
            x (autograd.Variable): 4D 图像变量
            scale (float): 在预处理期间应用于原始图像的缩放比例。
        Returns:
            返回下面列出的四个值的元组。
             roi_cls_locs:候选ROI的偏移和缩放 。形状`(R', (L + 1) \\times 4)`. `R':批量图片产生的ROI的总数。R_i第i个图片产生的ROI
             roi_scores: 候选ROI的类预测 shape `(R', L + 1)`.`L` 除去背景类的类别数量
             rois: RoIs proposed by RPN.  shape (R', 4)`.
             roi_indices : 批量图片产生的ROI的索引 shape`(R',)`.
        """
        img_size = x.shape[2:]#图像的大小H, W。x=(C, H, W)

        '''(1)提取x的特征图:x为调整后的图像'''
        h = self.extractor(x)

        '''(2)RPN网络生成ROI'''
        rpn_locs, rpn_scores, rois, roi_indices, anchor = self.rpn(h, img_size, scale)#输入:调整后图像的特征图、调整后图像尺寸、图像的调整尺度

        '''(3)头部,生成 roi_cls_locs, roi_scores'''
        roi_cls_locs, roi_scores = self.head(h, rois, roi_indices)#输入:调整后图像的特征图、roi坐标 (x,y,x,y)、roi的索引

        return roi_cls_locs, roi_scores, rois, roi_indices

    '''use_preset()函数:根据'visualize', 'evaluate',设置nms_thresh及score_thresh值'''
    def use_preset(self, preset):
        """Args: preset: ({'visualize', 'evaluate'): """
        if preset == 'visualize':
            self.nms_thresh = 0.3
            self.score_thresh = 0.7
        elif preset == 'evaluate':
            self.nms_thresh = 0.3
            self.score_thresh = 0.05
        else:
            raise ValueError('preset must be visualize or evaluate')

    '''_suppress()函数:对网络head的预测结果,进行非极大值抑制等后处理操作,得到最终的网络检测结果'''
    def _suppress(self, raw_cls_bbox, raw_prob):

        bbox = list()
        label = list()
        score = list()
        for l in range(1, self.n_class):#跳过类别0,因为是背景类
            cls_bbox_l = raw_cls_bbox.reshape((-1, self.n_class, 4))[:, l, :]
            prob_l = raw_prob[:, l]
            mask = prob_l > self.score_thresh
            cls_bbox_l = cls_bbox_l[mask]
            prob_l = prob_l[mask]
            keep = nms(cls_bbox_l, prob_l,self.nms_thresh)
            # import ipdb;ipdb.set_trace()
            # keep = cp.asnumpy(keep)
            bbox.append(cls_bbox_l[keep].cpu().numpy())
            # The labels are in [0, self.n_class - 2].
            label.append((l - 1) * np.ones((len(keep),)))
            score.append(prob_l[keep].cpu().numpy())
        bbox = np.concatenate(bbox, axis=0).astype(np.float32)
        label = np.concatenate(label, axis=0).astype(np.int32)
        score = np.concatenate(score, axis=0).astype(np.float32)
        return bbox, label, score

    '''predict()函数:使用模型,进行目标的检测。返回bboxes, labels, scores'''
    @nograd
    def predict(self, imgs,sizes=None,visualize=False):
        """从某张图像中检测目标
        Args:
            imgs (iterable of numpy.ndarray): images数组.所有图像都是CHW和RGB格式,值的范围为0-255
        Returns:返回一个元组,元组的每个元素为一个列表,`(bboxes, labels, scores)`.
           bboxes: 浮点数组列表。hape`(R, 4)`代表 R 一张图像中的边界框的数量,每个边界框(y_{min}, x_{min}, y_{max}, x_{max}
           labels: 整型数组列表 shape (R,)`.每个值代表边界框的类别。值的取值范围为`[0, L - 1]`L是前景类别的数量
           scores: 浮点数组列表。 :math:`(R,)`每个值代表预测置信度
        """
        '''(1)设置模型为测试模式:不进行梯度的更新'''
        self.eval()

        '''(2) 加载并处理图片'''
        if visualize:
            self.use_preset('visualize')#设置nms_thresh及score_thresh值
            prepared_imgs = list()
            sizes = list()
            for img in imgs:
                size = img.shape[1:]#读取原图的大小
                img = preprocess(at.tonumpy(img))#调整图像的大小、值的范围。返回调整大小后的img数组,归一化值的范围为-1~1
                prepared_imgs.append(img)#将调整后的图片加入列表
                sizes.append(size)#将原始图片尺寸
        else:
             prepared_imgs = imgs
        
        '''(3)执行检测'''
        bboxes = list()
        labels = list()
        scores = list()
        for img, size in zip(prepared_imgs, sizes):

            img = at.totensor(img[None]).float()
            
            '''1.获取图片的缩放尺度'''
            # 原始图片的缩放因子scale,因为原始图像经过大小等的调整,这个缩放因子scale在ProposalCreator 筛选roi时有用到,即将所有候选框按这个缩放因子映射回原图,
            # 超出原图边框的趋于将被截断。
            scale = img.shape[3] / size[1]
            
            '''2.图片执行forward:得到头部分支的预测roi_cls_loc, roi_scores, rois, _'''
            roi_cls_loc, roi_scores, rois, _ = self(img, scale=scale)#调用FasterRCNN的前向传播函数:返回roi_cls_locs, roi_scores, rois, roi_indices
            # We are assuming that batch size is 1.
            roi_score = roi_scores.data
            roi_cls_loc = roi_cls_loc.data
            
            roi = at.totensor(rois) / scale

            # 把预测边界框转换为图像坐标中的边界框。预测边界框是网络输入图像的预测,将其转换为适合原图像的尺寸
            mean = t.Tensor(self.loc_normalize_mean).cuda(). repeat(self.n_class)[None]
            std = t.Tensor(self.loc_normalize_std).cuda().repeat(self.n_class)[None]
            roi_cls_loc = (roi_cls_loc * std + mean)
            roi_cls_loc = roi_cls_loc.view(-1, self.n_class, 4)
            roi = roi.view(-1, 1, 4).expand_as(roi_cls_loc)

            '''3.解码roi_cls_loc:变为实际坐标(y,x,y,x),而不是偏移量'''
            #将网络预测关于anchor box的偏移解码为边界框的直接坐标:(y,x,y,x),roi是参考框,roi_cls_loc是相对于roi的偏移
            cls_bbox = loc2bbox(at.tonumpy(roi).reshape((-1, 4)),at.tonumpy(roi_cls_loc).reshape((-1, 4)))
            cls_bbox = at.totensor(cls_bbox)
            cls_bbox = cls_bbox.view(-1, self.n_class * 4)

            '''4.将超出图像的边界框区域进行裁剪'''
            cls_bbox[:, 0::2] = (cls_bbox[:, 0::2]).clamp(min=0, max=size[0])
            cls_bbox[:, 1::2] = (cls_bbox[:, 1::2]).clamp(min=0, max=size[1])

            prob = (F.softmax(at.totensor(roi_score), dim=1))
            
            '''5.得到真正的检测结果:'''
            # predict函数是对网络RoIhead网络输出的预处理
            # 函数_suppress将得到真正的预测结果。
            bbox, label, score = self._suppress(cls_bbox, prob)
            bboxes.append(bbox)
            labels.append(label)
            scores.append(score)

        self.use_preset('evaluate')
        self.train()
        return bboxes, labels, scores

    '''get_optimizer()函数:返回模型的优化器optimizer'''
    def get_optimizer(self):
        """返回优化器,如果自定义了优化器,则优化器被重写"""
        lr = opt.lr
        params = []

        '''网络层的学习率、权重衰减'''
        for key, value in dict(self.named_parameters()).items():
            if value.requires_grad:
                if 'bias' in key:
                    params += [{'params': [value], 'lr': lr * 2, 'weight_decay': 0}]
                else:
                    params += [{'params': [value], 'lr': lr, 'weight_decay': opt.weight_decay}]

        '''优化算法'''
        if opt.use_adam:
            self.optimizer = t.optim.Adam(params)
        else:
            self.optimizer = t.optim.SGD(params, momentum=0.9)
        return self.optimizer
    '''scale_lr()函数:用于调整网络层的学习率'''
    def scale_lr(self, decay=0.1):
        for param_group in self.optimizer.param_groups:
            param_group['lr'] *= decay
        return self.optimizer

2.1 faster_rcnn_vgg16.py

  本py文件定义了faster_rcnn_vgg16类,该类重写了faster_rcnn类。

from __future__ import  absolute_import
import torch as t
import torch
from torch import nn
# from torchvision.models import vgg16
from torchvision.ops import RoIPool
from model.region_proposal_network import RegionProposalNetwork
from model.faster_rcnn import FasterRCNN
from utils import array_tool as at
from utils.config import opt
'''基于VGG-16的Faster R-CNN。本类是FasterRCNN的子类'''

###################################################新增代码:decom_vgg16()中调用##############################################################
cfgs = {'A': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'B': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'D': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'E': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}
from torchvision.models import VGG
def make_layers(cfg, batch_norm=False):
    layers = []
    in_channels = 3
    for v in cfg:
        if v == 'M':
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            if batch_norm:
                layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
            else:
                layers += [conv2d, nn.ReLU(inplace=True)]
            in_channels = v
    return nn.Sequential(*layers)
def vgg16(pretrained=False, progress=True, **kwargs):
    return _vgg('vgg16', 'D', False, pretrained, progress, **kwargs)
def _vgg(arch, cfg, batch_norm, pretrained, progress, **kwargs):
    if pretrained:
        kwargs['init_weights'] = False
    model = VGG(make_layers(cfgs[cfg], batch_norm=batch_norm), **kwargs)
    if pretrained:
        model.load_state_dict(torch.load(r'D:\Project\PaperProject\simple-faster-rcnn-pytorch\weight\vgg16-397923af.pth'))
    return model
###################################################新增代码:decom_vgg16()中调用##############################################################

''' FasterRCNNVGG16类:父类为FasterRCNN,此类用于构建FasterRCNNVGG16网络结构'''
class FasterRCNNVGG16(FasterRCNN):
    """基于VGG-16的Faster R-CNN。父类是FasterRCNN的子类"""
    feat_stride = 16  # vgg16的 conv5输出的下采样步长
    '''__init__()函数:初始化函数'''
    def __init__(self, n_fg_class=20,ratios=[0.5, 1, 2],anchor_scales=[8, 16, 32]):
        '''
         n_fg_class (int): 包含背景类的数据集类的数量
        ratios (list of floats):anchors的宽和高比
        anchor_scales (list of numbers): anchors的面积。这些面积将是“ anchor_scales”中元素的平方与参考窗口原始面积的乘积。
        '''
        '''(1)主干网络:特征提取网络,调整后的vgg16'''
        extractor, classifier = decom_vgg16()

        '''(2)RPN网络:返回RPN预测的 rpn_locs(相对于anchor box的偏移,(t_y,t_x,t_h,t_w))、
            rpn_scores(rpn的前景类分数)、
            rois(经过筛选后的rpn,格式为y,x,y,x)、roi的索引,
            anchor box'''
        rpn = RegionProposalNetwork(512, 512,ratios=ratios,anchor_scales=anchor_scales,feat_stride=self.feat_stride,)
        #h, img_size, scale

        '''(3)头部网络:得到网络预测:roi_cls_locs(边界框坐标,4维), roi_scores(各种类别分数,num_cls维)'''
        head = VGG16RoIHead(n_class=n_fg_class + 1,roi_size=7,spatial_scale=(1. / self.feat_stride), classifier=classifier )
        super(FasterRCNNVGG16, self).__init__(extractor, rpn,head,)


'''decom_vgg16()函数:构建vgg16网络(Faster RCNN的特征提取网络)。返回nn.Sequential(*features), classifier'''
def decom_vgg16():
 
    '''(1)加载预训练 模型结构、权重'''
    # caffe_pretrain = False  # use caffe pretrained model instead of torchvision
    # caffe_pretrain_path = 'checkpoints/vgg16_caffe.pth'
    if opt.caffe_pretrain:
        model = vgg16(pretrained=False)
        if not opt.load_path:
            model.load_state_dict(t.load(opt.caffe_pretrain_path))
    else:
        # opt.load_path= None
        model = vgg16(not opt.load_path)#加载torchvision内的vgg16模型及权重
    #print(model)#打印VGG16分类网络

    '''(2)预训练模型 结构调整:调整vgg16网络使其适应于本项目'''
    #VGG16模型有两部分:features,classifier。
    #1.features保持不变
    features = list(model.features)[:30]
    #2.classifier层,删除最后一个分类层和其中包含的dropout层
    classifier = model.classifier
    classifier = list(classifier)
    del classifier[6]
    if not opt.use_drop:#删除dropout层
        del classifier[5]
        del classifier[2]
    classifier = nn.Sequential(*classifier)
    '''(3)固定某些层的参数,不训练'''
    # freeze top4 conv
    for layer in features[:10]:#features共计30层,固定其10层不训练
        for p in layer.parameters():
            p.requires_grad = False
    return nn.Sequential(*features), classifier

'''VGG16RoIHead类:Faster RCNN的头部,得到网络预测:roi_cls_locs(边界框坐标,4维), roi_scores(各种类别分数,num_cls维)'''
class VGG16RoIHead(nn.Module):
    """基于给定的RoI 的特征图输出位置和类别。
    Args:
        n_class (int): 包含背景类别的总类别的数量
        roi_size (int): 经过RoI-pooling作用后的特征图的高和宽.
        spatial_scale (float): roi调整的尺度
        classifier (nn.Module): 从vgg16移植的两层线性层
    """
    '''__init__()函数:初始化函数'''
    def __init__(self, n_class, roi_size, spatial_scale,classifier):
        super(VGG16RoIHead, self).__init__()
        '''特征提取网络的classifier网络层'''
        self.classifier = classifier
        
        '''构建头部网络的线性层:定位层和分类层(线性层),并初始化权重参数'''
        self.cls_loc = nn.Linear(4096, n_class * 4)
        self.score = nn.Linear(4096, n_class)
        normal_init(self.cls_loc, 0, 0.001)
        normal_init(self.score, 0, 0.01)
        
        '''包含背景的类别数量、ROI调整后的大小(通过roi pooling)、ROI调整的尺度'''
        self.n_class = n_class
        self.roi_size = roi_size
        self.spatial_scale = spatial_scale
        
        '''构建RoIPool层'''
        self.roi = RoIPool( (self.roi_size, self.roi_size),self.spatial_scale)

    '''forward()函数:前向传播'''
    def forward(self, x, rois, roi_indices):
        """ `N` batches.
        Args:
            x (Variable): 调整后的图片产生的特征图
            rois (Tensor): ROI边界框的坐标. 这个数组是批量所有图片的ROI 。shape : `(R', 4)`. `R_i` 第i张图片的ROI
            roi_indices (Tensor): 一维数组,是ROI在批量中图片的索引shape :math:`(R',)`,每个值指示批量的哪张图片
        """
        #from utils import array_tool as at
        '''(1)拼接roi_indices、 rois得到indices_and_rois:rois坐标为(y,x,y,x)'''
        #totensor()函数:将其他类型的数据转化为tensor类型,并迁移到cuda
        roi_indices = at.totensor(roi_indices).float()
        rois = at.totensor(rois).float()
        
        indices_and_rois = t.cat([roi_indices[:, None], rois], dim=1)

        '''(2)ROI由(y,x,y,x)转换为(x,y,x,y)格式,得到indices_and_rois'''
        xy_indices_and_rois = indices_and_rois[:, [0, 2, 1, 4, 3]]
        indices_and_rois =  xy_indices_and_rois.contiguous()

        '''(3)RoIPool后的ROI特征图:'''
        pool = self.roi(x, indices_and_rois)
        pool = pool.view(pool.size(0), -1)

        '''(4)定位和分类层作用于ROI特征图,得到检测位置及检测类别'''
        fc7 = self.classifier(pool)
        roi_cls_locs = self.cls_loc(fc7)
        roi_scores = self.score(fc7)

        return roi_cls_locs, roi_scores

'''normal_init()函数:网络层权重的初始化'''
def normal_init(m, mean, stddev, truncated=False):
    # x is a parameter
    if truncated:
        m.weight.data.normal_().fmod_(2).mul_(stddev).add_(mean)  # not a perfect approximation
    else:
        m.weight.data.normal_(mean, stddev)
        m.bias.data.zero_()

2.3 region_proposal_network.py

  本py文件构建faster Rcnn的RPN网络,返回RPN预测的 rpn_locs(相对于anchor box的偏移,(t_y,t_x,t_h,t_w))、rpn_scores(rpn的前景类分数)、rois(经过筛选后的rpn,格式为y,x,y,x)、roi的索引,anchor box

import numpy as np
from torch.nn import functional as F
import torch as t
from torch import nn
from model.utils.bbox_tools import generate_anchor_base
from model.utils.creator_tool import ProposalCreator

'''RegionProposalNetwork类:构建RPN网络.返回RPN预测的 rpn_locs(相对于anchor box的偏移,(t_y,t_x,t_h,t_w))、rpn_scores(rpn的前景类分数)、rois(经过筛选后的rpn,格式为y,x,y,x)、roi的索引,anchor box'''
class RegionProposalNetwork(nn.Module):
    """利用从图像中提取的特征,在“对象”周围产生类不可知的边界框。
    Args:
        in_channels (int): 输入通道数
        mid_channels (int): 中间张量的通道大小。
        ratios (list of floats): anchor的宽度与高度之比。
        anchor_scales (list of numbers): anchor的面积。这些面积是`anchor_scales`中元素的平方与参考窗口原始区域的乘积。
        feat_stride (int): 从图像中提取的特征的下采样步幅大小。
        initialW (callable): 初始化权重值.
        proposal_creator_params (dict): Key valued paramters for
            :class:`model.utils.creator_tools.ProposalCreator`.
    """
    ''' __init_()函数:属性以及方法的初始化函数'''
    def __init__(self, in_channels=512, mid_channels=512, ratios=[0.5, 1, 2],
                 anchor_scales=[8, 16, 32], feat_stride=16,proposal_creator_params=dict()):
        #ratios=[0.5, 1, 2],anchor_scales=[8, 16, 32]
        super(RegionProposalNetwork, self).__init__()

        '''生成一组先验base anchors(y_{min}, x_{min}, y_{max}, x_{max}),共9个,这组anchors在特征图上滑动,产生先验anchor box。 '''
        self.anchor_base = generate_anchor_base(anchor_scales=anchor_scales, ratios=ratios)

        self.feat_stride = feat_stride
        '''产生ROI,作为头部网络的输入 (ROI:(y,x,y,x)):ROI,是经过NMS等筛选处理后的rpn预测框'''
        self.proposal_layer = ProposalCreator(self, **proposal_creator_params)


        '''构建RPN网络:参数使用normal_init初始化'''
        #一组先验anchor的数量
        n_anchor = self.anchor_base.shape[0]
        self.conv1 = nn.Conv2d(in_channels, mid_channels, 3, 1, 1)
        self.score = nn.Conv2d(mid_channels, n_anchor * 2, 1, 1, 0)
        self.loc = nn.Conv2d(mid_channels, n_anchor * 4, 1, 1, 0)
        normal_init(self.conv1, 0, 0.01)
        normal_init(self.score, 0, 0.01)
        normal_init(self.loc, 0, 0.01)

    '''forward()函数:RPN网络的前向传播。输入为来自特征提取网络的特征图'''
    def forward(self, x, img_size, scale=1.):#h, img_size, scale
        """
        Args:
            x (~torch.autograd.Variable): 特征图 shape :`(N, C, H, W)`. `N` 批量大小  `C` 输入的通道数 `H` and `W` 特征图的宽和高
            img_size (tuple of ints):网络输入图像的(`height, width`)
            scale (float): 网络输入图像相对于原始图像的缩放尺度
        Returns: (~torch.autograd.Variable, ~torch.autograd.Variable, array, array, array):
           元组包含以下5个值:
                rpn_locs:网络预测边界框相对于anchor box的偏移  shape:`(N, H W A, 4)`.`A` 每个特征图单元的anchor的数量
                rpn_scores : anchor box的前景类的分数 shape  : `(N, H W A, 2)`.`A` 每个特征图单元的anchor的数量
                rois : ROI边界框的坐标,shape `(R', 4)`坐标(y,x,y,x).`R_i` 代表第i张图片预测的ROI
                roi_indices : RoI对应的图片索引的数组。 shape:(R',)`。
                anchor :anchor box的坐标.shape  : (H W A, 4)`,其中坐标为(y,x,y,x).
        """
        '''特征图的高、宽'''
        n, _, hh, ww = x.shape

        '''(1)为特征图产生anchor box(y,x,y,x)'''
        anchor = _enumerate_shifted_anchor(np.array(self.anchor_base),self.feat_stride, hh, ww)#参数

        '''(2)为特征图,生成RPN预测:位置rpn_locs(n, -1, 4)、前景类分数预测 rpn_scores(n, -1, 2)'''

        n_anchor = anchor.shape[0] // (hh * ww)
        '''1.网络边界框坐标预测,坐标为相对于anchor box的偏移量(t_y,t_x,t_h,t_w)'''
        h = F.relu(self.conv1(x))
        rpn_locs = self.loc(h)
        rpn_locs = rpn_locs.permute(0, 2, 3, 1).contiguous().view(n, -1, 4)

        '''2.网络边界框前景类分数预测:经过softmax处理'''
        rpn_scores = self.score(h)
        rpn_scores = rpn_scores.permute(0, 2, 3, 1).contiguous()
        rpn_softmax_scores = F.softmax(rpn_scores.view(n, hh, ww, n_anchor, 2), dim=4)
        rpn_fg_scores = rpn_softmax_scores[:, :, :, :, 1].contiguous()
        rpn_fg_scores = rpn_fg_scores.view(n, -1)
        rpn_scores = rpn_scores.view(n, -1, 2)


        rois = list()
        roi_indices = list()
        for i in range(n):#遍历批量中的每个图片
            '''(3)为第i张图片产生ROI(y,x,y,x):这些ROI,是经过NMS等处理后的rpn预测框'''
            roi = self.proposal_layer(rpn_locs[i].cpu().data.numpy(),rpn_fg_scores[i].cpu().data.numpy(),anchor, img_size,scale=scale)
            #输入参数:rpn位置预测、rpn前景类分数预测、anchor box、调整后的图像的尺寸、图像的缩放因子

            '''(4)将第i张图片的ROI添加到rois列表,并将batch_index的索引号设置为i'''
            batch_index = i * np.ones((len(roi),), dtype=np.int32)
            rois.append(roi)
            roi_indices.append(batch_index)

        rois = np.concatenate(rois, axis=0)
        roi_indices = np.concatenate(roi_indices, axis=0)
        
        '''(5)返回 rpn_locs(相对于anchor box的偏移,(t_y,t_x,t_h,t_w))、rpn_scores(rpn的前景类分数)、rois(经过筛选后的rpn,格式为y,x,y,x)、roi的索引,anchor box'''
        return rpn_locs, rpn_scores, rois, roi_indices, anchor

'''_enumerate_shifted_anchor()函数:使用一组先验anchor_base为特征图产生anchor box'''
def _enumerate_shifted_anchor(anchor_base, feat_stride, height, width):
    #将Aanchor_base(1,A,4)添加到单元格(图像块)K移位(K,1,4)中以获得移位后的 anchor(K,A,4),形状调整为(K * A,4)

    import numpy as xp
    '''(1)将图像划分为 网格(网格包含:feat_stride*feat_stride 个网格单元)'''
    shift_y = xp.arange(0, height * feat_stride, feat_stride)
    shift_x = xp.arange(0, width * feat_stride, feat_stride)
    shift_x, shift_y = xp.meshgrid(shift_x, shift_y)
    shift = xp.stack((shift_y.ravel(), shift_x.ravel(),shift_y.ravel(), shift_x.ravel()), axis=1)

    '''(2)将先验base anchor分配给每个网格单元,最终得到特征图的所有anchor box的坐标'''
    A = anchor_base.shape[0]
    K = shift.shape[0]
    anchor = anchor_base.reshape((1, A, 4)) +  shift.reshape((1, K, 4)).transpose((1, 0, 2))
    anchor = anchor.reshape((K * A, 4)).astype(np.float32)
    return anchor
'''同上作用'''
def _enumerate_shifted_anchor_torch(anchor_base, feat_stride, height, width):
    import torch as t
    shift_y = t.arange(0, height * feat_stride, feat_stride)
    shift_x = t.arange(0, width * feat_stride, feat_stride)
    shift_x, shift_y = np.meshgrid(shift_x, shift_y)
    shift = np.stack((shift_y.ravel(), shift_x.ravel(), shift_y.ravel(), shift_x.ravel()), axis=1)

    A = anchor_base.shape[0]
    K = shift.shape[0]
    anchor = anchor_base.reshape((1, A, 4)) + shift.reshape((1, K, 4)).transpose((1, 0, 2))
    anchor = anchor.reshape((K * A, 4)).astype(np.float32)
    return anchor

'''normal_init()函数:权重初始化函数'''
def normal_init(m, mean, stddev, truncated=False):
    """"""
    # m为网络层,
    if truncated:
        m.weight.data.normal_().fmod_(2).mul_(stddev).add_(mean)  # not a perfect approximation
    else:
        m.weight.data.normal_(mean, stddev)
        m.bias.data.zero_()

2.4 bbbox_tools.py

在这里插入图片描述

import numpy as np
import numpy as xp
import six
from six import __init__

'''loc2bbox()函数:例将网络预测关于anchor box的偏移(t_y,t_x,t_h,t_w),解码为直接坐标:(y,x,y,x)。返回值和输入值相同的形状 '''
def loc2bbox(src_bbox, loc):
    """
    给定网络预测的边界框rpn框(相对于anchor box偏移`t_y, t_x, t_h, t_w` )计算它的坐标公式为:
        hat{g}_y = p_h t_y + p_y`
        hat{g}_x = p_w t_x + p_x`
        hat{g}_h = p_h \\exp(t_x)`
        hat{g}_w = p_w \\exp(t_w)`
    Args:
        src_bbox (array): anchor box坐标。shape:math:`(R, 4)`坐标形式为:`p_{ymin}, p_{xmin}, p_{ymax}, p_{xmax}`.
        loc (array):loc):  loc为网络预测的边界框,这些边界框是相对于anchor box的偏移,坐标形式为:`t_y, t_x, t_h, t_w`.
        src_bbox, loc两者的形状相同
    Returns:数组,解码后的边界框坐标shap:`(R, 4)`.
    """
    if src_bbox.shape[0] == 0:
        return xp.zeros((0, 4), dtype=loc.dtype)

    src_bbox = src_bbox.astype(src_bbox.dtype, copy=False)
    '''(1)anchor box的w、h、y,x'''
    src_height = src_bbox[:, 2] - src_bbox[:, 0]
    src_width = src_bbox[:, 3] - src_bbox[:, 1]
    src_ctr_y = src_bbox[:, 0] + 0.5 * src_height
    src_ctr_x = src_bbox[:, 1] + 0.5 * src_width
    '''(2)预测框相对于anchor box的偏移dy,dx ,dh,dw'''
    dy = loc[:, 0::4]
    dx = loc[:, 1::4]
    dh = loc[:, 2::4]
    dw = loc[:, 3::4]

    '''(3)解码预测框坐标:返回解码后的预测边界框(y,x,y,x)'''
    ctr_y = dy * src_height[:, xp.newaxis] + src_ctr_y[:, xp.newaxis]
    ctr_x = dx * src_width[:, xp.newaxis] + src_ctr_x[:, xp.newaxis]
    h = xp.exp(dh) * src_height[:, xp.newaxis]
    w = xp.exp(dw) * src_width[:, xp.newaxis]

    dst_bbox = xp.zeros(loc.shape, dtype=loc.dtype)
    dst_bbox[:, 0::4] = ctr_y - 0.5 * h
    dst_bbox[:, 1::4] = ctr_x - 0.5 * w
    dst_bbox[:, 2::4] = ctr_y + 0.5 * h
    dst_bbox[:, 3::4] = ctr_x + 0.5 * w

    return dst_bbox

'''bbox2loc()函数:例计算anchor box相对于真值框的偏移(t_y, t_x, t_h, t_w)'''
def bbox2loc(src_bbox, dst_bbox):
    """例:给定anchor box,计算相对于真值框的偏移量和缩放比例。以将anchor框与真值框匹配。

    调整后的anchor框:(y, x,p_h, p_w`)
    规范化后的真值框:(`g_y, g_x` `g_h, g_w`,)
    偏移和缩放比例为:(t_y, t_x, t_h, t_w` )计算公式为:
                `t_y = \\frac{(g_y - p_y)} {p_h}`
                `t_x = \\frac{(g_x - p_x)} {p_w}`
                `t_h = \\log(\\frac{g_h} {p_h})`
                `t_w = \\log(\\frac{g_w} {p_w})`
    Args:
        src_bbox (array): anchor框,形状为`(R, 4)`.R代表anchor框的数量,格式为(`p_{ymin}, p_{xmin}, p_{ymax}, p_{xmax}`.)
        dst_bbox (array): 真值框。形状为(R, 4) 坐标格式为`g_{ymin}, g_{xmin}, g_{ymax}, g_{xmax}`.

    Returns:anchor相对于真值框的偏移shape`(R, 4)`.(`t_y, t_x, t_h, t_w`.)
    """
    '''(1)RPN网络产生的anchor box为(y_{min}, x_{min}, y_{max}, x_{max})形式,将其转换为(y, x,p_h, p_w`)'''
    height = src_bbox[:, 2] - src_bbox[:, 0]
    width = src_bbox[:, 3] - src_bbox[:, 1]
    ctr_y = src_bbox[:, 0] + 0.5 * height
    ctr_x = src_bbox[:, 1] + 0.5 * width
    '''(2)真值框的坐标为(y_{min}, x_{min}, y_{max}, x_{max})形式,将其转换为(y, x,p_h, p_w`)'''
    base_height = dst_bbox[:, 2] - dst_bbox[:, 0]
    base_width = dst_bbox[:, 3] - dst_bbox[:, 1]
    base_ctr_y = dst_bbox[:, 0] + 0.5 * base_height
    base_ctr_x = dst_bbox[:, 1] + 0.5 * base_width

    '''(3)计算anchor box 相对于真值框的偏移(t_y, t_x, t_h, t_w)'''
    eps = xp.finfo(height.dtype).eps
    height = xp.maximum(height, eps)
    width = xp.maximum(width, eps)

    dy = (base_ctr_y - ctr_y) / height
    dx = (base_ctr_x - ctr_x) / width
    dh = xp.log(base_height / height)
    dw = xp.log(base_width / width)
    
    loc = xp.vstack((dy, dx, dh, dw)).transpose()
    return loc

'''bbox_iou()函数:返回IOU值矩阵。计算两组边界框的IOU值矩阵'''
def bbox_iou(bbox_a, bbox_b):
    """
    Args:
        bbox_a (array):形状:`(N, 4)`.N为框的数量
        bbox_b (array):形状:`(K, 4)`.N为框的数量
    Returns:数组,形状为`(N, K)`.
    """
    if bbox_a.shape[1] != 4 or bbox_b.shape[1] != 4:
        raise IndexError
    '''相交区域的左上和右下坐标'''
    tl = xp.maximum(bbox_a[:, None, :2], bbox_b[:, :2])
    br = xp.minimum(bbox_a[:, None, 2:], bbox_b[:, 2:])
    
    '''相交面积'''
    area_i = xp.prod(br - tl, axis=2) * (tl < br).all(axis=2)
    
    area_a = xp.prod(bbox_a[:, 2:] - bbox_a[:, :2], axis=1)
    area_b = xp.prod(bbox_b[:, 2:] - bbox_b[:, :2], axis=1)
    '''iou值矩阵'''
    return area_i / (area_a[:, None] + area_b - area_i)


def __test():
    pass

if __name__ == '__main__':
    __test()

'''generate_anchor_base:一组先验 base anchors,这组anchors在特征图上滑动,为特征图产生先验anchor box。(y_{min}, x_{min}, y_{max}, x_{max}) '''
def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2],anchor_scales=[8, 16, 32]):
    """产生按给定宽高比缩放和修改的anchor。
    例如果scale是`8`,ratio是`0.25`,则基本窗口的宽度和高度被`8`拉伸。高度减半,宽度加倍。
    Args:
        base_size (number): 参考窗口的宽和高.
        ratios (list of floats): anchor宽高比的比率
        anchor_scales (list of numbers): anchor的面积
            这些面积将是“ anchor_scales”中元素的平方与参考窗口原始面积的乘积。
    Returns: ~numpy.ndarray。shape:`(R, 4)`.每个anchor的坐标为`(y_{min}, x_{min}, y_{max}, x_{max})`

    """
    '''(1)参考窗口的宽和高'''
    py = base_size / 2.#8
    px = base_size / 2.#8

    '''(2)每个位置的anchor的数量(一组anchors的数量)'''
    anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4),dtype=np.float32)#shape=[9,4]

    '''(3)每个anchor的坐标。这些anchor框以(0,0)为中心'''
    for i in six.moves.range(len(ratios)):
        for j in six.moves.range(len(anchor_scales)):
            #anchor的宽和高''',例 ratios=0.5,anchor_scales=8时,h=8*8*根下0.5=45.25,w=8*8*根下0.75=90.5
            h = base_size * anchor_scales[j] * np.sqrt(ratios[i])
            w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i])

            '''anchor的坐标(y_{min}, x_{min}, y_{max}, x_{max}),'''
            index = i * len(anchor_scales) + j
            anchor_base[index, 0] = py - h / 2.
            anchor_base[index, 1] = px - w / 2.
            anchor_base[index, 2] = py + h / 2.
            anchor_base[index, 3] = px + w / 2.
    return anchor_base

2.5 creator_tools.py

在这里插入图片描述

import numpy as np
import torch
from torchvision.ops import nms
from model.utils.bbox_tools import bbox2loc, bbox_iou, loc2bbox

''' ProposalTargetCreator类:采样ROI,返回sample_roi, gt_roi_loc, gt_roi_label.既采样后的ROI、ROI相对于真值框的偏移(t_y, t_x, t_h, t_w)、ROI的类标签索引'''
class ProposalTargetCreator(object):
    """把真值框分配给ROI
    Args:
        n_sample (int): 采样ROI的数量
        pos_ratio (float): 标记为前景的区域的分数。
        pos_iou_thresh (float): RoI被视为前景的IoU阈值。
        neg_iou_thresh_hi (float): RoI被视为背景:如果IOU 在 [`neg_iou_thresh_hi`,`neg_iou_thresh_hi`).范围
        neg_iou_thresh_lo (float): RoI被视为背景:如果IOU 在 [`neg_iou_thresh_hi`,`neg_iou_thresh_hi`).范围
    """
    '''__init__()函数:初始化函数,用于定义一些参数、阈值等'''
    def __init__(self,n_sample=128,pos_ratio=0.25, pos_iou_thresh=0.5, neg_iou_thresh_hi=0.5, neg_iou_thresh_lo=0.0):
        #采样后的ROI的数量
        self.n_sample = n_sample
        self.pos_ratio = pos_ratio
        self.pos_iou_thresh = pos_iou_thresh
        self.neg_iou_thresh_hi = neg_iou_thresh_hi
        self.neg_iou_thresh_lo = neg_iou_thresh_lo  # NOTE:default 0.1 in py-faster-rcnn
        
    '''__call__()函数:采样ROI,返回采样后的ROI、ROI相对于真值框的偏移(t_y, t_x, t_h, t_w)、ROI的类标签索引'''
    def __call__(self, roi, bbox, label,loc_normalize_mean=(0., 0., 0., 0.),loc_normalize_std=(0.1, 0.1, 0.2, 0.2)):
        """Assigns ground truth to sampled proposals.
        Args:
            roi (array): RoIs.shape :(R, 4)`
            bbox (array): 真值框的坐标。shape : (R', 4)`.
            label (array):真值框的标签.  shape :`(R',)`.值的范围为[0, L - 1]`, L为前景类的数量
            loc_normalize_mean (tuple of four floats):标准化后边界框坐标的均值
            loc_normalize_std (tupler of four floats): 边界框坐标的标准方差
        Returns:(array, array, array):
             sample_roi : 采样的ROI数量 。shape  :`(S, 4)`.`S` 采样RoIs的总数量=n_sample`.
             gt_roi_loc :采样的RoI相对于与真值框的偏移和尺度   shape  :`(S, 4)`.
             gt_roi_label: 采样的ROI的标签.  shape   :`(S,)`.取值范围`[0, L]`.   0 代表背景

        """
        '''(1)计算ROI与真值框的IOU值矩阵:iou'''
        #真值框的数量
        n_bbox, _ = bbox.shape
        roi = np.concatenate((roi, bbox), axis=0)#按轴axis连接array组成一个新的array
        pos_roi_per_image = np.round(self.n_sample * self.pos_ratio)

        iou = bbox_iou(roi, bbox)

        '''(2)每个ROI匹配的  真值框的索引(gt_assignment)、两者之间的IOU值(max_iou)、类标签索引(gt_roi_label)'''
        gt_assignment = iou.argmax(axis=1)#行中最大值的索引
        max_iou = iou.max(axis=1)#行中最大IOU值
        # Offset range of classes from [0, n_fg_class - 1] to [1, n_fg_class].
        gt_roi_label = label[gt_assignment] + 1#0代表背景

        '''(3)采样前景RoI,返回前景roi的索引pos_index:  IoU>>pos_iou_thresh ,数量为pos_roi_per_this_image'''
        pos_index = np.where(max_iou >= self.pos_iou_thresh)[0]
        pos_roi_per_this_image = int(min(pos_roi_per_image, pos_index.size))#每张图片应该保留的前景ROI数量
        if pos_index.size > 0:
            pos_index = np.random.choice( pos_index, size=pos_roi_per_this_image, replace=False)
        '''(4)采样背景ROI,返回背景roi的索引neg_index:IoU值范围在[neg_iou_thresh_lo, neg_iou_thresh_hi)',数量为=neg_roi_per_this_image'''
        neg_index = np.where((max_iou < self.neg_iou_thresh_hi) &(max_iou >= self.neg_iou_thresh_lo))[0]
        neg_roi_per_this_image = self.n_sample - pos_roi_per_this_image
        neg_roi_per_this_image = int(min(neg_roi_per_this_image,neg_index.size))
        if neg_index.size > 0:
            neg_index = np.random.choice(neg_index, size=neg_roi_per_this_image, replace=False)

        '''(5)最终采样保留的ROI的索引(pos_index, neg_index)'''
        keep_index = np.append(pos_index, neg_index)
        '''(6)采样后的ROI的坐标sample_roi、类标签索引gt_roi_label,'''
        gt_roi_label = gt_roi_label[keep_index]
        gt_roi_label[pos_roi_per_this_image:] = 0  # 背景ROI的类标签索引为0

        sample_roi = roi[keep_index]

        '''(7)计算采样ROI与其匹配的真值框的 偏移(t_y, t_x, t_h, t_w)'''
        # Compute offsets and scales to match sampled RoIs to the GTs.
        gt_roi_loc = bbox2loc(sample_roi, bbox[gt_assignment[keep_index]])
        gt_roi_loc = ((gt_roi_loc - np.array(loc_normalize_mean, np.float32)) / np.array(loc_normalize_std, np.float32))
        #返回采样后的ROI,ROI相对于真值框的偏移(t_y, t_x, t_h, t_w)、ROI的类标签索引
        return sample_roi, gt_roi_loc, gt_roi_label
    
    
'''AnchorTargetCreator类:构建anchor box相对于匹配的真值框的偏移(t_y, t_x, t_h, t_w)、anchor的标签(0,-1,1)、'''
class AnchorTargetCreator(object):
    """将真值边界框分配给 anchor。用于训练RPN网络
     把anchor匹配给真值框的偏移量和比例尺,都使用的编码方案`model.utils.bbox_tools.bbox2loc`计算
    """
    ''' __init__()函数:初始化函数,设置采样anchor的总数量、正负anchor的阈值、正anchor占总anchor的比例'''
    def __init__(self,n_sample=256,pos_iou_thresh=0.7, neg_iou_thresh=0.3,pos_ratio=0.5):
        '''
        n_sample (int): The number of regions to produce.
        pos_iou_thresh (float): IoU高于此阈值的anchor为正。
        neg_iou_thresh (float): IoU低于此阈值的anchor为负。
        pos_ratio (float): 采样anchor中正anchor的比率。'''
        self.n_sample = n_sample
        self.pos_iou_thresh = pos_iou_thresh
        self.neg_iou_thresh = neg_iou_thresh
        self.pos_ratio = pos_ratio
    '''__call__()函数:返回anchor box相对于真值框的偏移(t_y, t_x, t_h, t_w)、anchor box的标签(1=positive, 0=negative, -1=ignore)'''
    def __call__(self, bbox, anchor, img_size):
        """将真值框分配给采样anchor子集。
        Args:
            bbox (array): 真值框的坐标. shape `(R, 4)`.`R` 真值框数量
            anchor (array): 图片中所有anchor box的坐标. shape `(S, 4)`.`S` anchor的数量
            img_size (tuple of ints):经过调整后的图像的宽和高
        Returns: (array, array):
            loc: anchor box相对于真值框的偏移和缩放shape :`(S, 4)`.
            label: 带有值的anchor box标签(1=positive, 0=negative, -1=ignore)`.shape:`(S,)`.
        """
        '''(1)经过调整的图像的大小、anchor box的数量'''
        img_H, img_W = img_size
        n_anchor = len(anchor)

        '''(2)获取完全在图像内部的anchor box及anchor box的索引'''
        #index_inside:获取完全位于图像内部的anchor的索引。
        inside_index = _get_inside_index(anchor, img_H, img_W)
        #anchor:提取完全位于图像内部的anchor(anchor的坐标)
        anchor = anchor[inside_index]

        '''(3)每个anchor box最大iou对应的真实框索引、每个anchor box的标签数组label(1=positive, 0=negative, -1=ignore)'''
        argmax_ious, label = self._create_label(inside_index, anchor, bbox)

        '''(4)计算anchor box相对于匹配的真值框的偏移(t_y, t_x, t_h, t_w):anchor box和真值框的坐标都是(x,y,x,y)形式'''
        loc = bbox2loc(anchor, bbox[argmax_ious])

        '''(6)将anchor的标签既anchor相对于真值框的偏移,映射到所有anchor中:因为初始label、loc的计算都是对完全包含在图像中的anchor进行的,有一部分anchor可能有些区域在图像外部'''
        label = _unmap(label, n_anchor, inside_index, fill=-1)#anchor的标签、anchor的数量、完全在图像内部的anchor索引
        loc = _unmap(loc, n_anchor, inside_index, fill=0)

        return loc, label
    
    
    '''_create_label()函数:返回每个anchor box最大iou对应的真实框索引、每个anchor box的标签数组label(1=positive, 0=negative, -1=ignore)、'''
    def _create_label(self, inside_index, anchor, bbox):
        '''
        inside_index:完全位于图像内部的anchor box的索引
        anchor:完全位于图像内部的anchor box(anchor的坐标)
        bbox:真值框
        '''
        '''(1)创建anchor的lable数组:数组初始值为-1'''
        label = np.empty((len(inside_index),), dtype=np.int32)
        label.fill(-1)

        '''(2)每个anchor box最大iou对应的真实框索引(argmax_ious)、每个anchor box最大iou值(max_ious)、每个真值框最大iou的anchor box索引'''
        argmax_ious, max_ious, gt_argmax_ious = self._calc_ious(anchor, bbox, inside_index)

        '''(3)给anchor box设置标签(1=positive, 0=negative, -1=ignore):根据max_iou是否高于阈值'''
        # 负标签:首先分配负标签,以便正标签可以掩盖它们
        label[max_ious < self.neg_iou_thresh] = 0

        #正标签:
        #1.最大iou值对应的anchor设为正anchor
        label[gt_argmax_ious] = 1
        # 2.iou值高于阈值的anchor设为正anchor
        label[max_ious >= self.pos_iou_thresh] = 1

        '''(4)采样正anchor box:没有被采样的正anchor box标签label相应位置设置为-1'''
        #需要保留的正anchor的数量
        n_pos = int(self.pos_ratio * self.n_sample)
        #正anchor的索引
        pos_index = np.where(label == 1)[0]
        if len(pos_index) > n_pos:
            disable_index = np.random.choice(pos_index, size=(len(pos_index) - n_pos), replace=False)
            label[disable_index] = -1

        '''(5)采样负anchor box:'''
        #需要保留的负anchor的数量
        n_neg = self.n_sample - np.sum(label == 1)
        #负anchor的索引
        neg_index = np.where(label == 0)[0]
        if len(neg_index) > n_neg:
            disable_index = np.random.choice(
                neg_index, size=(len(neg_index) - n_neg), replace=False)
            label[disable_index] = -1

        '''返回:每个anchor box最大iou对应的真实框索引、每个anchor box的标签数组label(标记anchor为正、负anchor) '''
        return argmax_ious, label
    
    '''_calc_ious()函数:返回每个anchor box最大iou对应的真实框索引(argmax_ious)、每个anchor box最大iou值(max_ious)、每个真值框最大iou的anchor box索引'''
    def _calc_ious(self, anchor, bbox, inside_index):
        ''' '''
        '''1.计算真值框和anchor的iou值矩阵。形状为【anchor数量,真值框数量】'''
        ious = bbox_iou(anchor, bbox)

        '''2.每个anchor box最大iou的对应的真实框索引'''
        argmax_ious = ious.argmax(axis=1)#
        '''3.每个anchor box最大iou值'''
        max_ious = ious[np.arange(len(inside_index)), argmax_ious]#每个先验框对应的拥有最大iou的真实框的iou值
        '''4.每个真值框最大iou的anchor box索引'''
        gt_argmax_ious = ious.argmax(axis=0)#
        gt_max_ious = ious[gt_argmax_ious, np.arange(ious.shape[1])]
        gt_argmax_ious = np.where(ious == gt_max_ious)[0] # 每个真实框对应的先验框最大的iou

        return argmax_ious, max_ious, gt_argmax_ious
    
'''_unmap()函数:将anchor的标签(anchor的标签只包含完全在图像的anchor)映射到所有的anchor中'''
def _unmap(data, count, index, fill=0):
    # Unmap a subset of item (data) back to the original set of items (of size count)
    '''参数:完全在图像内部的anchor的标签、图像中所有anchor的数量、完全在图像内部的anchor索引'''
    if len(data.shape) == 1:
        ret = np.empty((count,), dtype=data.dtype)
        ret.fill(fill)
        ret[index] = data
    else:
        ret = np.empty((count,) + data.shape[1:], dtype=data.dtype)
        ret.fill(fill)
        ret[index, :] = data
    return ret

'''_get_inside_index()函数:获取完全位于图像内部的anchor box的索引。index_inside'''
def _get_inside_index(anchor, H, W):
    # 计算完全位于指定大小图像内部的anchor索引。
    index_inside = np.where(
        (anchor[:, 0] >= 0) &
        (anchor[:, 1] >= 0) &
        (anchor[:, 2] <= H) &
        (anchor[:, 3] <= W)
    )[0]
    return index_inside

'''ProposalCreator类:RPN网络生成ROI(感兴趣区域,用于后续的头部网络的检测(y,x,y,x))。既经过非极大值抑制等后处理操作后的RPN网络产生的rpn框 '''
class ProposalCreator:
    """为特征图生成提案框
   `__call__` 通过估计边界框相对于anchor的偏移量来输出对象检测提案(ROI)。
    此类使用参数来控制传递给NMS并在NMS之后保留的边界框的数量。如果参数为负,则使用提供的所有边界框或保留NMS返回的所有边界框。
    """
    '''__init__()函数:初始化函数,设置一些阈值、nms相关的参数'''
    def __init__(self,parent_model,nms_thresh=0.7, n_train_pre_nms=12000, n_train_post_nms=2000,n_test_pre_nms=6000,n_test_post_nms=300,min_size=16):
        '''
         nms_thresh (float): NMS.阈值
        n_train_pre_nms (int):在训练模式下,传递到NMS之前要保留的得分最高的边界框的数量。
        n_train_post_nms (int): 在训练模式下,传递到NMS后要保留的得分最高的边界框的数量。
        n_test_pre_nms (int): 在测试模式下,传递到NMS之前要保留的得分最高的边界框的数量。
        n_test_post_nms (int): 在测试模式下,传递到NMS后要保留的得分最高的边界框的数量。
        force_cpu_nms (bool): `True`代表在CPU模式下应用NMS 。`False`:False:根据输入类型选择NMS模式
        min_size (int): 用于根据边界框的大小确定丢弃边界框的阈值的参数。
        '''
        '''(1)模式、nms阈值'''
        self.parent_model = parent_model
        self.nms_thresh = nms_thresh

        '''(2)不同模式下,nms处理前和nms后保留的边界框的数量'''
        self.n_train_pre_nms = n_train_pre_nms
        self.n_train_post_nms = n_train_post_nms

        self.n_test_pre_nms = n_test_pre_nms
        self.n_test_post_nms = n_test_post_nms

        self.min_size = min_size
        
    '''__call__()函数:返回ROI框(用于输入到后续头部网络),这些ROI框,是对RPN预测框的筛选后的结果'''
    def __call__(self, loc, score , anchor, img_size, scale=1.):# rpn位置预测、rpn前景类分数预测、anchor box、调整后的图像的尺寸、图像的缩放因子
        """
        Args:
            loc (array):网络预测的相对于anchor box的偏移 shape `(R, 4)`.偏移的坐标(t_y,t_x,t_h,t_w),R为先验 anchor的总数量=特征图的宽*高*每个位置先验anchor的数量(9)
            score (array): anchor为前景的预测概率 shape:(R,)`.
            anchor (array): anchor  box的坐标. shape:`(R, 4)`.
            img_size (tuple of ints): (调整后的)图像的宽和高
            scale (float):从文件读取图像后,用于缩放图像的缩放因子。

        Returns:提案框的坐标数组。shape:`(S, 4)`.:`S` 在测试模式下少于n_test_post_nms在训练模式下少于n_train_post_nms。
        """
        # 测试模式下faster_rcnn.eval(),self.traing = False
        '''(1)不同训练模式下参数的设定:设置nms前、nms后保留的框的数量'''
        if self.parent_model.training:
            n_pre_nms = self.n_train_pre_nms
            n_post_nms = self.n_train_post_nms
        else:
            n_pre_nms = self.n_test_pre_nms
            n_post_nms = self.n_test_post_nms

        '''(2)网络预测边界框解码,返回rpn框的坐标(y,x,y,x):之前RPN的预测坐标为相对于anchor box的偏移,解码为坐标(y,x,y,x),将坐标超出图片的边界进行裁剪'''
        roi = loc2bbox(anchor, loc)# loc为rpn产生的框,是相对于anchor box的偏移(t_y,t_x,t_h,t_w)
        #对预测边界框的坐标进行处理
        # clip(数组,最小值,最大值):将数组中的元素限制在a_min, a_max之间,大于a_max的就使得它等于 a_max,小于a_min,的就使得它等于a_min
        roi[:, slice(0, 4, 2)] = np.clip(roi[:, slice(0, 4, 2)], 0, img_size[0])
        roi[:, slice(1, 4, 2)] = np.clip(roi[:, slice(1, 4, 2)], 0, img_size[1])

        '''(3)去除宽和高不满足阈值的rpn框:得到筛选后的网络预测边界框(roi)和边界框的前景类分数(score)'''
        # 删除高度或宽度<阈值的预测框。scale为缩放原图像的缩放因子。
        min_size = self.min_size * scale
        #预测框的高和宽:
        hs = roi[:, 2] - roi[:, 0]
        ws = roi[:, 3] - roi[:, 1]
        keep = np.where((hs >= min_size) & (ws >= min_size))[0]
        roi = roi[keep, :]
        score = score[keep]

        # Sort all (proposal, score) pairs by score from highest to lowest.
        # Take top pre_nms_topN (e.g. 6000).
        '''(4)非极大值抑制前,保留n_pre_nm个rpn预测框(前景类分数最大的前n_pre_nm个rpn框) '''
        #对边界框的前景类分数进行排序,保留前n_pre_nms个边界框
        order = score.ravel().argsort()[::-1]
        if n_pre_nms > 0:
            order = order[:n_pre_nms]
        roi = roi[order, :]
        score = score[order]

        # Apply nms (e.g. threshold = 0.7).
        # Take after_nms_topN (e.g. 300).

        '''(5)非极大值抑制'''
        keep = nms( torch.from_numpy(roi).cuda(),torch.from_numpy(score).cuda(),self.nms_thresh)

        '''(6)非极大值抑制后保留的n_post_nms个rpn预测框——最终ROI'''
        if n_post_nms > 0:
            keep = keep[:n_post_nms]
        roi = roi[keep.cpu().numpy()]

        return roi#返回ROI(输入到头部网络的rpn框,坐标为y,x,y,x格式)
  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值