1.Compose labels
YOLO网络需要的形式是 [b,16,16,5,6] , 而我们的标签shape则是 [batch,max_boxes, 5],明显真实标签shape与网络预测输出shape不一致,无法做比较,损失函数就不能完成,为了完成损失函数或者说是真实标签与网络预测输出作比较,需要修改真实标签的形状。
YOLOV2损失函数包含三部分:
- 坐标损失: x , y , w , h x,y,w,h x,y,w,h
- 类别损失: class,根据自己的标签设定
- 置信度损失: confidence, anchors与真实框的IOU
针对损失函数,需要预先准备四个变量,分别是真实标签掩码,五维张量的真实标签,转换格式的三维张量真实标签,只包含类别的五维张量。
1.1首先我们想从5锚点中分别计算iou,选取最佳的anchors
IMGSZ = 512
GRIDSZ = 16
#我们上一篇通过K-Means计算出的pre_anchors
ANCHORS = [0.63, 0.69, 0.76, 0.93, 0.93, 1.0, 1.03, 1.09, 2.0, 2.06]
def process_true_boxes(gt_boxes,anchors):
"""
:param gt_boxes:[40,5] 一张真实标签的位置坐标信息
:param anchors:YOLO的预设框anchors
:return:
"""
# 512//16 = 32 计算网络模型从输入到输出的缩小比例
scale = IMGSZ // GRIDSZ
# [5,2] 将anchors转化为矩阵形式,一行代表一个anchors
anchors = np.array(anchors).reshape((5,2))
# mask for object
#用来判断该方格位置的anchors有没有目标,每个方格有5个anchors
detector_mask = np.zeros([GRIDSZ,GRIDSZ,5,1])
# x-y-w-h-l
#在输出方格的尺寸上[16, 16, 5]制作真实标签, 用于和预测输出值做比较,计算损失值
matching_gt_box = np.zeros([GRIDSZ,GRIDSZ,5,5])
# [N,5] x1-y1-x2-y2-l => x-y-w-h-l
# 制作一个numpy变量,用于存储一张图片真实标签转换格式后的数据
# 将左上角与右下角坐标转化为中心坐标与宽高的形式
# [x_min, y_min, x_max, y_max] => [x_center, y_center, w, h]
gt_boxes_grid = np.zeros(gt_boxes.shape)
# DB: tensor => numpy
gt_boxes = gt_boxes.numpy()
for i,box in enumerate(gt_boxes):# [40,5]
# box: [5],x1-y1-x2-y2
# 512 => 16
# 将左上角与右下角坐标转化为中心坐标与宽高的形式
# [x_min, y_min, x_max, y_max] => [x_center, y_center, w, h]
x = ((box[0]+box[2])/2)/scale
y = ((box[1]+box[3])/2)/scale
w = (box[2]-box[0])/scale
h = (box[3]-box[1])/scale
# [40,5] x-y-w-h-i
# 将第 i 行的数据赋予计算得到的新数据
gt_boxes_grid[i] = np.array([x,y,w,h,box[4]])
if w*h > 0: # valid box
# fine the best anchor
# 用于筛选有效数据,当w, h为0时,表明该行没有目标,为无效的填充数据0
best_anchor = 0
best_iou = 0
for j in range(5):
# calcute IOU
# 计算真实目标框有5个anchros的交并比,选出做好的一个anchors
interct = np.minimum(w,anchors[j,0])*np.minimum(h,anchors[j,1])
union = w*h + (anchors[j,1]*anchors[j,0]) - interct
iou = interct/union
#best iou 筛选最大的iou,即最好的
if iou > best_iou:
#将更加优秀的anchors的索引赋值与之前定义好的变量
best_anchor = j
best_iou = iou #记录最好的iou
# found the best anchors
if best_iou > 0:#用于判断是否有anchors与真实目标产生交并
# 向下取整,即是将中心点坐标转化为左上角坐标, 用于后续计算赋值
x_coord = np.floor(x).astype(np.int32)
y_coord = np.floor(y).astype(np.int32)
# [b,h,w,5,1]
# 将最好的一个anchors赋值1,别的anchors默认为0
# 图像坐标系的坐标与数组的坐标互为转置:[x,y] => [y, x]
detector_mask[y_coord,x_coord,best_anchor] = 1
# [b,h,w,5,x-y-w-h-l]
# 将最好的一个anchors赋值真实标签的信息[x_center, y_center, w, h, label],别的anchors默认为0
"""
因为矩阵中第一维表示行,第二维表示列,比如a[4, 3],
a有4行3列;但在图像坐标系中,横轴是x, 纵轴是y,
这也就是说y的值是图像的行数,x的值是图像的列数。
所以在赋值中,需要将y写在第一维,x写在第二维,
即 detector_mask[y_coord, x_coord, best_anchor] = 1。
根据之前计算的IOU,可以知道与目标匹配最好的anchors的索引序号,
然后对该anchors赋予相对应的值
"""
matching_gt_box[y_coord,x_coord,best_anchor] = np.array([x,y,w,h,box[4]])
# [40,5] => [16,16,5,5]
# matching_gt_box:[16,16,5,5],用于计算损失值
# detector_mask:[16,16,5,1],掩码,判断哪个anchors有目标
# gt_boxes_grid:[40,5],一张图片中目标的位置信息,转化后的格式
return matching_gt_box,detector_mask,gt_boxes_grid
1.2批量处理Compose labels
在训练过程中,训练batch_size一般不是1,有可能为2,4, 8, 16等等,所以需要将保存单张图片标签信息的变量合成为保存多张图片的变量,使用列表,然后矩阵化即可,至于矩阵化的原因,是因为矩阵容易操作,而且tensorflow中基本都是张量。具体代码如下:
def ground_truth_generator(db):
"""
构建一个训练数据集迭代器,每次迭代的数量由batch决定
:param db:训练集队列,包含训练集原图片数据信息,标签位置[x_min, y_min, x_max, y_max, label]信息
:return:
"""
for imgs,imgs_boxes in db:
# imgs: [b,512,512,3]b的值由之前定义的batch_size来决定
# imgs_boxes: [b,40,5]不一定是40,要根据实际情况来判断
# 创建三个批量数据列表
# 对应上面函数的单个图片数据变量
batch_matching_gt_boxes = []
batch_detector_mask = []
batch_gt_boxes_grid = []
b = imgs.shape[0]#计算一个batch有多少张图片
for i in range(b): # for each image
matching_gt_box,detector_mask,gt_boxes_grid = process_true_boxes(imgs_boxes[i],ANCHORS)
batch_matching_gt_boxes.append(matching_gt_box)
batch_detector_mask.append(detector_mask)
batch_gt_boxes_grid.append(gt_boxes_grid)
#将其转化为矩阵形式并转化为tensor [b,16,16,5,1]
detector_mask = tf.cast(np.array(batch_detector_mask),dtype=tf.float32)
#将其转化为矩阵形式并转化为tensor,[b,16,16,5,5] x_center-y_center-w-h-l
matching_gt_box = tf.cast(np.array(batch_matching_gt_boxes),dtype=tf.float32)
#将其转化为矩阵形式并转化为tensor,[b,40,5] x_center-y_center-w-h-l
gt_boxes_grid = tf.cast(np.array(batch_gt_boxes_grid),dtype=tf.float32)
# [b,16,16,5]
# 将所有的label信息单独分出来,用于后续计算分类损失值
matching_classes = tf.cast(matching_gt_box[...,4],dtype=tf.int32)
# 将标签进行独热码编码 [b,16,16,5,num_classes:3],
matching_classes_oh = tf.one_hot(matching_classes,depth=3)
# 将背景标签去除,背景为0
# x_center-y_center-w-h-conf-l0-l1-l2 => x_center-y_center-w-h-conf-l1-l2
# [b,16,16,5,2]
matching_classes_oh = tf.cast(matching_classes_oh[...,1:],dtype=tf.float32)
# [b,512,512,3]
# [b,16,16,5,1]
# [b,16,16,5,5]
# [b,16,16,5,2]
# [b,40,5]
yield imgs,detector_mask,matching_gt_box,matching_classes_oh,gt_boxes_grid
1.3解释一段代码,这段代码开始我也不太明白,明白过来后,才发现及其重要,这里要感谢大神的解释:
# [b,16,16,5]
# 将所有的label信息单独分出来,用于后续计算分类损失值
matching_classes = tf.cast(matching_gt_box[...,4], dtype=tf.int32)
# 将标签进行独热码编码 [b,16,16,5,num_classes:3],
matching_classes_oh = tf.one_hot(matching_classes, depth=num_classes)
# 将背景标签去除,背景为0
# x_center-y_center-w-h-conf-l0-l1-l2 => x_center-y_center-w-h-conf-l1-l2
# [b,16,16,5,2]
matching_classes_oh = tf.cast(matching_classes_oh[...,1:], dtype=tf.float32)
如何将类别单独分出来,并另存为一个变量,就比较简单,matching_gt_box的shape为[b, 16, 16, 5, 5],最后一维代表的值为真实目标的坐标(x, y, w, h)和类别(label),所有只需要取该变量的最后一维的第5个值就可以,如上面代码所示。得到matching_classes变量后,事情并没有做完,因为网络输出shape为[b, 16, 16, 5, 7] 我的训练集只有2类,所以7表示x-y-w-h-confidece-label1-label2,不包含背景,类别数可以根据你的类别数修改。但实际类别是3类,即背景-label1-label2,虽然在网络输出中不包含背景,但自己需要知道在目标检测中,背景默认为一类,这也是为什么在xml解析这一小节中,制作标签时,默认将标签数加1,因为背景默认为0。
因为网络输出不包含背景,所有我们需要将真实标签中的背景去除,去除的方法也比较简单,先将matching_classes热编码,另存为matching_classes_oh: [b, 16, 16, 5, 3],在matching_classes_oh的最后一维中的第一个值就是背景类别,只需要使用切片即可,如代码所示。最后matching_classes_oh的shape为[b, 16, 16, 5, 2],在最后一维的值形式为:[1, 0]:label1, [0, 1]:label2, [0, 0]:背景,也表示该anchors没有真实目标
1.4 可视化看一下经过处理后的数据
from matplotlib import pyplot as plt
from matplotlib import patches
def db_visualize(db):
# imgs: [b,512,512,3]
# imgs_boxes: [b,40,5]
imgs,imgs_boxes = next(iter(db))
img,img_boxes = imgs[0],imgs_boxes[0]
f,ax1 = plt.subplots(1,figsize=(8,10))
# display the image ,[512,512,3]
ax1.imshow(img)
for x1,y1,x2,y2,l in img_boxes: # [40,5]
x1,y1,x2,y2 = float(x1),float(y1),float(x2),float(y2)
w =x2-x1
h = y2-y1
if l == 1: # green for sugarweet
color = (0,1,0)
elif l ==2: # red for weed
color = (0,0,1)
else: # ignore invalid boxes
break
rect = patches.Rectangle((x1,y1),w,h,linewidth=2,edgecolor=color,
facecolor='none')
ax1.add_patch(rect)
#数据:aug_train_db 来自上一篇的(TensorFlow2 一步一步实现Yolo2.数据集预处理:图片增强)
train_gen = ground_truth_generator(aug_train_db)
img,detector_mask,matching_gt_box,matching_classes_oh,gt_boxes_grid =next(train_gen)
img,detector_mask,matching_gt_box,matching_classes_oh,gt_boxes_grid=\
img[0],detector_mask[0],matching_gt_box[0],matching_classes_oh[0],gt_boxes_grid[0]
fig,(ax1,ax2) = plt.subplots(2,figsize=(5,10))
ax1.imshow(img)
# [b,16,16,5,1] => [16,16,1]
mask = tf.reduce_sum(detector_mask,axis=2)
ax2.matshow(mask[...,0]) # [16,16]
到这里,前期所有工作已经完成,下一篇开始搭建YOLO2网络。
参考学习自以下大神博客:https://blog.csdn.net/qq_37116150/article/details/105451627#4.1%20%E5%8D%95%E5%BC%A0%E5%9B%BE%E7%89%87%C2%A0
https://blog.csdn.net/python_LC_nohtyp/article/details/104842099