1. 程式人生 > >darknet原始碼解讀-load_data

darknet原始碼解讀-load_data

    這裡的資料載入部分的程式碼由detector.c檔案中train_detector函式中load_data處開始解讀。

void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *gpus, int ngpus, int clear)
{
    list *options = read_data_cfg(datacfg);
    char *train_images = option_find_str(options, "train", "data/train.list");
	//store weights?
    char *backup_directory = option_find_str(options, "backup", "/backup/");

    srand(time(0));

	//from /a/b/yolov2.cfg extract yolov2
    char *base = basecfg(cfgfile); //network config
    printf("%s\n", base);
	
    float avg_loss = -1;
    network **nets = calloc(ngpus, sizeof(network));

    srand(time(0));
    int seed = rand();
    int i;
    for(i = 0; i < ngpus; ++i){
        srand(seed);
#ifdef GPU
        cuda_set_device(gpus[i]);
#endif
		//create network for every GPU
        nets[i] = load_network(cfgfile, weightfile, clear);
        nets[i]->learning_rate *= ngpus;
    }
    srand(time(0));
    network *net = nets[0];

	//subdivisions,why not divide?
    int imgs = net->batch * net->subdivisions * ngpus;
    printf("Learning Rate: %g, Momentum: %g, Decay: %g\n", net->learning_rate, net->momentum, net->decay);

	data train, buffer;

    //the last layer e.g. [region] for yolov2
    layer l = net->layers[net->n - 1];

    int classes = l.classes; 
    float jitter = l.jitter;

    list *plist = get_paths(train_images);
    //int N = plist->size;
    char **paths = (char **)list_to_array(plist);

    load_args args = get_base_args(net);
    args.coords = l.coords;
    args.paths = paths;
    args.n = imgs;        //一次載入的數量       
    args.m = plist->size; //總的圖片數量
    args.classes = classes;
    args.jitter = jitter;
    args.num_boxes = l.max_boxes;
    args.d = &buffer;
    args.type = DETECTION_DATA;
    //args.type = INSTANCE_DATA;
    args.threads = 64;

	/*n張圖片以及圖片上的truth box會被載入到buffer.X,buffer.y裡面去*/
    pthread_t load_thread = load_data(args); 
    ....
}

在輸入到load_data函式的args結構中有幾個引數需要關注,args.n它是表示這一次載入的影象的數量,args.m是表示訓練集中影象的總量,args.num_boxes表示一張影象中允許的最大的檢測框的數量。

pthread_t load_data(load_args args)
{
    pthread_t thread;
    struct load_args *ptr = calloc(1, sizeof(struct load_args));
    *ptr = args; //e.g. ptr->path from args.path
    if(pthread_create(&thread, 0, load_threads, ptr)) error("Thread creation failed");
    return thread;
}

load_data函式裡面會建立一個load_threads的執行緒,從名字上來理解它是一個用來載入執行緒的執行緒,資料的載入並不是由這個執行緒直接來負責的,它更像是一個數據載入執行緒管理者的角色。

void *load_threads(void *ptr)
{
    int i;
    load_args args = *(load_args *)ptr;
    if (args.threads == 0) args.threads = 1;
	
    data *out = args.d; 
    int total = args.n;
    free(ptr);

	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;
		//why not total/args.threads?
        args.n = (i+1) * total/args.threads - i * total/args.threads;
        threads[i] = load_data_in_thread(args);
    }

	//waiting for thread to load data
    for(i = 0; i < args.threads; ++i){
        pthread_join(threads[i], 0);
    }
	
    *out = concat_datas(buffers, args.threads);
    out->shallow = 0;
    for(i = 0; i < args.threads; ++i){
        buffers[i].shallow = 1;
        free_data(buffers[i]);
    }
    free(buffers);
    free(threads);
    return 0;
}

為什麼上面說load_threads是一個數據載入執行緒管理者的角色就是因為在load_data_in_thread中會建立真正負責載入資料的執行緒,load_threads函式內部儲存這些資料載入子執行緒的執行緒id,通過pthread_join函式等待這些子執行緒完成資料載入。建立多少個子執行緒由傳入的args.threads成員決定,因為一次載入的影象的數量是args.n,現在有args.threads個執行緒去完成這項工作,所以分配到單個執行緒的話只需要去載入args.n/args.threads張影象。

