728x90

이미지 그라디언트를 마치고 허프 변환을 어떻게 구현할까 생각해보고 있었는데,

 

opencv를보니 에지 영상에서 허프 변환을 수행했었다.

 

그런데 내가 만든 이미지 그라디언트는 커널 사이즈도 3 x 3으로 고정되어 있고,

 

그렇게 좋은 이미지들을 만드는것 같지는 않아보였다.

 

 

그래서

 

이미지 그라디언트 구현을 공부하면서 조금 연습했으니

 

이번에는 opencv 문서를 보면서 캐니 에지 검출기를 구현해 보려고 한다.

 

 

ref 

docs.opencv.org/master/da/d22/tutorial_py_canny.html

 

 

캐니 에지 검출기 canny edge detector

- 존 F 캐니가 만든 많이 쓰이는 에지 검출기

- 아래의 단계로 구성

 

 

캐니 에지 검출기 구성 단계

1. 노이즈 제거

- 에지 검출기는 노이즈에 강인해야 한다. 그러니 우선  5 x 5 가우시안 필터로 노이즈를 제거하자.

 

2. 이미지 그라디언트 강도, 방향 구하기

- 이미지를 가우시안 필터로 블러링 해주고 나서, 소벨 커널로 수평, 수직 방향으로 필터링해서 그라디언트 구하기

- 이미지 그라디언트를 연산해서 에지 그라디언트 강도와 방향을 구해주자.

* 그라디언트 방향은 에지에 수직.

* 에지 방향 각도는 아래의 식으로 구한 후 반올림을 하여 (0, 45, 90, 135도)  중 하나가 되도록 한다.

 

 

3. 비최대 억제 Non-maximu suppression

- 그라디언트 크기와 방향을 구했다면, 필요없는 픽셀들을 없앨 준비가 되었다.

- 그러려먼 우선 모든 픽셀들에 대해 에지 그라디언트 방향 부근의 픽셀들이 지역 최대치인지 확인하여야 한다.

- 아래 그림을 보면 점 A는 에지이고, 그라디언트 방향은 에지에 수직이며, 점 B와 C는 그라디언트 방향에 있다.

- 점 B와 C를 보고 A가 지역 최대점인지 판단한다. A가 지역 최대가 아니라면, 억제되어 0이 된다.

 

4. 이력 현상 임계화 hystersis thresholding(이력 현상 : 이전의 상태가 현재에 영향을 주는 현상)

- 이제 모든 에지들이 정말로 에지인지 아닌지 판단하기 위해서 두 임계값 minVal, maxVal이 필요하다.

- 그라디언트 강도가 max val보다 크면 에지이고, min val보다 작으면 에지가 아니라고 판단해서 버린다.

- 만약 임계치에 있는 경우에는 연결성을 고려해서 에지 여부를 판단한다.

- 아래의 에지 A는 maxVal보다 크니 확실히 에지로 보지만, 에지 C는 에지 A에 연결되었으니 에지로본다.

- 하지만 에지 B는 minVal보다 크고, 에지 C랑 같은 곳에 있지만 A랑 연결이 안되있으므로 에지가 아닌것으로 본다.

 

 

 

 

 

2020. 12. 1  1:50

- 위 이론 내용 정리 후 자료 찾아가면서 구현하다보니 늦어졌다.

- 코드 일부분, 시각화 결과를 우선 보인 후 전체 코드를 맨 뒤에다가 배치.

 

 

전체 구성

1. 기본 이미지 시각화

2. 가우시안 필터링 적용

3. 이미지 그라디언트 방향 영상, 그라디언트 크기 영상 시각화

4. 비최대 억제 결과 시각화

5. 이력 현상 임계화 추가 -> 캐니 에지 검출기 완성

6. 캐니 에지 검출 과정 모든 이미지 시각화

7. 전체 코드

 

 

후기

- 기존의 opencv에서 제공하는 함수에 비하면 원하는 결과도 잘 나오지 않고, 속도도 매우 느린 편이다.

