파이썬 강의/openCV

파이썬 openCV 22. 노이즈 제거 : 메디안(median)과 하이브리드 메디안(hybrid median)

마리사라 2020. 12. 14. 14:33
반응형

파이썬 openCV 22번째 강의는 노이즈 제거의 방법인 median 필터와 hybrid median 필터입니다. 이전까지의 노이즈는 가우시안 노이즈를 제거하는 방법이었다면, 이번 강의와 다음 강의는 솔트 앤 페퍼 노이즈를 제거하는 방법입니다.


0. solt-and-pepper와 median?

median과 hybird median을 알아보기 전에 솔트 앤 페퍼(solt-and-pepper)노이즈를 알아야 합니다.

솔트 앤 페퍼 노이즈의 예시(출처 : 위키백과)

솔트 앤 페퍼 노이즈는 영상에 희고(salt - 소금) 검은(pepper - 후추) 노이즈가 끼는 현상을 말합니다. 이러한 노이즈는 영상에 점들이 뿌려져 있다고 해서 점 잡음이라고도 합니다. 솔트 앤 페퍼 노이즈는 영상 신호의 전송 중 잡음이 끼었을 때 발생하며, 꼭 그레이 스케일뿐 아니라 컬러 영상에서도 발생할 수 있습니다.

 

솔트 앤 페퍼 노이즈는 단순한 스무딩으로 지우기에는 한계가 있습니다. 그래서 나온것이 바로 median 필터와 그의 개량형인 hybrid median 필터입니다. median 필터는 중앙값이라는 뜻의 median이라는 단어처럼 일정 범위(3 x 3 또는 5 x 5) 내의 화소들의 중앙값을 출력 화소로 정하는 필터입니다.

a = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5], b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

만약 a와 b라는 범위가 있으면 median 필터를 거친 출력화소는 a = 3, b = 5.5가 될 것입니다.

median 필터의 결과들(출처 : 위키백과)

 

hybrid median 필터는 median 필터의 변형입니다.

a b c d e
f g h i j
k l m(원래 화소) n o
p q r s t
u v w x y

hybrid median 필터는 5 x 5의 범위를 기준으로 좌상->우하 대각선(a, g, m, s, y)의 중앙값, 우상-> 좌하 대각선(e, i, m, q, u)의 중앙값, 5 x 5 범위 전체의 중앙값을 각각 구하고 세 가지 값들의 중앙값이 출력 화소가 됩니다. 그렇기에 hybrid median 필터는 median 필터보다 조금 더 나은 결과물이 나오게 됩니다.

 

이 두 필터 모두 많은 연산을 거치기 때문에 파이썬에서는 속도가 매우 느립니다.


1. openCV에서의 median과 hybrid median

img = cv2.imread('lenna.png')
height, width, channel = img.shape

우선 이미지를 불러옵니다. 영상은 그레이 스케일이나 컬러나 상관이 없습니다.

 

noise = img.copy()
salt = int(height * width * channel * 0.1)
for i in range(salt):
    row = int(np.random.randint(99999, size=1) % height)
    col = int(np.random.randint(99999, size=1) % width)
    ch = int(np.random.randint(99999, size=1) % channel)
    noise[row][col][ch] = 255 if np.random.randint(99999, size=1) % 2 == 1 else 0

이제 salt-and-pepper를 적용한 영상을 생성합니다. 컬러 영상이기 때문에 height와 width에 channel까지 계산해야 됩니다. salt-and-pepper를 적용할 때 맨 마지막에 있는 숫자(0.1)가 확률입니다. 숫자가 커지면 커질수록 노이즈가 많이 끼게 됩니다.

 

out1 = np.zeros((height + 2, width + 2, channel), dtype=np.float)
out1[1: 1 + height, 1: 1 + width] = img.copy().astype(np.float)
temp1 = out1.copy()

out2 = np.zeros((height + 4, width + 4, channel), dtype=np.float)
out2[2: 2 + height, 2: 2 + width] = img.copy().astype(np.float)
temp2 = out2.copy()

이제 출력할 영상들에 제로 패딩을 해줍니다. 이때 median 필터는 3 x 3, hybrid median 필터는 5 x 5의 크기로 진행할 것이기에 제로 패딩의 크기를 다르게 해줍니다.

 

