본문 바로가기

IT/Deeplearning

[CNN classifier / YOLO DARKNET] classifier code review :: Intro- [1]

기존에 Darknet의 Object Detection 코드 리뷰를 진행하던 프로젝트의 방향성이 살짝 변경되어 Classifier 부분의 코드 리뷰를 먼저 진행하게되었습니다.


먼저 predict_classifier 부분을 진행하도록 하겠습니다.


predict_classifier 부분은 CNN을 이용해서 Classification을 하는 함수입니다.




이번 Intro에서는 간략하게 전체 내용을 훑어보는 정도로 진행하도록 하겠습니다.


만약에 predict_classifier를 통해서 YOLO9000 : Better, Faster, Stronger의 Darknet19 모델을 사용한다면,


네트워크 구성은 다음과 같게됩니다.






predict_classifier의 전체 코드는 다음과 같습니다.


1. predict_classifier :: darknet/examples/classifier.c

predict_classifier의 코드는 아래와 같으며, 대략적인 흐름은 다음과 같습니다.


1. *.cfg 파일을 읽어 network구성에 맞게 메모리 할당 및 weights파일 로드

2. network의 batch size를 조정

3. *.data 파일을 읽어들임

4. *.names파일의 path를 파싱

5. top 옵션이 없을 경우, default로 top 옵션을 1로 설정합니다.

6. *.names의 label들을 읽어들임

7. image의 file path를 input에 복사 혹은 사용자에게 직접 입력하도록 유도

8. input path의 image를 읽어 들임

9. image를 network input에 맞춰서 resize

10. 연산 시간 측정 시작

11. prediction

12. top옵션에 맞춰서, 연산 결과 출력 및 연산 시간 출력

13. 메모리 해제


void predict_classifier(char *datacfg, char *cfgfile, char *weightfile, char *filename, int top)
{
    network *net = load_network(cfgfile, weightfile, 0);
    set_batch_network(net, 1);
    srand(2222222);

    list *options = read_data_cfg(datacfg);

    char *name_list = option_find_str(options, "names", 0);
    if(!name_list) name_list = option_find_str(options, "labels", "data/labels.list");
    if(top == 0) top = option_find_int(options, "top", 1);

    int i = 0;
    char **names = get_labels(name_list);
    clock_t time;
    int *indexes = calloc(top, sizeof(int));
    char buff[256];
    char *input = buff;
    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 r = letterbox_image(im, net->w, net->h);
        //resize_network(net, r.w, r.h);
        //printf("%d %d\n", r.w, r.h);

        float *X = r.data;
        time=clock();
        float *predictions = network_predict(net, X);
        if(net->hierarchy) hierarchy_predictions(predictions, net->outputs, net->hierarchy, 1, 1);
        top_k(predictions, net->outputs, top, indexes);
        fprintf(stderr, "%s: Predicted in %f seconds.\n", input, sec(clock()-time));
        for(i = 0; i < top; ++i){
            int index = indexes[i];
            //if(net->hierarchy) printf("%d, %s: %f, parent: %s \n",index, names[index], predictions[index], (net->hierarchy->parent[index] >= 0) ? names[net->hierarchy->parent[index]] : "Root");
            //else printf("%s: %f\n",names[index], predictions[index]);
            printf("%5.2f%%: %s\n", predictions[index]*100, names[index]);
        }
        if(r.data != im.data) free_image(r);
        free_image(im);
        if (filename) break;
    }
}



2. load_network :: darknet/src/network.c


predict_classifier 부분에서 첫줄에 load_network부분으로 시작하게 됩니다.

load_network는 src/network.c 파일에 있으며, 코드 구성은 다음과 같습니다.

1. *.cfg 파일을 파싱합니다.
2. *.weights 파일을 로드합니다.
3. clear 옵션이 들어오면 network 구조체의 멤버 seen을 0으로 초기화합니다.
4. network 구조체 net을 반환합니다.


network *load_network(char *cfg, char *weights, int clear)
{
    network *net = parse_network_cfg(cfg);
    if(weights && weights[0] != 0){
        load_weights(net, weights);
    }
    if(clear) (*net->seen) = 0;
    return net;
}