- 하지만 직접 수식, 개념들을 완전히는 아니더라도 조금씩 이해해 가면서 구현하는 과정이 좋았다.

- 이걸 하는 중간에 보니 내가 연결성 부분이나 범람 채움 알고리즘 같은 내용들을 넘어갔던것 같다. 내일 한번 봐야할듯.

 

 

 

 

1. 기본 이미지 시각화

- opencv 예제에서 제공하는 기본 messi5.jpg

import numpy as np
import matplotlib.pyplot as plt
from utils import padding, gaussian_filtering
from PIL import Image
import cv2

plt.figure(figsize=(8,12))
img = Image.open("./res/messi5.jpg").convert("L")
img = np.asarray(img)
plt.imshow(img, cmap="gray")

 

2. 가우시안 필터링 적용

- opencv doc에서 kernel size가 5인 가우시안 커널을 사용한다고 한다. 시그마는 0.8로 지정

plt.figure(figsize=(8,12))
img_blur = gaussian_filtering(img,k_size=5, sigma=0.8)
plt.imshow(img_blur,cmap="gray")

 

 

 

 

 

3. 이미지 그라디언트 방향 영상, 그라디언트 크기 영상 시각화

 

grad_intensity = get_gradient_intensity(img_blur)
plt.title("gradient direction image")
plt.imshow(grad_intensity[:,:,0])

 

 

plt.title("gradient magnitude image")
plt.imshow(grad_intensity[:,:,1])

 

 

 

 

 

4. 비최대 억제 결과 시각화

- 그라디언드 방향에 존재하는 픽셀 세 개 비교.

- 중앙 픽셀이 가장 크면 나두고, 같은 방향 다른 픽셀이 크면 억제하여 0

 

supressed_img = non_maximum_suppression(grad_intensity)
plt.imshow(supressed_img)

 

 

 

 

 

5. 이력 현상 임계화 추가 -> 캐니 에지 검출기 완성

 

 

 

img = Image.open("./res/messi5.jpg").convert("L")
img = np.asarray(img)
img_blur = gaussian_filtering(img,k_size=5, sigma=1)
grad_intensity = get_gradient_intensity(img_blur)
suppressed_img = non_maximum_suppression(grad_intensity)
canny_edge = hysteresis_threshold(suppressed_img=suppressed_img,min_th=100, max_th=200)


plt.figure(figsize=(64,32))
plt.subplot(6, 1, 1)
plt.title("original")
plt.imshow(img)
plt.subplot(6, 1, 2)
plt.title("gaussian blur")
plt.imshow(img_blur)
plt.subplot(6, 1, 3)
plt.title("gradient direction")
plt.imshow(grad_intensity[:, :,0])
plt.subplot(6, 1, 4)
plt.title("gradient magnitude")
plt.imshow(grad_intensity[:, :,1])
plt.subplot(6, 1, 5)
plt.title("non maximum suppression ")
plt.imshow(suppressed_img)
plt.subplot(6, 1, 6)
plt.title("canny edge image")
plt.imshow(canny_edge)

 

 

 

 

 

6. 캐니 에지 검출 과정 모든 이미지 시각화

 

def canny_edge_visualize(img, min_th=100, max_th=200):
    """
    canny_edge_visualize
    - visualize all images from original to canny edge
    
    parameter
    - img : original image
    - min_th : minimal threshold value
    - max_th : maximum threshold value
    """
    img_blur = gaussian_filtering(img,k_size=5, sigma=1)
    grad_intensity = get_gradient_intensity(img_blur)
    suppressed_img = non_maximum_suppression(grad_intensity)
    canny_edge = hysteresis_threshold(suppressed_img=suppressed_img,min_th=100, max_th=200)

    plt.figure(figsize=(64,32))
    plt.subplot(6, 1, 1)
    plt.title("original")
    plt.imshow(img)
    plt.subplot(6, 1, 2)
    plt.title("gaussian blur")
    plt.imshow(img_blur)
    plt.subplot(6, 1, 3)
    plt.title("gradient magnitude")
    plt.imshow(grad_intensity[:, :,0])
    plt.subplot(6, 1, 4)
    plt.title("gradient direction")
    plt.imshow(grad_intensity[:, :,1])
    plt.subplot(6, 1, 5)
    plt.title("non maximum suppression ")
    plt.imshow(suppressed_img)
    plt.subplot(6, 1, 6)
    plt.title("canny edge image")
    plt.imshow(canny_edge)
   
