Image Histogram 을 만들어보자

2018, Jun 26    

이번 포스팅에서는 image를 이용하여 histogram을 그려볼 것이다. image histogram 이란 image 픽셀 값의 빈도를 막대 그래프로 나타낸 것이다.

방법은 단일 채널의 히스토그램을 그린 뒤 합쳐서 칼라이미지의 히스토그램을 그리는 것이다.

먼저 단일 채널의 히스토그램을 그려보자. 헤더 파일에 선언하는 것은 생략하기로 하고 정의 부분만 살펴보자.

ImageProc.cpp

void ImageProc::CreateHistogramSingleChannel(unsigned char* image_input,
	unsigned char* histo_output, const int width, const int height,
	const int histo_width, const int histo_height)
{
    // 빈도 값을 저장할 배열을 선언
    float valArr[256] = { 0.f };
    
    // 이미지 픽셀 값의 빈도를 늘리면서
    // 후에 정규화를 위해 max 값을 저장한다.
	float max = 0.f;
	for (int i = 0; i < width*height; i++)
	{
		valArr[image_input[i]] += 1.f;
		max = __max(valArr[image_input[i]], max);
	}
		
	// histogram image 에 빈도값을 그린다.
    // histogram value 는 255까지 이다.
	for (int i = 0; i < 256; i++)
	{
        // 구한 max 값을 이용해 정규화 한다.
        // histo_height는 해당 value 가 가질 수 있는 최대값이다.
		valArr[i] = (valArr[i] / max) * histo_height;
        // 빈도만큼 반복하면서 아래부터 색깔을 칠한다.
		for (int j = 0; j < valArr[i]; j++)
		{
            // histogram 너비는 512로 만들 예정이다.
            // 체크를 2px 에 걸쳐 해준다.
			histo_output[(histo_height - 1 - j)*histo_width + (i * 2) + 0] = 255;
			histo_output[(histo_height - 1 - j)*histo_width + (i * 2) + 1] = 255;
		}
	}
}

단일 히스토그램을 합쳐서 칼라 이미지의 히스토그램으로 만드는 함수를 정의하자. 칼라 이미지와 히스토그램 이미지를 단일 채널로 나눈뒤 작업하고 단일 칼라 이미지의 히스토그램을 하나로 합치면 된다.

ImageProc.cpp

void ImageProc::CreateHistogram(unsigned char* image_input,
	unsigned char* histo_output, const int width, const int height,
	const int histo_width, const int histo_height)
{
	unsigned char* img_R = new unsigned char[width*height];
	unsigned char* img_G = new unsigned char[width*height];
	unsigned char* img_B = new unsigned char[width*height];

	SplitChannels_ColorToRGB(img_R, img_G, img_B, image_input, width, height);
	
	unsigned char* histo_R = new unsigned char[histo_width*histo_height];
	unsigned char* histo_G = new unsigned char[histo_width*histo_height];
	unsigned char* histo_B = new unsigned char[histo_width*histo_height];

	SplitChannels_ColorToRGB(histo_R, histo_G, histo_B, histo_output, histo_width, histo_height);

	CreateHistogramSingleChannel(img_R, histo_R, width, height, histo_width, histo_height);
	CreateHistogramSingleChannel(img_G, histo_G, width, height, histo_width, histo_height);
	CreateHistogramSingleChannel(img_B, histo_B, width, height, histo_width, histo_height);

	MergeChannels_RGBToColor(histo_R, histo_G, histo_B, histo_output, histo_width, histo_height);

	delete[] img_R;
	delete[] img_G;
	delete[] img_B;
	delete[] histo_R;
	delete[] histo_G;
	delete[] histo_B;
}

이제 이벤트처리기를 달아보자.

ImageProcessingDoc.cpp

void CImageProcessingDoc::OnHistogramCreatehistogram()
{
	// TODO: 여기에 명령 처리기 코드를 추가합니다.
    // histogram 너비는 512, 높이는 이미지 사이즈와 같이 한다.
	const int histo_width = 512;
	const int histo_height = m_Images[cur_index].height;

    // histogram 을 4byte의 칼라이미지 버퍼로 만든다.
	histogram = new unsigned char[histo_width * histo_height * 4];
	memset(histogram, 0, sizeof(unsigned char)*histo_width*histo_height * 4);

    // 기능을 적용한다.
	ImageProc::CreateHistogram(m_Images[cur_index].image_color, histogram,
		m_Images[cur_index].width, m_Images[cur_index].height, histo_width, histo_height);

	CImageProcessingView* pView = (CImageProcessingView*)((CMainFrame*)(AfxGetApp()->m_pMainWnd))->GetActiveView();

    // 아래 함수는 새로 정의한다.
    // image + histogram 의 너비 만큼 그려주는 함수이다. 아래에서 정의하겠다.
	pView->SetDrawImageWithHistogram(m_Images[cur_index].image_color, histogram,
		m_Images[cur_index].width, m_Images[cur_index].height,histo_width, histo_height);

	pView->OnInitialUpdate();
}

historgram을 화면에 그려주는 함수를 짜보자. ImageProcessingView.cpp 에 정의하는 함수이다.

ImageProcessingView.cpp

void CImageProcessingView::SetDrawImageWithHistogram(unsigned char* image, unsigned char* histogram,
	const int width, const int height, const int histo_width, const int histo_height)
{
    // image 가 그려질 공간은 왼쪽 image width + 오른쪽 histogram width 이다.
	m_ImgWidth = width + histo_width;
	m_ImgHeight = height;

	if (m_Image)
		delete[] m_Image;

	// 화면을 histogram 너비 만큼 써야 하기 때문에 +histo_width 를 해준다.
	m_Image = new unsigned char[(width + histo_width) * height * 4];

    // 왼쪽 이미지를 그린다.
	for (int i = 0; i<width; i++)
	{
		for (int j = 0; j < height; j++)
		{
			m_Image[(m_ImgWidth*j + i) * 4 + 0] = image[(width*j + i) * 4 + 0];
			m_Image[(m_ImgWidth*j + i) * 4 + 1] = image[(width*j + i) * 4 + 1];
			m_Image[(m_ImgWidth*j + i) * 4 + 2] = image[(width*j + i) * 4 + 2];
		}
	}

    // 오른쪽 히스토그램을 그린다.
	for (int i = 0; i<histo_width; i++)
	{
		for (int j = 0; j < histo_height; j++)
		{
			m_Image[(m_ImgWidth*j + i + width) * 4 + 0] = histogram[(histo_width*j + i)*4 + 0];
			m_Image[(m_ImgWidth*j + i + width) * 4 + 1] = histogram[(histo_width*j + i)*4 + 1];
			m_Image[(m_ImgWidth*j + i + width) * 4 + 2] = histogram[(histo_width*j + i)*4 + 2];
		}
	}
	
}

실행하면 다음과 같이 이미지가 처리된다. histogram-hijy