1. 程式人生 > >(三)OpenCV中的影象處理之輪廓

(三)OpenCV中的影象處理之輪廓

註釋:本文翻譯自OpenCV3.0.0 document->OpenCV-Python Tutorials,包括對原文件種錯誤程式碼的糾正

該章節分為以下四個小節:

(一)     Contours:Getting Started(輪廓:開始)

(二)     Contours Features(輪廓特徵)

(三)     Contours Properties(輪廓屬性)

(四)     Contours:More Functions(輪廓:更多方法)

(五)     Contours Hierarchy(輪廓分級)

第一小節:Contours:GettingStarted

1.目標:

  • 明白什麼是輪廓
  • 學會找到這些輪廓,繪製輪廓
  • 學習這些函式:cv2.findContours(),cv2.drawContours()

2.什麼是輪廓

輪廓可以簡單地解釋為連線所有連續點(沿著邊界),具有相同顏色或強度的曲線。輪廓是形狀分析和物體檢測和識別的有用工具。

  •  為了更高的準確率,使用二值影象。在尋找輪廓之前,應用閾值或canny邊緣檢測
  • findContours函式修改源影象,所有想要在找到輪廓後儲存源影象,提前把源影象賦值給其它變數
  • 再OpenCV中,查詢輪廓就像從黑色背景中找到白色物體,所以記住,找到的物體應該是白色的,背景應該是黑色的。

下面的栗子:在二值影象中找到輪廓

'''
Opencv中的輪廓:
demo1
'''
import cv2

img = cv2.imread('2.jpg')
# 影象灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 3*3核的高斯濾波
gray = cv2.GaussianBlur(gray, (3, 3), 0)
# canny邊緣檢測
gray = cv2.Canny(gray, 100, 300)

ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# binary是最後返回的二值影象
#findContours()第一個引數是源影象、第二個引數是輪廓檢索模式,第三個引數是輪廓逼近方法
#輸出是輪廓和層次結構,輪廓是影象中所有輪廓的python列表,每個單獨的輪廓是物件邊界點的(x,y)座標的Numpy陣列
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1, (0, 0, 255), 1)
cv2.imshow("1", binary)
cv2.imshow("win10", img)

cv2.waitKey(0) & 0xFF

結果:


3.怎樣繪製輪廓

Cv2.drawContours()用來繪製輪廓,只要提供了邊界點它也可以用來繪製各種形狀。它的第一個邊界是源影象,第二個引數是Python列表傳遞的輪廓,第三個引數是輪廓的索引(在繪製單個輪廓時有用,在繪製多個輪廓時傳遞-1),剩餘的引數是顏色、厚度等。

繪製圖像中的所有輪廓:

cv2.drawContours(img, contours, -1, (0,255,0), 3)

繪製個別輪廓,如第四輪廓:

cv2.drawContours(img, contours, 3, (0,255,0), 3)

但是通常情況下,下面的方式更有用:

cnt = contours[4]
cv2.drawContours(img, [cnt], 0
, (0,255,0), 3)

4.輪廓近似法

輪廓近似法是cv2.findContours()中第三個引數。

前面,我們說輪廓是具有相同強度的形狀的邊界。它儲存形狀邊界的(x,y)座標,但是它是否儲存所有座標?這是通過這種輪廓法來逼近的。

如果傳遞的引數為cv2.CHIAN_APPROX_NONE,則會儲存所有邊界。但實際情況下我們可能並不需要所有的點,比如發現一條直線的輪廓,所需要的只是這條線的兩個端點。這就是cv2.CHAIN_APPROX_SIMPLE所做的,它刪除了所有冗餘的點並壓縮輪廓,從而大大節省了記憶體。

在下面矩形影象中顯示了這種技術,在輪廓陣列上的所有座標上繪製一個圓,第一個用cv2.CHIAN_APPROX_NONE繪製(734個點),第二個用cv2.CHAIN_APPROX_SIMPLE(4個點)繪製。


第二小節:ContoursFeatures(輪廓特徵)

