본문 바로가기

IT/Deeplearning

[Object Detection / YOLO DARKNET] object detection code review :: read_data_cfg -[1]

이번 포스팅에서는 대표적인 Deeplearning CNN C 프레임워크인 YOLO Darknet에서 Object Detection에 대한 코드 리뷰를 

진행하겠습니다.





Darknet은 Main함수에서 시작해서 여러가지 분기로 해서 여러 함수들로 진입하게됩니다.

이번 포스팅에서는 Main함수에서 제가 분석할 detector 함수로 진입하고, detector 함수 중 단일 이미지 test하는 함수인

test_detector 함수에서 read_data_cfg 함수에 대해서 리뷰를 진행하겠습니다.


1. Overview


먼저 코드 리뷰에 대상이 되는 분기 지점은 YOLO Darknet의 detector 모듈에 있는 test 함수임입니다.

detector 모듈은 YOLO 자체의 Object Detection 모델이며, test 함수는 이미 모두 학습된 weights파일을 가지고 

forward해서 결과값을 뽑는 함수입니다. 

먼저 test함수를 코드 리뷰한 후에, train 모듈에 대해서 리뷰를 하도록 하겠습니다.

해당 리뷰에서 darknet을 실행시킨 인자는 다음과 같다고 가정합니다.
./darknet detector test <*.data> <*.cfg> <*.wegiths> 


2. Main :: examples/darknet.c


해당 Main함수에서 제일 중요한 개념은 다음과 같습니다. 

파라미터로 들어온 첫번째 값, 그러니까 argv[1] == "detector"라면 받은 인자들을(argc, argv) 모두

run_detector로 다시 전달한다는 것입니다.

Main함수에서는 이를 잘 기억하고 계시면 되겠습니다.


Main함수는 다음과 같이 구성되어있습니다.


1. 입력으로 들어오는 파라미터의 갯수가 2개보다 작을 때, 에러메세지를 출력하고 종료합니다.

2. GPU 옵션이 들어왔는지 확인하고, 해당 gpu index를 할당해줍니다.

3. 만약에 -nogpu옵션이 들어오면 gpu_index를 -1로 대입합니다.

4. make시 GPU옵션이 정의되있지 않았다면, gpu_index값을 -1로 대입합니다.

5. make시 GPU옵션이 정의되었고, gpu_index의 값이 1보다 크면, cuda_set_device함수를 통해서 cuda 설정을 합니다.

6. 들어온 함수 파라미터에 따라서 해당 함수로 분기하면서 받았던 파라미터를 같이 넘겨줍니다.


여기서도 약간의 설명이 필요하지만, 이번 리뷰 포스팅에서는 전반적인 흐름을 따라서 진행하고자 하므로 몇가지는 생략한 상태로 

진행하도록 하겠습니다.

