Python과 OpenCV – 20 : 히스토그램(Histogram) 1/4

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_histograms/py_histogram_begins/py_histogram_begins.html#histograms-getting-started 입니다.

아래 그림을 보면..

이미지에 대해 어떤 그래프를 표시하고 있는데, 화소의 강도(Gray 값 또는 하나의 채널에 대한 값)를 갖는 화소의 개수를 각각 X축과 Y축으로 표시하고 있습니다. 대부분의 경우 화소의 강도는 0-255입니다.

히스토그램을 얻는 방법은 Numpy를 이용하는 것과 OpenCV를 이용하는 것이 있습니다. 먼저 OpenCV를 이용한 예제를 보면..

import cv2

img = cv2.imread('./data/home.jpg',0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])

4번째 cv2.calcHist 함수가 이미지에 대한 히스토그램 정보를 얻는데, 이 함수의 첫번째는 입력 이미지의 배열이며 두번째는 히스토그램을 얻을 채널 인덱스, 세번째는 Mask 이미지, 네번째는 X 축 요소(BIN)의 개수이고 다섯번째는 Y 축 요소값의 범위로 하나의 채널에 대한 화소 강도가 0~255이므로 대부분의 경우 [0,256]이 됩니다. 이 함수의 반환값은 256개의 요소를 갖는 배열입니다.

Numpy를 이용하여 히스토그램을 얻는 코드는 다음과 같습니다.

import cv2
import numpy as np

img = cv2.imread('./data/home.jpg',0)
hist,bins = np.histogram(img.ravel(),256,[0,256])

5번째 코드의 np.histogram 함수가 히스토그램을 얻는 함수인데 반환값은 반환값은 256개의 요소를 갖는 배열인 hist와 X축 요소의 값을 나타내는 배열인 bins입니다. 이 함수 이외에도 hist = np.bincount(img.ravel(),minlength=256) 와 같은 더 빠른 함수가 가능합니다.

속도면에서 Numpy보다 OpenCV 방식이 훨씬 빠르므로 OpenCV를 사용하는 것이 좋습니다.

히스토그램 값을 그래프로 표시하기 위한 예제는 다음과 같습니다.

import cv2
from matplotlib import pyplot as plt

img = cv2.imread('./data/home.jpg',0)
plt.hist(img.ravel(), 256, [0,256]); 
plt.show()

결과는 다음과 같습니다.

BGR 형태와 같이 3개의 채널로 구성된 이미지에 대한 각 채널의 히스토그램도 시각화가 가능한데, 관련된 예제는 다음과 같습니다.

import cv2
from matplotlib import pyplot as plt

img = cv2.imread('./data/home.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
    histr = cv2.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color = col)
    plt.xlim([0,256])
plt.show()

결과는 다음처럼 3개 채널 각각의 히스토그램 결과가 표시됩니다.

지금까지는 히스토그램 분석을 이미지 전체에 대해서 수행했는데, 필요할 경우 이미지의 원하는 영역에 대한 마스크를 지정해 해당 영역에 대한 히스토그램만을 분석할 수 있습니다. 아래의 코드가 예입니다.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('./data/home.jpg',0)

# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv2.bitwise_and(img,img,mask = mask)

# Calculate histogram with mask and without mask
# Check third argument for mask
hist_full = cv2.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv2.calcHist([img],[0],mask,[256],[0,256])

plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask,'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])

plt.show()

결과는 다음과 같습니다.

Python과 OpenCV – 19 : 이미지의 등치선(Contours) – 5/5

이 글은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contours_hierarchy/py_contours_hierarchy.html#contours-hierarchy 를 참조로 하였습니다.

먼저 다음과 같은 코드가 있습니다.

import numpy as np
import cv2

img = cv2.imread('./data/opencv_contour_h.png')
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255,0)

_, contours,hierarchy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

print(hierarchy)
cv2.imshow('img', img)

for i in range(len(contours)):
    cv2.waitKey()
    img = cv2.drawContours(img, contours, i, (0,0,255), 2)
    cv2.imshow('img', img)

