2017-06-02 13:32:44

오늘은 http://opencvexamples.blogspot.com/p/learning-opencv-functions-step-by-step.html에 있는 5번째 예제를 배워보겠습니다. 이 예제를 공부하는데 있어서 http://docs.opencv.org/2.4/doc/tutorials/imgproc/threshold/threshold.html도 큰 도움이 되었습니다.



5. Threshold operation


Threshold operation은 가장 간단한 분할 방법입니다. 분석하고자하는 물체와 배경을 픽셀 세기의 변화에 기반해서 분할합니다. 간단히 하나의 역치값(threshold value)을 설정해서 이것보다 크면 물체로, 이것보다 작으면 배경으로 판단합니다.  


opencv에서는 5가지 유형의 threshold operation을 제공합니다.


1) Threshold Binary 

이 유형은 이미지 내의 픽셀 값이 역치값보다 크면 픽셀세기의 최대값으로, 역치값보다 작으면 0으로 변환해주는 것입니다. 


2) Threshold Binary, Inverted

첫번째 유형과는 반대로, 픽셀 값이 역치값보다 크면 0으로, 작으면 픽셀세기의 최대값으로 변환해주는 것입니다. 


3) Truncate

이 유형은 픽셀 값이 역치값보다 크면 그 픽셀 값들은 모두 역치값으로 변환해주고, 작으면 그대로 나둡니다. 


4) Threshold to Zero

이 유형은 픽셀 값이 역치값보다 큰 부분은 그대로 나두고, 작은 부분은 모두 0으로 변환해주는 것입니다.


5) Threshold to Zero, Inverted

네번째 유형의 반대 경우로, 픽셀 값이 역치값보다 크면 0으로, 작으면 그대로 나둡니다.


소스코드입니다.


#include "opencv2/imgproc/imgproc.hpp"

#include "opencv2/highgui/highgui.hpp"

#include <stdlib.h>

#include <stdio.h>


using namespace cv;


int threshold_value = 0;

int threshold_type = 3;;

int const max_value = 255;

int const max_type = 4;

int const max_BINARY_value = 255;


Mat src, src_gray, dst;

char* window_name = "Threshold Demo";


char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted";

char* trackbar_value = "Value";


void Threshold_Demo(int, void*);


int main(int argc, char** argv)

{

/// Load an image

src = imread("apple.jpg", 1);


/// Convert the image to Gray

cvtColor(src, src_gray, CV_RGB2GRAY); //RGB 영상을 그레이 영상으로 변환


/// Create a window to display results

namedWindow(window_name, CV_WINDOW_AUTOSIZE); //결과를 나타내기 위한 창을 만든다.


/// Create Trackbar to choose type of Threshold

createTrackbar(trackbar_type,

window_name, &threshold_type,

max_type, Threshold_Demo); //Threshold의 타입을 선택하기 위한 트랙바를 만든다.


createTrackbar(trackbar_value,

window_name, &threshold_value,

max_value, Threshold_Demo);


/// Call the function to initialize

Threshold_Demo(0, 0); 


/// Wait until user finishes program

while (true)

{

int c;

c = waitKey(20);

if ((char)c == 'e') // e를 타이핑하면 프로그램 종료. 

{

break;

}

}

}



void Threshold_Demo(int, void*)

{

/* 0: Binary

1: Binary Inverted

2: Threshold Truncated

3: Threshold to Zero

4: Threshold to Zero Inverted

*/


threshold(src_gray, dst, threshold_value, max_BINARY_value, threshold_type); // Threshold operation 함수 실행


imshow(window_name, dst);

}



threshold 함수들의 입력인수들을 설명하겠습니다.

  • 1번째 인수: 처리를 해줄 이미지입니다.
  • 2번째 인수: 처리가 된 이미지를 대입해줄 변수를 넣어줍니다.
  • 3번째 인수: 역치값을 설정합니다.
  • 4번째 인수: threshold 타입 중에서 THRESH_BINARY와 THRESH_BINARY_INV를 사용할 때 필요한 최대값입니다. (maximum value to use with the THRESH_BINARY and THRESH_BINARY_INV thresholding types.)
  • 5번째 인수: threshold 타입을 결정합니다. THRESH_BINARY, THRESH_BINARY_INV, THRESH_TRUNC, THRESH_TOZERO, THREH_TOZERO_INV 이렇게 다섯가지가 있습니다. 


