이번 포스팅에서는 대표적인 Deeplearning CNN C 프레임워크인 YOLO Darknet에서 Object Detection에 대한 코드 리뷰를
진행하겠습니다.
Darknet은 Main함수에서 시작해서 여러가지 분기로 해서 여러 함수들로 진입하게됩니다.
이번 포스팅에서는 Main함수에서 제가 분석할 detector 함수로 진입하고, detector 함수 중 단일 이미지 test하는 함수인
test_detector 함수에서 read_data_cfg 함수에 대해서 리뷰를 진행하겠습니다.
1. Overview
./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
#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; }
'IT > Deeplearning' 카테고리의 다른 글
[YOLO / Object Detection / Keras] Code Review - [1] (2) | 2017.12.07 |
---|---|
[Object Detection / Deeplearning ] YOLO Darknet v2 - [3] (2) | 2017.12.07 |
[Object Detection / Deeplearning ] YOLO Darknet v2 - [2] (3) | 2017.11.20 |
[Object Detection / Deeplearning ] YOLO Darknet v2 - [1] (0) | 2017.11.20 |
[Code Review/ self-driving lab] Udacity Self-driving Car - (3) (0) | 2017.10.26 |