img = Image.open("./res/kid.jpg").convert("L")
img = np.asarray(img)
canny_edge_visualize(img)

 

 

 

 

 

 

7. 전체 코드

"""
Canny Edge Detector
- 2020. 12.1 01:50

"""


"""
ref
- https://en.wikipedia.org/wiki/Canny_edge_detector
"""
def sobel_kerenl():
    kernel_x = np.array([
        [-1, 0, 1],
        [-2, 0, 2],
        [-1, 0, 1]
    ])
    kernel_y = np.array([
        [1, 2, 1],
        [0, 0, 0],
        [-1, -2, -1]
    ])
    return kernel_x, kernel_y

def get_rounded_gradient_angle(ang):
    """
    get_rounded_gradient_angle
    - gradient direction angle round to one of four angles
    
    return : one of four angles
            representing vertical, horizontal and two diagonal direction
    
    parameteres
    ---------------
    ang : gradient direction angle
    """
    vals = [0, np.pi*1/4, np.pi*2/4, np.pi*3/4, np.pi*4/4]
    interval = [np.pi*1/8, np.pi*3/8, np.pi * 5/8, np.pi* 7/8]

    if ang < interval[0] and ang >= interval[-1]:
        ang = vals[0]
    elif ang < interval[1] and ang >= interval[0]:
        ang = vals[1]
    elif ang < interval[2] and ang >= interval[1]:
        ang = vals[2]
    elif ang < interval[3] and ang >= interval[2]:
        ang = vals[3]
    else:
        ang = vals[4]
    return ang
        

def get_gradient_intensity(img):
    """
    get gradient_intensity
    - calculate gradient direction and magnitude
    
    return (rows, cols, 2) shape of image about grad direction and mag
    
    parameteres
    ------------
    img : blured image
    """
    k_size = 3
    rows, cols = img.shape
    kernel_x, kernel_y = sobel_kerenl()
    pad_img = padding(img, k_size=k_size)
    res = np.zeros((rows, cols, 2))
    sx, sy = 0, 0

    for i in range(0, rows):
        for j in range(0, cols):
            boundary = pad_img[i:i+k_size, j:j+k_size]
            sx = np.sum(kernel_x * boundary)
            sy = np.sum(kernel_y * boundary)            
            ang = np.arctan2(sy, sx)
            mag = abs(sx) + abs(sy)
            if ang < 0:
                ang = ang + np.pi
            ang = get_rounded_gradient_angle(ang)
            res[i, j, 0] = ang
            res[i, j, 1] = mag
    return res



def check_local_maximum(direction=None, boundary=None):
    """
    check_local_maximum
    - check if center value is local maximum depend on gradient direction
    return True if center valus is local maximum
    
    parameter
    ------------
    direction : gradient direction
    boundary : region of image for finding local maximum
    """
    if direction == 0: # 0 degree, east and west direction
        kernel = np.array([
            [0, 0, 0],
            [1, 1, 1],
            [0, 0, 0]
        ])

    elif direction == 1: #45 degree, north east and south west direction
        kernel = np.array([
            [0, 0, 1],
            [0, 1, 0],
            [1, 0, 0]
        ])
    elif direction == 2: #90 degree, north & south direction
        kernel = np.array([
            [0, 1, 0],
            [0, 1, 0],
            [0, 1, 0]
        ])
    else : #135 degree, north west & south east direction
        kernel = np.array([
            [1, 0, 0],
            [0, 1, 0],
            [0, 0, 1]
        ])
    
    max_val = np.max(kernel * boundary)
    if boundary[1,1] == max_val: #local maximum
        return True
    else: # not local maximu
        return False

    
    