pthread_t load_data_in_thread(load_args args)
{
    pthread_t thread;
    struct load_args *ptr = calloc(1, sizeof(struct load_args));
    *ptr = args;
    if(pthread_create(&thread, 0, load_thread, ptr)) error("Thread creation failed");
    return thread;
}

void *load_thread(void *ptr)
{
    //printf("Loading data: %d\n", rand());
    load_args a = *(struct load_args*)ptr;
    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 == DETECTION_DATA){
    	//detection data
        *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);
    return 0;
}

load_data_in_thread函式中會建立真正的負責載入資料的子執行緒load_thread並返回執行緒描述符,load_thread會根據要載入的資料型別呼叫相應的函式,我這裡只考慮DETECTION_DATA也就是檢測資料的情形,因此會進一步呼叫load_data_detection函式。

/*
m,total of images
n,part of images for this thread
boxes,max number of boxes per picture
*/
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)
{
    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;

	//n * boxes * 5(x,y,h,w,score)
    d.y = make_matrix(n, 5*boxes); 
    
    for(i = 0; i < n; ++i){
        image orig = load_image_color(random_paths[i], 0, 0); //load an origin image
        image sized = make_image(w, h, orig.c); //make empty image,size is (w,h,c)
        fill_image(sized, .5);

        float dw = jitter * orig.w;
        float dh = jitter * orig.h;

		//width to height ratio after jitter
        float new_ar = (orig.w + rand_uniform(-dw, dw)) / (orig.h + rand_uniform(-dh, dh));
        float scale = rand_uniform(.25, 2);

        float nw, nh;

		//change w,h but keep the ratio,why?
        if(new_ar < 1){
            nh = scale * h;
            nw = nh * new_ar;
        } else {
            nw = scale * w;
            nh = nw / new_ar;
        }

        float dx = rand_uniform(0, w - nw);
        float dy = rand_uniform(0, h - nh);

        place_image(orig, nw, nh, dx, dy, sized);

        random_distort_image(sized, hue, saturation, exposure);

		//rand flip
        int flip = rand()%2;
        if(flip) flip_image(sized);

		//X is ready
        d.X.vals[i] = sized.data;

		//y is ready
        fill_truth_detection(random_paths[i], boxes, d.y.vals[i], classes, flip, -dx/w, -dy/h, nw/w, nh/h);

        free_image(orig);
    }
    free(random_paths);
    return d;
}

這裡的核心是這個n次的for迴圈,每次迴圈都載入一張影象。由load_image_color將影象檔案路徑中的影象載入到image結構中,因為我們要求的尺寸是(w,h)的,所以緊接著通過make_image生成一張(w,h,orig.c)的空白影象。將原始影象進行一定的變換後填充到生成的空白影象中,這裡面對原始影象一系列變換的意義,我至今不甚瞭解。整個函式中還有一個比較需要注意的點就是fill_truth_detection這個函式。

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);
	//上面一大堆就是根據資料集目錄結構將圖片路徑變換成labels文件的路徑
	
    int count = 0;
    box_label *boxes = read_boxes(labelpath, &count);

	//disrupt the box order
	randomize_boxes(boxes, count);

	//(x,y,w,h)有所調整,所以truth box也要有所糾正
    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;

	//what?
    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);
}

相關推薦

darknet原始碼解讀-load_data

    這裡的資料載入部分的程式碼由detector.c檔案中train_detector函式中load_data處開始解讀。 void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *

darknet原始碼解讀-yolov2損失計算

參考文章:     yolov2損失計算的原始碼集中在region_layer.c檔案forward_region_layer函式中,為了兼顧座標、分類、目標置信度以及訓練效率,損失函式由多個部分組成,且不同部分都被賦予了各自的損失權重,整體計算公式如下。  

darknet原始碼解讀-letterbox_image

letterbox_image對影象調整成輸入尺寸(w,h) //將原圖進行一定比例的縮放,返回的圖片尺寸為(w,h) image letterbox_image(image im, int w, int h) { int new_w = im.w; int

YOLO原始碼Darknet原始碼解讀(im2col.c)

#include "im2col.h" #include <stdio.h> // 獲取影象畫素值 float im2col_get_pixel(float *im, int height, int width, int channels,      

YOLO原始碼Darknet原始碼解讀(convolutional_layer.c)

