▩ 이미지 컨투어¶

In [1]:
### Packages
import os
import cv2
import numpy as np
from matplotlib import pyplot as plt
In [2]:
### 출력 영상 크기
plt.rcParams["figure.figsize"] = (16,9) # 그림(figure)의 크기. (가로, 세로) 인치 단위

### 출력 영상에 한글 표시
plt.rcParams['font.family'] = "Gulim"   # 'AppleGothic' in mac
In [3]:
### changes the current working directory
os.chdir(r'D:\image')
In [4]:
### 영상 출력 함수
def fn_imshow(img, axis='off', fig_size=''):
    if fig_size!='': plt.figure(figsize=fig_size)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    plt.imshow(img_rgb)
    if axis!='on': plt.axis('off')
    plt.show()

▢ 이미지 컨투어¶

  • 컨투어(contour)
    • (사물의)윤곽, 등고선
  • 영상에서 컨투어
    • 동일한 색상 또는 강도를 갖는 모든 연속 점(경계를 따라)을 연결하는 곡선
    • 모양 또는 객체 인식
  • OpenCV
    • https://docs.opencv.org/4.x/dd/d49/tutorial_py_contour_features.html
    • https://docs.opencv.org/4.x/d3/dc0/group__imgproc__shape.html

■ 컨투어 찾기¶

  • OpenCV에서 등고선을 찾는 것은 검정색 배경에서 흰색 물체를 찾는 것과 같음

▶ 윤곽선 검출¶

  • 파일: contour_test.png
In [5]:
### 파일
img_file = 'contour_test.png'

### 영상 읽기
img_raw = cv2.imread(img_file)

### color 영상을 Grayscale 영상으로 변환
img_gray = cv2.cvtColor(img_raw, cv2.COLOR_BGR2GRAY)

