Image processing (영상처리)에서 가장 흔하게 사용되는 기법중의 하나가 이미지
필터링 (image filtering) 입니다. OpenCV에서 기본 제공하는 필터 - Average, Sobel filter 등 -
또는 사용자가 제작한 필터를 이용해 대상 이미지와의 연산(convolution 등)을 통해 새로운
이미지를 생성하는 것을 이미지 필터링(image filtering) 이라고 합니다.
흔히 이미지상의 지정된 부분을 흐릿하게 하거나 대상의 윤곽선(edge)에 강조를 주어 더
선명하게 해주는 기능은 이미지 필터링의 직접적인 역할 입니다.
또한 간접적인 역할로써는, 딥러닝 (deep learning) 네트워크 중 이미지에서 원하는 물체
혹은 대상을 자동으로 검출하는 기능을 하는 CNN (Convolutional Neural Network) 에서의
이미지 데이터는 반드시 한가지 이상의 필터링 과정을 거칩니다.
컨볼루션 (convolution) - 여기서는 2D convolution을 예로 들겠습니다 - 은 이미지 필터링의
기본적인 연산 방법으로써, 대상이 되는 데이터의 각 element들과 필터의 각 계수들을
element-wise 곱셈을 하고 그 결과들을 더해서(element-wise sum) 새로운 픽셀 데이터를
얻는 방식입니다.
본 글에서는 컨볼루션의 설명을 생략하고 밑의 비디오로 대체하겠습니다. 동영상 재생이
안된다면 광고차단 프로그램 (AdBlock 등) 을 잠시 비활성화 해주시면 재생이 될 것입니다.
위의 비디오에서 보듯이, 이미지 필터 - 또는 커널 Kernel, 윈도우 Window 이라고도 합니다 - 를
대상 이미지 데이터의 모든 구역으로 이동하면서 대상 영역의 이웃에 있는 픽셀들과 컨볼루션
연산하는 방식을 선형 공간 필터링 (linear spatial filtering) 이라고 부릅니다.
대상 이미지와 이미지 필터간의 컨볼루션 연산후 결과 데이터의 크기는 줄어들기 때문에
대상 이미지의 테두리를 '0' 값을 가진 픽셀들로 확장 (zero padding) 합니다.
따라서 zero padding에 의해 본래 대상 이미지와 컨볼루션 연산결과의 크기가 같아집니다.
푸리에 변환 (Fourier transform) 을 통한 주파수 도메인 필터링 (Frequency domain filtering) 은
선형 공간 필터링과 구분되는것으로 후에 중급과정에서 다루겠습니다.
Average filtering
Average filter는 대상 이미지의 지정된 부분을 흐릿하게 (blur) 하는 역할을 합니다.
OpenCV에서는 커널(Kernel)의 크기를 지정해 주면 간편하게 연산해 주는 cv2.blur( ) 라는
메소드를 제공합니다. 커널의 계수는 사용자에 의해 지정된 한쌍의 tuple값에 의해 정해지며
그 알고리즘은 간단합니다. ( 밑의 코드중 syntax, algorithm 주석 부분 참조 )
예를들어 사용자가 kernel_size = 3 으로 지정한다면 average filter의 모든 계수가 1/9이 될 것이고,
이 필터 계수들이 대상 이미지의 픽셀들과 컨볼루션 연산을 수행함으로써 결과 이미지 데이터는
전반적으로 낮은 값을 가지며 흐릿한 영상을 보여줄 것입니다.
import numpy as np
# algorithm
# ksize=(a,b)
# kernel = np.ones((a,b),np.float32)/(a*b)
ksize = (3,3)
kernel = np.ones((ksize[0],ksize[1]),np.float32)/(ksize[0]*ksize[1])
print(kernel)
이번에는 대상 이미지의 특정 cell(세포) 영역을 지정하여 10x10 크기의 average filter를
적용해 보겠습니다.
( 코드에서 쓰인 이미지는 본 블로그 맨밑 출처 1에 가셔서 다운로드 받을 수 있습니다. )
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
img_color = cv2.imread('national-cancer-institute-jdfn7Z03Qa4-unsplash.jpg',cv2.IMREAD_COLOR)
img_gray = cv2.cvtColor(img_color,cv2.COLOR_BGR2GRAY)
img_gray = img_gray.copy().astype(np.float32) # convert from uint8 to float32 type data
img_gray /= 255. # rescaling
start_pt = [1210,753] # [y, x-axis]
end_pt = [2110,2115] # [y, x-axis]
img_gray_crop = img_gray[start_pt[0]:end_pt[0], start_pt[1]:end_pt[1]] # numpy slicing
img_avg_fil = cv2.blur(img_gray_crop,(10,10)) # filtering
fig = plt.figure(figsize=(12,10))
fig.add_subplot(1,2,1)
plt.axis("off")
plt.imshow(img_gray_crop, cmap='gray')
plt.title('Input image')
fig.add_subplot(1,2,2)
plt.axis("off")
plt.imshow(img_avg_fil, cmap='gray')
plt.title('(Average) Filtered image')
plt.show()
위의 코드에서 보듯이 cv2.blur( ) 명령어는 이미지 필터를 대상 이미지에 적용하여 연산을 수행한 후
결과를 반환 하였습니다.
입력 이미지 데이터는 소수점 형태(np.float32)로써 범위 0 - 1.0 으로 scaling 하였습니다.
만일 uint8 (기본)형식의 이미지 데이터를 입력으로 사용한다면 필터링 연산의 결과는 소수점의 값을
잃게되어 올바른 결과값을 표현하지 못할수도 있기 때문입니다.
기대한바와 같이 필터링의 결과는 입력 이미지에 비해 흐릿한 영상을 보여줍니다.
개인적인 경험상 Neuroscience 분야에서 average filter는 이미지 분석단계에서 자주 사용되지
않았고, 간혹 Image processing(영상처리)의 간단한 전처리(pre-processing)과정 중 노이즈 감소용으로
쓰일때가 있었습니다.
Laplacian filtering
Laplacian filter는 기본적으로 high-pass filter로써 주로 사용됩니다.
단조로운 배경에 물체가 하나 있고 이를 푸리에 변환 (fourier transform)을 통해 주파수 도메인
(frequency domain) 에서 보자면 물체의 윤곽선(edge)은 갑작스러운 변화로써 고주파 (high frequency)
특성을 갖습니다.
밑의 laplacian filter의 algorithm에서 보듯이 (2차 미분식) 영상신호 기울기의 급격한 변화를
감지하는것이 목적입니다. 그렇기 때문에 laplacian filter는 이러한 고주파 특성, 즉 물체의 윤곽선(edge)을
검출하는데 사용됩니다.
필터의 계수는 사용자에 의해 지정된 하나값에 의해 정해지며 그 알고리즘은 다음과 같습니다.
필터의 중앙 계수가 음수이기 때문에 laplacian filtering의 결과 영상이 음수값을 가질수 있습니다.
따라서 입력 영상 데이터에서 필터 결과 영상을 뺀다면 픽셀값이 증가하여 입력 영상보다 선명해
질수 있습니다. 마찬가지로 특정 cell(세포) 영역을 입력 영상 데이터로써 Laplacian filter 를 적용해
보겠습니다.
그전에 laplacian filter를 구현 및 연산하는 명령어 cv2.Laplacian( ) - 첫 글자 'L' 대문자임에 주의 -
의 입력 파라메터를 보자면,
# dst = cv2.Laplacian(src_gray, ddepth, ksize=kernel_size)
src_gray: 대상 입력 이미지 데이터
dst: 출력 이미지 데이터
ddepth: 출력 이미지의 데이터 타입. 입력 이미지 데이터가 uint8 형식이므로 overflow 를 피하기 위해
더 큰 데이터 타입으로 지정
ksize: kernel 크기. 기본값은 3
그 외에, delta: 기본값 0, borderType: 대상 이미지의 테두리 픽셀 처리 방식.
img_gray = cv2.cvtColor(img_color,cv2.COLOR_BGR2GRAY)
start_pt = [1210,753] # [y, x-axis]
end_pt = [2110,2115] # [y, x-axis]
img_gray_crop = img_gray[start_pt[0]:end_pt[0], start_pt[1]:end_pt[1]] # numpy slicing
# float type input data is not allowable
img_lap_fil = cv2.Laplacian(img_gray_crop,ddepth=cv2.CV_32F,ksize=3,
delta=0,borderType=cv2.BORDER_REPLICATE) # filtering
img_gray_crop_copy = img_gray_crop.copy()
img_gray_crop_copy = img_gray_crop_copy.astype(np.float32)
img_gray_crop_imp = img_gray_crop_copy - img_lap_fil
img_gray_crop_imp = cv2.convertScaleAbs(img_gray_crop_imp) # convert from float32 to uint8 type data
fig = plt.figure(figsize=(16,10))
fig.add_subplot(2,2,1)
plt.axis("off")
plt.imshow(img_gray_crop, cmap='gray')
plt.title('Input image')
fig.add_subplot(2,2,2)
plt.axis("off")
plt.imshow(img_gray_crop_imp, cmap='gray')
plt.title('(Laplacian) Improved image')
fig.add_subplot(2,2,3)
plt.axis("off")
plt.imshow(img_lap_fil, cmap='gray')
plt.title('(Laplacian) Filtered image')
plt.show()
Laplacian filter를 적용할시 주의할점은 명령어 cv2.Laplacian( )의 입력 이미지는 소수점 형태
(np.float32 등)의 데이터 타입을 허용하지 않습니다.
따라서 cv2.Laplacian( ) 입력 파라메터 중 입력 대상 이미지는 uint8 형식을 갖고, 연산의 결과
"img_lap_fil" 는 float32 형태를 갖기위해 ddepth=cv2.CV_32F 로 지정 하였습니다.
3x3크기에 alpha=0의 값을 갖는 laplacian filter를 생성하였고, 대상 이미지의 경계 부분을
zero-padding이 아니라 마지막 픽셀값을 복제 ('replicate') 하여 확장하여 필터링 연산을 수행
하였습니다.
마지막으로 입력 이미지의 복사본을 np.float32 타입으로 변환시킨후 필터 결과 영상을 빼기
(선형연산)를 하고 이를 다시 uint8 타입으로 변환 - cv2.convertScaleAbs( ) 명령어 - 을 하여
cell(세포)의 윤곽선(edge)이 개선된 결과물을 얻었습니다.
위의 코드에서 laplacian filter를 생성할 시 입력 파라메터인 alpha 값을 '0' 으로 지정하였지만,
다른값으로 지정하며 필터링 결과를 비교해 보기를 권해드립니다.
보통 고화질의 Confocal image 또는 Two-Photon image에서는 cell(세포)의 검출 혹은 관찰에
큰 문제가 없었지만 때로는 필터를 이용하여 cell(세포)의 윤곽(edge)에 대한 화질개선을 필요로
하였습니다. Laplacian filter는 또다른 윤곽선(edge) 검출 필터인 Sobel filter 보다 더 자주 사용
하였습니다.
Sobel filtering
Sobel filter 또한 윤곽선(edge)을 검출하는데 많이 사용되는 방법으로써 연산이 간편하다는
장점이 있습니다. Sobel filter는 laplacian filter와 다르게 (1차 미분식) 영상신호 기울기를 측정함
으로써 윤곽선(edge)를 검출합니다.
3x3 크기의 sobel filter의 계수는 다음과 같습니다.
# Gy = [ -1 -2 -1 # vertical gradient
# 0 0 0
# 1 2 1 ]
# Gx = [ -1 0 1 # horizontal gradient
# -2 0 2
# -1 0 1 ]
이미지 데이터의 x 혹은 y 방향을 구분지어 필터링을 수행하므로, 필터링의 목적에 맞게끔
필터의 계수를 선택하면 됩니다. 마찬가지로 특정 cell(세포) 영역을 입력 영상 데이터로써
sobel filter 를 x, y 방향에 각각 적용하고, 각각의 필터링 결과를 입력 영상에 더하여(선형 연산)
마지막에 보이겠습니다.
그전에 sobel filter를 구현 및 연산하는 명령어 cv2.Sobel( ) - 첫 글자 'S' 대문자임에 주의 - 의
입력 파라메터를 보자면,
grad_x / grad_y = cv2.Sobel(src_gray, ddepth, dx=1, dy=0, ksize, scale, delta, BORDER_DEFAULT)
src_gray: 대상 입력 이미지 데이터
grad_x / grad_y: 출력 이미지 데이터 (x 또는 y방향)
ddepth: 출력 이미지의 데이터 타입. 입력 이미지 데이터가 uint8 형식이므로 overflow 를 피하기 위해
더 큰 데이터 타입으로 지정
dx, dy: 필터의 x 또는 y방향 적용 선택
ksize: kernel 크기
img_gray = cv2.cvtColor(img_color,cv2.COLOR_BGR2GRAY)
start_pt = [1210,753] # [y, x-axis]
end_pt = [2110,2115] # [y, x-axis]
img_gray_crop = img_gray[start_pt[0]:end_pt[0], start_pt[1]:end_pt[1]] # numpy slicing
img_sobel_horizon = cv2.Sobel(img_gray_crop,ddepth=cv2.CV_32F,dx=1,dy=0,ksize=3,
scale=1,delta=0,borderType=cv2.BORDER_REPLICATE) # filtering in x-axis
img_sobel_vertical = cv2.Sobel(img_gray_crop,ddepth=cv2.CV_32F,dx=0,dy=1,ksize=3,
scale=1,delta=0,borderType=cv2.BORDER_REPLICATE) # filtering in y-axis
fig = plt.figure(figsize=(16,10))
fig.add_subplot(1,2,1)
plt.axis("off")
plt.imshow(img_sobel_horizon, cmap='gray')
plt.title('(Sobel) Horizontally filtered image')
fig.add_subplot(1,2,2)
plt.axis("off")
plt.imshow(img_sobel_vertical, cmap='gray')
plt.title('(Sobel) Vertically filtered image')
plt.show()
img_gray_crop_copy = img_gray_crop.copy()
img_gray_crop_copy = img_gray_crop_copy.astype(np.float32)
img_sobel_hor = img_gray_crop_copy + img_sobel_horizon
img_sobel_hor = cv2.convertScaleAbs(img_sobel_hor) # convert from float32 to uint8 type data
img_sobel_ver = img_gray_crop_copy + img_sobel_vertical
img_sobel_ver = cv2.convertScaleAbs(img_sobel_ver) # convert from float32 to uint8 type data
fig = plt.figure(figsize=(16,10))
fig.add_subplot(1,2,1)
plt.axis("off")
plt.imshow(img_sobel_hor, cmap='gray')
plt.title('Horizontally improved image')
fig.add_subplot(1,2,2)
plt.axis("off")
plt.imshow(img_sobel_ver, cmap='gray')
plt.title('Vertically improved image')
plt.show()
img_sobel_fil = img_sobel_horizon + img_sobel_vertical
img_sobel_fil_result = img_gray_crop_copy + img_sobel_fil
img_sobel_fil_result = cv2.convertScaleAbs(img_sobel_fil_result) # convert from float32 to uint8 type data
fig = plt.figure(figsize=(16,10))
fig.add_subplot(1,2,1)
plt.axis("off")
plt.imshow(img_gray_crop, cmap='gray')
plt.title('Input image')
fig.add_subplot(1,2,2)
plt.axis("off")
plt.imshow(img_sobel_fil_result, cmap='gray')
plt.title('(Sobel) Improved image')
plt.show()
위 중간에 표시된 결과 이미지 (Horizontally and Vertically improved image) 는
대상 이미지에 각각 x축, y축에 sobel filter 가 적용된 결과를 더하여(선형연산) 얻은 결과 이미지들
입니다. Cell (세포) 내부의 필터링 결과는 비슷해 보이지만 수직 (y축) 으로 뻗은 cell (세포) 바깥부분을
보면 각 방향에 적용된 필터링의 결과를 비교해 볼수 있습니다.
맨 밑의 결과 이미지는 입력 대상 이미지와 x,y 축 모두에 sobel filter 를 적용한 결과 이미지를 비교해
두었습니다.
uint8 형식의 입력 이미지는 필터링 연산을 거쳐 float32 형식으로써 더하기(선형연산)를 수행후 다시
uint8 타입으로써 변환 - cv2.convertScaleAbs( ) - 하여 결과 이미지를 출력하였습니다.
출처1: https://unsplash.com/photos/jdfn7Z03Qa4
사진 작가: National Cancer Institute, Unsplash
Cells from cervical cancer – Splash에서 National Cancer Institute의 이 사진 다운로드
unsplash.com
출처2: https://www.mathworks.com/discovery/convolution.html
Convolution
Convolution is a mathematical operation that combines two signals and outputs a third signal. See how convolution is used in image processing, signal processing, and deep learning.
www.mathworks.com
출처3: https://www.mathworks.com/help/images/ref/fspecial.html#d124e113760
Create predefined 2-D filter - MATLAB fspecial
You have a modified version of this example. Do you want to open this example with your edits?
www.mathworks.com
'Python 영상처리 기초 with OpenCV' 카테고리의 다른 글
[Python 영상처리 기초 7] 이미지의 화질개선 Contrast-Brightness-Gamma - part2 (0) | 2023.09.17 |
---|---|
[Python 영상처리 기초 7] 이미지의 화질개선 Contrast-Brightness-Gamma - part1 (2) | 2023.09.16 |
[Python 영상처리 기초 5] 이미지의 화질개선 - Normalization, Standardization (1) | 2023.08.02 |
[Python 영상처리 기초 4] 이미지의 픽셀값 다루기 - Crop, 선형연산 (2) | 2023.07.31 |
[Python 영상처리 기초 3] 이미지의 픽셀값 다루기 - Gray scale, Histogram, Binarization (0) | 2023.07.29 |