darknet源码剖析(二) 数据加载

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012927281/article/details/86550702

在上一篇文章中,我们分析了darknet的网络结构与初始化过程,在源码中是load_network函数。接下来继续分析数据加载过程。数据加载过程中涉及的函数有load_data、load_threads、load_data_in_thread、load_thread。

数据加载过程如下图所示。

(1)load_data中首先启动一个线程调用load_threads,load_threads是实际加载数据的线程,再load_threads加载完成前,主线程会等待。

(2)在load_threads通过循环方式,共调用load_data_in_thread函数64次。数据加载过程在load_data_in_thread函数中完成。

以下几行代码需要注意:

    data *buffers = calloc(args.threads, sizeof(data));
    pthread_t *threads = calloc(args.threads, sizeof(pthread_t));
    for(i = 0; i < args.threads; ++i){
        args.d = buffers + i;
        args.n = (i+1) * total/args.threads - i * total/args.threads;
        threads[i] = load_data_in_thread(args);
    }

共申请了64个data类型空间与64个pthread_t类型空间,在循环中分别调用load_data_in_thread,每次args.d的地址向后移动一个data类型空间,args.n的值一直为1。

(3)load_data_in_thread函数中启动线程,并调用load_thread函数,主线程返回load_threads中等待。

(4)load_thread函数调用load_data_detection函数加载数据。

下面对load_data_detection进行详细分析。load_data_detection函数每次仅加载一张图片,因此一个batch共加载64张图片。其中有3个函数需要重点关注。

    place_image(orig, nw, nh, dx, dy, sized);
    random_distort_image(sized, hue, saturation, exposure);
    fill_truth_detection(random_paths[i], boxes, d.y.vals[i], classes, flip, -dx/w, -dy/h, nw/w, nh/h);

以上三个函数的功能是来自于我的推测,还没有详细验证,place_image的功能是将原图(orig)的一部分放入输入图片中,random_distort_image是随机扭曲图片,fill_truth_detection的功能是填充bounding_box,但具体如何变换还没有搞清楚。

至此darknet的数据加载流程就已经搞清楚了,还有另一条线需要明确一下那就是数据是如何返回的。

1)首先是在detector.c中的train_detector函数中有以下几行代码:

    data train, buffer;
    args.d = &buffer;

通过以上两行代码可以知道,现在args.d指向buffer的地址。

2)进入load_data函数(data.c):

    struct load_args *ptr = calloc(1, sizeof(struct load_args));
    *ptr = args;

现在ptr是args的拷贝,二者使用不同的内存空间,但由于ptr是args的拷贝,因此ptr.d同样也指向buffer。

3)进入load_threads函数(data.c):

    load_args args = *(load_args *)ptr;
    data *out = args.d;
    free(ptr);
    data *buffers = calloc(args.threads, sizeof(data));

相同的套路在此处又出现了一次,此时args是ptr的拷贝,args.d同样指向buffer。然后令out同样指向buffer。接下来free ptr,也即在load_data中申请的内存在load_threads中释放。此处申请的buffers是用来实际存放训练数据的。

    args.d = buffers + i;

此处实际上是args的复用。由于args不是指针,是变量,因此分配有内存空间。

4)进入load_data_in_thread函数(data.c):

    struct load_args *ptr = calloc(1, sizeof(struct load_args));
    *ptr = args;

又是相同的套路,ptr复制args的数据,此时ptr.d指向的是buffers对应的内存地址,注意此处是buffers,不是buffer。

5)进入load_thread函数(data.c):

    load_args a = *(struct load_args*)ptr;
    *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);
    free(ptr);

此处a是ptr的拷贝,a.d指向的是buffers的地址空间,所以不需要再额外调用calloc函数,就可以直接使用*a.d。free ptr后,在load_data_in_thread中申请的内存空间也就被释放了。同时load_thread函数返回后,a变量占用的栈空间也会被释放。

6)数据回传

通过对函数的返回过程进行分析,同时分析数据的回传过程。

(1)在load_thread函数中训练数据已经被存储到buffers指向的内存空间中。同时在load_data_in_thread中申请的空间也被释放了,不存在内存泄漏的问题。

(2)回到load_threads函数中。

    *out = concat_datas(buffers, args.threads);

buffers中的数据都被concat在一起,同时复制到out执行的内存空间,而out指向的内存空间恰好是buffer的内存空间,由于已经申请过内存空间了,因此也不需要再calloc了。

    for(i = 0; i < args.threads; ++i){
        buffers[i].shallow = 1;
        free_data(buffers[i]);
    }
    free(buffers);
    free(threads);

通过上述过程彻底释放在load_threads中分配的内存空间。同样没有内存泄漏的问题。

(3)回到load_data函数中:

在load_data函数申请的ptr空间在load_threads中释放了,同样没有内存泄漏问题,同时训练数据也被传回到train_detector中。

还是记录一下当前的问题:

1)Box–Muller transform

2)load_data_detection的详细分析

猜你喜欢

转载自blog.csdn.net/u012927281/article/details/86550702
今日推荐