for i in range(height):
    for j in range(width):
        for k in range(channel):
            out1[1 + i, 1 + j, k] = np.median(temp1[i:i + 3, j:j + 3, k])

            hybrid_temp1 = np.median((temp2[i, j, k], temp2[i + 1, j + 1, k], temp2[i + 2, j + 2, k],
                                      temp2[i + 3, j + 3, k], temp2[i + 4, j + 4, k]))
            hybrid_temp2 = np.median((temp2[i + 4, j, k], temp2[i + 3, j + 1, k], temp2[i + 2, j + 2, k],
                                      temp2[i + 1, j + 3, k], temp2[i, j + 4, k]))
            hybrid_temp3 = np.median((temp2[i: i + 5, j:j + 5, k]))
            out2[2 + i, 2 + j, k] = np.median((hybrid_temp1, hybrid_temp2, hybrid_temp3))

이제 중첩 for문을 사용해서 화소를 연산하도록 합니다. median 필터는 단순히 3 x 3 크기의 범위의 중앙값, hybrid median 필터는 위에서 설명한 3가지의 중앙값들의 중앙값을 계산해서 화소로 정합니다.

 

out1 = out1[1:1 + height, 1:1 + width].astype(np.uint8)
out2 = out2[2:2 + height, 2:2 + width].astype(np.uint8)

마지막으로 출력할 영상을 int(정수)형으로 형 변환해주고 제로 패딩을 제거하게 되면 끝이 납니다.

 

이제 결과 영상입니다.

원본 영상
솔트 앤 페퍼 적용 영상
median 필터 적용 영상
hybrid median 필터 적용 영상

median 필터와 hybrid median 필터 모두 salt-and-pepper를 잘 제거한 모습을 보실 수 있습니다. 이때 hybrid median 필터가 median 필터보다 조금 더 흐린 모습을 보실 수 있는데, 이는 median 필터의 범위가 더 작고 salt-and-pepper의 확률도 크게 높지 않아서 차이가 생긴 것입니다.


2. 마치며

hybrid median 필터는 median 필터보다 더 강력한 효과를 보이기에 실제로는 hybrid median 필터가 median 필터보다 더 자주 쓰이는 방법입니다.

 

def median():
    img = cv2.imread('lenna.png')
    height, width, channel = img.shape

    noise = img.copy()
    salt = int(height * width * channel * 0.1)
    for i in range(salt):
        row = int(np.random.randint(99999, size=1) % height)
        col = int(np.random.randint(99999, size=1) % width)
        ch = int(np.random.randint(99999, size=1) % channel)
        noise[row][col][ch] = 255 if np.random.randint(99999, size=1) % 2 == 1 else 0

    out1 = np.zeros((height + 2, width + 2, channel), dtype=np.float)
    out1[1: 1 + height, 1: 1 + width] = img.copy().astype(np.float)
    temp1 = out1.copy()

    out2 = np.zeros((height + 4, width + 4, channel), dtype=np.float)
    out2[2: 2 + height, 2: 2 + width] = img.copy().astype(np.float)
    temp2 = out2.copy()

    for i in range(height):
        for j in range(width):
            for k in range(channel):
                out1[1 + i, 1 + j, k] = np.median(temp1[i:i + 3, j:j + 3, k])

                hybrid_temp1 = np.median((temp2[i, j, k], temp2[i + 1, j + 1, k], temp2[i + 2, j + 2, k],
                                          temp2[i + 3, j + 3, k], temp2[i + 4, j + 4, k]))
                hybrid_temp2 = np.median((temp2[i + 4, j, k], temp2[i + 3, j + 1, k], temp2[i + 2, j + 2, k],
                                          temp2[i + 1, j + 3, k], temp2[i, j + 4, k]))
                hybrid_temp3 = np.median((temp2[i: i + 5, j:j + 5, k]))
                out2[2 + i, 2 + j, k] = np.median((hybrid_temp1, hybrid_temp2, hybrid_temp3))

    out1 = out1[1:1 + height, 1:1 + width].astype(np.uint8)
    out2 = out2[2:2 + height, 2:2 + width].astype(np.uint8)

    cv2.imshow('original', img)
    cv2.imshow('salt', noise)
    cv2.imshow('median', out1)
    cv2.imshow('hybrid median', out2)
    cv2.waitKey(0)
반응형