OD with new tech

大部人还是跟随潮流,把最近热门的技术看看是否能迁移过来。于是……看看

Transformer

DERT——2020

特点

  • 利用transformer,将目标检测问题转换为无序集合预测问题【更加直接,目标检测本身其实就是个无序集合的问题】
  • 可解释性好;比如对于一个query id=1,那么得到的输出应该是类别为1这个目标的可能所在地;自注意力机制可以理解为对于某一个点,会将同属于这一个物体的像素都给attention到
  • 不需要设置anchor,超参数极少,不需要NMS

结构

  • 先用CNN提取特征,论文中用ResNet50【所有BN固定,stem「数据预处理层」和第一个stage不进行更新,学习率小于transformer】image: (batch, 3, 800, 1200) -> ResNet50 -> (batch, 1024, 25, 38)

  • 将特征变为特征序列,论文中仅用了最后一个stage,并进行降维(batch, 1024, 25, 38) -> 1*1 conv -> (batch, 256, 25, 38)

  • 将特征序列与位置信息特征相加后,输入到编码器中,输出长度为N的无序集合(N=100, batch, 256);再将输出送入一组解码器中,【解码器的个数越多越好,但是效率的问题,因此论文选择了6个】输出向量(6, batch, 100, 256)

    • N是指定的,表示一张图片最多包含的物体数目;但不足够的,用no object填充,表示为背景类别;论文中取了100,需要考虑不同数据集

    • 这个集合中每个元素包含物体类别和坐标

    • 编码器:Q、K表示图像特征和位置编码的信息和;V表示图像特征;Object queries作为解码器中引入的新Q,询问某一个Object是否在,然后根据图像的信息做相似计算,那么这个位置的特征就会有一个加权输出。

    • 位置信息特征编码这里用了sincos模式,需要考虑x,y两个方向

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      #输入是b,c,h,w
      #tensor_list的类型是NestedTensor,内部自动附加了mask,
      #用于表示动态shape,是pytorch中tensor新特性https://github.com/pytorch/nestedtensor
      x = tensor_list.tensors # 原始tensor数据
      # 附加的mask,shape是b,h,w 全是false
      mask = tensor_list.mask
      not_mask = ~mask
      # 1 1 1 1 .. 2 2 2 2... 3 3 3...
      y_embed = not_mask.cumsum(1, dtype=torch.float32)
      # 1 2 3 4 ... 1 2 3 4...
      x_embed = not_mask.cumsum(2, dtype=torch.float32)
      if self.normalize:
      eps = 1e-6
      y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale
      x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale
      # 0~127 self.num_pos_feats=128,因为前面输入向量是256,编码是一半sin,一半cos
      dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
      # 归一化
      dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats)
      # 输出shape=(b,h,w,128)
      pos_x = x_embed[:, :, :, None] / dim_t
      pos_y = y_embed[:, :, :, None] / dim_t
      pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)
      pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)
      pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
      # 每个特征图的xy位置都编码成256的向量,其中前128是y方向编码,而128是x方向编码
      return pos # (b,n=256,h,w)
    • 与原始transformer的不同之处:

      • DERT对每个编码器都输入了同一个位置编码向量;而原始的只在第一个编码器中输入了位置编码向量
      • DERT将位置编码向量只加入到了QK中,V中没有加入
      • DERT解码时将N个无序框并行输出(因为框的顺序不重要,前后框之间也没有什么特别的关系,因此通过一个全0的查询向量,作为BOS_WORD即可);原始的是按照句子按序解码输出的,并且会运用到前面所有解码器的输出信息
      • 额外引入可学习的Object queries,相当于可学习的anchor,提供全局注意力
        • 可以认为是输出位置编码,主要是在学习过程中提供目标对象和全局图像之间的关系;相当于全局注意力【相当于是一个说明目标相对位置关系的东西,类似于anchor,只是这个是可学习的】
        • 但是每个object query都和全局的特征图做attention交互,这本质上也是dense。
  • 将最后一个解码器的输出,送入分类和回归的head中,得到N个无序集合

  • 将无序的预测结果进行后处理,正负样本分配是one-to-one的双边匹配算法optimal bipartite matching——匈牙利算法来匹配【最优分配问题】,提取得到前景类别和对应bbox的坐标。相当于把无序的结果排了个序。【这里实际上比one-stage那么多的anchor匹配好;也不需要two-stage先进行proposal的操作】

    1
    2
    # 根据A和B集合两两元素之间的连接权重,根据重要性进行内部最优匹配
    scipy.optimize.linear_sum_assignment()
  • 将预测的坐标,复原到原始图片上,即乘以(800, 1200).

