在pytorch 实现fater_rcnn1中,我们已经实现到RNP网络层,接下来,我们要将最后一层进行处理了。还是首先将整个流程图放上来:
我们这篇文章要做的就是samploe_rois之后的步骤,其实说来说去,也就两个步骤,一个是在特征图里面进行抠图,第二个操作就是将抠出来的图放到网络组训练,看代码之前,咱先得了解一下roi pooling的意思,推荐:ROI Pooling解释
了解之后咱直接看看抠图的代码:
'''author:nike hu'''
def cut_out(featuremap, rios):
'''featuremap的维度是[batchsize, 512, w, h], rios的维度是[batchsize, 128, 4],最后输出的维度是[batchszize, 128, 512, 7, 7]'''
rios = rios.long()
'''接下来对rios进行处理,因为很可能坐标会超过featuremap的尺寸'''
rios[..., 0] = torch.clamp(rios[..., 0], min=0, max=62)
rios[..., 2] = torch.clamp(rios[..., 2], min=0, max=62)
rios[..., 1] = torch.clamp(rios[..., 1], min=0, max=37)
rios[..., 3] = torch.clamp(rios[..., 3], min=0, max=37)
last_out = torch.zeros(1, 128, 512, 7, 7)
for i in range(featuremap.shape[0]): # 对每张照片分别处理
out_map = torch.zeros(1, 512, 7, 7) # 创建一个为0的用于后面的合并
for j in range(rios.size(1)): # 对每个anchor分别进行抠图操作
if rios[i][i][2] - rios[i][j][0] < 7: # 如果截取的框小于7,那么要处理一下
if rios[i][j][0] > 2:
rios[i][j][0] -= 2
if rios[i][i][2] < 60:
rios[i][i][2] += 2
if rios[i][j][3] - rios[i][i][1] < 7: # 如果截取的框小于7,那么要处理一下,否则下面的torch.nn.functional.adaptive_max_pool2d要出错
if rios[i][j][3] < 35:
rios[i][j][3] += 2
if rios[i][j][1] >2:
rios[i][j][1] -= 2
out_cut_map = featuremap[i, :, rios[i][j][0]: rios[i][j][2], rios[i][j][1]:rios[i][j][3]]
# 这里就相当于抠图的操作,将我们想要的范围从featuremap中抠出来,维度为[512, n,m]
try:
roi_pool_map = torch.nn.functional.adaptive_max_pool2d(out_cut_map, output_size=7, return_indices=False)
# 这里就是pytorch自带的roipolling操作,第二个参数代表roi操作后的输出的尺寸,至于卷积核这些会自动分配
except Exception as e:
print(rios[i][j][0], rios[i][j][1], rios[i][j][2], rios[i][j][3], out_cut_map.shape, featuremap[i].shape)
print(e)
roi_pool_map = roi_pool_map.unsqueeze(0) # 增加一个维度用于合并
out_map = torch.cat((out_map, roi_pool_map), dim=0) # 合并
out_map = out_map[1:] # 去掉第一行的数据
last_out = torch.cat((last_out, out_map.unsqueeze(0))) # 再次合并,注意维度的扩充
last_out = last_out[1:] # 第一列要舍去
return last_out
经过这一步之后,我们可以得到一组数据,我们接下来更新一下网络,大家直接看代码吧:
'''author:nike hu'''
class Faster_rcnn(nn.Module):
def __init__(self, gt_groud):
super(Faster_rcnn, self).__init__()
self.gt_groud = gt_groud # 这个是标签的信息
self.net_body = Model.vgg16().features[0:30] # 这里截取官方给的vgg模型的前30层,到这一层就是主体部分
'''接下来构建RPN网络,这里的网络有分支,所以不好用一个nn.sequential直接操作'''
self.RPN_1 = nn.Sequential(
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True)
)
self.RPN_2_1 = nn.Sequential(
nn.Conv2d(512, 18, kernel_size=1), # 输出维度为[b, 18, w, h], 18=9×2(9个anchor,每个anchor二分类,使用交叉熵损失),
nn.ReLU(True) # 这个必须得跟上,否者最后模型跑出的值会有负数
)
self.RPN_2_2 = nn.Sequential(
nn.Conv2d(512, 36, kernel_size=1), # 进行回归的卷积核通道数为36=9×4(9个anchor,每个anchor有4个位置参数)
nn.ReLU(True)
)
self.fc = Model.vgg16().classifier[:6] # 这里截取vgg模型的分类层的最后一层之前的层数
self.lable = nn.Sequential(
nn.Linear(4096, 21), # 这里得21代表类别,会根据你的训练数据集的类别变化
nn.ReLU(True)
)
self.loc = nn.Sequential(
nn.Linear(4096, 84), # 这里得84=21*4,21代表类别数,会根据你的训练数据集的类别变化,4代表坐标参数
nn.ReLU(True)
)
def forward(self, x):
x = self.net_body(x)
feature = x # 这个参数是vgg主干层输出的,要单独拿出去用
x = self.RPN_1(x)
x1 = self.RPN_2_1(x)
x2 = self.RPN_2_2(x)
out1 = torch.cat((x1, x2), dim=1) # 这里是rpn网络输出的数据,后续用来计算rpn部分的loss
rois = do_rios(x1, x2)
last_rois = proposal_target_creator(rois, self.gt_groud)
roi_plooing_feature = cut_out(feature, last_rois) # 这里执行抠图操作,最后维度是[batchsize, 128, 512, 7, 7]
roi_plooing_feature = roi_plooing_feature.view(roi_plooing_feature.size(0), 128, -1) # 变化维度,将最后三个维度合并为一个维度,维度变为[batchsize, 128, 25088]
fc_feature = self.fc(roi_plooing_feature) # 这里输出维度变为[batchsize, 128, 4096]
label = self.lable(fc_feature) # 得到预测的类别数据
location = self.loc(fc_feature) # 得到预测的坐标数据
return label, location
其实也就是增加了几个全连接层,然后将网络前面的输出数据进行处理了一下,经过这里得处理,我们就可以得到图中的FC_21和FC81这两个数据,这两个数据我们另外去处理,对这些数据进行筛选,求loss这些操作,对这些操作,我理了一下思路
1:我们要得到最后预测的类别和坐标,首先就要要选出每一行概率最大的类别当成置信度,然后根据置信度进行筛选排序,然后还得进行一次nms筛选,将iou比较高的边框去掉,经过这些操作,就可以在图片上画边框了。
2:我们要求得loss函数,在RPN网络有一个loss,在roipolling这一层还有一个loss ,对于RPN的loss, 里面有前景和背景的二分类的Loss,还有坐标的loss,二分类的loss我们使用torch.BCEloss(),坐标的loss我们使用,但是这里存在一个问题,就是RPN网络输出的坐标数据的维度和gt_groud的数据的维度不一样,这里我们要处理一下,可以跟ssd里面的处理方法一样:我们先把类别数据和坐标数据进行合并,然后根据anchor是前景的概率大小筛选一些anchor,然后结合边框的数据进行处理,最后维度为[m,n,4]这里得m代表原图中有多少个标记的边框,n代表跟这些边框的IOU比较大的anchor,4就代表坐标参数了,然后我们要将这些坐标参数转化为真实的坐标,这个转化函数在最上面发的文章里面有。接下来我们就可以选择每个边框iou最大的那个anchor,组成维度为[m,4]的数据,这样,就可以求RPN部分的loss了,或者我们可以选择每个边框iou最大的前几个anchor,组成维度[m, n, 4]然后转化一下维度[n, m, 4],然后求n个anchor和这些anchor对应的边框的总的loss,最后求一个平均值。而对于roi_polling层的loss处理,处理思路差不多,只是这里预测的类别是21类,而rpn层的预测类别是2,我们将这21个类别的预测的最大值当成置信度来筛选一些边框就行,还有就是这里21个类别的loss计算要使用多类别的loss函数,我们可以使用nn.CrossEntropyLoss()这个类。
好了,我估计这后面的loss部分的解析我是不会做了,感觉有些麻烦,不想再去做了,反正大概的思路已经理清了,而且说真的,现在faster_rcnn最初版的研究的人也不会太多的,看我的faster_rcnn的人也不会太多,反正整个流程已经梳理完了,就不折腾了。
2020 4.29