본문 바로가기

OpenCV

모폴로지(Morphology) 연산, 이미지 피라미드(Image Pyramid)

1. 모폴로지 연산

■ 모폴로지 연산은 노이즈 제거, 구멍 채우기, 끊어진 선 이어 붙이기 등에 사용되며 그레이스케일 영상과 바이너리(binary) 영상에 모두 적용 가능하다.

주로 검은색과 흰색으로만 구성되어 있는 이진화된 영상에서 객체 모양을 변형하는 용도로 사용된다.

■ 모폴로지 연산으로 침식, 팽창, 열림, 닫힘 연산이 있다. 해당 연산들을 알기 위해서는 먼저 구조화 요소(structuring element)에 대해 알아야 한다.

■ 구조화 요소는 마치 필터링에 사용되는 마스크처럼 사용되는 0과 1로 구성된 작은 크기의 행렬이다.

구조화 요소는 1이 채워진 모양에 따라 다양한 모양(사각형, 타원형, 십자형 등)으로 정의할 수 있다.

다양한 형태의 구조화 요소 [출처] https://m.blog.naver.com/dorergiverny/223063828290

■ 구조화 요소 행렬에서 노란색으로 표시한 원소는 모폴로지 연산 결과가 저장될 위치를 나타내는 고정점(anchor point)이다. 일반적으로 구조 요소의 중심을 고정점으로 사용한다.

■ OpenCV에서는 cv2.getStructuringElement(shape, ksize, anchor) 함수를 이용해 구조화 요소 커널을 생성할 수 있다.

- shape은 구조화 요소 커널의 모양으로 cv2.MORPH_RECT을 지정하면 사각형, cv2.MORPH_EPLIPSE을 지정하면 타원형, cv2.MORPH_CROSS를 지정하면 십자형

- ksize는 커널 크기

- anchor은 구조화 요소의 기준(중심)점으로 shape을 십자형(cv2.MORPH_CROSS)으로 지정했다면, 중심점으로 (-1, 1)을 지정할 경우 구조화 요소의 중앙을 십자가 중심 좌표로 사용한다.

1.1 침식(erosion) 연산

■ 침식 연산은 이미지의 외곽을 골고루 깎아 내는 연산으로 구조화 요소 커널의 고정점이 영상/이미지 전체에 대해 스캔하면서, 구조화 요소가 이미지 전경(객체 영억) 내부에 완전히 겹치지 않을 때 0으로 변경한다.

■ 예를 들어 다음 그림은, 이미지의 배경이 검은색 영역, 전경이 흰색 영역이라고 할 때, 사각형 모양인 구조화 요소 커널을 사용해 침식 연산을 수행한 결과이다.

[출처] [ch07] 이진 영상 처리 - 모폴로지 (1) : 침식과 팽창

 침식 연산의 결과는 위의 그림처럼 이미지를 나타내는 영역이 축소된다 <=> 배경이 확대된다.

이는 구조화 요소가 전체 이미지를 스캔하면서, 객체 영역 내부에 완전히 겹치면 고정점 위치의 픽셀을 255로 설정하고, 겹치지 않으면 0으로 변경해 객체 영역의 외각을 골고루 깎아낸 것을 확인할 수 있다.

■ 침식 연산은 이렇게 어떤 객체 영역의 주변을 깎는 기능을 하기 때문에 객체 영역이 작다면 아예 제거하는 것도 가능하다. 이를 이용하여 픽셀에 있는 노이즈를 제거하거나 겹쳐 있는 부분을 떼어내는 데 효과적으로 사용할 수 있다.

■ OpenCV에서는 cv2.erode(src, kernel, anchor, iterations, borderType, borderValue) 함수를 이용해 침식 연산을 수행할 수 있다.

- src는 입력 영상/이미지

- kernel에는 구조화 요소 커널을 지정

- anchor는 cv2.getStructuringElement()와 동일. (-1, 1)로 지정하면 구조화 요소의 중앙을 고정점으로 사용

- iterations는 침식 연산 적용 반복 횟수

- boderType은 가장자리 픽셀 보정 방법

- boderValue는 boderType이 BORDER_CONSTANT인 경우의 보정 값

img = cv2.imread('cat1.jpg',0)
ret, img_binary = cv2.threshold(img, 175, 255, cv2.THRESH_BINARY)

## 사각형 3x3 구조화 요소 커널 생성 
k = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
print(k)
```#결과#```
[[1 1 1]
 [1 1 1]
 [1 1 1]]
````````````

## 침식 연산 적용
erosion = cv2.erode(img_binary, k)
plt.subplot(131),plt.imshow(img, cmap = 'gray'),plt.title('original'),plt.axis('off')
plt.subplot(132),plt.imshow(img_binary, cmap = 'gray'),plt.title('binary'),plt.axis('off')
plt.subplot(133),plt.imshow(erosion, cmap = 'gray'),plt.title('erosion'),plt.axis('off')
plt.show()

 

