YOLO V3边框(Anchor boxes)源码解析

  最近在学习YOLO源码,看了很多资料,在此总结解释一下边界框源码,理解错误的地方还请大家批评指正,我还是个小白,发出来也是总结交流一下。

  首先我们对图片的处理进行分析:

  • 当我们输入任意一张图片,在保持长宽比不变的情况下,会将w和h缩放达到416x416.如:原图尺寸768,576, 缩放后的尺寸为new_w, new_h=416,312,而我们需要输入的是416x416,所有空白处会进行填补。
  • 缩放后的图片经过网络预测得到的(tx,ty,tw,th)是相对于grid cell的偏移量,因此我们需要将其转换为相对于图片的坐标(bx,by,bw,bh)
  • 上面的这些计算我们通过源码就行讲解分析

yolo_head

def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
  • 功能:yolo特征图中三个先验框的预测值(tx,ty,tw,th)是相对于grid cell的偏移量,因此我们需要先对特征图中的预测值转换成真实值,即将先验框的预测值换为相对于图片的坐标和大小(bx,by,bw,bh)
  • 输入参数:
    feats:特征图,如13x13特征图为(?,13,13,255) ?代表图片数量
    anchors:3个先验框的值
    num_classes:类别个数,COCO是80个类别;
    实现:
获取x,y的网格:
# 获得x,y的网格    
# (13, 13, 1, 2)    
grid_shape = K.shape(feats)[1:3] # height, width : 获取(batch_size,13,13,3,85)中的13 ,13  
'''    
reshape(x,shape):x是张量或者变量,shape:目标尺寸元组     
arange(start ,stop=None):创建一个包含整数序列的 1D 张量,start:开始值,stop:结束值     
tile(x,n):创建一个用 n 平铺 的 x 张量。 两者维数需要相同   
'''   
# grid_y和grid_x用于生成网格grid,通过arange、reshape、tile的组合, 创建y轴的0~12的组合grid_y,    
# 再创建x轴的0~12的组合grid_x,将两者拼接concatenate,就是grid;    
grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),        [1, grid_shape[1], 1, 1])    
grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),        [grid_shape[0], 1, 1, 1])    
grid = K.concatenate([grid_x, grid_y])  
#基于指定的轴,连接张量的列表。    
grid = K.cast(grid, K.dtype(feats))     
#K.cast():把grid中值的类型变为和feats中值的类型一样
将预测值转换为真实值

YOLO V3边框(Anchor boxes)源码解析_第1张图片
预测得到的偏移值(tx,ty,tw,th)与最终的边框值(bx,by,bw,bh)如上图所示。
  其中的Cx,Cy为grid cell的左上角坐标,在yolov3中每个grid cell在feature map中的宽和高均为1。如上图的情形时,这个bbox边界框的中心属于第二行第二列的grid cell,它的左上角坐标为(1,1),故Cx=1,Cy=1
  注意:pw,ph为anchor的宽,高 , 在实际使用中,作者为了将bw,bh也归一化到0-1,实际程序中的 pw,ph为anchor的宽,高和特征图的宽,高的比值。最终得到的bw,bh为归一化后相对于特征图的比值

#获取先验框数量 3   
num_anchors = len(anchors)    
# [1, 1, 1, num_anchors, 2]    
anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])
  • 作用:由公式我们知道Pw,Ph要与预测值相乘,因此我们要将先验框的维度调整相同。
# (batch_size,13,13,3,85)    
# 将feats的最后一维展开,方便将anchors与其他数据(类别数+4个框值+框置信度)分离   
feats = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])

将预测值根据公式调整为真实值:

# 将预测值调成真实值  , xywh的计算公式,tx、ty、tw和th是feats值,而bx、by、bw和bh是输出值,     
# box_xy对应框的中心点 , box_wh对应框的宽和高    
# K.cast(grid_shape[::-1], K.dtype(feats))    13
box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))    
box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))    
box_confidence = K.sigmoid(feats[..., 4:5])    
box_class_probs = K.sigmoid(feats[..., 5:])    
# ...操作符,在Python中,“...”(ellipsis)操作符,表示其他维度不变,只操作最前或最后1维;

# 在计算loss的时候返回如下参数   
if calc_loss == True:        
	return grid, feats, box_xy, box_wh    
return box_xy, box_wh, box_confidence, box_class_probs

yolo_correct_boxes

def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape):
  • 参数:box_xy,box_wh为yolo_head转化后的中心坐标、宽和高
       input_shape为输入图像的尺寸
       image_shape为(416x416)
  • 功能:将box_xy和box_wh的(0~1)相对值,转换为真实坐标,输出boxes是(y_min,x_min,y_max,x_max)的值;经过分析知道图片是经过缩放再进行卷积的,因此我们还要将边界框的值转化为符合原始图片的值
box_yx = box_xy[..., ::-1]    #“::-1”是颠倒数组的值    
box_hw = box_wh[..., ::-1]        
input_shape = K.cast(input_shape, K.dtype(box_yx))    
image_shape = K.cast(image_shape, K.dtype(box_yx))

接下来我们分析一下图片缩放怎么计算:

YOLO V3边框(Anchor boxes)源码解析_第2张图片
  取 min(w/img_w, h/img_h)这个比例来缩放,保证长的边缩放为需要的输入尺寸416,而短边按比例缩放不会扭曲,img_w,img_h是原图尺寸768,576, 缩放后的尺寸为new_w, new_h=416,312,需要的输入尺寸是w,h=416*416.
  代码为:

new_shape = K.round(image_shape * K.min(input_shape/image_shape))
offset = (input_shape-new_shape)/2./input_shape
  • 作用:计算缩放图相对于原图的偏离量,input_shape包含宽和高(w,h),因此偏移量offset也为两个量(offset_x,offset_y)。后面还除了input_shape是做了归一化,因为输入的box_xy,box_wh都是做了归一,为0~1的值。
    scale = input_shape/new_shape
    box_yx = (box_yx - offset) * scale    
    box_hw *= scale

  作用:根据缩放比例计算边界框的长和宽

    box_mins = box_yx - (box_hw / 2.)    
    box_maxes = box_yx + (box_hw / 2.)

  作用:计算出边界框的左上顶点坐标和右下顶点坐标

    boxes =  K.concatenate([        
    box_mins[..., 0:1],  # y_min        
    box_mins[..., 1:2],  # x_min        
    box_maxes[..., 0:1],  # y_max        
    box_maxes[..., 1:2]  # x_max    ])
    boxes *= K.concatenate([image_shape, image_shape])    
    return boxes

返回:(y_min,x_min,y_max,x_max)的值

你可能感兴趣的:(目标检测)