算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def match(outputs, targets):
# detr分类输出,(batch,num_queries,num_classes)
bs, num_queries = outputs["pred_logits"].shape[:2]
# 得到概率输出(batch*num_queries,num_classes)
out_prob = outputs["pred_logits"].flatten(0, 1).softmax(-1)
# 得到bbox分支输出(batch,num_queries,num_classes,4)
out_bbox = outputs["pred_boxes"].flatten(0, 1)
# 准备分类target shape=(m,)里面存储的是类别索引,m包括了整个batch内部的所有gt bbox
tgt_ids = torch.cat([v["labels"] for v in targets])
# 准备bbox target shape=(m,4),已经归一化了
tgt_bbox = torch.cat([v["boxes"] for v in targets])
#核心
#batch*num_queries,num_classes->batch*num_queries,m,对于每个预测结果,把目前gt里面有的所有类别值提取出来,其余值不需要参与匹配
cost_class = -out_prob[:, tgt_ids]  
#计算out_bbox和tgt_bbox两两之间的l1距离 (batch*num_queries,m)
cost_bbox = torch.cdist(out_bbox, tgt_bbox, p=1)
#额外多计算一个giou loss (batch*num_queries,m)
cost_giou = -generalized_box_iou(box_cxcywh_to_xyxy(out_bbox), box_cxcywh_to_xyxy(tgt_bbox))
#得到最终的广义距离(batch*num_queries,m),距离越小越可能是最优匹配
C = self.cost_bbox * cost_bbox + self.cost_class * cost_class + self.cost_giou * cost_giou
# (batch*num_queries,m)--> (batch, num_queries,m)
C = C.view(bs, num_queries, -1).cpu()
#计算每个batch内部有多少物体,后续计算时候按照单张图片进行匹配,没必要batch级别匹配,徒增计算
sizes = [len(v["boxes"]) for v in targets]
#匈牙利最优匹配,返回匹配索引
indices = [linear_sum_assignment(c[i]) for i, c in enumerate(C.split(sizes, -1))]
return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]
def loss_labels(self, outputs, targets, indices, num_boxes, log=True):
#shape是(batch,num_queries,num_classes)
src_logits = outputs['pred_logits']
  #得到匹配后索引,作用在label上
idx = self._get_src_permutation_idx(indices)
#得到匹配后的分类target
target_classes_o = torch.cat([t["labels"][J] for t, (_, J) in zip(targets, indices)])
#加入背景(self.num_classes),补齐bx100个
target_classes = torch.full(src_logits.shape[:2], self.num_classes,
dtype=torch.int64, device=src_logits.device)
#shape是(b,100,),存储的是索引,不是one-hot
target_classes[idx] = target_classes_o
#计算ce loss,self.empty_weight前景和背景权重是1和0.1,克服类别不平衡
loss_ce = F.cross_entropy(src_logits.transpose(1, 2), target_classes, self.empty_weight)
losses = {'loss_ce': loss_ce}
return losses
def loss_boxes(self, outputs, targets, indices, num_boxes):
src_boxes = outputs['pred_boxes'][idx]
idx = self._get_src_permutation_idx(indices)
target_boxes_o = torch.cat([t['boxes'][i] for t, (_, i) in zip(targets, indices)], dim=0)
target_boxes = torch.full(src_boxes.shape, -1,
dtype=torch.double, device=src_boxes.device)
target_boxes[idx] = target_boxes_o
#l1 loss
loss_bbox = F.l1_loss(src_boxes, target_boxes, reduction='none')
losses = {}
losses['loss_bbox'] = loss_bbox.sum() / num_boxes
#giou loss
loss_giou = 1 - torch.diag(box_ops.generalized_box_iou(
box_ops.box_cxcywh_to_xyxy(src_boxes),
box_ops.box_cxcywh_to_xyxy(target_boxes)))
losses['loss_giou'] = loss_giou.sum() / num_boxes
return losses

RelationNet++:Bridging Visual Representations for Object Detection via Transformer Decoder——NeurlPS2020

针对的问题

  • 现有的目标检测框架通常建立在object/part表示的单一格式上,由于异构或非均一性,通常很难将这些表示形式组合(从不同方面提取网络特征)到单个框架中,因此用了注意力机制,以充分利用每种优势。目前的表示有:
    • anchor/proposal矩形框
    • 中心点:对分类好
    • 角点(CornerNet):对定位好

特点

  • 提出了一种基于注意力的解码器模块:BVR,类似于transformer,将不同的visual representations bridge起来。

结构

BVR模块

  • 由于特征图上的每一点都可以作为注意力机制中的Key,但是这样导致特征点过多,同时几何项很耗费计算与内存资源

    • Key采样,topk采样,用max pooling操作
    • 共享位置嵌入:为了减少复杂度
  • Decoder:multi-head attention

    • Point head网络预测Key的值:auxilliary特征作为key

    • 用key去增强query的主特征

      S表示query和key在外观和几何上的相似度,由于query和key的几何特征不一致(bbox是4d,点的表达是2d),因此先从bbox中提取中心点或角点得到2d表示之后,再计算相似性;外观相似性通过内积计算;几何相似性用小网络计算得到相对位置信息

      T将Key的外观特征通过线性变换得到value特征;

      计算出的attention,直接加在原始的query特征上

Non-local

输入信号x(feature map),首先用f计算i和j的相似度(等效于attention中计算q和k的相似度),然后和g函数计算出的j位置表示相乘(等效于attention中的positional embedding),最终通过响应因子C(x)进行标准化处理。

GNN

转载请注明出处,谢谢。

愿 我是你的小太阳

买糖果去喽