📔 오늘 공부한 내용

OpenCV 기초를 공부하는 마지막 날이다. 영상 필더링에 대한 내용들을 공부했다.

필터링(Filtering)

영상의 필터링은 영상에서 필요한 정보만 통과시키고, 원치 않는 정보들을 걸러내는 작업을 말한다. 블러링, 샤프닝, 잡음 제거, 에지 검출 등의 처리 등이 필터링 중 하나이다.

주파수 공간에서의 필터링(Frequency domain filtering)

주파수 공간에서의 필터링은 푸리에 변환(Fourier transform)을 이용해 영상을 주파수 공간으로 변환해 필터링을 수행하는 방법을 말한다. 이 경우 수학적인 배경 지식이 필요하고, 생각보다 복잡한 과정이 필요하다.

공간적 필터링(Spatial domain filtering)

공간적 필터링은 영상의 픽셀값을 직접 이용하는 방법으로, 마스크 연산을 사용해서 필터링하는 방법이다. OpenCV에서는 공간적 필터링 마스크 크기가 커질 경우 주파수 공간에서의 필터링을 수행한다.

공간적 필터링에서 가장 중요한 것은 마스크를 정의하는 것이다. 다양한 모양을 지정할 수 있지만, 대부분 3X3 정방형 필터를 사용한다.

보통 3X3 정방형 필터와 Convolution 연산을 수행해 이미지를 필터링 한다.

dst(x,y)=j=02i=02m(i,j)src(x+i1,y+j1)dst(x, y)=\sum^{2}_{j=0}\sum^{2}_{i=0}m(i,j)src(x+i-1, y+j-1)

3x3 마스크 분야의 중간값을 기반으로 Correlation 연산을 수행하게 된다. 하지만, 관용적으로 Convolution이라고 말한다.

필터링을 수행할때, 중앙값을 기반으로 처리하므로, 가장자리의 경우 없는 픽셀에 대한 처리가 필요하다. OpenCV에서는 다음 함수를 통해 가장자리 픽셀 확장을 진행할 수 있다.

void copyMakeBorder(InputArray src, OutputArray dst, int top, int bottom, int left, int right, int borderType, const Scalar& value = Scalar());

# border_type
BORDER_CONSTANT		:000|abcdefgh|000
BORDER_REPLICATE	:aaa|abcdefgh|hhh
BORDER_REFLECT		:cba|abcdefgh|hgf
BORDER_REFLECT_101	:dcb|abcdefgh|gfe
BORDER_REFLECT101	:BORDER_REFLECT_101
BORDER_DEFAULT		:BORDER_REFLECT_101

기본적인 2D 필터링 함수는 다음과 같다.

void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor = Point(-1, -1), double delta = 0, int borderType = BORDER_DEFAULT);
--- ddepth: 원하는 결과 영상의 깊이. -1이면 src와 같은 깊이
--- kernel: 필터 마스크 행렬. 1채널 실수형

엠보싱 필터(Embossing Filter)

엠보싱(Embossing)은 올록볼록한 형태의 무늬를 말한다. 대각을 기준으로 위쪽은 0보다 작은값, 아래쪽은 0보다 큰 값을 사용하면 간단하게 엠보싱 효과를 지정해줄 수 있다.

float data[] = { -1, -1, 0, -1, 0, 1, 0, 1, 1 };
Mat emboss(3, 3, CV_32FC1, data);

Mat dst;
filter2D(src, dst, -1, emboss, Point(-1, -1), 128);

평균값 필터(Mean Filter)

평균값 필터(Mean Filter)주변 픽셀 값들의 산술 평균을 계산하고, 이를 출력 영상의 픽셀 값으로 설정하는 것으로 날카로운 에지가 무뎌지고 영상의 잡음이 감소하는 효과를 얻을 수 있다.

float data[] = {
		1 / 9.f, 1 / 9.f, 1 / 9.f,
		1 / 9.f, 1 / 9.f, 1 / 9.f,
		1 / 9.f, 1 / 9.f, 1 / 9.f
};
Mat kernel(3, 3, CV_32F, data);

// 행렬의 값이 동일할 경우 이처럼 생성도 가능
// Mat kernel = Mat::ones(3, 3, CV_32FC1) / 9.f;

Mat dst;
filter2D(src, dst, -1, data);

평균값 필터링을 이용하면, 블러를 만들 수 있다. OpenCV에서는 블러링 함수를 제공해 쉽게 만들 수 있는 기능을 제공한다.

void blur(InputArray src, OutputArray dst, Size ksize, Point anchor = Point(-1, -1), int borderType = BORDER_DEFAULT);
--- ksize: 평균값 필터 크기

이를 직접 사용해 구현하면 다음과 같이 구현할 수 있다.