print('END')
cv2.waitKey()
cv2.destroyAllWindows()

8번 코드의 cv2.findContours 함수의 2번째 인자에 따라 그 결과가 달라지는데, 특히 반환값 중 3번째인 hierarchy 값이 크게 달라집니다. hierarchy 값은 추출된 등치선 간의 계층 정보를 나타냅니다. cv2.findContours 함수의 2번째 인자에 따라 어떻게 변경되는지 요약 그림을 언급하는 선에서 정리합니다.

먼저 cv2.RETR_LIST 일 경우, 추출된 등치선의 인덱스 번호에 대한 그림입니다.

그리고 반환된 계층 정보는 다음과 같습니다.

[[[ 1 -1 -1 -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]]]

총 9개의 등치선이 추출되었으므로 위와 같이 총 9개의 계층 정보가 반환되는데 위의 각 9개 요소의 순서는 앞선 그림에서 표시된 등치선의 인덱스 순서와 동일합니다. 0부터 시작하고요. 그리고 각 요소는 다시 4개로 구성되는데.. [Next, Previous, First_Child, Parent]와 같습니다. 즉, [다음 등치선의 인덱스, 이전 등치선의 인덱스, 첫번째 자식 등치선의 인덱스, 부모 등치선의 인덱스] 입니다. 위의 계층 정보에 대한 내용은 다음 그림으로 표시될 수 있습니다.

다음은 cv2.RETR_EXTERNAL 인자에 대한 등치선의 인덱스 번호에 대한 그림입니다.

계층 정보는 다음과 같습니다.

[[[ 1 -1 -1 -1]
  [ 2  0 -1 -1]
  [-1  1 -1 -1]]]

위 계층정보의 의미는 다음과 같습니다.

다음은 cv2.RETR_CCOMP 인자에 대한 등치선의 인덱스 번호에 대한 그림입니다.

계층 정보는 다음과 같습니다.

[[[ 3 -1  1 -1]
  [ 2 -1 -1  0]
  [-1  1 -1  0]
  [ 5  0  4 -1]
  [-1 -1 -1  3]
  [ 7  3  6 -1]
  [-1 -1 -1  5]
  [ 8  5 -1 -1]
  [-1  7 -1 -1]]]

위 계층정보의 의미는 다음과 같고요.

마지막으로 cv2.RETR_TREE 인자에 대한 등치선의 인덱스 번호에 대한 그림입니다.

계층 정보는 다음과 같습니다.

[[[ 7 -1  1 -1]
  [-1 -1  2  0]
  [-1 -1  3  1]
  [-1 -1  4  2]
  [-1 -1  5  3]
  [ 6 -1 -1  4]
  [-1  5 -1  4]
  [ 8  0 -1 -1]
  [-1  7 -1 -1]]]

위의 계층정보의 의미를 도식화하면 다음과 같습니다.

Python과 OpenCV – 18 : 이미지의 등치선(Contours) – 4/5

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contours_more_functions/py_contours_more_functions.html#contours-more-functions 입니다.

등치선에 대한 볼록껍질(Convex Hull) 연산에서 오목한 부분(즉, 블럭한 부분에 대한 결합)을 발견하는 함수와 등치선으로 만들어지는 도형(또는 객체)와 임이의 포인트에서의 거리를 구하는 함수, 마지막으로 객체간의 유사성 정도를 하나의 값으로 특정하는 함수에 대해 정리합니다.

먼저 볼록 껍질 연산에서 오목한 부분을 식별하는 예제는 아래와 같습니다.

import numpy as np
import cv2
 
img = cv2.imread('./data/thunder.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
 
cnt = contours[0]
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)

print(defects)

for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv2.line(img,start,end,[0,255,0],2)
    cv2.circle(img,far,5,[0,0,255],-1)

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

결과는 아래와 같은데요..

작은 빨간 원으로 표시되는 부분이 볼록껍질 연산에 있어서 오목한 부분으로 식별된 지점입니다. 그리고 초록색선은 오목한 지점에 대해 볼록하게 처리된 선분입니다.

