darknet加载训练数据load_data函数

29 篇文章 1 订阅
27 篇文章 2 订阅

数据加载函数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;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值