본문 바로가기

IT/Deeplearning

[Object Detection / YOLO DARKNET] object detection code review :: image structure - [6]

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


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


[Object Detection / YOLO DARKNET] object detection code review :: option_find_str -[3]


[Object Detection / YOLO DARKNET] object detection code review :: get_labels -[4]


[Object Detection / YOLO DARKNET] object detection code review :: load_alphabet -[5]


[Object Detection / YOLO DARKNET] object detection code review :: image structure -[6]



이번 포스팅에서는 Darknet에서 사용하고 있는 Image 구조체에 대해서 포스팅하도록 하겠습니다.


1. Concept Overview




2. image structure :: include/darknet.h

image 구조체는 다음과 같이 선언되어있습니다.


typedef struct {
    int w;
    int h;
    int c;
    float *data;
} image;


1. int타입의 w,h,c는 image의 width, height, channel 을 의미합니다.


2. float 포인터 변수 data는 image의 pixel 데이터의 주소를 의미합니다.


3. load_image :: src/image.c


image load_image(char *filename, int w, int h, int c)
{
#ifdef OPENCV
    image out = load_image_cv(filename, c);
#else
    image out = load_image_stb(filename, c);
#endif

    if((h && w) && (h != out.h || w != out.w)){
        image resized = resize_image(out, w, h);
        free_image(out);
        out = resized;
    }
    return out;
}


1. 파라미터 filename, c를 갖는 load_image_cv 함수 혹은 load_image_stb함수를 호출해서 반환값을 image 구조체 out에 대입합니다.


2. image 구조체의 w,h가 load_image의 파라미터로 받은 w,h와 일치하지 않는다면, 일치하도록 image 구조체의 image 크기를 resize합니다.


3-1. load_image_color :: src/image.c


image load_image_color(char *filename, int w, int h)
{
    return load_image(filename, w, h, 3);
}


1. load_image_color 함수는 load_image 함수에 파라미터 c값을 무조건 3으로 해서 전달해주는 함수입니다.

-> 이렇게 load_image_color라는 함수를 분리해서 만들 필요가 있었는지는 의문입니다.



함수 테스트


아래와 같이 일부 코드를 응용해서 load_image_color 함수를 실행하면 다음과 같은 결과를 얻을 수 있습니다.


image im = load_image_color("data/dog.jpg", 0,0);
save_image(im, "test");


1. "data/dog.jpg"에 있는 이미지를 읽어들입니다.


2. 해당 image를 "test"라는 이름으로 저장합니다.



4. get_image_layer :: src/image.c


get_image_layer 함수는 다차원 pixel image의 channel을 분리하는 함수입니다.

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

image get_image_layer(image m, int l)
{
    image out = make_image(m.w, m.h, 1);
    int i;
    for(i = 0; i < m.h*m.w; ++i){
        out.data[i] = m.data[i+l*m.h*m.w];
    }
    return out;
}


1. 파라미터로 받은 image 구조체의 width와 height가 같고, channel은 1인 빈 이미지를 만듭니다.


2. 파라미터로 받은 l계층의 image layer를 추출합니다.


3.추출된 image 구조체를 반환합니다.



함수 테스트


get_image_layer 함수를 아래와 같은 코드로 테스트해보았습니다.


void show_image_layers(image p, char *name)
{
    int i;
    char buff[256];
    for(i = 0; i < p.c; ++i){
        char name[256];
        sprintf(name, "layer-%d", i);
        sprintf(buff, "%s - Layer %d", name, i);
        image layer = get_image_layer(p, i);
        show_image(layer, buff);
        save_image(layer, name);
        free_image(layer);
    }
}

void test()
{
    image im = load_image_color("data/dog.jpg", 0,0);
    show_image_layers(im, "");
}


1. 먼저 image.c에 있는 show_image_layers함수가 각 개별의 layer를 추출해서 보여주는 함수이므로, 이를 변형하여 추출한 image를 저장하게 변경하였습니다.


2. 그리고 main 함수에서 실행되는 test 함수가 image를 열고, 이를 이용하여 show_image_layers 함수를 실행하게 코드를 변경하였습니다.


이렇게 변경하여 테스트한 코드는 아래와 같은 Image를 반환합니다.


  



5. print_image :: src/image.c

print_image 함수는 image의 pixel값들을 출력해서 보여주는 함수입니다.


모든 image를 출력하는 것은 아니며, channel값과 관계없이 width 30, height 30까지 정방형의 pixel값을 출력합니다.



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


void print_image(image m)
{
    int i, j, k;
    for(i =0 ; i < m.c; ++i){
        for(j =0 ; j < m.h; ++j){
            for(k = 0; k < m.w; ++k){
                printf("%.2lf, ", m.data[i*m.h*m.w + j*m.w + k]);
                if(k > 30) break;
            }
            printf("\n");
            if(j > 30) break;
        }
        printf("\n");
    }
    printf("\n");
}


1. image의 channel, width, height의 크기만큼 3중첩 for문을 순회합니다.


2. pixel값을 소숫점 2번째까지 보여줍니다.


3. 순회하고있는 width, height의 index 값이 30보다 크면, break를 통해서 끊고 나옵니다.



