반응형

이미지에서 원하는 영역을 찾기 위해 DeepLearning 기반의 모델을 사용하지만, 매우 간단하게 이미지 연산 방법을 통해 영역을 찾을 수도 있습니다. 그 첫번째로 윤곽선의 속성을 이용하여 모양을 검출하는 방법에 대해 알아 보겠습니다.

  • 이미지를 그레이스케일로 변환
  • 노이즈를 줄이기 위한 이미지 블러링
  • 이미지의 이진화
  • 윤곽선 검출
  • 추출된 윤곽선을 기준으로 근사 다각형 검출
  • Shape 종류 판단

Import packages

import cv2
import imutils
import matplotlib.pyplot as plt

Function declaration

Jupyter Notebook 및 Google Colab에서 이미지를 표시할 수 있도록 Function으로 정의

def img_show(title='image', img=None, figsize=(8 ,5)):
    plt.figure(figsize=figsize)

    if type(img) == list:
        if type(title) == list:
            titles = title
        else:
            titles = []

            for i in range(len(img)):
                titles.append(title)

        for i in range(len(img)):
            if len(img[i].shape) <= 2:
                rgbImg = cv2.cvtColor(img[i], cv2.COLOR_GRAY2RGB)
            else:
                rgbImg = cv2.cvtColor(img[i], cv2.COLOR_BGR2RGB)

            plt.subplot(1, len(img), i + 1), plt.imshow(rgbImg)
            plt.title(titles[i])
            plt.xticks([]), plt.yticks([])

        plt.show()
    else:
        if len(img.shape) < 3:
            rgbImg = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
        else:
            rgbImg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

        plt.imshow(rgbImg)
        plt.title(title)
        plt.xticks([]), plt.yticks([])
        plt.show()

Shape 을 판단하는 Function (이 부분을 수정하여 특정 Shape만 추출을 하는 기능을 구현할 수도 있습니다.)

def shape_label(c):
    shape = "unidentified"
    # cv2.arcLength를 이용해 윤곽선의 전체 길이를 계산
    peri = cv2.arcLength(c, True)
    # cv2.approxPolyDP를 활용해 윤곽선의 근사 다각형 검출
    approx_poly = cv2.approxPolyDP(c, 0.04 * peri, True)
    
    # 꼭지점이 3개이면 삼각형
    if len(approx_poly) == 3:
        shape = "triangle"
    # 꼭지점이 4개이면 사각형
    elif len(approx_poly) == 4:
        # 종횡비를 구해서 정사각형 판단
        (x, y, w, h) = cv2.boundingRect(approx_poly)
        ar = w / float(h)
        
        if ar >= 0.95 and ar <= 1.05:
            shape = "square"  
        else:
            shape = "rectangle"
    # 꼭지점이 5개이면 오각형
    elif len(approx_poly) == 5:
        shape = "pentagon"
    # 꼭지점이 6개이면 육각형
    elif len(approx_poly) == 6:
        shape = "hexagon"
    # 나머지는 원으로 판단
    else:
        shape = "circle"
        
    return shape

Load Image

cv2_image = cv2.imread('asset/images/shape.jpg', cv2.IMREAD_COLOR)
img_show('original image', cv2_image)

Shape Detection

아래 과정을 통해 이미지를 그레이스케일로 변환하고 노이즈를 줄이기 위한 이미지 블러링 후 이진화 합니다.

resized = imutils.resize(cv2_image, width=640)
ratio = cv2_image.shape[0] / float(resized.shape[0])
gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.threshold(blurred, 10, 255, cv2.THRESH_BINARY)[1]

img_show(['GaussianBlur', 'Threshold'], [blurred, thresh])

이진화 이미지에서 윤곽선을 검출합니다.

cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

추출된 윤곽선을 기준으로 Shape을 판단하여 이미지에 표시합니다.

더보기

cv2.moments는 객체의 무게 중심이나 면적 등을 계산할 때 유용합니다. 모멘트 종류는 3가지이며 아래와 같이 총 24개의 값을 가집니다.

  • 공간 모멘트(Spatial Moments) : m00, m10, m01, m20, m11, m02, m30, m21, m12, m03
  • 중심 모멘트(Central Moments) : mu20, mu11, mu02, mu30, mu21, mu12, mu03
  • 평준화된 중심 모멘트(Central Normalized Moments) : nu20, nu11, nu02, nu30, nu21, nu03
{'m00': 21314.0,
 'm10': 10380260.0,
 'm01': 2770943.0,
 'm20': 5107832193.666666,
 'm11': 1349493701.0,
 'm02': 387515034.0,
 'm30': 2538712455460.0,
 'm21': 664048224844.1666,
 'm12': 188726812226.33334,
 'm03': 57471376407.5,
 'mu20': 52479014.17900467,
 'mu11': -1.973632574081421,
 'mu02': 27276453.290184796,
 'mu30': -26011.04541015625,
 'mu21': 565031.7590560913,
 'mu12': 773196.0624542236,
 'mu03': -6977.8613204956055,
 'nu20': 0.11551961832262529,
 'nu11': -4.34446578758714e-09,
 'nu02': 0.06004239070743966,
 'nu30': -3.921889956072085e-07,
 'nu21': 8.5194283649921e-06,
 'nu12': 1.1658085338737212e-05,
 'nu03': -1.0521070489935076e-07}
vis = cv2_image.copy()

for c in cnts:
    # cv2.moments를 이용하여 객체의 중심을 계산
    M = cv2.moments(c)
    cX = int((M["m10"] / M["m00"]) * ratio)
    cY = int((M["m01"] / M["m00"]) * ratio)
    shape = shape_label(c)
    
    # 이미지에서 객체의 윤곽선과 Shape명을 표시
    c = c.astype("float")
    c *= ratio
    c = c.astype("int")
    cv2.drawContours(vis, [c], -1, (0, 255, 0), 10)
    cv2.circle(vis, (cX, cY), 20, (0, 255, 0), -1); 
    cv2.putText(vis, shape, (cX-80, cY-50), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 5)

Shape를 표현한 이미지를 확인합니다.

img_show('Shape Detection', vis, figsize=(16,10))


하지만 이러한 이미지 연산 방식은 수동으로 입력하는 매개변수 값에 따라 결과가 달라질 수 있다는 단점이 있다는 점을 참고하시길 바랍니다.

더보기

thresh = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1]

 

반응형