Mat dst;
for (int ksize = 3; ksize <= 7; ksize += 2) {
	blur(src, dst, Size(ksize, ksize));

	String desc = format("Mean: %dx%d", ksize, ksize);
	putText(dst, desc, Point(10, 30), FONT_HERSHEY_SIMPLEX, 1.0, 
		Scalar(255), 1, LINE_AA);

	imshow("dst", dst);
	waitKey();
}

#### 가우시안 필터(Gaussian Filter) 평균값 필터링은 NxN 필터의 평균을 이용해 계산하기에, 멀리 떨어져 있는 픽셀들의 영향이 커진다는 단점이 존재한다. 그래서 정규분포를 기반으로 한 가우시한 함수를 사용하는 경우가 많다.
Gμ,σ(x)=12πσe(xμ)22σ2,          μ:mean,  σ:standard divisionG_{\mu, \sigma}(x)=\frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}}, \;\;\;\;\;\mu: \text {mean},\;\sigma: \text{standard division}

행렬을 사용하기 때문에, 2차원 가우시안 함수를 사용해 표현을 많이 한다.

Gμ,σ(x)=12πσex2+y22σ2,      {μx=μy=0σx=σy=σG_{\mu, \sigma}(x)=\frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{x^2+y^2}{2\sigma^2}}, \;\;\; \left\{\begin{matrix} \mu_x=\mu_y=0 \\ \sigma_x=\sigma_y=\sigma \end{matrix}\right.

OpenCV에서는 가우시안 필터링을 위한 함수를 제공해주고 있기에, 위의 수식을 외우고 있을 필요는 없다.

void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, int borderType = BORDER_DEFAULT);
--- ksize: 가우시안 커널 크기로, sigma 값에 의해 자동 결정됨

이를 사용해 가우시안 필터를 적용하는 코드는 다음과 같다. σ\sigma가 커지면, 커널 사이즈가 커지므로 연산 시간도 증가한다.

Mat dst;
for (int sigma = 1; sigma <= 5; sigma++) {
	GaussianBlur(src, dst, Size(0, 0), (double)sigma);

	String desc = format("Sigma = %d", sigma);
	putText(dst, desc, Point(10, 30), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255), 1, LINE_AA);

	imshow(desc, dst);
}
waitKey(0);
destroyAllWindows();

샤프닝(Sharpening)

샤픈(Sharpen)은 영상의 날카로운 정도를 말하는 것으로, 샤프닝(Sharpening)은 날카로운 정도를 조정하는 필터링 중 하나이다. 블러링의 반대라고 볼 수 있다.

언샤프 마스크 필터링(Unsharp Mask Filtering)

Unsharp는 "날카롭지 않은"이라는 뜻으로, 언샤프 마스크 필터링은 날카롭지 않은 영상(블러링된 영상)을 이용해 날카로운 영상을 생성하는 기법이다. 원본의 2배를 만든 뒤, Unsharp Mask를 빼는 방법으로 구현한다.

Mat src=imread("rose.bmp", IMREAD_GRAYSCALE);

Mat blr;
blur(src, blr, Size(3, 3));

// Gaussian Filter를 사용한 Unsharp mask 생성
GaussianBlur(src, blr, Size(), 1.0);

Mat dst = 2 * size - blr;

3x3 행렬의 경우, 각 원소가 블러링 될때는 19\frac{1}{9}로 구성되므로, 다음과 같이 구현할 수 있다.

float sharpen[] = {
		-1 / 9.f, -1 / 9.f, -1 / 9.f,
		-1 / 9.f, 17 / 9.f, -1 / 9.f,
		-1 / 9.f, -1 / 9.f, -1 / 9.f
};
Mat kernel(3, 3, CV_32F, sharpen);

Mat dst;
filter2D(src, dst, -1, kernel);

샤프니스(Sharpeness) 조절을 위한 가중치 α\alpha에 따라 조절하는 함수는 다음과 같다.(g(x,y)g(x, y)는 블러링된 영상을 말함)

h(x,y)=f(x,y)+αg(x,y)h(x, y) = f(x, y) + \alpha \cdot g(x, y)

h(x,y)=f(x,y)+α(f(x,y)f(x,y))h(x, y) = f(x, y) + \alpha (f(x, y) - \overline{f}(x, y))
=(1+α)f(x,y)αf(x,y)\hspace{3.1em}= (1+ \alpha)f(x, y) - \alpha \cdot \overline{f}(x, y)

h(x,y)=(1+α)f(x,y)αGσ(f(x,y))h(x, y) = (1 + \alpha)f(x, y)-\alpha \cdot G_{\sigma}(f(x, y))

이를 코드로 구현하면, 다음과 같이 구현할 수 있다.

Mat blr;
GaussianBlur(srcf, blr, Size(), sigma);

float alpha = 1.0f;
Mat dst = (1.f + alpha) * srcf - alpha * blr;

