본문 바로가기
카테고리 없음

49Day_2021_08_10 (레이블링과 외곽선 검출)

by 우리집야옹이룰루 2021. 8. 10.
728x90
반응형
SMALL

레외곽선 검출


객체 외곽선 좌표 추출
외부 외곽선 & 내부 외곽선 => 외곽선 계층 구조 표현 가능

 

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()

src                                                                                              dst

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()

 

src                                                              src_bin                                                           dst

 


속성들


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()

src                                                      src_bin                                                            dst

 

 

 

점들을 근사화시켜서 출력.

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()

src                                                      src_bin                                                            dst

 

 

 

 

 

외곽선 검출

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()

src                                                      src_bin                                                            dst

 


폴리곤 이미지 파일을 읽어서 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()

src                                                      src_bin                                                            dst

 

 

 

 

다각형 외곽을 검출해서 사각형 그리기

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()

728x90
반응형
LIST