【YOLOv4探讨 之三】mosaic数据增强
【YOLOv4探讨 之三】mosaic数据增强
- mosaic数据增强原理
- darknet框架下mosaic数据增强的代码解读
- 参数配置
- 处理过程
- 判断采用哪种数据增强模式
- 获取图像路径
- 设置初始平移量
- 图像偏移与truth值更新
- 常规数据增强
- 图像填充
- 绑定目标参数
- 步骤综合
- 总结
最近太忙,久未更新,对不住大家。进入正题,我们聊一聊YOLOv4中使用的mosaic数据增强。关于mosaic数据增强相关文章不少,三个月前这个方面的学习开了个头,那时候各路诸侯都是以TensorFlow框架为主,我这里依然坚持分析研究darknet框架下的数据增强。
mosaic数据增强原理
YOLOv4中在载入图片数据时同步进行mosaic数据增强。
mosaic数据增强基本原理就是在训练集中随机选择若干个(一般是4个)图像,经过裁剪拼接形成新的训练集元素,可以缓解训练集元素少或者增强识别能力,是cutmix数据增强的升级版,基本原理如下图。

darknet框架下mosaic数据增强的代码解读
参数配置
darknet框架下是否使用mosaic数据增强通过cfg文件进行配置,核心的参数包括
由于实际的yolo4.cfg中只有
#cutmix=1
mosaic=1
其他的配置参数我们通过parser.c文件中parse_net_options函数中的一些代码来看:
net->flip = option_find_int_quiet(options, "flip", 1);//翻转,默认为flip =1,net->blur = option_find_int_quiet(options, "blur", 0);//糊化,默认不糊化net->gaussian_noise = option_find_int_quiet(options, "gaussian_noise", 0);//高斯噪声,默认不加噪声net->mixup = option_find_int_quiet(options, "mixup", 0);//使用数据增强,默认为0int cutmix = option_find_int_quiet(options, "cutmix", 0); // cutmix增强,默认为0
int mosaic = option_find_int_quiet(options, "mosaic", 0); // mosaic增强,默认为0
//对使用不同类型的数据增强进行值定义if (mosaic && cutmix) net->mixup = 4;else if (cutmix) net->mixup = 2;else if (mosaic) net->mixup = 3;net->letter_box = option_find_int_quiet(options, "letter_box", 0);net->mosaic_bound = option_find_int_quiet(options, "mosaic_bound", 0);net->contrastive = option_find_int_quiet(options, "contrastive", 0);net->contrastive_jit_flip = option_find_int_quiet(options, "contrastive_jit_flip", 0);
通过以上函数将关键参数传入net,再传给detector.c中的train_detector函数中的args ,再通过load_thread = load_data(args)进行数据载入和同时进行数据增强。
对模型进行训练时使用load_data_detection函数1:
data load_data_detection(int n, char **paths, int m, int w, int h, int c, int boxes, int truth_size, int classes, int use_flip, int use_gaussian_noise, int use_blur, int use_mixup, float jitter, float resize, float hue, float saturation, float exposure, int mini_batch, int track, int augment_speed, int letter_box, int mosaic_bound, int contrastive, int contrastive_jit_flip, int show_imgs)
这里对每个参数进行分析:
int n表示一个完整的批次处理的图片数量,在cfg文件中的n = batch*ngpus,在单GPU条件下,一个批次通常取64张图片
char **paths 表示训练数据集的路径的集合
int m 表示所使用的数据集的所有图片数量
int w,int h,int c 表示载入图片后归一化的用于模型处理的图片数据宽、高和通道数,是一个正方形图片数据,一般w = h
int boxes表示一张图片最多能够识别出来的目标数,即一个层中使用的最大目标识别框的个数,对应args.num_boxes = l.max_boxes = option_find_int_quiet(options, "max", 200),在yolov4.cfg文件中没有明确,表示一次最多支持200个目标识别框
int truth_size 表示真值包含的元素个数一般为5
int classes 表示可以分类的数量,YOLOv4标准型为80个
int use_flip 表示是否使用翻转,默认为1,表示使用翻转
int use_gaussian_noise 表示是否加噪声,默认不加
int use_blur 表示是使用糊化,默认不使用
int use_mixup 使用的混合数据增强的种类,包括cutmix,mixip,mosaic等
int jitter表示图像抖动范围,默认0.2,既然是抖动就可能往左往右都有
float resize 表示图像缩放的比例,默认不缩放
float hue 表示色调(色相),默认为0,表示色调(色相)没有偏移
float exposure 表示曝光度,通常曝光度设置为1
int mini_batch 表示最小批次,mini_batch = batch / subdivs,表示每次实际处理图像的批次
int track 在cfg文件中未定义track,则在进行数据增强时不采取序列路径,而是随机抽取图像进行组合
int augment_speed 表示增强速度,默认为2,当依序列获取增强数据路径时,每次跳过的图像索引个数
int letter_box 表示是否使用letter_box变换,一般默认为0,表示不使用。图像进入神经网络后被拉伸成长宽比为1的正方形,会造成图像失真,letter_box变换就是将图像还原成原始比例,放在正方形中,将剩余的图像填入灰色。在YOLOv3中使用test_detector函数进行探测时直接进行了这个操作,在YOLOv4中专门设置一个if(letter_box)的语句,可以进行选择使用。如果需要使用letter_box变换,后面在计算剪切后的图像时就要考虑进去。
int contrastive 表示是否使用对比,这个和序列抽取需要增强的数据图像时的起始索引有关,设置为1表示采用相邻索引,设置为0表示采用随机起始索引
int contrastive_jit_flip表示是否使用翻转对比
int show_imgs 表示是否需要显示组合的图像
处理过程
判断采用哪种数据增强模式
使用use_mixup参数配置数据循环及图像组合次数,当传入mosaic && cutmix ==1或者cutmix ==1,令use_mixup = 3,即统统转为使用mosaic数据增强。在YOLOv4默认的cfg文件中,令mosaic = 1。这里表示YOLOv4的模型主要支撑mosaic数据增强。需要注意的一点是cutmix的数据增强方法主要用于分类而非探测。
use_mixup = 3意味着后续的处理要循环四次,即把四张图片拼接在一起。
当use_mixup = 3时,设置(cut_x,cut_y)得位置,这个位置是随机的,示意图如下:

