本文主要內容來自于 OpenCV-Python 教程 的 OpenCV 中的圖像處理 部分,這部分的全部主要內容如下:
-
學習在不同色彩空間之間改變圖像。另外學習跟蹤視頻中的彩色對象。
-
學習對圖像應用不同的幾何變換,比如旋轉、平移等。
-
學習使用全局閾值、自適應閾值、Otsu 的二值化等將圖像轉換為二值圖像。
-
學習模糊圖像,使用自定義內核過濾圖像等。
-
了解形態學變換,如侵蝕、膨脹、開放、閉合等。
-
學習尋找圖像漸變、邊緣等。
-
學習通過 Canny 邊緣檢測尋找邊緣。
-
學習關于圖像金字塔的內容,以及如何使用它們進行圖像混合。
-
所有關于 OpenCV 中的輪廓的內容。
-
所有關于 OpenCV 中的直方圖的內容。
-
在 OpenCV 中遇到不同的圖像變換,如傅里葉變換、余弦變換等。
-
學習使用模板匹配在圖像中搜索對象。
-
學習在一幅圖像中探測線。
-
學習在一幅圖像中探測圓。
-
學習使用分水嶺分割算法分割圖像。
-
學習使用 GrabCut 算法提取前景
輪廓:入門
目標
- 理解輪廓是什么
- 學習尋找輪廓,繪制輪廓等
- 我們將看到這些函數:cv.findContours(),cv.drawContours()
輪廓是什么?
輪廓可以簡單地解釋為連接所有連續點(沿邊界)的曲線,具有相同的顏色或強度。輪廓對于形狀分析,目標探測和識別是一個很有用的工具。
- 為了更高的精度,使用二值圖像。因此,在尋找輪廓之前,應用閾值或 canny 邊緣探測
- 自 OpenCV 3.2 開始,findContours() 不再修改原始圖像
- 在 OpenCV 中,尋找輪廓就像從黑色背景中找出白色物體一樣。因此要記得,要尋找的物體應該為白色的,背景應該為黑色的。
讓我們看一下如何尋找二值圖像的輪廓:
import numpy as np
import cv2 as cv
def simple_find_contours():
cv.samples.addSamplesDataSearchPath("/home/hanpfei/data/multimedia/opencv/samples/data")
img = cv.imread(cv.samples.findFile('butterfly.jpg'))
imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print("hierarchy shape ", hierarchy.shape)
if __name__ == '__main__':
simple_find_contours()
可以看到,cv.findContours() 函數中有三個參數,第一個是源圖像,第二個是輪廓檢索模式,第三個是輪廓逼近法。它輸出輪廓和層次結構。輪廓為圖像中所有輪廓的 Python 列表。每個單獨的輪廓為一個物體的邊界點的 (x,y) 坐標的 Numpy 數組。
注意
我們將在后面討論第二個和第三個參數,以及層次結構的詳細內容。在那之前,代碼示例中給它們的值將適用于所有圖像。
如何繪制輪廓?
要想繪制輪廓,可以使用 cv.drawContours 函數。只要你有邊界點,它也可以用于繪制任何形狀。它的第一個參數是源圖像,第二個參數是應該以 Python 列表的形式傳入的輪廓,第三個參數是輪廓的索引(繪制單個輪廓時很有用。要繪制所有輪廓,請傳遞 -1),其余的參數為顏色,粗細度等等。
- 在圖像中繪制所有輪廓的方法如下:
cv.drawContours(img, contours, -1, (0,255,0), 3)
- 在圖像中繪制單個輪廓,比如第 4 個輪廓的方法如下:
cv.drawContours(img, contours, 3, (0,255,0), 3)
- 但在大多數時候,下面的方法將很有用:
cnt = contours[4]
cv.drawContours(img, [cnt], 0, (0,255,0), 3)
在上面的示例代碼中添加在原圖像中繪制輪廓的邏輯,并顯示原圖像和包含輪廓的圖像的代碼如下:
import numpy as np
import cv2 as cv
def simple_find_contours():
cv.samples.addSamplesDataSearchPath("/home/hanpfei/data/multimedia/opencv/samples/data")
img = cv.imread(cv.samples.findFile('butterfly.jpg'))
img_original = img.copy()
imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(img, contours, -1, (0, 0, 255), 2)
dst = cv.hconcat([img_original, img])
cv.imshow("Butterfly contours", dst)
cv.waitKey(-1)
cv.destroyAllWindows()
if __name__ == '__main__':
simple_find_contours()
看到的結果應該像下面這樣:
輪廓逼近法
這是 cv.findContours 函數的第三個參數。它實際上表示什么呢?
上面,我們提到過輪廓是具有相同強度的形狀的邊界。它存儲形狀的邊界的 (x,y) 坐標。但它存儲所有的坐標么?這由輪廓逼近法指定。
如果傳入 cv.CHAIN_APPROX_NONE,則存儲所有的邊界點。但實際上我們需要所有的點么?比如,你找到了一條直線輪廓。你需要直線上的所有點來表示它么?不,我們只需要直線兩端的點即可。這就是 cv.CHAIN_APPROX_SIMPLE 所作的事情。它移除所有的冗余點并壓縮輪廓,從而節省內存。
下面的矩形圖像演示了這種技術。只需在輪廓數組中的所有坐標上畫一個圓圈(以藍色繪制)。第一張圖像顯示了我使用 cv.CHAIN_APPROX_NONE 獲得的點(734 個點),第二張圖像顯示了使用 cv.CHAIN_APPROX_SIMPLE 獲得的點(只有 4 個點)。
輪廓特征
目標
在這篇文章中,我們將學習
- 找出輪廓的不同特征,如面積、周長、質心、邊界框等
- 你將看到許多與輪廓相關的函數。
1. 矩
圖像矩可以幫助你計算一些特征,如物體的質心、物體的面積等。查看 圖像矩 的維基百科頁面。
函數 cv.moments() 給出了計算出的所有矩值的字典。 見下文:
import numpy as np
import cv2 as cv
def image_moments():
img = cv.imread('/media/data2/Download/star.jpg', 0)
ret, thresh = cv.threshold(img, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv.moments(cnt)
print(M)
if __name__ == '__main__':
image_moments()
從這個矩里,你可以提取有用的數據,比如面積、質心等。質心由這個關系給出, 和
。這可以通過如下代碼完成:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
如對于如下圖像 star.png
:
提取并繪制出質心的方法如下:
import numpy as np
import cv2 as cv
def image_moments():
img = cv.imread('/media/data2/Download/star.png', 0)
ret, thresh = cv.threshold(img, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv.moments(cnt)
print(M)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
cv.circle(img, (cx, cy), 10, (0, 0, 255), -1)
cv.imshow("Star", img)
cv.waitKey(-1)
cv.destroyAllWindows()
if __name__ == '__main__':
image_moments()
看到的結果應該像下面這樣:
2. 輪廓面積
輪廓面積由 cv.contourArea() 函數給出,或來自于矩,M['m00']。
area = cv.contourArea(cnt)
3. 輪廓周長
它也稱為弧長。可以使用 cv.arcLength() 函數找到它。第二個參數指定形狀是閉合輪廓(如果傳遞 True),還是只是曲線。
perimeter = cv.arcLength(cnt,True)
4. 輪廓逼近
它根據我們指定的精度將輪廓形狀近似為具有較少頂點數的另一個形狀。它是 Douglas-Peucker 算法 的一種實現。查看維基百科頁面以獲取算法和演示。
為了理解這一點,假設你試圖在圖像中找到一個正方形,但由于圖像中的一些問題,你沒有得到一個完美的正方形,而是一個“壞形狀”(如下圖第一張所示)。現在你可以使用這個函數來逼近形狀。在此,第二個參數稱為 epsilon,它是從輪廓到近似輪廓的最大距離。它是一個精度參數。需要明智地選擇 epsilon 以獲得正確的輸出。
epsilon = 0.1*cv.arcLength(cnt,True)
approx = cv.approxPolyDP(cnt,epsilon,True)
下面,在第二張圖片中,綠線顯示了 epsilon = 10% 弧長的近似曲線。第三張圖片顯示了相同的 epsilon = 1% 的弧長。第三個參數指定曲線是否閉合。
完整代碼如下所示:
import numpy as np
import cv2 as cv
def contour_approximation():
img = cv.imread('/media/data2/Download/approx.png')
gray_image = cv.imread('/media/data2/Download/approx.png', 0)
ret, thresh = cv.threshold(gray_image, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
image1 = img.copy()
epsilon = 0.1 * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)
cv.drawContours(image1, [approx], -1, (0, 0, 255), 2)
image2 = img.copy()
epsilon = 0.01 * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)
cv.drawContours(image2, [approx], -1, (0, 0, 255), 2)
dst = cv.hconcat([img, image1, image2])
cv.imshow("Contour Approximation", dst)
cv.waitKey(-1)
cv.destroyAllWindows()
if __name__ == '__main__':
contour_approximation()
結果如下圖:
5. 凸包
凸包看起來類似于輪廓逼近,但實際上并非如此(在某些情況下兩者可能提供相同的結果)。在這里,cv.convexHull() 函數檢查曲線是否存在凸面缺陷并進行糾正。一般來說,凸曲線是總是凸出或至少平坦的曲線。如果它向內凸出,則稱為凸面缺陷。例如,檢查下面的手的圖像。紅線展示了手的凸包。雙邊箭頭標記表示凸面缺陷,即包絡與輪廓的局部最大偏差。
關于它的語法有一點需要討論:
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]])
參數詳情:
- points 是我們傳入的輪廓。
- hull 是輸出,通常我們會避免它。
- clockwise:方向標志。如果為 True,則輸出凸包為順時針方向。否則,它是逆時針方向的。
- returnPoints:默認情況下,為 True。然后它返回包絡點的坐標。如果為 False,則返回與包絡點對應的輪廓點的索引。
因此,要獲得如上圖所示的凸包,以下內容就足夠了:
hull = cv.convexHull(cnt)
但是如果要查找凸性缺陷,則需要通過 returnPoints = False。為了理解它,我們將使用上面的矩形圖像。首先我找到它的輪廓為cnt。現在我找到了 returnPoints = True 的凸包,我得到了以下值:[[[234 202]],[[ 51 202]],[[ 51 79]],[[234 79]]],它們是矩形四個角的點。現在如果設置 returnPoints = False 做同樣的事情,我會得到以下結果:[[129],[ 67],[ 0],[142]]。這些是輪廓中對應點的索引。比如,檢查第一個值:cnt[129] = [[234, 202]] 這與第一個結果相同(其它結果以此類推)。
當我們討論凸性缺陷時,你會再次看到它。
上面例子中,手的圖像如下:
完整代碼如下所示:
import numpy as np
import cv2 as cv
def convex_hull():
img = cv.imread('/media/data2/Download/hands.png')
gray_image = cv.imread('/media/data2/Download/hands.png', 0)
ret, thresh = cv.threshold(gray_image, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
image1 = img.copy()
hull = cv.convexHull(cnt)
cv.drawContours(image1, [hull], -1, (0, 0, 255), 2)
dst = cv.hconcat([img, image1])
cv.imshow("Convex Hull", dst)
cv.waitKey(-1)
cv.destroyAllWindows()
if __name__ == '__main__':
convex_hull()
結果如下圖:
6. 檢查凸度
有一個函數來檢查曲線是否是凸的,cv.isContourConvex()。它只返回 True 或 False。沒有大礙。
k = cv.isContourConvex(cnt)
7. 邊界矩形
有兩種類型的邊界矩形。
7.a. 直邊界矩形
它是一個直的矩形,它不考慮物體的旋轉。所以邊界矩形的面積不會是最小的。它由函數 cv.boundingRect() 找到。
令 (x,y) 為矩形的左上角坐標, (w,h) 為其寬度和高度。
7.b. 旋轉矩形
在這里,邊界矩形是用最小面積繪制的,所以它也考慮了旋轉。用到的函數為 cv.minAreaRect()。它返回一個 Box2D 結構,其中包含以下詳細信息 - (中心 (x,y)、(寬度、高度)、旋轉角度)。但是要繪制這個矩形,我們需要矩形的 4 個角。由函數 cv.boxPoints() 獲得。
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img,[box],0,(0,0,255),2)
兩個矩形都顯示在一個圖像中。綠色矩形顯示正常邊界矩形。紅色矩形是旋轉后的矩形。原始圖像如下:
完整代碼如下所示:
import numpy as np
import cv2 as cv
def bounding_rectangle():
img = cv.imread('/media/data2/Download/image.png')
gray_image = cv.imread('/media/data2/Download/image.png', 0)
ret, thresh = cv.threshold(gray_image, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
image1 = img.copy()
x, y, w, h = cv.boundingRect(cnt)
cv.rectangle(image1, (x, y), (x + w, y + h), (0, 255, 0), 2)
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(image1, [box], 0, (0, 0, 255), 2)
cv.imshow("Bounding Rectangle", image1)
cv.waitKey(-1)
cv.destroyAllWindows()
if __name__ == '__main__':
bounding_rectangle()
結果如下圖:
8. 最小封閉圓
接下來我們使用函數 cv.minEnclosingCircle() 尋找物體的外接圓。它是完全覆蓋物體的面積最小的圓。
(x, y), radius = cv.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
cv.circle(img, center, radius, (0, 255, 0), 2)
完整代碼如下所示:
import numpy as np
import cv2 as cv
def minimum_enclosing_circle():
img = cv.imread('/media/data2/Download/image.png')
gray_image = cv.imread('/media/data2/Download/image.png', 0)
ret, thresh = cv.threshold(gray_image, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
(x, y), radius = cv.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
cv.circle(img, center, radius, (0, 255, 0), 2)
cv.imshow("Bounding Rectangle", img)
cv.waitKey(-1)
cv.destroyAllWindows()
if __name__ == '__main__':
minimum_enclosing_circle()
9. 擬合橢圓
下一個是將橢圓擬合到對象。它返回內接橢圓的旋轉矩形。
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img, ellipse, (0, 255, 0), 2)
完整代碼如下所示:
import numpy as np
import cv2 as cv
def fitting_an_ellipse():
img = cv.imread('/media/data2/Download/image.png')
gray_image = cv.imread('/media/data2/Download/image.png', 0)
ret, thresh = cv.threshold(gray_image, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img, ellipse, (0, 255, 0), 2)
cv.imshow("Bounding Rectangle", img)
cv.waitKey(-1)
cv.destroyAllWindows()
if __name__ == '__main__':
fitting_an_ellipse()
9. 擬合直線
同樣,我們可以將一條線擬合到一組點。 下圖包含一組白點。 我們可以近似為一條直線。
rows, cols = img.shape[:2]
[vx, vy, x, y] = cv.fitLine(cnt, cv.DIST_L2, 0, 0.01, 0.01)
lefty = int((-x * vy / vx) + y)
righty = int(((cols - x) * vy / vx) + y)
cv.line(img, (cols - 1, righty), (0, lefty), (0, 255, 0), 2)
完整代碼如下所示:
import numpy as np
import cv2 as cv
def fitting_a_ine():
img = cv.imread('/media/data2/Download/image.png')
gray_image = cv.imread('/media/data2/Download/image.png', 0)
ret, thresh = cv.threshold(gray_image, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
rows, cols = img.shape[:2]
[vx, vy, x, y] = cv.fitLine(cnt, cv.DIST_L2, 0, 0.01, 0.01)
lefty = int((-x * vy / vx) + y)
righty = int(((cols - x) * vy / vx) + y)
cv.line(img, (cols - 1, righty), (0, lefty), (0, 255, 0), 2)
cv.imshow("Bounding Rectangle", img)
cv.waitKey(-1)
cv.destroyAllWindows()
if __name__ == '__main__':
fitting_a_ine()
其它資源
練習
參考文檔
Done.