#include "convolutional_layer.h" #include "utils.h" #include "batchnorm_layer.h" #include "im2col.h" #include "col2im.h" #include "blas.h" #include "g

YOLO原始碼Darknet原始碼解讀(layer.c)

#include "layer.h" #include "cuda.h" #include <stdlib.h> void free_layer(layer l) { if(l.type == DROPOUT){ if(l.rand) fr

YOLO原始碼Darknet原始碼解讀(network.c)

network.c #include <stdio.h> #include <time.h> #include <assert.h> #include "network.h" #include "image.h" #include "data.h" #inclu

YOLO原始碼Darknet原始碼解讀(utils.c)

utils.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <assert.h> #include <u

YOLO原始碼Darknet原始碼解讀(yolo.c)

// 將檢測的boxes結果寫入檔案 void print_yolo_detections(FILE **fps, char *id, int total, int classes, int w, int h, detection *dets) { int i, j; for(i = 0; i

yolo v2 損失函式原始碼解讀

前提說明:     1, 關於 yolo 和 yolo v2 的詳細解釋請移步至如下兩個連結,或者直接看論文(我自己有想寫 yolo 的教程,但思前想後下面兩個連結中的文章質量實在是太好了_(:з」∠)_)         yo

【React原始碼解讀】- 元件的實現

前言 react使用也有一段時間了,大家對這個框架褒獎有加,但是它究竟好在哪裡呢? 讓我們結合它的原始碼,探究一二!(當前原始碼為react16,讀者要對react有一定的瞭解) 回到最初 根據react官網上的例子,快速構建react專案 npx create-react-app

【1】pytorch torchvision原始碼解讀之Alexnet

最近開始學習一個新的深度學習框架PyTorch。 框架中有一個非常重要且好用的包:torchvision,顧名思義這個包主要是關於計算機視覺cv的。這個包主要由3個子包組成,分別是:torchvision.datasets、torchvision.models、torchvision.trans

Set介面_HashSet常用方法_JDK原始碼解讀

Set 介面繼承自 Collection ,Set 沒有新增方法,方法和 Collection 保持一致, Set 容器的特點:無序,不可重複,無序指Set 中的元素沒有索引,我們只能遍歷查詢,不重複指不允許加入重複的元素,更確切的說,新元素如果和Set 中某個元素通過 equals() 方

vux之x-input使用以及原始碼解讀

前言 近期專案中使用的vux中的input,以及使用自定義校驗規則和動態匹配錯誤提示,有時間記錄下自己的使用經歷和原始碼分析。希望大家多多指正,留言區發表自己寶貴的建議。 詳解 列舉官方文件中常用的幾個屬性的使用方法,程式碼如下 <group ref="group">

react-redux connect原始碼解讀

今天看了下react-redux的原始碼,主要來看下connect的方法 首先找到connect的入口檔案。在src/index.js下找到。對應connect資料夾下的connect.js檔案。 大致說下原始碼connect流程 connect.js對外暴露是通過ex

java原始碼解讀之HashMap

1:首先下載openjdk(http://pan.baidu.com/s/1dFMZXg1),把原始碼匯入eclipse,以便看到jdk原始碼            Windows-Prefe

以太坊原始碼解讀(5)BlockChain類的解析及NewBlockChain()分析

一、blockchain的資料結構 type BlockChain struct { chainConfig *params.ChainConfig // 初始化配置 cacheConfig *CacheConfig // 快取配置 db ethdb.Databas

以太坊原始碼解讀(4)Block類及其儲存

一、Block類 type Block struct { /******header*******/ header *Header /******header*******/ /******body*********/ uncle

Hystrix之@EnableCircuitBreaker原始碼解讀

Hystrix是一個供分散式系統使用,提供延遲和容錯功能,保證複雜的分佈系統在面臨不可避免的失敗時,仍能有其彈性。 比如系統中有很多服務,當某些服務不穩定的時候,使用這些服務的使用者執行緒將會阻塞,如果沒有隔離機制,系統隨時就有可能會掛掉,從而帶來很大的風險。 SpringCloud使用Hy

String的valueOf方法原始碼解讀

valueOf 中的祕密 String中的valueOf方法大致可以分為三種: String.valueOf(Object)、String.valueOf(char[])、String.valueOf(基本資料型別) 案例: Integer arg = null; St