2016年12月1日 星期四

OPENCV(11)--contours and convex hull(輪廓與凸包)


         這篇介紹OPENCV提供找出輪廓與繪製輪廓,凸包的函數。

<圖一輪廓,凸包>





 什麼是輪廓:

        輪廓可以簡單認為成將連續的點(連著邊界)連在一起的曲線,具有相同的顏色或者灰度。輪廓在形狀分析和物體的檢測和識別中很有用。

• 為了更加準確,必須使用二值化圖像。所以在尋找輪廓之前,要進行閾值化處理或者 Canny
   邊界檢測。
• 查找輪廓的函數會修改原始圖像。如果你在找到輪廓之後還想使用原始圖像的話,應該將
   原始圖像存儲到其他變數中。
• 在 OpenCV 中,查找輪廓就像在黑色背景中找超白色物體。注意,要找的物體應該是白色
   而背景應該是黑色。

OPENCV提供函數 cv2.ftndContours()找輪廓:
        它有三個參數,第一個是輸入圖像,第二個是輪廓檢索模式,第三個是輪廓近似方法。
返回值有三個,第一個是圖像,第二個是輪廓,第三個是輪廓的層析結構。
第二個返回值"輪廓"是一個Python 清單(list),其中存儲這圖像中的所有輪廓。每一個輪廓都是一個 Numpy 陣列,包含物件邊界點(x,y)的座標。



Ex:
img, contours_b, hierarchy = \
cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
參數:
        thresh:輸入二值化圖像
        cv2.RETR_TREE:輪廓檢索模式
        cv2.CHAIN_APPROX_SIMPLE:輪廓近似方法
        img:處理後的圖像
        contours_b:輪廓
        hierarchy:輪廓的層析結構

怎樣繪製輪廓:

OPENCV提供函數cv2.drawContours() 繪製輪廓:
        它可以根據你提供的邊界點繪製任何形狀。它的第一個參數是原始圖像,第二個參數是輪廓,一個Python列表。第三個參數是輪廓的索引(在繪製獨立輪廓是很有用,當設 置為 -1
時繪製所有輪廓)。接下來的參數是輪廓的顏色和厚度。

EX:
cv2.drawContours(img_O, contours_b, 0, (255,0,0), 3)
參數:
img_O:原始圖像
contours_b:輪廓
0:輪廓的索引,-1:all contours
  通常同一張圖上找到的輪廓不只一個,所以有輪廓個別的索引值
(255,0,0):顏色
3:畫線厚度


輪廓的近似方法:

         在函數cv2.ftndCountours()的第三個參數。它到底代表什麼意思呢?
上面我們提到輪廓是一個形狀具有相同灰度值的邊界。它會儲存形狀邊界上所有的 (x,y) 座
標。但是需要將所有的這些邊界點都存儲嗎?答案就是可以根據這個參數來告訴
cv2.ftndContours函數。

        參數如果被設置為 cv2.CHAIN_APPROX_NONE,所有的邊界點 都會被存儲。
但是我們真的需要這麼多點嗎?例如,當我們找的邊界是一條直線時。你用需要直線上所有
的點來表示直線嗎?
        不是的,我們只需要這條直線的兩個端點而已。所以這時參數就可以設置為
cv2.CHAIN_APPROX_SIMPLE。
它會將輪廓上的冗餘點都去掉,從而節省記憶體開支!

輪廓特徵:

可以查找輪廓的不同特徵,例如面積,周長,重心,邊界框等。

矩(Image Moments):

    圖像的矩可以説明我們計算圖像的質心,面積等。詳細資訊請查看維基百科Image Moments。

OPENCV 提供函數 cv2.moments() 可得到以一個字典的形式返回的矩。如下<圖二>紅色框框所示:

<圖二>

    根據這些矩的值,我們可以計算出物件的重心:
cx = int(M['m10']/M['m00'])
        cy = int(M['m01']/M['m00'])

P.S 這裡實際coding須注意,如果在沒找到輪廓的情況下,m00會等於0,會造成程式錯
        誤,所以編程時需特別注意這一點。

輪廓面積:
    輪廓的面積可以使用函數 cv2.contourArea() 計算得到。
area = cv2.contourArea(cnt)

輪廓周長:
也被稱為弧長。可以使用函數 cv2.arcLength() 計算得到。這個函數的第二參數可以用來指定物件的形狀是閉合的(True),還是打開的(一條曲線)。
    perimeter = cv2.arcLength(cnt,True)
 
 其重心,面積,周長如上<圖二>藍色框框所示。


凸包:

        凸包與輪廓近似相似,但不同,雖然有些情況下它們給出的結果是一樣的。
 函數 cv2.convexHull() 可以用來檢測一個曲線是否具有凸性缺陷,並能糾正缺陷。一般來說,凸性曲線總是凸出來的,至少是平的。如果有地方凹進去 了就被叫做"凸性缺陷"。
           例如下圖中的手。紅色曲線顯示了手的凸包,凸性缺陷被雙箭頭標出來了。

<圖三凸包>


要獲得上圖的凸包,下面的命令就夠了:
        hull = cv2.convexHull(cntb)
另外函數 cv2.isContourConvex() 可以用來檢測一個曲線是不是凸的。它只會返回 True 或 False。
              True 代表它是凸包並有"凸性缺陷",False 代表像是輪廓剛好貼齊邊界,沒有"凸性缺
              陷"。


凸包可以畫出兩種邊界矩形

1.直邊界矩形
          為一個直矩形就是沒有旋轉的矩形。它不會考慮物件是否旋轉。
 所以邊界矩形的面積不是最小的。可以使用函數 cv2.boundingRect() 查找得到。