1.2 팽창(dilatation) 연산

■ 팽창 연산은 침식 연산과 반대로 객체 영역을 확장 <=> 배경 영역을 축소하는 연산이다.

침식 연산에서 구조화 요소 커널이 객체 영역과 완전히 겹치지 않으면 0으로 변경했는데, 팽창 연산은 반대로 완전히 겹치지 않으면 255(흰색)으로 변경한다.

■ OpenCV에서는 cv2.dilate(src, kernel, dst, anchor, iterations, bordeType, borderValue) 함수를 이용해 팽창 연산을 수행할 수 있다.

- 모든 파라미터는 cv2.erode( )와 동일하다.

img = cv2.imread('cat1.jpg',0)
ret, img_binary = cv2.threshold(img, 175, 255, cv2.THRESH_BINARY)

## 사각형 3x3 구조화 요소 커널 생성 
k = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
print(k)
```#결과#```
[[1 1 1]
 [1 1 1]
 [1 1 1]]
````````````

## 팽창 연산 적용 
dilatation = cv2.dilate(img_binary, k, iterations=3) # 반복 3번
plt.subplot(131),plt.imshow(img, cmap = 'gray'),plt.title('original'),plt.axis('off')
plt.subplot(132),plt.imshow(img_binary, cmap = 'gray'),plt.title('binary'),plt.axis('off')
plt.subplot(133),plt.imshow(dilatation, cmap = 'gray'),plt.title('dilatation'),plt.axis('off')
plt.show()

- 팽창 연산을 적용한 결과, 위의 그림처럼 객체 내부의 검은색 구멍이 사라지거나 좁아진 것을 확인할 수 있다.

■ 침식 연산과 팽창 연산 결과를 비교해 보면, 침식 연산은 객체 영역의 픽셀(255)를 제거하고 팽창 연산은 배경 영역의 픽셀(0)을 제거한 것을 확인할 수 있다. 어떻게 보면 침식 연산은 객체 영역 일부를 검은색으로 덮고 팽창 연산은 하얀색으로 덮었다고 볼 수 있다.

이는 침식 연산은 검은색(어두운) 영역의 노이즈를 제거하는 효과가 있고 팽창 연산은 흰색(밝은) 영역의 노이즈를 제거하는 효과가 있는 것으로 볼 수 있다.

 

1.3 열림(opening) 연산과 닫힘(closing) 연산

■ 침식 연산이나 팽창 연산을 수행할 경우 노이즈를 제거하지만 원래 모양(객체 영역의 크기)이 변형된다. 열림과 닫힘 연산은 침식 연산과 팽창 연산을 한 번씩 적용하여 원래 모양을 크게 변형하지 않으면서 노이즈를 제거할 수 있다.

■ 열림 연산은 침식 연산을 수행한 후 팽창 연산을 수행한 것이고, 닫힘 연산은 팽창 연산을 수행한 후 침식 연산을 수행한 것이다. 

■ 이렇게 침식과 팽창 연산을 적용하는 순서가 다르기 때문에 열림 연산과 닫힘 연산은 서로 다른 효과가 발생한다.

- 열림 연산은 침식 연산을 먼저 수행하기 때문에 객체 영역의 일부 픽셀이 먼저 제거한 후, 팽창 연산을 적용한다. 이로 인해 서로 붙어 있는 것처럼 보이는 혹은 연결되어 있는 독립된 개체를 분리하거나 돌출된 영역을 제거하는 데 효과적이다.

- 또한, 주변보다 밝은 노이즈를 제거하는 데 효과적이다.

- 닫힘 연산은 팽창 연산을 먼저 수행하기 때문에 객체 내부의 구멍을 먼저 채운 후, 침식 연산을 적용한다. 이로 인해 구멍을 메우거나  끊어져 보이는 혹은 끊어진 개체를 연결하는데 효과적이다.

■ 열림 연산과 닫힘 연산 그리고 그밖의 모폴로지 연산은 OpenCV에서 cv2.morphologyEx(src, op, kernel, dst, anchor, iteration, borderType, borderValue) 함수를 이용해 수행할 수 있다.

- src는 입력 영상/이미지

- op에는 모폴로지 연산 종류를 지정한다. 모폴로지 연산 종류는 다음과 같다.

모폴로지 연산 종류 의미
cv2.MORPH_OPEN 열림 연산
cv2.MORPH_COLSE 닫힘 연산
cv2.MORPH_GRADIENT 그래디언트 연산
cv2.MORPH_TOPHAT 탑햇 연산
cv2.MORPH_BLACKHAT 블랙햇 연산

- kernel에는 구조화 요소 커널을 지정

