해당 포스트는 "OpenCV로 배우는 영상 처리 및 응용", "C++ API OpenCV 프로그래밍" 책의 내용을 요약한 것이다.



※ 히스토그램 생성

void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform = true, bool accumulate=false)

: images는 히스토그램을 계산할 영상의 배열이고 nimages는 영상 배열의 크기 즉, 영상의 개수이다. channel은 히스토그램을 적용할 채널 값을 배열로 나타낸다. 만약 회색조 영상에 대해서 히스토그램을 수행한다면 channels = {0} 으로 하면 된다. hist는 입력 영상에 대한 출력 히스토그램, histSize는 히스토그램의 구간 크기, dims는 히스토그램  hist의 차원 크기이다. ranges는 히스토그램의 범위를 지정한다. 보통 회색조 영상은 ranges={0,256}을 지정한다. accumulate가 true에면 calcHist 함수를 수행할 때 히스토그램을 초기화하지 않고 이전 값을 계속 누적한다.


ex) 

- uniform = true, histSize[] = {4}, valueRange[] ={0,8}, ranges[] = {valueRange} 일 경우

: 히스토그램의 구간은 0~2, 2~4, 4~6, 6~8 이 된다.


- uniform = false, histSize[] = {4}, valueRange[] ={0,1,4,5,8}, ranges[] = {valueRange} 일 경우

: 히스토그램의 구간은 0~1, 1~4, 4~5, 5~8 이 된다.


※ 히스토그램 평활화

히스토그램 평활화에 대한 이론적인 부분은 "영상의 광학적 변환(4) - 히스토그램"에 설명되어 있다.

void equalizeHist(InputArray src, OutputArray dst)

: src 영상에 대해 히스토그램 평활화를 수행한 결과를 dst에 저장한다.


※ 히스토그램 역투영

void calcBackProject(const Mat* images, int nimages, const int* channels, const SparseMat& hist, OutputArray backProject, const float** ranges, double scale=1, bool uniform=true)

: 히스토그램 hist의 역투영을 backProject에 계산한다. 얼굴 인식에서 얼굴 영역 분할을 위해 피부 색상을 이용할 때, 샘플 영상을 피부 영역의 히스토그램을 계산한 후에 역투영 하면 손, 얼굴 등의 영역을 분할할 수 있다. 또한, 추척할 물체를 관심 영역으로 지정 후 히스토그램을 얻어 비디오에 역투영하고 임계치를 적용하면 물체 추적도 가능하다. 


ex)

#include "opencv.hpp" using namespace cv; using namespace std; int main() { uchar dataA[16] = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3 }; Mat A(4, 2, CV_8UC2, dataA); //2채널의 4*2 행렬 생성 cout << "A=" << A << endl; int histSize[] = { 4, 4 }; float range1[] = { 0, 4 }; float range2[] = { 0, 4 }; const float* ranges[] = { range1, range2 }; // 각 채널에 대해 0~4 범위 지정 int channels[] = { 0, 1 }; int dims = 2; Mat hist; calcHist(&A, 1, channels, Mat(), hist, dims, histSize, ranges, true); //A 행렬 각 채널에 대해 히스토그램을 계산 cout << "hist=" << hist << endl; Mat backProject; calcBackProject(&A, 1, channels, hist, backProject, ranges); // A행렬에 대해 hist를 기준으로 역투행 cout << "backProject=" << backProject << endl; return 0; }


위 사진은 코드의 결과이다. A 행렬에서 (0,0)을 가지는 행렬 요소가 2개 있다. 따라서 hist의 (0,0) 행렬 요소가 2가 된다. (1,1)을 가지는 행렬 요소는 3개가 있기 때문에 hist의 (1,1) 행렬 요소는 3이 된다. 역투영 결과를 보자. 역투영은 역투영 하는 행렬 요소 값이 얼마나 있는 지 hist를 통해 역투영 행렬 안에 명시하는 것이다. 예를 들어 값이 (0,0)인 행렬 요소는 hist를 보면 2개가 있는 걸 알 수 있다. 따라서 역투영을 하게 되면 역투영 행렬의 (0,0)에 2가 들어간다. 마찬가지로 역투영 행렬의 (0,1)에도 2가 들어간다. 이러한 역투영을 이용하면 임계값을 이용해서 물체를 구별할 수 있다. 다음의 예를 보자


ex) 

#include "opencv2\opencv.hpp" using namespace cv; using namespace std; int main() { Mat srcImage = imread("fruits.jpg"); if (srcImage.empty()) return -1; Mat hsvImage; cvtColor(srcImage, hsvImage, COLOR_BGR2HSV); //히스토그램은 밝기값을 통해 계산하기 때문에

//RGB 영상을 HSV 영상으로 바꾼 후 H 채널만 분리하도록 한다. vector<Mat> planes; split(hsvImage, planes); Mat hueImage = planes[0]; // H 채널을 따로 분리 Rect roi(100, 100, 100, 100); // yellow orange //관심 영역 rectangle(srcImage, roi, Scalar(0, 0, 255), 2); //srcImage의 관심 영역에 사각형 그림 Mat roiImage = hueImage(roi); int histSize = 256; float hValue[] = { 0, 256 }; const float* ranges[] = { hValue }; int channels = 0; int dims = 1; Mat hist; calcHist(&roiImage, 1, &channels, Mat(), hist, dims, &histSize, ranges); //관심 영역을 히스토그램 계산 Mat backProject; calcBackProject(&hueImage, 1, &channels, hist, backProject, ranges); Mat backProject2; normalize(backProject, backProject2, 0, 255, NORM_MINMAX, CV_8U); //정규화하여 더 나은 시각화를 이룸 imshow("backProject2", backProject2); imshow("srcImage", srcImage); waitKey(); return 0; }

위 그림은 위 소스의 결과 영상이다. 관심영역의 픽셀 값과 비슷한 부분이 왼쪽 역투영한 사진에서 햐얀색으로 보인다. 위 역투영 영상에 threshold 메서드를 적용해 임계값으로 이진화 되는 영상을 만든 다면 더욱 더 뚜렷하게 영상 속 물체들을 구분 할 수 있을 것이다. 다음은 backProject2 영상에 대해서 임계값을 적용한 예이다.

	Mat dstImage;
	threshold(backProject2, dstImage, 250, 255, THRESH_BINARY);


위에 임계치를 적용하지 않은 영상과 비교해 보면 그나마 더 비슷한 부분의 영역이 하얀 걸 볼 수 있다.

+ Recent posts