개발자 블로그

영상처리 강좌 - 10. 컬러로 놀아보자! 본문

영상처리강좌

영상처리 강좌 - 10. 컬러로 놀아보자!

로이드.Roid 2016. 1. 4. 11:14


  안녕하세요~

  영상처리 강좌 열 번째 시간입니다~!

  이전 시간에는 컬러영상을 읽는 방법에 대해 배워봤는데요, 오늘은 예전에 흑백영상을 가지고 했던 실습들을 컬러영상을 대상으로 똑같이 적용시켜 보도록 하겠습니다. 
  오늘 강의는 이전에 배웠던 내용을 다시한번 복습한다는 생각으로 가볍게 봐 주셔도 될 것 같네요~


본 강좌를 처음부터 보시려면 아래 링크를 클릭하세요. (새창)
 ☞ 2015/08/10 - [영상처리강좌] - 영상처리 강좌를 시작합니다~!!



  우리가 지금까지 강의를 통해서 배웠던 내용은 크게

  1. 밝게, 어둡게, 색반전
  2. 이동, 미러, 플립
  3. 확대, 축소, 회전

  이 정도로 분류할 수 있겠네요. 


  오늘 강의는 이전에 배웠던 내용들을 샘플 영상만 PGM 이미지에서 PPM 이미지로 바꿔서 실습을 하는 것이므로, 이론에 대한 설명은 많지 않습니다. 실습 위주로 진행하도록 할테니 이론 설명이 필요하다면 본문 중간중간에 추가한 이전 강의 링크를 클릭하셔서 관련내용을 살펴보시기 바랍니다~


  위 내용들을 전부다 하기에는 너무 많은 분량이므로, 일부만 골라서 실습해 보도록 하겠습니다. 밝게, 미러, 확대 이렇게만 실습을 해보도록 하죠. 사실 위 세가지만 알아도 조금만 응용하면 나머지도 모두 구현할 수 있습니다.



  그럼 먼저 영상을 밝게 변화시키는 소스에 대해 알아보도록 하겠습니다. 역시나 전체소스는 본문 하단에 따로 파일로 첨부하였습니다. 여기서는 주요부분의 소스만 보도록 하죠.


int fnWritePPM(char* fileNm, PPMImage* img)
{
    const int brightness = 50;
    int r, g, b;
    FILE* fp;

    fp = fopen(fileNm, "w");
    if(fp == NULL){
        fprintf(stderr, "파일 생성에 실패하였습니다.\n");
        return FALSE;
    }

    fprintf(fp, "%c%c\n", 'P', '3');
    fprintf(fp, "%d %d\n" , img->width, img->height);
    fprintf(fp, "%d\n", 255);


    for(int i=0; i<img->height; i++){
        for(int j=0; j<img->width * 3; j+=3){
            r = img->pixels[i][j]   + brightness;
            g = img->pixels[i][j+1] + brightness;
            b = img->pixels[i][j+2] + brightness;

            if(r > 255) r = 255;
            if(g > 255) g = 255;
            if(b > 255) b = 255;

            fprintf(fp, "%d ", r);
            fprintf(fp, "%d ", g);
            fprintf(fp, "%d ", b);

        }

        fprintf(fp, "\n"); // 생략가능
    }

    fclose(fp);

    return TRUE;
}


  원본 컬러영상의 픽셀값에 +50을 해줬습니다. PGM 파일과 비교했을 때 크게 다른 부분은 없습니다. 한 픽셀당 한 개의 변수와 대응하느냐, 아니면 한 픽셀당 세 개의 변수와 대응하느냐의 차이밖에 없죠. 따로 설명드릴 부분은 없을 것 같습니다. 


  혹시 설명이 필요하다면 아래 링크를 클릭해서 이전 강의를 참고하시기 바랍니다.




  아래는 결과 영상입니다. 왼쪽이 원본, 오른쪽이 밝게 변경시킨 결과 영상입니다.


< 클릭하시면 큰 화면으로 볼 수 있습니다 >




  다음으로 미러(Mirror) 효과를 주는 프로그램입니다. 밝게 변화시키는 것은 픽셀의 값에 대한 처리이고, 미러는 좌표에 대해서 연산을 해주는 것이죠. 


