다운로드
작성자: admin 작성일시: 2018-11-23 23:36:06 조회수: 2430 다운로드: 87
카테고리: 기타 태그목록:

이미지 컨투어

이미지에서 컨투어란 동일한 색 또는 동일한 픽셀값(강도,intensity)을 가지고 있는 영역의 경계선을 연결한 선을 말한다. 일기예보나 지도의 등고선과 같은 것이다. 이미지에서 컨투어는 물체의 윤곽선, 외형을 파악하는데 사용된다. OpenCV의 cv2.findContours() 함수를 이용하여 이미지의 컨투어를 찾을 수 있다. 이 때 결과물의 정확성을 위하여 임계처리 등을 통해 이미지를 이진화하여 사용한다. cv2.findContours() 함수의 파라미터는 다음과 같다.

  • cv2.findContours()의 파라미터
    • image - 그레이스케일 이미지 혹은 이진화한 이미지
    • mode : 컨투어를 찾는 방법으로 각 방법들에 대한 설명은 다음 표로 정리했다.
    • method : 컨투어를 찾을 때 사용하는 근사치 방법으로 이것 역시 다음 표로 정리했다.

cv2.findContours()함수는 이미지와 컨투어 정보, 컨투어의 상하구조(hierachy) 정보를 출력한다. 자세한 것은 다음에 다루겠다. 이렇게 찾은 컨투어 정보를 cv2.drawContours() 함수에 입력하여 이미지의 컨투어를 시각화 할 수 있다. 이 때, cv2.findContours()함수는 원본 이미지를 직접 수정하므로 원본 이미지를 보존하기 위해서는 복사본을 사용하도록 한다.

mode 컨투어를 찾는 방법
cv2.RETR_EXTERNAL 컨투어 라인 중 가장 바깥쪽의 라인만 찾음
cv2.RETR_LIST 모든 컨투어 라인을 찾지만, 상하구조(hierachy)관계를 구성하지 않음
cv2.RETR_CCOMP 모든 컨투어 라인을 찾고, 상하구조는 2 단계로 구성함
cv2.RETR_TREE 모든 컨투어 라인을 찾고, 모든 상하구조를 구성함
표 38.4 : cv2.findContours 함수가 지원하는 컨투어를 찾는 방법
mode 컨투어를 표현하는 방법
cv2.CHAIN_APPROX_NONE 모든 컨투어 포인트를 반환
cv2.CHAIN_APPROX_SIMPLE 컨투어라인을 그릴 수 있는 포인트만 반환
cv2.CHAIN_APPROX_TC89_L1 Teh_Chin 연결 근사 알고리즘을 적용하여 컨투어 포인트를 줄임
cv2.CHAIN_APPROX_TC89_KCOS Teh_Chin 연결 근사 알고리즘을 적용하여 컨투어 포인트를 줄임
표 38.5 : cv2.findContours 함수가 지원하는 컨투어를 표현하는 방법
In [1]:
import cv2
from skimage.data import page, horse

img_raw = horse().astype('uint8')
img_raw = np.ones(img_raw.shape) - img_raw
img = img_raw.copy().astype('uint8')

