睿智的目标检测27——Pytorch搭建Faster R-CNN目标检测平台

  • 学习前言
  • 什么是FasterRCNN目标检测算法
  • 源码下载
  • Faster-RCNN实现思路
    • 一、预测部分
      • 1、主干网络介绍
      • 2、获得Proposal建议框
      • 3、Proposal建议框的解码
      • 4、对Proposal建议框加以利用(RoiPoolingConv)
      • 5、在原图上进行绘制
      • 6、整体的执行流程
    • 二、训练部分
      • 1、建议框网络的训练
      • 2、Roi网络的训练
  • 训练自己的Faster-RCNN模型
    • 一、数据集的准备
    • 二、数据集的处理
    • 三、开始网络训练
    • 四、训练结果预测

学习前言

好的pytorch版本也应该有个faster rcnn。

什么是FasterRCNN目标检测算法


Faster-RCNN是一个非常有效的目标检测算法,虽然是一个比较早的论文, 但它至今仍是许多目标检测算法的基础。

Faster-RCNN作为一种two-stage的算法,与one-stage的算法相比,two-stage的算法更加复杂且速度较慢,但是检测精度会更高。

事实上也确实是这样,Faster-RCNN的检测效果非常不错,但是检测速度与训练速度有待提高。

源码下载

https://github.com/bubbliiiing/faster-rcnn-pytorch
喜欢的可以点个star噢。

Faster-RCNN实现思路

一、预测部分

1、主干网络介绍


Faster-RCNN可以采用多种的主干特征提取网络,常用的有VGG,Resnet,Xception等等,本文以Resnet网络为例子来给大家演示一下。

Faster-Rcnn对输入进来的图片尺寸没有固定,但是一般会把输入进来的图片短边固定成600,如输入一张1200x1800的图片,会把图片不失真的resize到600x900上。

ResNet50有两个基本的块,分别名为Conv Block和Identity Block,其中Conv Block输入和输出的维度是不一样的,所以不能连续串联,它的作用是改变网络的维度;Identity Block输入维度和输出维度相同,可以串联,用于加深网络的。
Conv Block的结构如下:

Identity Block的结构如下:

这两个都是残差网络结构。

Faster-RCNN的主干特征提取网络部分只包含了长宽压缩了四次的内容,第五次压缩后的内容在ROI中使用。即Faster-RCNN在主干特征提取网络所用的网络层如图所示。

以输入的图片为600x600为例,shape变化如下:

最后一层的输出就是公用特征层。

在代码里里面,我们使用resnet50()函数来获得resnet50的公用特征层。

其中features部分为公用特征层,classifier部分为第二阶段用到的分类器。

import mathimport torch.nn as nn
from torchvision.models.utils import load_state_dict_from_urlclass Bottleneck(nn.Module):expansion = 4def __init__(self, inplanes, planes, stride=1, downsample=None):super(Bottleneck, self).__init__()self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, stride=stride, bias=False)self.bn1 = nn.BatchNorm2d(planes)self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(planes)self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)self.bn3 = nn.BatchNorm2d(planes * 4)self.relu = nn.ReLU(inplace=True)self.downsample = downsampleself.stride = stridedef forward(self, x):residual = xout = self.conv1(x)out = self.bn1(out)out = self.relu(out)out = self.conv2(out)out = self.bn2(out)out = self.relu(out)out = self.conv3(out)out = self.bn3(out)if self.downsample is not None:residual = self.downsample(x)out += residualout = self.relu(out)return outclass ResNet(nn.Module):def __init__(self, block, layers, num_classes=1000):#-----------------------------------##   假设输入进来的图片是600,600,3#-----------------------------------#self.inplanes = 64super(ResNet, self).__init__()# 600,600,3 -> 300,300,64self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)self.bn1 = nn.BatchNorm2d(64)self.relu = nn.ReLU(inplace=True)# 300,300,64 -> 150,150,64self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=0, ceil_mode=True)# 150,150,64 -> 150,150,256self.layer1 = self._make_layer(block, 64, layers[0])# 150,150,256 -> 75,75,512self.layer2 = self._make_layer(block, 128, layers[1], stride=2)# 75,75,512 -> 38,38,1024 到这里可以获得一个38,38,1024的共享特征层self.layer3 = self._make_layer(block, 256, layers[2], stride=2)# self.layer4被用在classifier模型中self.layer4 = self._make_layer(block, 512, layers[3], stride=2)self.avgpool = nn.AvgPool2d(7)self.fc = nn.Linear(512 * block.expansion, num_classes)for m in self.modules():if isinstance(m, nn.Conv2d):n = m.kernel_size[0] * m.kernel_size[1] * m.out_channelsm.weight.data.normal_(0, math.sqrt(2. / n))elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()def _make_layer(self, block, planes, blocks, stride=1):downsample = None#-------------------------------------------------------------------##   当模型需要进行高和宽的压缩的时候,就需要用到残差边的downsample#-------------------------------------------------------------------#if stride != 1 or self.inplanes != planes * block.expansion:downsample = nn.Sequential(nn.Conv2d(self.inplanes, planes * block.expansion,kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(planes * block.expansion),)layers = []layers.append(block(self.inplanes, planes, stride, downsample))self.inplanes = planes * block.expansionfor i in range(1, blocks):layers.append(block(self.inplanes, planes))return nn.Sequential(*layers)def forward(self, x):x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = self.avgpool(x)x = x.view(x.size(0), -1)x = self.fc(x)return xdef resnet50(pretrained = False):model = ResNet(Bottleneck, [3, 4, 6, 3])if pretrained:state_dict = load_state_dict_from_url("https://download.pytorch.org/models/resnet50-19c8e357.pth", model_dir="./model_data")model.load_state_dict(state_dict)#----------------------------------------------------------------------------##   获取特征提取部分,从conv1到model.layer3,最终获得一个38,38,1024的特征层#----------------------------------------------------------------------------#features    = list([model.conv1, model.bn1, model.relu, model.maxpool, model.layer1, model.layer2, model.layer3])#----------------------------------------------------------------------------##   获取分类部分,从model.layer4到model.avgpool#----------------------------------------------------------------------------#classifier  = list([model.layer4, model.avgpool])features    = nn.Sequential(*features)classifier  = nn.Sequential(*classifier)return features, classifier

