openCV
前回、openCVでテキスト(文字列)や図形(線、矢印、四角形、丸、マーカー)を表示する方法を紹介しました。
今回はopenCVを使って円を検出する方法を紹介します。
使う画像はこちらの画像(shapedetection.png)です。
丸、線、四角で適当に作りました。
それでは始めていきましょう。
円の検出とやりがちなミス
とりあえず円を検出してみましょう。
円を検出するには「cv2.HoughCircles(画像, cv2.HOUGH_GRADIENT, dp=検出基準, minDist=円が離れるべき最小距離, param1=Canny法の閾値, param2=円の中心を検出するための閾値, minRadius=半径の下限, maxRadius=半径の上限)」です。
とりあえずこれで試してみるとエラーとなります。
import cv2
img = cv2.imread("shapedetection.png")
circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)
print(circles)
実行結果
---------------------------------------------------------------------------
error Traceback (most recent call last)
Cell In[21], line 5
1 import cv2
3 img = cv2.imread("shapedetection.png")
----> 5 circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)
7 print(circles)
error: OpenCV(4.8.0) /Users/runner/work/opencv-python/opencv-python/opencv/modules/imgproc/
src/hough.cpp:2269: error: (-215:Assertion failed) !_image.empty() && _image.type() ==
CV_8UC1 && (_image.isMat() || _image.isUMat()) in function 'HoughCircles'
これは私だけかもしれませんが、openCVのエラーは分かりにくい。
上の例でも何を言っているのか分かりませんが、実は渡す画像が間違っています。
円を検出する「cv2.HoughCircles」では中でCanny法によるエッジ検出をしているため、渡す画像はグレースケールでなければなりません。
ということでグレースケールで渡すとエラーが出ず、円の検出が行えます。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)
print(circles)
実行結果
[[[104.5 225.5 62.9]
[481.5 361.5 37.7]]]
ここでは2つの円が検出されました。
データは「中心のX座標, 中心のY座標, 半径」として出力されます。
ちなみに結果は3次元配列になっていることに気をつけてください。
元の画像への表示の仕方
円の検出ができたら、元の画像でどの円が検出されたか表示したくなることでしょう。
ここでもエラーに遭遇したので、その例を紹介します。
得られた円の情報をfor文で一つずつ取得します。
この時、3次元配列となっており、最後の次元は特に意味を成してないので、最後の次元を外した形でfor文を作ります。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)
for circle in circles[0]:
print(circle)
実行結果
[104.5 225.5 62.9]
[481.5 361.5 37.7]
これで無事円の情報が一つずつ取得できたので、前回学んだ「cv2.circle」で元の画像に円を追加してみます。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)
img = cv2.imread("shapedetection.png")
for circle in circles[0]:
cv2.circle(img, center=(circle[0], circle[1]), radius=circle[2], color=(0, 255, 0), thickness=10, lineType=cv2.LINE_4, shift=0)
cv2.imwrite("circle7-1.jpg", img)
実行結果
---------------------------------------------------------------------------
error Traceback (most recent call last)
Cell In[23], line 10
7 img = cv2.imread("shapedetection.png")
9 for circle in circles[0]:
---> 10 cv2.circle(img, center=(circle[0], circle[1]), radius=circle[2], color=(0, 255, 0), thickness=10, lineType=cv2.LINE_4, shift=0)
12 cv2.imwrite("circle7-1.jpg", img)
error: OpenCV(4.8.0) :-1: error: (-5:Bad argument) in function 'circle'
> Overload resolution failed:
> - Can't parse 'center'. Sequence item with index 0 has a wrong type
> - Can't parse 'center'. Sequence item with index 0 has a wrong type
するとこんな感じでエラーになります。
ここでもエラーがよく分からないのですが、実は「cv2.circle」は「float型」は受け付けてくれないので、「int型」に変換する必要があるのです。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)
img = cv2.imread("shapedetection.png")
for circle in circles[0]:
x = int(circle[0])
y = int(circle[1])
r = int(circle[2])
cv2.circle(img, center=(x, y), radius=r, color=(0, 0, 0), thickness=5, lineType=cv2.LINE_4, shift=0)
cv2.imwrite("circle7-1.jpg", img)
実行結果
黒丸のところが検出された円です。
緑色に被っているピンク色の円が検出されていません。
ちなみに真ん中上にあるのは楕円形なので、今回の「cv2.HoughCircles」では正しくは検出できないようです。
ここからはパラメーターをいじって、すべての円を検出できるようにしていく作業になります。
そのためパラメータを見ていきましょう。
dp:検出基準
dpは検出基準で大きいほど緩く、小さいほど厳しい検出になります。
例えばとりあえず「2」にしてみましょう。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=2, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)
img = cv2.imread("shapedetection.png")
for circle in circles[0]:
x = int(circle[0])
y = int(circle[1])
r = int(circle[2])
cv2.circle(img, center=(x, y), radius=r, color=(0, 0, 0), thickness=5, lineType=cv2.LINE_4, shift=0)
cv2.imwrite("circle7-2.jpg", img)
実行結果
こんな感じで円でないものまで円と検出してしまいました。
dpは小数でも良いため、次は「1.1」としてみました。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=1.1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=0)
img = cv2.imread("shapedetection.png")
for circle in circles[0]:
x = int(circle[0])
y = int(circle[1])
r = int(circle[2])
cv2.circle(img, center=(x, y), radius=r, color=(0, 0, 0), thickness=5, lineType=cv2.LINE_4, shift=0)
cv2.imwrite("circle7-3.jpg", img)
実行結果
先ほど検出されていなかった緑色の円に被ったピンク色の円も検出されるようになりました。
こんな感じで目的の円が検出されるように調整できます。
minDist:円が離れるべき最小距離
minDistは2つの円が離れるべき最小の距離です。
つまり値が小さければ2つの円が近くでも円として検出されますが、値が大きくなると距離的に近い円は円として検出されなくなります。
先ほどのdp=1.1で認識された3つの円の条件で、minDistを変えてみましょう。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=1.1, minDist=100, param1=100, param2=60, minRadius=0, maxRadius=0)
img = cv2.imread("shapedetection.png")
for circle in circles[0]:
x = int(circle[0])
y = int(circle[1])
r = int(circle[2])
cv2.circle(img, center=(x, y), radius=r, color=(0, 0, 0), thickness=5, lineType=cv2.LINE_4, shift=0)
cv2.imwrite("circle7-4.jpg", img)
実行結果
緑の円に距離的に近いピンクの円は検出されなくなりました。
param1:Canny法の閾値
Canny法の閾値に関してはこちらで解説していますので、ここでは割愛します。
param2:円の中心を検出するための閾値
param2は円の中心を検出するための閾値で、小さくなるほど誤検出が増え、大きくすると検出できない円が増えます。
ここではとりあえず「60」としていたものを「20」にしてみます。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=100, param2=20, minRadius=0, maxRadius=0)
img = cv2.imread("shapedetection.png")
for circle in circles[0]:
x = int(circle[0])
y = int(circle[1])
r = int(circle[2])
cv2.circle(img, center=(x, y), radius=r, color=(0, 0, 0), thickness=5, lineType=cv2.LINE_4, shift=0)
cv2.imwrite("circle7-5.jpg", img)
minRadius:検出する円の半径の下限
minRadiusは検出する円の半径の下限です。
つまり円の半径がこの値以下のものは円として認識されなくなります。
先ほどのdp=1.1で認識された3つの円の条件で、minRadiusを「0」から「50」に変えてみます。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=1.1, minDist=20, param1=100, param2=60, minRadius=50, maxRadius=0)
img = cv2.imread("shapedetection.png")
for circle in circles[0]:
x = int(circle[0])
y = int(circle[1])
r = int(circle[2])
cv2.circle(img, center=(x, y), radius=r, color=(0, 0, 0), thickness=5, lineType=cv2.LINE_4, shift=0)
cv2.imwrite("circle7-6.jpg", img)
実行結果
ピンクの円と青の円が検出されなくなりました。
maxRadius:検出される円の半径の上限
maxRadiusは検出される円の半径の上限です。
つまりこの値を超えると円として認識されなくなります。
先ほどのdp=1.1で認識された3つの円の条件で、maxRadiusを「0」から「50」に変えてみます。
import cv2
img_gray = cv2.imread("shapedetection.png", cv2.IMREAD_GRAYSCALE)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, dp=1.1, minDist=20, param1=100, param2=60, minRadius=0, maxRadius=50)
img = cv2.imread("shapedetection.png")
for circle in circles[0]:
x = int(circle[0])
y = int(circle[1])
r = int(circle[2])
cv2.circle(img, center=(x, y), radius=r, color=(0, 0, 0), thickness=5, lineType=cv2.LINE_4, shift=0)
cv2.imwrite("circle7-7.jpg", img)
実行結果
関数で簡単に円を検出できるかと思ったら、結構パラメータが多くて、そしてそのパラメータがシビアで驚きました。
兎にも角にも円は検出できるようになったので、次回は線の検出を試してみます。
ではでは今回はこんな感じで。
コメント