2013년 1월 6일 일요일

OpenCV #4-1 Example (히스토그램 계산)

OpenCV #4 히스토그램을 이용한 화소 개수 세기

 - 영상은 여러 값을 갖는 화소로 구성 (1채널 그레이레벨 영상에서 각 화소는 0(검은색)과 255(흰색) 사이의 값을 갖는다.)
 - 히스토그램은 영상(또는 영상 집합) 내 해당 값을 갖는 화소 개수를 담은 간단한 테이블.
 - 그레이레벨 영상의 히스토그램은 당연히 256개의 항목(또는 빈도)이다.

 - 빈도가 0이면 0인 값을 갖는 화소 개수를 의미, 1이면 1인 값을 갖는 화소 개수를 의미.
 - 히스토그램의 모든 항목을 합하면 화소의 총 개수를 얻을 수 있음.
 - 히스토그램을 빈도의 합인 1로 정규화할 수 있다. (이 경우 각 빈도는 영상 내 특정 값을 갖는 화소의 비율)

 - cv::calcHist 함수를 사용해 히스토그램을 쉽게 계산할 수 있다.
 - cv::calcHist 함수는 임의의 화소값 타입을 갖는 다중 채널의 히스토그램을 계산할 수 있는 일반적인 함수.
 - 1채널 그레이레벨 영상을 처리하기 위한 전문 클래스로 사용하게 만들어본다.


  • Example
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

// 멤버 변수 정의
class Histogram1D {
private:
int histSize[1]; // 빈도수
float hranges[2]; // 최소/최대 화소값
const float* ranges[1];
int channels[1]; // 여기서 1채널만 사용
public :
Histogram1D() { // 1차원 히스토그램을 위한 인자 준비
histSize[0]= 256;
hranges[0]= 0.0;
hranges[1]= 255.0;
ranges[0]= hranges; 
channels[0]= 0; // 기본적으로 채널을 0으로 보기
}

// 정의한 멤버 변수로 그레이레벨 영상의 히스토그램을 계산할 때는 다음 메소드를 사용해 수행
cv::MatND getHistogram(const cv::Mat &image) { 
// 1차원(1D) 히스토그램 계산.
cv::MatND hist;
cv::calcHist(&image, // 히스토그램 계산 
1, // 단일 영상의 히스토그램만
channels, // 대상 채널               
cv::Mat(), // 마스크 사용하지 않음     
hist, // 결과 히스토그램         
1, // 1차원(1D) 히스토그램           
histSize, // 빈도수                  
ranges // 화소값 범위             
);
return hist;
}

// 값의 순서만으로 의미를 파악하기 어려우므로 바 그래프를 사용
// 그래프를 생성하는 메소드
cv::Mat getHistogramImage(const cv::Mat &image){ 
// 1차원(1D) 히스토그램을 계산하고, 영상으로 반환

cv::MatND hist= getHistogram(image); // 먼저 히스토그램 계산

double maxVal=0; // 최대 빈도수 가져오기
double minVal=0; // 최소 빈도수 가져오기
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);

cv::Mat histImg(histSize[0], histSize[0], CV_8U,cv::Scalar(255));
// 히스토그램을 출력하기 위한 영상

int hpt = static_cast<int>(0.9*histSize[0]);
// nbins의 90%를 최대점으로 설정


for(int h=0; h<histSize[0]; h++){ // 각 빈도에 대한 수직선을 그리기 
float binVal = hist.at<float>(h);
int intensity = static_cast<int>(binVal*hpt/maxVal);
cv::line(histImg,cv::Point(h,histSize[0]),cv::Point(h,histSize[0]-intensity),cv::Scalar::all(0));
// 두 점 간의 거리를 그리는 함수
}
return histImg;
}
};

int main()
{
cv::Mat image = cv::imread("batgirl.jpg", 0);
// 입력 영상 읽기
// 이진 영상으로 간주해 열기
if(!image.data)
return 0;

cv::namedWindow("Image");
cv::imshow("Image", image);

Histogram1D h; // 히스토그램 객체
cv::MatND histo = h.getHistogram(image); // 히스토그램 계산

for(int i=0 ; i<256 ; i++) // 각 빈도 조회
std::cout << "Value " << i << " = " << histo.at<float>(i) << std::endl;

cv::namedWindow("Histogram");
cv::imshow("Histogram", h.getHistogramImage(image));
// 히스토그램을 영상으로 띄우기
// 가운데를 중심으로 왼쪽이 검정색, 오른쪽이 흰색값
// 가운데 봉우리 부분은 중간 명암도 값
// 왼쪽이 영상의 전경, 오른쪽이 배경

// 영상을 두 그룹으로 나누는 부분을 경계값으로 처리해 확인
cv::Mat thresholded; // 경계값으로 이진 영상 생성
cv::threshold(image,thresholded,60,255,cv::THRESH_BINARY);
// 영상을 경계화 하기 위해 히스토그램의 
// 높은 봉우리(명암값 60) 방향으로 증가하기 직전인 최소값으로 정함.
cv::namedWindow("Binary Image"); // 경계화된 영상 띄워 보기
cv::imshow("Binary Image",thresholded); // 배경과 전경이 분할됨

cv::waitKey(0);

return 0;
}


  • Result

  • 예제 분석
 - cv::calcHist 함수는 다양한 상황에서 사용할 수 있는 많은 파라미터를 갖는다.
 - 대부분 히스토그램은 단일 1채널이나 3채널 영상 중 하나.
 - cv::calcHist 함수는 분산된 여러 영상인 다중 채널 영상을 지정할 수 있다.

 - 영상 배열을 함수에 입력 하는 이유 : 여섯 번째 파라미터는 히스토그램의 차원수를 결정. (1이면 1차원 히스토그램 - 히스토그램 계산 시 고려해야 할 채널은 설정한 차원수를 갖는 배열 중 하나)
 - 구현한 클래스에서 기본 채널인 단인 채널은 0(세 번째 파라미터).
 - 히스토그램은 자체적으로 각 차원 내 빈도수(일곱 번째 파라미터, 정수형 배열), 최소값과 최대값(여덟 번째 파라미터, 2요소 배열을 갖는 배열)으로 기술.
 - 각 빈도의 한계를 설정해야 하는 경우 비균등 히스토그램을 정의할 수 있다.

 - OpenCV 함수에서 원하는 개수(마스크의 모든 화소값이 0이면 무시)가 들어있는 화소를 의미하는 마스크를 지정할 수 있다.
 - 두 개의 부가적인 옵션 파라미터를 설정할 수 있는데, 모두 Boolean 값.
 - 첫 번째는 히스토그램이 균등한지 아닌지(균등화는 기본 값)를 나타 냄.
 - 두 번째는 여러 히스토그램 계산 결과를 누적.
 - 마지막 파라미터가 참이면 영상의 화소 개수에 입력 히스토그램의 현재 값을 더함. (영상 그룹의 히스토그램을 계산하길 원할 때 유용)

 - cv::MatND 인스턴스에 히스토그램 결과를 저장. (N차원 행렬을 조작할 때 사용하는 일반적인 클래스 - 1, 2, 3차언 행렬을 위해 정의된 at 메소드를 갖고 있다.)
 - float binVal = histo.at<float>(h)

 - getHistogramImage 메소드 내 1차원 히스토그램의 각 빈도에 접근할 때 히스토그램 값을 float로 저장했다. (histo.at<float>(i))

  • 참고문헌 : OpenCV 2 Computer Vision Application Programming Cookbook