728x90
반응형






히스토그램 구현하기






영상은 다양한 화소 값으로 이루어져있으며, 영상에 걸친 화소 값 분포는 영상의 중요한 특징을 구성하게된다. 예를 들어, 1채널 그레이레벨 영상에서 각 화소는 0~255 사이의 값을 갖는다. 영상 내용에 따라 영상 내부에 펼처진 그레이 음영의 양이 저마다 다르므로 여기서 히스토그램은 영상에서 특정 값을 갖는 화소 개수를 제공하게되는 단순한 테이블이다.


그레이레벨의 히스토그램은 256개 항목(빈, bin)을 갖게 되며, 빈의 합이 1이 되게끔 히스토그램을 정규화 할 수 도 있다.


cv::calcHist 함수를 이용하여 OpenCV를 이용하여 히스토그램을 쉽게 계산 할 수 있다.




다음은 히스토그램을 계산하는 histogram.h 코드이다.


 


/*------------------------------------------------------------------------------------------*\
   This file contains material supporting chapter 4 of the cookbook:  
   Computer Vision Programming using the OpenCV Library 
   Second Edition 
   by Robert Laganiere, Packt Publishing, 2013.

   This program is free software; permission is hereby granted to use, copy, modify, 
   and distribute this source code, or portions thereof, for any purpose, without fee, 
   subject to the restriction that the copyright notice may not be removed 
   or altered from any source or altered source distribution. 
   The software is released on an as-is basis and without any warranties of any kind. 
   In particular, the software is not guaranteed to be fault-tolerant or free from failure. 
   The author disclaims all warranties with regard to this software, any use, 
   and any consequent failure, is purely the responsibility of the user.
 
   Copyright (C) 2013 Robert Laganiere, www.laganiere.name
\*------------------------------------------------------------------------------------------*/

#if !defined HISTOGRAM
#define HISTOGRAM

#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>

// 그레이레벨 영상의 히스토그램 생성
class Histogram1D {

  private:

    int histSize[1];         // 히스토그램의 빈 개수
	float hranges[2];        // 히스토그램 값의 범위
    const float* ranges[1];  // 값 범위를 가리키는 포인터
    int channels[1];         // 조사할 채널 번호

  public:

	Histogram1D() {

		// 1D 히스토그램의 기본적인 인자 준비
		histSize[0]= 256;   // 256개의 빈
		hranges[0]= 0.0;    // 0부터 (포함)
		hranges[1]= 256.0;  // 256까지 (제외)
		ranges[0]= hranges;
		channels[0]= 0;     // 채널 0에서 봄
	}

	// Sets the channel on which histogram will be calculated.
	// By default it is channel 0.
	void setChannel(int c) {

		channels[0]= c;
	}

	// Gets the channel used.
	int getChannel() {

		return channels[0];
	}

	// Sets the range for the pixel values.
	// By default it is [0,256[
	void setRange(float minValue, float maxValue) {

		hranges[0]= minValue;
		hranges[1]= maxValue;
	}

	// Gets the min pixel value.
	float getMinValue() {

		return hranges[0];
	}

	// Gets the max pixel value.
	float getMaxValue() {

		return hranges[1];
	}

	// Sets the number of bins in histogram.
	// By default it is 256.
	void setNBins(int nbins) {

		histSize[0]= nbins;
	}

	// Gets the number of bins in histogram.
	int getNBins() {

		return histSize[0];
	}

	// 1D 히스토그램 계산
	cv::Mat getHistogram(const cv::Mat &image) {

		cv::Mat hist;

		// 히스토그램 계산
		cv::calcHist(&image, 
			1,			// 단일 영상의 히스토그램
			channels,	// 대상 채널
			cv::Mat(),	// 마스크 사용 안함
			hist,		// 결과 히스토그램 
			1,			// 1D 히스토그램
			histSize,	// 빈 개수
			ranges		// 화소값의 범위
		);

		return hist;
	}


    // 1D 히스토그램을 계산한 후 히스토그램 영상으로 변환
	cv::Mat getHistogramImage(const cv::Mat &image, int zoom = 1){

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

		// 반환할 영상 생성
        return Histogram1D::getImageOfHistogram(hist, zoom);
	}


    // 히스토그램을 표현하는 영상 생성 ( 정적 메소드 )
    static cv::Mat getImageOfHistogram(const cv::Mat &hist, int zoom) {

        // 최소, 최대 빈 얻기
        double maxVal = 0;
        double minVal = 0;
        cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);

        // 히스토그램 크기 얻기
        int histSize = hist.rows;

        // 히스토그램을 표시하는 정사각형 영상
        cv::Mat histImg(histSize*zoom, histSize*zoom, CV_8U, cv::Scalar(255));

        // 총 빈의 90%를 정점으로 설정(즉, 영상의 높이)
        int hpt = static_cast<int>(0.9*histSize);

        // 각 빈 당 수직선 그리기 
        for (int h = 0; h < histSize; h++) {

            float binVal = hist.at<float>(h);
            if (binVal>0) {
                int intensity = static_cast<int>(binVal*hpt / maxVal);
                cv::line(histImg, cv::Point(h*zoom, histSize*zoom),
                    cv::Point(h*zoom, (histSize - intensity)*zoom), cv::Scalar(0), zoom);
            }
        }

        return histImg;
    }
};


#endif




다음은 main 코드이다.



 

#include<iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include "histogram.h"

using namespace std;
using namespace cv;

int main()
{
	Mat image = imread("image/image1.jpg", 0); // 이진영상으로 열기
	if (!image.data) return 0;

	resize(image, image, Size(), 0.7, 0.7);

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


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

	// 영상에서 각 빈을 순회
	for (int i = 0; i < 256; i++)
		cout << "Value " << i << " = " << histo.at<float>(i) << endl;

	namedWindow("Histogram");
	imshow("Histogram", h.getHistogramImage(image));

	waitKey(0);

	return 0;

}




결과화면은 다음과 같다.





사진에서 확인 할 수 있듯이 히스토그램 영상은 위와 같고, 각 빈의 화소값을 조회하였다.


히스토그램 영상은 중간부터 시작해서 오른쪽으로 솟구쳐있다. 이러한 영상은 밝은 화소가 아주 많다는 것을 의미한다. 반대로 가운데를 기준으로 왼쪽이 솟아있다면, 어두운 화소가 아주 많다는 것을 의미한다.


두 그룹(밝고, 어두운) 사이에서 바뀌는 부분을 가지고 영상을 경계화 하면 전경과 배경을 분리 할 수 있다.


경계화를 위해 사용할 수 있는 함수는 cv::threshold 함수이다.




경계를 기준으로 이진화를 적용한 코드와 결과는 다음과 같다.


 
	// 가운데를 기준으로 경계화 (이진화)

	cv::Mat thresholded; // 결과 이진 영상
	cv::threshold(image, thresholded,
		100,    // 임의의 경계 값
		255,   // 경계값 이상이면 화소에 할당하는 값
		cv::THRESH_BINARY); // 경계화 타입

	// Display the thresholded image
	cv::namedWindow("Binary Image");
	cv::imshow("Binary Image", thresholded);
	thresholded = 255 - thresholded;
	cv::imwrite("binary.bmp", thresholded);
















728x90
반응형