def non_maximum_suppression(grad_intensity=grad_intensity):
    """
    non_maximum_suppression
    - check a pixel wiht it's neighbors in the direction of gradient
    - if a pixel is not local maximum, it is suppressed(means to be zero)
    """
    directions = [0, np.pi*1/4, np.pi*2/4, np.pi*3/4, np.pi*4/4]
    
    k_size = 3
    grad_direction = grad_intensity[:, :, 0]
    grad_magnitude = grad_intensity[:, :, 1]
    
    rows, cols = grad_magnitude.shape
    pad_img = padding(grad_magnitude, k_size=k_size)
    res_img = np.zeros((rows,cols))
    sx, sy = 0, 0
    
    
    for i in range(0, rows):
        for j in range(0, cols):
            direction = directions.index(grad_direction[i,j])
            boundary = pad_img[i:i+k_size, j:j+k_size]
            if check_local_maximum(direction, boundary) == True:
                res_img[i, j] = grad_magnitude[i, j]
            else:
                res_img[i, j] = 0
    return res_img



def hysteresis_threshold(suppressed_img=None, min_th=None, max_th=None):
    """
    hysterisis_threshold
    - check edge is connected to strong edge using eight connectivity
    
    parameter
    ---------------------
    - suppressed img : non maximum suppressed image
    - min_th : minimal threshold value
    - max_th : maximum threshold value
    """
    k_size = 3
    rows, cols = supressed_img.shape
    pad_img = padding(suppressed_img, k_size=k_size)
    res_img = np.zeros((rows,cols))

    eight_connectivity = np.array([
        [1, 1, 1],
        [1, 0, 1],
        [1, 1, 1]
    ])    
    
    for i in range(0, rows):
        for j in range(0, cols):
            if pad_img[i+1, j+1] < min_th:
                res_img[i, j] = 0
            else:
                boundary = pad_img[i:i+k_size, j:j+k_size]
                max_magnitude = np.max(boundary * eight_connectivity)
                if max_magnitude >= max_th : # pixel is connected to real edge
                    res_img[i, j] = 255
                else:
                    res_img[i, j] = 0
    return res_img


def canny_edge_detector(img=None, min_th=None, max_th=None):
    """
    canny_edge_detector
    - detect canny edge from original image
    
    parameter
    - img : original image
    - min_th : minimal threshold value
    - max_th : maximum threshold value
    """
    img_blur = gaussian_filtering(img,k_size=5, sigma=1)
    grad_intensity = get_gradient_intensity(img_blur)
    suppressed_img = non_maximum_suppression(grad_intensity)
    canny_edge = hysteresis_threshold(suppressed_img=suppressed_img,
                                      min_th=min_th, max_th=max_th)
    return canny_edge


def canny_edge_visualize(img, min_th=100, max_th=200):
    """
    canny_edge_visualize
    - visualize all images from original to canny edge
    
    parameter
    - min_th : minimal threshold value
    - max_th : maximum threshold value
    """
    img_blur = gaussian_filtering(img,k_size=5, sigma=1)
    grad_intensity = get_gradient_intensity(img_blur)
    suppressed_img = non_maximum_suppression(grad_intensity)
    canny_edge = hysteresis_threshold(suppressed_img=suppressed_img,min_th=100, max_th=200)

    plt.figure(figsize=(64,32))
    plt.subplot(6, 1, 1)
    plt.title("original")
    plt.imshow(img)
    plt.subplot(6, 1, 2)
    plt.title("gaussian blur")
    plt.imshow(img_blur)
    plt.subplot(6, 1, 3)
    plt.title("gradient magnitude")
    plt.imshow(grad_intensity[:, :,0])
    plt.subplot(6, 1, 4)
    plt.title("gradient direction")
    plt.imshow(grad_intensity[:, :,1])
    plt.subplot(6, 1, 5)
    plt.title("non maximum suppression ")
    plt.imshow(suppressed_img)
    plt.subplot(6, 1, 6)
    plt.title("canny edge image")
    plt.imshow(canny_edge)

    
300x250

+ Recent posts