######################
######################
######################
int main(int argc, char **argv)
{
    if(argc < 2){
        fprintf(stderr, "usage: %s \n", argv[0]);
        return 0;
    }
    gpu_index = find_int_arg(argc, argv, "-i", 0);
    if(find_arg(argc, argv, "-nogpu")) {
        gpu_index = -1;
    }

#ifndef GPU
    gpu_index = -1;
#else
    if(gpu_index >= 0){
        cuda_set_device(gpu_index);
    }
#endif

    if (0 == strcmp(argv[1], "average")){
        average(argc, argv);
    } else if (0 == strcmp(argv[1], "yolo")){
        run_yolo(argc, argv);
    } else if (0 == strcmp(argv[1], "super")){
        run_super(argc, argv);
    } else if (0 == strcmp(argv[1], "lsd")){
        run_lsd(argc, argv);
    } else if (0 == strcmp(argv[1], "detector")){
        run_detector(argc, argv);
    } else if (0 == strcmp(argv[1], "detect")){
        float thresh = find_float_arg(argc, argv, "-thresh", .24);
        char *filename = (argc > 4) ? argv[4]: 0;
        char *outfile = find_char_arg(argc, argv, "-out", 0);
        int fullscreen = find_arg(argc, argv, "-fullscreen");
        test_detector("cfg/coco.data", argv[2], argv[3], filename, thresh, .5, outfile, fullscreen);
    } else if (0 == strcmp(argv[1], "cifar")){
        run_cifar(argc, argv);
    } else if (0 == strcmp(argv[1], "go")){
        run_go(argc, argv);
    } else if (0 == strcmp(argv[1], "rnn")){
        run_char_rnn(argc, argv);
    } else if (0 == strcmp(argv[1], "coco")){
        run_coco(argc, argv);
    } else if (0 == strcmp(argv[1], "classify")){
        predict_classifier("cfg/imagenet1k.data", argv[2], argv[3], argv[4], 5);
    } else if (0 == strcmp(argv[1], "classifier")){
        run_classifier(argc, argv);
    } else if (0 == strcmp(argv[1], "attention")){
        run_attention(argc, argv);
    } else if (0 == strcmp(argv[1], "regressor")){
        run_regressor(argc, argv);
    } else if (0 == strcmp(argv[1], "segmenter")){
        run_segmenter(argc, argv);
    } else if (0 == strcmp(argv[1], "art")){
        run_art(argc, argv);
    } else if (0 == strcmp(argv[1], "tag")){
        run_tag(argc, argv);
    } else if (0 == strcmp(argv[1], "3d")){
        composite_3d(argv[2], argv[3], argv[4], (argc > 5) ? atof(argv[5]) : 0);
    } else if (0 == strcmp(argv[1], "test")){
        test_resize(argv[2]);
    } else if (0 == strcmp(argv[1], "captcha")){
        run_captcha(argc, argv);
    } else if (0 == strcmp(argv[1], "nightmare")){
        run_nightmare(argc, argv);
    } else if (0 == strcmp(argv[1], "rgbgr")){
        rgbgr_net(argv[2], argv[3], argv[4]);
    } else if (0 == strcmp(argv[1], "reset")){
        reset_normalize_net(argv[2], argv[3], argv[4]);
    } else if (0 == strcmp(argv[1], "denormalize")){
        denormalize_net(argv[2], argv[3], argv[4]);
    } else if (0 == strcmp(argv[1], "statistics")){
        statistics_net(argv[2], argv[3]);
    } else if (0 == strcmp(argv[1], "normalize")){
        normalize_net(argv[2], argv[3], argv[4]);
    } else if (0 == strcmp(argv[1], "rescale")){
        rescale_net(argv[2], argv[3], argv[4]);
    } else if (0 == strcmp(argv[1], "ops")){
        operations(argv[2]);
    } else if (0 == strcmp(argv[1], "speed")){
        speed(argv[2], (argc > 3 && argv[3]) ? atoi(argv[3]) : 0);
    } else if (0 == strcmp(argv[1], "oneoff")){
        oneoff(argv[2], argv[3], argv[4]);
    } else if (0 == strcmp(argv[1], "oneoff2")){
        oneoff2(argv[2], argv[3], argv[4], atoi(argv[5]));
    } else if (0 == strcmp(argv[1], "partial")){
        partial(argv[2], argv[3], argv[4], atoi(argv[5]));
    } else if (0 == strcmp(argv[1], "average")){
        average(argc, argv);
    } else if (0 == strcmp(argv[1], "visualize")){
        visualize(argv[2], (argc > 3) ? argv[3] : 0);
    } else if (0 == strcmp(argv[1], "mkimg")){
        mkimg(argv[2], argv[3], atoi(argv[4]), atoi(argv[5]), atoi(argv[6]), argv[7]);
    } else if (0 == strcmp(argv[1], "imtest")){
        test_resize(argv[2]);
    } else {
        fprintf(stderr, "Not an option: %s\n", argv[1]);
    }
    return 0;
}
######################
######################
######################



