見出し画像

OpenCV 入門 (11) - 輪郭抽出

OpenCVの輪郭抽出についてまとめました。

前回

1. 輪郭抽出

輪郭抽出」とは、同じ色を持つ連続する点をつなげた曲線を抽出することです。画像内に任意の形状を持つ物体が存在するかどうか等を判定するために使います。OpenCVの「輪郭抽出」は、二値画像を使い、黒い背景から白い物体の輪郭を検出します。

2. 輪郭抽出の実行

輪郭抽出ため、RGB画像「sample.jpg」を準備します。

画像1

青色部分の輪郭抽出のコードは、次のとおりです。

import cv2
import numpy as np

# 画像の読み込み
img = cv2.imread('sample.jpg')

# グレースケール変換
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# HSV変換
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV_FULL)

# 色範囲によるマスク生成
img_mask = cv2.inRange(img_hsv, np.array([159, 127,0]), np.array([177, 255, 255]))

# 輪郭抽出
contours, hierarchy = cv2.findContours(
    img_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 小さい輪郭は誤検出として削除
contours = list(filter(lambda x: cv2.contourArea(x) > 100, contours))

# 輪郭の描画
cv2.drawContours(img, contours, -1, color=(0, 0, 255), thickness=2)

# イメージの表示
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

青の周囲に赤の輪郭が表示されます。

画像2

輪郭抽出には、cv2.findContours()を使います。

contours, hierarchy = cv2.findContours(image, mode, method[, offset])
・引数
 ・image : 入力画像。
 ・mode : 輪郭を検索する方法。
 ・method : 輪郭を近似する方法。
 ・offset : 輪郭に加算するオフセット。
・戻り値 
 ・contours
: 輪郭のリスト 。
 ・hierarchy : 階層構造のリスト。

modeでは輪郭をどのような階層構造で検索するかを指定します。

・cv2.RETR_EXTERNAL : 一番外側の輪郭のみ抽出。
・cv2.RETR_LIST : 全ての輪郭を抽出し、階層構造を作成しない。
・cv2.RETR_CCOMP : 全ての輪郭を抽出し、2階層の階層構造を作成。
・cv2.RETR_TREE : 全て輪郭を抽出し、ツリーの階層構造を作成。

methodでは、輪郭を近似する方法を指定します。多くの場合は「cv2.CHAIN_APPROX_SIMPLE」を選択します。

・cv2.CHAIN_APPROX_NONE : 全て。
・cv2.CHAIN_APPROX_SIMPLE : 端点のみ(長方形の場合は4点)。
・cv2.CHAIN_APPROX_TC89_L1 : Teh-Chin chain approximationアルゴリズムの1つを適用。
・cv2.CHAIN_APPROX_TC89_KCOS : Teh-Chin chain approximationアルゴリズムの1つを適用。

3. 輪郭の面積

輪郭の面積を計算するには、cv2.contourArea()を使います。

area = cv2.contourArea(contour[, oriented])
・引数
 ・contour
: 輪郭
 ・oriented : 輪郭の方向
・戻り値
 ・area
: 輪郭の面積

例の輪郭の面積を計算すると、次のようになります。

print('面積:', cv2.contourArea(contours[0]))
面積: 4932.0

4. 輪郭の周囲長

輪郭の周囲長を計算するには、cv2.arcLength()を使います。

len = cv2.arcLength(contour[, closed ])
・引数
 ・contour
: 輪郭
 ・closed : 輪郭が閉じているかどうか
・戻り値
 ・len
: 輪郭の周囲長

例の輪郭の周囲長を計算すると、次のようになります。

print('周囲長:', cv2.arcLength(contours[0], True))
周囲長: 431.58787393569946

5. 輪郭の近似

輪郭の近似を計算するには、cv2.arcLength()を使います。複雑な輪郭の頂点数を減らすことができます。

approx = cv2.approxPolyDP(contour, epsilon, closed)
・引数
 ・contour
: 輪郭
 ・epsilon : 実輪郭と近似輪郭の最大距離
 ・ closed : 輪郭が閉じているかどうか
・戻り値
 ・approx : 輪郭の近似

例の輪郭を近似すると、次のようになります。

# 輪郭の近似
contours = list(map(lambda x: cv2.approxPolyDP(x, 3, True), contours))

画像3

頂点数は、contour.shapeで調べることができます。今回は、204から22に減っていることがわかります。

6. 輪郭の外接矩形

輪郭の外接矩形を計算するには、cv2.boundingRect()を使います。

x, y, w, h = cv2.boundingRect(contour)
・引数
 ・contour
: 輪郭
・戻り値
 ・x, y, w, h : 外接矩形のX座標、Y座標、幅、高さ

例の輪郭の外接矩形を計算すると、次のようになります。

# 輪郭の外接矩形の描画
x, y, w, h = cv2.boundingRect(contours[0])
img = cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), 2)

