OpenCV 楕円抽出

画像から直線とか円をハフ変換で検出できたから、もしかしたら楕円もあるか?
と思ったが、さすがにそれは無かった。当たり前だろう膨大な計算量だ。
ただ輪郭から楕円を求める関数が画像処理ライブラリOpenCVに存在したので
それを試してみたいと思います。
楕円を描く式はあるから、回帰計算で求められるけれど、いやーあるものは
使わねば。それをやっている手間で別のことが出来るし。
作って頂いた方、ありがとうございます。


楕円のフィッティング

関数cv2.fitEllipse()使って輪郭から楕円フィッティングを行います。

関数
retval = cv.fitEllipse(points)
  • points - 2Dポイントセットを入力します
  • retval - 返却値 ellipse / rotatedRectデータ
    返却値データは下記のタプル形式をとります。

    ((cx, cy), (h, w), deg)

    • cx - 中心X
    • cy - 中心Y
    • h - 楕円縦方向の長さ
    • w - 楕円横方向の長さ
    • deg - 傾き角度

    この関数を実行する前処理として輪郭を求める必要があります。
    ちなみに外接矩形の角4点を求める場合、cv2.boxPoints()を使います。


2Dポイントのセットの周りに楕円をフィットします。
この関数は、(最小二乗の意味で)2Dポイントのセットに
最もよく適合する楕円を計算します。
楕円が内接する回転した長方形を返します。
返却値 ellipse / rotatedRectデータに負のインデックスが
含まれている可能性があります。

アルゴリズム
Andrew W Fitzgibbon and Robert B Fisher. A buyer's guide to conic fitting.
In Proceedings of the 6th British conference on Machine vision (Vol. 2),
pages 513–522. BMVA Press, 1995.


参考サンプルコード
contours,hierarchy =  cv2.findContours(binimg,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for i, cnt in enumerate(contours):
ellipse = cv2.fitEllipse(cnt)
resimg = cv2.ellipse(resimg,ellipse,(255,0,0),2)

#外接矩形4点を求める
box = cv2.boxPoints(ellipse)
box = np.int0(box)
im = cv2.drawContours(im,[box],0,(0,0,255),2)


C/C++の場合のfitEllipse()返却値について

C/C++コードではリストではなくRotatedRectクラスインスタンスが返却されます。
ただ、Python版OpenCVにはRotatedRectがありません。
C/C++コードで外接矩形4点を求める場合、RotatedRect::points()メソッドを使います。



入力画像


20200217_ellipse2.png

今回のテスト用画像はこの画像を使います。それぞれの図を楕円フィッテングしてみて
どうなるか?試します。



二値化について

入力画像の左上三角形がシアンで、通常のグレイスケール化しても
うまく三角形が二値化できなかった。
あんまりこんなことは書かないのですが、まあ必要な方もいると思いますので
書いておきます。

  • 一般的なグレイスケール二値化
    三角形が二値化できなかった例をしめします。

    一般的なグレイスケール化サンプル
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    _, binimg = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)


    二値化結果
    20200217_binimg2.png

    手動でしきい値を設定するのも有なんですけれど、それもなんかやだな。


  • カスタムなグレイスケール二値化
    RGB3プレーンをすべてAND合成してグレイスケールを作ります。

    カスタムなグレイスケール
    gray1 = cv2.bitwise_and(img[:,:,0], img[:,:,1])
    gray1 = cv2.bitwise_and(gray1, img[:,:,2])
    _, binimg = cv2.threshold(gray1 , 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)


    二値化結果
    20200217_binimg.png

    出来ていますね。



Pythonプログラム

import cv2
import math
import numpy as np

def main():
img = cv2.imread('ellipse2.png', cv2.IMREAD_COLOR)

# グレイスケール化
gray1 = cv2.bitwise_and(img[:,:,0], img[:,:,1])
gray1 = cv2.bitwise_and(gray1, img[:,:,2])

# 二値化
_, binimg = cv2.threshold(gray1, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
binimg = cv2.bitwise_not(binimg)

# 結果画像の黒の部分を灰色にする。
bimg = binimg // 4 + 255 * 3 //4
resimg = cv2.merge((bimg,bimg,bimg))

# 輪郭取得
contours,hierarchy = cv2.findContours(binimg,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for i, cnt in enumerate(contours):
# 楕円フィッティング
ellipse = cv2.fitEllipse(cnt)
print(ellipse)

cx = int(ellipse[0][0])
cy = int(ellipse[0][1])

# 楕円描画
resimg = cv2.ellipse(resimg,ellipse,(255,0,0),2)
cv2.drawMarker(resimg, (cx,cy), (0,0,255), markerType=cv2.MARKER_CROSS, markerSize=10, thickness=1)
cv2.putText(resimg, str(i+1), (cx+3,cy+3), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,80,255), 1,cv2.LINE_AA)

cv2.imshow('resimg',resimg)
cv2.waitKey()

if __name__ == '__main__':
main()

中心位置と番号も表示するように作りました。
ただ、三角形はうまく動作しませんでしたので、三角形を
少し変形させました。


動作環境

Windows10 Anaconda
Python 3.8.1
OpenCV 4.0.1
numpy 1.18.1


結果画像

20200217_resimg.png

二値画像で黒の部分は見にくいので、グレイにしています。
三角形がうまくいかない問題について、何らかの入力値の問題だと思うのだけれど、
そこまで調べきれませんでした。
まあ、それ以外はうまく動作し、楕円フィッティングしています。
最小二乗法で計算しているのでしょうね。実際に計算するのは大変。
楕円の縦と横の長さが同じか近ければ、円として判断しても良いのかなと
思っています。



修正・加筆

  • 2020/3/13
    OpenCV4でfindContours()の返却値が変わりましたので修正しました。

    contours, hierarchy = cv.findContours(...)

    ソースコードも修正しておきます。
    また、動作環境も更新しました。

  • 2020/10/27
    cv2.fitEllipse()の返却値とC++の場合について、少し加筆しました。

  • 2021/3/14
    cv2.fitEllipse()の関数の説明を加筆。

  • 2021/3/23
    cv2.fitEllipse()の関数の説明をさらに加筆。

  • 2021/05/02 修正
    IntersectionObserver's による画像のオフスクリーン遅延読み込み処理に変更。
    IE11未対応。




関連



参考


#画像処理
#近似

関連記事
スポンサーサイト



コメント

非公開コメント