領域(輪郭)の特徴

目的

このチュートリアルでは

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

1. モーメント

画像のモーメントは物体の重心,面積などを計算するのに役立つ.詳細についてはWikipediaの 画像モーメント のページを参照せよ.

cv2.moments(array[, binaryImage])) 関数は3次までのモーメントをすべて計算し、辞書形式の値を返す (コード, 注意: OpenCV2では返す値が違うためエラーになる):

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(contour[, oriented]) 関数を使うか,モーメントの結果をMとして M[‘m00’] で求める.

area = cv2.contourArea(cnt)

3. 周囲長(arc length)

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

perimeter = cv2.arcLength(cnt,True)

4. 輪郭の近似

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

輪郭の近似について理解するため、例を使って説明しよう,今、画像の中から矩形を見つけようとしているとする。ここで何らかの問題が生じたため、きちんとした形状の矩形が見つからず、 (下の最初の画像のように)“崩れた形” しかないとしよう.ここで cv2.approxPolyDP(curve, epsilon, closed[, approxCurve]) 関数を使ってこの形の近似を行ってみる。ここで第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(points[, hull[, clockwise[, returnPoints]]]) 関数は曲線の凸性の欠陥を調べ修正する.一般的に言うと,凸性を持つ曲線とは常に突き出るか少なくとも平坦な曲線を指す.内側にへこんでいる部分は凸性の欠陥(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. 直線のフィッティング

楕円フィッティングと同様に,点の集合に対して直線のあてはめも可能である.それには cv2.fitLine関数を用いる。次の画像は白色の点の集合に対して直線をフィッティングした結果である.

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
7から10までをまとめたコード対象図形

補足資料

課題