image, contours, hierachy = cv2.findContours(
    img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
image = cv2.drawContours(img, contours, -1, 3)

plt.subplot(1, 2, 1)
plt.imshow(img_raw, cmap='gray')
plt.title("원본 이미지")
plt.axis('off')

plt.subplot(1, 2, 2)
plt.imshow(image, cmap='gray')
plt.title("말 그림의 컨투어")
plt.axis('off')
plt.tight_layout()
plt.show()

cv2.findContours()함수의 결과 중, 컨투어 정보는 다음과 같이 컨투어를 구성하는 점들의 좌표로 구성 되어 있다. 이전 코드의 컨투어 정보는 contours라는 변수로 저장 했다. 반환값은 리스트이고, 리스트 내에는 찾은 컨투어 포인트들의 배열들이 들어있다.

In [2]:
contours[0]
Out:
array([[[350,   9]],

       [[346,  13]],

       [[345,  13]],

       ...,

       [[349,  18]],

       [[349,  14]],

       [[350,  13]]], dtype=int32)
In [3]:
contours[1]
Out:
array([[[ 34, 239]],

       [[ 35, 238]],

       [[ 36, 239]],

       [[ 36, 244]],

       [[ 35, 245]],

       [[ 34, 244]]], dtype=int32)

컨투어 특징

이미지 모멘트

이미지의 컨투어에 관한 정보를 이미지 모멘트라고 한다. 이미지 모멘트는 대상을 구분할 수 있는 특징값을 뜻하는 것이며, Area(면적), 둘레 등이 있다. 이미지 모멘트는 다른 대상과 구분하기 위해 대상을 설명하는 자료로 사용된다. 다음 코드는 이전에 찾은 컨투어의 이미지 모멘트를 찾는 코드이다. OpenCV에서는 이미지 모멘트를 구하는 것을 cv2.moments()라는 함수로 구현 했다. 이 함수에 컨투어 포인트 배열을 입력하면 해당 컨투어의 모멘트를 딕셔너리 타입으로 반환한다. 반환하는 모멘트는 총 24개로 10개의 위치 모멘트, 7개의 중심 모멘트, 7개의 정규화된 중심 모멘트로 이루어져있다.

  • Spatial Moments : M00, M01, M02, M03, M10, M11, M12, M20, M21, M30
  • Central Moments : Mu02, Mu03, Mu11, Mu12, Mu20, Mu21, Mu30
  • Central Normalized Moments : Nu02, Nu03, Nu11, Nu12, Nu20, Nu21, Nu30
In [4]:
import cv2
from skimage.data import horse

cnt = contours[0]
M = cv2.moments(cnt)

print(M.items())
dict_items([('m00', 42390.0), ('m10', 7957647.5), ('m01', 6122659.166666666), ('m20', 1918567975.1666665), ('m11', 1045850406.25), ('m02', 1043207731.5), ('m30', 518349606804.75), ('m21', 234532504815.23334), ('m12', 169961654938.16666), ('m03', 201193042374.45), ('mu20', 424721461.02993035), ('mu11', -103523583.15733838), ('mu02', 158872858.38918722), ('mu30', -1274243072.28302), ('mu21', -3710699587.248932), ('mu12', 4031007007.4581795), ('mu03', 4621820258.191528), ('nu20', 0.23636184598563853), ('nu11', -0.05761193502861847), ('nu02', 0.08841437396363862), ('nu30', -0.0034442426452973808), ('nu21', -0.01002991504548003), ('nu12', 0.010895696857668564), ('nu03', 0.012492648206940333)])
  • 컨투어의 면적 (ContourArea)
    • 컨투어의 면적은 모멘트의 m00 값이고, cv2.contourArea() 함수로도 구할 수 있다.
In [5]:
print(cv2.contourArea(cnt), M["m00"])
42390.0 42390.0
  • 컨투어의 둘레(arc Length)
    • 컨투어의 둘레는 arcLength()함수로 구할 수 있다. 두번째 파라미터인 closed의 의미는 폐곡선의 여부로, 설정한 값이 True 일 때는 컨투어의 시작점과 끝점을 이어 도형을 구성하고 그 둘레 값을 계산한다. False인 경우 시작점과 끝점을 잇지 않고 둘레를 계산한다.
In [6]:
cv2.arcLength(cnt, closed=True), cv2.arcLength(cnt, closed=False)
Out:
(2296.72913479805, 2292.72913479805)

Contour Property

  • Aspect Ratio
    • 컨투어라인의 가로 세로 비율, 이를 계산하기 위해 cv2.boundingRect() 함수를 이용하여 가로/세로 크기를 구하여 계산한다. 이 함수는 다음에 컨투어 라인을 둘러싸는 사각형을 그릴 때 설명할 것이다.
In [7]:
x, y, w, h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h
print("Aspect Ratio : {}".format(aspect_ratio))
Aspect Ratio : 1.2203947368421053
  • Extend
    • 컨투어 라인을 포함하는 사각형의 면적에서 컨투어 면적의 비율이다. 방금 사용한 cv2.boundingRect()함수와 컨투어의 면적을 구하는 cv2.contourArea() 함수를 사용하여 구할 수 있다.
In [8]:
area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extend = float(area) / rect_area

print("Extend : {}".format(extend))
Extend : 0.37585118456518657
  • Solidity
    • Solidity Ratio는 Convex hull 면적 대비 Contour의 면적 비율이다.
In [9]:
area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area) / hull_area
print("Solidity : {}".format(solidity))
Solidity : 0.5091066313570772
  • 컨투어의 중심값
    • 컨투어의 중심값은 다음과 같이 구할 수 있다.
In [10]:
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
  • Extream Points
    • 컨투어 라인의 좌우상하의 끝점을 의미한다.
In [11]:
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])
In [12]:
plt.subplot(1,2,1)
plt.imshow(image, cmap='gray')
plt.title("컨투어의 중심점")
plt.axis('off')
plt.scatter([cx], [cy], c="r", s=30)

plt.subplot(1,2,2)
plt.imshow(img_raw, cmap='gray')
plt.axis("off")
plt.scatter([leftmost[0], rightmost[0], topmost[0], bottommost[0]], [
            leftmost[1], rightmost[1], topmost[1], bottommost[1]], c="r", s=30)
plt.title("Extream Points")

plt.show()

컨투어 추정

cv2.findContours() 함수를 통해 찾은 컨투어 라인은 각각의 컨투어 포인트들을 가지고 있다. 컨투어 추정은 Douglas-Peucker 알고리즘을 이용해 컨투어 포인트의 수를 줄여 실제 컨투어 라인과 근사한 라인을 그릴 때 사용된다. OpenCV에서는 cv2.approxPolyDP() 라는 함수로 구현되어 있다. 입력값으로는 컨투어 포인트 배열, 실제 컨투어 라인과 근사치의 최대거리, 폐곡선 여부가 있다. 다음 코드는 실제 컨투어 라인과 근사치의 최대거리를 0.01, 0.05 로 설정하여 실제 컨투어 라인과 비교 한다.