등치선으로 구성된 객체에 대해 어떤 좌표에서의 거리를 얻는 예제는 다음과 같습니다.

dist = cv2.pointPolygonTest(cnt,(50,50),True)

위의 예제는 (50,50) 좌표에서 등치선까지의 거리를 얻습니다. 등치선으로 구성된 도형의 내부에 포인트 좌표(여기서는 50,50)이 존재하면 양수가, 밖에 존재하면 음수가, 등치선 상에 정확이 위치하면 0인 거리가 반환됩니다. pointPolygonTest 함수의 세번째 인자는 True인데, 이를 False로 지정하면 거리값이 아닌 -1, 0, 1 중 하나의 값이 반환됩니다. 이값들은 각각 도형의 외부, 경계, 내부인지의 여부를 나타내는 부호값입니다.

마지막으로 도형에 대해 유사성을 하나의 수치값으로 특정할 수가 있는데, 예제를 살펴보면..

import numpy as np
import cv2

img1 = cv2.imread('./data/shapes/1.png')
img1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)

img2 = cv2.imread('./data/shapes/2.png')
img2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)

ret, thresh1 = cv2.threshold(img1, 127, 255,0)
ret, thresh2 = cv2.threshold(img2, 127, 255,0)

_, contours,hierarchy = cv2.findContours(thresh1,2,1)
cnt1 = contours[0]

_, contours,hierarchy = cv2.findContours(thresh2,2,1)
cnt2 = contours[0]

ret = cv2.matchShapes(cnt1,cnt2,1,0.0)
print(ret)

위의 예제는 1.png 파일과 2.png 파일에 대한 등치선을 추출하고 이 등치선으로 구성된 도형에 대한 유사성을 하나의 값으로 특정하는 예제입니다. 입력 이미지 파일이 아래와 같은데요. 파일명만 표시하고 확장자인 png는 생략되었습니다.

1.png 파일에 대해 나머지 파일들에 대한 유사성 값을 위의 코드를 통해 출력해보면 각각 아래와 같습니다.

1.png와 2.png 간의 cv2.matchShapes 반환값 = 0.16
1.png와 3.png 간의 cv2.matchShapes 반환값 = 14.1
1.png와 4.png 간의 cv2.matchShapes 반환값 = 0.11
1.png와 5.png 간의 cv2.matchShapes 반환값 = 0.26
1.png와 6.png 간의 cv2.matchShapes 반환값 = 0.26
1.png와 7.png 간의 cv2.matchShapes 반환값 = 0.32
1.png와 8.png 간의 cv2.matchShapes 반환값 = 0.32
1.png와 9.png 간의 cv2.matchShapes 반환값 = 0.17
1.png와 10.png 간의 cv2.matchShapes 반환값 = 0.005
1.png와 11.png 간의 cv2.matchShapes 반환값 = 1.03

Python과 OpenCV – 17 : 이미지의 등치선(Contours) – 3/5

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contour_properties/py_contour_properties.html#contour-properties 입니다.

Contour(등치선)에 대해 자주 사용되는 몇가지 속성에 대해 좀더 살펴보겠습니다. 그 속성에는 Solidity, Equivalent Diameter, Mask image, Mean Intensity 등입니다.

먼저 Aspect Ratio인데, 객체(Contour를 표현되는 것)에 대한 경계상자의 높이와 너비에 대한 비율이며 아래와 같습니다.

OpenCV에서는 다음과 같은 코드를 통해 얻을 수 있습니다.

x,y,w,h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h

다음은 Extent입니다. 이 속성은 경계상자의 넓이에 대한 객체의 실제 넓이의 비율입니다.

코드는 다음과 같습니다.

area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area

다음은 Equivalent Diameter입니다. 이 속성은 객체의 실제 면적와 동일한 면적을 갖는 원의 지름입니다.

코드는 다음과 같습니다.

area = cv2.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)

다음은 Solidity입니다. 이 값은 객체의 볼록껍질 면적에 대한 객체의 면적 비율입니다.