5.目標

  • 找不同的輪廓特徵,如:面積、周長、重心、邊框
  • 學會關於輪廓的方法

6.時刻

影象時刻可以幫助你計算一些特徵,如物體的質心、物體的面積等。關於影象時刻的概念,參考維基百科。

函式cv2.moments()給出了計算所有時刻值的字典,詳細如下:

import cv2
import numpy as np
img = cv2.imread('star.jpg',0)
ret,thresh = cv2.threshold(img,127,255,0)
binary,contours,hierarchy = cv2.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv2.moments(cnt)
print M

然後可以提取有用的資訊,如面積、質心等。質心由下面關係式給出:

    
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])

7.輪廓區域

輪廓區域由cv2.contourArea()計算,或者由時刻中的M[‘m00’]得到:

area = cv2.contourArea(cnt)

8.輪廓周長

也稱為輪廓弧長,由cv2.arcLength()計算得到,第二個引數指定shape是封閉輪廓(True)或僅僅是曲線。

perimeter = cv2.arcLength(cnt,True)

9.輪廓近似

如果影象沒有明確的輪廓,使用cv2.approxPolyDP近似此輪廓。

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

10.Convex Hull

ConvexHull將看起來類似於輪廓近似,但它不是(兩者可能在某些情況下提供相同的結果)。這裡,cv2.convexHull()函式檢查一個曲線的凸性缺陷並進行修正。一般來說,凸曲線是總是凸出的或至少平坦的曲線。如果內部膨脹,則稱為凸面缺陷。例如,檢查下面的影象。紅線顯示手的凸包。雙面箭頭標記顯示凸度缺陷,這是船體與輪廓的區域性最大偏差。

有必要去解釋以下這個函式的引數:

hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]

引數詳情:

  • Points:是傳入的輪廓;
  • Hull:是輸出,通常我們避免它;
  • Clockwise:方向標誌。如果是true,則順時針輸出凸包,否則逆時針方式輸出;
  • ReturnPoints:預設情況下為true。它返回hull點的座標,如果是false,則返回與hull點對應的輪廓點的索引。

所以要得到如上圖所示的凸包,下面這條語句就足夠了:

hull = cv2.convexHull(cnt)

但是如果你想找到凸面缺陷,你需要傳遞returnPoints=False。為了理解它,我們將採用上面的矩形影象。首先,我發現它的輪廓為cnt。現在我發現它的凸包帶有returnPoints=True,我得到了以下值:[[[234202]],[[51202]],[[5179]],[[23479]]]這是四角矩形點。現在,如果對returnPoints=False做同樣的處理,我會得到如下結果:[[129],[67],[0],[142]]。這些是輪廓中相應點的指數。例如,檢查第一個值:cnt[129]=[[234,202]],它與第一個結果相同(其它的點也是如此)。

11.檢查凸度

這裡有一個函式可以檢查曲線是否凸起,cv2.isContourConvex().返回值為True或False.

k = cv2.isContourConvex(cnt)


12.邊界矩形

1)直線邊界矩形

它是一個直的矩形,不考慮物件的旋轉。所以邊界矩形的面積不會最小。這個矩形由函式cv2.boundingRect()發現。

設(x,y)為矩形的左上角座標,(w,h)為其寬度和高度.

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

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

2)旋轉矩形

這裡,邊界矩形是以最小面積繪製的,所以它也考慮旋轉。使用的函式是cv2.minAreaRect().它返回一個Box2D的結構,使用包含以下結構——(中心(x,y),(寬度,高度),旋轉角度)。但要繪製這個矩形,我們需要矩形的四個角。它是通過函式cv2.boxPoints()獲得的。

rect = cv2.minAreaRect(cnt)

box = cv2.boxPoints(rect)

box = np.int0(box)

cv2.drawContours(img,[box],0,(0,0,255),2)

兩種邊界矩形都在下圖中顯示,綠色是一般的邊界矩形,紅色的是旋轉邊界矩形


13.最小封閉圈

接下來我們使用函式cv2.minEnclosingCircle()找到物件的外接圓。它是一個以最小面積完全覆蓋物體的圓圈。

