2013년 1월 7일 월요일

OpenCV #4-2 Example (영상 모습을 수정하기 위한 룩업 테이블 적용 - 부연 설명)

 - 영상의 명암 대비를 개선하기 위해 시도하는 룩업 테이블을 정의할 수 있다.
 - 첫 번째 예제의 원 히스토그램을 보면 가능한 명암도 값의 전체 범위를 사용하지 않았다. (빛나는 명암도 값을 사용하지 않음)

 - 따라서 한 가지로는 명암 대비를 확장시킨 영상을 만들기 위해 히스토그램을 스트레치 할 수 있다.
 - 이 과정은 히스토그램 내의 넌제로 개수 부분중 가장 낮고(imin) 가장 높은(imax) 명암도 값을 감지하기 위해 설계됬다.
 - imin 값을 명암도 0에 저장, imax에 255값을 할당시키는 방법으로 명암도 값을 재매핑 할 수 있음.
 - 중간 명암도 값인 i는 다음과 같이 간단하게 선형적으로 재매핑 함.
 - 255.0*(i-imin) / (imax-imin)+0.5);


  • 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;
}

cv::Mat applyLookUp (const cv::Mat& image, const cv::Mat& lookup)
// const cv::Mat& image는 입력 영상
// const cv::Mat& lookup는 1x256 uchar 행렬
{
cv::Mat result; // 결과 영상
cv::LUT(image, lookup, result);
// 새로운 영상을 만들기 위해 룩업 테이블을 영상에 적용
// 새로운 영상에는 룩업 테이블에 의해 처리된 그대로 화소의 명암도 값을 수정한 결과가 들어감
return result;
}

// 영상 스트레치 메소드
cv::Mat stretch(const cv::Mat &image, int minValue=0) {
// 원 영상을 스트레치.

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

// 히스토그램의 왼쪽으로 시작하는 부분을 찾기
int imin= 0;
for( ; imin < histSize[0]; imin++ ) {
std::cout<<hist.at<float>(imin)<<std::endl;
if (hist.at<float>(imin) > minValue)
break;
}

// 히스토그램의 오른쪽으로 끝나는 부분을 찾기
int imax= histSize[0]-1;
for( ; imax >= 0; imax-- ) {
if (hist.at<float>(imax) > minValue)
break;
}

int dims[1]={256}; // 룩업 테이블 생성
cv::MatND lookup(1,dims,CV_8U);

for (int i=0; i<256; i++) {
if (i < imin) lookup.at<uchar>(i)= 0;
else if (i > imax) lookup.at<uchar>(i)= 255;
else lookup.at<uchar>(i)= static_cast<uchar>(255.0*(i-imin)/(imax-imin)+0.5);
}

// 룩업 테이블 적용
cv::Mat result;
result= applyLookUp(image,lookup);

return result;
}
};

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::Mat streteched= h.stretch(image,1200);
// 1200 화소보다 작은 시작 빈도와 마지막 빈도를 무시
// 1200 화소 미만의 빈도를 무시한 영상 스트레치

cv::namedWindow("Stretched Image"); // 결과 보기
cv::imshow("Stretched Image",streteched);

cv::namedWindow("Stretched Histogram"); // 새로운 히스토그램 보기
cv::imshow("Stretched Histogram",h.getHistogramImage(streteched));

cv::waitKey(0);

return 0;
}


  • Result

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