참고로 트랙바(track bar)란 일정한 범위 내의 특정한 값을 선택하고자 할 때 사용하는 일종의 스크롤 바입니다. (참고: http://terms.naver.com/entry.nhn?docId=864874&cid=50371&categoryId=50371)


실행된 결과입니다 (그림 1). 사과들을 잘 분할해낸 것을 볼 수 있습니다. 


그림1. Threshold binary, inverted 타입으로 역치 연산을 한 결과



이것을 응용해서 이번에는 초록색 사과와 빨간 색 사과를 구별해보겠습니다. 그냥 그레이 영상의 픽셀 세기로 역치값 연산을 시행하면 어려울 것 같습니다. 그래서 RGB중에 G이미지를 추출해서 역치값 연산을 해보겠습니다. 


코드는 아래와 같습니다.


#include "opencv2/imgproc/imgproc.hpp"

#include "opencv2/highgui/highgui.hpp"

#include <stdlib.h>

#include <stdio.h>


using namespace cv;


int threshold_value = 0;

int threshold_type = 3;;

int const max_value = 255;

int const max_type = 4;

int const max_BINARY_value = 255;


Mat src, src_g, dst;

char* window_name = "Threshold Demo";


char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted";

char* trackbar_value = "Value";


void Threshold_Demo(int, void*);


int main(int argc, char** argv)

{

/// Load an image

src = imread("apples.jpg", 1);


namedWindow("Original image", CV_WINDOW_AUTOSIZE); //원본 이미지를 나타내기 위한 창을 만든다.

imshow("Original image", src); // 원본 이미지를 보여줍니다. 


/// RGB영상을 R채널, G채널, B채널로 분리합니다. 

Mat image_r(src.rows, src.cols, CV_8UC1);

Mat image_g(src.rows, src.cols, CV_8UC1);

Mat image_b(src.rows, src.cols, CV_8UC1);


Mat out[] = { image_r, image_g, image_b };

int from_to[] = { 0,2, 1,1, 2,0 };    // BGR 이미지를 RGB로 순서를 바꾸겠다는 의미. 

mixChannels(&src, 1, out, 3, from_to, 3);


src_g = image_g;


namedWindow("G image", CV_WINDOW_AUTOSIZE); //G 이미지를 나타내기 위한 창을 만든다.

imshow("G image", src_g); // G 이미지를 보여줍니다. 


/// Create a window to display results

namedWindow(window_name, CV_WINDOW_AUTOSIZE); //결과를 나타내기 위한 창을 만든다.


/// Create Trackbar to choose type of Threshold

createTrackbar(trackbar_type,

window_name, &threshold_type,

max_type, Threshold_Demo); //Threshold의 타입을 선택하기 위한 트랙바를 만든다.


createTrackbar(trackbar_value,

window_name, &threshold_value,

max_value, Threshold_Demo);


/// Call the function to initialize

Threshold_Demo(0, 0); 


/// Wait until user finishes program

while (true)

{

int c;

c = waitKey(20);

if ((char)c == 'e') // e를 타이핑하면 프로그램 종료. 

{

break;

}

}

}



void Threshold_Demo(int, void*)

{

/* 0: Binary

1: Binary Inverted

2: Threshold Truncated

3: Threshold to Zero

4: Threshold to Zero Inverted

*/


threshold(src_g, dst, threshold_value, max_BINARY_value, threshold_type); // Threshold operation 함수 실행


imshow(window_name, dst);

}


RGB영상을 각 채널로 분리시키는 것은 mixChannels 함수로 가능합니다. opencv에서 칼라이미지는 BGR 순서로 저장되어 있기 때문에, RGB로 순서를 바꾸겠습니다. 

(참고: http://docs.opencv.org/master/d2/de8/group__core__array.html#ga51d768c270a1cdd3497255017c4504be)


mixChannels(const Mat *src, int nsrc, Mat* dst, int ndst, const int *fromTo, size_t npairs);

  • src: 행렬들의 인풋 배열

  • nsrc: src의 행렬의 갯수 

  • dst: 행렬들의 아웃풋 배열

  • ndst: dst의 행렬의 갯수

  • fromTo: 인덱스 쌍들의 배열, 즉 어떻게 채널들을 구성해줄지 설정.

  • npairs: fromTo 내의 인덱스 쌍의 갯수 


실행된 결과입니다 (그림 2). 중간에 있는 Green 채널 이미지를 보시면 초록색 사과가 전반적으로 빨간 색 사과보다 밝습니다. 그렇기 때문에 초록색 사과와 빨간 색 사과를 역치값 연산으로 어느 정도 구별해 낼수 있습니다.


그림 2. 왼쪽부터 차례로 원본 이미지, Green 채널 이미지, 역치값 연산을 실행한 이미지