본문 바로가기
Image Processing/OpenCV

[OpenCV] 필터링 (Filtering)

by AteN 2022. 11. 25.

최댓값/최솟값 필터링

  • 입력 영상의 줌심 화소에서 마스크로 씌워진 영역의 입력 화소들을 가져와 계수를 구성하고 그중에서 최댓값과 최솟값을 출력 화소로 결정하는 방법이다. 측, 최댓값 필터링은 게수중에서 최대값을 통과시켜 출력 화소가 되고, 최솟값 필터링은 최솟값을 통과시켜 출력 화소가 된다.
import numpy as np, cv2
 
def minmax_filter(image, ksize, mode):
    rows, cols = image.shape[:2]
    dst = np.zeros((rows, cols), np.uint8)
    center = ksize // 2                                 # 마스크 절반 크기

    for i in range(center, rows - center):             # 입력 영상 순회
        for j in range(center, cols - center):
            # 마스크 영역 행렬 처리 방식
            y1, y2 = i - center, i + center + 1          # 마스크 높이 범위
            x1, x2 = j - center, j + center + 1          # 마스크 너비 범위
            mask = image[y1:y2, x1:x2]                   # 마스크 영역
            dst[i, j] = cv2.minMaxLoc(mask)[mode]
    return dst
image = cv2.imread("img/min_max.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")
 
minfilter_img = minmax_filter(image, 3, 0)               	# 3x3 마스크 최솟값 필터링
maxfilter_img = minmax_filter(image, 3, 1)               	# 3x3 마스크 최솟값 필터링
 
cv2.imshow("image", image)
cv2.imshow("minfilter_img", minfilter_img)
cv2.imshow("maxfilter_img", maxfilter_img)
cv2.waitKey(0)
 
 

 

평균값 필터링

  • 마스크로 씌워진 영역의 입력 화소들을 가져와서 그 화소들의 평균을 구하여 출력 화소로 지정하는 방법이다. 마스크 영역의 화소값들을 평균하기 때문에 블러링 효과가 나타난다. 회선에서 블러링 마스크를 적용한 것과 결과가 같다
import numpy as np, cv2
 
def average_filter(image, ksize):
    rows, cols = image.shape[:2]
    dst = np.zeros((rows, cols), np.uint8)
    center = ksize // 2                                 # 마스크 절반 크기

    for i in range(rows):           # 입력 영상 순회
        for j in range(cols):
            y1, y2 = i - center, i + center + 1        # 마스크 높이 범위
            x1, x2 = j - center, j + center + 1        # 마스크 너비 범위
            if y1 < 0 or y2 > rows or x1 < 0 or x2 > cols :
                dst[i, j] = image[i, j]
            else:
                mask = image[y1:y2, x1:x2]                 # 범위 지정
                dst[i, j] = cv2.mean(mask)[0]
    return dst
 
image = cv2.imread("img/filter_avg.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")
 
avg_img  = average_filter(image, 5)                    # 사용자 정의 평균값 필터 함수
blur_img = cv2.blur(image, (5, 5), borderType=cv2.BORDER_CONSTANT) # OpenCV의 블러링 함수
box_img  = cv2.boxFilter(image, ddepth=-1, ksize=(5, 5))   # OpenCV의 박스 필터 함수
 
cv2.imshow("image", image),
cv2.imshow("avg_img", avg_img)
cv2.imshow("blur_img", box_img)
cv2.imshow("box_img", box_img)
cv2.waitKey(0)

미디언 필터링

  • 미디언이라는 용어처럼 중간값을 나타낸다,
  • 일정 여역에서 다른 화소드로가 밝기가 심하게 차이가 나는 화소들은 임펄스 잡음(impluse noise) 이나 소금 - 후추 잡음(salt & pepper noise)일 가능성이 높다. 미디언 필터링 과정에서 마스크 영역 내의 심하게 차이가 나는 화소들은 정렬로 인해서 최하위 값이나 최상위 값이 된다. 미디언 필터링에서 중간값 이외의 다른 나머지 값들은 출력 화소로 지정되지 않고 제거된다. 이러한 이유로 미디언 필터링은 임펄스 잡음이나 소금-후추 잡음을 잘 제거해 준다. 그리고 평균 값 필터링에 비하면 블러링 현상이 적다
  • 다만, 마스크의 크기가 커지면 잡음 제거 성능은 향상되지만, 모든 화소를 순회하며 마스크 범위에 대해 정렬 알고리즘을 수행해야 하는 부담 대문에 수행 시간이 기하급수적으로 증가한다.
    • 임펄스 잡음 : 0 또는 255의 픽셀 값과 같이 두력하게 잘못된 픽셀 값을 갖는 noise
    • 소금 -후추 잡음 : 영상 내에서 검은색 또는 흰색 점의 형태로 발생하는 noise
import numpy as np, cv2
 
