OpenCV - 영상 필터링

5 분 소요

필터링 연산

필터링: 영상에서 원하는 정보만 통과시키고 원치 않는 정보는 걸러내는 작업.

필터의 값을 어떻게 정의하냐에 따라 특정한 값을 제거하거나 특정한 성분만을 남겨둘 수도 있고, 영상 전체를 변환 시킬수도 있다.

필터링은 마스크, 커널, 또는 윈도우라 불리는 작은 행렬을 커널의 정중앙, 한 픽셀을 고정점(anchor point)로 하여 원본 이미지에 대입하여 연산한다. 커널과 일대일로 대응하는 위치에 있는 픽셀 값들을 곱해서 모두 합하는 컨벌루션(Convolution) 결과를 고정점의 값으로 정하고, 이 연산을 마지막 픽셀까지 반복한다.

커널은 일자형, 정사각형, 십자형 등이 있고 크기도 정할 수 있다. 보통은 3x3 정방영 행렬이 가장 널리 사용된다.

4

이 때, 영상의 가장자리에 대해 필터링 연산을 하려면 실제 영상에 존재하지 않는 픽셀들에 대한 연산이 불가능해지는데, 가장자리 밖 영역에 대해 가상의 픽셀값을 대입하는 테두리 외삽법 (Border Extrapolation) 으로 문제를 해결할 수 있다.

  • BORDER_CONSTANT: 000 abcdef 000
  • BORDER_REPLICATE: aaa abcdef fff
  • BORDER_REFLECT: cba abcdef fed
  • BORDER_REFLECT_101: dcb abcdef edc
  • BORDER_DEFAULT: BORDER_REFLECT101 과 동일

필터링 함수

**dst =** **cv2.filter2D(src, ddepth, kernel, dst=None, anchor=None, delta=None, borderType=None)**
  • src: 입력 이미지
  • ddepth: 결과 영상의 깊이/데이터 타입. cv2.CV_8U, CV_32F, CV_64F, -1 등을 저장하면 지정된 타입의 dst 영상을 생성한다.
  • kernel: 필터 마스크 행렬, 실수형
  • dst: 출력 영상. src와 같은 크기, 같은 채널 수를 갖는다
  • anchor: 고정점 위치
  • delta: 필터링 연산 후 추가적으로 더할 값
  • borderType: 가장자리 픽셀 확장 방식

opencv 이미지 데이터 타입 / ddepth 종류

  • CV_8U : 8-bit unsigned integer: uchar ( 0..255 )
  • CV_8S : 8-bit signed integer: schar ( -128..127 )
  • CV_16U : 16-bit unsigned integer: ushort ( 0..65535 )
  • CV_16S : 16-bit signed integer: short ( -32768..32767 )
  • CV_32S : 32-bit signed integer: int ( -2147483648..2147483647 )
  • CV_32F : 32-bit floating-point number: float ( -FLT_MAX..FLT_MAX, INF, NAN )
  • CV_64F : 64-bit floating-point number: double ( -DBL_MAX..DBL_MAX, INF, NAN )
  • -1 : 입력영상과 같은 깊이의 출력영상 생성

엠보싱(embossing) 필터링

엠보싱: 직물이나 종이, 금속판 등에 올록볼록한 형태로 만든 객체의 윤관 또는 무늬.

입력 영상에서 픽셀 값 변화가 적은 평탄한 영역은 회색으로 설정하고, 객체의 경계부분은 좀 더 밝거나 어둡게 설정하면 올록볼록한 엠보싱 느낌이 나게 한다.

kernel = [ [-1, -1, 0], [-1, 0, 1], [0, 1, 1] ]

대각선으로 +1 또는 -1 값이 지정되어 있어 필터링을 수행하면 대각선 방향으로 픽셀 값이 급격하게 변하는 부분에서 결과 영상 픽셀 값이 0보다 훨씬 크거나 0보다 훨씬 작은 값을 가지게 된다.

