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



※ 주파수 스펙트럼과 위상각의 시각화

위 그림은 고속 푸리에 변환으로 얻은 주파수 변환의 출력 영상인데 영상의 주파수 성분에 대해 직관적으로 분석하기는 어렵다. 그 이유로는 두 가지 이유가 있다.

1. 주파수 공간의 위치 (k,l)에 대하여 주파수 변환 결과값 G(k,l)은 실수부와 허수부로 나뉘어 있다. 어떤 위치의 주파수 성분이 강한지를 알려면 실수부와 허수부를 모두 이용하여 분석해야 한다. 

2. 주파수 변환 영상에서 저주파 성분에 해당하는 부분이 영상의 가장자리에 있다. 일반적인 영상은 저주파 성분이 큰 비중을 차지하는 데 영상의 가장자리에 있으면 눈으로 확인하기 어렵다.

1번 문제를 해결하기 위해서는 복소수로 된 주파수 변환 결과의 절대값을 살펴보는 방법을 많이 사용한다. 2번 문제는 주파수 변환 결과 영상을 저주파 성분이 영상 가운데에 있도록 평행이동을 수행하여 관측한다. 먼저 2번 문제를 해결해 보자. 다음 그림과 같이 영상의 가장자리에 있는 저주파 성분을 영상 가운데로 오게 하면 된다. 반대로 고주파 성분은 영상의 가장자리로 옮겨진다.

 다음 코드는 저주파 성분이 영상 가운데로 모이도록 평행이동 하는 코드이다. 영역별로 평행이동하기 때문에 픽셀 단위로 이동하기보다는 한 번에 많은 픽셀을 복사할 수 있도록 memcpy() 메서드를 사용했다.


CDoubleImage FFTShift(const CDoubleImage& src)
{
	int nWidth = src.GetWidth();
	int nHeight = src.GetHeight();

	CDoubleImage dst(nWidth, nHeight, 2); // 결과 영상
	double* pSrc = src.GetPtr();
	double* pDst = dst.GetPtr();

	int nWStep = nWidth*2;    // 두 배를 한 이유는 실수부, 복수부 2채널로 되있기 때문이다.
	int nHalfY = nHeight/2;
	for (int r=0 ; r<nHalfY ; r++)
	{
		int LT = r*nWStep;			// 1번 영역
		int RT = LT+nWidth;			// 2번 영역
		int LB = (nHalfY+r)*nWStep;	// 3번 영역
		int RB = LB+nWidth;			// 4번 영역

		memcpy(&pDst[RB], &pSrc[LT], nWidth*sizeof(double)); // 1 -> 4
		memcpy(&pDst[LB], &pSrc[RT], nWidth*sizeof(double)); // 2 -> 3
		memcpy(&pDst[RT], &pSrc[LB], nWidth*sizeof(double)); // 3 -> 2
		memcpy(&pDst[LT], &pSrc[RB], nWidth*sizeof(double)); // 4 -> 1
	}

	return dst;
}


1번 문제를 해결하기 위해서는 주파수 성분의 절대값을 구해야한다. a+bi가 있을 때 이 복소수의 절대값은 

 이다. 주파수 변환 결과의 실수부 영상과 허수부 영상에 대한 절대값도 마찬가지로 구한다. 이렇게 해서 주파수 변환 결과의 절대값을 시각화하여 나타낸 것을 주파수 스펙트럼이라고 한다. 주파수 스펙트럼을 만들 때 저주파 성분이 매우 큰 비중을 차지해 저주파 성분과 고주파 성분의 편차가 심하게 나타난다. 그래서 절댓값을 시각화할 때 로그 함수를 사용해 편차를 줄이다. 원래 절대값을 |G(k,l)| 이라고 표현하면 로그 함수에 의해 변형된 대값 m(k,l)은 다음과 같다. 



또한 위 식을 사용할 경우 영상 전체에 걸쳐 값이 작게 나타날 수 있기 때문에 눈에 잘 보이게 절대값의 최댓값이 255가 되도록 비율을 조절할 수도 있다. 다음 메서드는 이러한 비율 조절을 포함한 주파수 변환 결과의 절대값 영상 계산 함수이다. 첫 번째 매개변수는 복소수로 된 주파수 변환 결과 영상이고, 두 번째 매개변수는 비율을 조절할지 설정하는 변수이다.



CDoubleImage GetMagImage(const CDoubleImage& imgFreq, bool bScaled)
{
	int nWidth  = imgFreq.GetWidth();
	int nHeight = imgFreq.GetHeight();

	CDoubleImage imgMag(nWidth, nHeight);

	double maxMag = 0; // 최대값 기억을 위한 변수

	for (int r=0 ; r<nHeight ; r++)
	{
		double* pFrq = imgFreq.GetPtr(r);
		double* pMag = imgMag.GetPtr(r);

		int idx = 0;
		for (int c=0 ; c<nWidth ; c++)
		{
			pMag[c] = log(sqrt(pFrq[idx]*pFrq[idx] + pFrq[idx+1]*pFrq[idx+1])+1);
			if (pMag[c] > maxMag)
			{
				maxMag = pMag[c];
			}
			idx += 2;
		}
	}
	if (bScaled) // 최댓값 255로 비율 조절
		return imgMag*(255.0/maxMag);
	else
		return imgMag;
}


주파수 결과 영상에 대해서 위상각도 얻을 수 있다. 위상각은 절대값에 비해서 눈으로 의미를 확인할 수 있는 정도가 크지는 않지만 복소수로 얻어지는 주파수 변환 결과의 중요한 부분이다. 복소수 a+bi에 대해서 위상각은 arctan(b/a)로 구해진다. 위상각은 -180도에서 180도 사이의 값이므로 절대값과 마찬가지로 시각화하려면 모든 주파수 공간에서 위상각이 0에서 255사이가 되도록 비율을 조절할 수 있어야 한다. 다음 메서드는 위상각 영상 생성 함수이다.

CDoubleImage GetPhsImage(const CDoubleImage& imgFreq, bool bScaled)
{
	int nWidth  = imgFreq.GetWidth();
	int nHeight = imgFreq.GetHeight();

	CDoubleImage imgPhs(nWidth, nHeight);

	for (int r=0 ; r<nHeight ; r++)
	{
		double* pFrq = imgFreq.GetPtr(r);
		double* pPhs = imgPhs.GetPtr(r);

		int idx = 0;
		for (int c=0 ; c<nWidth ; c++)
		{
			pPhs[c] = atan2(pFrq[idx+1], pFrq[idx]);
			idx += 2;
		}
	}
	if (bScaled)
		return imgPhs*(127/PI)+128; 
               //atan2의 반환 값이 -PI ~ PI이다. 거기에 127을 곱하면 -127 ~ 127이 된다. 우리는 0에서 255까지의                 
              //범위를 원하기 때문에 128을 더해준다.  
	else
		return imgPhs;
}

결과적으로 푸리에 변환 결과 영상에 대해서 2번 문제의 해결을 위해 평행이동을 하고 1번 문제 해결을 위해 절대값을 구하면 다음 그림과 같은 결과가 나온다.



+ Recent posts