본문 바로가기
Python/Computer Vision

[파이썬, Python] OpenCV - 레이블링(labeling)과 외곽선 검출

by coding-choonsik 2023. 9. 2.
728x90
반응형
SMALL

1. 레이블링(labeling)

  • 이진화, 모폴로지를 수행하면 객체와 배경 영역을 구분할 수 있게됨
  • 객체 단위 분석을 통해 각 객체를 분할하여 특징을 분석하고 객체의 위치, 크기 정보, 모양 분석, ROI 추출 등이 가능함
  • 서로 연결되어 있는 객체 픽셀에 고유번호를 할당하여 영역 기반 모양 분석, 레이블맵, 바운딩 박스, 픽셀 개수, 무게중심, 좌표 등을 반환할 수 있게 함
  • cv2.connectedComponents(영상, 레이블 맵)
    • 레이블 맵: 픽셀 연결 관계(4방향 연결, 8방향 연결..)
    • return: 객체 개수, 레이블 맵 행렬
  • cv2.connectedComponentsWithStats(영상, 레이블맵)
    • return: 객체 개수, 레이블 맵 행렬, (객체 위치, 가로세로길이, 면적 등 행렬, 무게중심 정보)

 

import cv2

img = cv2.imread('keyboard.bmp', cv2.IMREAD_GRAYSCALE)

# 이진화 - 글씨가 또렷하게 됨
_, img_bin = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)  # 자동 이진화
dst = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
# 레이블링
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(img_bin)
print(cnt)
print(labels)
print(stats)
print(centroids)

for i in range(1, cnt):
    (x, y, w, h, area) = stats[i]
    if area < 20:  # 노이즈의 면적이 20px미만으로 작다고 생각
        continue
    cv2.rectangle(dst, (x, y, w, h), (0, 255, 255))
cv2.imshow('img', img)
cv2.imshow('img_bin', img_bin)
cv2.imshow('dst', dst)
cv2.waitKey()

▲ 왼쪽부터 img, img_bin, dst


2. 객체의 외곽선 검출

  • 레이블링과 함께 영상에서 객체의 정보를 검출하는 방법 중 하나
  • 이진화된 영상에서 검출되며 배경 영역과 닿아 있는 픽셀을 찾아 외곽선으로 인식
  • 외곽선은 객체 외부뿐 아니라 내부에도 생길 수 있음

 

2-1. 외곽선 검출하기

  • 외곽선이 각각 인덱스값을 가짐
  • cv2.findContours(영상, 검출모드, 외곽선 좌표 근사화 방법) -> 리턴 외곽선 좌표 정보
  • 검출모드
    • RETR_EXTERNAL: 객체 외부 외곽선만 검출
    • RETR_LIST: 객체 외부, 내부 외곽선 모두 검출
    • RETR_CCOMP: 모든 외곽선 검출, 2단계 계층 구조를 구성
    • RETR_TREE: 모든 외곽선 검출, 전체 계층 구조 구성
  • 외곽선 좌표 근사화 방법
    • CHAIN_APPROX_NONE: 모든 외곽선 좌표를 저장
    • CHAIN_APPROX_SIMPLE: 외곽선 중에서 수평, 수직, 대각선 성분은 끝 점만 저장

 

2-2. 외곽선 그리기

  • cv2.drawContours(영상, 외곽선 좌표 정보, 외곽선 인덱스, 색상, 두께)
    • 외곽선 인덱스: -1을 지정하면 모든 외곽선을 그림

 

import cv2
import random

img = cv2.imread('contours.bmp', cv2.IMREAD_GRAYSCALE)
contours, _ = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
dst = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) # 랜덤한 색상을 뽑아주기 위해
cv2.drawContours(dst, contours, -1, color, 2)  # -1: 모든 외곽선 검출(내부, 외부)

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()

▲ img와 dst

 

✅ 외곽선만 따로 추출하기

import cv2
import random
import numpy as np

img = cv2.imread('milkdrop.bmp', cv2.IMREAD_GRAYSCALE)
_, img_bin = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)
contours, _ = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# dst = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

h, w = img.shape[:2]
dst = np.zeros((h, w, 3), np.uint8)

for i in range(len(contours)):
    color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) # 랜덤한 색상을 뽑아주기 위해
    cv2.drawContours(dst, contours, i, color, 2)  # i 개수만큼 그림

cv2.imshow('img', img)
cv2.imshow('img_bin', img_bin)
cv2.imshow('dst', dst)
cv2.waitKey()

▲ 순서대로 img, img_bin, dst