이렇게 구한 결과를 그대로 화면에 나타내면 음수 값은 모두 포화 연산에 의해 0이 되어버리기 때문에 입체감이 줄어드므로 엠보싱 필터를 구현할 때에는 결과 영상에 128을 더하는 것이 좋다.

kernel = np.array([-1,-1,0,-1,0, 1, 0, 1, 1]).reshape(3,3)
img = cv2.imread('a.jpg', 0)

dst = cv2.filter2D(img, -1, kernel, None, (-1,-1), 128)  
#src, ddepth, kernel, dst, anchor, delta 순. borderType = default

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

블러링 (blurring)

초첨이 맞지 않은 사진처럼 영상을 부드럽게 만드는 필터링 기법이며 스무딩이라고도 한다. 영상에서 인접한 픽셀 간의 픽셀 값 변화가 크지 않은 경우 부드러운 느낌을 받을 수 있다.

거친 느낌의 영상을 부드럽게 만들거나 입력 영상에 존재하는 잡음(noise)의 영향을 제거하는 전처리 과정으로 사용된다.

모션 블러

kernel = 1/3 * [ [0, 0, 0], [1, 1, 1], [0, 0, 0] ] (size가 3x3일 때)

1이 나열된 방향을 따라 카메라가 흔들린 효과를 만들어 준다.

size = 5
kernel = np.zeros((size, size))
kernel[int((size-1)/2), :]=np.ones(size)
kernel = kernel/size

img = cv2.imread('a.jpg', 0)
dst = cv2.filter2D(img, -1, kernel)

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

평균값 필터 (mean filter)

kernel = (1 / (3*3)) * [ [1, 1, 1], [1, 1, 1], [1, 1, 1] ] (size가 3x3일 때)

주변 픽셀값의 평균값으로 필터링하면 영상은 픽셀 값의 급격한 변화가 줄어들어 날카로운 에지가 무뎌지고 잡음의 영향이 크게 사라지는 효과가 있다.

평균값 필터는 마스크의 크기가 커지면 커질수록 더욱 부드러운 느낌의 결과 영상을 생성하지만 대신 연산량이 크게 증가할 수 있다.

img = cv2.imread('b.jpg', 0)
size = 3
kernel = np.ones((size, size)) / (size*size)
dst = cv2.filter2D(img, -1, kernel)

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV의 blur() 함수를 이용해서도 평균값 필터링을 수행할 수 있다.

img = cv2.imread('b.jpg', 0)

#cv2.blur(img, kernelSize)
dst = cv2.blur(img, (3,3))

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

가우시안 필터 (Gaussian filter)

가우시안 분포 (Gaussian distribution) 함수를 근사하여 생성한 필터 마스크를 사용하는 필터링 기법으로 평균값 필터보다 자연스러운 블러링 결과를 생성한다.

가우시안 분포 = bell curve = 정규분포, normal distribution

가우시안 필터에서는 평균이 0인 가우시안 분포 함수를 사용하며 표준편차가 커질수록 그래프가 넓게 퍼지고 경사가 완만하고, 표준편차가 작아질수록 그래프가 좁아지고 중앙이 뾰족해진다.

표준편차x 와 표준편차y가 동일한 2차원 가우시안 분포 함수를 바탕으로 구한 마스크 행렬을 사용하며, 이 필터 마스크를 사용하여 마스크 연산을 한다는 것은 필터링 대상 픽셀 근처에는 가중치를 크게 주고, 필셀과 멀리 떨어진 주변부에는 가중치를 조금만 주어 가중 평균을 구하는 것과 같다.

OpenCV에서는 GaussianBlur() 함수를 사용하여 필터링을 수행할 수 있다.

cv2.GaussianBlur(img, ksize, sigmaX)

  • ksize(w, h) : 커널 크기
  • sigmaX: x방향 가우시안 커널 표준 편차