코드는 다음과 같습니다.

area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area

다음은 Orientation입니다. 이 속성은 객체가 놓여진 방향에 대한 각도인데, 다음 식은 주축과 보조축으로써 표현한 코드 예로 angle 변수값이 각도입니다.

(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)

다음은 Mask과 Pixel Points입니다. 어떤 경우에 객체를 구성하는 모든 포인트가 필요할 수 있습니다. 이를 위해 다음 코드가 사용됩니다.

mask = np.zeros(imgray.shape,np.uint8)
cv2.drawContours(mask,[cnt],0,255,-1)
pixelpoints1 = np.transpose(np.nonzero(mask))
pixelpoints2 = cv2.findNonZero(mask)

pixelpoints1와 pixelpoints2는 동일한 결과인데, 각각 Numpy와 OpenCV를 사용한 방식입니다.

다음은 최대값, 최소값과 위치값입니다. 이 값들은 마스크 이미지를 사용해 얻을 수 있습니다.

mask = np.zeros(imgray.shape,np.uint8)
cv2.drawContours(mask,[cnt],0,255,-1)

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)

위의 1,2번 코드는 마스크 이미지를 생성합니다. 이 마스크 이미지는 최대, 최소값과 이 값들의 위치가 어디인지에 대한 범위를 제한하는데 사용됩니다. mask 이미지의 적용은 옵션입니다.

다음은 평균 색상과 평균 강도입니다. 마스크 이미지를 적용해 해당 마스크 이미지 범위에 존재하는 픽셀들의 평균색상 또는 평균강도 값을 얻는 코드는 다음과 같습니다.

mean_val = cv2.mean(im,mask = mask)

끝으로 Extreme Points입니다. 이 속성은 객체의 최상단, 최하단, 좌측끝단, 우측끝단의 포인트를 의미하는데, 먼저 코드를 보면..

import numpy as np
import cv2
 
img = cv2.imread('./data/thunder.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
 
cnt = contours[0]
 
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])

cv2.circle(img, leftmost, 10, (0,0,255), -1)
cv2.circle(img, rightmost, 10, (0,0,255), -1)
cv2.circle(img, topmost, 10, (0,0,255), -1)
cv2.circle(img, bottommost, 5, (0,255,255), -1)

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

위의 코드 중 11-14번과 16-19번이 최상단, 최하단, 좌측끝단, 우측끝단의 포인트를 얻고 그려주는 코드입니다. 결과는 다음과 같습니다.

Python과 OpenCV – 16 : 이미지의 등치선(Contours) – 2/5

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contour_features/py_contour_features.html#contour-features 입니다.

이 글에서는 등치선에서 면적, 가장자리 길이, 중심선, 바운딩 박스(Bounding Box) 등을 추출하는 내용에 대한 다양한 함수를 살펴 봅니다.

이미지에는 Moments라는 속성을 통해 객체의 무게중심점이나 면적등과 같은 몇가지 특성을 계산할 수 있습니다. 이미지 Moments에 대한 내용은 https://en.wikipedia.org/wiki/Image_moment 을 참고하기 바랍니다. 여기서 객체는 이미지에서 추출한 등치선으로 구성된 것을 의미합니다.

cv2.moments 함수가 이미지의 Moments를 계산하는데, 예제는 아래와 같습니다.

import cv2
import numpy as np

