【OpenCV】cv2.HoughCircles()の使い方【円を検出する】

【OpenCV】cv2.HoughCircles()の使い方【円を検出する】プログラミング
ゆうすけ
ゆうすけ

OpenCVで画像から円を検出したいです。

資格マフィア
資格マフィア

cv2.HoughCircles 関数を使えば円を検出することができる。
ただ、その前に前処理をしないときれいに円が検出されないぞ。

 

✔️ 本記事のテーマ

 OpenCVで円を検出する方法

 

✔️ 読者さんへの前置きメッセージ

本記事は「OpenCV の cv2.HoughCircles 関数」について書いています。

 

この記事を読むことで
「cv2.HoughCircles の使い方 や 円検出に必要な前処理」
について理解できます。

 

OpenCVを使うことで画像中の円を検出することができます。

 

OpenCVの画像処理は画像に対する前処理が重要となることが多いですが、
円の検出も同様に前処理がとても大切です。

 

この記事では、OpenCV を使って円を検出する方法
それに必要な前処理を合わせて解説します。

 

それでは、解説していきましょう。

 

OpenCV で円を検出するサンプルコード

OpenCV で円を検出するサンプルコード

OpenCV で円を検出するサンプルコードは以下のようになります。
この記事ではこのサンプルコードに沿って解説していきます。

 

import cv2
import numpy as np


img = cv2.imread("./sample.png")

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)

circles = np.uint16(np.around(circles))

for circle in circles[0, :]:
    # 円周を描画する
    cv2.circle(img, (circle[0], circle[1]), circle[2], (0, 165, 255), 5)
    # 中心点を描画する
    cv2.circle(img, (circle[0], circle[1]), 2, (0, 0, 255), 3)

cv2.imwrite("sample_after.png", img)

このサンプルコードによって

OpenCVの円検出処理前の画像

↑このような画像から

OpenCVの円検出処理後の画像

↑このような円(オレンジ線部分)を検出することができます。

 

では、順番に解説していきましょう。

 

OpenCV で円を検出する手順

OpenCV で直線を検出する手順

OpenCVで画像中から円を検出する手順は以下の通りです。

  1. 画像の読み込み
  2. 画像のグレースケール化
  3. 画像中から円を検出する
  4. 検出した情報をキャストして丸める
  5. 検出した円を書き込む
  6. 画像の書き出し

実際に、円を検出する処理手順3 だけなのですが、
前処理となる手順2 や後処理となる手順 5はとても大切です。

 

また、円を検出している手順3 でも引数などを調整する必要があります。

 

では、円を検出する方法を手順ごとに解説していきましょう。

 

画像の読み込み

まず、OpenCVで画像処理を行うために対象の画像を読み込みます

 

画像の読み込みにはOpenCV のcv2.imread関数を使います。

 

以下のように、対象の画像パスを引数で指定することで、画像の読み込みを行えます。

img = cv2.imread("./sample.png")

 

なお、OpenCV の cv2.imread関数については
OpenCVで画像を読み込む方法【Python】
の記事で詳しく解説しています。

 

画像のグレースケール化

次に、画像のグレースケール化を行います。

 

画像をグレースケールにすることで後続のcv2.HoughCircles 関数にインプットすることができます。

 

画像のグレースケール化には OpenCV のcv2.cvtColor関数を使います。

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

cv2.HoughCircles前処理のグレースケール画像

 

なお、OpenCV の cv2.cvtColor関数については
OpenCVでグレースケール画像を簡単に作成する【Python】
の記事で詳しく解説しています。

 

画像中から円を検出する

ここまでの前処理が完了したら、円を検出していきましょう。

 

円の検出には cv2.HoughCircles 関数を使用します。

circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)

 

cv2.HoughCircles 関数は戻り値として検出した円の情報Ndarray で返します。
この戻り値が少しややこしいのですが、以下のような形状になっています。

[ [ [円の中心点のx座標, 円の中心点のy座標, 円の半径], [円の中心点のx座標, 円の中心点のy座標, 円の半径], … ] ]

 

検出した円の数だけ要素がある配列が3重にネストされて返ってきます。
(なぜ3重にネストされているのかはわかりません…)

 