- anchor는 cv2.getStructuringElement()와 동일. (-1, 1)로 지정하면 구조화 요소의 중앙을 고정점으로 사용

- iterations는 모폴로지 연산 적용 반복 횟수

- boderType은 가장자리 픽셀 보정 방법

- boderValue는 boderType이 BORDER_CONSTANT인 경우의 보정 값

img = cv2.imread('opening.jpg',0)
ret, img_binary = cv2.threshold(img, 175, 255, cv2.THRESH_BINARY)

## 사각형 9x9 구조화 요소 커널 생성 
k = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))

## 열림 연산 적용
opening = cv2.morphologyEx(img_binary, cv2.MORPH_OPEN, k)
plt.subplot(121),plt.imshow(img_binary, cmap = 'gray'),plt.title('binary'),plt.axis('off')
plt.subplot(122),plt.imshow(opening, cmap = 'gray'),plt.title('opening'),plt.axis('off')
plt.show()

[출처] https://diyver.tistory.com/62

- 열림 연산을 적용한 결과, 객체 영역의 크기를 거의 유지하면서 연결되어 있던 부분은 끊기고 살짝 돌출되었던 영역이 제거된 것을 볼 수 있다.

img = cv2.imread('closing.jpg',0)
ret, img_binary = cv2.threshold(img, 175, 255, cv2.THRESH_BINARY)

## 사각형 11x11 구조화 요소 커널 생성 
k = cv2.getStructuringElement(cv2.MORPH_RECT, (11,11))

## 닫힘 연산 적용
closing = cv2.morphologyEx(img_binary, cv2.MORPH_CLOSE, k)

 

plt.subplot(121),plt.imshow(img_binary, cmap = 'gray'),plt.title('binary'),plt.axis('off')
plt.subplot(122),plt.imshow(closing, cmap = 'gray'),plt.title('closing'),plt.axis('off')
plt.show()

 

[출처] https://diyver.tistory.com/62

- 닫힘 연산을 적용한 결과, 객체 영역의 크기를 거의 유지하면서 끊겨 있던 픽셀들이 모두 합쳐진 것을 볼 수 있다.

 

1.4 그밖의 모폴로지 연산

1.4.1 그래디언트(gradient) 연산

■ 그래디언트 연산은 팽창 연산을 적용한 이미지에서 침식 연산을 적용한 이미지를 빼는 연산으로, 객체의 외곽선을 추출하는 연산이다. gradient  =  dilate(src, element) - erode(src, element)

## 사각형 3x3 구조화 요소 커널 생성 
k = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))

## 그래디언트 연산 적용
gradient  = cv2.morphologyEx(closing, cv2.MORPH_GRADIENT, k)
plt.subplot(121),plt.imshow(closing, cmap = 'gray'),plt.title('closing'),plt.axis('off')
plt.subplot(122),plt.imshow(gradient, cmap = 'gray'),plt.title('gradient'),plt.axis('off')
plt.show()

 

1.4.2 탑햇(top hat) 연산과 블랙햇(black hat) 연산

■ 탑햇 연산은 원본 이미지에서 열림 연산을 뺀 결과로 밝은 영역을 강조하는 효과가 있다.

■ 반대로 블랙햇 연산은 닫힘 연산에서 원본 이미지를 뺀 결과로 어두운 영역을 강조하는 효과가 있다.

img = cv2.imread('moon.jpg')
img.shape
```#결과#```
(424, 410, 3)
````````````

## 사각형 9x9 구조화 요소 커널 생성 
k = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))

## 탑햇 연산 적용 
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, k)

## 블랫햇 연산 적용 
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, k)
plt.subplot(131),plt.imshow(img, cmap = 'gray'),plt.title('original'),plt.axis('off')
plt.subplot(132),plt.imshow(tophat, cmap = 'gray'),plt.title('tophat'),plt.axis('off')
plt.subplot(133),plt.imshow(blackhat, cmap = 'gray'),plt.title('blackhat'),plt.axis('off')
plt.show()

 

2. 이미지 피라미드

참고) https://deep-learning-study.tistory.com/187?category=946336

 

[파이써 OpenCV] 이미지 피라미드(다운샘플링, 업샘플링) - cv2.pryDown, cv2.pryUp

황선규 박사님의 , 패스트 캠퍼스 OpenCV 강의를 공부한 내용을 정리해 보았습니다. [파이썬 OpenCV] 영상의 확대와 축소(크기 변환) - cv2.resize 함수 설명, interpolation 인자 황선규 박사님의 , 패스트

deep-learning-study.tistory.com

■ 이미지 피라미드는 다음 그림과 같이 피라미드 형태처럼 업샘플링과 다운샘플링을 통해 이미지를 단계적으로 확대/축소하는 것을 말하며, 가우시안 피라미드와 라플라시안 피라미드가 있다.

[출처] https://deep-learning-study.tistory.com/187?category=946336

