勉強しないとな~blog

ちゃんと勉強せねば…な電気設計エンジニアです。

OpenCVやってみる-12. ハフ変換(円検出)

どんどん続きを進めます。
次はハフ変換による円検出。

ハフ変換による円検出 — OpenCV-Python Tutorials 1 documentation

ハフ変換による円検出

円上の点(x,y)は、以下の式を満たします。

(x-x_{c})^{2} +(y-y_{c})^{2} = r^{2}

直線検出のときと同様、この式のパラメータ(x_{c}, y_{c}, r)の空間に、 その円上に乗った点の数をマッピングする感じかと。

直線検出と比べてパラメータが1つ多いため、単純に全パラメータ空間を 走査するのは非効率ということで、何がしかの手法を使っているようです。

前回と同じ画像を対象とします。
シールの領域を取得できるといいなと。

import cv2
img1 = cv2.imread('harupan_200317_1.jpg')
img2 = cv2.imread('harupan_210402_1.jpg')
img3 = cv2.imread('harupan_210402_2.jpg')
img1 = cv2.resize(img1, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_AREA)
img2 = cv2.resize(img2, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_AREA)
img3 = cv2.resize(img3, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_AREA)

import numpy as np
img123 = np.hstack((img1,img2,img3))
cv2.imshow('harupan', img123)

f:id:nokixa:20210706090007p:plain


OpenCVでのハフ変換(円検出)関数

処理の内容はさておき、ハフ変換(円検出)の関数の使い方を確認します。

特徴検出 — opencv 2.2 documentation

【OpenCV】cv2.HoughCircles()の使い方【円を検出する】 | 資格マフィア

ハフ変換(円検出)の中でCannyエッジ検出が行われているようです。
前回は自分でグレースケール化、エッジ検出をやりましたが、今回はグレースケール化するだけになります。

引数が色々ありますが、以下のように設定します。

  • dp : 投票空間(パラメータ空間?)の比率の逆数とのこと。参考サイトでは0.8~1.2がよさそうと書いてありますが、1と1.5ぐらいを試してみるかな。
  • minDist : 円の中心同士の最小距離。今回は、シールが横に5個並んでいて、台紙が画像(横約300pixel)の横半分ぐらいに写っているものもあるので、シール間は約30pixel、値は20ぐらいにしておくかな。
  • param1 : Cannyエッジ検出の閾値の大きいほうということで、前回使った 200 を与えます。
  • param2 : 円検出の投票数閾値とのこと。シールが一番小さく写っている画像で直径30pixelぐらいなので、円周100pixelぐらい、閾値は50ぐらいにしておきます。 → やってみると、円が全く検出されなかったので、結局25にしました。
  • minRadius : 最小半径、10にします。
  • maxRadius : 最大半径、30にします。
img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
img3_gray = cv2.cvtColor(img3, cv2.COLOR_BGR2GRAY)

circles1 = cv2.HoughCircles(img1_gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=200, param2=25, minRadius=10, maxRadius=30)
circles2 = cv2.HoughCircles(img2_gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=200, param2=25, minRadius=10, maxRadius=30)
circles3 = cv2.HoughCircles(img3_gray, cv2.HOUGH_GRADIENT, dp=1, minDist=20, param1=200, param2=25, minRadius=10, maxRadius=30)

計算結果を確認すると、

>>> circles1.shape
(1, 40, 3)
>>> circles2.shape
(1, 33, 3)
>>> circles3.shape
(1, 23, 3)
>>> circles1[0,0,:]
array([221.5, 329.5,  27.2], dtype=float32)

まあ妥当か。
結果の次元の先頭に謎の次元が付いています。
描画します。

circles1 = np.uint16(np.around(circles1))
circles2 = np.uint16(np.around(circles2))
circles3 = np.uint16(np.around(circles3))
imgs = [img1.copy(), img2.copy(), img3.copy()]
circles = [circles1, circles2, circles3]

for i in range(3):
    for c in circles[i][0]:
        imgs[i] = cv2.circle(imgs[i], (c[0],c[1]),c[2],(0,255,0),2)

imgs123 = np.hstack((imgs[0], imgs[1], imgs[2]))
cv2.imshow('Images', imgs123)

f:id:nokixa:20210706222823p:plain

まあまあといったところでしょうか。

  • 一番左の画像では全シールが検出されています。形がきちんとした丸ではないにも関わらず。
    ただし中心、半径は少しずれていたりします。
  • 他の画像では、検出できていないシールがあります。
  • 全画像で、台紙下側で円を誤検出しています。細かい模様があって、エッジ検出したときにパターンが発生してしまうからか。

パラメータを変えてやってみます。

  • dp : 少し増やして1.5
  • param2 : パラメータ空間が小さくなるということは、1つのパラメータ組み合わせに対する投票数が増えるのでは、ということで50
circles1 = cv2.HoughCircles(img1_gray, cv2.HOUGH_GRADIENT, dp=1.5, minDist=20, param1=200, param2=50, minRadius=10, maxRadius=30)
circles2 = cv2.HoughCircles(img2_gray, cv2.HOUGH_GRADIENT, dp=1.5, minDist=20, param1=200, param2=50, minRadius=10, maxRadius=30)
circles3 = cv2.HoughCircles(img3_gray, cv2.HOUGH_GRADIENT, dp=1.5, minDist=20, param1=200, param2=50, minRadius=10, maxRadius=30)

circles1 = np.uint16(np.around(circles1))
circles2 = np.uint16(np.around(circles2))
circles3 = np.uint16(np.around(circles3))
imgs = [img1.copy(), img2.copy(), img3.copy()]
circles = [circles1, circles2, circles3]

for i in range(3):
    for c in circles[i][0]:
        imgs[i] = cv2.circle(imgs[i], (c[0],c[1]),c[2],(0,255,0),2)

imgs123 = np.hstack((imgs[0], imgs[1], imgs[2]))
cv2.imshow('Images', imgs123)

f:id:nokixa:20210706225809p:plain

だいたい同じような、ただ一番右の画像の結果は悪くなっています。
元の画像シール領域のサイズが小さいので、粗いパラメータ刻みではうまくいかなかった、ということかと。

以上

シール点数集計には使いにくそうなので、このあたりにしておきます。

まだまだチュートリアルは続きます。