해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.



※ 영상 분할

영상을 주어진 기준에 따라 몇 개의 영역으로 나누는 것을 말한다. 영상 분할은 물체 인식, 제스처 인식, 검사 장비에서 이상 영역 검출, 사물/사람 인식 등에 널리 쓰인다. 여기서는 기초적인 영상 분할 기법인 픽셀 값 기반의 영상 분할 기법을 알아볼 것이다.



※ 이진화를 이용한 영상 분할

이진화는 회색조 영상에서 특정 밝기값을 기준으로 더 밝은 영역과 어두운 영역을 나누는 것이다. 단순히 if문을 사용하여 각 픽셀 값을 기준 값과 비교한다. 밑에 있는 코드가 이진화를 이용한 영상 분할 코드다.

void Binarization(const CByteImage& imageIn, CByteImage& imageOut, int nThreshold) { ASSERT(imageIn.GetChannel()==1); int nWidth = imageIn.GetWidth(); int nHeight = imageIn.GetHeight(); for (int r=0 ; r<nHeight ; r++) { BYTE* pIn = imageIn.GetPtr(r); BYTE* pOut = imageOut.GetPtr(r); for (int c=0 ; c<nWidth ; c++) { if (pIn[c] > nThreshold) pOut[c] = 255; else pOut[c] = 0; } } }

영상의 각 픽셀 영역을 돌면서 if문을 사용해 nThreshold 즉, 문턱값을 기준으로 픽셀값을 비교한다. 단 위 메서드는 회색조 영상에서만 이진화를 수행할 수 있기 때문에 컬러 영상의 경우 회색조 영상으로 변환해야 한다. 아래 그림이 결과 영상이다. 결과 영상을 보면 문턱값에 따라서 영상의 분할 정도가 다르다. 따라서 적절한 문턱값으로 설정해야 한다. 다음은 적절한 문턱값을 찾기 위한 방법을 알아보자.


 



※ 이진화를 수행하는 문턱값 자동 조절하기

회색조 영상에서 최적의 문턱값을 찾는 방법은 영상의 밝기값 분포를 이용하는 것이다.


위 그림은 손과 배경화면에 해당하는 히스토그램이다. 위 히스토그램에서 초록색 화살표가 가리키는 밝기값 즉, 히스토그램의 분포가 드문 밝기값을 기준으로 이진화를 수행한다면 손과 배경화면이 잘 분할될 것이라 예상할 수 있다. 초록색 화살표가 가리키는 밝기값을 구하려면 두 영역의 평균 밝기의 중간값을 구하면 된다. 각 영역의 평균 밝기는 (영역 내 픽셀의 밝기값 합) / (영역 내 픽셀 수) 이다. 두 영역의 평균 밝기의 중간값을 구하면 그 중간값을 가지고 이진화를 수행한다. 그러면 다시 두 영역이 나오게 되고 두 영역의 평균 밝기 중간값이 새롭게 나온다. 구한 중간값을 가지고 또 이진화를 수행한다. 이진화는 중간값을 기준으로 픽셀의 변화가 없을 때까지 수행한다. 다음은 이에 관한 순서도다.


위 순서도를 구현한 코드는 다음과 같다.


int BinarizationAuto(const CByteImage& imageIn, CByteImage& imageOut, int nThreshold)
{
	ASSERT(imageIn.GetChannel()==1);

	int nWidth  = imageIn.GetWidth();
	int nHeight = imageIn.GetHeight();

	unsigned int nSumHi, nSumLo; // 두 영역 밝기의 합
	unsigned int nNumHi, nNumLo; // 두 영역의 픽셀 수
	bool bChanged = true;

	while (bChanged)
	{
		bChanged = false;
		nSumHi = nSumLo = 0; // 합 초기화
		nNumHi = nNumLo = 0; // 개수 초기화

		for (int r=0 ; r<nHeight ; r++)
		{
			BYTE *pIn  = imageIn.GetPtr(r);
			BYTE *pOut = imageOut.GetPtr(r);

			for (int c=0 ; c<nWidth ; c++)
			{
				if (pIn[c] > nThreshold)
				{
					nSumHi += pIn[c]; // 밝기 합 더하기
					nNumHi++;		  // 픽셀 수 증가	
					if (!pOut[c]) // 이전에 for문에서 문턱값 이하
						bChanged = true; // 분할에 변화 있음
					pOut[c] = 255;
				}
				else
				{
					nSumLo += pIn[c]; // 밝기 합 더하기
					nNumLo++;		  // 픽셀 수 증가
					if (pOut[c])  // 이전에 for문에서 문턱값 초과
						bChanged = true; // 분할에 변화 있음
					pOut[c] = 0;
				}
			}
		}

		if (!nNumHi) //nNumHi가 0일 경우를 대비
			nThreshold = nSumLo/(double)nNumLo;
		else if (!nNumLo)
			nThreshold = nSumHi/(double)nNumHi;
		else
			nThreshold = (nSumHi/(double)nNumHi + nSumLo/(double)nNumLo) / 2;
	} // while (bChanged)

	return nThreshold; // 최종 문턱값
}

위 메서드는 매개 변수로 주는 초기 문턱값과 상관없이 항상 같은 최종 문턱값을 얻는다. 단, 초기 문턱값은 자동 조절 기능이 수렴하는 시간에 영향을 줄 수 있다.

+ Recent posts