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



※ 주파수 영역을 이용한 영상 필터

주파수 영역 변환을 이용하면 영상 필터링을 매우 효과적으로 수행할 수 있다. 주파수 영역에서의 필터는 주파수 변환 결과 영상에서 원하는 부분을 강화하거나 약화하는 것으로 이루어진다. 일반적으로 원하는 성분을 강화하기 보다는 원하지 않는 성분을 걸러내는 방식을 많이 사용한다. 고주파 성분을 길러내는 것을 저주파 통과 필터, 저주파 성분을 걸러내는 것을 고주파 통과 필터라고 한다. 


그림5.


위 그림에서 fc는 차단 주파수라고 하는데 원본 신호의 주파수 성분들 가운데 차단 주파수 값을 기준으로 크거나 작은 주파수 성분이 모두 0으로 바뀌게 된다. 위에 있는 주파수 스펙트럼을 볼 때 저주파 성분은 영상의 가운데에 원 모양으로 몰리게 된다. 해당 원의 반지름을 차단 주파수라고 하자. 저주파 통과 필터를 사용하면 저주파가 모여있는 원 바깥 영역이 차단된다. 고주파 통과 필터를 사용하면 저주파가 모여 있는 원 안쪽 영역이 차단된다. 주파수 스펙트럼은 시각화를 위해서 저주파가 영상의 가운데로 모이도록 평행이동을 시킨 것이다. 만약 주파수 영역을 이용해서 영상 필터를 하고자 할 때 푸리에 변환 결과를 얻고 이에 대해서 시각화를 위해 주파수 스펙트럼을 만드는 것은 수행시간이 많이 걸린다. 그래서 주파수 스펙트럼 전 즉, 푸리에 변환 결과 영상을 놓고 고주파 통과 필터와 저주파 통과 필터를 사용하게 된다면 다음과 같다. 고주파 통과 필터를 사용하면 저주파를 차단해야 한다. 저주파는 영상의 가장자리에 원의 1/4 부채꼴 모양으로 있게 된다. 따라서 코드를 구현할 때 고주파 통과 필터는 영상의 가장자리를 차단시키면 된다. 저주파 통과 필터는 반대다. 고주파를 차단해야 하므로 저주파가 있는 곳 영상의 가장자리가 아닌 곳을 차단 시키면 된다. 다음은 이에 관한 저주파 및 고주파 통과 필터를 영상에 접목시킨 메서드이다.





void HighFrequencyFilter(){
	CDoubleImage filteredF = m_imgFreq; // 원본 주파수 변환을 복사

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

	double* pFrq = filteredF.GetPtr();

	double dRadius2 = m_dCutoff*m_dCutoff; // m_dCutoff : 차단 주파수(원의 반지름)
	int nWidth2 = nWidth*2;          // 실수부, 허수부 채널 두 개

	for (int r=0 ; r<m_dCutoff ; r++)
	{
		for (int c=0 ; c<m_dCutoff ; c++)
		{
			if (r*r+c*c < dRadius2)
			{
				pFrq[nWidth2*(			r) + 2*(		 c)] = 
				pFrq[nWidth2*(			r) + 2*(nWidth-1-c)] = 
				pFrq[nWidth2*(nHeight-1-r) + 2*(		 c)] = 
				pFrq[nWidth2*(nHeight-1-r) + 2*(nWidth-1-c)] = 
				pFrq[nWidth2*(			r) + 2*(		 c)+1] = 
				pFrq[nWidth2*(			r) + 2*(nWidth-1-c)+1] = 
				pFrq[nWidth2*(nHeight-1-r) + 2*(		 c)+1] = 
				pFrq[nWidth2*(nHeight-1-r) + 2*(nWidth-1-c)+1] = 0;
			}
		}
	}

	CDoubleImage freqShft = FFTShift(filteredF);  // 주파수 변환 결과의 시각화를 위해서 평행이동
	CDoubleImage imgMag = GetMagImage(freqShft, true);  
                // 최종적으로 절대값을 구해 주파수 스펙트럼 도출  
	ShowImage(imgMag, "필터링된 주파수 스펙트럼 영상");

	m_imgOut = (IFFT(filteredF).GetChannelImg(0)); // 실수부만 뽑아냄

	ShowImage(m_imgOut, "고주파 통과 필터 결과");
        
	CByteImage newmg = imgMag;
	newmg.SaveImage("HPFmag.bmp");
	m_imgOut.SaveImage("HPFres.bmp");
}

위 메서드는 저주파 통과 필터 처리 함수이다. 

