本篇博客是我学习(https://blog.csdn.net/weixin_44791964)博主写的pytorch的ssd的博客后写的,大家可以直接去看这位博主的博客(https://blog.csdn.net/weixin_44791964/article/details/104981486)。这位博主在b站还有配套视频,传送门:(https://www.bilibili.com/video/BV1A7411976Z)。这位博主的在GitHub的源代码(https://github.com/bubbliiiing/ssd-pytorch)。 侵删
————————————————
版权声明:本文为CSDN博主「ni_cai_ya」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ni_cai_ya/article/details/105724160
今天就康康先验框是怎么回事儿
下面是先验框的生成部分
import numpy as np
from utils.config import Config
from math import sqrt as sqrt
from itertools import product as product
import matplotlib.pyplot as plt
#先验框生成的部分
mean = []
#首先对feature_maps进行循环,feature_maps就是有效特征层的长和宽的大小(也就是我们的32x32,19x19等特征层)
#Config["feature_maps"]也就是我们的config.py的feature_maps键
for k, f in enumerate(Config["feature_maps"]):
#这里进行网格的生成
x,y = np.meshgrid(np.arange(f),np.arange(f))
x = x.reshape(-1)
y = y.reshape(-1)
#对我们获得的网格中心进行一个循环(其实我们的网格就是网格的中心)
for i, j in zip(y,x):
# print(x,y)
"""
首先利用我们原始图片的大小除以步长,相当于把我们的图像平均划分成了同等大小的区域
例如我们的300/8等于37.5,四舍五入后就是38,相当于把图片划分为38x38的网格
"""
f_k = Config["min_dim"] / Config["steps"][k]
# 计算网格的中心,cx和cy就是网格中心的坐标
cx = (j + 0.5) / f_k
cy = (i + 0.5) / f_k
#下面两步操作会获得比较小的和比较大的正方形
"""
求先验框的边长,这个是求小正方形的边长,用Config["min_sizes"]除以Config["min_dim"]
例如38x38的有效特征层对应的是30/300,这样我们就获得了一个比较小的正方形的先验框,
"""
s_k = Config["min_sizes"][k]/Config["min_dim"]
#cx, cy正方形中心坐标(也是网格的中心坐标,s_k正方形的边长)
mean += [cx, cy, s_k, s_k]
"""
求长边,这里和上面求小正方形的操作是一样的,区别在于,这里求的是大正方形的
例如,例如38x38的有效特征层对应的是60/300
"""
s_k_prime = sqrt(s_k * (Config["max_sizes"][k]/Config["min_dim"]))
mean += [cx, cy, s_k_prime, s_k_prime]
# 获得长方形的先验框
for ar in Config["aspect_ratios"][k]:
mean += [cx, cy, s_k*sqrt(ar), s_k/sqrt(ar)]
mean += [cx, cy, s_k/sqrt(ar), s_k*sqrt(ar)]
#将先验框调整,使其介于0和一之间
mean = np.clip(mean,0,1)
mean = np.reshape(mean,[-1,4])*Config["min_dim"]
#先验框可视化的部分
linx = np.linspace(0.5 * Config["steps"][4], Config["min_dim"] - 0.5 * Config["steps"][4],
Config["feature_maps"][4])
liny = np.linspace(0.5 * Config["steps"][4], Config["min_dim"] - 0.5 * Config["steps"][4],
Config["feature_maps"][4])
print("linx:",linx)
print("liny:",liny)
centers_x, centers_y = np.meshgrid(linx, liny)
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ylim(-100,500)
plt.xlim(-100,500)
plt.scatter(centers_x,centers_y)
step_start = 8708
step_end = 8712
# step_start = 8728
# step_end = 8732
box_widths = mean[step_start:step_end,2]
box_heights = mean[step_start:step_end,3]
prior_boxes = np.zeros_like(mean[step_start:step_end,:])
prior_boxes[:,0] = mean[step_start:step_end,0]
prior_boxes[:,1] = mean[step_start:step_end,1]
prior_boxes[:,0] = mean[step_start:step_end,0]
prior_boxes[:,1] = mean[step_start:step_end,1]
# 获得先验框的左上角和右下角
prior_boxes[:, 0] -= box_widths/2
prior_boxes[:, 1] -= box_heights/2
prior_boxes[:, 2] += box_widths/2
prior_boxes[:, 3] += box_heights/2
rect1 = plt.Rectangle([prior_boxes[0, 0],prior_boxes[0, 1]],box_widths[0],box_heights[0],color="r",fill=False)
rect2 = plt.Rectangle([prior_boxes[1, 0],prior_boxes[1, 1]],box_widths[1],box_heights[1],color="r",fill=False)
rect3 = plt.Rectangle([prior_boxes[2, 0],prior_boxes[2, 1]],box_widths[2],box_heights[2],color="r",fill=False)
rect4 = plt.Rectangle([prior_boxes[3, 0],prior_boxes[3, 1]],box_widths[3],box_heights[3],color="r",fill=False)
ax.add_patch(rect1)
ax.add_patch(rect2)
ax.add_patch(rect3)
ax.add_patch(rect4)
plt.show()
print(np.shape(mean))
通过上述代码,我们已经获得了先验框,目前我们已经有回归预测结果和分类预测结果了以及先验框
下面的代码就是通过我们的回归预测结果和分类预测结果以及我们的先验框,来得到我们的预测框。
Config = {
'num_classes': 21,
'feature_maps': [38, 19, 10, 5, 3, 1],
'min_dim': 300,
'steps': [8, 16, 32, 64, 100, 300],
'min_sizes': [30, 60, 111, 162, 213, 264],
'max_sizes': [60, 111, 162, 213, 264, 315],
'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
'variance': [0.1, 0.2],
'clip': True,
'name': 'VOC',
}
```python
class Detect(Function):
def __init__(self, num_classes, bkg_label, top_k, conf_thresh, nms_thresh):
self.num_classes = num_classes
self.background_label = bkg_label
self.top_k = top_k
self.nms_thresh = nms_thresh
if nms_thresh <= 0:
raise ValueError('nms_threshold must be non negative.')
self.conf_thresh = conf_thresh
self.variance = Config['variance']
#一共有三个输入,分别是回归预测结果,分类预测结果,和先验框
def forward(self, loc_data, conf_data, prior_data):
loc_data = loc_data.cpu()
conf_data = conf_data.cpu()
#首先获得了图片的数量,也就是一共传入进来了多少张图片
num = loc_data.size(0)
#这里获得了我们先验框的数量,对于ssd来说,一共有8732个先验框
num_priors = prior_data.size(0)
#创建一个output的阵列,用于存放我们的输出
output = torch.zeros(num, self.num_classes, self.top_k, 5)
#将我们传入进来的分类预测的结果进行shape的改变,同时对第二个维度和第一个维度进行反转transpose(2, 1)
conf_preds = conf_data.view(num, num_priors,
self.num_classes).transpose(2, 1)
# 对每一张图片进行处理,进行了一个解码的过程
for i in range(num):
# 利用回归预测结果,对先验框解码获得预测框,下面代码在box_utils.py的decode有实现
decoded_boxes = decode(loc_data[i], prior_data, self.variance)
#然后取出一张图片所有先验框的种类
conf_scores = conf_preds[i].clone()
#对所有先验框的种类进行一次循环
for cl in range(1, self.num_classes):
# 对每一类进行非极大抑制
"""
进行非极大抑制的目的:
我们有时候可能在图片的某一个物体上有很多框框,然后这些框框都会指向这一个物体
但是我们只需要一个最精确的框框就好了,
然后非极大抑制就回去判断同一个区域里面,判断框框的重合程度,取出得分最高的那个框框
此时,得分最高的框框可能就是预测最精确的框框
"""
c_mask = conf_scores[cl].gt(self.conf_thresh)
scores = conf_scores[cl][c_mask]
if scores.size(0) == 0:
continue
l_mask = c_mask.unsqueeze(1).expand_as(decoded_boxes)
boxes = decoded_boxes[l_mask].view(-1, 4)
# 进行非极大抑制
ids, count = nms(boxes, scores, self.nms_thresh, self.top_k)
output[i, cl, :count] = \
torch.cat((scores[ids[:count]].unsqueeze(1),
boxes[ids[:count]]), 1)
flt = output.contiguous().view(num, -1, 5)
_, idx = flt[:, :, 0].sort(1, descending=True)
_, rank = idx.sort(1)
flt[(rank < self.top_k).unsqueeze(-1).expand_as(flt)].fill_(0)
return output
def decode(loc, priors, variances):
boxes = torch.cat((
#首先计算先验框调整之后的中心的位置
priors[:, :2] + loc[:, :2] * variances[0] * priors[:, 2:],
#计算出调整后的先验框的宽和高
priors[:, 2:] * torch.exp(loc[:, 2:] * variances[1])), 1)
#计算先验框的左上角,
boxes[:, :2] -= boxes[:, 2:] / 2
#计算先验框的右下角
boxes[:, 2:] += boxes[:, :2]
return boxes
总结
首先获得先验框(根据网格数量的不同,先验框的数量不同),然后根据我们之前的分类和回归的结果,得到预测框