In [13]:
img1 = img_raw.copy().astype('uint8')
img2 = img_raw.copy().astype('uint8')
img3 = img_raw.copy().astype('uint8')

cnt = contours[0]

epsilon1 = 0.01*cv2.arcLength(cnt, True)
epsilon2 = 0.05*cv2.arcLength(cnt, True)

approx1 = cv2.approxPolyDP(cnt, epsilon1, True)
approx2 = cv2.approxPolyDP(cnt, epsilon2, True)

image1 = cv2.drawContours(img1, [cnt], -1, 7)  #
image2 = cv2.drawContours(img2, [approx1], -1, 7)
image3 = cv2.drawContours(img3, [approx2], -1, 7)

titles = ['Original', '$\epsilon=0.01$', '$\epsilon=0.05$']
images = [image1, image2, image3]

for i in range(3):
    plt.subplot(1, 3, i+1)
    plt.title(titles[i])
    plt.imshow(images[i], cmap='gray')
    plt.axis('off')

plt.tight_layout()
plt.show()

Convex Hull

Convex Hull이란 컨투어 포인트를 모두 포함하는 볼록한 외곽선을 의미한다. 결과는 컨투어 추정과 비슷하지만 방법이 다르다. 먼저, cv2.isContourConvex() 함수를 사용해 이미지의 컨투어가 볼록(convex)한지 확인 할 수 있다. 입력한 컨투어 배열이 볼록(convex)하다면 True, 아니라면 False 값을 반환한다. 이때 볼록하다는 것은 컨투어 라인이 볼록하거나 평평한 선이라는 의미이다. 이전에 확인 했듯이 말 이미지의 컨투어 라인은 볼록한 선으로만 이루어져있지 않다. 따라서 다음 코드의 결과는 False를 반환한다.

In [14]:
cv2.isContourConvex(cnt)
Out:
False

컨투어 라인이 볼록하지 않다면, cv2.convexHull() 함수를 사용해 컨투어라인을 볼록하게 만들 수 있다.

In [15]:
img4 = img_raw.copy().astype('uint8')

cnt = contours[0]
hull = cv2.convexHull(cnt)
image4 = cv2.drawContours(img4, [hull], 0, 3)
titles = ['원본 이미지', 'Convex Hull']
images = [img_raw, img4]

for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.title(titles[i])
    plt.imshow(images[i], 'gray')
    plt.axis('off')

plt.show()

Bounding Rectangle

Bounding Rectangle은 컨투어 라인을 둘러싸는 사각형을 그리는 방법이다. 사각형을 그리는 방법은 2가지가 있다.

  1. Straight Bounding Rectangle : 물체의 회전은 고려하지 않은 사각형
  2. Rotated Rectangle : 물체의 회전을 고려한 사각형

다음 코드는 컨투어 라인 위에 두 가지 종류의 사각형을 그린다.

In [16]:
img1 = img_raw.copy().astype("uint8")
img2 = img_raw.copy().astype("uint8")

# Straight Rectangle
x, y, w, h = cv2.boundingRect(cnt)
img1 = cv2.rectangle(img1, (x, y), (x+w, y+h), 7)

# Rotated Rectangle
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = box.astype('int')
img2 = cv2.drawContours(img2, [box], -1, 7) # blue

plt.subplot(1,2,1)
plt.imshow(img1, cmap="gray")
plt.axis('off')
plt.title("Straight Rectangle")
plt.subplot(1,2,2)
plt.imshow(img2, cmap="gray")
plt.axis('off')
plt.title("Rotated Rectangle")
plt.show()

Minumum Enclosing Circle 과 Fitting Ellipse

Minumum Enclosing Circle과 Fitting Ellipse는 각각 컨투어 라인을 완전히 포함하는 가장 작은 원, 타원을 그리는 것이다.

In [17]:
img3 = img_raw.copy().astype("uint8")
img4 = img_raw.copy().astype("uint8")

(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
img3 = cv2.circle(img3, center, radius, 7)

ellipse = cv2.fitEllipse(cnt)
img4 = cv2.ellipse(img4, ellipse, 7)


plt.subplot(1,2,1)
plt.imshow(img3, cmap="gray")
plt.axis('off')
plt.title("Minumum Enclosing Circle")
plt.subplot(1,2,2)
plt.imshow(img4, cmap="gray")
plt.axis('off')
plt.title("Fitting Ellipse")
plt.show()

질문/덧글

안녕하세요. 컨투어 윤곽잡힌거 안에있는것 포함해서 lzes*** 2019년 4월 11일 6:17 오후

안녕하세요. 컨투어 윤곽잡힌거 안에있는것 포함해서
잘라내기 하려면 어떻게 해야하나요?

그리고
컨투어 외의 배경을 날리는 방법을 알수있을까요?

답변: 안녕하세요. 컨투어 윤곽잡힌거 안에있는것 포함해서 관리자 2019년 4월 17일 10:14 오전

cv2.drawContours() 명령으로 그림 그림을 mask로 사용하시면 됩니다.