void LowFrequencyFilter()
{
        CDoubleImage filteredF = m_imgFreq; // 원본 주파수 변환을 복사

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

	double* pFrq = filteredF.GetPtr();

	double dRadius2 = m_dCutoff*m_dCutoff;
	int nWidth2  = nWidth*2;
	int nHalf = nWidth/2;

	for (int r=0 ; r<=nHalf ; r++)
	{
		for (int c=0 ; c<=nHalf ; c++)
		{
			if (r*r+c*c > dRadius2)
			{
				pFrq[nWidth2*(			r) + 2*(		 c)] = 
				pFrq[nWidth2*(			r) + 2*(nWidth-1-c)] = 
				pFrq[nWidth2*(nHeight-1-r) + 2*(		 c)] = 
				pFrq[nWidth2*(nHeight-1-r) + 2*(nWidth-1-c)] = 
				pFrq[nWidth2*(			r) + 2*(		 c)+1] = 
				pFrq[nWidth2*(			r) + 2*(nWidth-1-c)+1] = 
				pFrq[nWidth2*(nHeight-1-r) + 2*(		 c)+1] = 
				pFrq[nWidth2*(nHeight-1-r) + 2*(nWidth-1-c)+1] = 0;
			}
		}
	}

	CDoubleImage freqShft = FFTShift(filteredF);
	CDoubleImage imgMag = GetMagImage(freqShft, true);
	ShowImage(imgMag, "필터링된 주파수 스펙트럼 영상");

	m_imgOut = (IFFT(filteredF).GetChannelImg(0)); // 실수부만 뽑아냄

	ShowImage(m_imgOut, "저주파 통과 필터 결과");

	CByteImage newmg = imgMag;
	newmg.SaveImage("LPFmag.bmp");
	m_imgOut.SaveImage("LPFres.bmp");
}

위 메서드는 저주파 통과 필터 처리 메서드이다. for문을 보면 고주파 통과 필터와 달리 영상의 모든 범위를 순회한다. 고주파 통과 필터의 경우 원 영역 내부만 0으로 할당하기 때문에 for문을 원 안을 순회하도록 했다. 하지만 저주파 통과 필터의 경우 영상의 원 영역을 제외한 나머지 부분이기 때문에 영상 전 범위를 for문을 통해 순회한다.




위 그림은 주파수 영역 필터를 수행한 결과이다. 고주파 통과 필터의 결과 영상에는 고주파 성분인 경계선 정보만 남은 것을 볼 수 있다. 저주파 통과 필터의 결과에는 영상의 경계선이 희미하게 변한 것을 볼 수 있다. 이들 결과는 회선 연산을 이용하여 영상을 강조하거나 잡음을 제거한 결과와 유사하다. 



※ 가우시안 주파수 필터 함수

위에서 주파수 통과 필터를 사용한 결과 영상을 보면 급격하게 영상의 픽셀이 바뀐다. 그 이유는 우리가 위에서 구현한 저주파/고주파 통과 필터가 차단 주파수를 기준으로 주파수 영역을 급격하게 잘라낸 부작용에 의한 것이다. 이렇게 차단 주파수를 기준으로 저주파나 고주파 영역을 남김없이 차단하는 필터를 이상적 고주파/저주파 차단 필터라고 한다. 

이러한 부작용을 제거하려면 고주파나 저주파 영역을 차단할 때 차단 주파수를 기준으로 점차 해당 성분을 차단하게 만들면 된다. 이 문제를 이상적으로 해결하는 방법이 가우스 함수를 사용하여 차단 곡선을 부드럽게 만드는 것으로 이를 가우시안 고주파/저주파 통과필터라고 한다. 아래 그림은 이상적 필터와 가우시안 필터를 그래프 상에 나타낸 것이다.



가우시안 저주파 통과 필터에서는 원본 주파수 성분이 직류 성분과의 거리에 따라 가우스 함수값을 곱한 값으로 변환되는데 가우스 함수값은 다음과 같다.


d는 직류 성분 위치로부터의 거리이고 fc는 차단 주파수이다. 가우시안 저주파 통과 필터는 w 값을 곱하면 되고 가우시안 고주파 통과 필터는 1-w 값을 곱하면 된다.