(x,y),radius = cv2.minEnclosingCircle(cnt)

center = (int(x),int(y))

radius =int(radius)

cv2.circle(img,center,radius,(0,255,0),2)

14.擬合橢圓

接下來將一個橢圓擬合到一個物件,它返回橢圓被刻在其中的旋轉矩形。

ellipse = cv2.fitEllipse(cnt)

cv2.ellipse(img,ellipse,(0,255,0),2)

15.擬合線條

同樣我們可以用一條線來擬合一組點。下圖包含一組白點,可以近似為一條線。

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)

cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)

第三小節:ContoursProperties(輪廓屬性)

1.目標:

  • 學會找到不同輪廓的屬性,如solidity,mean intensity等
  • 學習提取像Solidity、Equivalent Diameter, Mask image,Mean Intensity等物件的一些常用屬性

2.縱橫比(Aspect Ratio)

 它是物件矩陣的寬高比:


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

3.範圍(Extent)

範圍是輪廓面積和邊界矩形面積的比率:


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

4.密實度(Solidity)

密實度是輪廓面積與其凸包面積的比率:

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

5.等效直徑(EquivalentDiameter)

等效直徑是與輪廓面積相同的圓的直徑


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

6.方向(Orientation)

方向是物體指向的角度。以下方法也給出主軸和副軸長度。

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

7.蒙版和畫素點(Maskand Pixel Points)

在某些情況下,我們可能需要包含該物件的所有點。它可以用以下方法做到:

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

這裡有兩個方法,一個使用Numpy函式,另一個使用OpenCV函式(最後一行註釋)來執行相同操作。結果是一樣的,但略有不同。Numpy以(行,列)格式給出座標,而OpenCV以(x,y)格式給出座標。所以基本上答案會互換。請注意,row=x和column=y.


8.最大最小值和它們的位置(maximumvalue,minimum value and their locations)

我們可以使用蒙版影象來找到這些引數。

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

9.平均顏色或平均強度(meanColor or mean Intensity)

在這裡,我們可以找到一個物體的平均顏色。或者它可以是灰度模式下物體的平均強度。我們再次使用相同的掩膜來做到這一點。

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

10.極值點(ExtremePoints)

極值點表示物件的最上面、最下面、最左邊和最右邊點。

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

第四小節:Contours:More Functions (輪廓:更多方法)

1.目標:

  • 如何找到凸起缺陷
  • 尋找從一個點到多個點的最短距離
  • 匹配不同的形狀

2.凸起缺陷

我們在第二章看到什麼是凸包,關於輪廓。物體與該hull之間的任何偏差均可視為凸度缺陷。

OpenCV提供了一個現成的函式來查詢這個,cv2.convexityDefects().基本的函式呼叫如下:

hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)

Note:請記住,我們必須在找到凸包時通過returnPoints=False,以便找到凹凸缺陷。

它返回一個數組,其中每行包含這些值——[起點,終點,最遠點,到最遠點的近似距離]。我們可以使用影象對其進行視覺化。我們繪製一條連線起點和終點的線,然後在最遠點繪製一個圓。記得前三個返回的時cnt的索引。所以我們必須從cnt中提取這些點。

示例程式碼:

# -*- coding: utf-8 -*-
import cv2
import numpy as np

img = cv2.imread('13.png')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(img_gray, 127, 255, 0)
binary, contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt = contours[0]

hull = cv2.convexHull(cnt, returnPoints=False)
defects = cv2.convexityDefects(cnt, hull)

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(0)
cv2.destroyAllWindows()

結果如下:


3.點多邊形測試

該函式可查詢影象中某點與輪廓之間的最短距離。當點在輪廓之外時,它返回負值的距離,點在內部時返回正值,如果點在輪廓上則返回零。

例如,我們可以按如下方式檢查點(50,50):

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

在這個函式中,點三個引數是measureDist。如果它是true,它會找到標記的距離。如果是false,它會查詢該點是在內部還是在外部還是在輪廓上。(分別返回+1,-1,0).

