目标检测中的偏移量是什么,附代码实现

文章介绍了目标检测模型训练过程中锚框的处理,包括如何为锚框分配类别标签和计算偏移量,以及如何通过掩码矩阵处理负类锚框。偏移量的计算涉及中心坐标和相对大小的归一化变换,以利于模型训练。此外,文章提供了一个具体示例来说明这些概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一:回顾                

二: 标记类别和偏移量

 三:计算偏移量

四:一个例子

所有项目代码+UI界面


一:回顾                

        上一节课我们了解了目标检测基础中的将真实边界框分配给锚框,在训练集中,我们将每个锚框视为一个训练样本。 为了训练目标检测模型,我们需要每个锚框的类别(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界面

视频,笔记和代码,以及注释都已经上传网盘,放在主页置顶文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值