void GHighFrequencyFilter()
{
	CDoubleImage filteredF = m_imgFreq; // 원본 주파수 변환을 복사

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

	double* pFrq = filteredF.GetPtr();

	double dRadius2 = m_dCutoff*m_dCutoff;
	double dDenom = 1.0 / (2*dRadius2);
	int nWidth2 = nWidth*2;
	int nHalf = nWidth/2;

	for (int r=0 ; r<=nHalf ; r++)
	{
		for (int c=0 ; c<=nHalf ; c++)
		{
			double w = 1.0 - exp(-(r*r+c*c)*dDenom);
			pFrq[nWidth2*(			r) + 2*(		 c)] *= w;
			pFrq[nWidth2*(			r) + 2*(nWidth-1-c)] *= w;
			pFrq[nWidth2*(nHeight-1-r) + 2*(		 c)] *= w;
			pFrq[nWidth2*(nHeight-1-r) + 2*(nWidth-1-c)] *= w;
			pFrq[nWidth2*(			r) + 2*(		 c)+1] *= w;
			pFrq[nWidth2*(			r) + 2*(nWidth-1-c)+1] *= w;
			pFrq[nWidth2*(nHeight-1-r) + 2*(		 c)+1] *= w;
			pFrq[nWidth2*(nHeight-1-r) + 2*(nWidth-1-c)+1] *= w;
		}
	}

	CDoubleImage freqShft = FFTShift(filteredF);
	CDoubleImage imgMag = GetMagImage(freqShft, true);
	ShowImage(imgMag, "가우시안 필터링된 주파수 스펙트럼 영상");

	m_imgOut = (IFFT(filteredF).GetChannelImg(0)); // 실수부만 뽑아냄

	ShowImage(m_imgOut, "가우시안 고주파 통과 필터 결과");

	CByteImage newmg = imgMag;
	newmg.SaveImage("GHPFmag.bmp");
	m_imgOut.SaveImage("GHPFres.bmp");
}

void GLowFrequencyFilter()
{
	
	CDoubleImage filteredF = m_imgFreq; // 원본 주파수 변환을 복사

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

	double* pFrq = filteredF.GetPtr();

	double dRadius2 = m_dCutoff*m_dCutoff;
	double dDenom = 1.0 / (2*dRadius2);
	int nWidth2 = nWidth*2;
	int nHalf = nWidth/2;

	for (int r=0 ; r<=nHalf ; r++)
	{
		for (int c=0 ; c<=nHalf ; c++)
		{
			double w = exp(-(r*r+c*c)*dDenom);
			pFrq[nWidth2*(			r) + 2*(		 c)] *= w;
			pFrq[nWidth2*(			r) + 2*(nWidth-1-c)] *= w;
			pFrq[nWidth2*(nHeight-1-r) + 2*(		 c)] *= w;
			pFrq[nWidth2*(nHeight-1-r) + 2*(nWidth-1-c)] *= w;
			pFrq[nWidth2*(			r) + 2*(		 c)+1] *= w;
			pFrq[nWidth2*(			r) + 2*(nWidth-1-c)+1] *= w;
			pFrq[nWidth2*(nHeight-1-r) + 2*(		 c)+1] *= w;
			pFrq[nWidth2*(nHeight-1-r) + 2*(nWidth-1-c)+1] *= w;
		}
	}

	CDoubleImage freqShft = FFTShift(filteredF);
	CDoubleImage imgMag = GetMagImage(freqShft, true);
	ShowImage(imgMag, "가우시안 필터링된 주파수 스펙트럼 영상");

	m_imgOut = (IFFT(filteredF).GetChannelImg(0)); // 실수부만 뽑아냄

	ShowImage(m_imgOut, "가우시안 저주파 통과 필터 결과");

	CByteImage newmg = imgMag;
	newmg.SaveImage("GLPFmag.bmp");
	m_imgOut.SaveImage("GLPFres.bmp");
}



다음은 가우시안 필터를 이용한 결과 영상이다. 이상적 필터를 이용한 결과 영상보다 급격한 변화가 없이 더 자연스러운 결과 영상이 도출된다.



회선 연산과 얼마나 차이가 나는 지 비교해보자. 512*512 크기의 입력 영상과 17*17 크기의 마스크를 사용하는 경우, 주파수 변환을 이용하여 저주파 통과 필터를 수행하면 주파수 변환과 역변환 과정을 포함하여 약 200ms정도 소요된다. 회선 연산을 사용할 시 70ms가 소요된다. 오히려 주파수 변환을 이용한 방법이 더 오래 걸린다. 하지만 주파수 변환은 수행 시간이 마스크 크기에 영향을 받지 않고 가로와 세로 방향으로 회선 연산을 분리할 수 없는 임의의 모양을 가지는 마스크에 대해서도 같은 수행 시간이 소요된다. 알맞게 사용하면 될 것 같다.

+ Recent posts