源码及注释如下:
//这里是针对所有含cutmix=1的情况//cutmix=1只适用于分类,不支持Detector,所以在load_data_detection中如果有cutmix=1,将会提醒以下语句://"cutmix=1 - isn't supported for Detector (use cutmix=1 only for Classifier)"if (use_mixup == 2 || use_mixup == 4) {printf("\n cutmix=1 - isn't supported for Detector (use cutmix=1 only for Classifier) \n");if (check_mistakes) getchar();if(use_mixup == 2) use_mixup = 0;//循环次数use_mixup+1 = 1;else use_mixup = 3;//同时使用mosaic和cutmix数据增强方法,循环次数use_mixup+1 = 4}//这里不支持letterbox变换和mosaic变换同时成立的情况。if (use_mixup == 3 && letter_box) {//printf("\n Combination: letter_box=1 & mosaic=1 - isn't supported, use only 1 of these parameters \n");//if (check_mistakes) getchar();//exit(0);}if (random_gen() % 2 == 0) use_mixup = 0;//当使用mosaicDA,平均有一半的use_mixup = 0,即不进行任何变换int i;
//数据增强策略:mosaic条件平均有一半的use_mixup = 0,其他条件下所有use_mixup = 0//use_mixup == 3的条件下,设置mosaic DA 初始cut值int *cut_x = NULL, *cut_y = NULL;if (use_mixup == 3) {cut_x = (int*)calloc(n, sizeof(int));cut_y = (int*)calloc(n, sizeof(int));const float min_offset = 0.2; // 20%//一个imgs中每个图片都设置一组cut_x,cut_y值,取值范围在举例边界20%距离的内圈for (i = 0; i < n; ++i) {cut_x[i] = rand_int(w*min_offset, w*(1 - min_offset));cut_y[i] = rand_int(h*min_offset, h*(1 - min_offset));}}
设置数据增强相关初始参数:
data d = {0};//设置data变量d,所有变换后的数据存储在这个变量中d.shallow = 0;d.X.rows = n;//d.X中存储一个batch的n个图片d.X.vals = (float**)xcalloc(d.X.rows, sizeof(float*));d.X.cols = h*w*c;//每个图片的尺寸,这里要考虑RGB通道float r1 = 0, r2 = 0, r3 = 0, r4 = 0, r_scale = 0;//设置图像抖动最小随机平移量float resize_r1 = 0, resize_r2 = 0;//用于当图像出现缩放的时候的最小随机平移量float dhue = 0, dsat = 0, dexp = 0, flip = 0, blur = 0;//抖动、色调、曝光、翻转、糊化参数初始值int augmentation_calculated = 0, gaussian_noise = 0;//增强计算标志位和是否使用高斯噪声//d.y存储标签值,每一行为一幅图的标签,容量90*5个d.y = make_matrix(n, truth_size*boxes);
利用循环进行数据增强图像拼接处理,mosaic数据增强循环分4次完成,每次获取一个batch的图像,获取完同时进行拼接,4次循环完毕,完成batch个拼接图像的处理。
数据增强拼接处理分几个步骤:
获取图像路径
分为两种方式:序列获取法和随机获取法,序列获取法适合图片集较少的情况,序列获取图片后,对图片进行糊化、高斯噪声、曝光、色调偏移、抖动位置重新设置等等操作;随机获取法适合图片集较多的情况,直接组合拼接图片也不担心出现组合图像重复的情况。如此设置可以保证满足数据增强需求的情况下不至于过度增加运算量。
每次获取一个batch的图片,使用函数的源码如下:
//抽取序列图像路径
char **get_sequential_paths(char **paths, int n, int m, int mini_batch, int augment_speed, int contrastive)
{int speed = rand_int(1, augment_speed);//speed在(1, augment_speed)或(augment_speed,1)中间取值,程序中默认augment_speed =2所以speed一般为1.xif (speed < 1) speed = 1;//如果speed小于1,则取1char** sequentia_paths = (char**)xcalloc(n, sizeof(char*));int i;pthread_mutex_lock(&mutex);//printf("n = %d, mini_batch = %d \n", n, mini_batch);//start_time_indexes中存了一个mini_batch的开始时间序列unsigned int *start_time_indexes = (unsigned int *)xcalloc(mini_batch, sizeof(unsigned int));for (i = 0; i < mini_batch; ++i) {//如果使用contrastive模式且i为奇数,start_time_indexes和上一个start_time_indexes相同if (contrastive && (i % 2) == 1) start_time_indexes[i] = start_time_indexes[i - 1];else start_time_indexes[i] = random_gen() % m;//否则,start_time_indexes为一个0-m之间的随机数,即在所有图片集中随机抽取//这里是说,如果非contrastive模式,图片起始位置为随机选取//如果contrastive模式,图片两个相邻起始位置相同,但也都是随机选取//printf(" start_time_indexes[i] = %u, ", start_time_indexes[i]);}
//按一个完整批次的图片进行循环//这里是针对一个mimi_batch的图像循环取完,然后一起进行增强for (i = 0; i < n; ++i) {do {int time_line_index = i % mini_batch;//循环设置time_line_index索引//实际图片的索引为从start_time_indexes[0]开始的mimi_batch个顺序图像索引,对m取模是为了防止取到所有图片末尾后从头循环unsigned int index = start_time_indexes[time_line_index] % m;//start_time_indexes序列中的值在start_time_indexes[0]基础上按照speed顺序增加,如果mimi_batch为4,speed为1,mimi_batch为8,一般speed在1-2之间start_time_indexes[time_line_index] += speed;//int index = random_gen() % m;sequentia_paths[i] = paths[index];//获取一个batch的图片序列//printf(" index = %d, ", index);//if(i == 0) printf("%s\n", paths[index]);//printf(" index = %u - grp: %s \n", index, paths[index]);//图片路径应大于4个字符if (strlen(sequentia_paths[i]) <= 4) printf(" Very small path to the image: %s \n", sequentia_paths[i]);} while (strlen(sequentia_paths[i]) == 0);}free(start_time_indexes);pthread_mutex_unlock(&mutex);return sequentia_paths;
}
//随机抽取序列比较简单,就是直接在所有的m个图像中随机选取
char **get_random_paths_custom(char **paths, int n, int m, int contrastive)
{char** random_paths = (char**)xcalloc(n, sizeof(char*));int i;pthread_mutex_lock(&mutex);int old_index = 0;//printf("n = %d \n", n);for(i = 0; i < n; ++i){do {int index = random_gen() % m;if (contrastive && (i % 2 == 1)) index = old_index;else old_index = index;random_paths[i] = paths[index];//if(i == 0) printf("%s\n", paths[index]);//printf("grp: %s\n", paths[index]);if (strlen(random_paths[i]) <= 4) printf(" Very small path to the image: %s \n", random_paths[i]);} while (strlen(random_paths[i]) == 0);}pthread_mutex_unlock(&mutex);return random_paths;
}
设置初始平移量
抖动一般是针对于序列获取路径的情况下设置的,第一个batch的图像不需要抖动,后面的三个batch的图像需要抖动。
抖动通过int pleft = rand_precalc_random(-dw, dw, r1);等函数完成,如果抖动r1使用r1 = random_float();等函数设置随机数,如果不抖动pleft = -dw,即-0.2*原始图像的宽度。
rand_precalc_random函数定义如下:
//如果random_part为0,则返回min
float rand_precalc_random(float min, float max, float random_part)
{if (max < min) {float swap = min;min = max;max = swap;}return (random_part * (max - min)) + min;
}
如果还存在缩放,则进一步进行修正:
以图像收缩为例,resize<1,则
pleft += rand_precalc_random(min_rdw, 0, resize_r1);
在非抖动的情况下,pleft = pleft + min_rdw
如果还存在letter_box变换,则pleft或者ptop的位置要切掉五十度灰的区域
图像偏移与truth值更新
对图像进行偏移处理后,将需要处理的图像在w×h的特征框中拉伸,并计算新的truth值。如果无抖动,裁剪图像裁剪位置就是图像的(0,0)坐标,如果有抖动,且抖动为正方向,就裁减掉pleft左边、ptop上边的部分图像,最后取得w和h尺寸的部分,如果尺寸不够,再使用resize_image函数进行拉伸处理,拉伸到w×h的尺寸。
然后,在使用fill_truth_detection函数修正图像中目标的位置标签等。
其中pleft和dx关系如下所示,计算新的truth值是一个线性变换,核心函数为中的correct_boxes函数,dx、dy为偏移量,sx、sy为拉伸比例
图像上下左右四个点坐标更新计算方法为:
l e f t n e w = l e f t _ o r
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
