본문 바로가기

IT/Deeplearning

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

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

저번 포스팅에서는 기본적으로 detector 메소드에 들어오는 과정과 

Darknet에서 사용하고 있는 list구조체에 대해서 자세하게 살펴봤습니다.

오늘은 read_data_cfg 메소드에 대해서 전반적으로 살펴봄과 동시에, 해동 로직에 있는 추가 메소드에 대해서 알아보겠습니다.




1. read_data_cfg Method :: src/option_list.c

read_data_cfg 메소드는 src폴더에 option_list.c에 정의되어있습니다.

read_data_cfg 메소드의 구성은 다음과 같습니다.


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;
}


1. .cfg파일을 읽습니다.

2. file_error 메소드를 통해서 에러를 확인합니다.

3. 한줄씩 읽어들일 char 포인터 line을 생성합니다.

4. int형 변수 nu를 선업합니다.

5. options라는 리스트 구조체 포인터를 만들고 초기화합니다.

6. 해당 파트부터 파일을 모두 읽어들일 때까지 순회를 돕니다.

6-1. fgetl 메소드로 file을 읽고 해당 리턴값을 line에 넣어주고 validation 확인을 합니다.

6-2. nu의 값을 하나 올립니다.

6-3. line에 공백을 없애주는 것처럼 보이는 strip 메소드를 적용합니다.

6-4. 주석에 대한 내용이나, 파일의 끝을 확인하기 위해서 해당 line의 첫글자에 대한 체크를 합니다.

6-5. 첫글자가 '\0', '#', ';'이면 해당 line을 동적 해제해주고 무시하고 넘어갑니다.

6-6. (10)번의 케이스가 아니라면 기본적으로 read_option 메소드에 list 구조체 포인터 options와 해당 line을 파라미터로 넣어주고 options값을 읽어들입니다.

6-7. (11)번의 케이스에서 에러가 난다면 에러구분을 표시해주고 line을 동적헤제하고 넘어갑니다.

7.  파일을 모두 다 읽었다면 file을 닫아줍니다.

8. 옵션값을 모두 읽은 list구조체 options을 반환합니다.


2. file_error :: src/utils.c

file_error 메소드는 아래와 같이 구성되어있습니다.

하는 역활은 단순하게 인자로 들어온 파일을 열 수 없다고 에러 메세지를 표시하는 것입니다.


void file_error(char *s)
{
    fprintf(stderr, "Couldn't open file: %s\n", s);
    exit(0);
}


3. fgetl :: src/utils.c

fgetl 메소드는 파일을 읽고 읽은 내용을 돌려주는 함수 입니다.

fgetl 메소드는 아래와 같이 정의되어있습니다.


char *fgetl(FILE *fp)
{
    if(feof(fp)) return 0;
    size_t size = 512;
    char *line = malloc(size*sizeof(char));
    if(!fgets(line, size, fp)){
        free(line);
        return 0;
    }

    size_t curr = strlen(line);

    while((line[curr-1] != '\n') && !feof(fp)){
        if(curr == size-1){
            size *= 2;
            line = realloc(line, size*sizeof(char));
            if(!line) {
                printf("%ld\n", size);
                malloc_error();
            }
        }
        size_t readsize = size-curr;
        if(readsize > INT_MAX) readsize = INT_MAX-1;
        fgets(&line[curr], readsize, fp);
        curr = strlen(line);
    }
    if(line[curr-1] == '\n') line[curr-1] = '\0';

    return line;
}


1. feof함수로 해당 파일의 끝 표시자(End-of-File indicator)를 검사하고, 해당 파일의 끝이라면 0을 반환하고 메소드를 종료합니다.

2. size를 기본으로 512로 잡고 char 포인터 line에 해당 사이즈만큼 동적 할당을 해줍니다.

3. fgets함수를 통해서 해당 파일 포인터에서 기본 사이즈 512까지 읽어서 line으로 가져옵니다.

4. 만약 (3)번에서 에러가 났다면, line을 동적해제하고 0을 반환하여 메소드를 종료합니다.

5. line의 길이를 size_t 타입의 변수 curr에 저장합니다.

6. 만약에 끝 문자가 '\n'이 아니거나, 파일 끝 지시자가 아니라면 순회를 돈다.

6-1. 만약에 기본 사이즈 512에서 1을 뺀 511사이즈가 현재 읽어들인 line의 길이와 같은지 확인한다.

6-2. 만약에 같다면 size를 2배로 만든다.

6-3. 변경한 size만큼 line의 길이를 동적 재할당한다.

6-4. 만약에 line의 주소값이 잘못되었다면, size에 대한 값을 출력하고, malloc_error 메소드를 호출 한다.

6-5. 현재 읽어들일 수 있는 여유 공간을 확인한다.

6-6. 만약에 읽어들일 수 있는 여유 공간이 INT형의 최대치를 초과하였다면 이를 INT의 최대사이즈에서 한칸 뺀 크기로 재 조정한다.

