2017-06-14 16:39:01

이번엔 http://opencvexamples.blogspot.com/p/learning-opencv-functions-step-by-step.html에 있는 7번째 예제를 공부하자


7. 2D Convolution / Creating new filter


바로 전 예제에서 gaussian 필터, bilateral 필터, median 필터와 같이 이미 만들어져 있는 필터를 사용했다면, 이번 예제에서는 스스로 커널을 만들어서 이미지 매트릭스와 컨볼루션 연산함으로 필터링을 실행한다. 여기선 가장 간단한 normalized box filter를 구현했다. 커널을 1로 채운 다음, 커널의 사이즈로 나눠준다. 만약 3 by 3 사이즈의 커널이면 1/9 * [1 1 1; 1 1 1; 1 1 1] 형태가 된다 (그림 1).

 

그림 1. normalized box filter를 위한 커널


이 커널로 단순한 엣지가 있는 이미지(그림 2)를 필터링해보자. 이미지는 10 x 10의 크기를 갖는다. 


그림 2. 단순한 엣지가 있는 이미지와 대응하는 픽셀 값

 

이 이미지와 위의 커널로 컨볼루션을 한다는 것은 커널의 중심을 처리하고자하는 이미지의 픽셀 위에 놓은 다음, 커널과 이미지가 겹치는 부분에서 서로 대칭되는 위치에 있는 값들을 서로 곱해서 모두 더한 값을 그 픽셀의 새로운 값으로 대체한다는 것이다(그림 3). 노란색으로 표시된 위치의 픽셀값은 az + by + cx + dw + ... + ir이 된다는 것을 기억하자. 반대로 correlation 연산이면, ar + bs + ct + ... + hy + iz 와 같이 겹치는 위치에 있는 값들을 그대로 곱해서 더해준다. 

그림 3. 2D 이미지 컨볼루션 연산 설명 (출처: 동국대 멀티미디어 콘텐츠 프로그래밍 및 실험 수업 PPT)


이제 컨볼루션 연산을 이해했으니, 위의 커널과 이미지를 컨볼루션하는 필터링해보자(그림 4). 여기서는 커널이 이미지 안에 모두 들어갈 수 있는 점부터 컨볼루션을 하도록 설정했다. 따라서 (2, 2) 픽셀 부터 컨볼루션 연산을 시작해서 한 칸씩 오른쪽으로 옮겨가면서 계산한다. 빨간 색 화살표는 anchor를 의미한다. 한 행에서 연산을 마치면 다음 행으로 넘어 간다. 이 연산을 모든 이미지 픽셀값들이 처리될 때까지 한다 (여기서는 테두리에 있는 픽셀값들은 제외하고).  


그림 4. 필터링 과정 설명



필터링이 완료되면 테두리에 있는 픽셀값들은 제외했기 때문에 8 x 8 사이즈의 이미지가 된다 (그림 5). 엣지 정보가 조금 무뎌진 것을 확인할 수 있다. 


그림 5. 필터 처리된 이미지와 대응하는 픽셀값들.

  

opencv에서 2D 컨볼루션 연산을 제공하는 함수는 filter2D이다.

void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BORDER_DEFAULT )

  • src - 입력 이미지

  • dst - 목적 이미지

  • ddpeth - desired depth of the destination image

  • kernel - 컨볼루션 커널

  • anchor - anchor of the kernel that indicates the relative position of a filtered point within the kernel; the anchor should lie within the kernel; default value (-1, -1) means that the anchor is at the kernel center. 

  • delta - optional value added to the filtered pixels before storing them in dst.

  • borderType - 픽셀 외삽 방법.


이번에는 다른 이미지를 필터링해보자. 소스코드는 아래와 같다. 

#include "opencv2/imgproc/imgproc.hpp"

#include "opencv2/highgui/highgui.hpp"

#include <stdlib.h>

#include <stdio.h>


using namespace cv;


void conv2(Mat src, int kernel_size)

{

Mat dst, kernel;

kernel = Mat::ones(kernel_size, kernel_size, CV_32F) / (float)(kernel_size*kernel_size);  // normalized box filter의 커널 만들기.


/// Apply filter

filter2D(src, dst, -1, kernel, Point(-1, -1), 0, BORDER_DEFAULT);

namedWindow("Before filtering", CV_WINDOW_AUTOSIZE);

imshow("Before filtering", src);

namedWindow("filter2D Demo", CV_WINDOW_AUTOSIZE); 

imshow("filter2D Demo", dst);

}


int main(int argc, char** argv)

{

Mat src;


/// Load an image

src = imread("hee.jpg");

if (!src.data) { return -1; }


conv2(src, 7); // 7 x 7 사이즈의 커널로 설정함. 클수록 더 블러 효과가 크다. 


waitKey(0);

return 0;

}


그림 6는 원본 이미지와 필터처리된 이미지를 보여준다. 예상했듯이 가장 자리들이 무뎌지면서 전반적으로 이미지가 블러된 것을 확인할 수 있다. 


그림 6. 원본 이미지와 normalized box filter된 이미지