YOLOV4用掉的一些tricks以及代码实现(1)——CutMix and Mosaic data augmentation
之前的几篇博客将YOLOV4整个流程都过了一边,其中把重心放在相关的语法中,从这篇博客开始,我将YOLOV4中提到的一些tricks逐一进行介绍并用代码实现。首先看下论文提到了哪些tricks:
Bag of Freebies(BoF) 指那些能够提高精度而不增加推断时间的技术。比如数据增广的方法图像几何变换、CutOut、grid mask等,网络正则化的方法DropOut、DropBlock等,类别不平衡的处理方法、难例挖掘方法、损失函数的设计等。
Bag of Specials (BoS)是指那些增加稍许推断代价,但可以提高模型精度的方法,比如增大模型感受野的SPP、ASPP、RFB等,引入注意力机制Squeeze-and-Excitation (SE) 、Spatial Attention Module (SAM)等 ,特征集成方法SFAM , ASFF , BiFPN等,改进的激活函数Swish、Mish等,或者是后处理方法如soft NMS、DIoU NMS等。
本篇博客主要介绍:CutMix and Mosaic data augmentation
代码来源:https://github.com/jason9075/opencv-mosaic-data-aug
示例代码利用opencv展示Mosaic data augmentation算法
原理:
Yolov4的mosaic数据增强参考了CutMix数据增强方式,理论上具有一定的相似性!
CutMix数据增强方式利用两张图片进行拼接。
但是mosaic利用了四张图片这样在BN计算的时候一下子会计算四张图片的数据!
实现思路
1、每次读取四张图片。
2、分别对四张图片进行翻转、缩放、色域变化等,并且按照四个方向位置摆好。
3、进行图片的组合和框的组合。
通过查看实现思路,其实发现整体思路并不复杂,真正复杂的实在代码实现上,包括坐标转换,标签转换等。
本次示例中,利用opencv仅仅用于图像展示,算法原理与yoloV4中data augment 一模一样。
按照笔者一贯思路,逐一对代码进行分析后,解答上诉问题:
def get_dataset(anno_dir, img_dir):
首先通过dataset获取标签数据和图像数据。
for anno_file in glob.glob(os.path.join(anno_dir, '*.txt'))
遍历anno_dir中的数据,这其中牵扯到glob语法。glob模块是最简单的模块之一,内容非常少。用它可以查找符合特定规则的文件路径名。跟使用windows下的文件搜索差不多。查找文件只用到三个匹配符:””, “?”, “[]”。””匹配0个或多个字符;”?”匹配单个字符;”[]”匹配指定范围内的字符,如:[0-9]匹配数字。
在这里就是查找以“txt"结尾的文件,返回的是文件路径。
xmin = max(obj[1], 0) / img_width
ymin = max(obj[2], 0) / img_height
xmax = min(obj[3], img_width) / img_width
ymax = min(obj[4], img_height) / img_height
获取到标注信息的位置后进行归一化。
idxs = random.sample(range(len(annos)), 4)
random.sample()可以从指定的序列中,随机的截取指定长度的片断,不作原地修改。在这里用于随机选取图片。
def update_image_and_anno(all_img_list, all_annos, idxs, output_size, scale_range, filter_scale=0.)
该函数用于修改图像和标签,既实现mosaic数据增强。
scale_x = scale_range[0] + random.random() * (scale_range[1] - scale_range[0])
scale_y = scale_range[0] + random.random() * (scale_range[1] - scale_range[0])
divid_point_x = int(scale_x * output_size[1])
divid_point_y = int(scale_y * output_size[0])
利用random获取长和宽。注:生成的是0-1之间的浮点数。
for i, idx in enumerate(idxs)
根据之前产生的随机数获取数据,注:for遍历enumerate时,返回第一个值是index,第二个值是相对位置的数据。如:
date = [2,4,6,8,10]
for index, item in enumerate(date):
print("index=%d ,item=%d" %(index,item))
for i in enumerate(date):
print(i)
#结果:
index=0 ,item=2
index=1 ,item=4
index=2 ,item=6
index=3 ,item=8
index=4 ,item=10
(0, 2)
(1, 4)
(2, 6)
(3, 8)
(4, 10)
遍历随机选择出来的四张图后,更改图像尺寸:
img = cv2.imread(path)
if i == 0: # top-left
img = cv2.resize(img, (divid_point_x, divid_point_y))
output_img[:divid_point_y, :divid_point_x, :] = img
for bbox in img_annos:
xmin = bbox[1] * scale_x
ymin = bbox[2] * scale_y
xmax = bbox[3] * scale_x
ymax = bbox[4] * scale_y
new_anno.append([bbox[0], xmin, ymin, xmax, ymax])
分别遍历四个角的图片并修改图像尺寸和标签,步骤如下:
1.根据随机生成的divid_point_x,divid_point_y,将图像随机resize后,放入左上角的位置。
2.根据scale_x,scale_y修改标签的缩放比,注意:一张图片可能有多个标签,故这里用了for bbox in img_annos。
3.选取另一个位置,重复上两步。
new_anno = [anno for anno in new_anno if filter_scale < (anno[3] - anno[1]) and filter_scale < (anno[4] - anno[2])]
通过filter_scale设定阈值,去除修改后标签尺寸过小的图片。
for anno in new_annos:
start_point = (int(anno[1] * OUTPUT_SIZE[1]), int(anno[2] * OUTPUT_SIZE[0]))
end_point = (int(anno[3] * OUTPUT_SIZE[1]), int(anno[4] * OUTPUT_SIZE[0]))
cv2.rectangle(new_image, start_point, end_point, (0, 255, 0), 1, cv2.LINE_AA)
cv2.imwrite('img/output_box.jpg', new_image)
最后利用opencv将方框画出来。