数据加载函数load_data()在src/data.c中实现(src/detector.c函数中的train_detector直接调用这个函数加载数据)。load_data()函数调用流程如下:load_data(args)->load_threads()->load_data_in_threads()->load_thread()->load_data_detection(),前四个函数都是在对线程的调用进行封装。最底层的数据加载任务由load_data_detection()函数完成。所有的数据(图片数据和标注信息数据)加载完成之后再拼接到一个大的数组中。在DarkNet中,图片的存储形式是一个行向量,向量长度为hw3。同时图片被归一化到[0, 1]之间。
pthread_t load_thread = load_data(args);
pthread_t load_data(load_args args)
{
pthread_t thread;//声明线程
//在对指针进行间接访问之前,指针必须进行初始化:或是使他指向现有的内存,或者给他动态分配内存,否则我们并不知道指针指向哪儿,
struct load_args *ptr = calloc(1, sizeof(struct load_args));//定义指针ptr并使用动态分配内存的方式进行初始化,令其指向分配的内存地址
*ptr = args;//对ptr指向的动态分配内存进行赋值,将结构体args中的内容复制到ptr指向的分配空间内,ptr指向不变,属于深拷贝,对ptr指针的任何数据操作都对args没有影响。但是args中的指针对应ptr来说也是普通变量,只是复制了指针保存的地址,所以保存的地址内在做操作就会有影响了;例如:ptr->d = 000000A07E54F190;args.d = 000000A07E54F190,都是指向同一内存单元,若是修改d指向单元内的数据就会影响args了;这里ptr就是起来一个传递数据的作用,同时又防止其对原始数据做修改;
if(pthread_create(&thread, 0, load_threads, ptr)) error("Thread creation failed");//创建线程
return thread;
}
pthread_create(&thread, 0, load_threads, ptr)函数中最重要的是load_threads函数
load_threads函数完成线程分配和数据拼接
//说明: 1) load_threads()是一个指针函数,只是一个返回变量为void*的普通函数,不是函数指针
//2) 输入ptr是一个void*指针(万能指针),使用时需要强转为具体类型的指针
//3) 函数中涉及四个用来存储读入数据的变量:ptr, args, out, buffers,除args外都是data*类型,
//所有这些变量的指针变量其实都指向同一块内存(当然函数中间有些动态变化),因此读入的数据都是互通的。
//流程: 本函数首先会获取要读入图片的张数、要开启线程的个数,而后计算每个线程应该读入的图片张数(尽可能的均匀分配),并创建所有的线程,并行读入数据,最后合并每个线程读入的数据至一个大data中,这个data的指针变量与ptr的指针变量 指向的是统一块内存,因此也就最终将数据读入到ptr.d中(所以其实没有返回值)
void *load_threads(void *ptr)
int i;
//深拷贝:为另一个指针重新申请内存,然后拷贝,那么第一个指针被释放,也完全没有影响。
// 虽然args不是指针,args是深拷贝了ptr中的内容,但是要知道ptr(也就是load_args数据类型),有很多的
// 指针变量,args深拷贝将拷贝这些指针变量到args中(这些指针变量本身对ptr来说就是内容,
// 而args所指的值是args的内容,不是ptr的,不要混为一谈),因此,args与ptr将会共享所有指针变量所指的内容,好好理解这句
load_args args = *(load_args *)ptr;//将指针ptr所指向的地址中内容赋给结构体args,是深拷贝,之后args的数据操作与指针ptr无关
if (args.threads == 0) args.threads = 1;//判断若是线程为0,表示没有线程,这时会自动设置为1,即一个线程
data *out = args.d;//使得out与args.d指向统一块内存,之后指针out操作会影响args.d所指的内存块,但out不会变,这样可以保证out与最原始的ptr指向同一块存储读入图片数据的内存块,因此最终将图片读到out中, 实际就是读到了最原始的ptr中,比如train_detector()函数中定义的args.d中;例如args.d = 000000A07E54F190;out = 000000A07E54F190,其与之前的地址相同;这里定义out的含义就是为了记录结构体原始args.d的地址,防止地址丢失。
// 读入图片的总张数= batch * subdivision * ngpus,可参见train_detector()函数中的赋值
int total = args.n;//更新一次参数训练的所有图像数量,即一个大的batch
free(ptr);//释放指针ptr,其只是起到了一个传递作用,传递完了就可以释放了,不会影响其他数据
data *buffers = calloc(args.threads, sizeof(data));//每一个线程都会读入一个data,定义并分配args.thread个data的内存
pthread_t *threads = calloc(args.threads, sizeof(pthread_t));//此处定义线程,并分配args.threads个pthread_t的内存
for(i = 0; i < args.threads; ++i){//遍历所有线程
//args.d指针变量本身发生了改动,使得本函数的args.d与out不再指向同一块内存,
//改为指向buffers指向的某一段内存,
//实际是想把图片数据读入到buffers[i]中,只能令args.d与buffers[i]指向同一块内存
args.d = buffers + i;
//下面这句,因为有多个线程,所有线程读入的总图片张数为total,需要将total均匀的分到各个线程上,
//但很可能会遇到total不能整除的args.threads的情况,比如total = 61, args.threads =8,显然不能做到
// 完全均匀的分配,但又要保证读入图片的总张数一定等于total,用下面的语句刚好在尽量均匀的情况下,
// 保证总和为total,比如61,那么8个线程各自读入的照片张数分别为:7, 8, 7, 8, 8, 7, 8, 8
args.n = (i+1) * total/args.threads - i * total/args.threads;//每个线程加载的图像数
//load_threads内部保存这些数据加载子线程的线程id,通过pthread_join等待这些子线程完成数据加载。
//创建多少个子线程由传入的args.threads成员决定,所有线程需要加载的图像的数量是args.n;
//分配到单个线程的话只需要去加载args.n/args.threads张图像。
//开启线程,读入数据到args.d中(也就读入到buffers[i]中)
//load_data_in_thread返回所开启的线程,并存储之前已经动态分配内存用来存储所有线程的threads中,
//方便下面使用pthread_join()函数控制相应线程
threads[i] = load_data_in_thread(args);//load_threads是一个数据加载线程管理者的角色,因为在load_data_in_thread中会创建真正负责加载数据的线程
}
// 以阻塞的方式等待线程threads[i]结束:阻塞是指阻塞启动该子线程的母线程(此处应为主线程),
// 是母线程处于阻塞状态,一直等待所有子线程执行完(读完所有数据)才会继续执行下面的语句
// 关于多线程的使用,进行过代码测试,测试代码对应:darknet_test_pthread_join.c
for(i = 0; i < args.threads; ++i){
pthread_join(threads[i], 0);
}
// 多个线程读入所有数据之后,分别存储到buffers[0],buffers[1]...中,接着使用concat_datas()函数将buffers中的数据全部合并成一个大数组,然后将其赋值到指针out指向的地址内存中去,而out指向不变,变得是指向地址内存中的数据;而out指向与原始args.d相同,所以这里原始的d指向地址内存中的数据也更新了;
*out = concat_datas(buffers, args.threads);
// 也就只有out的shallow敢置为0了,为什么呢?因为out是此次迭代读入的最终数据,该数据参与训练(用完)之后,当然可以深层释放了,而此前的都是中间变量,
// 还处于读入数据阶段,万不可设置shallow=0
out->shallow = 0;
// 释放buffers,buffers也是个中间变量,切记shallow设置为1,如果设置为0,那就连out中的数据也没了
for(i = 0; i < args.threads; ++i){
buffers[i].shallow = 1;
free_data(buffers[i]);//释放buffers所指向的地址
}
// 最终直接释放buffers,threads,注意buffers是一个存储data的一维数组,上面循环中的内存释放,实际是释放每一个data的部分内存
// (这部分内存对data而言是非主要内存,不是存储读入数据的内存块,而是存储指向这些内存块的指针变量,可以释放的)
free(buffers);
free(threads);
return 0;
}
threads[i] = load_data_in_thread(args);
/*
创建一个线程,读入相应图片数据(此时args.n不再是一次迭代读入的所有图片的张数,而是经过load_threads()均匀分配给每个线程的图片张数)
输入: args 包含该线程要读入图片数据的信息(读入多少张,读入图片最终的宽高,图片路径等等)
返回: phtread_t 线程id
说明: 本函数实际没有做什么,就是深拷贝了args给ptr,然后创建了一个调用load_thread()函数的线程并返回线程id
*/
pthread_t load_data_in_thread(load_args args)//为每个线程加载数据
{
pthread_t thread;//声明一个线程
struct load_args *ptr = calloc(1, sizeof(struct load_args));//定义load_args结构体指针并指向动态分配的内存地址
*ptr = args;//将args结构体内容复制到ptr指向的内存空间;还是使用了深拷贝,重复了上面的过程;
//创建一个线程,读入相应数据,绑定load_thread()函数到该线程上,第四个参数是load_thread()的输入参数,第二个参数表示线程属性,设置为0(即NULL)
if(pthread_create(&thread, 0, load_thread, ptr)) error("Thread creation failed");//它的功能是创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。thread_create的返回值 表示成功,返回0;表示出错,返回-1。
return thread;
}
load_thread
void *load_thread(void *ptr)
{
//printf("Loading data: %d\n", rand());
load_args a = *(struct load_args*)ptr;//将指针ptr所指向的地址中内容赋给结构体a,是深拷贝,之后a的数据操作与指针ptr无关,感觉上一个函数完全可以不用转化为指针,这里可以直接将传入参数格式改为结构体load_args,然后传入args更加简便;作者可能是为了多一步pthread_create过程,提高错检度;
if(a.exposure == 0) a.exposure = 1;
if(a.saturation == 0) a.saturation = 1;
if(a.aspect == 0) a.aspect = 1;
if (a.type == OLD_CLASSIFICATION_DATA){
*a.d = load_data_old(a.paths, a.n, a.m, a.labels, a.classes, a.w, a.h);
} else if (a.type == REGRESSION_DATA){
*a.d = load_data_regression(a.paths, a.n, a.m, a.classes, a.min, a.max, a.size, a.angle, a.aspect, a.hue, a.saturation, a.exposure);
} else if (a.type == CLASSIFICATION_DATA){
*a.d = load_data_augment(a.paths, a.n, a.m, a.labels, a.classes, a.hierarchy, a.min, a.max, a.size, a.angle, a.aspect, a.hue, a.saturation, a.exposure, a.center);
} else if (a.type == SUPER_DATA){
*a.d = load_data_super(a.paths, a.n, a.m, a.w, a.h, a.scale);
} else if (a.type == WRITING_DATA){
*a.d = load_data_writing(a.paths, a.n, a.m, a.w, a.h, a.out_w, a.out_h);
} else if (a.type == ISEG_DATA){
*a.d = load_data_iseg(a.n, a.paths, a.m, a.w, a.h, a.classes, a.num_boxes, a.scale, a.min, a.max, a.angle, a.aspect, a.hue, a.saturation, a.exposure);
} else if (a.type == INSTANCE_DATA){
*a.d = load_data_mask(a.n, a.paths, a.m, a.w, a.h, a.classes, a.num_boxes, a.coords, a.min, a.max, a.angle, a.aspect, a.hue, a.saturation, a.exposure);
} else if (a.type == SEGMENTATION_DATA){
*a.d = load_data_seg(a.n, a.paths, a.m, a.w, a.h, a.classes, a.min, a.max, a.angle, a.aspect, a.hue, a.saturation, a.exposure, a.scale);
} else if (a.type == REGION_DATA){//YOLO1时需要使用这种加载方式
*a.d = load_data_region(a.n, a.paths, a.m, a.w, a.h, a.num_boxes, a.classes, a.jitter, a.hue, a.saturation, a.exposure);
} else if (a.type == DETECTION_DATA){
//加载检测的训练数据,并将其赋值到a.d指向的内存中,这里a.d的指向和原始相同,故这里也修改了args.d指向的内存块中的变量
*a.d = load_data_detection(a.n, a.paths, a.m, a.w, a.h, a.num_boxes, a.classes, a.jitter, a.hue, a.saturation, a.exposure);
} else if (a.type == SWAG_DATA){
*a.d = load_data_swag(a.paths, a.n, a.classes, a.jitter);
} else if (a.type == COMPARE_DATA){
*a.d = load_data_compare(a.n, a.paths, a.m, a.classes, a.w, a.h);
} else if (a.type == IMAGE_DATA){
*(a.im) = load_image_color(a.path, 0, 0);
*(a.resized) = resize_image(*(a.im), a.w, a.h);
} else if (a.type == LETTERBOX_DATA){
*(a.im) = load_image_color(a.path, 0, 0);
*(a.resized) = letterbox_image(*(a.im), a.w, a.h);
} else if (a.type == TAG_DATA){
*a.d = load_data_tag(a.paths, a.n, a.m, a.classes, a.min, a.max, a.size, a.angle, a.aspect, a.hue, a.saturation, a.exposure);
}
else if (a.type == MSEG_DATA) {
*a.d = load_data_mseg(a.n, a.paths, a.m, a.w, a.h, a.classes, a.min, a.max, a.angle, a.aspect, a.hue, a.saturation, a.exposure, a.scale);
}
free(ptr);//释放ptr指针
return 0;
}
load_data_region函数,yolo1执行
data load_data_region(int n, char **paths, int m, int w, int h, int size, int classes, float jitter, float hue, float saturation, float exposure)
{
char **random_paths = get_random_paths(paths, n, m);//获取图像
int i;
data d = {0};
d.shallow = 0;
d.X.rows = n;
d.X.vals = calloc(d.X.rows, sizeof(float*));
d.X.cols = h*w*3;
int k = size*size*(5+classes);
d.y = make_matrix(n, k);//创建标签内存
for(i = 0; i < n; ++i){
image orig = load_image_color(random_paths[i], 0, 0);//加载图像
int oh = orig.h;
int ow = orig.w;
//抖动处理
int dw = (ow*jitter);
int dh = (oh*jitter);
int pleft = rand_uniform(-dw, dw);
int pright = rand_uniform(-dw, dw);
int ptop = rand_uniform(-dh, dh);
int pbot = rand_uniform(-dh, dh);
/*
float rand_uniform(float min, float max)
{
if(max < min){
float swap = min;
min = max;
max = swap;
}
return ((float)rand()/RAND_MAX * (max - min)) + min;
}
*/
int swidth = ow - pleft - pright;
int sheight = oh - ptop - pbot;
float sx = (float)swidth / ow;
float sy = (float)sheight / oh;
int flip = rand()%2;
image cropped = crop_image(orig, pleft, ptop, swidth, sheight);
float dx = ((float)pleft/ow)/sx;
float dy = ((float)ptop /oh)/sy;
image sized = resize_image(cropped, w, h);
if(flip) flip_image(sized);
random_distort_image(sized, hue, saturation, exposure);
d.X.vals[i] = sized.data;
fill_truth_region(random_paths[i], d.y.vals[i], classes, size, flip, dx, dy, 1./sx, 1./sy);//加载标签数据
free_image(orig);
free_image(cropped);
}
free(random_paths);
return d;
}
void fill_truth_region(char *path, float *truth, int classes, int num_boxes, int flip, float dx, float dy, float sx, float sy)
{
//获取标签地址
char labelpath[4096];
find_replace(path, "images", "labels", labelpath);
find_replace(labelpath, "JPEGImages", "labels", labelpath);
find_replace(labelpath, ".jpg", ".txt", labelpath);
find_replace(labelpath, ".png", ".txt", labelpath);
find_replace(labelpath, ".JPG", ".txt", labelpath);
find_replace(labelpath, ".JPEG", ".txt", labelpath);
int count = 0;
//获取标签数据和标签个数
box_label *boxes = read_boxes(labelpath, &count);
randomize_boxes(boxes, count);
//对之前增强处理过的图像进行标签矫正
correct_boxes(boxes, count, dx, dy, sx, sy, flip);
float x,y,w,h;
int id;
int i;
//对一幅图像上的每个框进行标签加载
for (i = 0; i < count; ++i) {
x = boxes[i].x;
y = boxes[i].y;
w = boxes[i].w;
h = boxes[i].h;
id = boxes[i].id;
if (w < .005 || h < .005) continue;//去除很小的框
//设置标签所在grid cell位置索引
int col = (int)(x*num_boxes);
int row = (int)(y*num_boxes);
x = x*num_boxes - col;
y = y*num_boxes - row;
int index = (col+row*num_boxes)*(5+classes);
//truth[index]表示置信度,当等于1表示其已经存在标签了
if (truth[index]) continue;
//将每个标签的第一个数即置信度置为1,并令指针指向下一个数;
truth[index++] = 1;
if (id < classes) truth[index+id] = 1;//设置类别
index += classes;//令指针指向所有类别后面的内存
//加载坐标信息
truth[index++] = x;
truth[index++] = y;
truth[index++] = w;
truth[index++] = h;
//从此函数可以看出标签的保存方式为(1+class+box),即(1+20+5),同时x,y都是相对于grid cell的偏移量
}
free(boxes);
}
/*
** 可以参考,看一下对图像进行jitter处理的各种效果:
** https://github.com/vxy10/ImageAugmentation
** 从所有训练图片中,随机读取n张,并对这n张图片进行数据增强,同时矫正增强后的数据标签信息。最终得到的图片的宽高为w,h(原始训练集中的图片尺寸不定),也就是网络能够处理的图片尺寸,
** 数据增强包括:对原始图片进行宽高方向上的插值缩放(两方向上缩放系数不一定相同),下面称之为缩放抖动;随机抠取或者平移图片(位置抖动);
** 在hsv颜色空间增加噪声(颜色抖动);左右水平翻转,不含旋转抖动。
** 输入: n 一个线程读入的图片张数(详见函数内部注释)
** paths 所有训练图片所在路径集合,是一个二维数组,每一行对应一张图片的路径(将在其中随机取n个)
** m paths的行数,也即训练图片总数
** w 网络能够处理的图的宽度(也就是输入图片经过一系列数据增强、变换之后最终输入到网络的图的宽度)
** h 网络能够处理的图的高度(也就是输入图片经过一系列数据增强、变换之后最终输入到网络的图的高度)
** c 用来指定训练图片的通道数(默认为3,即RGB图)
** boxes 每张训练图片最大处理的矩形框数(图片内可能含有更多的物体,即更多的矩形框,那么就在其中随机选择boxes个参与训练,具体执行在fill_truth_detection()函数中)
** classes 类别总数,本函数并未用到(fill_truth_detection函数其实并没有用这个参数)
** use_flip 是否使用水平翻转
** use_mixup 是否使用mixup数据增强
** jitter 这个参数为缩放抖动系数,就是图片缩放抖动的剧烈程度,越大,允许的抖动范围越大(所谓缩放抖动,就是在宽高上插值缩放图片,宽高两方向上缩放的系数不一定相同)
** hue 颜色(hsv颜色空间)数据增强参数:色调(取值0度到360度)偏差最大值,实际色调偏差为-hue~hue之间的随机值
** saturation 颜色(hsv颜色空间)数据增强参数:色彩饱和度(取值范围0~1)缩放最大值,实际为范围内的随机值
** exposure 颜色(hsv颜色空间)数据增强参数:明度(色彩明亮程度,0~1)缩放最大值,实际为范围内的随机值
** mini_batch 和目标跟踪有关,这里不关注
** track 和目标跟踪有关,这里不关注
** augment_speed 和目标跟踪有关,这里不关注
** letter_box 是否进行letter_box变换
** show_imgs
** 返回: data类型数据,包含一个线程读入的所有图片数据(含有n张图片)
** 说明: 最后四个参数用于数据增强,主要对原图进行缩放抖动,位置抖动(平移)以及颜色抖动(颜色值增加一定噪声),抖动一定程度上可以理解成对图像增加噪声。
** 通过对原始图像进行抖动,实现数据增强。最后三个参数具体用法参考本函数内调用的random_distort_image()函数
** 说明2:从此函数可以看出,darknet对训练集中图片的尺寸没有要求,可以是任意尺寸的图片,因为经该函数处理(缩放/裁剪)之后,
** 不管是什么尺寸的照片,都会统一为网络训练使用的尺寸
*/
*a.d = load_data_detection(a.n, a.paths, a.m, a.w, a.h, a.num_boxes, a.classes, a.jitter, a.hue, a.saturation, a.exposure);
data load_data_detection(int n, char **paths, int m, int w, int h, int boxes, int classes, float jitter, float hue, float saturation, float exposure)
{
// 指针path指向的地址内存和原始结构体相同;paths包含所有训练图片的路径,get_random_paths函数从中随机提出n条,即为此次读入的n张图片的路径
char **random_paths = get_random_paths(paths, n, m);//从m个图像中随机选择n幅图像,并将n幅图像的第一幅地址赋给指针random_paths,这里m是所有的训练集图像,n则是每个线程需要加载的图像数,即batch*sub*ngpus/threads,例如若是设置了64个线程,则每个线程只需要加载一幅图像,若是设置了一个线程,则这个线程就需要加载64幅图像,其中min_batch*sub=64.
/*
char **get_random_paths(char **paths, int n, int m)
{
char **random_paths = calloc(n, sizeof(char*));
int i;
pthread_mutex_lock(&mutex);
for(i = 0; i < n; ++i){
int index = rand()%m;
random_paths[i] = paths[index];
//if(i == 0) printf("%s\n", paths[index]);
}
pthread_mutex_unlock(&mutex);
return random_paths;
}
*/
int i;
data d = {0};//定义结构体d并初始化为0
d.shallow = 0;
// 一次读入的图片张数:d.X中每行就是一张图片的数据,因此d.X.cols等于h*w*3
// 因此本函数中的n实际不是总的n,而是分配到该线程上的n,比如总共要读入128张图片,共开启8个线程读数据,那么本函数中的n为16,而不是总数128
d.X.rows = n;//设置矩阵的行为需要加载的图像数n
d.X.vals = calloc(d.X.rows, sizeof(float*));//定义并初始化指针
d.X.cols = h*w*3;//列为每幅图像的大小即高*宽*通道
// d.y存储了所有读入照片的标签信息,每条标签包含5条信息:类别,以及矩形框的x,y,w,h
// boxes为一张图片最多能够处理(参与训练)的矩形框的数(如果图片中的矩形框数多于这个数,那么随机挑选boxes个,这个参数仅在parse_region以及parse_detection中出现,好奇怪?
// 在其他网络解析函数中并没有出现)。同样,d.y是一个matrix,make_matrix会指定y的行数和列数,同时会为其第一维动态分配内存
d.y = make_matrix(n, 5*boxes);//存放标签数据,即每幅图像标注框的数据,这里和yolo1的存储方式不同了,只是存储了n个图像上最大框数的位置标签,默认是1幅图像最大检测框是90,所以这里就保存了每幅图上90个框;显然yolo1上一幅图最大检测框也就49个;在yolo1加载数据存储的是一幅图上所以的标签即7*7*(5+20),即包含类别也包含位置;
/*
matrix make_matrix(int rows, int cols)
{
int i;
matrix m;//声明一个matrix结构体m
//对结构体m进行赋值
m.rows = rows;
m.cols = cols;
m.vals = calloc(m.rows, sizeof(float *));//初始化指针
for(i = 0; i < m.rows; ++i){//对每一个指针进行初始化
m.vals[i] = calloc(m.cols, sizeof(float));
}
return m;
}
*/
for(i = 0; i < n; ++i){
image orig = load_image_color(random_paths[i], 0, 0);//装载原始图像
image sized = make_image(w, h, orig.c);//根据设置的w和h创建一幅图像
fill_image(sized, .5);//对设置的图像像素进行初始化为0.5
// 缩放抖动大小:缩放抖动系数乘以原始图宽高即得像素单位意义上的缩放抖动
float dw = jitter * orig.w;
float dh = jitter * orig.h;
//得到原始图像的宽和高进行抖动缩放后的宽高比
float new_ar = (orig.w + rand_uniform(-dw, dw)) / (orig.h + rand_uniform(-dh, dh));
//float scale = rand_uniform(.25, 2);
float scale = 1;
float nw, nh;
//如果宽高比小于1,则获取设置的高不变,宽变为高乘以new_ar的值;
//若大于1,则宽不变,高变为宽除以new_ar的值;
if(new_ar < 1){
nh = scale * h;
nw = nh * new_ar;
} else {
nw = scale * w;
nh = nw / new_ar;
}
//从设定的范围内随机获得dx,dy的值,由于之前的设置使得nw和nh总有一个与w或h相同,故dx、dy总有一个为0
float dx = rand_uniform(0, w - nw);
float dy = rand_uniform(0, h - nh);
//将原始图像使用创建的图像替代
place_image(orig, nw, nh, dx, dy, sized);
/*
void place_image(image im, int w, int h, int dx, int dy, image canvas)
{
int x, y, c;
for(c = 0; c < im.c; ++c){
for(y = 0; y < h; ++y){
for(x = 0; x < w; ++x){
float rx = ((float)x / w) * im.w;
float ry = ((float)y / h) * im.h;
float val = bilinear_interpolate(im, rx, ry, c);
set_pixel(canvas, x + dx, y + dy, c, val);
}
}
}
}
static void set_pixel(image m, int x, int y, int c, float val)
{
if (x < 0 || y < 0 || c < 0 || x >= m.w || y >= m.h || c >= m.c) return;
assert(x < m.w && y < m.h && c < m.c);
m.data[c*m.h*m.w + y*m.w + x] = val;
}
*/
//使用亮度、饱和度、曝光度增强图像样本
random_distort_image(sized, hue, saturation, exposure);
/*
void random_distort_image(image im, float hue, float saturation, float exposure)
{
float dhue = rand_uniform(-hue, hue);//在范围内随机获得色彩值
float dsat = rand_scale(saturation);//在范围内随机获得饱和度值
float dexp = rand_scale(exposure);//在范围内随机获得曝光值
distort_image(im, dhue, dsat, dexp);
}
void distort_image(image im, float hue, float sat, float val)
{
rgb_to_hsv(im);//将图像转化为hsv格式通道
scale_image_channel(im, 1, sat);
scale_image_channel(im, 2, val);
int i;
for(i = 0; i < im.w*im.h; ++i){
im.data[i] = im.data[i] + hue;
if (im.data[i] > 1) im.data[i] -= 1;
if (im.data[i] < 0) im.data[i] += 1;
}
hsv_to_rgb(im);
constrain_image(im);
}
*/
//使用翻转增强图像样本
int flip = rand()%2;
if(flip) flip_image(sized);
//进行深拷贝,令sized.data中的值复制到d.X.vals[i]指向的空间
d.X.vals[i] = sized.data;
//根据增强图像的变化相应的改变真实标签并填充到d.y.vals[i]中
fill_truth_detection(random_paths[i], boxes, d.y.vals[i], classes, flip, -dx/w, -dy/h, nw/w, nh/h);
/*
void fill_truth_detection(char *path, int num_boxes, float *truth, int classes, int flip, float dx, float dy, float sx, float sy)
{
获取图像对应的标签地址
char labelpath[4096];
find_replace(path, "images", "labels", labelpath);
find_replace(labelpath, "JPEGImages", "labels", labelpath);
find_replace(labelpath, "raw", "labels", labelpath);
find_replace(labelpath, ".jpg", ".txt", labelpath);
find_replace(labelpath, ".png", ".txt", labelpath);
find_replace(labelpath, ".JPG", ".txt", labelpath);
find_replace(labelpath, ".JPEG", ".txt", labelpath);
int count = 0;
从地址中读取标签文件
box_label *boxes = read_boxes(labelpath, &count);
randomize_boxes(boxes, count);
correct_boxes(boxes, count, dx, dy, sx, sy, flip);
if(count > num_boxes) count = num_boxes;
float x,y,w,h;
int id;
int i;
int sub = 0;
for (i = 0; i < count; ++i) {
x = boxes[i].x;
y = boxes[i].y;
w = boxes[i].w;
h = boxes[i].h;
id = boxes[i].id;
if ((w < .001 || h < .001)) {
++sub;
continue;
}
truth[(i-sub)*5+0] = x;
truth[(i-sub)*5+1] = y;
truth[(i-sub)*5+2] = w;
truth[(i-sub)*5+3] = h;
truth[(i-sub)*5+4] = id;
}
free(boxes);
}
*/
free_image(orig);//释放orig内存
}
free(random_paths);//释放指针random_paths
return d;
}