int fnMirrorImg(PPMImage *img_org, PPMImage *img_mir)
{
    int new_x;


    // 헤더값 복사
    img_mir->M      = img_org->M;
    img_mir->N      = img_org->N;
    img_mir->width  = img_org->width;
    img_mir->height = img_org->height;
    img_mir->max    = img_org->max;

    // *img_mir = *img_org; 위 코드는 이런식으로 간단하게 할 수도 있음



    // <-- 메모리 할당 - 원본이미지와 동일한 크기로
    img_mir->pixels = (unsigned char**)calloc(img_org->height, sizeof(unsigned char*));

    for(int i=0; i<img_org->height; i++){
        img_mir->pixels[i] = (unsigned char*)calloc(img_org->width * 3, sizeof(unsigned char));
    }
    // -->



    // <-- 미러(좌우반전) 효과
    for(int i=0; i<img_org->height; i++){
        for(int j=0; j<img_org->width * 3; j+=3){
            
// 배열은 0부터 시작이므로 1px의 크기(3 byte)만큼 빼줌
            new_x = (img_org->width * 3) - j - 3;   

            img_mir->pixels[i][new_x]     = img_org->pixels[i][j];
            img_mir->pixels[i][new_x + 1] = img_org->pixels[i][j + 1];
            img_mir->pixels[i][new_x + 2] = img_org->pixels[i][j + 2];
        }
    }
    // -->


    return TRUE;
}




  역시나 미러에 대한 이론적인 설명이 필요하신 분은 아래 링크를 클릭해서 이전강의를 참고하시기 바랍니다.



  다만, 이건 밝게처리 하는 소스와 달리 약간 설명드려야 할 부분이 있겠네요. 소스에서 굵게 표시한 부분인데, 예전에 PGM 이미지를 가지고 실습할 때는 -1을 했었습니다. 그 이유는 10px의 이미지라고 가정한다면, 원본의 (0, 0)의 위치에 있는 픽셀은 (0, 9)의 위치로 이동이 되어야 하는데(크기가 10인 경우 배열첨자는 0~9 까지의 값을 갖습니다.) 이를 위해서 -1을 해줬던 것입니다.

  하지만 PPM 이미지의 경우에는 상황이 좀 다릅니다. 10px의 이미지라고 하면 배열첨자는 0~29까지의 값을 갖게되는 것이고, -3을 해줘야지 (0, 0)의 위치에 있는 픽셀의 첫 번째 값(Red 값이 되겠죠)은 배열의 [27] 위치로 이동을 하고, 나머지 Green과 Blue에 해당되는 픽셀도 각각 [28], [29]의 위치로 이동을 하게 되는 것이죠.


  단순하게 이해하시려면 1px의 size만큼 빼준다고 생각하시면 될 것 같습니다. 이렇게 되면 PGM 파일의 경우에는 -1을 해주면 되고, PPM 파일의 경우 -3을 해주는 것이죠.




  그럼 결과를 보도록 하겠습니다. 원하는 대로 결과가 나왔네요.



< 클릭하시면 큰 화면으로 볼 수 있습니다 >




  미러와는 관계없는 내용이지만, R, G, B 각각의 픽셀을 옮기는 소스를 살짝 바꾸면 재미있는 사진이 나옵니다. 아래는 R과 B의 값을 서로 바꿔서 대입한 결과입니다. 원본의 붉은 부분은 파랗게 되었고, 반대로 원본의 파란 부분은 붉게 변했습니다. 스머프 같기도 하고 좀 공포스러운 느낌도 들고 그러네요~ㅋ


< 클릭하시면 큰 화면으로 볼 수 있습니다 >






  다음~ 마지막으로 확대입니다. 확대 처리는 소스를 살짝 수정해봤는데요. 컬러영상의 픽셀에 논리적인 접근을 할 수 있도록 기존의 unsigned char 변수 대신 COLORPIXEL 이라는 구조체 변수를 선언했습니다. 해당 구조체에 보간법에서 hole 여부를 판단하기 위한 함수도 추가를 했구요.

  이 소스를 컴파일 하기 위해서는 소스의 확장자가 .c 가 아닌 .cpp 이어야 합니다. 아래 소스가 변경된 구조체의 소스 입니다.


typedef struct {
    unsigned char r;
    unsigned char g;
    unsigned char b;

    int isBlackPixel(){
        return (r + g + b == 0);
    }
} COLORPIXEL;