Note:如果你不想查詢距離,請確保第三個引數是false,因為這是一個耗時的過程。所以,設定為false可以加速2-3倍。

4.形狀匹配

OpenCV帶有一個函式cv2.matchShapes(),可以用來比較兩個形狀或兩個輪廓,並返回一個顯示相似度的度量。結果越低,匹配效果越好。它基於hu-moment值進行計算。文件中解釋了不同的測量方法。

# -*- coding: utf-8 -*-
'''
形狀匹配
'''
import cv2
import numpy as np

img1 = cv2.imread('car.png', 0)
img2 = cv2.imread('car1.png', 0)

ret, thresh = cv2.threshold(img1, 127, 255, 0)
ret, thresh2 = cv2.threshold(img2, 127, 255, 0)
binary, contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt1 = contours[0]
binary, contours, hierarchy = cv2.findContours(thresh2, 2, 1)
cnt2 = contours[0]

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

試著用上面的圖片進行融合,輸出結果:

得到的結果:

  • 形狀A和它自身,返回值:0
  •  形狀A和形狀B,返回值:0.001946
  • 形狀A和形狀C,返回值:0.326911

可以看出,即使影象旋轉對此比較的影響也不大。

聯絡:

1. 檢查cv2.pointPolygonTest()的文件,你可以找到一個紅色和藍色的漂亮影象。它表示從所有畫素到白色曲線的距離。 取決於距離,曲線內的所有畫素都是藍色的。 同樣的外點是紅色的。 輪廓邊緣用白色標記。 所以問題很簡單。 編寫一個程式碼來建立這樣的距離表示

2. 使用cv2.matchShapes()比較數字或字母的影象。(這將是向OCR邁出的簡單一步)


第五小節:ContourHierarchy(輪廓的層次結構)

1.目標:我們瞭解輪廓的層次結構,即輪廓中的親子關係。

2.原理

在最近幾篇關於輪廓的文章中,我們已經使用了OpenCV提供的與輪廓相關的幾個函式。但當我們使用cv2.findContours()函式在影象中找到輪廓時,我們已經傳遞了一個引數ContourRetrieval Mode,我們通常會傳遞cv2.RETR_LIST或者cv2.RETR_TREE,而且執行效果很好。但它們實際上意味著什麼呢?

另外,在輸出中,我們得到了三個陣列,第一個是影象,第二個是輪廓,還有一個我們命名為層次結構的輸出。但是我們從來沒有在任何地方使用這個層次,那麼這個層次結構是什麼?它有什麼用途?它與前面提到的函式引數有什麼關係?

這些就是我們要談論的點。


3.什麼是層次結構

通常我們使用cv2.findContours()函式來檢測影象中的物件。有時物體位於不同的位置,但在某些情況下,某些形狀是其它形狀。就像巢狀數字一樣。在這種情況下,我們稱外層為父層,內層為子層。這樣,影象中的輪廓彼此之間有一些關係。我們可以指定一個輪廓是如何互相連線的,例如,它是否是其它輪廓的子節點,或者它是否是父節點等。此關係的表示形式稱為層次結構。

考慮下面的示例圖片:

在這幅影象裡,這裡有一些形狀被標記為0-5,2和2a表示最外面盒子的外部和內部輪廓。

這裡,輪廓0,1,2是外部或者最外面的。我們可以說,它們在層次結構0中,或者只是它們處於同一層次結構中。

接下來是輪廓2a,它可以被認為是輪廓2的子輪廓(或者相反,輪廓2是輪廓2a的父輪廓)。所以,讓它在層次1,類似的,輪廓3是輪廓2的子輪廓,並且它在下一層出現。最後,輪廓4,5是輪廓3a的子輪廓,它們進入最後一個層級。從我對盒子的編號方式來看,我會說輪廓4是輪廓3a的第一個子輪廓(也就是輪廓5)。

我提到了這些名詞術語。相同的層級(same hierarchy level),外部輪廓(external contour),子輪廓(child contour),父輪廓(parent contour),第一個子輪廓(first child)。好了現在進入OpenCV.


