つれづれなる備忘録

日々の発見をあるがままに綴る

OpenCVの使い方26 ~ 輪郭抽出3

今回はOpenCVを用いて画像の輪郭を抽出する際の特徴量、輪郭の近似について紹介したい。

1. 輪郭の特徴量

画像はOpenCVのモノクロロゴをグレーで読み込み、contoursの11番目の輪郭:下向きCについて処理をする。

ロゴ画像読み込み、輪郭抽出は前々回を参照。

atatat.hatenablog.com

ret,thresh = cv2.threshold(gray,127,255,0)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnt_img = np.zeros_like(gray, dtype=np.uint8) 
cnt_img_tree = cv2.drawContours(cnt_img, contours, 11, 255, 1)
plt.imshow(cnt_img_tree,cmap='gray')

"輪郭11"
輪郭11

cntを輪郭11の座標群として、モーメントを算出するにはcv2.moments(cnt)とする。

cnt = contours[11]
M = cv2.moments(cnt)
M

算出されたMを表示するとDictionary形式で、さまざまなモーメントが出力される。

{'m00': 7005.5,
 'm10': 1293147.5,
 'm01': 625488.1666666666,
 'm20': 246294365.91666666,
 'm11': 115440859.95833333,
 'm02': 60851083.916666664,
 'm30': 48267626590.850006,
 'm21': 22035579640.083332,
 'm12': 11228558672.616667,
 'm03': 6346029109.25,
 'mu20': 7591852.64049077,
 'mu11': -18202.06501737237,
 'mu02': 5004185.531108499,
 'mu30': 1305261.314529419,
 'mu21': 51833228.29247081,
 'mu12': -712141.2543185949,
 'mu03': 19321438.742378235,
 'nu20': 0.1546925843274873,
 'nu11': -0.00037088766220471075,
 'nu02': 0.1019659401886588,
 'nu30': 0.0003177602898898274,
 'nu21': 0.012618577954314989,
 'nu12': -0.00017336774552026435,
 'nu03': 0.004703721705013629}

ここで、重心座標を求めたければMを用いて以下のようにする。

cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
print("mom center (",cx,",", cy, ")")

>mom center ( 184 , 89 )

面積はcv2.contourArea(cnt), 輪郭の周長はcv2.arcLength(cnt,True)とすると求まる。

area = cv2.contourArea(cnt)
perimeter = cv2.arcLength(cnt,True)
print("mom center (",cx,",", cy, ")")
print("area:",area, "perimeter:", perimeter)

>area: 7005.5 perimeter: 509.77164113521576

2. 輪郭の近似

 複雑な輪郭を少ない座標点数で近似することができる。輪郭の近似としてcv2.approxPolyDPは指定された長さを最大値として輪郭を近似した場合の近似点を出力する。例えば、輪郭周長の10%を指定して近似を行うと、

epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
cnt_img = np.zeros_like(gray, dtype=np.uint8) 
cnt_img_list = cv2.drawContours(cnt_img, approx, -1, 255, 5)
plt.imshow(cnt_img_list+cnt_img_tree,cmap='gray')

輪郭の近似
輪郭の近似

元の輪郭を細い線で表してみると、近似点と重なっていることがわかる。なお、指定の長さを短くしていくと元の輪郭に近づく。例えば周長の0.1%(1/1000)で指定すると近似点が増える。近似点が単純に増えるだけでなく。直線のような単純な部分は2点で近似し、曲線の部分に近似点が密集していることがわかる。

輪郭の近似2
輪郭の近似2

3. バウンダリーボックス

単純なバウンダリーボックスはcv2.boundingRectを用いることで、開始座標(左上)x,y, ボックスの幅/高さw,hを出力する。またボックスの幅/高さから輪郭のアスペクト比を求めることもできる。

x,y,w,h = cv2.boundingRect(cnt)
cnt_img = np.zeros_like(gray, dtype=np.uint8) 
cnt_img = cv2.rectangle(cnt_img,(x,y),(x+w,y+h),255,2)
plt.imshow(cnt_img+cnt_img_tree,cmap='gray')
aspect_ratio = float(w)/h
print('aspect ratio:',aspect_ratio)

>aspect ratio: 1.0754716981132075

バウンダリーボックス
バウンダリーボックス

単純なバウンダリーボックスは座標に直交した形で近似するため、輪郭に回転があるような場合は、輪郭に対して大きなバウンダリーボックスが設定されてしまう。回転に対応したバウンダリーボックスはcv2.minAreaRectを用いる。開始座標、幅/高さ、に加えて回転角が出力される。 実際に輪郭11は回転していないので、結果的に単純なバウンダリーボックスと同じになるがcv2.minAreaRect(cnt)の出力は((184.5, 93.5), (105.0, 113.0), 90.0)となる。描画するためには、cv2.boxPointsでボックス4隅の座標点を求めてしまえばよい。

rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
cnt_img = np.zeros_like(gray, dtype=np.uint8) 
cnt_img = cv2.drawContours(cnt_img,[box],0,255,2)
plt.imshow(cnt_img+cnt_img_tree,cmap='gray')

回転バウンダリーボックス
回転バウンダリーボックス

4. まとめ

 今回は輪郭の特徴量としてモーメント(重心)、面積、周長、また輪郭の近似、バウンダリーボックスについて紹介した。