cv2.HoughCircles 関数は多くの引数を取ります。
そしてこの引数が正しく円を検出する上で重要になってきます。
それぞれ解説しましょう。

 

cv2.HoughCircles 関数の第1引数: image

第1引数: image画像データ です。

ここまでで前処理を実施した画像データを渡しましょう。
サンプルコードでは グレースケール画像 gray を渡しています。

 

cv2.HoughCircles 関数の第2引数: method

第2引数: methodハフ変換の手法です。
OpenCVの 独自コードで指定します。

この引数はOpenCVの内部的な話になる上に、
ドキュメントもないのでどのようなコードが使えるのか具体的にわかりません。

おまじない的に cv2.HOUGH_GRADIENT を渡しておくのが良いと思います。

cv2.HoughCircles 関数の第3引数: dp

第3引数: dp は投票器の解像度です。
float 型で指定します。

dp の値が大きいほど検出基準が緩くなり、値が小さいほど検出基準が厳しくなります。

ケースや画像にもよりますが、0.8 ~ 1.2 くらいの幅で調整するのが良いかと思います。

2以上を渡すと誤検出が多くなり、0だとerrorが発生します。

cv2.error: OpenCV(4.4.0) /private/var/folders/nz/vv4_9tw56nv9k3tkvyszvwg80000gn/T/pip-req-build-gi6lxw0x/opencv/modules/imgproc/src/hough.cpp:2257: error: (-211:One of the arguments' values is out of range) dp, min_dist and canny_threshold must be all positive numbers in function 'HoughCircles'

cv2.HoughCircles 関数の第4引数: minDist

第4引数: minDist検出される円同士が最低限離れていなければならない距離です。
float 型で指定します。

 

この引数で同じ円に対する重複検出を調整します。

cv2.HoughCircles 関数はハフ検出を行うので、同じ円に対して複数の検出結果を返してしまいます。
そこで、検出する円同士の距離を定義することで、重複検出をなくします。

 

試しに、minDist=10 とすると、
以下のように一つの円に対して重複検出されてしまいます。

circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1, minDist=10, param1=100, param2=60, minRadius=0, maxRadius=0)

cv2.HoughCircles関数のmiddist変更後画像

 

 

cv2.HoughCircles 関数の第5引数: param1

第5引数: param1Canny法のHysteresis処理の閾値 です。
float 型で指定します。

 

cv2.HoughCircles 関数では内部でCanny法によるエッジ検出を行っています。
Canny法によるエッジ検出では、上限値以下、下限値以上のものを正しいエッジとして判断します。

この上限値を param1 で設定しています。
また、下限値は param1 の1/2が自動的に設定されます。

 

Canny法によるエッジ検出の細かな調整をしたい場合は、この引数を変更すれば良いのですが、
よく分からないのであれば、100前後を固定で渡せば良いと思います。

 

個人的には、この引数で調整するくらいなら、
他の引数で調整した方がきれいな円を検出できると思います。

 

cv2.HoughCircles 関数の第6引数: param2

第6引数: param2円の中心を検出する際の閾値です。

 

この引数が円検出処理の重要な鍵となっています。

 

閾値を低い値にすると円の誤検出が多くなり、高い値にすると未検出が多くなります。

インプットする画像データに対して、
どの値が円の重複検出 / 未検出 が少なくなるかを試しながら調整していくのが良いです。

 

試しに、param2=30 とすると、
以下のように一つの円に対して重複検出されてしまいます。

circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=30, minRadius=0, maxRadius=0)

cv2.HoughCircles関数のparam2変更画像

 

 

cv2.HoughCircles 関数の第7引数: minRadius

第7引数: minRadius検出する円の半径の下限です。
Int 型で指定します。

minRadius で設定した値以下の半径の円を検出対象外とします。

 

試しに、minRadius=50 とすると、以下のように半径が小さな円が検出されなくなります。

circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=50, maxRadius=0)

cv2.HoughCircles関数のminRadius変更後画像

 

ちなみに、 minRadius はデフォルト引数なので
引数を渡さなくともcv2.HoughCircles 関数は動きます。

 

なお、デフォルト引数については
pythonのデフォルト引数についての解説
の記事で解説しています。

 