4.OpenCV中的層次結構的表示(HierarchyRepresentation in OpenCV)

所以每個輪廓都有自己的資訊,包括它是什麼層級、誰是它的孩子,誰是它的父母等。OpenCV將它表示為一個包含四個值的陣列:[Next,Previous,First_child,Parent].


5.輪廓檢索模式(ContourRetrieval Mode)

1)RETR_LIST

這是四種flag中最簡單(從解釋的角度看)。它只檢索所有輪廓,但不建立任何父子關係。在這條規則下,父母和孩子是平等的,他們只是輪廓。即它們都屬於同一層級。

所以在這裡,層級陣列中的第三和第四項總是-1。但顯然,next和previous項將具有相應的值。只需自己檢查並驗證它。

下面是我得到的結果,每行都是相應輪廓的層次結構細節。例如,第一行對應於輪廓0,下一個輪廓是輪廓1。因此,next=1沒有先前的輪廓,所以previous=0.其餘兩個,如前所述,它是-1.

>>> hierarchy
array([[[ 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],
        [-1,  6, -1, -1]]])

如果你不使用任何層次結構功能,則這是在程式碼中使用的理想選擇。

2)RETR_EXTERNAL

如果你使用這個標誌,它只會返回極端的外部標誌。所有的孩子輪廓都被留下。我們可以說,根據這條規定,只有每個家庭中年長的人才會得到照顧。它不關係家庭的其它成員。

那麼,在我們的影象中,那裡還有多少個極端的外部輪廓呢?即在層級0級別的?只有3個,即輪廓0,1,2對嗎?現在嘗試用這個flag查詢輪廓。在這裡,給每個元素的值與上面相同。將它與以上結果進行比較以下是我得到:

hierarchy
array([[[ 1, -1, -1, -1],
        [ 2,  0, -1, -1],
        [-1,  1, -1, -1]]])

3)RETR_CCOMP

該flag檢索所有輪廓並將它們排列為2級層次結構。即物件的外部輪廓(即其邊界)被放置在層級-1中。物件內的孔的輪廓(如果有的話)放置在層次結構-2中。如果它內部有任何物體,其輪廓只能重新放置在層次1中。它在等級2中的孔洞等等。

只要考慮黑色背景上的“大白色的零”影象即可。零的外圈屬於第一層次,零的內圈屬於第二層次。

我們可以用一個簡單的影象來解釋它。在這裡,我們已經用紅色標出了輪廓的順序以及它們所屬的層次,顏色為綠色(1或2)。順序與OpenCV檢測輪廓的順序相同。


所以考慮第一個輪廓,即輪廓0.它的層次結構是1.它有兩個孔,輪廓1和2,它們屬於層次2.因此對於輪廓0,同一層級中的下一個輪廓是輪廓3.其第一個孩子是在等級2中的輪廓-1。他沒有父項,因此它在層次結構1中。所以它的層次陣列是[3,-1,1,-1]。

現在輪廓-1。他在層次結構2中。下一個在同一層次中(在輪廓-1的父項之下)是輪廓2.沒有前一個,也就是沒有孩子。父母的輪廓是0,所以陣列是[2,-1,-1,0].

同樣的輪廓-2:它在層次-2.輪廓0下的同一層次中沒有下一個輪廓。所以沒有下一步。以前是輪廓-1.沒有孩子啊,父母是輪廓-0.所以陣列是[-1,1,-1,0].

輪廓-3:層次-1中的下一個輪廓是-5.以前是輪廓-0。孩子是輪廓4並且沒有父母。所以陣列是[5,0,4,-1].

輪廓4:在輪廓-3下的層次2中,他沒有兄弟。所以沒有下一個,沒有以前,沒有孩子,父母是輪廓3.所以陣列是[-1,-1,-1,3].

這是我得到的最終答案:

>>> hierarchy
array([[[ 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]]])

4)RETR_TREE