def median_filter(image, ksize):
    rows, cols = image.shape[:2]
    dst = np.zeros((rows, cols), np.uint8)
    center = ksize // 2                                 # 마스크 절반 크기

    for i in range(center, rows - center):              # 입력 영상 순회
        for j in range(center, cols - center):
            y1, y2 = i - center, i + center + 1             # 마스크 높이 범위
            x1, x2 = j - center, j + center + 1             # 마스크 너비 범위
            mask = image[y1:y2, x1:x2].flatten()            # 마스크 영역

            sort_mask = cv2.sort(mask, cv2.SORT_EVERY_COLUMN)    # 정렬 수행
            dst[i, j] = sort_mask[sort_mask.size//2]                  # 출력화소로 지정
    return dst
 
def salt_pepper_noise(img, n):
    h, w = img.shape[:2]
    x, y = np.random.randint(0, w, n), np.random.randint(0, h, n)
    noise = img.copy()
    for (x,y) in zip(x,y):
        noise[y, x] = 0 if np.random.rand() < 0.5 else 255
    return noise
 
image = cv2.imread("img/median2.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")
noise = salt_pepper_noise(image, 500)
med_img1 = median_filter(noise, 3)                            # 사용자 정의 함수
med_img2 = cv2.medianBlur(noise, 3)                          # OpenCV 제공 함수
 
cv2.imshow("image", image),
cv2.imshow("noise", noise),
cv2.imshow("median - User", med_img1)
cv2.imshow("median - OpenCV", med_img2)
cv2.waitKey(0)

 

가우시안 필터링

  • 스무딩(smoothing)은 영상의 세세한 부분을 회선을 통해서 부드럽게 하는 기법으로 블러링과 같은 의미이다. 스무딩 처리에 사용되는 대표적인 방법으로 가우시안 필터링이 있다
  • 가우시안 필터링은 가우시안 분포를 마스크의 게수로 사용하여 회선을 수행하는 것을 말한다
  • 가우시안 분포는 같은 표현으로 정규 분포(normal distribution)이라고 한다
  • 정규 분포는 특정 값의 출현 비율을 그래프로 그렸을 때, 평균에서 가장 큰 수치를 가지며, 평균의 기준으로 좌우 대칭 형태가 나타나고, 좌우 양끝으로 갈수록 급격하게 수치가 낮아지는 종의 모양의 형태를 보인다
  • 정보분포를 평균과 표준 편차를 이용해서 나타낼수 있으며, 표준편차가 커지면 그래프의 폭이 널어지고 표준편차가 작아지면 폭이 좁아진다.
  • 가우시안 분포를 회선 마스크로 적용하려면 2차원 행렬로 구성해야 한다
  • OpenCV에서 cv2.GaussianBlur() 함수로 가우시안 스무딩을 수행한다
  • 다른 방법으로 cv2.sepFilter2D() 함수에 1차원 가우시안 계수를 적용 가능하다. 이 때 1차원 가우시안 계수는 cv2.getGaussianKernel() 함수로 생성한다
import numpy as np, cv2
def getGaussianMask(ksize, sigmaX, sigmaY):
    sigma = 0.3 * ((np.array(ksize) - 1.0) * 0.5 - 1.0) + 0.8  # 표준 편차
    if sigmaX <= 0: sigmaX = sigma[0]
    if sigmaY <= 0: sigmaY = sigma[1]

    u = np.array(ksize)//2            # 커널 크기 절반
    x = np.arange(-u[0], u[0]+1, 1)   # x 방향 범위
    y = np.arange(-u[1], u[1]+1, 1)   # y 방향 범위
    x, y = np.meshgrid(x, y)          # 정방 행렬 생성

    ratio = 1 / (sigmaX*sigmaY * 2 * np.pi)
    v1 = x ** 2 / (2 * sigmaX ** 2)
    v2 = y ** 2 / (2 * sigmaY ** 2 )
    mask = ratio * np.exp(-(v1+v2))  # 2차원 정규 분포 수식 
    return mask / np.sum(mask)
 
image = cv2.imread("img/smoothing.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")
 
ksize = (5, 17)                                        # 크기는 가로x세로로 표현
gaussian_2d = getGaussianMask(ksize, 0, 0)
gaussian_1dX = cv2.getGaussianKernel(ksize[0], 0, cv2.CV_32F)   # 가로 방향 마스크
gaussian_1dY = cv2.getGaussianKernel(ksize[1], 0, cv2.CV_32F)   # 세로 방향 마스크

gauss_img1 = cv2.filter2D(image, -1, gaussian_2d)     # 사용자 생성 마스크 적용
gauss_img2 = cv2.GaussianBlur(image, ksize, 0)
gauss_img3 = cv2.sepFilter2D(image, -1, gaussian_1dX, gaussian_1dY)
 
titles = ['image','gauss_img1','gauss_img2','gauss_img3']
[cv2.imshow(t, eval(t)) for t in titles]
cv2.waitKey(0)
 

댓글