본문 바로가기
Programming/Image Processing

[Image Processing][Python]이미지 스무딩(mean, median, k-nearest neighbor)

by NoiB 2021. 12. 9.
반응형

이번 친구들은 스무딩, 블러링 같은 이름으로 불리는 처리 방법입니다. 이런 필터링 작업들을 해주는 이유는 이미지의 노이즈를 줄이거나 이미지를 흐리게 하기 위해서 진행하는 경우가 많습니다.

mean filter

결과 :

코드 : 

import cv2
import numpy as np

def mean_filter(img):
    h,w = img.shape[:2]
    img1 = img
    for i in range(1,h-1):
        for j in range(1,w-1):
            img1[i][j] = (int(img[i-1][j-1])+int(img[i][j-1])+int(img[i+1][j-1])+int(img[i-1][j])+int(img[i][j])+int(img[i+1][j])+int(img[i-1][j+1])+int(img[i][j+1])+int(img[i+1][j+1]))/9
    return img1

img = cv2.imread('./img_sample/img2.jpg',0)
mean_img = mean_filter(img)
cv2.imwrite('./processed_img/img2_mean.jpg',mean_img)
cv2.imshow('img',mean_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

상당히 무식하게 짜버렸지만... 일단 개념은 저런 느낌입니다... 아직 구현 능력이 부족해서 이렇게 됐지만 꼭 다시 수정해서 깔끔하게 만들어보겠습니다. mean filter의 경우 마스크의 내부의 모든 intensity의 평균을 중앙값에 넣음으로써 이미지를 전체적으로 흐리게 만드는 방법입니다. 개념적으로도 가장 간단한 필터링 방법이었습니다.

 

median filter

결과 :

코드 : 

import cv2
import numpy as np

def median_filter(img):
    h,w = img.shape[:2]
    img1 = img
    for i in range(1,h-1):
        for j in range(1,w-1):
            values = [img[i-1][j-1],img[i][j-1],img[i+1][j-1],img[i-1][j],img[i][j],img[i+1][j],img[i-1][j+1],img[i][j+1],img[i+1][j+1]]
            values.sort()
            img1[i][j] = values[4]
    return img1

img = cv2.imread('./img_sample/img2.jpg',0)
median_img = median_filter(img)
cv2.imwrite('./processed_img/img2_median.jpg',median_img)
cv2.imshow('img',median_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

median filter는 마스크 내부의 intensity 들의 중앙값을 사용하는 방법으로 mean filter와 비교하면 엣지는 비교적 선명하나, 내부가 비교적 많이 뭉개지는 느낌이 납니다. 어떤 작업을 수행할지에 따라 어떤 방법으로 필터링을 진행할지 선택하면 되겠죠.

 

k-nearest neighbor filter

결과 :

코드 : 

import cv2
import numpy as np

def k_nearest_neighbor_filter(n,img):
    h,w = img.shape[:2]
    img1 = img
    for i in range(1,h-1):
        for j in range(1,w-1):
            values = [img[i-1][j-1],img[i][j-1],img[i+1][j-1],img[i-1][j],img[i][j],img[i+1][j],img[i-1][j+1],img[i][j+1],img[i+1][j+1]]
            tmp = {0:abs(int(img[i-1][j-1])-int(img[i][j])),1:abs(int(img[i][j-1])-int(img[i][j])),2:abs(int(img[i+1][j-1])-int(img[i][j])),3:abs(int(img[i-1][j])-int(img[i][j])),4:abs(int(img[i][j])-int(img[i][j])),5:abs(int(img[i+1][j])-int(img[i][j])),6:abs(int(img[i-1][j+1])-int(img[i][j])),7:abs(int(img[i][j+1])-int(img[i][j])),8:abs(int(img[i+1][j+1])-int(img[i][j]))}
            tmp1 = sorted(tmp.items(), key=lambda x: x[1])
            tmp2 = 0
            for i in range(n):
                tmp2 += values[tmp1[i][0]]
            #print(img1[i][j], tmp2/n)
            img1[i][j] = tmp2/n
    return img1

img = cv2.imread('./img_sample/img2.jpg',0)
k_nearest_neighbor_img = k_nearest_neighbor_filter(4,img)
cv2.imwrite('./processed_img/img2_k_nearest_neighbor.jpg',k_nearest_neighbor_img)
cv2.imshow('img',k_nearest_neighbor_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

이 방법은 좀 특이한데요. 마스크의 중앙값과 마스크 내부의 모든 값의 각각 차의 절댓값을 구해서 작은 순서대로 k개를 선택해 그 평균을 이용하는 방법입니다. 이게 무슨 소린가 싶으실 것 같은데요. 예를 들어서 3*3의 배열이 있고 그 값들이 0~8까지 순서대로 들어가 있다고 가정해보겠습니다([[0 1 2][3 4 5][6 7 8]]). 여기서 중앙값은 4가 되고 중앙값을 각각의 모든 값에서 뺀 절댓값들은 4,3,2,1,0,1,2,3,4 이렇게 나열되겠죠. 가령 k를 4라고 한다면 여기서 0,1,1,2까지가 작은 순서대로 k개를 선택한 것이므로 여기서 차의 절댓값을 구하기 전의 원래 intensity인 2,3,4,5의 평균을 구해서 4의 위치에 집어넣는 방식입니다. 상당히 난해한 방법이죠. 귀찮은 만큼 필터링을 거쳤음에도 원본과 거의 차이가 없는 사진을 만들어준다는 게 신기하긴 합니다만(처음에는 이게 필터링이 제대로 안된게 아닌가? 했지만 원본이랑 데이터를 하나하나 비교해보니 분명히 스무딩이 완료된 후의 사진이긴 했습니다. 잘은 모르겠지만 원본에 큰 손상없이 용량을 줄인다거나 혹은 특수한 작업의 전처리 용도로 사용되는 방법이 아닌가 합니다)... 다른 방법들에 비해 연산이 많으니 잘 선택해서 사용해야 할 것 같습니다.

 

오늘은 이렇게 3가지의 필터링 방법을 알아봤는데요. 원래는 하나씩 하려고 했는데 주제가 같은데 방법만 조금씩 달랐던 친구들이라 이렇게 한 번에 진행해봤습니다. 긴 글 봐주셔서 감사합니다.

반응형