画像4

7. 輪郭の回転を考慮した外接矩形

輪郭の回転を考慮した外接矩形を計算するには、cv2.minAreaRect()を使います。

box = cv2.minAreaRect(contour)
・引数
 ・contour
: 輪郭
・戻り値
 ・box : ((X座標, Y座標),(幅, 高さ),回転角)

描画には4隅の頂点が必要なので、cv2.boxPoints()で変換します。

points = cv2.boxPoints(box)
・引数
 ・box
: ((左上X座標, 左上Y座標),(幅, 高さ),回転角)
・戻り値
 ・points : 4隅の頂点

例の輪郭の回転を考慮した外接矩形を計算すると、次のようになります。回転がわかりやすいように以下の画像に入れ替えました。

画像5

# 輪郭の回転を考慮した外接矩形の描画
rect = cv2.minAreaRect(contours[0])
box = cv2.boxPoints(rect)
box = np.int0(box)
img = cv2.drawContours(img, [box], 0, (0, 0, 255), 2)

画像6

8. 輪郭の最小外接円

輪郭の最小外接円を計算するには、cv2.minEnclosingCircle()を使います。

center, radius = cv2.minAreaRect(contour)
・引数
 ・contour
: 輪郭
・戻り値
 ・center : 中心点(x, y)
 ・radius : 半径

例の輪郭の最小外接円を計算すると、次のようになります。

# 輪郭の最小外接円の描画
(x, y), radius = cv2.minEnclosingCircle(contours[0])
center = (int(x), int(y))
radius = int(radius)
img = cv2.circle(img, center, radius, (0, 0, 255), 2)

画像7

9. 輪郭の楕円のフィッティング

輪郭の楕円のフィッティングを計算するには、cv2.fitEllipse()を使います。

ellipse = cv2.fitEllipse(contour)
・引数
 ・contour
: 輪郭
・戻り値
 ・ellipse : 楕円

例の輪郭の楕円のフィッティングを計算すると、次のようになります。

# 輪郭の楕円のフィッティングの描画
ellipse = cv2.fitEllipse(contours[0])
img = cv2.ellipse(img, ellipse, (0, 0, 255), 2)

画像8

10. 輪郭の直線のフィッティング

輪郭の直線のフィッティングを計算するには、cv2.fitLine()を使います。

line = cv2.fitLine(contour, distType, param, reps, aeps)
・引数
 ・contour
: 輪郭
 ・distType : M-estimatorによって使用される距離
 ・param : 距離の数値パラメータ(C)
 ・reps : 半径の精度
 ・aeps : 角度の精度
・戻り値
 ・line : 直線

例の輪郭の直線のフィッティングを計算すると、次のようになります。

# 輪郭の直線のフィッティングの描画
rows, cols = img.shape[:2]
[vx, vy, x, y] = cv2.fitLine(contours[0], 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)

画像9

11. 点が輪郭内かどうかの確認

点が輪郭内かどうかを確認するには、pointPolygonTest()を使います。

result = cv2.pointPolygonTest(contour, point, measureDist)
・引数
 ・contour
: 輪郭
 ・point : 点の座標 (x, y)
 ・measureDist : measureDist=Falseの時、点が輪郭内の場合は +1、輪郭上の場合は0、輪郭外の場合は-1を返す。measureDist=Trueの時、点と輪郭との距離を返す。
・戻り値
 ・result : 結果 (measureDist参照)

点が輪郭内に含まれる場合は黄、含まれない場合は黒で表示するコードは、次のようになります。

# 点が輪郭内かどうかの確認
point = (180, 180) #point  = (180, 100)
if cv2.pointPolygonTest(contours[0], point, False) >= 0:
    cv2.circle(img, point, 5, (0, 255, 255), -1)
else:
    cv2.circle(img, point, 5, (0, 0, 0), -1)

画像10

12. 輪郭によるマスク生成

輪郭によるマスク生成を行うには、黒画像を用意し、輪郭をthickness=-1drawContours()で白を描画します。

例の輪郭でマスク生成すると、次のようになります。

# 輪郭によるマスク生成
contour_mask = np.zeros_like(img_gray)
cv2.drawContours(contour_mask, contours, -1, color=255, thickness=-1)

画像11

13. 白黒割合の確認

白黒割合を確認するには、countNonZero()を使います。

例の輪郭でマスクを生成した後、白黒割合を確認すると、次のようになります。

# 白黒割合の確認
image_size = contour_mask.size
white_pixels = cv2.countNonZero(contour_mask)
black_pixels = contour_mask.size-white_pixels
print('white :', int((white_pixels/image_size)*100), '%')
print('black :', int((black_pixels/image_size)*100), '%')
white : 13 %
black : 86 %

14. 参考


この記事が気に入ったらサポートをしてみませんか?