typedef struct {
    char    M, N;       // 매직넘버
    int     width;
    int     height;
    int     max;
    COLORPIXEL **pixels;
} PPMImage;


  이런식으로 소스를 작성하게 되면, 한 개의 픽셀의 1byte인지, 3byte인지 따져가면서 코딩할 필요가 없기 때문에 좀 더 논리적인 부분에 집중해서 코딩할 수 있다는 장점이 있습니다. 더 나아가서 클래스를 이용한다면 추상화, 다형성 같은 더 많은 장점들을 얻을 수 있겠죠. 하지만 뭐 강의가 C언어를 기본으로 하고있으니 클래스까지는 진행하지 않도록 하겠습니다. (사실 오래되서 C++로 강의를 하려면 다시 책을 펴봐야 될 것 같네요^^; 회사에서도 C언어를 주로 사용해서..)


  다음으로 리사이즈(확대) 함수를 보도록 하겠습니다.

int fnResizeImg(PPMImage *img_org, PPMImage *img_new, double n)
{
    int new_height = img_org->height * n;
    int new_width  = img_org->width  * n;
    int new_x;
    int new_y;


    // 헤더값 복사
    img_new->M      = img_org->M;
    img_new->N      = img_org->N;
    img_new->width  = new_width;
    img_new->height = new_height;
    img_new->max    = img_org->max;


    // <-- 메모리 할당 - 새로 생성될 이미지의 크기에 따라
    img_new->pixels = (COLORPIXEL**)calloc(new_height, sizeof(COLORPIXEL*));

    for(int i=0; i<new_height; i++){
        img_new->pixels[i] = (COLORPIXEL*)calloc(new_width, sizeof(COLORPIXEL));
    }
    // -->



    // <-- 배경색으로 초기화
    for(int i=0; i<img_new->height; i++){
        for(int j=0; j<img_new->width; j++){
            img_new->pixels[i][j] = BLACK_PIXEL;
        }
    }
    // -->



    // <-- 리사이즈(확대, 축소)
    for(int i=0; i<img_org->height; i++){
        new_y = ceil(i * n);


        if(new_y >= img_new->height){
            new_y = img_new->height - 1;
        }


        for(int j=0; j<img_org->width; j++){
            new_x = ceil(j * n);

            if(new_x >= img_new->width){
                new_x = img_new->width - 1;
            }

            img_new->pixels[new_y][new_x] = img_org->pixels[i][j];
        }
    }
    // -->


    return TRUE;
}



  이전과 비교했을 때 좀 더 단순해졌다는게 느껴지시나요??
  메모리를 할당하는 부분이나, for문에서의 조건들이 예전에 PGM 이미지로 실습할 때 처럼 단순해졌습니다. '한 픽셀은 3byte로 이루어져 있다' 라는 부분을 신경쓰지 않고 코딩할 수 있어서 소스를 작성하는 것도 쉬워지고 코드 또한 단순해졌습니다.

  소스 중간에 BLACK_PIXEL 이라는 변수는 r, g, b값이 모두 0, 0, 0인 상수로 main() 함수 바깥쪽에 전역변수로 선언하였습니다. 각각의 변수에 0을 대입하는 것 보다 저런식으로 사용하는게 더 간단하고 논리적이죠. 


  확대, 축소에 대한 설명과 보간법에 대한 설명은 역시 이전 강의를 참고하시기 바랍니다.



  예전 방식(COLORPIXEL 변수 대신 unsigned char 변수를 이용한 방식)으로 작성한 확대 함수 또한 본문 하단에 소스 첨부하였으니 필요하신 분들이나 궁금하신 분들은 참고하시기 바랍니다.


  아래는 원본 영상을 1.3배 확대한 결과 영상입니다. 



< 클릭하시면 큰 화면으로 볼 수 있습니다 >






  오늘은 거의 복습 위주의 강의가 되었네요. 지난 강의를 잘 따라 오셨던 분들이라면 쉽게 이해하셨을 것 이라고 생각합니다.
  다음 강의에서는 컬러영상의 다양한 색상체계에 대해서 알아보도록 하겠습니다. 

  혹시 궁금한 부분이나 잘못된 부분은 댓글로 남겨주세요~ 응원의 메시지도 환영입니다~ㅋ 

  부족한 강의 봐주셔서 대단히 감사합니다.






※ 본 포스트에 대한 링크는 가능하지만, 퍼가는 것은 정중하게 사양합니다.


Comments