img = cv2.imread('b.jpg', 0)
dst = cv2.GaussianBlur(img, (5,5), 2)

cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

샤프닝

블러링과 반대로 영상을 선명하게 만드는 필터링으로, 초점이 안 맞은 것 같은 사진에서 사물의 윤곽을 뚜렷하게 해준다.

언샤프닝

샤프닝을 구현하기 위해서는 영상을 먼저 블러링 시켜줘야 한다. 즉, 날카롭지 않은 영상을 먼저 만드는 언샤프닝이 이루어진다. 그 이유는 아래 그림과 같이 원본 이미지와 블러링한 이미지의 차를 구하면 샤프닝을 하기 위해 강조시켜줄 부분을 찾을 수 있기 때문이다. 원본과 블러된 이미지의 차인 g(x)를 원본에 더해주면 경계선이 뚜렷해진 결과물을 얻게 된다.

5

OpenCV에서는 따로 언샤프 필터링을 위한 함수를 제공하지 않는다. 하지만 위 이미지의 수식을 그대로 소스 코드 형태로 작성하면 어렵지 않게 샤프닝 결과 영상을 얻을 수 있다.

h(x) = f(x) + g(x) = 2f(x) - f’(x)

img = cv2.imread('c.jpg')

blr = cv2.GaussianBlur(img, (5,5), 5)  #f'(x)
dst = np.clip(**2.0*img - blr**, 0, 255).astype('uint8')  #2*f(x) - f'(x)

cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
  • 흑백, 컬러 모두 적용방식은 동일하다

잡음 제거 필터링

잡음(noise) : 원본 신호에 추가된 원치 않은 신호

잡음 모델 (noise model)

잡음이 생성되는 방식을 잡음 모델이라고 하며, 다양한 잡음 모델 중에서 가장 대표적인 것이 가우시안 잡음 모델이다. 평균이 0인 가우시안 분포를 따라 잡음을 인위적으로 추가할 수 있다.

cv2.randn(난수를 담을 배열, 가우시안 분포 평균, 표준 편차)

img = cv2.imread('c.jpg')

x = img.copy()  #노이즈 생성할 배열. 이미지와 동일 모양
x = x.astype('int8')  #부호있는 타입으로 변환
cv2.randn(x, 0, 0.3)   #가우시안 난수 생성하여 배열 x에 담음
x = x.astype('uint8')  #이미지 타입으로 변환
y = cv2.add(img, x)    #원본 이미지와 노이즈 합성

cv2.imshow('img', y)
cv2.waitKey(0)
cv2.destroyAllWindows()

양방향 필터 (bilateral filter)

가우시안 필터를 사용해서 잡음을 제거할 수도 있지만, 경계선도 함께 흐려지는 문제가 있다. 이를 보완해서 노이즈만 흐리게하고 경계선은 명확하게 하는 방법이 양방향 필터링이다.

cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace)

  • src – image
  • d – filtering시 고려할 주변 pixel 지름
  • sigmaColor – 색 공간 표준편차. 클수록 주변 픽셀과의 값 차이에 예민하다.
  • sigmaSpace – 좌표 공간 표준편차. 클수록 더 많은 주변 픽셀을 고려한다.
img = cv2.imread('c.jpg')
dst = cv2.bilateralFilter(y, 9, 100, 100)
cv2.imshow('img', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

미디언 필터 (median filter)

영상에서 자기 자신 픽셀과 주변 픽셀 값 중에서 중간값을 선택하여 결과 영상 픽셀 값으로 설정하는 필터링 기법.

픽셀값이 일정 확률로 0 또는 255로 변경되는 형태의 소금과 후추 잡음(salt&pepper noise)처럼 잡음 픽셀 값이 주변 픽셀 값과 큰 차이가 있어 도드라지는 경우에 효과적으로 동작한다.

cv2.medianBlur(img, ksize)

img = cv2.imread('c.jpg')
dst = cv2.medianBlur(y, 3)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

댓글남기기