2、获得Proposal建议框


获得的公用特征层在图像中就是Feature Map,其有两个应用,一个是和ROIPooling结合使用、另一个是进行一次3x3的卷积后,进行一个18通道的1x1卷积,还有一个36通道的1x1卷积。

在Faster-RCNN中,num_priors也就是先验框的数量就是9,所以两个1x1卷积的结果实际上也就是:

9 x 4的卷积 用于预测 公用特征层上 每一个网格点上 每一个先验框的变化情况。(为什么说是变化情况呢,这是因为Faster-RCNN的预测结果需要结合先验框获得预测框,预测结果就是先验框的变化情况。)

9 x 2的卷积 用于预测 公用特征层上 每一个网格点上 每一个预测框内部是否包含了物体,序号为1的内容为包含物体的概率

当我们输入的图片的shape是600x600x3的时候,公用特征层的shape就是38x38x1024,相当于把输入进来的图像分割成38x38的网格,然后每个网格存在9个先验框,这些先验框有不同的大小,在图像上密密麻麻。

9 x 4的卷积的结果会对这些先验框进行调整,获得一个新的框。
9 x 2的卷积会判断上述获得的新框是否包含物体。

到这里我们可以获得了一些有用的框,这些框会利用9 x 2的卷积判断是否存在物体。

到此位置还只是粗略的一个框的获取,也就是一个建议框。然后我们会在建议框里面继续找东西。

实现代码为:

class RegionProposalNetwork(nn.Module):def __init__(self, in_channels     = 512, mid_channels    = 512, ratios          = [0.5, 1, 2],anchor_scales   = [8, 16, 32], feat_stride     = 16,mode            = "training",):super(RegionProposalNetwork, self).__init__()#-----------------------------------------##   生成基础先验框,shape为[9, 4]#-----------------------------------------#self.anchor_base    = generate_anchor_base(anchor_scales = anchor_scales, ratios = ratios)n_anchor            = self.anchor_base.shape[0]#-----------------------------------------##   先进行一个3x3的卷积,可理解为特征整合#-----------------------------------------#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)#-----------------------------------------##   特征点间距步长#-----------------------------------------#self.feat_stride    = feat_stride#-----------------------------------------##   用于对建议框解码并进行非极大抑制#-----------------------------------------#self.proposal_layer = ProposalCreator(mode)#--------------------------------------##   对FPN的网络部分进行权值初始化#--------------------------------------#normal_init(self.conv1, 0, 0.01)normal_init(self.score, 0, 0.01)normal_init(self.loc, 0, 0.01)def forward(self, x, img_size, scale=1.):n, _, h, w = x.shape#-----------------------------------------##   先进行一个3x3的卷积,可理解为特征整合#-----------------------------------------#x = F.relu(self.conv1(x))#-----------------------------------------##   回归预测对先验框进行调整#-----------------------------------------#rpn_locs = self.loc(x)rpn_locs = rpn_locs.permute(0, 2, 3, 1).contiguous().view(n, -1, 4)#-----------------------------------------##   分类预测先验框内部是否包含物体#-----------------------------------------#rpn_scores = self.score(x)rpn_scores = rpn_scores.permute(0, 2, 3, 1).contiguous().view(n, -1, 2)#--------------------------------------------------------------------------------------##   进行softmax概率计算,每个先验框只有两个判别结果#   内部包含物体或者内部不包含物体,rpn_softmax_scores[:, :, 1]的内容为包含物体的概率#--------------------------------------------------------------------------------------#rpn_softmax_scores  = F.softmax(rpn_scores, dim=-1)rpn_fg_scores       = rpn_softmax_scores[:, :, 1].contiguous()rpn_fg_scores       = rpn_fg_scores.view(n, -1)

3、Proposal建议框的解码

通过第二步我们获得了38x38x9个先验框的预测结果。预测结果包含两部分。

9 x 4的卷积 用于预测 公用特征层上 每一个网格点上 每一个先验框的变化情况。**