3. run_detector :: examples/detector.c


run_detector 함수에서 제일 중요한 개념은 위에서 설명했던 Main과 같습니다.

argv[2]=="test"임이 확인이 되면, 이미 파싱해놓았던 datacfg, cfg, weights, filename의 값을 test_detector에 

파라미터로 전달한다는 것입니다.

run_detector 함수에서는 이를 잘 기억하고 계시면 되겠습니다.


darknet.c에서 run_detector로 들어오면, 몇가지 파라미터들을 파싱해주고, 메인 함수에서 원하는 부분 함수로 다시 분기하게됩니다.


1. 여러 옵션들에 대한 파싱을 진행합니다.

2. 파싱이 되었다면, 다시한번 세부 함수로 분기합니다.


void run_detector(int argc, char **argv)
{
    char *prefix = find_char_arg(argc, argv, "-prefix", 0);
    float thresh = find_float_arg(argc, argv, "-thresh", .24);
    float hier_thresh = find_float_arg(argc, argv, "-hier", .5);
    int cam_index = find_int_arg(argc, argv, "-c", 0);
    int frame_skip = find_int_arg(argc, argv, "-s", 0);
    int avg = find_int_arg(argc, argv, "-avg", 3);
    if(argc < 4){
        fprintf(stderr, "usage: %s %s [train/test/valid] [cfg] [weights (optional)]\n", argv[0], argv[1]);
        return;
    }
    char *gpu_list = find_char_arg(argc, argv, "-gpus", 0);
    char *outfile = find_char_arg(argc, argv, "-out", 0);
    int *gpus = 0;
    int gpu = 0;
    int ngpus = 0;
    if(gpu_list){
        printf("%s\n", gpu_list);
        int len = strlen(gpu_list);
        ngpus = 1;
        int i;
        for(i = 0; i < len; ++i){
            if (gpu_list[i] == ',') ++ngpus;
        }
        gpus = calloc(ngpus, sizeof(int));
        for(i = 0; i < ngpus; ++i){
            gpus[i] = atoi(gpu_list);
            gpu_list = strchr(gpu_list, ',')+1;
        }
    } else {
        gpu = gpu_index;
        gpus = &gpu;
        ngpus = 1;
    }

    int clear = find_arg(argc, argv, "-clear");
    int fullscreen = find_arg(argc, argv, "-fullscreen");
    int width = find_int_arg(argc, argv, "-w", 0);
    int height = find_int_arg(argc, argv, "-h", 0);
    int fps = find_int_arg(argc, argv, "-fps", 0);

    char *datacfg = argv[3];
    char *cfg = argv[4];
    char *weights = (argc > 5) ? argv[5] : 0;
    char *filename = (argc > 6) ? argv[6]: 0;
    if(0==strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, outfile, fullscreen);
    else if(0==strcmp(argv[2], "train")) train_detector(datacfg, cfg, weights, gpus, ngpus, clear);
    else if(0==strcmp(argv[2], "valid")) validate_detector(datacfg, cfg, weights, outfile);
    else if(0==strcmp(argv[2], "valid2")) validate_detector_flip(datacfg, cfg, weights, outfile);
    else if(0==strcmp(argv[2], "recall")) validate_detector_recall(cfg, weights);
    else if(0==strcmp(argv[2], "demo")) {
        list *options = read_data_cfg(datacfg);
        int classes = option_find_int(options, "classes", 20);
        char *name_list = option_find_str(options, "names", "data/names.list");
        char **names = get_labels(name_list);
        demo(cfg, weights, thresh, cam_index, filename, names, classes, frame_skip, prefix, avg, hier_thresh, width, height, fps, fullscreen);
    }
}


4. test_detector :: examples/detector.c


test_detector함수는 단일 Image에 대해서 Object Detection & Recognition하는 로직입니다.

