最近在学习YOLO源码,看了很多资料,在此总结解释一下边界框源码,理解错误的地方还请大家批评指正,我还是个小白,发出来也是总结交流一下。
首先我们对图片的处理进行分析:
def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
# 获得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中值的类型一样
预测得到的偏移值(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])
# (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
def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape):
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))
接下来我们分析一下图片缩放怎么计算:
取 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
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)的值