9 x 2的卷积 用于预测 公用特征层上 每一个网格点上 每一个预测框内部是否包含了物体。

相当于就是将整个图像分成38x38个网格;然后从每个网格中心建立9个先验框,一共38x38x9个,12996个先验框。

当输入图像shape不同时,先验框的数量也会发生改变。

先验框虽然可以代表一定的框的位置信息与框的大小信息,但是其是有限的,无法表示任意情况,因此还需要调整。

9 x 4中的9表示了这个网格点所包含的先验框数量,其中的4表示了框的中心与长宽的调整情况。

实现代码如下:

class ProposalCreator():def __init__(self, mode, nms_iou             = 0.7,n_train_pre_nms     = 12000,n_train_post_nms    = 600,n_test_pre_nms      = 3000,n_test_post_nms     = 300,min_size            = 16):#-----------------------------------##   设置预测还是训练#-----------------------------------#self.mode               = mode#-----------------------------------##   建议框非极大抑制的iou大小#-----------------------------------#self.nms_iou            = nms_iou#-----------------------------------##   训练用到的建议框数量#-----------------------------------#self.n_train_pre_nms    = n_train_pre_nmsself.n_train_post_nms   = n_train_post_nms#-----------------------------------##   预测用到的建议框数量#-----------------------------------#self.n_test_pre_nms     = n_test_pre_nmsself.n_test_post_nms    = n_test_post_nmsself.min_size           = min_sizedef __call__(self, loc, score, anchor, img_size, scale=1.):if self.mode == "training":n_pre_nms   = self.n_train_pre_nmsn_post_nms  = self.n_train_post_nmselse:n_pre_nms   = self.n_test_pre_nmsn_post_nms  = self.n_test_post_nms#-----------------------------------##   将先验框转换成tensor#-----------------------------------#anchor = torch.from_numpy(anchor)if loc.is_cuda:anchor = anchor.cuda()#-----------------------------------##   将RPN网络预测结果转化成建议框#-----------------------------------#roi = loc2bbox(anchor, loc)#-----------------------------------##   防止建议框超出图像边缘#-----------------------------------#roi[:, [0, 2]] = torch.clamp(roi[:, [0, 2]], min = 0, max = img_size[1])roi[:, [1, 3]] = torch.clamp(roi[:, [1, 3]], min = 0, max = img_size[0])#-----------------------------------##   建议框的宽高的最小值不可以小于16#-----------------------------------#min_size    = self.min_size * scalekeep        = torch.where(((roi[:, 2] - roi[:, 0]) >= min_size) & ((roi[:, 3] - roi[:, 1]) >= min_size))[0]#-----------------------------------##   将对应的建议框保留下来#-----------------------------------#roi         = roi[keep, :]score       = score[keep]#-----------------------------------##   根据得分进行排序,取出建议框#-----------------------------------#order       = torch.argsort(score, descending=True)if n_pre_nms > 0:order   = order[:n_pre_nms]roi     = roi[order, :]score   = score[order]#-----------------------------------##   对建议框进行非极大抑制#   使用官方的非极大抑制会快非常多#-----------------------------------#keep    = nms(roi, score, self.nms_iou)keep    = keep[:n_post_nms]roi     = roi[keep]return roi

4、对Proposal建议框加以利用(RoiPoolingConv)


让我们对建议框有一个整体的理解:
事实上建议框就是对图片哪一个区域有物体存在进行初步筛选。

通过主干特征提取网络,我们可以获得一个公用特征层,当输入图片为600x600x3的时候,它的shape是38x38x1024,然后建议框会对这个公用特征层进行截取。

其实公用特征层里面的38x38对应着图片里的38x38个区域,38x38中的每一个点相当于这个区域内部所有特征的浓缩。

建议框会对这38x38个区域进行截取,也就是认为这些区域里存在目标,然后将截取的结果进行resize,resize到14x14x1024的大小。

然后再对每个建议框再进行Resnet原有的第五次压缩。压缩完后进行一个平均池化,再进行一个Flatten,最后分别进行一个num_classes的全连接和(num_classes)x4全连接。

num_classes的全连接用于对最后获得的框进行分类,(num_classes)x4全连接用于对相应的建议框进行调整。

通过这些操作,我们可以获得所有建议框的调整情况,和这个建议框调整后框内物体的类别。

事实上,在上一步获得的建议框就是ROI的先验框

对Proposal建议框加以利用的过程与shape变化如图所示:

建议框调整后的结果就是最终的预测结果了,可以在图上进行绘画了。