코드는 아래와 같습니다.

이번 포스팅에서는 여기서의 첫줄

list* options = read_data_cfg(datacfg)에 대해서 코드 리뷰하도록 하겠습니다.


read_data_cfg(datacfg) 함수에서 메인 컨셉은 datacfg의 값들을 읽어들이겠다는 것입니다.

read_data_cfg에서 읽으려고하는 datacfg의 데이터 포맷은 다음과 같습니다.

아래는 voc.data파일입니다.

classes= 20
train  = /home/pjreddie/data/voc/train.txt
valid  = /home/pjreddie/data/voc/2007_test.txt
names = data/voc.names
backup = backup

voc.data 파일에서 각 옵션들이 의미하는 것은 다음과 같습니다

classes : Object Detection에서 인식할 class 종류의 개수

train : Object Detection에서 학습할 학습 데이터 위치를 가지고 있는 manifest 파일

valid : Object Detection에서 validation 데이터 위치를 가지고 있는 manifest 파일

names : class의 이름들을 가지고있는 파일 이름

backup : weights파일을 저장할 위치


void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename, float thresh, float hier_thresh, char *outfile, int fullscreen)
{
    list *options = read_data_cfg(datacfg);
    char *name_list = option_find_str(options, "names", "data/names.list");
    char **names = get_labels(name_list);

    image **alphabet = load_alphabet();
    network *net = load_network(cfgfile, weightfile, 0);
    set_batch_network(net, 1);
    srand(2222222);
    double time;
    char buff[256];
    char *input = buff;
    int j;
    float nms=.3;
    while(1){
        if(filename){
            strncpy(input, filename, 256);
        } else {
            printf("Enter Image Path: ");
            fflush(stdout);
            input = fgets(input, 256, stdin);
            if(!input) return;
            strtok(input, "\n");
        }
        image im = load_image_color(input,0,0);
        image sized = letterbox_image(im, net->w, net->h);
        //image sized = resize_image(im, net->w, net->h);
        //image sized2 = resize_max(im, net->w);
        //image sized = crop_image(sized2, -((net->w - sized2.w)/2), -((net->h - sized2.h)/2), net->w, net->h);
        //resize_network(net, sized.w, sized.h);
        layer l = net->layers[net->n-1];

        box *boxes = calloc(l.w*l.h*l.n, sizeof(box));
        float **probs = calloc(l.w*l.h*l.n, sizeof(float *));
        for(j = 0; j < l.w*l.h*l.n; ++j) probs[j] = calloc(l.classes + 1, sizeof(float *));
        float **masks = 0;
        if (l.coords > 4){
            masks = calloc(l.w*l.h*l.n, sizeof(float*));
            for(j = 0; j < l.w*l.h*l.n; ++j) masks[j] = calloc(l.coords-4, sizeof(float *));
        }

        float *X = sized.data;
        time=what_time_is_it_now();
        network_predict(net, X);
        printf("%s: Predicted in %f seconds.\n", input, what_time_is_it_now()-time);
        get_region_boxes(l, im.w, im.h, net->w, net->h, thresh, probs, boxes, masks, 0, 0, hier_thresh, 1);
        //if (nms) do_nms_obj(boxes, probs, l.w*l.h*l.n, l.classes, nms);
        if (nms) do_nms_sort(boxes, probs, l.w*l.h*l.n, l.classes, nms);
        draw_detections(im, l.w*l.h*l.n, thresh, boxes, probs, masks, names, alphabet, l.classes);
        if(outfile){
            save_image(im, outfile);
        }
        else{
            save_image(im, "predictions");
#ifdef OPENCV
            cvNamedWindow("predictions", CV_WINDOW_NORMAL); 
            if(fullscreen){
                cvSetWindowProperty("predictions", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
            }
            show_image(im, "predictions");
            cvWaitKey(0);
            cvDestroyAllWindows();
#endif
        }

        free_image(im);
        free_image(sized);
        free(boxes);
        free_ptrs((void **)probs, l.w*l.h*l.n);
        if (filename) break;
    }
}


5. node Structure :: include/darknet.h

Darknet에서는 list구조체의 기본 구성이 되는 node 구조체를 darknet.h에 선언해놓았습니다.

node 구조체는 다음과 같이 정의되어있습니다.


typedef struct node{
    void *val;
    struct node *next;
    struct node *prev;
} node;


1. 구조체의 구성은 값을 담는 void 포인터 val, list 연결을 위한 다음 노드의 포인터 next와 이전 포인터 prev가 있습니다.


6. list Structure :: include/darknet.h


Darknet에서는 list구조체를 darknet.h에 선언해놓았습니다.

구조체는 다음과 같이 구성되어있습니다.


기본적인 linked list의 형태를 띄고있는 것을 확인할 수 있습니다.

#
typedef struct list{
    int size;
    node *front;
    node *back;
} list;
#


7. list :: src/list.c, src/list.h


list에 대한 자세한 내용은 src 폴더에 list.c, list.h에 정의되어있습니다.


7-1. list.h



list.h파일에는 4가지 함수가 선언되어있는데, 이를 통해 list.h를 참조하는 것을 통해서 쓸 수 있는 함수는 4가지 함수로 제한합니다.

일종의 public 메소드와 같다고 볼 수 있습니다.


1. make_list함수는 파라미터는 없으며, list 구조체 포인터를 반환합니다.

   이를 통해, 함수의 역활을 유추해보면, 빈 list구조체를 동적할당으로 만들어주고, 해당 구조체의 포인터를 반환해주는 함수일 것이라고 생각해볼 수 있습니다.


2. list_find함수는 파라미터로 list 구조체의 포인터를 받고, void포인터 val값을 받습니다.

   이를 통해, 함수의 역활을 유추해보면, 특정 val값을 구조체 l에서 찾아내서 돌려주는 함수이지 않을까 생각해볼 수 있습니다.

   아마도 list의 특정 val값을 갖는 index를 return값으로 줄 것 처럼 보입니다.


3. list_insert함수는 파라미터로 list 구조체의 포인터를 받고, void포인터를 받습니다.

   이를 통해, 함수의 역활을 유추해보면, 특정 val값을 list구조체에 삽입해주는 함수이지 않을까 생각해볼 수 있습니다.


4. free_list_contents는 파라미터로 list구조체의 포인터를 받습니다.

   list구조체를 만들 때, 아마 동적할당을 진행할 것으로 생각됩니다. 따라서 list구조체에 대한 메모리를 동적해제해주는 함수이지 않을까 생각 해 볼 수 있습니다.

#ifndef LIST_H
#define LIST_H
#include "darknet.h"

list *make_list();
int list_find(list *l, void *val);

void list_insert(list *, void *);


void free_list_contents(list *l);

#endif


7-2. list.c



1. make_list

1-1. malloc함수로 구조체 list크기만큼 메모리 동적할당을 진행하고 구조체 포인터를 돌려받습니다.
1-2. 구조체 l의 size,front,back을 0으로 초기화하고, 구조체 포인터를 반환합니다.


2. list_pop

* 일반적으로 pop함수는 리스트에서 마지막 노드의 값을 리스트에서 뽑아내는(삭제하고, 해당 값을 return하는)함수로 작동합니다.



* 일반적으로 c언어에서 true, false의 값을 false는 0으로 true는 0이 아닌 모든 값으로 인식합니다.

2-1. 만약에 구조체멤버 back이 0이라면 함수는 종료됩니다. :: list 구조체에서 빼낼 것이 없다는 것을 의미합니다.
2-2. 구조체멤버 back의 포인터값을 node포인터 b에 대입합니다.
2-3. 구조체멤버 val의 포인터값을 void형 포인터 val에 대입합니다.
2-4. 노드 구조체멤버 prev의 포인터값을 list구조체 back에 대입합니다.
2-5. 만약에 list구조체멤버 back이 0이 아니라면 list구조체 멤버 back의 멤버 next에 0값을 대입합니다. 
2-6. 노드구조체 b에 대해서 메모리 동적해제를 합니다.
2-7. list구조체의 멤버 size의 값을 하나 낮춰줍니다.
2-8. 빼냈던 void포인터 val을 return합니다.



3. list_insert

list_insert 함수는 list 구조체의 마지막에 새로운 node를 추가하는 함수입니다.

3-1. malloc함수로 구조체 node크기만큼 메모리 동적할당을 진행하고 구조체 포인터를 돌려받습니다.
3-2. 파라미터로 받은 값 val을 node구조체 멤버 val값에 넣어줍니다.
3-3. node 구조체 멤버 next에 0을 대입하여 초기화합니다.
3-4. 만약에 list 구조체 멤버 back이 false(0)라면 list 구조체 멤버 front에 node구조체의 포인터값인 new를 대입하고, prev에는 0을 대입합니다.
3-5. 만약에 list 구조체 멤버 back이 true(!0)라면 list 구조체 멤버 back의 node 구조체 멤버 next에 node 구조체의 포인터값인 new를 대입하고 node 구조체의 멤버 prev에 기존 list구조체의 멤버 back을 대입합니다.
3-6. list 구조체 멤버 back에 node구조체 포인터값인 new를 대입합니다.
3-7. list구조체의 멤버 size를 늘려줍니다.



4. free_node

4-1. node 구조체 포인터 변수를 생성합니다.
4-2. 파라미터로 받은 노드 구조체 포인터의 멤버 next를 순환하면서 해당 node 구조체의 메모리를 동적 해제한다.



5. free_list

5-1. 인자로 받은 구조체 포인터 l의 시작 지점의 node 구조체 포인터를 free_node함수로 전달해서 모든 node의 메모리를 동적해제한다.
5-2. 남은 list구조체의 메모리를 동적해제한다.



6. list_to_array

6-1. void 이중 포인터를 선언해서 calloc 함수로 list구조체의 size만큼 생성한다.
6-2. int형 변수 count를 선언하고 0으로 초기화한다.
6-3. node 구조체 포인터 n을 선언하고 여기에 list구조체 l의 멤버 front값을 대입한다.
6-4. list구조체를 순환하면서 void형 포인터에 node 구조체의 멤버 val값을 대입한다.
6-5. void형 이중 포인터 a값을 return한다.


#include 
#include 
#include "list.h"

list *make_list()
{
	list *l = malloc(sizeof(list));
	l->size = 0;
	l->front = 0;
	l->back = 0;
	return l;
}

void *list_pop(list *l){
    if(!l->back) return 0;
    node *b = l->back;
    void *val = b->val;
    l->back = b->prev;
    if(l->back) l->back->next = 0;
    free(b);
    --l->size;
    
    return val;
}

void list_insert(list *l, void *val)
{
	node *new = malloc(sizeof(node));
	new->val = val;
	new->next = 0;

	if(!l->back){
		l->front = new;
		new->prev = 0;
	}else{
		l->back->next = new;
		new->prev = l->back;
	}
	l->back = new;
	++l->size;
}

void free_node(node *n)
{
	node *next;
	while(n) {
		next = n->next;
		free(n);
		n = next;
	}
}

void free_list(list *l)
{
	free_node(l->front);
	free(l);
}

void free_list_contents(list *l)
{
	node *n = l->front;
	while(n){
		free(n->val);
		n = n->next;
	}
}

void **list_to_array(list *l)
{
    void **a = calloc(l->size, sizeof(void*));
    int count = 0;
    node *n = l->front;
    while(n){
        a[count++] = n->val;
        n = n->next;
    }
    return a;
}