.BandPass Filter란?
Band Pass Filter란 말 그대로 특정 영역의 주파수만 통과시키는 필터이다.
우리는 흔히 LPF HPF로 부르는 Low pass filter와 High pass filter의 혼합인데
영상에서 주파수란 흔히 변화량이라고 생각하면 되겠다.
예를 들어 바다가 넓게 펼쳐져 있는 이미지의 경우 파란색의 반복이라 주파수가 좀 낮은 영역에 해당할 것이고 사람의 머리카락을 찍은 사진의 경우 해상도가 높다면 머리카락 하나하나가 다 보일것이기 때문에 주파수가 높은 편에 속할 것이다. 또한 한 이미지안에 픽셀별로 주파수는 다르다.
Low pass filter는 낮은 주파수 영역대만 통과시키고 높은 주파수 영역은 차단시킨다. 즉 상대적으로 영상에서 밋밋한 부분만을 나타내는 것이다.
High pass filter는 높은 주파수 영역대만 통과시키므로 낮은 주파수 영역은 차단시킨다. 즉 상대적으로 영상에서 변화량이 많은 부분을 나타내는 것이다.
2. C++로 구현한 Band Pass Filter
Mat doBPF(Mat srcImg)
{
Mat complexImg = doDft(srcImg);
Mat centerComplexImg = centralize(complexImg);
Mat magImg = getMagnitude(centerComplexImg);
Mat phaImg = getPhase(centerComplexImg);
//<BPF>
double minVal, maxVal;
Point minLoc, maxLoc;
minMaxLoc(magImg, &minVal, &maxVal, &minLoc, &maxLoc);
normalize(magImg, magImg, 0, 1, NORM_MINMAX);
Mat maskImg = Mat::zeros(magImg.size(), CV_32F);
circle(maskImg, Point(maskImg.cols / 2, maskImg.rows / 2), 100, Scalar::all(1), -1, -1, 0);
circle(maskImg, Point(maskImg.cols / 2, maskImg.rows / 2), 20, Scalar::all(0), -1, -1, 0);
Mat magImg2;
multiply(magImg, maskImg, magImg2);
imshow("BPFmask", magImg2);
//<IDFT>
normalize(magImg2, magImg2, (float)minVal, (float)maxVal, NORM_MINMAX);
Mat complexImg2 = setComplex(magImg2, phaImg);
Mat dstImg = doIdft(complexImg2);
return myNormalize(dstImg);
}
코드를 살펴보겠다.
Band Pass Filter 함수에서 처음으로 실행하는 함수는 doDft함수이다.
Mat doDft(Mat srcImg) // discrete fourier transform
{
Mat floatImg;
srcImg.convertTo(floatImg, CV_32F);
Mat complexImg;
dft(floatImg, complexImg, DFT_COMPLEX_OUTPUT);
return complexImg;
}
이는 discrete한 이미지를 주파수 영역으로 변환시켜주는 fourier transform을 수행하는 함수이다. 우리는 주파수 domain에서 control할 것이기 때문에 우선 fourier transform을 통해 이미지를 주파수의 형태로 변환해주는 것이다.
3. 고주파 성분 중앙정렬
이렇게 주파수 영역으로 변환된 이미지는 각 이미지마다 주파수 성분이 다르기때문에 퓨리에 변환의 결과도 다르게 나타난다. 따라서 중앙으로 모아주는 과정을 실행해야하는데 정규화 과정 중 가장 첫 번째가 바로 centralization 즉, 중앙에 모으는 것이다. 말하자면 정사각형 모양의 이미지를 4등분 한 후 4등분한 것을 다시 조합해서 고주파 성분은 가운데로 모으고 저주파 성분을 외곽으로 만들어주는 과정이다.
이 과정을 c++코드로 구현하면 다음과 같다.
Mat centralize(Mat complex)
{
Mat planes[2];
split(complex, planes);
int cx = planes[0].cols / 2;
int cy = planes[1].rows / 2;
Mat q0Re(planes[0], Rect(0, 0, cx, cy));
Mat q1Re(planes[0], Rect(cx, 0, cx, cy));
Mat q2Re(planes[0], Rect(0, cy, cx, cy));
Mat q3Re(planes[0], Rect(cx, cy, cx, cy));
Mat tmp;// 임시 저장
q0Re.copyTo(tmp);
q3Re.copyTo(q0Re);
tmp.copyTo(q3Re);
q1Re.copyTo(tmp);
q2Re.copyTo(q1Re);
tmp.copyTo(q2Re);
Mat q0Im(planes[1], Rect(0, 0, cx, cy));
Mat q1Im(planes[1], Rect(cx, 0, cx, cy));
Mat q2Im(planes[1], Rect(0, cy, cx, cy));
Mat q3Im(planes[1], Rect(cx, cy, cx, cy));
q0Im.copyTo(tmp);
q3Im.copyTo(q0Im);
tmp.copyTo(q3Im);
q1Im.copyTo(tmp);
q2Im.copyTo(q1Im);
tmp.copyTo(q2Im);
Mat centerComplex;
merge(planes, 2, centerComplex);
return centerComplex;
} // 좌표계 중앙 이동;
그 후 퓨리에 변환과 중앙 정렬을 해준 이미지에서 magnitude와 phase를 각각 얻어준다.
3. 행렬의 최대 최소값 얻어내기
이 과정은 opencv에서 기본적으로 제공해주는 함수인 minMaxLoc()함수를 사용하면 된다.
이 함수의 사용법은 위치를 나타낼 수 있는 포인터 변수 2개를 설정해주고 최대값과 최솟값 정보를 저장할 수 있는 double형 변수 2개를 설정해주면 된다.
그 후 minMaxLoc( 얻고자 하는 행렬, 최솟값, 최대값, 최솟값 위치, 최대값 위치)
이런식으로 얻어주면 된다.
이렇게 최대값과 최솟값을 얻어내는 이유는 정규화를 하기 위함이다.
4. 정규화
정규화를 하는 이유는 각 데이터 셋에 따라 최솟값과 최대값의 격차가 다르기 때문이다. 1부터 10000까지의 데이터 셋과 1부터 0까지의 데이터 셋이 있고 이 두 데이터 셋에 모두 영향을 받는 결과가 있다면 전자의 데이터 셋에 더 영향을 많이 받을 것이기 때문이다. 따라서 같은 범위인 0 ~ 1로 두 데이터 셋 모두 정규화를 진행해주는 것이다.
opencv에서는 정규화를 위해 normalize라는 함수를 제공한다. 이 함수에는 4가지의 파라미터가 필요한데
normalize(정규화할 행렬, 정규화 된 행렬, 정규화범위의 최솟값, 정규화 범위의 최댓값, 정규화 방법)
5. band pass filter
특정 주파수 성분만 보여주고 싶다면 주파수 성분에서 낮은 부분과 높은 부분을 가려주면 된다.
이것은 아까 퓨리에 변환을 통해 변환한 주파수 이미지에서 가운데와 가장자리를 검정색으로 가려주면 된다.
마스크를 통해 가리고 싶은 부분을 가려주면 주파수 성분 이미지는 다음과 같다.
Mat maskImg = Mat::zeros(magImg.size(), CV_32F);
circle(maskImg, Point(maskImg.cols / 2, maskImg.rows / 2), 100, Scalar::all(1), -1, -1, 0);
circle(maskImg, Point(maskImg.cols / 2, maskImg.rows / 2), 20, Scalar::all(0), -1, -1, 0);
Mat magImg2;
multiply(magImg, maskImg, magImg2);
imshow("BPFmask", magImg2);
이 과정은 검정색 동그라미 2개를 만든 후 주파수 성분 이미지에 가려주는 과정이다.
6. 퓨리에 역변환
Mat doIdft(Mat complexImg)
{
Mat idftcvt;
idft(complexImg, idftcvt);
//IDFT를 이용한 원본 영상 취득
Mat planes[2];
split(idftcvt, planes);
Mat dstImg;
magnitude(planes[0], planes[1], dstImg);
normalize(dstImg, dstImg, 255, 0, NORM_MINMAX);
dstImg.convertTo(dstImg, CV_8UC1);
return dstImg;
}
그 후 다음과 같이 퓨리에 역변환을 통해 밴드패스필터를 통과한 주파수 이미지를 다시 원상태로 되돌리면 다음과 같아진다.
왼쪽이 bandPass Filter를 통과한 모습 오른쪽이 원본 이미지다.
'open-cv 영상처리' 카테고리의 다른 글
opencv c++ sobel filter (0) | 2022.03.23 |
---|---|
opencv c++ 가우시안 필터 (0) | 2022.03.23 |
opencv 사진 합성 (0) | 2021.06.18 |
opencv spread salt (0) | 2021.06.18 |
댓글