6-7. 여태까지 읽어들인 부분부터 파일스트림에서 추가적인 사이즈만큼 fgets함수를 이용해 더 읽어들인다.

7. 만약 line의 curr-1의 문자가 '\n'이라면 해당 문자를 '\0'으로 변경해준다.

8. line을 반환하고 메소드를 종료한다.


4. malloc_error :: src/utils.c

malloc_error 메소드는 동적 할당이 실패했다는 에러를 출력하고 프로그램을 종료시키는 함수입니다.

malloc_error 메소드의 구성은 아래와 같습니다.


void malloc_error()
{
    fprintf(stderr, "Malloc error\n");
    exit(-1);
}


5. strip :: src/utils.c

strip 메소드는 받은 문자열 포인터에서 공백문자나 개행문자, 탭문자를 없애주는 메소드입니다.

메소드의 구조는 아래와 같습니다.


void strip(char *s)
{
    size_t i;
    size_t len = strlen(s);
    size_t offset = 0;
    for(i = 0; i < len; ++i){
        char c = s[i];
        if(c==' '||c=='\t'||c=='\n') ++offset;
        else s[i-offset] = c;
    }
    s[len-offset] = '\0';
}


1. size_t 타입의 변수 i를 선언합니다.

2. size_t 타입의 변수 len을 선언하고, 문자열 포인터 s의 문자 길이를 확인해서 len에 대입합니다.

3. size_t 타입의 변수 offset을 선언하고, 0으로 초기화합니다.

4. 문자열의 길이만큼 순환을 돌면서 해당 문자의 성분 중에 공백문자, 탭문자, 개행문자를 찾아서 offset의 값을 증가시킵니다.

5. 만약 순회시에 공백문자, 탭문자, 개행문자를 발견하지 못했다면 현재 문자열에서 offset만큼 이전의 위치에 해당 문자 c를 대입해줍니다.

6. 처음에 측정했던 처음 길이와 offset만큼 빼준 위치에 '\0'문자를 넣습니다.


6. read_option :: src/option_list.c

read_option은 cfg의 "batch=64"같은 형태의 option값을 분리하여 읽어들이고 구분하기 위한 함수입니다.

read_option 메소드의 구성은 아래와 같습니다.


int read_option(char *s, list *options)
{
    size_t i;
    size_t len = strlen(s);
    char *val = 0;
    for(i = 0; i < len; ++i){
        if(s[i] == '='){
            s[i] = '\0';
            val = s+i+1;
            break;
        }
    }
    if(i == len-1) return 0;
    char *key = s;
    option_insert(options, key, val);
    return 1;
}


1. size_t 타입의 변수 i를 선언합니다.

2. size_t 타입의 변수 len을 선언하고 파라미터 문자열 포인터 s의 문자열 길이를 확인해서 len에 대입합니다.

3. char 포인터 타입 val을 선언하고 0으로 대입합니다.

4. 문자열 길이만큼 순회를 돌면서 '='문자열을 찾으면 이를 '\0'으로 대입합니다.

5. 문자열 포인터 val의 값을 s+i+1로 대입합니다.

6. 만약에 변수 i가 문자열의 길이 len-1과 같으면 0을 반환하고 함수를 종료합니다.

7. char 포인터 타입의 변수 key를 선언하고 문자열 s를 대입합니다. (이미 중간에 '\0'값이 들어가있기 때문에 '\0'값이 들어가있는 부분까지만 대입이 됩니다.)

8. option_insert 함수에 list 구조체 포인터 options와 read_option 메소드에서 만든 key값과 val값을 파라미터로 전달해줍니다.

9. 1을 리턴하고 메소드를 종료합니다.


7. option_insert :: src/option_list.c


option_insert 메소드는 key값과 val값을 받아서 리스트 구조체에 넣어주는 메소드입니다.
구성은 다음과 같습니다.


void option_insert(list *l, char *key, char *val)
{
    kvp *p = malloc(sizeof(kvp));
    p->key = key;
    p->val = val;
    p->used = 0;
    list_insert(l, p);
}


1. kvp 구조체 p를 선언하고, 동적할당합니다

2. kvp구조체의 멤버 key에 파라미터로 받은 key값을, kvp 구조체의 멤버 val에 val값을, kev구조체의 멤버 used에는 0을 대입합니다.

3. list_insert 메소드를 통해서 kvp 구조체를 리스트에 넣어줍니다.


8. kvp Structure :: src/option_list.h

kvp 구조체는 key값과 val값 그리고 해당 값들이 사용되었는지 사용되지 않았는지 확인하는 파라미터 used로 구성되어있는 멤버입니다.

구성은 아래와 같습니다.


typedef struct{
    char *key;
    char *val;
    int used;
} kvp;


9. Summary


이제 여태까지 정리한 read_data_cfg에 대한 전체 뷰를 아래의 그림에서 확인할 수 있습니다.

datacfg파일인 *.data 확장자를 갖는 txt 파일을 읽어서 list 구조체인 options에 node와 kvp 구조체를 이용해서

파싱하는 함수가 바로 read_data_cfg 함수인 것입니다.