해당 포스팅은 Mastering OpenCV 4 with Python 원서를 바탕으로 작성했습니다. 원서를 옮기는 과정에서 부자연스러운 부분이 있을 수 있습니다. 잘못 작성되거나 어색한 부분에 대해서 알려주시면 감사하겠습니다! 코드 정보는 여기를 클릭하시면 확인하실 수 있습니다.
지난 포스팅에 이어 오늘은 이미지 처리를 위한 방법들 중 몇 가지 소개합니다. 이번 포스팅에는 다양한 필터를 적용하여 이미지를 흐리게 하거나 선명하게 하는 방법을 소개합니다.
- 채널 분할 및 병합(Splitting and merging channels)
- 이미지의 기하학적인 변환 - 회전, 스케일링, 아핀 변환, 자르기
- 이미지를 사용한 산술 연산 - 비트 연산(AND, OR, XOR, NOT), 마스킹
- smoothing and sharpening 기법
- 모폴로지 연산
- Color spaces
- Color maps
1. Smoothing images
OpenCV에서는 cv2.filter2D라는 함수를 사용하여 이미지에 커널을 적용하도록 제공하고 있습니다. 우선 smoothing 기법은 일반적으로 노이즈를 줄이는 데 사용되며, 저해상도 이미지에서 픽셀들이 도드라지는 현상을 감소시켜줍니다. 픽셀이 도드라지는 현상은 아래의 사진처럼 확대된 부분을 말합니다. 흔히, 작은 사진을 크게 확대할 때 많이 발생하는 현상입니다.
아래는 오늘 배워볼 Smoothing 기법들을 동일한 이미지에 적용한 결과입니다.
1) Averaging filter
OpenCV에서 Average filter를 적용할 수 있는 방법은 cv2.blur()와 cv2.boxFilter()가 있습니다. 두 함수의 차이는 Normalize를 진행하느냐의 차이입니다. blur의 경우 normalize를 진행하지만 boxFilter는 normalize를 진행하지 않습니다. 물론, boxFliter에도 normalize 파라미터가 존재하기에 True로 입력해주면 blur와 동일하게 normalize를 진행합니다.
smooth_image_b = cv2.blur(image, (10, 10)) #(10, 10) : kernel size
smooth_image_bfi = cv2.boxFilter(image, -1, (10, 10), normalize=True) # -1 : 출력영상의 dtype(-1: 입력영상과 동일)
위의 결과에서 보시는 것처럼 normalize를 진행하지 않은 경우에는 확실히 다르게 나오는 것을 확인할 수 있습니다. 직접 해당 값을 print 해보면 normalize를 진행하지 않은 경우에는 대부분 255로 채워져 있습니다. 그래서 흰색으로 결과가 나오게 되었습니다.
2) Gaussian filter
가우시안 필터는 기본적으로 기준 픽셀에 가까울 수록 더 높은 가중치를 주는 방법입니다. 이 함수는 x방향의 표준편차와 y방향의 표준편차를 통해 제어할 수 있습니다.
smooth_image_gb = cv2.GaussianBlur(image, (9, 9), 0) # 0 : sigmaX 값
kernel 크기는 동일하게 설정하고 sigmaX값만 수정한 결과 우측으로 갈수록 흐려지는 이미지를 보여줍니다.
3) Median filter
Median filter는 말 그대로 중앙값을 활용하는 방법입니다. 이 필터는 위의 그림처럼 salt-and-pepper(점 잡음)이 존재할 때 자주 활용됩니다.
smooth_image_mb = cv2.medianBlur(image, 9) # 9 : kernel size
위의 그림처럼 잡음이 있는 이미지를 kernel size를 다르게 하여 적용한 결과 점점 잡음이 사라지는 것을 볼 수 있지만 가장자리에 대한 정보는 많이 없어지는 것을 확인할 수 있습니다. 자신의 상황에 적절한 kernel size를 찾아볼 필요가 있을 것 같습니다.
4) Bilateral filter
Median Blur는 edge가 뭉개지는 현상이 발생했었는데, Bilateral filter는 edge를 보존하면서 노이즈를 제거하는 방법입니다. 이 필터는 다른 edge가 아닌 부분에서만 Blur 효과를 줍니다.
smooth_image_bf = cv2.bilateralFilter(image, 5, 10, 10)
# 5(d) : 필터링에 사용될 거리(지름)
# 10(sigmaColor) : 색 공간에서 필터의 표준편차(클수록 멀리있는 색도 반영)
# 10(sigmaSpace) : 좌표 공간에서 필터의 표준편차(클수록 멀리 있는 pixel도 반영)
값에 따라 점점 노이즈가 제거되는 효과를 볼 수 있으며, median blur에 비해 조금 더 edge가 살아있는 모습을 볼 수 있습니다. 아래의 접은 글에는 처음에 있는 고양이 결과를 확인할 수 있는 코드입니다. 이를 활용해서 다양한 결과를 만들어보세요!
import cv2
import numpy as np
import matplotlib.pyplot as plt
def show_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(3, 3, pos)
plt.imshow(img_RGB)
plt.title(title)
plt.axis('off')
# figure 생성
plt.figure(figsize=(12, 6))
plt.suptitle("Smoothing techniques", fontsize=14, fontweight='bold')
# 이미지 불러오기
image = cv2.imread('cat-face.png')
# Average kernel(10, 10) 정의
kernel_averaging_10_10 = np.ones((10, 10), np.float32) / 100
# Average kernel(5, 5) 정의
kernel_averaging_5_5 = np.array([[0.04, 0.04, 0.04, 0.04, 0.04],
[0.04, 0.04, 0.04, 0.04, 0.04],
[0.04, 0.04, 0.04, 0.04, 0.04],
[0.04, 0.04, 0.04, 0.04, 0.04],
[0.04, 0.04, 0.04, 0.04, 0.04]])
print("kernel: {}".format(kernel_averaging_5_5))
# filter2D 활용
smooth_image_f2D_5_5 = cv2.filter2D(image, -1, kernel_averaging_5_5)
smooth_image_f2D_10_10 = cv2.filter2D(image, -1, kernel_averaging_10_10)
# cv2.blur 적용
smooth_image_b = cv2.blur(image, (10, 10))
# cv2.boxfilter 적용
smooth_image_bfi = cv2.boxFilter(image, -1, (10, 10), normalize=True)
# cv2.GaussianBlur 적용
smooth_image_gb = cv2.GaussianBlur(image, (9, 9), 0)
# cv2.MedianBlur 적용
smooth_image_mb = cv2.medianBlur(image, 9)
# cv2.bilateralFilter 적용
smooth_image_bf = cv2.bilateralFilter(image, 5, 10, 10)
smooth_image_bf_2 = cv2.bilateralFilter(image, 9, 200, 200)
# 결과 출력
show_with_matplotlib(image, "original", 1)
show_with_matplotlib(smooth_image_f2D_5_5, "cv2.filter2D() (5,5) kernel", 2)
show_with_matplotlib(smooth_image_f2D_10_10, "cv2.filter2D() (10,10) kernel", 3)
show_with_matplotlib(smooth_image_b, "cv2.blur()", 4)
show_with_matplotlib(smooth_image_bfi, "cv2.boxFilter()", 5)
show_with_matplotlib(smooth_image_gb, "cv2.GaussianBlur()", 6)
show_with_matplotlib(smooth_image_mb, "cv2.medianBlur()", 7)
show_with_matplotlib(smooth_image_bf, "cv2.bilateralFilter() - small values", 8)
show_with_matplotlib(smooth_image_bf_2, "cv2.bilateralFilter() - big values", 9)
plt.show()
2. Sharpening images
이미지 처리를 진행할 때, Edge가 정보로 작용하는 경우가 많습니다. 뭉개진 Edge를 살리기 위해서 Sharpening을 진행합니다. 가장 간단하게 적용할 수 있는 것은 원본 이미지에서 blur 된 이미지를 빼는 방법을 적용할 수 있습니다. cv2.addWeighted는 두 개의 영상을 가중 합을 통해 새로운 이미지를 만들어냅니다.
smoothed = cv2.GaussianBlur(img, (9, 9), 10)
unsharped = cv2.addWeighted(img, 1.5, smoothed, -0.5, 0)
# 1.5(alpha) : img에 대한 가중치
# -0.5(beta) : smoothed에 대한 가중치
# 0(gamma) : 결과에 추가적으로 더할 값
원본에는 1.5 weight를 곱하고 GaussianBlur에 -0.5 weight를 곱하여 가중합했더니, 원본보다 edge가 선명해지는 결과를 얻을 수 있습니다.
이 방법 외에도 다양한 kernel을 만들어서 적용해볼 수 있습니다.
kernel_sharpen_1 = np.array([[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]])
kernel_sharpen_2 = np.array([[-1, -1, -1],
[-1, 9, -1],
[-1, -1, -1]])
kernel_sharpen_3 = np.array([[1, 1, 1],
[1, -7, 1],
[1, 1, 1]])
kernel_sharpen_4 = np.array([[-1, -1, -1, -1, -1],
[-1, 2, 2, 2, -1],
[-1, 2, 8, 2, -1],
[-1, 2, 2, 2, -1],
[-1, -1, -1, -1, -1]]) / 8.0
sharp_image_1 = cv2.filter2D(image, -1, kernel_sharpen_1)
sharp_image_2 = cv2.filter2D(image, -1, kernel_sharpen_2)
sharp_image_3 = cv2.filter2D(image, -1, kernel_sharpen_3)
sharp_image_4 = cv2.filter2D(image, -1, kernel_sharpen_4)
위에 작성한 코드의 다양한 kernel을 적용한 결과입니다. 가장 선을 잘 살려주는 것은 Sharp 2인 것 같습니다. 또한 Sharp 3는 주변을 살리는 kernel이라 Blur에 비슷한 느낌을 얻을 수 있습니다. 이처럼 filter2D를 적용하여 각자가 적용해보고 싶은 kernel을 만들어서 적용해볼 수 있습니다. 아래의 접은 글에는 마지막에 보여드린 고양이 결과를 확인할 수 있는 코드입니다. 직접 진행해보시는 것을 추천드립니다.
import cv2
import numpy as np
import matplotlib.pyplot as plt
def show_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(2, 3, pos)
plt.imshow(img_RGB)
plt.title(title)
plt.axis('off')
def unsharped_filter(img):
smoothed = cv2.GaussianBlur(img, (9, 9), 10)
return cv2.addWeighted(img, 1.5, smoothed, -0.5, 0)
# figure 생성
plt.figure(figsize=(12, 6))
plt.suptitle("Sharpening images", fontsize=14, fontweight='bold')
# 이미지 불러오기
image = cv2.imread('cat-face.png')
# sharpening에 사용할 kernel 정의
kernel_sharpen_1 = np.array([[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]])
kernel_sharpen_2 = np.array([[-1, -1, -1],
[-1, 9, -1],
[-1, -1, -1]])
kernel_sharpen_3 = np.array([[1, 1, 1],
[1, -7, 1],
[1, 1, 1]])
kernel_sharpen_4 = np.array([[-1, -1, -1, -1, -1],
[-1, 2, 2, 2, -1],
[-1, 2, 8, 2, -1],
[-1, 2, 2, 2, -1],
[-1, -1, -1, -1, -1]]) / 8.0
# kernel 적용하기
sharp_image_1 = cv2.filter2D(image, -1, kernel_sharpen_1)
sharp_image_2 = cv2.filter2D(image, -1, kernel_sharpen_2)
sharp_image_3 = cv2.filter2D(image, -1, kernel_sharpen_3)
sharp_image_4 = cv2.filter2D(image, -1, kernel_sharpen_4)
# unsharped filter 활용
sharp_image_5 = unsharped_filter(image)
# 결과 출력
show_with_matplotlib(image, "original", 1)
show_with_matplotlib(sharp_image_1, "sharp 1", 2)
show_with_matplotlib(sharp_image_2, "sharp 2", 3)
show_with_matplotlib(sharp_image_3, "sharp 3", 4)
show_with_matplotlib(sharp_image_4, "sharp 4", 5)
show_with_matplotlib(sharp_image_5, "addWeighted(1.5, -0.5)", 6)
plt.show()
지금까지 읽어주셔서 감사합니다. 다음 글에서는 다른 주제를 가지고 오도록 하겠습니다!