### Otsu's thresholding
_, img_binary = cv2.threshold(img_gray, 0, 255, 
                              cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

### 닫기 연산
kernel = np.ones((3, 3), np.uint8)
img_morph = cv2.morphologyEx(img_binary, cv2.MORPH_CLOSE, kernel)

### 영상 출력
titles = ["Original", "Gray", "Binary", "Close"]
images = [img_raw, img_gray, img_binary, img_morph]
for i in range(len(images)):
    img_rgb = cv2.cvtColor(images[i], cv2.COLOR_BGR2RGB)
    plt.subplot(2, 2, i+1), plt.imshow(img_rgb)
    plt.title(titles[i])
    plt.axis('off')
plt.show()
No description has been provided for this image

ㅇ findContours() - OpenCV¶

  • dst, contours, hierarchy = cv2.findContours(src, mode, method, contours, hierarchy, offset)
    • src: 입력 영상, 검정과 흰색으로 구성된 바이너리 이미지
    • mode: 컨투어 제공 방식
      • cv2.RETR_EXTERNAL: 가장 바깥쪽 라인만 생성
      • cv2.RETR_LIST: 모든 라인을 계층 없이 생성
      • cv2.RETR_CCOMP: 모든 라인을 2 계층으로 생성
      • cv2.RETR_TREE: 모든 라인의 모든 계층 정보를 트리 구조로 생성
    • method: 근사 값 방식
      • cv2.CHAIN_APPROX_NONE: 근사 없이 모든 좌표 제공
      • cv2.CHAIN_APPROX_SIMPLE: 컨투어 꼭짓점 좌표만 제공
      • cv2.CHAIN_APPROX_TC89_L1: Teh-Chin 알고리즘으로 좌표 개수 축소
      • cv2.CHAIN_APPROX_TC89_KC0S: Teh-Chin 알고리즘으로 좌표 개수 축소
    • contours(optional): 검출한 컨투어 좌표 (list type)
    • hierarchy(optional): 컨투어 계층 정보 (-1 = 해당 없음)
      • [Next, Prev, FirstChild, Parent]: [다음 윤곽선, 이전 윤곽선, 내곽 윤곽선, 외곽 윤곽선]
    • offset(optional): ROI 등으로 인해 이동한 컨투어 좌표의 오프셋
In [6]:
### 컨투어 찾기 - 꼭짓점 좌표
contours, hierarchy = cv2.findContours(img_morph, 
                                       cv2.RETR_EXTERNAL, 
                                       cv2.CHAIN_APPROX_SIMPLE)

### 컨투어의 수
len(contours)
Out[6]:
5

■ 컨투어 그리기¶

ㅇ drawContours() - OpenCV¶

  • cv2.drawContours(img, contours, contourIdx, color, thickness)
    • img: 입력 영상
    • contours: 그림 그릴 컨투어 배열 (cv2.findContours() 함수의 반환 결과)
    • contourIdx: 그림 그릴 컨투어 인덱스, -1: 모든 컨투어 표시
    • color: 색상 값
    • thickness: 선 두께, 0: 채우기
In [7]:
### 컨투어 그리기
img_out = img_raw.copy()
_ = cv2.drawContours(img_out, contours, -1, (0,0,0), 3)

### 영상 출력
fn_imshow(img_out)
No description has been provided for this image
In [8]:
### 글자 추가
idx = 0
for i in contours:
    M = cv2.moments(i)
    cx = int(M['m10']/M['m00'])
    cy = int(M['m01']/M['m00'])
    cv2.circle(img_out, (cx, cy), radius=5, color=(255,255,255), thickness=-1)
    cv2.putText(img_out, str(idx), (cx, cy), 0, 2, (255,255,255), 2, cv2.LINE_AA)
    idx += 1

### 영상 출력
fn_imshow(img_out)
No description has been provided for this image

■ 컨투어의 길이¶

  • 둘레길이의 합

ㅇ arcLength() - OpenCV¶

  • cv2.arcLength(curve, closed)
    • curve: 외곽선 좌표. numpy.ndarray. shape=(K, 1, 2)
    • closed: True이면 폐곡선으로 간주(시작점과 끝점을 연결)
In [9]:
### 컨투어의 길이
for cnt in contours:
    cont_len = cv2.arcLength(cnt, True)
    print(cont_len)
1436.2426406145096
1170.371707201004
1364.5971026420593
1434.8284270763397
1287.2245188951492

■ 컨투어의 면적¶

  • 영역의 크기

ㅇ contourArea() - OpenCV¶

  • cv2.contourArea(contour, oriented=None)
    • contour: 외곽선 좌표. numpy.ndarray. shape=(K, 1, 2)
    • oriented: True이면 외곽선 진행 방향에 따라 부호 있는 면적을 반환. 기본값은 False
In [10]:
### 컨투어의 면적
for cnt in contours:
    area = cv2.contourArea(cnt)
    print(area)
120681.5
93536.0
81792.0
128880.0
118835.0

■ 컨투어를 둘러싸는 박스(Bounding Rectangle)¶

  • 컨투어 라인을 둘러싸는 사각형을 그리는 방법
    • boundingRect(): 직선 경계 직사각형
    • minAreaRect(): 면적이 최소인 회전된 직사각형

▶ 직선 경계 직사각형¶

In [11]:
### 직선 경계 직사각형
cnt = contours[2]
x,y,w,h = cv2.boundingRect(cnt)
_ = cv2.rectangle(img_out,(x,y),(x+w,y+h),(0,0,0),2)

### 영상 출력
fn_imshow(img_out)
No description has been provided for this image

▶ 면적이 최소인 회전된 직사각형¶

In [12]:
### 회전된 직사각형
cnt = contours[2]
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
_ = cv2.drawContours(img_out,[box],0,(0,0,255),2)

### 영상 출력
fn_imshow(img_out)
No description has been provided for this image

▣ 면적이 최소인 회전된 직사각형¶

  • contour_test2.png

image.png

■ 볼록체(Convex Hull)¶

  • 수학: 주어진 도형을 포함하는 최소의 철체(凸體)(들어간 데가 전혀 없는 입체)
  • 컨투어 포인트를 모두 포함하는 볼록한 외곽선

ㅇ OpenCV¶

  • cv2.convexHull()
In [13]:
### 파일
img_file = 'contour_test2.png'

### 영상 읽기
img_raw = cv2.imread(img_file)

### color 영상을 Grayscale 영상으로 변환
img_gray = cv2.cvtColor(img_raw, cv2.COLOR_BGR2GRAY)

### Otsu's thresholding
_, img_binary = cv2.threshold(img_gray, 0, 255, 
                              cv2.THRESH_BINARY+cv2.THRESH_OTSU)

### 컨투어 찾기 - 꼭짓점 좌표
contours, hierarchy = cv2.findContours(img_binary, 
                                       cv2.RETR_EXTERNAL, 
                                       cv2.CHAIN_APPROX_SIMPLE)
In [14]:
### 출력 영상
img_out = img_raw.copy()

### 볼록체 그리기
for cnt in contours:
    ### Convex Hull
    hull = cv2.convexHull(cnt)
    cv2.drawContours(img_out, [hull], 0, (0, 255, 0), 3)
    
### 영상 출력
fn_imshow(img_out)
No description has been provided for this image

■ Minumum Enclosing Circle과 Fitting Ellipse¶

  • Minumum Enclosing Circle: 컨투어 라인을 완전히 포함하는 가장 작은 원
    • cv2.minEnclosingCircle()
  • Fitting Ellipse: 컨투어 라인에 적합한 타원
    • cv2.fitEllipse()
In [15]:
### 출력 영상
img_out = img_raw.copy()

### Minumum Enclosing Circle과 Fitting Ellipse
for cnt in contours:
    ### Minumum Enclosing Circle
    (x,y), r = cv2.minEnclosingCircle(cnt)
    cv2.circle(img_out, (int(x), int(y)), int(r), (0, 255, 0), 3)
    
    ### Fitting Ellipse
    ellipse = cv2.fitEllipse(cnt)
    cv2.ellipse(img_out, ellipse, (0, 0, 255), 3)
    
### 영상 출력
fn_imshow(img_out)
No description has been provided for this image

■ 근사화(Contour Approximation)¶

  • 꼭짓점의 수를 줄여서 반환

ㅇ approxPolyDP() - OpenCV¶

  • cv2.approxPolyDP(contour, epsilon, closed)
    • contour: 대상 컨투어 좌표
    • epsilon: 근사 값 정확도, 오차 범위
    • closed: 컨투어의 닫힘 여부
In [16]:
### 파일
img_file = 'bbox.png'

### 영상 읽기
img_raw = cv2.imread(img_file)

### color 영상을 Grayscale 영상으로 변환
img_gray = cv2.cvtColor(img_raw, cv2.COLOR_BGR2GRAY)

### Otsu's thresholding
_, img_binary = cv2.threshold(img_gray, 0, 255, 
                              cv2.THRESH_BINARY+cv2.THRESH_OTSU)
In [17]:
### 컨투어 찾기 - 꼭짓점 좌표
contours, hierarchy = cv2.findContours(img_binary, 
                                       cv2.RETR_EXTERNAL, 
                                       cv2.CHAIN_APPROX_SIMPLE)

### 근사화 그리기
img_appr = img_raw.copy()
for cnt in contours:
    ### 오차 범위 지정 - 0.05
    epsilon = 0.05 * cv2.arcLength(cnt, True)
    ### 근사화
    approx = cv2.approxPolyDP(cnt, epsilon, True)
    ### 근사화 그리기
    cv2.drawContours(img_appr, [approx], -1, (0,0,255), 3)
    
### 컨투어 그리기
img_cont = img_raw.copy()
_ = cv2.drawContours(img_cont, contours, -1, (0,255,0), 3)
In [18]:
### 영상 출력
titles = ["Original", "Binary", "Contour", "Approximation"]
images = [img_raw, img_binary, img_cont, img_appr]
for i in range(len(images)):
    plt.subplot(2, 2, i+1)
    plt.imshow(cv2.cvtColor(images[i], cv2.COLOR_BGR2RGB))
    plt.title(titles[i])
    plt.axis('off')
plt.show()
No description has been provided for this image

■ 컨투어 계층(Contours Hierarchy)¶

  • https://docs.opencv.org/4.x/d9/d8b/tutorial_py_contours_hierarchy.html

ㅇ findContours() - OpenCV¶

  • contours, hierarchy = cv2.findContours(image, mode, method)
    • image: 입력 영상, 검정과 흰색으로 구성된 이진화(Binary) 영상
    • mode: 컨투어 검색 모드
      • cv2.RETR_EXTERNAL: 외부 컨투어만 검색
      • cv2.RETR_LIST: 모든 컨투어를 찾지만, 계층 구조를 무시
      • cv2.RETR_CCOMP: 모든 컨투어를 두 계층으로 나누어 저장
      • cv2.RETR_TREE: 모든 컨투어를 트리 구조로 저장
    • method: 컨투어 근사화 방법
      • cv2.CHAIN_APPROX_NONE: 컨투어의 모든 점을 저장
      • cv2.CHAIN_APPROX_SIMPLE: 컨투어를 단순화하여 꼭짓점만 저장

ㅇ Contours Hierarchy (계층 구조)¶

  • 컨투어 간의 부모-자식 관계
  • 계층 구조는 findContours() 함수의 hierarchy 반환 값으로 제공되며, 이는 Numpy 배열로 구성
- Hierarchy 배열의 구조¶
hierarchy[i] = [next, previous, child, parent]
  • next: 같은 레벨에서 다음 컨투어의 인덱스 (없으면 -1)
  • previous: 같은 레벨에서 이전 컨투어의 인덱스 (없으면 -1)
  • child: 하위 컨투어의 인덱스 (없으면 -1)
  • parent: 상위 컨투어의 인덱스 (없으면 -1)

■ 컨투어와 도형 매칭¶

  • 서로 다른 물체의 컨투어를 비교하여 비슷한 정도를 숫자로 반환

ㅇ matchShapes - OpenCV¶

  • retval = cv2.matchShapes(contour1, contour2, method, parameter): 두 개의 컨투어로 도형 매칭
    • contour1, contour2: 비교할 두 개의 컨투어
    • method: 비교 알고리즘
      • cv2.CONTOURS_MATCH_I1
      • cv2.CONTOURS_MATCH_I2
      • cv2.CONTOURS_MATCH_I3
    • parameter: 알고리즘에 사용하는 인수(0으로 고정)
    • retval: 두 도형의 닮은 정도(0=동일, 숫자가 클수록 다름;오차의 개념)