cv2.HoughCircles 関数の第8引数: maxRadius

第8引数: maxRadius は検出する円の半径の上限です。
Int 型で指定します。

maxRadius で設定した値以上の半径の円を検出対象外とします。

 

試しに、maxRadius = とすると、以下のように半径が大きな円が検出されなくなります。

circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=200)

cv2.HoughCircles関数のmaxdist変更後画像

 

ちなみに、 maxRadius もデフォルト引数なので
引数を渡さなくともcv2.HoughCircles 関数は動きます。

 

検出した情報をキャストして丸める

cv2.HoughCircles 関数の戻り値は np.floatという Numpy の独自クラス型になっています。

この検出した情報をもとに、画像中に円を描画するためにはcv2.circle関数を使用します。

 

ただ、cv2.circle関数のいくつかの引数は Int型である必要があるので、
このタイミングで np.float型から整数値に丸め、さらに16ビット情報にキャストします。

circles = np.uint16(np.around(circles))

 

この処理をしないと、後続の cv2.circle 関数を使う時点でerrorが発生します。

 

なお、Numpyライブラリ の詳しい情報については
Numpyの使い方と便利な関数」の記事で解説しています。

 

検出した円を書き込む

circles には「検出した円の数だけの座標配列」が格納されているので、
それらをfor文で回しながら、円を書き込んでいきます。

for circle in circles[0, :]:
    # 円周を描画する
    cv2.circle(img, (circle[0], circle[1]), circle[2], (0, 165, 255), 5)
    # 中心点を描画する
    cv2.circle(img, (circle[0], circle[1]), 2, (0, 0, 255), 3)

 

少し補足説明をすると、
circles は3重のネスト構造になっているので、
forループの対象を circles[0, :] とすることで余分なネストを外しています。

そして、このループ中の circle には
[ 中心点のx座標, 中心点のy座標, 円の半径 ] という情報が格納されています。

よって、それらを各ループの中で circle[0], circle[1], circle[2] として取り出しています。

 

円の描画にはcv2.circle関数を使用しています。

OpenCV の cv2.circle関数 の詳細や使い方については
【OpenCV】cv2.circle関数の使い方【円を描画する】
の記事で詳しく解説しています。

 

画像の書き出し

最後に、検出した円を書き込んだ画像データを画像ファイルとして書き出します。

 

画像データの書き出しにはOpenCV のcv2.imwrite関数を使います。

 

第1引数に画像データ、第2引数に保存先のファイルパスを指定します。

cv2.imwrite("sample_after.png", line_img)

 

なお、OpenCV の のcv2.imwrite関数については
OpenCVで画像を保存する方法【Python】
の記事で詳しく解説しています。

 

画像処理プログラミングを独学で勉強するなら

画像処理プログラミングを独学で勉強するなら

画像から円を検出する方法について解説しました。

 

OpenCVは画像処理には欠かすことのできないライブラリです。

 

もし、OpenCVについて独学でスキルをつけるなら、以下の書籍がオススメです。

 

この書籍はOpenCVの基礎から応用までを
丁寧にかつ詳細に解説しています。

 

OpenCVのほぼ全てを網羅しているとも言えるほどの徹底ぶりなので、
関数のリファレンスとしても使用することができます。
本記事で解説したcv2.HoughCircles関数も掲載されています。

エンジニアとしての自身の価値をチェックする(完全無料)

エンジニアとして、

自分の価値がどれくらいのものかご存知でしょうか?

 

エンジニアとしてIT業界に身を置いていると

今の会社でずっと働くのか、フリーランスとして独立するのか …

と様々な選択肢があります。

 

どの選択肢が正解なのかを見極めるためにも、選択肢を広げるためにも

自身の価値を知っておくことはとても重要です。

 

TechClips ME では、

職務経歴書をアップロードするだけで企業からのスカウトを受けることができます。

▼▼▼▼▼

▲▲▲▲▲

しかもTechClips MEでは想定年収を企業から提示してくれるので、

自身の価値を数字で分かりやすくたしかめることができます。

 

登録はもちろん完全無料なので、一度登録してみると良いかもしれません。

 

コメント

タイトルとURLをコピーしました