天天看點

【AlexeyAB DarkNet架構解析】三,加載資料進行訓練

前言

昨天講了DarkNet的底層資料結構,并且将網絡配置檔案進行了解析存放到了一個​

​network​

​結構體中,那麼今天我們就要來看一下Darknet是如何加載資料進行訓練的。

加載訓練資料

DarkNet的資料加載函數​

​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中,圖檔的存儲形式是一個行向量,向量長度為​

​h*w*3​

​​。同時圖檔被歸一化到​

​[0, 1]​

​之間。

load_threads()完成線程配置設定和資料拼接

​load_threads​

​​在​

​src/data.c​

​中實作,代碼如下:

// copy from https://github.com/hgpvision/darknet/blob/master/src/data.c#L355
/*
** 開辟多個線程讀入圖檔資料,讀入資料存儲至ptr.d中(主要調用load_in_thread()函數完成)
** 輸入: ptr    包含所有線程要讀入圖檔資料的資訊(讀入多少張,開幾個線程讀入,讀入圖檔最終的寬高,圖檔路徑等等)
** 傳回: void*  萬能指針(實際上不需要傳回什麼)
** 說明: 1) load_threads()是一個指針函數,隻是一個傳回變量為void*的普通函數,不是函數指針
**       2) 輸入ptr是一個void*指針(萬能指針),使用時需要強轉為具體類型的指針
**       3) 函數中涉及四個用來存儲讀入資料的變量:ptr, args, out, buffers,除args外都是data*類型,所有這些變量的
**          指針變量其實都指向同一塊記憶體(當然函數中間有些動态變化),是以讀入的資料都是互通的。
** 流程: 本函數首先會擷取要讀入圖檔的張數、要開啟線程的個數,而後計算每個線程應該讀入的圖檔張數(盡可能的均勻配置設定),
**       并建立所有的線程,并行讀入資料,最後合并每個線程讀入的資料至一個大data中,這個data的指針變量與ptr的指針變量
**       指向的是統一塊記憶體,是以也就最終将資料讀入到ptr.d中(是以其實沒有傳回值)
*/
void *load_threads(void *ptr)
{
    //srand(time(0));
    int i;
    // 先使用(load_args*)強轉void*指針,而後取ptr所指内容指派給args
    // 雖然args不是指針,args是深拷貝了ptr中的内容,但是要知道ptr(也就是load_args資料類型),有很多的
    // 指針變量,args深拷貝将拷貝這些指針變量到args中(這些指針變量本身對ptr來說就是内容,
    // 而args所指的值是args的内容,不是ptr的,不要混為一談),是以,args與ptr将會共享所有指針變量所指的内容
    load_args args = *(load_args *)ptr;
    if (args.threads == 0) args.threads = 1;
    // 另指針變量out=args.d,使得out與args.d指向統一塊記憶體,之後,args.d所指的記憶體塊會變(反正也沒什麼用了,變就變吧),
    // 但out不會變,這樣可以保證out與最原始的ptr指向同一塊存儲讀入圖檔資料的記憶體塊,是以最終将圖檔讀到out中,
    // 實際就是讀到了最原始的ptr中,比如train_detector()函數中定義的args.d中
    data *out = args.d;
    // 讀入圖檔的總張數= batch * subdivision * ngpus,可參見train_detector()函數中的指派
    int total = args.n;
    // 釋放ptr:ptr是傳入的指針變量,傳入的指針變量本身也是按值傳遞的,即傳入函數之後,指針變量得到複制,函數内的形參ptr
    // 擷取外部實參的值之後,二者本身沒有關系,但是由于是指針變量,二者之間又存在一絲關系,那就是函數内形參與函數外實參指向
    // 同一塊記憶體。又由于函數外實參記憶體是動态配置設定的,是以函數内的形參可以使用free()函數進行記憶體釋放,但一般不推薦這麼做,因為函數内釋放記憶體,
    // 會影響函數外實參的使用,可能使之成為野指針,那為什麼這裡可以用free()釋放ptr呢,不會出現問題嗎?
    // 其一,因為ptr是一個結構體,是一個包含衆多的指針變量的結構體,如data* d等(當然還有其他非指針變量如int h等),
    // 直接free(ptr)将會導緻函數外實參無法再通路非指針變量int h等(實際經過測試,在gcc編譯器下,能通路但是值被重新初始化為0),
    // 因為函數内形參和函數外實參共享一塊堆記憶體,而這些非指針變量都是存在這塊堆記憶體上的,記憶體一釋放,就無法通路了;
    // 但是對于指針變量,free(ptr)将無作為(這個結論也是經過測試的,也是用的gcc編譯器),不會釋放或者擦寫掉ptr指針變量本身的值,
    // 當然也不會影響函數外實參,更不會牽扯到這些指針變量所指的記憶體塊,總的來說,
    // free(ptr)将使得ptr不能再通路指針變量(如int h等,實際經過測試,在gcc編譯器下,能通路但是值被重新初始化為0),
    // 但其指針變量本身沒有受影響,依舊可以通路;對于函數外實參,同樣不能通路非指針變量,而指針變量不受影響,依舊可以通路。
    // 其二,darknet資料讀取的實作一層套一層(似乎有點羅嗦,總感覺代碼可以不用這麼寫的:)),具體調用過程如下:
    // load_data(load_args args)->load_threads(load_args* ptr)->load_data_in_thread(load_args args)->load_thread(load_args* ptr),
    // 就在load_data()中,重新定義了ptr,并為之動态配置設定了記憶體,且深拷貝了傳給load_data()函數的值args,也就是說在此之後load_data()函數中的args除了其中的指針變量指着同一塊堆記憶體之外,
    // 二者的非指針變量再無瓜葛,不管之後經過多少個函數,對ptr的非指針變量做了什麼改動,比如這裡直接free(ptr),使得非指針變量值為0,都不會影響load_data()中的args的非指針變量,也就不會影響更為頂層函數中定義的args的非指針變量的值,
    // 比如train_detector()函數中的args,train_detector()對args非指針變量賦的值都不會受影響,保持不變。綜其兩點,此處直接free(ptr)是安全的。
    // 說明:free(ptr)函數,确定會做的事是使得記憶體塊可以重新配置設定,且不會影響指針變量ptr本身的值,也就是ptr還是指向那塊位址, 雖然可以使用,但很危險,因為這塊記憶體實際是無效的,
    //      系統已經認為這塊記憶體是可配置設定的,會毫不考慮的将這塊記憶體分給其他變量,這樣,其值随時都可能會被其他變量改變,這種情況下的ptr指針就是所謂的野指針(是以經常可以看到free之後,置原指針為NULL)。
    //      而至于free(ptr)還不會做其他事情,比如會不會重新初始化這塊記憶體為0(擦寫掉),以及怎麼擦寫,這些操作,是不确定的,可能跟具體的編譯器有關(個人猜測),
    //      經過測試,對于gcc編譯器,free(ptr)之後,ptr中的非指針變量的位址不變,但其值全部擦寫為0;ptr中的指針變量,絲毫不受影響,指針變量本身沒有被擦寫,
    //      存儲的位址還是指向先前配置設定的記憶體塊,是以ptr能夠正常通路其指針變量所指的值。測試代碼為darknet_test_struct_memory_free.c。
    //      不知道這段測試代碼在VS中執行會怎樣,還沒經過測試,也不知道換用其他編譯器(darknet的Makefile檔案中,指定了編譯器為gcc),darknet的編譯會不會有什麼問題??
    //      關于free(),可以看看:http://blog.sina.com.cn/s/blog_615ec1630102uwle.html,文章最後有一個很有意思的比喻,但意思好像就和我這裡說的有點不一樣了(到底是不是編譯器搞得鬼呢??)。
    free(ptr);
    // 每一個線程都會讀入一個data,定義并配置設定args.thread個data的記憶體
    data* buffers = (data*)xcalloc(args.threads, sizeof(data));
    pthread_t* threads = (pthread_t*)xcalloc(args.threads, sizeof(pthread_t));
    // 此處定義了多個線程,并為每個線程動态配置設定記憶體
    for(i = 0; i < args.threads; ++i){
        // 此處就承應了上面的注釋,args.d指針變量本身發生了改動,使得本函數的args.d與out不再指向同一塊記憶體,
        // 改為指向buffers指向的某一段記憶體,因為下面的load_data_in_thread()函數統一了結口,需要輸入一個load_args類型參數,
        // 實際是想把圖檔資料讀入到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;
        // 開啟線程,讀入資料到args.d中(也就讀入到buffers[i]中)
        // load_data_in_thread()函數傳回所開啟的線程,并存儲之前已經動态配置設定記憶體用來存儲所有線程的threads中,
        // 友善下面使用pthread_join()函數控制相應線程
        threads[i] = load_data_in_thread(args);
    }
    for(i = 0; i < args.threads; ++i){
        // 以阻塞的方式等待線程threads[i]結束:阻塞是指阻塞啟動該子線程的母線程(此處應為主線程),
        // 是母線程處于阻塞狀态,一直等待所有子線程執行完(讀完所有資料)才會繼續執行下面的語句
        // 關于多線程的使用,進行過代碼測試,測試代碼對應:darknet_test_pthread_join.c
        pthread_join(threads[i], 0);
    }
    // 多個線程讀入所有資料之後,分别存儲到buffers[0],buffers[1]...中,接着使用concat_datas()函數将buffers中的資料全部合并成一個大數組得到out
    *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,threads,注意buffers是一個存儲data的一維數組,上面循環中的記憶體釋放,實際是釋放每一個data的部分記憶體
    // (這部分記憶體對data而言是非主要記憶體,不是存儲讀入資料的記憶體塊,而是存儲指向這些記憶體塊的指針變量,可以釋放的)
    free(buffers);
    free(threads);
    return 0;
}      

