파이썬 openCV 21번째 강의는 나가오-마츠야마 필터(Nagao-Matsuyama filter)입니다. 나가오-마츠야마 필터도 쿠와하라(Kuwahara) 필터처럼 이름이 잘 알려지지 않은 필터입니다. 이 필터 또한 국내에서는 잘 알려지지 않아서 해외 사이트를 많이 참고해서 썼습니다.
0. 나가오-마츠야마 필터(Nagao-Matsuyama filter)
나가오-마츠야마 필터(Nagao-Matsuyama filter)는 다른말로는 모서리 보존 스무딩 필터(Edge-Preserving Smoothing Filters)라고도 합니다. Kuwahara 필터와 마찬가지로 모서리를 보존하면서 스무딩을 진행하는 필터입니다. 이 역시 가우시안 잡음을 잡는 데 사용하는 필터로 사용됩니다.
Nagao-Matsuyama는 Kuwahara 필터와 비슷하면서 다릅니다. Kuwahara와 Nagao-Matsuyama는 둘 다 일정 영역의 밝기의 분산을 기준으로, 그 영역의 평균값이 출력 영상의 화소 값이 되는 것은 같습니다.
하지만 Kuwahara 필터가 5 x 5의 공간을 4개의 영역으로 나누는 것에 비해, Nagao-Matsuyama는 5 x 5의 공간을 기준으로 8개의 영역으로 나눕니다.
그렇기에 Kuwahara 필터보다 연산량이 많아서 처리속도는 느리지만 Kuwahara 필터보다는 원본 영상에 가까운 영상을 얻을 수 있습니다.
1. openCV에서의 Nagao-Matsuyama
이제 코드를 확인해 보겠습니다.
img = cv2.imread('lenna.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
height, width = gray.shape
img_noise = np.zeros_like(gray)
for i in range(height):
for j in range(width):
make_noise = np.random.normal()
set_noise = 10 * make_noise
img_noise[i][j] = gray[i][j] + set_noise
out = np.zeros((height + 4, width + 4), dtype=np.float)
out[2: 2 + height, 2: 2 + width] = img_noise.copy().astype(np.float)
이 부분까지는 이전 강의인 Kuwahara 필터와 중복되는 부분입니다. 이미지를 불러오고, 그레이 스케일로 만든 후, 노이즈를 생성하고, 제로 패딩을 하는 부분입니다.
for i in range(height):
for j in range(width):
point1 = np.ravel(out[i + 1:i + 4, j:j + 2])
point1 = np.append(point1, out[i + 2, j + 2])
point2 = np.ravel(out[i + 3:i + 5, j + 1:j + 4])
point2 = np.append(point2, out[i + 2, j + 2])
point3 = np.ravel(out[i + 1:i + 4, j + 3:j + 5])
point3 = np.append(point3, out[i + 2, j + 2])
point4 = np.ravel(out[i:i + 2, j + 1:j + 4])
point4 = np.append(point4, out[i + 2, j + 2])
point5 = np.ravel(out[i:i + 2, j:j + 2])
point5 = np.append(point5, out[i + 2, j + 1])
point5 = np.append(point5, out[i + 1, j + 2])
point5 = np.append(point5, out[i + 2, j + 2])
point6 = np.ravel(out[i + 3:i + 5, j:j + 2])
point6 = np.append(point6, out[i + 2, j + 1])
point6 = np.append(point6, out[i + 3, j + 2])
point6 = np.append(point6, out[i + 2, j + 2])
point7 = np.ravel(out[i + 3:i + 5, j + 3:j + 5])
point7 = np.append(point7, out[i + 2, j + 3])
point7 = np.append(point7, out[i + 3, j + 2])
point7 = np.append(point7, out[i + 2, j + 2])
point8 = np.ravel(out[i:i + 2, j + 3:j + 5])
point8 = np.append(point8, out[i + 1, j + 2])
point8 = np.append(point8, out[i + 2, j + 3])
point8 = np.append(point8, out[i + 2, j + 2])
if min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point1):
out[2 + i, 2 + j] = np.mean(point1)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point2):
out[2 + i, 2 + j] = np.mean(point2)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point3):
out[2 + i, 2 + j] = np.mean(point3)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point4):
out[2 + i, 2 + j] = np.mean(point4)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point5):
out[2 + i, 2 + j] = np.mean(point5)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point6):
out[2 + i, 2 + j] = np.mean(point6)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point7):
out[2 + i, 2 + j] = np.mean(point7)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point8):
out[2 + i, 2 + j] = np.mean(point8)
이제 중첩 for문입니다. 각각의 포인트에 계산할 화소를 담아 줍니다. 포인트에 담는 부분은 제가 임의로 만든 부분이라 코드를 더 줄이실 수 있으면 변형해서 쓰셔도 됩니다.
이후 Kuwahara 필터와 마찬가지로 각각의 분산의 최솟값이 해당하는 영역의 평균값을 출력 화소에 저장하게 됩니다. 이때 Nagao-Matsuyama 필터는 Kuwahara 필터의 연산의 두배 가량 되므로, 시간이 매우 오래 걸립니다. 저는 결과가 나올 때까지 약 5분의 시간이 걸렸으므로, 인내를 가지고 기다리셔야 합니다.
out = out[2:2 + height, 2:2 + width].astype(np.uint8)
마지막으로 제로 패딩 된 부분을 없애고, 정수형으로 변환하면 끝입니다.
이제 결과 영상을 확인해보겠습니다.
결과는 Kuwahara와 비슷하지만 조금 더 원본 영상에 가까운 영상이 나왔습니다.
2. 마치며
Nagao-Matsuyama 필터는 단순히 노이즈를 제거하는 작업뿐 아니라 영상 분할의 전처리 과정에도 적합한 필터입니다. 이는 지금은 다루지 않을 예정이며, 나중에 영상 분할을 설명하게 될 때 언급하도록 하겠습니다.
def nagao():
img = cv2.imread('lenna.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
height, width = gray.shape
img_noise = np.zeros_like(gray)
for i in range(height):
for j in range(width):
make_noise = np.random.normal()
set_noise = 10 * make_noise
img_noise[i][j] = gray[i][j] + set_noise
out = np.zeros((height + 4, width + 4), dtype=np.float)
out[2: 2 + height, 2: 2 + width] = img_noise.copy().astype(np.float)
for i in range(height):
for j in range(width):
point1 = np.ravel(out[i + 1:i + 4, j:j + 2])
point1 = np.append(point1, out[i + 2, j + 2])
point2 = np.ravel(out[i + 3:i + 5, j + 1:j + 4])
point2 = np.append(point2, out[i + 2, j + 2])
point3 = np.ravel(out[i + 1:i + 4, j + 3:j + 5])
point3 = np.append(point3, out[i + 2, j + 2])
point4 = np.ravel(out[i:i + 2, j + 1:j + 4])
point4 = np.append(point4, out[i + 2, j + 2])
point5 = np.ravel(out[i:i + 2, j:j + 2])
point5 = np.append(point5, out[i + 2, j + 1])
point5 = np.append(point5, out[i + 1, j + 2])
point5 = np.append(point5, out[i + 2, j + 2])
point6 = np.ravel(out[i + 3:i + 5, j:j + 2])
point6 = np.append(point6, out[i + 2, j + 1])
point6 = np.append(point6, out[i + 3, j + 2])
point6 = np.append(point6, out[i + 2, j + 2])
point7 = np.ravel(out[i + 3:i + 5, j + 3:j + 5])
point7 = np.append(point7, out[i + 2, j + 3])
point7 = np.append(point7, out[i + 3, j + 2])
point7 = np.append(point7, out[i + 2, j + 2])
point8 = np.ravel(out[i:i + 2, j + 3:j + 5])
point8 = np.append(point8, out[i + 1, j + 2])
point8 = np.append(point8, out[i + 2, j + 3])
point8 = np.append(point8, out[i + 2, j + 2])
if min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point1):
out[2 + i, 2 + j] = np.mean(point1)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point2):
out[2 + i, 2 + j] = np.mean(point2)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point3):
out[2 + i, 2 + j] = np.mean(point3)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point4):
out[2 + i, 2 + j] = np.mean(point4)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point5):
out[2 + i, 2 + j] = np.mean(point5)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point6):
out[2 + i, 2 + j] = np.mean(point6)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point7):
out[2 + i, 2 + j] = np.mean(point7)
elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4), np.var(point5), np.var(point6), np.var(point7), np.var(point8)) == np.var(point8):
out[2 + i, 2 + j] = np.mean(point8)
out = out[2:2 + height, 2:2 + width].astype(np.uint8)
cv2.imshow('original', gray)
cv2.imshow('noise', img_noise)
cv2.imshow('out', out)
cv2.waitKey(0)