2-3. 외곽선 길이 구하기

  •  cv2.arclength(외곽선 좌표, 폐곡선 여부)

 

2-4. 면적 구하기

  • cv2.contourArea(외곽선 좌표, False)

 

2-5. 바운딩 박스 구하기

  • cv2.boundingRect(외곽선 좌표)

 

2-6. 외곽선 근사화

  • 검출한 윤곽선 정보를 분석하여 정점수가 적은 윤곽선 또는 다각형으로 표현할 수 있게 만드는 것, 다각형의 좌표를 반환
  • cv2.approxPolyDP(외곽선 좌표, 근사화 정밀도 조절, 폐곡선 여부)
    • 근사화 정밀도 조절: 입력 컨투어와 근사화된 컨투어 사이의 최대 거리. 값이 작을수록 다각형이 정확해지고, 꼭지점의 수가 늘어남
  • cv2.isContourConvex()
    • contour에 오목한 부분이 있는지 체크(있으면 True, 없으면 False를 반환, 하나라도 볼록한 부분이 있으면 False로 나옴)
  • cv2.convexHull()
    • contour에 있는 오목한 부분을 제거

 

✅ 아래 그림을 사각형, 삼각형, 원형, 알 수 없음으로 나누는 프로그램을 작성하여보자.

import cv2
import math

def setLabel(img, pts, label):
    (x, y, w, h) = cv2.boundingRect(pts)  # 좌표들을 통해 사각형을 입힘
    pt1 = (x, y)
    pt2 = (x+w, y+h)
    cv2.rectangle(img, pt1, pt2, (125,125,30),2)
    # cv2.drawContours(img, contours, -1, 120, 2)
    # img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    cv2.putText(img, label, pt1, cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 255))

img = cv2.imread('polygon.bmp')
gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
# cv2.THRESH_BINARY_INV: 외부 외곽선1개만 잡히는걸 반대로 안에 도형만 검출하게 하기 위해
_, img_bin = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
contours, _ = cv2.findContours(img_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# print(contours)  # 좌표정보

for pts in contours:
    # 면적을 비교해서 잡음(노이즈)는 객체로 잡지 않게 하기 위해
    if cv2.contourArea(pts) < 400:
        continue
    # 외곽선 길이를 구한 값에 0.02를 곱해 값을 줄임(근사화 정밀도를 낮춤(다각형의 꼭짓점수를 찾기위해 조절함))
    approx = cv2.approxPolyDP(pts, cv2.arcLength(pts, True) * 0.02,  True)
    # print(approx)
    num_pts = len(approx)
    # print(num_pts)

    if num_pts == 3:
        setLabel(img, pts, 'Triangle')
    elif num_pts == 4:
        setLabel(img, pts, 'Rectangle')
    else:
        length = cv2.arcLength(pts, True)
        area = cv2.contourArea(pts)
        # 둥굴기 구하기 공식
        # 4.: 상수는 아무값이나 줘도 됨. 0 ~ 1사이의 값으로 임계값을 계산할때는 4를 곱해주는 것이 좋음
        ratio = 4. * math.pi * area / (length * length)

        if ratio > 0.85:  # 1에 가까울수록 원형, 0.8이상정도 되면 원이라고 봄
            setLabel(img, pts, 'Circle')
        elif ratio < 0.7:
            setLabel(img, pts, 'NoName')
        else:
            setLabel(img, pts, 'Ellipse')

cv2.imshow('img', img)
cv2.imshow('img_bin', img_bin)
cv2.imshow('gray', gray)
cv2.waitKey()

 

▲ gray
▲이진화
▲ 결과

 


✅ 외곽선 근사화하기(볼록, 오목한 부분)

import cv2

img = cv2.imread('hat.png')
cpy = img.copy()
gray = cv2.cvtColor(cpy, cv2.COLOR_BGRA2GRAY)

_, thr = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
contour, _ = cv2.findContours(thr, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# print(contour[1])
cnt = contour[0]

cv2.drawContours(img, [cnt], -1, (255, 0, 0), 2)
check = cv2.isContourConvex(cnt)
print(check)  # False(하나라도 볼록한 부분이 있으면 False)
>>> False

if not check:
    hull = cv2.convexHull(cnt)
    cv2.drawContours(cpy, [hull], -1, (0, 255, 0), 2)
    cv2.imshow('hull', cpy)

cv2.imshow('contour', img)
# cv2.imshow('img', img)
cv2.waitKey()

 

▲ Hull
▲contour

 

 

 

728x90
반응형
LIST