目录
一:回顾
上一节课我们了解了目标检测基础中的将真实边界框分配给锚框,在训练集中,我们将每个锚框视为一个训练样本。 为了训练目标检测模型,我们需要每个锚框的类别(class)和偏移量(offset)标签,其中前者是与锚框相关的对象的类别,后者是真实边界框相对于锚框的偏移量。 在预测时,我们为每个图像生成多个锚框,预测所有锚框的类别和偏移量,根据预测的偏移量调整它们的位置以获得预测的边界框,最后只输出符合特定条件的预测边界框。
二: 标记类别和偏移量
现在我们可以为每个锚框标记类别和偏移量了。 假设一个锚框A被分配了一个真实边界框B。 一方面,锚框A的类别将被标记为与B相同。 另一方面,锚框A的偏移量将根据B和A中心坐标的相对位置以及这两个框的相对大小进行标记。 鉴于数据集内不同的框的位置和大小不同,我们可以对那些相对位置和大小应用变换,使其获得分布更均匀且易于拟合的偏移量。 这里介绍一种常见的变换。 给定框A和B,中心坐标分别为(Xa,Xa)和(Xb,Ya),宽度分别为Wa和Wb,高度分别为ℎa和ℎb,可以将A的偏移量标记为:
为什么需要除以一个数或者log,类似于归一化操作?
因为考虑到梯度计算,不能让梯度太大,所以需要设置较小的值。
三:计算偏移量
def offset_anchors(anchors,gtboxs_lisks_anchors,device,eps=1e-6):
#gtbox_lisks_anchors是根据anchors分配的gtbox,扩展成anchors数量一样多的矩阵,用于计算偏移量
anchors,gtboxs_lisks_anchors = anchors.to(device),gtboxs_lisks_anchors.to(device)
num_batch = anchors.shape[0]
off_anchors = []
center_anchors = corner_to_center(anchors)
center_gtboxs = corner_to_center(gtboxs_lisks_anchors)
#计算偏移量
for i in range(num_batch):
offset_x = center_gtboxs[i,:,0] - center_anchors[i,:,0] / center_anchors[i,:,3]
offset_y = center_gtboxs[i,:,1] - center_anchors[i,:,1] / center_anchors[i,:,2]
offset_h = torch.log(eps + center_gtboxs[i,:,2]/center_anchors[i,:,2])
offset_w = torch.log(eps + center_gtboxs[i,:,3]/center_anchors[i,:,3])
off_anchor = torch.stack((offset_x,offset_y,offset_h,offset_w),dim=1)
off_anchors.append(off_anchor)
off_anchors = torch.cat([*off_anchors],dim=0).reshape(num_batch,-1,4)
return off_anchors
如果一个锚框没有被分配真实边界框,我们只需将锚框的类别标记为背景(background)。 背景类别的锚框通常被称为负类锚框,其余的被称为正类锚框。 我们使用真实边界框(labels
参数)实现以下multibox_target
函数,来标记锚框的类别和偏移量(anchors
参数)。 此函数将背景类别的索引设置为零,然后将新类别的整数索引递增一。
注意:除了真实边界框(包括填充)个数小于锚框个数时,false才有-1出现意思是如果没有填充类,也就是当类别个数等于像素点的锚框个数的时候。gtbox_lisks_anchor[_idx]因为_idx是所有从0开始的所有坐标,值就是gtbox,但是如果类别个数小于锚框个数时,就有填充类出现,
其中 gtbox_lisks_anchor[_idx] = gtboxs[i,:,1:][idx_]得到的是真实锚框的坐标(也是锚框经过筛选后与这个真实框比较相似的),取出坐标,与anchors计算偏移量。虽然不知道这个锚框到底跟哪个真是边界框相似,但是可以通过_idx和idx_来找到。
#给锚框分配真实边界框以及标记为此类别,锚框与真实边界框的偏移量,真实的掩码矩阵
def offset_anchor_gtbox(anchors,gtboxs,device,threshold=0.1):
anchors,gtboxs = anchors.to(device),gtboxs.to(device)
num_batch = anchors.shape[0]
#得出上个函数:真实边界框分配给锚框
cls_anchors_false,cls_anchors_true = assign_gtbox_to_anchor(anchors, gtboxs, device=device, threshold=threshold)
print('cls_anchors_false is :',cls_anchors_false)
print('cls_anchors_true is :',cls_anchors_true)
mask_anchors = []
off_anchors = []
#制作掩码矩阵,处理随机制作的真实边界(-1)
for i in range(num_batch):
# (cls_anchors_true[i,:]>=0)是放在有填充的无效真实边界框
# .long()转true为1将false转换为0,cls_anchors_true[i,:]只拿出来了一维,升一个维度,repeat后与对应坐标全部置为0或者不变,因为只有true和false
mask_anchor = (cls_anchors_true[i,:]>=0).long().unsqueeze(1).repeat(1,4) #复制4列,行是一个像素点的锚框个数
# print('mask_anchor is ',mask_anchor)
#利用cls_anchors_false创建gtbox_lisks_anchor
gtbox_lisks_anchor = torch.full(anchors[i,:,:].shape,0,dtype=torch.float,device=device)#建立一个全0的形状为anchor的矩阵
_idx = torch.nonzero(cls_anchors_false[i,:].ravel()>=0).ravel()#除了真实边界框(包括填充)个数小于锚框个数时,false才有-1出现
idx_ = cls_anchors_false[i,:][(cls_anchors_false[i,:]>=0)]#从cls_anchors_false获取大于0的索引对应的值
#赋值给gtboxs_lisk_anchor来建立一个和anchor一样大的,跟anchor中每一个锚框一一对应的真实边界框坐标组成的矩阵,文档有图片
print('_idx是_idx is:',_idx)
print('idx_是idx_ is:',idx_)
gtbox_lisks_anchor[_idx] = gtboxs[i,:,1:][idx_]
#计算偏移量,anchors[i,:,:]只取了最后两列,batch去掉了,需要升维,每个函数都是在batch基础上进行的,mask_anchors可以去掉负类边界框
#返回的off_anchor是真正的偏移量,已经去除了没有用的框
#上面操作的是真实边界框小于锚框。已经去除了一行,这里又用mask_anchor去除了填充类虽然我定义了四个类别,有一个是填充类。
off_anchor = offset_anchors(anchors[i,:,:].unsqueeze(0),gtbox_lisks_anchor.unsqueeze(0),device=device)*mask_anchor
mask_anchors.append(mask_anchor)
off_anchors.append(off_anchor)
#真实锚框类别,将负类变成0,正类从1开始计数
#因为在训练的过程中,模型只能分类从0开始的,例argmax
cls_anchors_true += 1
mask_anchors = torch.cat([*mask_anchors],dim=0).reshape(num_batch,-1,4)
off_anchors = torch.cat([*off_anchors],dim=0).reshape(num_batch,-1,4)
return cls_anchors_true.long(),mask_anchors,off_anchors
四:一个例子
下面通过一个具体的例子来说明锚框标签。 我们已经为加载图像中的狗和猫定义了真实边界框,其中第一个元素是类别(0代表狗,1代表猫),其余四个元素是左上角和右下角的(x,y)轴坐标(范围介于0和1之间)。 我们还构建了五个锚框,用左上角和右下角的坐标进行标记:A0,…,A4(索引从0开始)。 然后我们在图像中绘制这些真实边界框和锚框。
anchors = torch.randn(1,5,4)
gtboex = torch.randn(1,4,5)
gtboex[0,:,0] = torch.tensor([5,7,8,-1])
cls_anchor_true,mask_anchors,off_anchors = offset_anchor_gtbox(anchors,gtboex,device='cpu',threshold=0.1)
print('cls_anchor_true is',cls_anchor_true)
print('mask_anchors is',mask_anchors)
print('off_anchors is',off_anchors)
其中mask_anchor是掩码矩阵用来生成的列表形式的偏移量,对于负分类的锚框或者填充的边界框,就不需要进行方向传播再去计算他们损失量了了。所以要把没用的剔除掉,这需要用到掩码矩阵。
如果锚框检测到的是真实的物体,就置为1,否则置为 0。这样反向传播的时候,值为0就不会进行反向传播。 所以需要掩码矩阵的生成。先筛选大于0的数,然后升维度,然后repeat成与锚框一样的大小,让他们对应相乘得到掩码矩阵。
偏移量是中心坐标
max的用法
W,h的偏移量是nan,说明生成的真实边界框是一条竖线,不过真实的框是不可能这样的
gtbox有五个坐标,第一个是类别,后面四个是偏移量。所以修改只需要修改第0列的就行了
所有项目代码+UI界面
视频,笔记和代码,以及注释都已经上传网盘,放在主页置顶文章