▩ 이미지 컨투어¶
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
■ 컨투어 찾기¶
- 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()
ㅇ 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)
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)
■ 컨투어의 길이¶
- 둘레길이의 합
ㅇ 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)
▶ 면적이 최소인 회전된 직사각형¶
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)
▣ 면적이 최소인 회전된 직사각형¶
- contour_test2.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)
■ 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)
■ 근사화(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()
■ 컨투어 계층(Contours Hierarchy)¶
ㅇ 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=동일, 숫자가 클수록 다름;오차의 개념)