1. Grid创建 YOLOv3是一个单阶段的目标检测器,将目标划分为不同的grid,每个grid分配3个anchor作为先验框来进行匹配。首先读一下代码中关于grid创建的部分。
首先了解一下pytorch中的API:torch.mershgrid
举一个简单的例子就比较清楚了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Python 3.7 .3 (default, Apr 24 2019 , 15 :29 :51 ) [MSC v.1915 64 bit (AMD64)] :: Anaconda, Inc. on win32 Type "help" , "copyright" , "credits" or "license" for more information.>>> import torch>>> a = torch.arange(3 )>>> b = torch.arange(5 )>>> x,y = torch.meshgrid(a,b)>>> atensor([0 , 1 , 2 ]) >>> btensor([0 , 1 , 2 , 3 , 4 ]) >>> xtensor([[0 , 0 , 0 , 0 , 0 ], [1 , 1 , 1 , 1 , 1 ], [2 , 2 , 2 , 2 , 2 ]]) >>> ytensor([[0 , 1 , 2 , 3 , 4 ], [0 , 1 , 2 , 3 , 4 ], [0 , 1 , 2 , 3 , 4 ]]) >>>
单纯看输入输出,可能不是很明白,列举一个例子:
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 >>> for i in range (3 ):... for j in range (4 ):... print ("(" , x[i,j], "," ,y[i,j],")" )... ( tensor(0 ) , tensor(0 ) ) ( tensor(0 ) , tensor(1 ) ) ( tensor(0 ) , tensor(2 ) ) ( tensor(0 ) , tensor(3 ) ) ( tensor(1 ) , tensor(0 ) ) ( tensor(1 ) , tensor(1 ) ) ( tensor(1 ) , tensor(2 ) ) ( tensor(1 ) , tensor(3 ) ) ( tensor(2 ) , tensor(0 ) ) ( tensor(2 ) , tensor(1 ) ) ( tensor(2 ) , tensor(2 ) ) ( tensor(2 ) , tensor(3 ) ) >>> torch.stack((x,y),2 )tensor([[[0 , 0 ], [0 , 1 ], [0 , 2 ], [0 , 3 ], [0 , 4 ]], [[1 , 0 ], [1 , 1 ], [1 , 2 ], [1 , 3 ], [1 , 4 ]], [[2 , 0 ], [2 , 1 ], [2 , 2 ], [2 , 3 ], [2 , 4 ]]]) >>>
现在就比较清楚了,划分了3×4的网格,通过遍历得到的x和y就能遍历全部格子。
下面是yolov3中提供的代码(需要注意的是这是针对某一层YOLOLayer,而不是所有的YOLOLayer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def create_grids (self, img_size=416 , ng=(13 , 13 ), device='cpu' , type =torch.float32 ): nx, ny = ng self.img_size = max (img_size) self.stride = self.img_size / max (ng) yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)]) self.grid_xy = torch.stack((xv, yv), 2 ).to(device).type (type ).view( (1 , 1 , ny, nx, 2 )) self.anchor_vec = self.anchors.to(device) / self.stride self.anchor_wh = self.anchor_vec.view(1 , self.na, 1 , 1 , 2 ).to(device).type (type ) self.ng = torch.Tensor(ng).to(device) self.nx = nx self.ny = ny
2. YOLOLayer 在之前的文章中讲过,YOLO层前一层卷积层的filter个数具有特殊的要求,计算方法为:
如下图所示:
训练过程:
YOLOLayer的作用就是对上一个卷积层得到的张量进行处理,具体可以看training过程涉及的代码(暂时不关心ONNX部分的代码):
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 class YOLOLayer (nn.Module ): def __init__ (self, anchors, nc, img_size, yolo_index, arc ): super (YOLOLayer, self).__init__() self.anchors = torch.Tensor(anchors) self.na = len (anchors) self.nc = nc self.no = nc + 5 self.nx = 0 self.ny = 0 self.arc = arc if ONNX_EXPORT: stride = [32 , 16 , 8 ][yolo_index] nx = int (img_size[1 ] / stride) ny = int (img_size[0 ] / stride) create_grids(self, img_size, (nx, ny)) def forward (self, p, img_size, var=None ): ''' onnx代表开放式神经网络交换 pytorch中的模型都可以导出或转换为标准ONNX格式 在模型采用ONNX格式后,即可在各种平台和设备上运行 在这里ONNX代表规范化的推理过程 ''' if ONNX_EXPORT: bs = 1 else : bs, _, ny, nx = p.shape if (self.nx, self.ny) != (nx, ny): create_grids(self, img_size, (nx, ny), p.device, p.dtype) p = p.view(bs, self.na, self.no, self.ny, self.nx).permute(0 , 1 , 3 , 4 , 2 ).contiguous() if self.training: return p
在理解以上代码的时候,需要理解每一个通道所代表的意义,原先的P是由上一层卷积得到的feature map, 形状为(以80个类别、输入416、下采样32倍为例):【batch size, anchor×(80+5), 13, 13】,在训练的过程中,将feature map通过张量操作转化的形状为:【batch size, anchor, 13, 13, 85】。
测试过程:
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 else : io = p.clone() io[..., :2 ] = torch.sigmoid(io[..., :2 ]) + self.grid_xy io[..., 2 :4 ] = torch.exp( io[..., 2 :4 ]) * self.anchor_wh io[..., :4 ] *= self.stride if 'default' in self.arc: torch.sigmoid_(io[..., 4 ]) elif 'BCE' in self.arc: torch.sigmoid_(io[..., 5 :]) io[..., 4 ] = 1 elif 'CE' in self.arc: io[..., 4 :] = F.softmax(io[..., 4 :], dim=4 ) io[..., 4 ] = 1 if self.nc == 1 : io[..., 5 ] = 1 return io.view(bs, -1 , self.no), p
理解以上内容是需要对应以下公式:
xy部分:
$c_{x}, c_{y}$代表的是格子的左上角坐标;$t_{x}, t_{y}$代表的是网络预测的结果; $\sigma$代表sigmoid激活函数。对应代码理解:
1 2 io[..., :2 ] = torch.sigmoid(io[..., :2 ]) + self.grid_xy
wh部分:
$p_{w}, p_{h}$代表的是anchor先验框在feature map上对应的大小。$t_{w}, t_{h}$代表的是网络学习得到的缩放系数。对应代码理解:
1 2 io[..., 2 :4 ] = torch.exp(io[..., 2 :4 ]) * self.anchor_wh
class部分:
在类别部分,提供了几种方法,根据arc参数来进行不同模式的选择。以CE(crossEntropy)为例:
1 2 3 io[..., 4 :] = F.softmax(io[..., 4 :], dim=4 ) io[..., 4 ] = 1