(x,y)為矩形左上角的座標,(w,h)是矩形的寬和高。
Ex:
    x,y,w,h = cv2.boundingRect(cntb)
    cv2.rectangle(imga,(x,y),(x+w,y+h),(0,255,0),2)
2.旋轉的邊界矩形
        這個邊界矩形是面積最小的,因為它考慮了對象的旋轉。用到的函數為 cv2.minAreaRect()。返回的是一個 Box2D 結構,其中包含 矩形左上角角點的座標(x,y),矩形的寬和高(w,h),以及"旋轉角度"。要繪製這個矩形需要矩形的 4 個角點,可以通過函數 cv2.boxPoints() 獲 得。
程式執行結果如<圖一>右上角Flash_contours1
Ex:
    rect = cv2.minAreaRect(cntb)
    box = cv2.boxPoints(rect)
    box = np.int0(box)
    cv2.drawContours(imga,[box],0,(0,0,255),2)

最小外接圓:
     函數 cv2.minEnclosingCircle() 可以幫我們找到一個物件的外切圓。 它是所有能夠包括物件的圓中面積最小的一個。
EX:
(x,y),radius = cv2.minEnclosingCircle(cntb)
center = (int(x),int(y))
radius = int(radius)
cv2.circle(imgb,center,radius,(0,255,255),2)

橢圓擬合:

    使用的函數為  cv2.ellipse(),返回值其實就是旋轉邊界矩形的內切圓。
EX:
ellipse = cv2.fitEllipse(cntb)
cv2.ellipse(imgb,ellipse,(255,0,255),2)

程式執行結果如<圖一>左下角Flash_contours2

直線擬合:
我們可以根據一組點擬合出一條直線,同樣我們也可以為圖像中的白色點擬合出一條直線。
EX:
rows,cols = imgc.shape[:2]
[vx,vy,x,y] = cv2.fitLine(cntb, cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv2.line(imgc,(cols-1,righty),(0,lefty),(255,255,0),2)

程式執行結果如<圖一>右下角Flash_contours3



<完整範例程式>

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

imga = cv2.imread('flash.png')

imgRGB = cv2.cvtColor(imga,cv2.COLOR_BGR2RGB)
img_O =imgRGB.copy()
imgb =imgRGB.copy()
imgc =imgRGB.copy()
##灰階化
imgrayb = cv2.cvtColor(imga,cv2.COLOR_BGR2GRAY)
##二值化
ret,thresh = cv2.threshold(imgrayb,127,255,cv2.THRESH_BINARY)
##找輪廓
img, contours_b, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img_O, contours_b, 0, (255,0,0), 3)   #-1:all contours

##moments+
cntb = contours_b[0]
M = cv2.moments(cntb)
print (M)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
print ("重心=",cx,cy)
area = cv2.contourArea(cntb)
print("面積=",area)
perimeter = cv2.arcLength(cntb,True)
print("周長=",perimeter)
##moments-

##凸包檢測
hull = cv2.convexHull(cntb)
print("hull=",hull)
k = cv2.isContourConvex(cntb)
print(k)

for i in range(len(hull)):
 cv2.circle(img_O, tuple(hull[i][0]),3,[0,0,255],-1)

##畫矩形凸包
x,y,w,h = cv2.boundingRect(cntb)
cv2.rectangle(imga,(x,y),(x+w,y+h),(0,255,0),2)

rect = cv2.minAreaRect(cntb)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(imga,[box],0,(0,0,255),2)
##畫圓形凸包,橢圓擬合
ellipse = cv2.fitEllipse(cntb)
cv2.ellipse(imgb,ellipse,(255,0,255),2)

(x,y),radius = cv2.minEnclosingCircle(cntb)
center = (int(x),int(y))
radius = int(radius)
cv2.circle(imgb,center,radius,(0,255,255),2)
##畫直線擬合
rows,cols = imgc.shape[:2]
[vx,vy,x,y] = cv2.fitLine(cntb, cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv2.line(imgc,(cols-1,righty),(0,lefty),(255,255,0),2)

##顯示圖片
plt.subplot(2,2,1),plt.imshow(img_O)
plt.title("Flash_contours0"), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,2),plt.imshow(imga)
plt.title("Flash_contours1"), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,3),plt.imshow(imgb)
plt.title("Flash_contours2"), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,4),plt.imshow(imgc)
plt.title("Flash_contours3"), plt.xticks([]), plt.yticks([])
plt.show()
 





加入阿布拉機的3D列印與機器人的FB專頁
https://www.facebook.com/arbu00/


<其他有關OPENCV 文章>
機器學習(2)--使用OPENCV SVM實作手寫辨識
機器學習(1)--使用OPENCV KNN實作手寫辨識
OPENCV(10)--Canny Edge Detection(Canny邊緣檢測)
OPENCV(9)--Image Gradients(圖像梯度)
OPENCV(8)--Histogram & Histograms Equalization(長條圖與長條圖均衡化)
OPENCV(7)--2D Convolution ,Image Filtering and Blurring (旋積,濾波與模糊)
OPENCV(6)--Trackbar(軌道桿)
OPENCV(5)--Drawing
OPENCV(4)--Grayscale,Binarization,Threshole(灰階化,二值化,閥值)
OPENCV(3)--Matplotlib pyplot bassic function
OPENCV(2)--Capture Video from Camera
OPENCV(1 )--How to install OPENCV in Python

<參考資料:>OPENCV官網
http://docs.opencv.org/3.1.0/index.html