class Resnet50RoIHead(nn.Module):def __init__(self, n_class, roi_size, spatial_scale, classifier):super(Resnet50RoIHead, self).__init__()self.classifier = classifier#--------------------------------------##   对ROIPooling后的的结果进行回归预测#--------------------------------------#self.cls_loc = nn.Linear(2048, n_class * 4)#-----------------------------------##   对ROIPooling后的的结果进行分类#-----------------------------------#self.score = nn.Linear(2048, n_class)#-----------------------------------##   权值初始化#-----------------------------------#normal_init(self.cls_loc, 0, 0.001)normal_init(self.score, 0, 0.01)self.roi = RoIPool((roi_size, roi_size), spatial_scale)def forward(self, x, rois, roi_indices, img_size):n, _, _, _ = x.shapeif x.is_cuda:roi_indices = roi_indices.cuda()rois = rois.cuda()rois_feature_map = torch.zeros_like(rois)rois_feature_map[:, [0,2]] = rois[:, [0,2]] / img_size[1] * x.size()[3]rois_feature_map[:, [1,3]] = rois[:, [1,3]] / img_size[0] * x.size()[2]indices_and_rois = torch.cat([roi_indices[:, None], rois_feature_map], dim=1)#-----------------------------------##   利用建议框对公用特征层进行截取#-----------------------------------#pool = self.roi(x, indices_and_rois)#-----------------------------------##   利用classifier网络进行特征提取#-----------------------------------#fc7 = self.classifier(pool)#--------------------------------------------------------------##   当输入为一张图片的时候,这里获得的f7的shape为[300, 2048]#--------------------------------------------------------------#fc7 = fc7.view(fc7.size(0), -1)roi_cls_locs    = self.cls_loc(fc7)roi_scores      = self.score(fc7)roi_cls_locs    = roi_cls_locs.view(n, -1, roi_cls_locs.size(1))roi_scores      = roi_scores.view(n, -1, roi_scores.size(1))return roi_cls_locs, roi_scores

5、在原图上进行绘制

在第四步的结尾,我们对建议框进行再一次进行解码后,我们可以获得预测框在原图上的位置,而且这些预测框都是经过筛选的。这些筛选后的框可以直接绘制在图片上,就可以获得结果了。

6、整体的执行流程


几个小tip:
1、共包含了两次解码过程。
2、先进行粗略的筛选再细调。
3、第一次获得的建议框解码后的结果是对共享特征层featuremap进行截取。

二、训练部分

Faster-RCNN的训练过程和它的预测过程一样,分为两部分,首先要训练获得建议框网络,然后再训练后面利用ROI获得预测结果的网络。

1、建议框网络的训练

公用特征层如果要获得建议框的预测结果,需要再进行一次3x3的卷积后,进行一个2通道的1x1卷积,还有一个36通道的1x1卷积。

在Faster-RCNN中,num_priors也就是先验框的数量就是9,所以两个1x1卷积的结果实际上也就是:

9 x 4的卷积 用于预测 公用特征层上 每一个网格点上 每一个先验框的变化情况。(为什么说是变化情况呢,这是因为Faster-RCNN的预测结果需要结合先验框获得预测框,预测结果就是先验框的变化情况。)

9 x 2的卷积 用于预测 公用特征层上 每一个网格点上 每一个预测框内部是否包含了物体。

也就是说,我们直接利用Faster-RCNN建议框网络预测到的结果,并不是建议框在图片上的真实位置,需要解码才能得到真实位置。

而在训练的时候,我们需要计算loss函数,这个loss函数是相对于Faster-RCNN建议框网络的预测结果的。我们需要把图片输入到当前的Faster-RCNN建议框的网络中,得到建议框的结果;同时还需要进行编码,这个编码是把真实框的位置信息格式转化为Faster-RCNN建议框预测结果的格式信息

也就是,我们需要找到 每一张用于训练的图片每一个真实框对应的先验框,并求出如果想要得到这样一个真实框,我们的建议框预测结果应该是怎么样的。

从建议框预测结果获得真实框的过程被称作解码,而从真实框获得建议框预测结果的过程就是编码的过程。

因此我们只需要将解码过程逆过来就是编码过程了。

实现代码如下:

def bbox_iou(bbox_a, bbox_b):if bbox_a.shape[1] != 4 or bbox_b.shape[1] != 4:print(bbox_a, bbox_b)raise IndexErrortl = np.maximum(bbox_a[:, None, :2], bbox_b[:, :2])br = np.minimum(bbox_a[:, None, 2:], bbox_b[:, 2:])area_i = np.prod(br - tl, axis=2) * (tl < br).all(axis=2)area_a = np.prod(bbox_a[:, 2:] - bbox_a[:, :2], axis=1)area_b = np.prod(bbox_b[:, 2:] - bbox_b[:, :2], axis=1)return area_i / (area_a[:, None] + area_b - area_i)def bbox2loc(src_bbox, dst_bbox):width = src_bbox[:, 2] - src_bbox[:, 0]height = src_bbox[:, 3] - src_bbox[:, 1]ctr_x = src_bbox[:, 0] + 0.5 * widthctr_y = src_bbox[:, 1] + 0.5 * heightbase_width = dst_bbox[:, 2] - dst_bbox[:, 0]base_height = dst_bbox[:, 3] - dst_bbox[:, 1]base_ctr_x = dst_bbox[:, 0] + 0.5 * base_widthbase_ctr_y = dst_bbox[:, 1] + 0.5 * base_heighteps = np.finfo(height.dtype).epswidth = np.maximum(width, eps)height = np.maximum(height, eps)dx = (base_ctr_x - ctr_x) / widthdy = (base_ctr_y - ctr_y) / heightdw = np.log(base_width / width)dh = np.log(base_height / height)loc = np.vstack((dx, dy, dw, dh)).transpose()return locclass AnchorTargetCreator(object):def __init__(self, n_sample=256, pos_iou_thresh=0.7, neg_iou_thresh=0.3, pos_ratio=0.5):self.n_sample       = n_sampleself.pos_iou_thresh = pos_iou_threshself.neg_iou_thresh = neg_iou_threshself.pos_ratio      = pos_ratiodef __call__(self, bbox, anchor):argmax_ious, label = self._create_label(anchor, bbox)if (label > 0).any():loc = bbox2loc(anchor, bbox[argmax_ious])return loc, labelelse:return np.zeros_like(anchor), labeldef _calc_ious(self, anchor, bbox):#----------------------------------------------##   anchor和bbox的iou#   获得的ious的shape为[num_anchors, num_gt]#----------------------------------------------#ious = bbox_iou(anchor, bbox)if len(bbox)==0:return np.zeros(len(anchor), np.int32), np.zeros(len(anchor)), np.zeros(len(bbox))#---------------------------------------------------------##   获得每一个先验框最对应的真实框  [num_anchors, ]#---------------------------------------------------------#argmax_ious = ious.argmax(axis=1)#---------------------------------------------------------##   找出每一个先验框最对应的真实框的iou  [num_anchors, ]#---------------------------------------------------------#max_ious = np.max(ious, axis=1)#---------------------------------------------------------##   获得每一个真实框最对应的先验框  [num_gt, ]#---------------------------------------------------------#gt_argmax_ious = ious.argmax(axis=0)#---------------------------------------------------------##   保证每一个真实框都存在对应的先验框#---------------------------------------------------------#for i in range(len(gt_argmax_ious)):argmax_ious[gt_argmax_ious[i]] = ireturn argmax_ious, max_ious, gt_argmax_iousdef _create_label(self, anchor, bbox):# ------------------------------------------ ##   1是正样本,0是负样本,-1忽略#   初始化的时候全部设置为-1# ------------------------------------------ #label = np.empty((len(anchor),), dtype=np.int32)label.fill(-1)# ------------------------------------------------------------------------ ##   argmax_ious为每个先验框对应的最大的真实框的序号         [num_anchors, ]#   max_ious为每个真实框对应的最大的真实框的iou             [num_anchors, ]#   gt_argmax_ious为每一个真实框对应的最大的先验框的序号    [num_gt, ]# ------------------------------------------------------------------------ #argmax_ious, max_ious, gt_argmax_ious = self._calc_ious(anchor, bbox)# ----------------------------------------------------- ##   如果小于门限值则设置为负样本#   如果大于门限值则设置为正样本#   每个真实框至少对应一个先验框# ----------------------------------------------------- #label[max_ious < self.neg_iou_thresh] = 0label[max_ious >= self.pos_iou_thresh] = 1if len(gt_argmax_ious)>0:label[gt_argmax_ious] = 1# ----------------------------------------------------- ##   判断正样本数量是否大于128,如果大于则限制在128# ----------------------------------------------------- #n_pos = int(self.pos_ratio * self.n_sample)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# ----------------------------------------------------- ##   平衡正负样本,保持总数量为256# ----------------------------------------------------- #n_neg = self.n_sample - np.sum(label == 1)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] = -1return argmax_ious, label

focal会忽略一些重合度相对较高但是不是非常高的先验框,一般将重合度在0.3-0.7之间的先验框进行忽略。

2、Roi网络的训练

通过上一步已经可以对建议框网络进行训练了,建议框网络会提供一些位置的建议,在ROI网络部分,其会将建议框根据进行一定的截取,并获得对应的预测结果,事实上就是将上一步建议框当作了ROI网络的先验框。

因此,我们需要计算所有建议框和真实框的重合程度,并进行筛选,如果某个真实框和建议框的重合程度大于0.5则认为该建议框为正样本,如果重合程度小于0.5则认为该建议框为负样本

因此我们可以对真实框进行编码,这个编码是相对于建议框的,也就是,当我们存在这些建议框的时候,我们的ROI预测网络需要有什么样的预测结果才能将这些建议框调整成真实框。

每次训练我们都放入128个建议框进行训练,同时要注意正负样本的平衡。
实现代码如下:

class ProposalTargetCreator(object):def __init__(self, n_sample=128, pos_ratio=0.5, pos_iou_thresh=0.5, neg_iou_thresh_high=0.5, neg_iou_thresh_low=0):self.n_sample = n_sampleself.pos_ratio = pos_ratioself.pos_roi_per_image = np.round(self.n_sample * self.pos_ratio)self.pos_iou_thresh = pos_iou_threshself.neg_iou_thresh_high = neg_iou_thresh_highself.neg_iou_thresh_low = neg_iou_thresh_lowdef __call__(self, roi, bbox, label, loc_normalize_std=(0.1, 0.1, 0.2, 0.2)):roi = np.concatenate((roi.detach().cpu().numpy(), bbox), axis=0)# ----------------------------------------------------- ##   计算建议框和真实框的重合程度# ----------------------------------------------------- #iou = bbox_iou(roi, bbox)if len(bbox)==0:gt_assignment = np.zeros(len(roi), np.int32)max_iou = np.zeros(len(roi))gt_roi_label = np.zeros(len(roi))else:#---------------------------------------------------------##   获得每一个建议框最对应的真实框  [num_roi, ]#---------------------------------------------------------#gt_assignment = iou.argmax(axis=1)#---------------------------------------------------------##   获得每一个建议框最对应的真实框的iou  [num_roi, ]#---------------------------------------------------------#max_iou = iou.max(axis=1)#---------------------------------------------------------##   真实框的标签要+1因为有背景的存在#---------------------------------------------------------#gt_roi_label = label[gt_assignment] + 1#----------------------------------------------------------------##   满足建议框和真实框重合程度大于neg_iou_thresh_high的作为负样本#   将正样本的数量限制在self.pos_roi_per_image以内#----------------------------------------------------------------#pos_index = np.where(max_iou >= self.pos_iou_thresh)[0]pos_roi_per_this_image = int(min(self.pos_roi_per_image, pos_index.size))if pos_index.size > 0:pos_index = np.random.choice(pos_index, size=pos_roi_per_this_image, replace=False)#-----------------------------------------------------------------------------------------------------##   满足建议框和真实框重合程度小于neg_iou_thresh_high大于neg_iou_thresh_low作为负样本#   将正样本的数量和负样本的数量的总和固定成self.n_sample#-----------------------------------------------------------------------------------------------------#neg_index = np.where((max_iou < self.neg_iou_thresh_high) & (max_iou >= self.neg_iou_thresh_low))[0]neg_roi_per_this_image = self.n_sample - pos_roi_per_this_imageneg_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)#---------------------------------------------------------##   sample_roi      [n_sample, ]#   gt_roi_loc      [n_sample, 4]#   gt_roi_label    [n_sample, ]#---------------------------------------------------------#keep_index = np.append(pos_index, neg_index)sample_roi = roi[keep_index]if len(bbox)==0:return sample_roi, np.zeros_like(sample_roi), gt_roi_label[keep_index]gt_roi_loc = bbox2loc(sample_roi, bbox[gt_assignment[keep_index]])gt_roi_loc = (gt_roi_loc / np.array(loc_normalize_std, np.float32))gt_roi_label = gt_roi_label[keep_index]gt_roi_label[pos_roi_per_this_image:] = 0return sample_roi, gt_roi_loc, gt_roi_label

训练自己的Faster-RCNN模型

首先前往Github下载对应的仓库,下载完后利用解压软件解压,之后用编程软件打开文件夹。
注意打开的根目录必须正确,否则相对目录不正确的情况下,代码将无法运行。

一定要注意打开后的根目录是文件存放的目录。

一、数据集的准备

本文使用VOC格式进行训练,训练前需要自己制作好数据集,如果没有自己的数据集,可以通过Github连接下载VOC12+07的数据集尝试下。
训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。

训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。

此时数据集的摆放已经结束。

二、数据集的处理

在完成数据集的摆放之后,我们需要对数据集进行下一步的处理,目的是获得训练用的2007_train.txt以及2007_val.txt,需要用到根目录下的voc_annotation.py。

voc_annotation.py里面有一些参数需要设置。
分别是annotation_mode、classes_path、trainval_percent、train_percent、VOCdevkit_path,第一次训练可以仅修改classes_path

'''
annotation_mode用于指定该文件运行时计算的内容
annotation_mode为0代表整个标签处理过程,包括获得VOCdevkit/VOC2007/ImageSets里面的txt以及训练用的2007_train.txt、2007_val.txt
annotation_mode为1代表获得VOCdevkit/VOC2007/ImageSets里面的txt
annotation_mode为2代表获得训练用的2007_train.txt、2007_val.txt
'''
annotation_mode     = 0
'''
必须要修改,用于生成2007_train.txt、2007_val.txt的目标信息
与训练和预测所用的classes_path一致即可
如果生成的2007_train.txt里面没有目标信息
那么就是因为classes没有设定正确
仅在annotation_mode为0和2的时候有效
'''
classes_path        = 'model_data/voc_classes.txt'
'''
trainval_percent用于指定(训练集+验证集)与测试集的比例,默认情况下 (训练集+验证集):测试集 = 9:1
train_percent用于指定(训练集+验证集)中训练集与验证集的比例,默认情况下 训练集:验证集 = 9:1
仅在annotation_mode为0和1的时候有效
'''
trainval_percent    = 0.9
train_percent       = 0.9
'''
指向VOC数据集所在的文件夹
默认指向根目录下的VOC数据集
'''
VOCdevkit_path  = 'VOCdevkit'

classes_path用于指向检测类别所对应的txt,以voc数据集为例,我们用的txt为:

训练自己的数据集时,可以自己建立一个cls_classes.txt,里面写自己所需要区分的类别。

三、开始网络训练

通过voc_annotation.py我们已经生成了2007_train.txt以及2007_val.txt,此时我们可以开始训练了。
训练的参数较多,大家可以在下载库后仔细看注释,其中最重要的部分依然是train.py里的classes_path。

