OpenCV 비전 실무7장 · Median 과 Bilateral 필터, 필터 비교8 / 21← 이전목차다음 →MetaContents

7장. Median 과 Bilateral 필터, 필터 비교

Bilateral 필터

조별 과제에서 "우리 조의 의견"을 정한다고 해 봅시다. 교실 전체의 의견을 다 섞으면 다른 조 생각까지 끼어듭니다. 그래서 가까이 앉은 친구이면서 같은 조인 친구의 의견만 크게 듣고, 다른 조 친구의 의견은 거의 듣지 않습니다. Bilateral 필터가 이렇게 섞습니다. 가까운 칸이면서 밝기가 비슷한 칸만 크게 섞고, 경계 건너편처럼 밝기가 크게 다른 칸은 거의 섞지 않습니다.

거리 무게, 색 차이 무게, 둘을 곱한 무게의 세 상자를 나란히 놓은 비교 그림

첫째 상자는 Gaussian 필터에서 본 거리 무게입니다. 둘째 상자가 Bilateral 필터에 더해진 색 차이 무게입니다. 셋째 상자처럼 두 무게를 곱하므로, 둘 가운데 하나라도 작으면 그 칸은 평균에 거의 들어가지 않습니다. 여기서 가까이 앉은 친구가 거리 무게, 같은 조 친구가 색 차이 무게입니다. 비유와 다른 점도 있습니다. 실제 필터는 조를 딱 잘라 나누지 않고, 밝기 차이가 클수록 무게를 조금씩 줄여 갑니다.

계단 모양 밝기 한 줄에 Gaussian 과 Bilateral 을 건 결과를 견준 선 그래프

그림은 밝기 60 에서 200 으로 뛰는 경계 하나를 가로 한 줄로 줄여 본 것입니다. 연한 선이 노이즈 낀 줄, 초록 점선이 깨끗한 원본입니다. 다홍 선(Gaussian)은 자글자글함은 줄였지만 경계 앞뒤를 섞어 비탈을 만들었습니다. 보라 선(Bilateral)은 자글자글함도 줄이고 계단도 거의 그대로 지켰습니다.

숫자로 보는 두 무게

sigmaColor 를 30 으로 줬을 때, 가운데 칸과 밝기가 얼마나 다르면 무게가 얼마가 되는지 보입니다. OpenCV 는 종 모양(가우시안) 곡선으로 무게를 계산합니다.

가운데 칸과 밝기 차이0103060150
색 차이 무게1.00.950.610.14거의 0

밝기 차이가 sigmaColor(30)만큼이면 무게는 0.61, 그 두 배(60)면 0.14 로 줄고, 150 처럼 경계 건너편이면 0 에 가깝습니다.

짧은 예시

가로 한 줄의 다섯 칸으로 손계산해 봅니다. 가운데 칸은 100 이고, 왼쪽 둘은 같은 쪽(104, 98), 오른쪽 둘은 경계 건너편(250, 246)입니다. 계산을 쉽게 하려고 거리 무게는 모두 같다고 둡니다.

Python
import numpy as np

row = np.array([104, 98, 100, 250, 246], dtype=float)
center = 100

# 그냥 평균: 경계 건너편 두 칸이 함께 섞인다
print(round(float(row.mean()), 1))                  # 결과: 159.6

# 색 차이 무게: 밝기 차이가 클수록 작아지는 종 모양 곡선(sigmaColor 30)
# np.exp 는 종 모양 곡선의 값을 계산하는 데 쓰는 함수다. 여기서는 무게를 얻는 도구로만 본다
weight = np.exp(-((row - center) ** 2) / (2 * 30 ** 2))
print([round(float(w), 3) for w in weight])         # 결과: [0.991, 0.998, 1.0, 0.0, 0.0]

# 무게를 곱해 더한 뒤 무게의 합으로 나눈다(무게를 준 평균)
print(round(float((row * weight).sum() / weight.sum()), 1))   # 결과: 100.7

그냥 평균은 159.6 으로 경계 건너편 쪽으로 끌려갔지만, 무게를 준 평균은 100.7 로 같은 쪽 칸만 섞었습니다. 6장에서 본 가중 평균 그대로, 무게 × 값을 모두 더하고 무게의 합으로 나눈 것입니다. Bilateral 필터는 이 무게를 칸마다 밝기 차이를 보고 새로 정한다는 점만 다릅니다.

cv2.bilateralFilter 의 인자

순서인자뜻보통 값
1src고칠 그림. 흑백이나 컬러(채널 1 또는 3), uint8 이나 float32noisy
2d들여다볼 이웃의 지름(칸 수). 0 이하로 주면 sigmaSpace 로 정한다5~9
3sigmaColor밝기가 얼마나 달라도 섞을까. 클수록 다른 색도 섞는다50~100
4sigmaSpace얼마나 먼 칸까지 섞을까. 클수록 먼 칸도 섞는다50~100
Python
import cv2

img = cv2.imread("ch07_sign.png")
# bilateralFilter(그림, d, sigmaColor, sigmaSpace)
smooth = cv2.bilateralFilter(img,    # src: 컬러 그림도 된다
                             9,      # d: 지름 9칸의 이웃을 본다
                             75,     # sigmaColor: 밝기 차이 75 안팎까지 섞는다
                             75)     # sigmaSpace: 거리 무게의 퍼짐
print(smooth.shape, smooth.dtype)    # 결과: (240, 320, 3) uint8

주의할 점

  • cv2.bilateralFilter 는 원본 자리에 바로 덮어쓸 수 없습니다(OpenCV 공식 문서). 늘 smooth = cv2.bilateralFilter(img, ...) 처럼 새 이름으로 받습니다.
  • Salt & Pepper 의 점은 이웃과 밝기 차이가 아주 커서, Bilateral 필터가 보기에는 "경계 건너편"입니다. 그래서 점의 이웃은 점을 섞지 않고, 점 자신도 이웃을 거의 섞지 않아 점이 그대로 남습니다. 점 노이즈는 Median 필터의 몫입니다.
← 이전 절7장 · 경계를 지키는 필터가 필요한 까닭다음 절 →7장 · Bilateral 의 인자와 비용