img = cv2.imread('./data/cornerTest.jpg', cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(img,127,255,0)
image, contours,hierarchy = cv2.findContours(thresh, 1, 2)

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

결과는 다음과 같은 이미지의 Moments 특성들이 출력됩니다.

{'m00': 58816.0, 'm10': 14851193.5, 'm01': 14810238.5, 'm20': 4092644028.833333, 'm11': 3834175447.5, 'm02': 4001879218.1666665, 'm30': 1206388074009.75, 'm21': 1075214481081.5, 'm12': 1056320648809.1666, 'm03': 1146271400880.25, 'mu20': 342678910.93272305, 'mu11': 94551573.98514414, 'mu02': 272568071.0828414, 'mu30': -69863083.65454102, 'mu21': -3088048920.343384, 'mu12': -1781659324.0393372, 'mu03': 1304351953.3330078, 'nu20': 0.09905956288466355, 'nu11': 0.027332401528683933, 'nu02': 0.07879234209158287, 'nu30': -8.327392546410929e-05, 'nu21': -0.0036808274437723507, 'nu12': -0.0021236647166352715, 'nu03': 0.0015547339404300424}

위의 값 중 m00은 객체의 넓이이고, 무게 중심점 cx, cy는 각각 m10/m00, m01/m00입니다. 객체의 넓이는 cv2.contourArea 함수를 통해서도 얻을 수 있습니다.

area = cv2.contourArea(cnt)

객체, 즉 등치선의 가장자리 길이는 다음처럼 얻을 수 있습니다.

perimeter = cv2.arcLength(cnt,True)

함수의 두번째 인자는 등치선이 폐합 여부입니다.

등치선은 매우 많은 좌표로 구성될 수 있습니다. 이에 대해 필요에 맞게 간략화가 필요한데, 간략화 알고리즘은 Douglas-Peucker Algorithm을 사용합니다. 아래의 예제는 어떤 등치선에 대해 자신의 가장자리 길이의 10% 길이값을 기준으로 간략화가 적용해 새로운 등치선을 계산합니다.

epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)

아래 그림을 보면..

첫번째 이미지가 입력된 등치선이고.. 두번째가 가장자리 거리의 10% 길이를 기준으로 간략화된 등치선, 세번째가 1% 길이를 기준으로 간략화된 등치선을 초록색 선으로 표기하고 있습니다.

지금까지의 내용을 되짚어 보면, 등치선은 하나의 2차원 도형으로 생각할 수 있습니다. 도형에 대한 연산 중 볼록껍대기(Convex Hull) 도형을 추출하는 함수는 다음과 같습니다.

hull = cv2.convexHull(cnt)

주어진 등치선이 볼록 도형인지의 여부는 다음 함수를 통해 식별할 수 있습니다.

k = cv2.isContourConvex(cnt)

등치선으로 구성된 도형에 대해.. 이 도형을 감싸는 다양한 또 다른 도형을 생각해 볼 수 있는데, 가장 쉽게는 사각영역을 생각해 볼 수 있습니다. 아래 코드가 바로 도형을 감싸는 사각형 영역을 추출하는 예제입니다.

import numpy as np
import cv2
 
img = cv2.imread('./data/thunder.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

cnt = contours[0]

x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),2)

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

결과는 다음과 같습니다.

단순한 사각형이 아닌 최소 넓이를 갖는 회전된 사각영역도 얻을 수 있는데, 코드는 다음과 같습니다.

import numpy as np
import cv2
 
img = cv2.imread('./data/thunder.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

cnt = contours[0]

rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
img = cv2.drawContours(img,[box],0,(0,0,255),2)

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

결과는 다음과 같습니다.

도형을 감싸는 정방원에 대한 예제 코드는 아래와 같습니다.

import numpy as np
import cv2
 
img = cv2.imread('./data/thunder.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

cnt = contours[0]

(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,0,255),2)

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

결과는 다음과 같습니다.

감싸는 최소 넓이의 타원은 다음과 같습니다.

import numpy as np
import cv2
 
img = cv2.imread('./data/thunder.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

cnt = contours[0]

ellipse = cv2.fitEllipse(cnt)
img = cv2.ellipse(img,ellipse,(0,0,255),2)

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

결과는 다음과 같습니다. (결과가 좀 이상한데…)

끝으로 등치선으로 구성된 도형의 주축에 대한 선을 얻는 예제는 다음과 같습니다.

import numpy as np
import cv2
 
img = cv2.imread('./data/thunder.png')
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

cnt = contours[0]

rows,cols = img.shape[:2]
[vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
img = cv2.line(img,(cols-1,righty),(0,lefty),(0,0,255),2)

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

결과는 다음과 같습니다.