classes_path用于指向检测类别所对应的txt,这个txt和voc_annotation.py里面的txt一样!训练自己的数据集必须要修改!

修改完classes_path后就可以运行train.py开始训练了,在训练多个epoch后,权值会生成在logs文件夹中。
其它参数的作用如下:

#-------------------------------#
#   是否使用Cuda
#   没有GPU可以设置成False
#-------------------------------#
Cuda = True
#--------------------------------------------------------#
#   训练前一定要修改classes_path,使其对应自己的数据集
#--------------------------------------------------------#
classes_path    = 'model_data/voc_classes.txt'
#----------------------------------------------------------------------------------------------------------------------------#
#   权值文件请看README,百度网盘下载。数据的预训练权重对不同数据集是通用的,因为特征是通用的。
#   预训练权重对于99%的情况都必须要用,不用的话权值太过随机,特征提取效果不明显,网络训练的结果也不会好。
#
#   如果想要断点续练就将model_path设置成logs文件夹下已经训练的权值文件。
#   当model_path = ''的时候不加载整个模型的权值。
#
#   此处使用的是整个模型的权重,因此是在train.py进行加载的,下面的pretrain不影响此处的权值加载。
#   如果想要让模型从主干的预训练权值开始训练,则设置model_path = '',下面的pretrain = True,此时仅加载主干。
#   如果想要让模型从0开始训练,则设置model_path = '',下面的pretrain = Fasle,Freeze_Train = Fasle,此时从0开始训练,且没有冻结主干的过程。
#   一般来讲,从0开始训练效果会很差,因为权值太过随机,特征提取效果不明显。
#----------------------------------------------------------------------------------------------------------------------------#
model_path      = 'model_data/voc_weights_resnet.pth'
#------------------------------------------------------#
#   输入的shape大小
#------------------------------------------------------#
input_shape     = [600, 600]
#---------------------------------------------#
#   vgg或者resnet50
#---------------------------------------------#
backbone        = "resnet50"
#----------------------------------------------------------------------------------------------------------------------------#
#   是否使用主干网络的预训练权重,此处使用的是主干的权重,因此是在模型构建的时候进行加载的。
#   如果设置了model_path,则主干的权值无需加载,pretrained的值无意义。
#   如果不设置model_path,pretrained = True,此时仅加载主干开始训练。
#   如果不设置model_path,pretrained = False,Freeze_Train = Fasle,此时从0开始训练,且没有冻结主干的过程。
#----------------------------------------------------------------------------------------------------------------------------#
pretrained      = False
#------------------------------------------------------------------------#
#   anchors_size用于设定先验框的大小,每个特征点均存在9个先验框。
#   anchors_size每个数对应3个先验框。
#   当anchors_size = [8, 16, 32]的时候,生成的先验框宽高约为:
#   [90, 180] ; [180, 360]; [360, 720]; [128, 128];
#   [256, 256]; [512, 512]; [180, 90] ; [360, 180];
#   [720, 360]; 详情查看anchors.py
#   如果想要检测小物体,可以减小anchors_size靠前的数。
#   比如设置anchors_size = [4, 16, 32]
#------------------------------------------------------------------------#
anchors_size    = [8, 16, 32]#----------------------------------------------------#
#   训练分为两个阶段,分别是冻结阶段和解冻阶段。
#   显存不足与数据集大小无关,提示显存不足请调小batch_size。
#----------------------------------------------------#
#----------------------------------------------------#
#   冻结阶段训练参数
#   此时模型的主干被冻结了,特征提取网络不发生改变
#   占用的显存较小,仅对网络进行微调
#----------------------------------------------------#
Init_Epoch          = 0
Freeze_Epoch        = 50
Freeze_batch_size   = 4
Freeze_lr           = 1e-4
#----------------------------------------------------#
#   解冻阶段训练参数
#   此时模型的主干不被冻结了,特征提取网络会发生改变
#   占用的显存较大,网络所有的参数都会发生改变
#----------------------------------------------------#
UnFreeze_Epoch      = 100
Unfreeze_batch_size = 2
Unfreeze_lr         = 1e-5
#------------------------------------------------------#
#   是否进行冻结训练,默认先冻结主干训练后解冻训练。
#------------------------------------------------------#
Freeze_Train        = True
#------------------------------------------------------#
#   用于设置是否使用多线程读取数据
#   开启后会加快数据读取速度,但是会占用更多内存
#   内存较小的电脑可以设置为2或者0
#------------------------------------------------------#
num_workers         = 4
#----------------------------------------------------#
#   获得图片路径和标签
#----------------------------------------------------#
train_annotation_path   = '2007_train.txt'
val_annotation_path     = '2007_val.txt'

四、训练结果预测

训练结果预测需要用到两个文件,分别是yolo.py和predict.py。
我们首先需要去yolo.py里面修改model_path以及classes_path,这两个参数必须要修改。

model_path指向训练好的权值文件,在logs文件夹里。
classes_path指向检测类别所对应的txt。


完成修改后就可以运行predict.py进行检测了。运行后输入图片路径即可检测。

