파이썬 강의/openCV

파이썬 openCV 28. 형태학적 처리 : 열림/닫힘(opening/closing)

마리사라 2021. 1. 18. 14:47
반응형

파이썬 openCV 28번째 강의는 형태학적 처리의 열림과 닫힘(opening/closing) 연산입니다.


0. 열림/닫힘?

이전 강의에서 형태학적 처리의 침식 연산과 팽창 연산을 배웠습니다. 이번에 배울 열림 연산과 닫힘 연산은 침식 연산과 팽창 연산의 응용으로, 열림 연산과 닫힘 연산을 배우시지 않은 분들은 이전 강의를 참고하시기 바랍니다.

2021/01/12 - [파이썬/openCV] - 파이썬 openCV 27. 형태학적 처리 : 침식/팽창(Erosion/Dilation)

 

파이썬 openCV 27. 형태학적 처리 : 침식/팽창(Erosion/Dilation)

파이썬 openCV 27번째 강의는 형태학적 처리의 침식과 팽창(Erosion/Dilation)입니다. 0. 형태학적 처리?  형태학적 처리는 특정한 모양의 형태소(structuring element)를 이진 영상에 적용해서 출력 영상을

marisara.tistory.com

(1) 열림

열림 연산은 연산에 사용되는 형태소가 객체의 내부 영역을 순회할 때 겹치는 모든 점들로 구성되는 객체를 출력하는 연산을 말합니다. 정의는 어렵지만, 결론적으로 주어진 영상에 대하여 침식 연산 이후 팽창 연산을 적용한다는 뜻입니다. 그렇기 때문에 침식 연산의 장점에서 원본 영상과의 차이를 줄이는 결과가 됩니다. 열림 연산을 수학적으로 표현하면 다음과 같습니다.

A = 원본 영상 / B = 형태소

 

(2) 닫힘

닫힘 연산은 열림과 반대로, 팽창 연산이후 침식 연산을 적용합니다. 닫힘 연산은 다른 연산과 다른 특징이 있습니다.

  • 좁은 각극으로 분리되어 있던 두 객체가 해당 간극이 채워짐에 따라 연결됨
  • 결과 영상에서의 객체의 크기가 유사하게 유지됨
  • 구조 요소의 형태에 따라 작은 구멍이 채워질 수 있음

닫힘 연산은 객체 내에서 발생되는 모호한 연결을 강하게 만들거나 비선형적 조도 변화에 의해 발생되는 객체 내부의 구멍을 메우는 등의 분야에 응용됩니다. 닫힘 연산을 수학적으로 표현하면 다음과 같습니다.

A = 원본 영상 / B = 형태소


1. openCV에서의 열림/닫힘

img = cv2.imread('lenna.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
height, width = gray.shape

우선 lenna 영상을 그레이 스케일로 불러옵니다.

 

out = np.zeros((height + 2, width + 2), dtype=np.uint8)
out2 = np.zeros((height + 2, width + 2), dtype=np.uint8)
out[1:1 + height, 1:1+width] = gray.copy()
opened = np.zeros_like(gray)
closed = np.zeros_like(gray)

연산을 위한 제로 패딩과 결과 영상이 들어갈 빈 배열을 만들어 줍니다.

 

for i in range(height):
    for j in range(width):
        temp = out[i:i+3, j:j+3]
        opened[i][j] = np.min(temp)
        closed[i][j] = np.max(temp)

첫 번째 중첩 for문 입니다. 여기서는 각각 침식 연산과 팽창 연산의 결과를 넣어 줍니다.

 

out[1:1 + height, 1:1+width] = opened.copy()
out2[1:1 + height, 1:1 + width] = closed.copy()

이제 다음 연산을 위해 이전 결과 값을 재로 패딩해 줍니다.

 

for i in range(height):
    for j in range(width):
        temp = out[i:i+3, j:j+3]
        temp2 = out2[i:i + 3, j:j + 3]
        opened[i][j] = np.max(temp)
        closed[i][j] = np.min(temp2)
opened = opened[1:-1, 1:-1]
closed = closed[1:-1, 1:-1]

두 번째 중첩 for문 입니다. 여기서는 첫 번째와 반대로 각각 팽창 연산과 침식 연산의 결과를 저장합니다. 그 후 마무리로 제로 패딩을 제거하면 끝입니다.

 

array = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]])
opening = cv2.dilate(cv2.erode(gray, array), array)
closing = cv2.erode(cv2.dilate(gray, array), array)

위의 과정을 openCV에서 지원하는 함수로 표현하면 다음과 같습니다. 열림은 침식 후 팽창, 닫힘은 팽창 후 침식입니다.

 

이제 결과를 확인해보겠습니다.

원본 영상
좌 : 중첩 for문을 통한 열림 연산 / 우 : 함수를 통한 열림 연산

 

좌 : 중첩 for문을 통한 닫힘 연산 / 우 : 함수를 통한 닫힘 연산

두 연산의 가장 뚜렷한 차이점은 눈을 보시면 알 수 있습니다. 열림 연산은 눈동자가 대부분 검은색이지만, 닫힘 연산은 원래의 눈동자와 비슷함을 볼 수 있습니다.


2. 마치며

열림과 닫힘은 침식과 팽창보다 더 많이 사용되는 기술입니다. 그렇기 때문에 이번 강의와 이전 강의를 번갈아 가면서 이해하셨으면 합니다.

def opening_closing():
    img = cv2.imread('lenna.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    height, width = gray.shape

    out = np.zeros((height + 2, width + 2), dtype=np.uint8)
    out2 = np.zeros((height + 2, width + 2), dtype=np.uint8)
    out[1:1 + height, 1:1+width] = gray.copy()
    opened = np.zeros_like(gray)
    closed = np.zeros_like(gray)

    for i in range(height):
        for j in range(width):
            temp = out[i:i+3, j:j+3]
            opened[i][j] = np.min(temp)
            closed[i][j] = np.max(temp)

    out[1:1 + height, 1:1+width] = opened.copy()
    out2[1:1 + height, 1:1 + width] = closed.copy()

    for i in range(height):
        for j in range(width):
            temp = out[i:i+3, j:j+3]
            temp2 = out2[i:i + 3, j:j + 3]
            opened[i][j] = np.max(temp)
            closed[i][j] = np.min(temp2)
    opened = opened[1:-1, 1:-1]
    closed = closed[1:-1, 1:-1]

    array = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]])
    opening = cv2.dilate(cv2.erode(gray, array), array)
    closing = cv2.erode(cv2.dilate(gray, array), array)

    cv2.imshow('original', gray)
    cv2.imshow('opening', opened)
    cv2.imshow('closing', closed)
    cv2.imshow('opening2', opening)
    cv2.imshow('closing2', closing)
    cv2.waitKey(0)


opening_closing()
반응형