함수 테스트


print_image 함수를 아래와 같은 코드를 이용하여 테스트보면 출력은 다음과 같습니다.


void test()
{
    image im = load_image_color("data/dog.jpg", 0,0);
    print_image(im);
}




6. copy_image :: src/image.c


image 구조체를 복사하는 함수입니다.


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



image copy_image(image p)
{
    image copy = p;
    copy.data = calloc(p.h*p.w*p.c, sizeof(float));
    memcpy(copy.data, p.data, p.h*p.w*p.c*sizeof(float));
    return copy;
}


1. 새로운 image구조체 copy를 만들고 파라미터로 받은 image 구조체 p를 대입합니다.


2. image 구조체 멤버변수 data는 포인터이므로, 단지 주소만 복사된 것이므로, 동적할당을 통해서 image pixel들 크기만큼 메모리를 할당합니다.


3. 이제 파라미터로 받은 image 구조체 p의 pixel 데이터들을 memcpy함수를 통해서 image 구조체  copy에 복사합니다.


4. image 구조체 copy를 반환합니다.



7. get_pixel :: src/image.c


image 구조체에서 특정 위치의 pixel 값을 반환해주는 함수입니다.

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



static float get_pixel(image m, int x, int y, int c)
{
    assert(x < m.w && y < m.h && c < m.c);
    return m.data[c*m.h*m.w + y*m.w + x];
}


1. 파라미터로 받은 x,y,c의 값이 유효한 값인지 검사합니다.


2. image 구조체 m의 pixel들에서 원하는 위치의 pixel값을 반환합니다.



8. set_pixel :: src/image.c


image 구조체의 pixel 데이터의 값을 바꿔주는 함수입니다.

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



static void set_pixel(image m, int x, int y, int c, float val)
{
    if (x < 0 || y < 0 || c < 0 || x >= m.w || y >= m.h || c >= m.c) return;
    assert(x < m.w && y < m.h && c < m.c);
    m.data[c*m.h*m.w + y*m.w + x] = val;
}


1. 파라미터로 받은 x,y,z의 값이 유효한지 유효성검사를 합니다.


2. image 구조체 m의 특정 위치의 pixel 데이터값을 파라미터로 받은 val값으로 변경합니다.



9. embed_image :: src/image.c


image를 원하는 방향 (dx,dy)만큼 움직이는 함수입니다.

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


void embed_image(image source, image dest, int dx, int dy)
{
    int x,y,k;
    for(k = 0; k < source.c; ++k){
        for(y = 0; y < source.h; ++y){
            for(x = 0; x < source.w; ++x){
                float val = get_pixel(source, x,y,k);
                set_pixel(dest, dx+x, dy+y, k, val);
            }
        }
    }
}


1. 파라미터로 받은 source의 channels, height, width 순으로 순회를 돌면서 pixel value를 얻습니다.


2. 파라미터로 받은 dest에 해당 좌표값이 dx, dy만큼 움직인 좌표로 얻은 pixel value값을 넣어줍니다.



10. collapse_images_vert :: src/image.c


collapse_images_vert는 여러장의 사진을 수직으로 붙여서, 세로로 긴 하나의 이미지를 만드는 함수입니다.

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


image collapse_images_vert(image *ims, int n)
{
    int color = 1;
    int border = 1;
    int h,w,c;
    w = ims[0].w;
    h = (ims[0].h + border) * n - border;
    c = ims[0].c;
    if(c != 3 || !color){
        w = (w+border)*c - border;
        c = 1;
    }

    image filters = make_image(w, h, c);
    int i,j;
    for(i = 0; i < n; ++i){
        int h_offset = i*(ims[0].h+border);
        image copy = copy_image(ims[i]);
        //normalize_image(copy);
        if(c == 3 && color){
            embed_image(copy, filters, 0, h_offset);
        }
        else{
            for(j = 0; j < copy.c; ++j){
                int w_offset = j*(ims[0].w+border);
                image layer = get_image_layer(copy, j);
                embed_image(layer, filters, w_offset, h_offset);
                free_image(layer);
            }
        }
        free_image(copy);
    }
    return filters;
}


1. width 는 첫 이미지의  width값으로 합니다.


2. height는 image의 개수(파라미터 n) 곱하기 첫번째 image의 height값으로 합니다.


3. channels은 첫번째 image의 채널을 이용합니다.


4. 다른 컬러 스페이스를 갖는 이미지라면, width의 크기를 channel의 갯수만큼 키웁니다.


5. 빈 image인 filters를 만듭니다.


6. 이미지 갯수만큼 순회를 돌면서 차례차례 위에서 아래쪽으로 사진을 붙여나갑니다.


7. 다 붙였으면 해당 이미지를 반환합니다.



함수 테스트


collapse_images_vert 함수를 아래와 같은 코드를 이용하여 테스트보면 출력은 다음과 같습니다.



void test()
{

    image im[3] = {0,0,0};

    im[0] = load_image_color("data/dog.jpg", 0,0);
    im[1] = load_image_color("data/dog.jpg", 0,0);
    im[2] = load_image_color("data/dog.jpg", 0,0);

    show_images(&im, 3, "test");

    //save_image(m, "test");
}