領域(輪郭)の特徴

目的

このチュートリアルでは

  • 領域(輪郭)の特徴である面積,周囲長,重心,外接矩形などについて学びます.
  • 領域(輪郭)を対象とした様々な関数について学ぶことになります.

1. モーメント

画像のモーメントは物体の重心,面積などを計算するのに役立ちます.詳細についてはWikipediaの 画像モーメント(英語) のページを参照してください.

cv2.moments() 関数はあらゆるモーメントを計算します.:

import cv2
import numpy as np

img = cv2.imread('star.jpg',0)
ret,thresh = cv2.threshold(img,127,255,0)
imgEdge,contours,hierarchy = cv2.findContours(thresh, 1, 2)

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

ここで計算したモーメントの中から面積や重心など自分にとって有用な情報を抽出します.重心は C_x = \frac{M_{10}}{M_{00}}C_y = \frac{M_{01}}{M_{00}} の関係から求められます :

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

2. 面積(Contour area)

領域が占める面積を計算するには cv2.contourArea() 関数を使うか,モーメント M[‘m00’] を使います.

area = cv2.contourArea(cnt)

3. 周囲長(arc length)

領域を囲む周囲長(もしくは長さ)は cv2.arcLength() 関数を使って計算できます.第2引数は対象とする領域(輪郭)が閉じている(True を指定)か単なる曲線かを表すフラグです.

perimeter = cv2.arcLength(cnt,True)

4. 輪郭の近似

複雑な形状をした輪郭を,より少ない数の点で表現できる単純な形状によって近似する事が出来ます.近似する点の数はユーザが指定できます.この関数は Douglas-Peucker algorithm を実装したものです.アルゴリズムの詳細についてはWikipediaのページを参照してください.

輪郭の近似について理解するために例を使って説明します,今あなたは画像中から正方形を見つけようとしていますが,何かしらの問題が生じたため見つけたい長方形が “崩れた形” をしています(下の最初の画像のように).この cv2.approxPolyDP 関数を使うと検出した形状の近似が出来ます.第2引数は epsilon と呼ばれ,実際の輪郭と近似輪郭の最大距離を表し,近似の精度を表すパラメータです. epsilon を妥当な値に設定する事で,期待に沿った結果が得られます.

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

以下の画像の内,二番目の画像は epsilon = 弧長の10% と設定した結果,三番目の画像は epsilon = 弧長の1% と設定した結果です.第3引数は近似曲線を閉じた曲線にするか否かを指定します.

Contour Approximation

5. 凸包(Convex Hull)

凸包(Convex Hull)は輪郭の近似に似ていますが,厳密には異なる処理です.

cv2.convexHull() 関数は曲線の凸性の欠陥を調べ修正します.一般的に言うと,凸性を持つ曲線とは常に突き出るか少なくとも平坦な曲線を指します.内側にへこんでいる部分は凸性の欠陥(convexity defects)と呼ばれます.以下の手の画像を例として扱います.赤い線は手の凸包,両面矢印マークは輪郭から外郭の極大値となる凸性の欠陥を指している.

Convex Hull

関数の使い方については少しだけ議論する必要があります:

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

入力引数の詳細:

  • points :Convex Hullを計算する輪郭です.
  • hull : 出力ですが,普通は入力を避けます(左辺値で出力できるから?).
  • clockwise : 傾きを表すフラグです. True を指定すると出力のconvex hullは時計回り,そうでかねれば反時計回りの形式で出力されます.
  • returnPoints : convex hullの出力する情報を決めるフラグです. True を指定するとconvex hull上の点の座標, False を指定するとconvex hull上の点に対応する輪郭上の点のインデックスを返します.

まとめると,上記の画像のconvex hullを取得するコードは以下のようになります:

hull = cv2.convexHull(cnt)

もし凸性の欠陥を検出したい場合は returnPoints = False を指定する必要があります.その理由を理解してもらうために,上記の長方形画像を例に使います.まず初めに長方形の輪郭を cnt として検出します.この輪郭に対して returnPoints = True を指定したconvex hullを使うと以下のような出力を得ます: [[[234 202]], [[ 51 202]], [[ 51 79]], [[234 79]]] これは長方形の4隅の点を指しています.次に,全く同じ輪郭に対して returnPoints = False を指定すると以下のような出力になります: [[129],[ 67],[ 0],[142]] .これは各convex hull上の点に対応する輪郭上の点のインデックスの値になっており,最初の点を確認してみると: cnt[129] = [[234, 202]] となり,フラグを True にした時に得た出力と一致していることが分かります.

凸性の欠陥について後で議論しますが,その時にまた見れるでしょう.

6. 凸性の確認

曲線の凸性を確認するための関数は cv2.isContourConvex() です.返戻値はTrue か Falseの二値の値です.

k = cv2.isContourConvex(cnt)

7. 外接矩形

外接矩形は2種類あります.

7.a. 外接矩形

単純な長方形で物体の回転を仮定していません.そのため外接矩形の面積は最小になる保障はありません. cv2.boundingRect() 関数を使います.

外接矩形の左上の位置を(x,y),横と縦のサイズを(w,h)とすると,以下のようになります.

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

7.b. 回転を考慮した外接矩形

もう一つの外接矩形は回転を考慮したものです. cv2.minAreaRect() を使います.返戻値は Box2D の構造(左上の点(x,y),横と縦のサイズ(width, height),回転角).しかし,この長方形を描画する時に必要な情報は長方形の4隅の点なので, cv2.boxPoints() 関数を使って計算します.

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

同一画像上に二つの外接矩形を描画します.緑の長方形が外接矩形,赤い長方形が回転を考慮した外接矩形にです.

Bounding Rectangle

8. 最小外接円

物体の最小外接円を計算する時は 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)
Minimum Enclosing Circle

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

物体に楕円をフィッティングする時は cv2.fitEllipse 関数を使います.出力は求めた楕円に外接する回転外接矩形になります.

ellipse = cv2.fitEllipse(cnt)
im = cv2.ellipse(im,ellipse,(0,255,0),2)
Fitting an Ellipse

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

楕円フィッティングと同様に,点の集合に対して直線のフィッティングもできます.以下の画像は白色の点の集合に対して直線をフィッティングした結果です.

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)
Fitting a Line

補足資料

課題