dst.convertTo(dst, CV_8UC1);

잡음제거 필터

영상의 잡음(Noise)은 픽셀 값에 추가되는 원치 않은 형태의 신호를 말한다. 보통 카메라에서 광학 신호를 전기적 신호로 변환하는 과정(센서)에서 잡음이 추가된다. 영상은 다음과 같은 과정으로 획득된다.

f(x,y)=s(x,y)+n(x,y),      (n:noise)f(x, y) = s(x, y) + n(x, y), \;\;\; (n: \text{noise})

잡음의 종류는 보통 2가지가 존재한다.

Salt & Papper Noise, Gaussian Nois

Salt & Papper Noise는 왼쪽 사진과 같은데, 픽셀 값이 0 또는 255로 바뀌는 노이즈를 이야기 한다. Gaussian Noise는 오른쪽 시간과 같은데, 일부 값이 변형되어 보이는 노이즈이다.

사람이 직접 잡음을 추가할 수도 있다. 가우시안 잡음을 생성하는 방법은 정상분포 난수를 발생시켜 잡음을 추가할 수 있다.

void randn(InputOutputArray dst, InputArray mean, InputArray stddev);
--- dst: 	정상 분포 난수 행렬
--- mean: 	평균
--- stddev: 표준편차

이를 이용해 가우시안 잡음을 추가하는 코드는 다음과 같다.

Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);

Mat noise(src.size(), CV_32S);
randn(noise, 0, 10);

Mat dst;
add(src, noise, dst, noArray(), CV_8U);

프로파일(Profile)

프로파일은 영상의 특정 경로(라인 또는 곡선) 상에 있는 픽셀의 밝기 값을 그래프 형태로 나타낸 것을 말한다. 영상의 잡음 정보를 획득하기 위해 사용한다.

namedWindow("profile");
profile.create(256, src.cols, CV_8UC1);

양방향 필터(Bilateral Filter)

양방향 필터는 노이즈를 제거하는 필터 중 하나로, Edge-preserving noise removal filter(엣지 보전 잡음 제거 필터) 중 하나이다. 기준 픽셀과 이웃 픽셀간의 거리, 픽셀 값의 차이를 함께 고려해 블러링 정도를 조정한다.

BF[I]p=1WpqSGσS(pq)Gσr(IpIq)Iq{p,q:point vector of p, qIp,Iq:value of p, qWp:normalize const valueBF[I]_p=\frac{1}{W_p}\sum_{q \in S}^{}G_{\sigma_S}(||\bold{p}-\bold{q}||)G_{\sigma_r}(|I_p-I_q|)I_q \left\{\begin{matrix} \bold{p, q}: \text{point vector of p, q} \\ I_p,I_q: \text{value of p, q} \\ W_p: \text{normalize const value} \end{matrix}\right.

OpenCV에서는 bilateralFilter() 함수를 통해 양방향 필터링을 할 수 있게 지원해준다.

void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType = BORDER_DEFAULT);
--- d: 필터링에 사용될 이웃 픽셀의 거리. -1은 sigmaSpace에 의해 자동 결정
--- sigmaColor: 색 공간에서 필터의 표준 편차
--- sigmaSpace: 좌표 공간에서 필터의 표준 편차
--- borderType: 가장자리 픽셀 처리 방식

인자값을 통해 조절하면 되지만, 다음과 같이 구성할 수 있다.

Mat dst;
bilateralFilter(src, dst, -1, 10, 5);

자세히 보면, 이미지의 노이즈들이 사라지는 것을 볼 수 있다.

📝 TIL을 정리하며

🤕 어려웠던 점

createTrackbar() 함수에서 value부분이 OpenCV 4.X 대에서는 deprecated 되었다. 계속 켜지자마자 창이 바로 닫혀서 당황했다.

createTrackbar("Profile", "dst", &row, src.rows - 1, on_trackbar);

이런식으로 &row를 참조변수로 지정할 수 없다. nullptr또는 0으로 지정한 뒤, argument로 처리해줘야 정상 동작한다.

void on_trackbar(int pos, void*){
	...
	uchar* pSrc = (uchar*)src.ptr<uchar>(pos);
    ...
}

🤔 궁금한 점

아직은 궁금한 점이 없었다.

😁 느낀 점

오늘은 조금 어려운 내용들이 있었다고 생각한다. OpenCV에 대한 기능들을 계속 배우고 있지만, 이것들이 나중에 확실히 생각 나려면 여러번 복습해봐야 되겠다고 생각했다.


📌 프로그래머스 데브코스 6기 자율주행 인지과정(Perception) 수강 내용을 바탕으로 정리한 TIL 입니다.
📅 Today: 2023.10.20.

profile
그냥 끄적여보는 블로그

0개의 댓글