레외곽선 검출
객체 외곽선 좌표 추출
외부 외곽선 & 내부 외곽선 => 외곽선 계층 구조 표현 가능
cv2.findContours(img, mode, method, offset) => contours, hierarchy
- mode: contours 찾는 방법 지정
- cv2.RETR_EXTERNAL: 가장 바깥 경계선만 찾음
- cv2.RETR_LIST: 모든 컨투어 탐색, hierarchy 관계 구성 않음
- cv2.RETR_CCOMP: 모든 컨투어 탐색, hierarchy 관계 2단계까지 구성
- cv2.RETR_TREE: 모든 컨투어 탐색, 모든 hierarchy 관계 구성
- method: 컨투어 좌표와 관련된 설정
- cv2.CHAIN_APPROX_NONE: 모든 컨투어 좌표 저장
- cv2.CHAIN_APPROX_SIMPLE: 컨투어 라인을 그릴 수 있는 좌표만 저장
- cv2.CHAIN_APPROX_TC89_L1: 컨투어 좌표 찾는 알고리즘
- cv2.CHAIN_APPROX_TC89_KCOS: 컨투어 좌표 찾는 알고리즘
- offset: 좌표 이동 옵셋. 기본값(0,0)
- contours: 검출된 외곽선 좌표. len(contours)는 전체 길이이므로 외곽선 좌표의 개수가 됨.
- contours[i].shape=(k,1,2) k는 한 객체 외곽선 좌표 개수, contours[i].dtype=numpy.int32.
- hierarchy: 외곽선 계층 정보. numpy.ndarray. shape=(1, N, 4). type=numpy.int32.
- hierarchy[0, i, 0] ~ hierarchy[0, i, 3]이 순서대로 next, prev, child, parent의 정보를 표현해야 하기 때문에 사이즈가 4이다.
*컨투어 계층구조(hierarchy)
contours, hierarchy = cv2.findContours(thresh, cv2.CHAIN_APPROX_SIMPLE)
print(hierarchy)
=> 실행결과
array([[[ 1, -1, -1, -1], #[next, prev, child, parent]. -1은 없음을 의미
[ 2, 0, -1, -1],
[ 3, 1, -1, -1],
[ 4, 2, -1, -1],
[ 5, 3, -1, -1],
[ 6, 4, -1, -1],
[ 7, 5, -1, -1],
[ 8, 6, -1, -1],
[-1, 7, -1, -1]]])
외곽선 인덱스를 가리킴. 해당 외곽선이 없으면 -1.
- 가장 외곽을 찾기 때문에 0과 4만 찾는다.
cv2.RETR_EXTERNAL: 0 -> 4 - 위에서부터 순서대로 모든 외곽선을 찾는다.
cv2.RETR_LIST: 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
=> 위 둘은 계층 정보 없음
- 관계 2까지만 외곽을 찾는다. 0의 자식 1,2,3 / 4의 자식 5,8 / 6의 자식 7을 찾는다.
cv2.RETR_CCOMP: 0(1,2,3) -> 4(5,8) -> 6(7) - 모든 외곽을 찾는다.
cv2.RETR_TREE: 0(1,2,3) -> 4((5,6,7), 8)
외곽선 그리기
cv2.drawContours(img, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None) => img
- contours: (cv2.findContours() 함수로 구한) 외곽선 좌표 정보
- contourIdx: 외곽선 인덱스. 음수(-1)를 지정하면 모든 외곽선을 그림
- color:외곽선 색상
- thickness:외곽선 두께. thinkness < 0이면 내부를 채움
- lineType:LINE_4, LINE_8, LINE_AA 중 하나 지정
- hierarchy:외곽선 계층 정보
- maxLevel:그리기를 수행할 최대 외곽선 레벨. maxLevel = 0 이면 contourIdx로 지정된 외곽선만 그림
<사용 예>
원본 이미지를 흑백으로 처리
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
이미지 단순화 시킴
ret, thresh = cv2.threshold(gray, 127, 255, 0)
외곽선을 계층구조 없이 구함
contours, hierarchy = cv2.findContours(thresh, cv2.CHAIN_APPROX_SIMPLE)
좌표를 그린다
cont_img = cv2.drawContours(img, contours, -1, (0,0,255), 2)
import sys
import random
import numpy as np
import cv2
src = cv2.imread('img/contours.bmp', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
contours, hier = cv2.findContours(src, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
dst = cv2.cvtColor(src, cv2.COLOR_GRAY2BGR)
idx = 0
while idx >= 0:
c = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cv2.drawContours(dst, contours, idx, c, 2, cv2.LINE_8, hier)
# 0번째 줄에, idx는 칸, 0은 다음 외곽선 next => 다음에 실행할 인덱스값을 불러옴?
# 맨 마지막이 -1이기 때문에 0보다 클 때까지만 while문이 돈다.
# [[[n,p,c,p],[],[]]]
idx = hier[0, idx, 0]
cv2.imshow('src', src)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
import sys
import random
import numpy as np
import cv2
src = cv2.imread('img/milkdrop.bmp', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_OTSU)
contours, _ = cv2.findContours(src_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
h, w = src.shape[:2]
dst = np.zeros((h, w, 3), np.uint8)
for i in range(len(contours)):
c = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cv2.drawContours(dst, contours, i, c, 1, cv2.LINE_AA)
cv2.imshow('src', src)
cv2.imshow('src_bin', src_bin)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
속성들
moments는 컨투어 결괏값을 가지고, 외곽선 좌표들로 계산을 해서 여러 가지 넓이나, 중심점들을 계산하는 방법.
- moments: 이미지의 특징들. 넓이, 중심점
contours, hierarchy = cv2.findContours(thresh, cv2.CHAIN_APPROX_SIMPLE)
첫 번째 좌표
cont1 = contours [0]
M = cv2.moments(cont1)
print(M['m00'])#cont1 영역의 넓이 출력
print(M['m10']/M['m00']) #중심점의 x좌표
print(M['m01']/M['m00']) #중심점의 y좌표
- *contour영역의 넓이
cv2.contourArea(cont1)#넓이 반환
- *contour 둘레 길이
cv2.arcLength(cont1, True/False)
#True:막힌 영역, False:뚫린 영역 의미
- *contour의 포인트 줄이기 – 대략적 모양은 유지
cv2.approxPolyDP(cont1, 근사치, True/False)
#True:막힌 영역, False:뚫린 영역 의미 => 리턴 값: 근사치로 줄인 포인트 array
<사용 예>
p1 = 0.01 * cv2.arcLength(cont1, True)#곱하는 숫자가 클수록 점의 수는 작아짐
p2 = 0.1 * cv2.arcLength(cont1, True)
ap = cv2.approxPolyDP(cont1, p1, True)
cont_img = cv2.drawContours(img, [ap], 0, (0,0,255), 2)
- *convex hull
contour의 모든 점을 포함하면서 볼록한 점들을 연결한 선
contours, hierarchy = cv2.findContours(thresh, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
hull = cv2.convexHull(cnt)#convex hull 추출
cont_img = cv2.drawContours(img, [hull], 0, (0,0,255), 2)
- *bounding rectangle
컨투어 영역을 모두 포함한 사각형의 좌측 상단 좌표와 가로, 세로 길이 반환
contours, hierarchy = cv2.findContours(thresh, cv2.CHAIN_APPROX_SIMPLE)
cont1 = contours[0]
x, y, w, h = cv2.boundingRect(cont1)
img = cv2.rectangle(img, (x, y), (x+w, y+h), (0,0,255),2)
- *최소한의 사각형(컨투어 각도에 맞추어 사각형 만듦)
rect = cv2.minAreaRect(cont1)#컨투어를 포함한 최소한 크기의 사각형
box = cv2.boxPoints(rect)#사각형의 4점을 반환
cont_img = cv2.drawContours(img, [box], 0, (0,0,255), 2)
- *컨투어 포함한 원
#컨투어를 포함한 원의 중심점 좌표와 반지름 반환
(x, y), r = cv2.minEnclosingCircle(cont1)
- *컨투어 포함한 타원
e = cv2.fitEllipse(cont1)#컨투어를 포함한 타원의 정보 반환
- *컨투어의 가로, 세로 비율
x, y, w, h = cv2.boundingRect(cont1)
r = w/h
- *컨투어 면적과 bounding 면적의 비
area = cv2.contourArea(cont1)
x, y, w, h = cv2.boundingRect(cont1)
b_area = wh r =area/b_area - *컨투어 면적과 convex hull 면적의 비
area = cv2.contourArea(cont1)
hull = cv2.convexHull(cont1)
h_area = area = cv2.contourArea(hull)
r = area/h_area
- *컨투어를 영역의 동서남북 4 꼭짓점 찾기
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
import sys
import random
import numpy as np
import cv2
src = cv2.imread('img/milkdrop.bmp', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_OTSU)
contours, _ = cv2.findContours(src_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
h, w = src.shape[:2]
dst = np.zeros((h, w, 3), np.uint8)
for i in range(len(contours)):
points = contours[i]
area = cv2.contourArea(points)
if area >= 600:
c = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cv2.drawContours(dst, contours, i, c, 1, cv2.LINE_AA)
# 외곽선으로 모멘트 계산
m = cv2.moments(points)
# 외곽선의 중심점 좌표
x = m['m10']/m['m00']
y = m['m01']/m['m00']
cv2.circle(dst, (int(x),int(y)), 3, c, -1)
cv2.imshow('src', src)
cv2.imshow('src_bin', src_bin)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
점들을 근사화시켜서 출력.
import sys
import random
import numpy as np
import cv2
src = cv2.imread('img/milkdrop.bmp', cv2.IMREAD_GRAYSCALE)
if src is None:
print('Image load failed!')
sys.exit()
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_OTSU)
contours, _ = cv2.findContours(src_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
h, w = src.shape[:2]
dst = np.zeros((h, w, 3), np.uint8)
for i in range(len(contours)):
points = contours[i]
area = cv2.contourArea(points)
if area >= 200:
c = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cv2.drawContours(dst, contours, i, c, 1, cv2.LINE_AA)
# 외곽선으로 모멘트 계산
m = cv2.moments(points)
# 외곽선의 중심점 좌표
x = m['m10']/m['m00']
y = m['m01']/m['m00']
cv2.circle(dst, (int(x),int(y)), 3, c, -1)
# 외곽선 둘레 * 0.01
p1 = 0.01 * cv2.arcLength(points, True)
# 외곽선 근사화 (점의 수를 줄임)
ap = cv2.approxPolyDP(points, p1, True)
# 그릴 선의 색상을 랜덤하게 지정
c = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
# 계산된 근사치 좌표로 외곽선 그림
cv2.drawContours(dst, [ap], 0, c, 1, cv2.LINE_AA)
cv2.imshow('src', src)
cv2.imshow('src_bin', src_bin)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
외곽선 검출
import numpy as np
import cv2
src = cv2.imread('img/milkdrop.bmp', cv2.IMREAD_GRAYSCALE)
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_OTSU)
contours, _ = cv2.findContours(src_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# contours[외곽선1, 외곽선2]
for cont in contours:
area = cv2.contourArea(cont)
if area > 600:
cv2.drawContours(dst, [cont], 0, (255,0,0), 1, cv2.LINE_AA)
hull = cv2.convexHull(cont)
cv2.drawContours(dst, [hull], 0, (0,0,255), 1, cv2.LINE_AA)
cv2.imshow('src', src)
cv2.imshow('src_bin', src_bin)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
폴리곤 이미지 파일을 읽어서 convexHull 처리 후 좌표의 개수로 삼각형, 사각형, 다각형을 구분하는 코드 작성
import numpy as np
import cv2
src = cv2.imread('img/polygon.bmp', cv2.IMREAD_GRAYSCALE)
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU )
contours, _ = cv2.findContours(src_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
h, w = src.shape[:2]
dst = np.zeros((h, w, 3), np.uint8)
for cont in contours:
area = cv2.contourArea(cont)
if area > 600:
cv2.drawContours(dst, [cont], 0, (255,0,0), 1, cv2.LINE_AA)
p1 = 0.01 * cv2.arcLength(cont, True)
ap = cv2.approxPolyDP(cont,p1, True)
hull = cv2.convexHull(ap)
p_cnt = len(hull)
if p_cnt == 3:
label = 'triangle'
elif p_cnt == 4:
label = 'rectangle'
elif 4 < p_cnt < 10:
label = 'poly'
elif p_cnt >= 10:
label = 'circle'
print(label)
cv2.drawContours(dst, [hull], 0, (0,0,255), 1, cv2.LINE_AA)
# 꼭짓점 좌표를 계산.
x = cont[cont[:,:,0].argmin()][0][0]
y = cont[cont[:,:,1].argmin()][0][1]
cv2.putText(dst, label, (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200,200,200), 1, cv2.LINE_AA)
cv2.imshow('src', src)
cv2.imshow('src_bin', src_bin)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
다각형 외곽을 검출해서 사각형 그리기
import numpy as np
import cv2
src = cv2.imread('img/polygon.bmp', cv2.IMREAD_GRAYSCALE)
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
contours, _ = cv2.findContours(src_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
h, w = src.shape[:2]
dst = np.zeros((h, w, 3), np.uint8)
for cont in contours:
area = cv2.contourArea(cont)
if area > 600:
cv2.drawContours(dst, [cont], 0, (255, 0, 0), 1, cv2.LINE_AA)
x, y, w, h = cv2.boundingRect(cont)
img = cv2.rectangle(dst, (x,y), (x+w, y+h), (0,0,255),2)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
최소 크기로 사각형 그리기
- 각 도형에 대해서 x_min, x_max, y_min, y_max 좌표 찍기
import numpy as np
import cv2
src = cv2.imread('img/polygon.bmp', cv2.IMREAD_GRAYSCALE)
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
contours, _ = cv2.findContours(src_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
h, w = src.shape[:2]
dst = np.zeros((h, w, 3), np.uint8)
for cont in contours:
area = cv2.contourArea(cont)
if area > 600:
cv2.drawContours(dst, [cont], 0, (255, 0, 0), 1, cv2.LINE_AA)
x_min = cont[cont[:,:,0].argmin()][0]
y_min = cont[cont[:,:,1].argmin()][0]
x_max = cont[cont[:,:,0].argmax()][0]
y_max = cont[cont[:,:,1].argmax()][0]
cv2.circle(dst, (x_min[0], x_min[1]), 3, (0,0,255), -1)
cv2.circle(dst, (y_min[0], y_min[1]), 3, (0,0,255), -1)
cv2.circle(dst, (x_max[0], x_max[1]), 3, (0,0,255), -1)
cv2.circle(dst, (y_max[0], y_max[1]), 3, (0,0,255), -1)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()
구한 점들에 대해서 사각형을 그린다
import numpy as np
import cv2
src = cv2.imread('img/polygon.bmp', cv2.IMREAD_GRAYSCALE)
_, src_bin = cv2.threshold(src, 0, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
contours, _ = cv2.findContours(src_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
h, w = src.shape[:2]
dst = np.zeros((h, w, 3), np.uint8)
for cont in contours:
area = cv2.contourArea(cont)
if area > 600:
cv2.drawContours(dst, [cont], 0, (255, 0, 0), 1, cv2.LINE_AA)
rect = cv2.minAreaRect(cont) # 컨투어를 포함한 최소한 크기의 사각형
box = cv2.boxPoints(rect) # 사각형의 4점을 반환
box = box.astype('int32')
cv2.drawContours(dst, [box], 0, (0,0,255),2, cv2.LINE_AA)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()