728x90
반응형


OpenCV를 이용한 적응적 경계화 (Adaptive Thresholding)





적분 영상으로 화소 개수 세기



영상 안의 여러 관심 영역을 대상으로 히스토그램 같은 여러 가지를 계산 해야한다고 가정하자, 이런 모든 계산에 드는 비용은 급격히 늘어 날 수 있는데, 이러한 문제점을 해결해 줄 수 있는 것이 바로 적분 영상(Integral Image)이다.


적분 영상은 영상 부분 영역에 걸친 화소를 셀 때의 효율성을 극적으로 개선할 수 있는 도구이다.


또한, 적분영상은 각 화소를 해당 화소로 범위를 정해진 왼쪽 상단 사분면의 내부에 위치한 모든 화소의 합으로 바꿈으로써 적분영상을 얻을 수 있다. 현재 화소의 적분 값은 앞에 설명했던 화소의 적분 값을 더한 현재 줄의 누적 합인 값이기 때문에 한 번만 영상을 조회해 적분 영상을 계산할 수 있다. 따라서 적분 영상은 화소 합을 포함하는 새로운 영상이다.


적분 영상은 오버플로우를 방지하기 위해 보통 int 값(CV_23S)의 영상이나 float(CV_32F)으로 한다. 이 적분 영상은 매번 수행해야 하는 여러 화소 합산에 사용된다. 바로 '적응적 경계화(Adapticve Thresholding)' 개념 소개로 적분 영상의 유용함을 입증할 수 있다.




다음과 같은 책 영상이 있다고 하자.





이러한 영상을 가지고 고정된 경계 값을 사용하여 이진화 하게 될 경우,  부분부분이 시커멓게 보여 글자를 식별 할 수 없게 된다. 이럴 때 적응적 경계화를 실시하여 글자를 식별하도록 한다.


각 화소의 이웃으로 계산하는 지역 경계화를 사용하는 것이다. 이러한 것을 적응적 경계화라고 일컫으며, 각 화소를 이웃 화소의 평균값과 비교해서 구성한다. 화소가 지역 평균값과 두드러지게 차이가 날 경우 이상치(Outlier)로 간주한 후 경계화 과정에서 잘라낸다. 그로인해 적응적 경계화에서는 모든 화소 주변의 지역 평균 계산이 필요하다. 적분 영상을 통해 필요한 여러 영상 윈도우 합산을 효율적으로 계산할 수 있다.




적분 영상을 계산하여 적응적 경계화를 적용하는 코드는 아래와 같으며, 결과화면도 아래와 같다.



 

#include <iostream>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>


using namespace cv;
using namespace std;

int main()
{
	Mat image = imread("image/book.jpg",0);
	if (!image.data) return 0;

	resize(image, image, Size(), 0.6, 0.6);
	namedWindow("original image");
	imshow("original image", image);

	// 고정된 경계 값 사용
	Mat binaryFixed;
	Mat binaryAdaptive;
	threshold(image, binaryFixed, 160, 255, THRESH_BINARY);


	
	/*
		영상의 일부에서 텍스트가 누락되는 현상을 보임
		이러한 문제를 극복하기 위해서
		각 화소의 이웃으로 계산하는 지역 경계화를 사용
		즉, 적응적 경계화(Adaptive Threshold)를 적용
	*/
	cv::namedWindow("Fixed Threshold");
	cv::imshow("Fixed Threshold", binaryFixed);

	

	

	/* 
						적분 영상 계산 
		모든 화소를 순회하면서 정방형 이웃의 평균을 계산
	*/
	
	
	
	/*			함수를 이용하여 적응적 경계화			*/

	int blockSize = 21; // 이웃 크기
	int threshold = 10; //화소를 (평균-경계 값)과 비교


	adaptiveThreshold(image, // 입력영상
		binaryAdaptive, // 이진화 결과 영상
		255, // 최대 화소 값 
		ADAPTIVE_THRESH_MEAN_C, // Adaptive 함수
		THRESH_BINARY, // 이진화 타입
		blockSize,  // 이웃크기
		threshold); // threshold used

	cv::namedWindow("Adaptive Threshold");
	cv::imshow("Adaptive Threshold", binaryAdaptive);



	/*			함수를 이용하지 않고 직접 구현		*/

	Mat binary = image.clone();

	int nl = binary.rows; // number of lines
	int nc = binary.cols; // total number of elements per line

	Mat iimage;
	integral(image, iimage, CV_32S);

	/*
		모든 화소를 순회하면서 정방형 이웃의 평균을 계산
		포인터를 이용하여 영상을 순회
	*/
	int halfSize = blockSize / 2;
	for (int j = halfSize; j<nl - halfSize - 1; j++) {

		// j행의 주소 얻기
		uchar* data = binary.ptr<uchar>(j);
		int* idata1 = iimage.ptr<int>(j - halfSize);
		int* idata2 = iimage.ptr<int>(j + halfSize + 1);

		// 행의 각 화소
		for (int i = halfSize; i<nc - halfSize - 1; i++) {

			// 합 계산
			int sum = (idata2[i + halfSize + 1] - idata2[i - halfSize] -
				idata1[i + halfSize + 1] + idata1[i - halfSize]) / (blockSize*blockSize);

			// 적응적 경계화 적용
			if (data[i]<(sum - threshold))
				data[i] = 0;
			else
				data[i] = 255;
		}
	}

	cv::namedWindow("Adaptive Threshold (integral)");
	cv::imshow("Adaptive Threshold (integral)", binary);






	/*			영상 연산자를 이용하여 적응적 경계화		*/

	Mat filtered;
	Mat binaryFiltered;
	boxFilter(image, filtered, CV_8U, Size(blockSize, blockSize));
	filtered = filtered - threshold;
	binaryFiltered = image >= filtered;

	namedWindow("Adaptive Threshold (filtered)");
	imshow("Adaptive Threshold (filtered)", binaryFiltered);

	waitKey(0);

	return 0;

}







728x90
반응형