load_data_in_thread()配置設定線程

​load_data_in_thread()​

​​函數仍然在​

​src/data.c​

​中,代碼如下:

/*
** 建立一個線程,讀入相應圖檔資料(此時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;
    // 同樣第一件事深拷貝了args給ptr(為什麼每次都要做這一步呢?求指點啊~)
    struct load_args* ptr = (load_args*)xcalloc(1, sizeof(struct load_args));
    *ptr = args;
    // 建立一個線程,讀入相應資料,綁定load_thread()函數到該線程上,第四個參數是load_thread()的輸入參數,第二個參數表示線程屬性,設定為0(即NULL)
    if(pthread_create(&thread, 0, load_thread, ptr)) error("Thread creation failed");
    return thread;
}      

load_data_detection()完成底層的資料加載任務

​load_data_detection()​

​​函數也定義在​

​src/data.c​

​中,帶注釋的代碼如下:

/*
** 可以參考,看一下對圖像進行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對訓練集中圖檔的尺寸沒有要求,可以是任意尺寸的圖檔,因為經該函數處理(縮放/裁剪)之後,
**       不管是什麼尺寸的照片,都會統一為網絡訓練使用的尺寸
*/

data load_data_detection(int n, char **paths, int m, int w, int h, int c, int boxes, int classes, int use_flip, int use_blur, int use_mixup, float jitter,
    float hue, float saturation, float exposure, int mini_batch, int track, int augment_speed, int letter_box, int show_imgs)
{
    const int random_index = random_gen();
    c = c ? c : 3;
    char **random_paths;
    char **mixup_random_paths = NULL;
    // paths包含所有訓練圖檔的路徑,get_random_paths函數從中随機提出n條,即為此次讀入的n張圖檔的路徑
    if(track) random_paths = get_sequential_paths(paths, n, m, mini_batch, augment_speed);
    else random_paths = get_random_paths(paths, n, m);

    assert(use_mixup < 2);
    int mixup = use_mixup ? random_gen() % 2 : 0;
    //printf("\n mixup = %d \n", mixup);
    // 如果使用mixup政策,需要再随機取出n條資料,即n張圖檔
    if (mixup) {
        if (track) mixup_random_paths = get_sequential_paths(paths, n, m, mini_batch, augment_speed);
        else mixup_random_paths = get_random_paths(paths, n, m);
    }

    int i;
    // 初始化為0,清空記憶體中之前的舊值
    data d = { 0 };
    d.shallow = 0;
    // 一次讀入的圖檔張數:d.X中每行就是一張圖檔的資料,是以d.X.cols等于h*w*3
    // n = net.batch * net.subdivisions * ngpus,net中的subdivisions這個參數暫時還沒搞懂有什麼用,
    // 從parse_net_option()函數可知,net.batch = net.batch / net.subdivision,等号右邊的那個batch就是
    // 網絡配置檔案.cfg中設定的每個batch的圖檔數量,但是不知道為什麼多了subdivision這個參數?總之,
    // net.batch * net.subdivisions又得到了在網絡配置檔案中設定的batch值,然後乘以ngpus,是考慮多個GPU實作資料并行,
    // 一次讀入多個batch的資料,配置設定到不同GPU上進行訓練。在load_threads()函數中,又将整個的n僅可能均勻的劃分到每個線程上,
    // 也就是總的讀入圖檔張數為n = net.batch * net.subdivisions * ngpus,但這些圖檔不是一個線程讀完的,而是配置設定到多個線程并行讀入,
    // 是以本函數中的n實際不是總的n,而是配置設定到該線程上的n,比如總共要讀入128張圖檔,共開啟8個線程讀資料,那麼本函數中的n為16,而不是總數128
    d.X.rows = n;
    //d.X為一個matrix類型資料,其中d.X.vals是其具體資料,是指針的指針(即為二維數組),此處先為第一維動态配置設定記憶體
    d.X.vals = (float**)xcalloc(d.X.rows, sizeof(float*));
    d.X.cols = h*w*c;

    float r1 = 0, r2 = 0, r3 = 0, r4 = 0, r_scale;
    float dhue = 0, dsat = 0, dexp = 0, flip = 0;
    int augmentation_calculated = 0;
    // 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);
    int i_mixup = 0;
    for (i_mixup = 0; i_mixup <= mixup; i_mixup++) {
        if (i_mixup) augmentation_calculated = 0;
        for (i = 0; i < n; ++i) {
            
            float *truth = (float*)xcalloc(5 * boxes, sizeof(float));
            char *filename = (i_mixup) ? mixup_random_paths[i] : random_paths[i];
            //讀入原始的圖檔
            image orig = load_image(filename, 0, 0, c);
            // 原始圖像長寬
            int oh = orig.h;
            int ow = orig.w;
            // 縮放抖動大小:縮放抖動系數乘以原始圖寬高即得像素機關意義上的縮放抖動
            int dw = (ow*jitter);
            int dh = (oh*jitter);

            if (!augmentation_calculated || !track)
            {
                augmentation_calculated = 1;
                r1 = random_float();
                r2 = random_float();
                r3 = random_float();
                r4 = random_float();

                r_scale = random_float();

                dhue = rand_uniform_strong(-hue, hue);
                dsat = rand_scale(saturation);
                dexp = rand_scale(exposure);

                flip = use_flip ? random_gen() % 2 : 0;
            }

            int pleft = rand_precalc_random(-dw, dw, r1);
            int pright = rand_precalc_random(-dw, dw, r2);
            int ptop = rand_precalc_random(-dh, dh, r3);
            int pbot = rand_precalc_random(-dh, dh, r4);
            // 這個系數沒用到
            float scale = rand_precalc_random(.25, 2, r_scale); // unused currently

            if (letter_box)
            {
                float img_ar = (float)ow / (float)oh; //原始圖像寬高比
                float net_ar = (float)w / (float)h; //輸入到網絡要求的圖像寬高比
                float result_ar = img_ar / net_ar; //兩者求比值來判斷如何進行letter_box縮放
                //printf(" ow = %d, oh = %d, w = %d, h = %d, img_ar = %f, net_ar = %f, result_ar = %f \n", ow, oh, w, h, img_ar, net_ar, result_ar);
                if (result_ar > 1)  // sheight - should be increased
                {
                    float oh_tmp = ow / net_ar;
                    float delta_h = (oh_tmp - oh) / 2;
                    ptop = ptop - delta_h;
                    pbot = pbot - delta_h;
                    //printf(" result_ar = %f, oh_tmp = %f, delta_h = %d, ptop = %f, pbot = %f \n", result_ar, oh_tmp, delta_h, ptop, pbot);
                }
                else  // swidth - should be increased
                {
                    float ow_tmp = oh * net_ar;
                    float delta_w = (ow_tmp - ow) / 2;
                    pleft = pleft - delta_w;
                    pright = pright - delta_w;
                    //printf(" result_ar = %f, ow_tmp = %f, delta_w = %d, pleft = %f, pright = %f \n", result_ar, ow_tmp, delta_w, pleft, pright);
                }
            }
            // 以下步驟就是執行了letter_box變換
            int swidth = ow - pleft - pright;
            int sheight = oh - ptop - pbot;

            float sx = (float)swidth / ow;
            float sy = (float)sheight / oh;

            image cropped = crop_image(orig, pleft, ptop, swidth, sheight);

            float dx = ((float)pleft / ow) / sx;
            float dy = ((float)ptop / oh) / sy;
            // resize到指定大小
            image sized = resize_image(cropped, w, h);
            // 翻轉
            if (flip) flip_image(sized);
            //随機對圖像jitter(在hsv三個通道上添加擾動),實作資料增強
            distort_image(sized, dhue, dsat, dexp);
            //random_distort_image(sized, hue, saturation, exposure);
            // truth包含所有圖像的标簽資訊(包括真實類别與位置
            // 因為對原始圖檔進行了資料增強,其中的平移抖動勢必會改動每個物體的矩形框标簽資訊(主要是矩形框的像素坐标資訊),需要根據具體的資料增強方式進行相應矯正
            // 後面的參數就是用于資料增強後的矩形框資訊矯正
            fill_truth_detection(filename, boxes, truth, classes, flip, dx, dy, 1. / sx, 1. / sy, w, h);

            if (i_mixup) {
                image old_img = sized;
                old_img.data = d.X.vals[i];
                //show_image(sized, "new");
                //show_image(old_img, "old");
                //wait_until_press_key_cv();
                // 做mixup,混合系數為0.5
                blend_images(sized, 0.5, old_img, 0.5);
                // 标簽也要對應改變
                blend_truth(truth, boxes, d.y.vals[i]);
                free_image(old_img);
            }

            d.X.vals[i] = sized.data;
            memcpy(d.y.vals[i], truth, 5 * boxes * sizeof(float));

            if (show_imgs)// && i_mixup)
            {
                char buff[1000];
                sprintf(buff, "aug_%d_%d_%s_%d", random_index, i, basecfg(filename), random_gen());

                int t;
                for (t = 0; t < boxes; ++t) {
                    box b = float_to_box_stride(d.y.vals[i] + t*(4 + 1), 1);
                    if (!b.x) break;
                    int left = (b.x - b.w / 2.)*sized.w;
                    int right = (b.x + b.w / 2.)*sized.w;
                    int top = (b.y - b.h / 2.)*sized.h;
                    int bot = (b.y + b.h / 2.)*sized.h;
                    draw_box_width(sized, left, top, right, bot, 1, 150, 100, 50); // 3 channels RGB
                }

                save_image(sized, buff);
                if (show_imgs == 1) {
                    show_image(sized, buff);
                    wait_until_press_key_cv();
                }
                printf("\nYou use flag -show_imgs, so will be saved aug_...jpg images. Press Enter: \n");
                //getchar();
            }

            free_image(orig);
            free_image(cropped);
            free(truth);
        }
    }
    free(random_paths);
    if (mixup_random_paths) free(mixup_random_paths);
    return d;
}
#endif    // OPENCV      

load_data(args)使用方法

在​

​src/detector.c​

​​中的的​

​train_detector()​

​​函數共有​

​3​

​​次調用​

​load_data(args)​

​​,第一次調用是為訓練階段做好資料準備工作,充分利用這段時間來加載資料。第二次調用是在​

​resize​

​​操作中,可以看到這裡隻有​

​random​

​​和​

​count​

​​同時滿足條件的情況下會做​

​resize​

​​操作,也就是說​

​resize​

​​加載的資料是未進行​

​resize​

​​過的,是以,需要調整​

​args​

​​中的圖像寬高之後再重新調用​

​load_data(args)​

​​加載資料。反之,不做任何處理,之前加載的資料仍然可用。第三次調用就是在資料加載完成後,将加載好的資料儲存起來​

​train=buffer​

​​; 然後開始下一次的加載工作。這一次的資料就會進行這一次的訓練操作(調用​

​train_network​

​函數)。

後記