2.1 가우시안 피라미드(gaussian pyramid)

■ 가우시안 피라미드는 가우시안 필터(블러링)를 적용한 뒤 이미지 업샘플링 또는 다운샘플링을 수행한 것을 말한다. 

■ OpenCV에서는 5 x 5 크기의 가우시안 필터를 적용한 후, 모든 짝수 행과 열을 제거해 입력 영상/이미지의 크기를 1/4로 축소하는 cv2.pyrDown(src, dst, dstsize, borderType) 함수와 0으로 채워진 짝수 행과 열을 새로 삽입한 후, 가우시안 필터를 적용해 자연스러운 형태(주변 픽셀과 비슷하게 만드는)로 만드는 cv2.pyrUp(src, dst, dstsize, borderType) 함수를 제공한다. 

- 전자가 다운샘플링, 후자가 업샘플링이며 cv2.pyrUp( ) 함수는 입력 영상/이미지의 크기를 4배 확대한다.

- src는 입력 영상/ 이미지

- distsize는 출력 영상/이미지의 크기이며 별도로 지정하지 않으면 입력 영상/이미지 가로, 세로 크기의 2배가 적용된다.

- borderType은 가장자리 픽셀 보정 방법

img = cv2.imread('cat1.jpg')

## 가우시안 피라미드 - 이미지 축소(x 1/2 x 1/2 = x 1/4) 
smaller = cv2.pyrDown(img)

## 가우시안 피라미드 - 이미지 확대(x 2 x 2 = x 4) 
bigger = cv2.pyrUp(img)

print(img.shape, smaller.shape, bigger.shape)
```#결과#```
(145, 216, 3) (73, 108, 3) (290, 432, 3)
````````````
cv2.imshow('img', img)
cv2.imshow('pyrDown', smaller)
cv2.imshow('pyrUp', bigger)

cv2.waitKey()
cv2.destroyAllWindows()

- 이미지를 확대한 경우 화질이 떨어지는 것을 확인할 수 있다.

- 이미지를 확대하기 위해 cv2.pyrUp( ) 함수를 이용하면, 0으로 채워진 짝수 행과 열이 삽입되기 때문에 확대된 이미지의 화질이 떨어진 것이다.

2.2 라플라시안 피라미드(laplacian pyramid)

cv2.pyrUp( ) 함수를 이용할 경우 화질이 떨어지는 문제점을 개선한 방법이 라플라시안 피라미드이다.

■ 라플라시안 피라미드의 방법은 간단하다. 화질 저하 문제가 발생하는 이유는 원본과 달리 0으로 채워진 새로운 짝수 행과 열이 존재하기 때문이다. 그러므로 cv2.pyrUp( ) 함수를 적용한 이미지(확대된 이미지)와 원본 이미지를 빼서 차이를 계산하고 확대된 이미지에 이 차분을 더해 복원하면 된다.

- cv2.pyrUp( )으로 단순 확대 시 손실되는 고주파 성분을 차분이 보존하고 저주파 성분(전체적인 구조)는 확대된 이미지가 가지고 있으므로, 두 성분을 결합함으로써 원본에 가까운 화질의 이미지를 얻을 수 있는 것이다.

img = cv2.imread('cat1.jpg')

## 가우시안 피라미드 - 이미지 확대(x 4) 
bigger = cv2.pyrUp(img)

img.shape, bigger.shape
```#결과#```
((145, 216, 3), (290, 432, 3))
````````````

- 원본 이미지와 확대된 이미지의 차분을 구하기 위해선 두 이미지의 크기가 동일해야 한다. 그러므로 다음과 같이 확대된 이미지의 크기를 재조정한 다음 (라플라시안) 차분을 계산한다.

## 확대 이미지 크기 재조정
bigger_resized = cv2.resize(bigger, (img.shape[1], img.shape[0]))

## 원본 이미지에서 확대 이미지 빼기
laplacian = cv2.subtract(img, bigger_resized) # (라플라시안) 차분

## 확대 이미지에 (라플라시안) 차분을 더해서 복원
restored = bigger_resized + laplacian
merged = np.hstack((img, laplacian, bigger_resized, restored))
cv2.imshow('laplacian pyramid', merged)

cv2.waitKey(0)
cv2.destroyAllWindows()

- 첫 번째 이미지는 원본 이미지, 두 번째 이미지는 차분 결과, 세 번째 이미지는 재조정한 확대 이미지, 마지막 이미지는 화질을 복원한 이미지이다.

'OpenCV' 카테고리의 다른 글

분할(segmentation) (2)  (0) 2024.12.22
분할(segmentation) (1)  (0) 2024.12.22
필터와 블러링  (0) 2024.12.18
기하학적 변환 (2)  (0) 2024.12.16
기하학적 변환 (1)  (0) 2024.12.16