3. set_batch_network :: src/network.c

set_batch_network함수는 darknet의 network.c 파일에 있습니다.


1. network 구조체의 멤버변수 batch를 b로 변경합니다.

2. network 구조체의 모든 레이어멤버의 batch 값을 b로 변경합니다.

3. (추후보강)위의 과정에 맞춰서 CUDNN일 경우에 CUDNN 코드를 변경합니다.


void set_batch_network(network *net, int b)
{
    net->batch = b;
    int i;
    for(i = 0; i < net->n; ++i){
        net->layers[i].batch = b;
#ifdef CUDNN
        if(net->layers[i].type == CONVOLUTIONAL){
            cudnn_convolutional_setup(net->layers + i);
        }
        if(net->layers[i].type == DECONVOLUTIONAL){
            layer *l = net->layers + i;
            cudnnSetTensor4dDescriptor(l->dstTensorDesc, CUDNN_TENSOR_NCHW, CUDNN_DATA_FLOAT, 1, l->out_c, l->out_h, l->out_w);
            cudnnSetTensor4dDescriptor(l->normTensorDesc, CUDNN_TENSOR_NCHW, CUDNN_DATA_FLOAT, 1, l->out_c, 1, 1); 
        }
#endif
    }
}



4. read_data_cfg


read_data_cfg함순는 src/option_list.c에 있습니다.


코드 구성은 다음과 같습니다.


1. 파일을 엽니다.

2. list 구조체를 만듭니다.

3. 해당 옵션을 {key : value} pair로 리스트에 순차적으로 삽입합니다.

4. 해당 options 리스트 구조체를 반환합니다.



list *read_data_cfg(char *filename)
{
    FILE *file = fopen(filename, "r");
    if(file == 0) file_error(filename);
    char *line;
    int nu = 0;
    list *options = make_list();
    while((line=fgetl(file)) != 0){
        ++ nu;
        strip(line);
        switch(line[0]){
            case '\0':
            case '#':
            case ';':
                free(line);
                break;
            default:
                if(!read_option(line, options)){
                    fprintf(stderr, "Config file error line %d, could parse: %s\n", nu, line);
                    free(line);
                }
                break;
        }
    }
    fclose(file);
    return options;
}


5. letterbox_image :: src/image.c


letterbox_image 함수는 src/image.c에 위치하고있습니다.


함수 구성은 아래와 같습니다.


하는 역활은 image ratio에 따라서 이미지를 정방으로 resize해주고, 이를 다시


네트워크 입력에 맞춰 재 resize하는 역활을 합니다.


여기서 letterbox의 의미는 아래 그림과 같이 영상 위아래에 검은색으로 채워져있는 것 같이


이미지에 여분의 픽셀을 추가하는 것을 의미합니다.




여기서 letterbox를 하는 이유는 어떤 이미지의 ratio가 1:1이 아닐 경우, 이를 그대로 정방형태 혹은 network input에 맞춰 resize 경우


이미지의 ratio가 망가질 수 있으니, 이를 letterboxing하는 것을 통해서 image를 resize해도 영상의 ratio를 망가뜨리지 않게 resize를 하기 위해서


하는 전처리라고 이해하시면 될 것 같습니다.


image letterbox_image(image im, int w, int h)
{
    int new_w = im.w;
    int new_h = im.h;
    if (((float)w/im.w) < ((float)h/im.h)) {
        new_w = w;
        new_h = (im.h * w)/im.w;
    } else {
        new_h = h;
        new_w = (im.w * h)/im.h;
    }
    image resized = resize_image(im, new_w, new_h);
    image boxed = make_image(w, h, im.c);
    fill_image(boxed, .5);
    //int i;
    //for(i = 0; i < boxed.w*boxed.h*boxed.c; ++i) boxed.data[i] = 0;
    embed_image(resized, boxed, (w-new_w)/2, (h-new_h)/2);
    free_image(resized);
    return boxed;
}