目录
1.计算标号
目标检测的损失,首先要根据真实框计算出预测框,预测框标注了锚框与真实框的中心点和高宽的偏差,物体的类别。模型预测出的是这些偏差值,损失由这些偏差值来建立。
1.1生成锚框
给出下采样值,将图片划分为 M 行 N 列个小方框,以每个小方框的中心生成多个形状的锚框。
将原始图片划分成m×n个区域,假设原始图片高度H=640, 宽度W=480,如果我们选择小块区域的尺寸为32×32,即下采样值为32,则m和n分别为:
m= 640/32 =20 n= 480/32 =15
1.2生成预测框
预测框的表达式为:
bx=cx+σ(tx) by=cy+σ(ty), σ(x)是Sigmoid函数。, 。既tx,ty,tw, th是要预测的值,他们由真实框计算得出。cx,cy为真实框中心所在小方块的左上角坐标。pw, ph为锚框的高宽。
1.3标注
按照1.1,1.2所述公式标注锚框的位置偏差,除此之外还要标注锚框是否框住物体,以及物体的类别。如此得到锚框的标注信息,这些信息与模型的预测经过计算得到损失函数。
2.计算损失
2.1模型的预测
经过模型卷积后得到的特征图(左),与划分小方框后的图片(右)具有相同的行数列数。这个要通过调整卷积参数或划分小方框的大小的参数来统一。
objectness:是否包含物体, K:每个小方框生成的锚框数, C:物体类别数,采用one-hot编码
下面需要将像素点(i,j)与第i行第j列的小方块区域所需要的预测值关联起来,每个小方块区域产生K个预测框,每个预测框需要(5+C)个实数预测值,则每个像素点相对应的要有K(5+C)个预测实数, 其中C是类别。为了解决这一问题,对特征图进行多次卷积,并将最终的输出通道数设置为K(5+C)),即可将生成的特征图与每个预测框所需要的预测值巧妙的对应起来,如上图。
[t,4,i,j]与输入的第t张图片上小方块区域(i,j)第1个预测框的objectness对应
[t,0:4,i,j]与输入的第ttt张图片上小方块区域(i,j)(i, j)(i,j)第1个预测框的位置对应
[t,5:12,i,j]与输入的第ttt张图片上小方块区域(i,j)(i, j)(i,j)第1个预测框包含物体的类别对应
2.2 损失的计算
通过第1节计算得到了真实框与锚框的偏差和类别,即标号。2.1节给出了如何从模型输出的多通道数据中得到对锚框偏差和类别的预测,这样就可以计算损失了。由于预测由分类和回归两种类型,故需要分别计算损失,然后相加。
3.参考代码
3.1计算标号
def get_objectness_label(img, gt_boxes, gt_labels, iou_threshold = 0.7,
anchors = [116, 90, 156, 198, 373, 326],
num_classes=7, downsample=32):
"""
img 是输入的图像数据,形状是[N, C, H, W]
gt_boxes,真实框,维度是[N, 50, 4],其中50是真实框数目的上限,当图片中真实框不足50个时,不足部分的坐标全为0
真实框坐标格式是xywh,这里使用相对值
gt_labels,真实框所属类别,维度是[N, 50]
iou_threshold,当预测框与真实框的iou大于iou_threshold时不将其看作是负样本
anchors,锚框可选的尺寸
anchor_masks,通过与anchors一起确定本层级的特征图应该选用多大尺寸的锚框
num_classes,类别数目
downsample,特征图相对于输入网络的图片尺寸变化的比例
"""
img_shape = img.shape
batchsize = img_shape[0]
num_anchors = len(anchors) // 2
input_h = img_shape[2]
input_w = img_shape[3]
# 将输入图片划分成num_rows x num_cols个小方块区域,每个小方块的边长是 downsample
# 计算一共有多少行小方块
num_rows = input_h // downsample
# 计算一共有多少列小方块
num_cols = input_w // downsample
label_objectness = np.zeros([batchsize, num_anchors, num_rows, num_cols])
label_classification = np.zeros([batchsize, num_anchors, num_classes, num_rows, num_cols])
label_location = np.zeros([batchsize, num_anchors, 4, num_rows, num_cols])
scale_location = np.ones([batchsize, num_anchors, num_rows, num_cols])
# 对batchsize进行循环,依次处理每张图片
for n in range(batchsize):
# 对图片上的真实框进行循环,依次找出跟真实框形状最匹配的锚框
for n_gt in range(len(gt_boxes[n])):
gt = gt_boxes[n][n_gt]
gt_cls = gt_labels[n][n_gt]
gt_center_x = gt[0]
gt_center_y = gt[1]
gt_width = gt[2]
gt_height = gt[3]
if (gt_width < 1e-3) or (gt_height < 1e-3):
continue
i = int(gt_center_y * num_rows)
j = int(gt_center_x * num_cols)
ious = []
for ka in range(num_anchors):
bbox1 = [0., 0., float(gt_width), float(gt_height)]
anchor_w = anchors[ka * 2]
anchor_h = anchors[ka * 2 + 1]
bbox2 = [0., 0., anchor_w/float(input_w), anchor_h/float(input_h)]
# 计算iou
iou = box_iou_xywh(bbox1, bbox2)
ious.append(iou)
ious = np.array(ious)
inds = np.argsort(ious)
k = inds[-1]
label_objectness[n, k, i, j] = 1
c = gt_cls
label_classification[n, k, c, i, j] = 1.
# for those prediction bbox with objectness =1, set label of location
dx_label = gt_center_x * num_cols - j
dy_label = gt_center_y * num_rows - i
dw_label = np.log(gt_width * input_w / anchors[k*2])
dh_label = np.log(gt_height * input_h / anchors[k*2 + 1])
label_location[n, k, 0, i, j] = dx_label
label_location[n, k, 1, i, j] = dy_label
label_location[n, k, 2, i, j] = dw_label
label_location[n, k, 3, i, j] = dh_label
# scale_location用来调节不同尺寸的锚框对损失函数的贡献,作为加权系数和位置损失函数相乘
scale_location[n, k, i, j] = 2.0 - gt_width * gt_height
# 目前根据每张图片上所有出现过的gt box,都标注出了objectness为正的预测框,剩下的预测框则默认objectness为0
# 对于objectness为1的预测框,标出了他们所包含的物体类别,以及位置回归的目标
return label_objectness.astype('float32'), label_location.astype('float32'), label_classification.astype('float32'), \
scale_location.astype('float32')
3.2计算损失
def get_loss(output, label_objectness, label_location, label_classification, scales, num_anchors=3, num_classes=7):
# 将output从[N, C, H, W]变形为[N, NUM_ANCHORS, NUM_CLASSES + 5, H, W]
reshaped_output = paddle.reshape(output, [-1, num_anchors, num_classes + 5, output.shape[2], output.shape[3]])
# 从output中取出跟objectness相关的预测值
pred_objectness = reshaped_output[:, :, 4, :, :]
loss_objectness = F.binary_cross_entropy_with_logits(pred_objectness, label_objectness, reduction="none")
# pos_samples 只有在正样本的地方取值为1.,其它地方取值全为0.
pos_objectness = label_objectness > 0
pos_samples = paddle.cast(pos_objectness, 'float32')
pos_samples.stop_gradient=True
# 从output中取出所有跟位置相关的预测值
tx = reshaped_output[:, :, 0, :, :]
ty = reshaped_output[:, :, 1, :, :]
tw = reshaped_output[:, :, 2, :, :]
th = reshaped_output[:, :, 3, :, :]
# 从label_location中取出各个位置坐标的标签
dx_label = label_location[:, :, 0, :, :]
dy_label = label_location[:, :, 1, :, :]
tw_label = label_location[:, :, 2, :, :]
th_label = label_location[:, :, 3, :, :]
# 构建损失函数
loss_location_x = F.binary_cross_entropy_with_logits(tx, dx_label, reduction="none")
loss_location_y = F.binary_cross_entropy_with_logits(ty, dy_label, reduction="none")
loss_location_w = paddle.abs(tw - tw_label)
loss_location_h = paddle.abs(th - th_label)
# 计算总的位置损失函数
loss_location = loss_location_x + loss_location_y + loss_location_h + loss_location_w
# 乘以scales
loss_location = loss_location * scales
# 只计算正样本的位置损失函数
loss_location = loss_location * pos_samples
# 从output取出所有跟物体类别相关的像素点
pred_classification = reshaped_output[:, :, 5:5+num_classes, :, :]
# 计算分类相关的损失函数
loss_classification = F.binary_cross_entropy_with_logits(pred_classification, label_classification, reduction="none")
# 将第2维求和
loss_classification = paddle.sum(loss_classification, axis=2)
# 只计算objectness为正的样本的分类损失函数
loss_classification = loss_classification * pos_samples
total_loss = loss_objectness + loss_location + loss_classification
# 对所有预测框的loss进行求和
total_loss = paddle.sum(total_loss, axis=[1,2,3])
# 对所有样本求平均
total_loss = paddle.mean(total_loss)
return total_loss