睿智的目标检测27——Pytorch搭建Faster R-CNN目标检测平台相关推荐

  1. Faster R CNN

    Faster R CNN 3 FASTER R-CNN 我们的Faster R CNN 由两个模块组成,第一个模块是 proposes regions 的全卷积网络,第二个是使用 proposed r ...

  2. 睿智的目标检测35——Pytorch搭建YoloV4-Tiny目标检测平台

    睿智的目标检测35--Pytorch搭建YoloV4-Tiny目标检测平台 学习前言 什么是YOLOV4-Tiny 代码下载 YoloV4-Tiny结构解析 1.主干特征提取网络Backbone 2. ...

  3. 睿智的目标检测30——Pytorch搭建YoloV4目标检测平台

    睿智的目标检测30--Pytorch搭建YoloV4目标检测平台 学习前言 什么是YOLOV4 代码下载 YOLOV4改进的部分(不完全) YOLOV4结构解析 1.主干特征提取网络Backbone ...

  4. 睿智的目标检测61——Pytorch搭建YoloV7目标检测平台

    睿智的目标检测61--Pytorch搭建YoloV7目标检测平台 学习前言 源码下载 YoloV7改进的部分(不完全) YoloV7实现思路 一.整体结构解析 二.网络结构解析 1.主干网络Backb ...

  5. 睿智的目标检测36——Pytorch搭建Efficientdet目标检测平台

    睿智的目标检测33--Pytorch搭建Efficientdet目标检测平台 学习前言 什么是Efficientdet目标检测算法 源码下载 Efficientdet实现思路 一.预测部分 1.主干网 ...

  6. 睿智的目标检测56——Pytorch搭建YoloV5目标检测平台

    睿智的目标检测56--Pytorch搭建YoloV5目标检测平台 学习前言 源码下载 YoloV5改进的部分(不完全) YoloV5实现思路 一.整体结构解析 二.网络结构解析 1.主干网络Backb ...

  7. 睿智的目标检测53——Pytorch搭建YoloX目标检测平台

    睿智的目标检测53--Pytorch搭建YoloX目标检测平台 学习前言 源码下载 YoloX改进的部分(不完全) YoloX实现思路 一.整体结构解析 二.网络结构解析 1.主干网络CSPDarkn ...

  8. 睿智的目标检测23——Pytorch搭建SSD目标检测平台

    睿智的目标检测23--Pytorch搭建SSD目标检测平台 学习前言 什么是SSD目标检测算法 源码下载 SSD实现思路 一.预测部分 1.主干网络介绍 2.从特征获取预测结果 3.预测结果的解码 4 ...

  9. 睿智的目标检测66——Pytorch搭建YoloV8目标检测平台

    睿智的目标检测66--Pytorch搭建YoloV8目标检测平台 学习前言 源码下载 YoloV8改进的部分(不完全) YoloV8实现思路 一.整体结构解析 二.网络结构解析 1.主干网络Backb ...

  10. 憨批的语义分割重制版9——Pytorch 搭建自己的DeeplabV3+语义分割平台

    憨批的语义分割重制版9--Pytorch 搭建自己的DeeplabV3+语义分割平台 注意事项 学习前言 什么是DeeplabV3+模型 代码下载 DeeplabV3+实现思路 一.预测部分 1.主干 ...

最新文章

  1. RHEL5.5学习--安装vmtools
  2. 30年前未曾发行的任天堂红白机游戏,被这个团队从21张软盘中重新恢复了,还是3D的...
  3. CryptoZombies学习笔记——Lesson4
  4. Linux系统卡慢之调优方法
  5. myeclipse怎么如何激活
  6. 2017阿里云代码管理服务公测上线
  7. 自动化测试:Selenium webdriver 学习笔记-C#版(四)
  8. 论计算机在教学中的作用论文,计算机在教学中的应用
  9. 项目经理的个人体会、经验总结
  10. 机器学习导论(张志华):核定义(2)
  11. 安全专家在硬盘固件中发现NSA的网络间谍程序
  12. 小白兔生小白兔-菲波拉契数列问题
  13. markdown入门2-插入图片
  14. 资源分享|平面设计师可参考的素材网站
  15. mysql读写分离实战
  16. linux释放分区命令,Linux fdisk命令操作磁盘(添加、删除、转换分区等)
  17. C#--图表控件(Chart)
  18. 学习Linux命令(11) startx
  19. 【winPE系统下如何安装游戏手柄】
  20. 使用reduce实现数组扁平化

热门文章

  1. 《剑指offer》读后感
  2. 有赞云支付php接口,有赞个人免签支付设置
  3. 服务器 备案 文档,备案需要备案服务器
  4. 触动-20181130
  5. 几何光学学习笔记(30)-6.5光通量和光亮度在光学系统中的传递、像面光照度
  6. GPU 编程 CPU 异同点_22年后再战显卡市场 分析师:英特尔GPU不会构成威胁
  7. ceph 写流程(1)
  8. 【云主机迁移原理】华为云主机迁移服务SMS的原理分析
  9. 调用百度大脑AI开放平台接口实现java+web的图像识别技术
  10. 图片rar 加密文件