這是最後一個,它檢索所有輪廓並建立完整的家庭層次列表。它甚至告訴我們,爺爺,父親,兒子,孫子是誰,甚至還有….

例如,我拍攝了上面的影象,重寫了cv2.RETR_TREE的程式碼,根據OpenCV給出的輪廓重新排序並對其進行分析。再次,紅色字母給出等高線數字,綠色字母給出等級順序。

取輪廓-0:它在層次結構-0中。同一層次中的下一個輪廓是林廓-7.沒有以前的輪廓。孩子是輪廓-1.沒有父母。所以陣列是[7,-1,1,-1].

採取輪廓-2:它在層次1.沒有同一級別的輪廓。沒有前一個,孩子是輪廓2。父級輪廓為0。所以陣列是[-1,-1,2,0].

以下是完整答案:

>>> hierarchy
array([[[ 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]]])

最後一份程式碼示例:


'''
OpenCV中的輪廓:
1.繪製輪廓:cv2.drawCOntours(img,contours,index,color,thickness)
2.輪廓近似法:有時候並不需要儲存所有邊界,只需要儲存這條線的兩個端點。刪除所有冗餘的點並壓縮輪廓,從而大大節省記憶體
3.輪廓特徵:cv2.moments()計算所有時刻值的字典,如物體的質心、物體的面積、周長、重心等
'''
import cv2
import numpy as np
from matplotlib import pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用來正常顯示中文標籤
plt.rcParams['axes.unicode_minus'] = False  # 用來正常顯示負號

img = cv2.imread('temple.png', 0)

ret, thresh = cv2.threshold(img, 100, 255, 0)
binary, contours, hierarchy = cv2.findContours(thresh, 1, 2)
cv2.imshow('1', binary)

cnt = contours[0]
'''
M = cv2.moments(cnt)
# 輪廓周長
perimeter = cv2.arcLength(cnt, True)
# 輪廓區域
area = cv2.contourArea(cnt)  # area=M['m00']
# 輪廓近似
epsilon = 0.1 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 輪廓凸包
hull = cv2.convexHull(cnt)

print('cnt:', cnt)
print('輪廓近似座標:', approx)
print('輪廓周長:', perimeter)
print('輪廓區域:', area)
print('影象時刻:', M)
'''

# 直線邊界矩形:是一個直的矩形,不考慮物件的旋轉,所以邊界矩形的面積不會最小,這個矩形由cv2.boundingRect()發現
x, y, w, h = cv2.boundingRect(cnt)
img = cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)

# 旋轉矩形:邊界矩形以最小面積繪製的,所以它考慮旋轉,使用的函式是cv2.minAreaRect(),
# 返回一個Box2D的結構(中心(x,y),(寬度,高度),旋轉角度)
# 繪製這個矩形需要矩形的四個角,它是通過函式cv2.boxPoints()獲得的
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
img = cv2.drawContours(img, [box], 0, (0, 0, 255), 0)

# 最小封閉圈:使用函式cv2.minEnclosingCircle()找到物件的外接圓;它是一個以最小面積完全覆蓋物體的圓圈
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
img = cv2.circle(img, center, radius, (0, 255, 0), 2)

# 擬合橢圓:接下來將一個橢圓擬合到一個物件,它返回橢圓被刻在其中的旋轉矩形
ellipse = cv2.fitEllipse(cnt)
img = cv2.ellipse(img, ellipse, (0, 255, 0), 2)

# 擬合線條:用一條線來擬合一組點,下圖包含一組白點,可以近似為一條線
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, 255, 0), 2)
cv2.imshow('6', img)

'''
titles = ['原圖', '直線邊界矩形', '旋轉矩形', '最小封閉圈', '擬合橢圓', '擬合線條']
images = [img, rectContours, rotateRect, minCloseCircle, fitEllipse, fitLine]

for i in range(6):
    plt.subplot(2, 3, i + 1), plt.title(titles[i]), plt.imshow(images[i])
    plt.xticks([]), plt.yticks([])

plt.show()
'''

cv2.waitKey(0) & 0xFF

結果: