次の非常に簡単なpythonコードを書いて、画像内の円を見つけます。
import cv
import numpy as np
WAITKEY_DELAY_MS = 10
STOP_KEY = 'q'
cv.NamedWindow("image - press 'q' to quit", cv.CV_WINDOW_AUTOSIZE);
cv.NamedWindow("post-process", cv.CV_WINDOW_AUTOSIZE);
key_pressed = False
while key_pressed != STOP_KEY:
# grab image
orig = cv.LoadImage('circles3.jpg')
# create tmp images
grey_scale = cv.CreateImage(cv.GetSize(orig), 8, 1)
processed = cv.CreateImage(cv.GetSize(orig), 8, 1)
cv.Smooth(orig, orig, cv.CV_GAUSSIAN, 3, 3)
cv.CvtColor(orig, grey_scale, cv.CV_RGB2GRAY)
# do some processing on the grey scale image
cv.Erode(grey_scale, processed, None, 10)
cv.Dilate(processed, processed, None, 10)
cv.Canny(processed, processed, 5, 70, 3)
cv.Smooth(processed, processed, cv.CV_GAUSSIAN, 15, 15)
storage = cv.CreateMat(orig.width, 1, cv.CV_32FC3)
# these parameters need to be adjusted for every single image
HIGH = 50
LOW = 140
try:
# extract circles
cv.HoughCircles(processed, storage, cv.CV_HOUGH_GRADIENT, 2, 32.0, HIGH, LOW)
for i in range(0, len(np.asarray(storage))):
print "circle #%d" %i
Radius = int(np.asarray(storage)[i][0][2])
x = int(np.asarray(storage)[i][0][0])
y = int(np.asarray(storage)[i][0][1])
center = (x, y)
# green dot on center and red circle around
cv.Circle(orig, center, 1, cv.CV_RGB(0, 255, 0), -1, 8, 0)
cv.Circle(orig, center, Radius, cv.CV_RGB(255, 0, 0), 3, 8, 0)
cv.Circle(processed, center, 1, cv.CV_RGB(0, 255, 0), -1, 8, 0)
cv.Circle(processed, center, Radius, cv.CV_RGB(255, 0, 0), 3, 8, 0)
except:
print "nothing found"
pass
# show images
cv.ShowImage("image - press 'q' to quit", orig)
cv.ShowImage("post-process", processed)
cv_key = cv.WaitKey(WAITKEY_DELAY_MS)
key_pressed = chr(cv_key & 255)
次の2つの例からわかるように、「円の検出品質」は非常に大きく異なります。
ケース1:
CASE2:
Case1とCase2は基本的に同じ画像ですが、それでもアルゴリズムは異なる円を検出します。異なるサイズの円を含む画像をアルゴリズムに提示すると、円の検出が完全に失敗することさえあります。これは主に、新しい画像ごとに個別に調整する必要があるHIGH
およびLOW
パラメータによるものです。
したがって私の質問:このアルゴリズムをより堅牢にするさまざまな可能性は何ですか?異なる色と異なるサイズの異なる円が検出されるように、サイズと色が不変である必要があります。ハフ変換を使用することは、物事を行う最良の方法ではないでしょうか?より良いアプローチはありますか?
以下は、視覚研究者としての私の経験に基づいています。あなたの質問から、機能するコードだけではなく、可能なアルゴリズムと方法に興味があるようです。最初に、サンプル画像用の手っ取り早い汚いPythonスクリプトと、問題を解決できる可能性があることを証明するための結果をいくつか示します。堅牢な検出アルゴリズムに関する。
検出された円を含むいくつかのサンプル画像(あなた以外のすべての画像はflickr.comからダウンロードされ、CCライセンスされています)(パラメーターを変更/調整せずに、すべての画像の円を抽出するために正確に次のコードが使用されます):
そして、ここにコードがあります:
import cv2
import math
import numpy as np
d_red = cv2.cv.RGB(150, 55, 65)
l_red = cv2.cv.RGB(250, 200, 200)
orig = cv2.imread("c.jpg")
img = orig.copy()
img2 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
detector = cv2.FeatureDetector_create('MSER')
fs = detector.detect(img2)
fs.sort(key = lambda x: -x.size)
def supress(x):
for f in fs:
distx = f.pt[0] - x.pt[0]
disty = f.pt[1] - x.pt[1]
dist = math.sqrt(distx*distx + disty*disty)
if (f.size > x.size) and (dist<f.size/2):
return True
sfs = [x for x in fs if not supress(x)]
for f in sfs:
cv2.circle(img, (int(f.pt[0]), int(f.pt[1])), int(f.size/2), d_red, 2, cv2.CV_AA)
cv2.circle(img, (int(f.pt[0]), int(f.pt[1])), int(f.size/2), l_red, 1, cv2.CV_AA)
h, w = orig.shape[:2]
vis = np.zeros((h, w*2+5), np.uint8)
vis = cv2.cvtColor(vis, cv2.COLOR_GRAY2BGR)
vis[:h, :w] = orig
vis[:h, w+5:w*2+5] = img
cv2.imshow("image", vis)
cv2.imwrite("c_o.jpg", vis)
cv2.waitKey()
cv2.destroyAllWindows()
ご覧のとおり、これは [〜#〜] mser [〜#〜] blob Detectorに基づいています。コードは、グレースケールへの単純なマッピングを除き、画像を前処理しません。したがって、画像にこれらのかすかな黄色の塊がないことが予想されます。
簡単に言うと、2つのサンプル画像だけを提供し、それらの説明を付けないこと以外は、問題について知っていることを教えてはなりません。ここで、私が謙虚な意見で、問題を攻撃するための効率的な方法を尋ねる前に、問題についてより多くの情報を得ることが重要である理由を説明します。
主な質問に戻ります。この問題の最良の方法は何ですか?これを検索の問題として見てみましょう。議論を簡単にするために、特定のサイズ/半径の円を探していると仮定します。したがって、問題はセンターを見つけることに要約されます。すべてのピクセルは候補中心であるため、検索スペースにはすべてのピクセルが含まれます。
P = {p1, ..., pn}
P: search space
p1...pn: pixels
この検索問題を解決するには、他の2つの関数を定義する必要があります。
E(P) : enumerates the search space
V(p) : checks whether the item/pixel has the desirable properties, the items passing the check are added to the output list
アルゴリズムの複雑さは重要ではないと仮定すると、Eがすべてのピクセルを取得してVに渡す徹底的検索または総当たり検索を使用できます。リアルタイムアプリケーションでは、検索スペースを削減し、Vの計算効率を最適化することが重要です。
主な質問に近づいています。 Vを定義する方法、より正確に候補の特性を測定する方法、および候補を望ましいものと望ましくないものに分割する二分法の問題を解決する方法。最も一般的なアプローチは、プロパティの測定に基づいて単純な決定ルールを定義するために使用できるいくつかのプロパティを見つけることです。これはあなたが試行錯誤をしていることです。肯定的な例と否定的な例から学習することにより、分類器をプログラミングしています。これは、使用しているメソッドが何をしたいかわからないためです。二分法問題の方法で使用される(望ましい候補の)プロパティの変動が小さくなるように、決定ルールのパラメーターを調整/調整し、データを前処理する必要があります。機械学習アルゴリズムを使用して、特定のサンプルセットの最適なパラメーター値を見つけることができます。この問題に使用できる決定木から遺伝的プログラミングまで、学習アルゴリズムは多数あります。また、学習アルゴリズムを使用して、複数の円検出アルゴリズムの最適なパラメーター値を見つけ、どれがより正確かを確認することもできます。これは、サンプル画像を収集するために必要な学習アルゴリズムの主な負担となります。
しばしば見落とされがちな堅牢性を改善する他のアプローチは、簡単に入手できる余分な情報を利用することです。実質的に余分な手間をかけずに円の色を知っていれば、検出器の精度を大幅に改善できます。平面上の円の位置を知っていて、画像化された円を検出したい場合は、これら2つの位置セット間の変換が2Dホモグラフィによって記述されることを覚えておく必要があります。また、ホモグラフィは4つのポイントのみを使用して推定できます。次に、堅牢性を改善して、堅実な方法を使用できます。ドメイン固有の知識の価値はしばしば過小評価されています。このように見てください。最初のアプローチでは、限られた数のサンプルに基づいていくつかの決定ルールを近似しようとします。 2番目のアプローチでは、決定ルールを知っており、それらをアルゴリズムで効果的に利用する方法を見つける必要があります。
まとめると、ソリューションの精度/堅牢性を改善するための2つのアプローチがあります。
共有したこれらの2つの画像には、HTメソッドではなくblob検出器を使用します。背景の減算では、円の色は変化するが変化しない2つの画像のように、背景の色を推定することをお勧めします。そして、ほとんどの領域がむき出しになっています。
これは素晴らしいモデリングの問題です。次の推奨事項/アイデアがあります。
さらに詳細に:
1:他の回答で述べたように、グレースケールにストレートに変換すると、多くの情報が破棄されます-背景と同じ明るさの円は失われます。カラーチャネルを単独で、または別のカラースペースで検討することをお勧めします。ここには、2つの方法があります。前処理された各チャネルで単独でHoughCircles
を実行し、結果を結合するか、チャネルを処理してから結合し、HoughCircles
を操作します。以下の試みでは、RGBチャンネルに分割し、処理してから結合する2番目の方法を試しました。結合する際に画像が飽和しすぎることに注意してください。cv.And
を使用してこの問題を回避します(この段階では、私の円は常に白い背景の黒いリング/ディスクです)。
2:前処理は非常に注意が必要です。多くの場合、これをいじるのが最善です。 AdaptiveThreshold
を使用しました。これは、ローカル平均に基づいてピクセルのしきい値を設定することにより、画像のエッジを強調できる非常に強力な畳み込み手法です(哺乳類の視覚システムの初期経路でも同様のプロセスが発生します)。これは、ノイズを減らすためにも役立ちます。 dilate/erode
を1回のパスで使用しました。そして、私はあなたがそれらを持っている方法の他のパラメータを保持しました。 Canny
の前にHoughCircles
を使用すると、「塗りつぶされた円」を見つけるのに大いに役立つので、おそらくそれを維持するのが最善です。この前処理はかなり重く、やや誤検知につながる可能性がありますより多くの「ブロブサークル」ですが、私たちの場合、これはおそらく望ましいですか?
3:HoughCirclesのパラメーターparam2
(パラメーターLOW
)に注意したように、実際には から最適なソリューションを得るために画像ごとに調整する必要がありますdocs :
小さいほど、より多くの偽の円が検出される可能性があります。
問題は、画像ごとにスイートスポットが異なることです。ここでの最良のアプローチは、条件を設定し、この条件が満たされるまで異なるparam2
値を検索することだと思います。画像には重ならない円が表示され、param2
が低すぎると、重なる円の負荷が通常発生します。そこで、以下を検索することをお勧めします。
重複しない、含まれていない円の最大数
したがって、これが満たされるまで、param2
の異なる値でHoughCirclesを呼び出し続けます。以下の例では、param2
をしきい値の仮定に達するまでインクリメントするだけでこれを行います。バイナリ検索を実行してこれが満たされた場合を見つけると、はるかに高速になります(かなり簡単になります)が、opencvはparam2
の無害な値に対してエラーをスローすることが多いため、例外処理に注意する必要があります(少なくとも私のインストールでは)。一致させるのに非常に役立つ別の条件は、円の数です。
4:モデルに追加できる制約はありますか?モデルに伝えることができるものが多くなればなるほど、サークルを検出するためのタスクを簡単に行うことができます。たとえば、次のことを知っていますか?
5:画像内の一部のブロブは、おおまかに円としか呼べません! 2番目の画像の2つの「非円形の塊」を考えてみましょう、私のコードはそれらを見つけることができません(良い!)、しかし...私がそれらを「フォトショップ」してより円形にすると、私のコードはそれらを見つけることができます...円ではないものを検出したい場合は、Tim Lukins
などの別のアプローチの方が良いかもしれません。
問題
重い前処理AdaptiveThresholding
と `Canny 'を実行すると、画像内のフィーチャに多くの歪みが発生する可能性があり、誤った円の検出や誤った半径の報告につながる可能性があります。たとえば、処理後の大きな固体ディスクはリングに見える可能性があるため、HughesCirclesは内側のリングを見つけることができます。さらに、ドキュメントでも次のことに注意してください。
...通常、関数は円の中心を適切に検出しますが、正しい半径を見つけることができない場合があります。
より正確な半径検出が必要な場合は、次のアプローチを推奨します(実装されていません):
結果
終了時のコードは非常に多くの場合非常によく機能します。これらの例は、次のようにコードを使用して行われました。
最初の画像のすべての円を検出します:
キャニーフィルターが適用される前の前処理画像の外観(異なる色の円が非常に目立ちます):
2番目の画像で2つ(blob)を除くすべてを検出します。
変更された2番目の画像(blobは円になり、大きな楕円形はより円形になり、検出が改善されます)、すべて検出されました:
このカンディンスキーの絵の中心を検出するのに非常にうまくいきます(境界条件のために同心円を見つけることができません)。
コード:
import cv
import numpy as np
output = cv.LoadImage('case1.jpg')
orig = cv.LoadImage('case1.jpg')
# create tmp images
rrr=cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
ggg=cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
bbb=cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
processed = cv.CreateImage((orig.width,orig.height), cv.IPL_DEPTH_8U, 1)
storage = cv.CreateMat(orig.width, 1, cv.CV_32FC3)
def channel_processing(channel):
pass
cv.AdaptiveThreshold(channel, channel, 255, adaptive_method=cv.CV_ADAPTIVE_THRESH_MEAN_C, thresholdType=cv.CV_THRESH_BINARY, blockSize=55, param1=7)
#mop up the dirt
cv.Dilate(channel, channel, None, 1)
cv.Erode(channel, channel, None, 1)
def inter_centre_distance(x1,y1,x2,y2):
return ((x1-x2)**2 + (y1-y2)**2)**0.5
def colliding_circles(circles):
for index1, circle1 in enumerate(circles):
for circle2 in circles[index1+1:]:
x1, y1, Radius1 = circle1[0]
x2, y2, Radius2 = circle2[0]
#collision or containment:
if inter_centre_distance(x1,y1,x2,y2) < Radius1 + Radius2:
return True
def find_circles(processed, storage, LOW):
try:
cv.HoughCircles(processed, storage, cv.CV_HOUGH_GRADIENT, 2, 32.0, 30, LOW)#, 0, 100) great to add circle constraint sizes.
except:
LOW += 1
print 'try'
find_circles(processed, storage, LOW)
circles = np.asarray(storage)
print 'number of circles:', len(circles)
if colliding_circles(circles):
LOW += 1
storage = find_circles(processed, storage, LOW)
print 'c', LOW
return storage
def draw_circles(storage, output):
circles = np.asarray(storage)
print len(circles), 'circles found'
for circle in circles:
Radius, x, y = int(circle[0][2]), int(circle[0][0]), int(circle[0][1])
cv.Circle(output, (x, y), 1, cv.CV_RGB(0, 255, 0), -1, 8, 0)
cv.Circle(output, (x, y), Radius, cv.CV_RGB(255, 0, 0), 3, 8, 0)
#split image into RGB components
cv.Split(orig,rrr,ggg,bbb,None)
#process each component
channel_processing(rrr)
channel_processing(ggg)
channel_processing(bbb)
#combine images using logical 'And' to avoid saturation
cv.And(rrr, ggg, rrr)
cv.And(rrr, bbb, processed)
cv.ShowImage('before canny', processed)
# cv.SaveImage('case3_processed.jpg',processed)
#use canny, as HoughCircles seems to prefer ring like circles to filled ones.
cv.Canny(processed, processed, 5, 70, 3)
#smooth to reduce noise a bit more
cv.Smooth(processed, processed, cv.CV_GAUSSIAN, 7, 7)
cv.ShowImage('processed', processed)
#find circles, with parameter search
storage = find_circles(processed, storage, 100)
draw_circles(storage, output)
# show images
cv.ShowImage("original with circles", output)
cv.SaveImage('case1.jpg',output)
cv.WaitKey(0)
ああ、はい...円問題の古い色/サイズ不変式(別名ハフ変換はあまりにも具体的で堅牢ではありません)...
以前は、OpenCVの 構造および形状分析 関数にはるかに依存してきました。可能性のある「samples」フォルダから非常に良いアイデアを得ることができます-特にfitellipse.py
およびsquares.py
。
説明のために、これらの例のハイブリッドバージョンを、元のソースに基づいて提示します。検出された輪郭は緑色で、適合した楕円は赤色で表示されます。
まだ完全ではありません:
がんばろう!
import cv
import numpy as np
# grab image
orig = cv.LoadImage('circles3.jpg')
# create tmp images
grey_scale = cv.CreateImage(cv.GetSize(orig), 8, 1)
processed = cv.CreateImage(cv.GetSize(orig), 8, 1)
cv.Smooth(orig, orig, cv.CV_GAUSSIAN, 3, 3)
cv.CvtColor(orig, grey_scale, cv.CV_RGB2GRAY)
# do some processing on the grey scale image
cv.Erode(grey_scale, processed, None, 10)
cv.Dilate(processed, processed, None, 10)
cv.Canny(processed, processed, 5, 70, 3)
cv.Smooth(processed, processed, cv.CV_GAUSSIAN, 15, 15)
#storage = cv.CreateMat(orig.width, 1, cv.CV_32FC3)
storage = cv.CreateMemStorage(0)
contours = cv.FindContours(processed, storage, cv.CV_RETR_EXTERNAL)
# N.B. 'processed' image is modified by this!
#contours = cv.ApproxPoly (contours, storage, cv.CV_POLY_APPROX_DP, 3, 1)
# If you wanted to reduce the number of points...
cv.DrawContours (orig, contours, cv.RGB(0,255,0), cv.RGB(255,0,0), 2, 3, cv.CV_AA, (0, 0))
def contour_iterator(contour):
while contour:
yield contour
contour = contour.h_next()
for c in contour_iterator(contours):
# Number of points must be more than or equal to 6 for cv.FitEllipse2
if len(c) >= 6:
# Copy the contour into an array of (x,y)s
PointArray2D32f = cv.CreateMat(1, len(c), cv.CV_32FC2)
for (i, (x, y)) in enumerate(c):
PointArray2D32f[0, i] = (x, y)
# Fits ellipse to current contour.
(center, size, angle) = cv.FitEllipse2(PointArray2D32f)
# Convert ellipse data from float to integer representation.
center = (cv.Round(center[0]), cv.Round(center[1]))
size = (cv.Round(size[0] * 0.5), cv.Round(size[1] * 0.5))
# Draw ellipse
cv.Ellipse(orig, center, size, angle, 0, 360, cv.RGB(255,0,0), 2,cv.CV_AA, 0)
# show images
cv.ShowImage("image - press 'q' to quit", orig)
#cv.ShowImage("post-process", processed)
cv.WaitKey(-1)
編集:
これらすべての答えの主なテーマは、circular 。私自身の答えは、低レベルの前処理でも高レベルの幾何学的フィッティングでも、これを装っていません。多くの円は、描画方法や画像の非アフィン/投影変換、およびレンダリング/キャプチャ方法の他のプロパティ(色、ノイズ、照明、エッジの厚さ)-すべて1つの画像内に任意の数の候補円ができます。
より高度な技術があります。しかし、彼らはあなたに費用がかかります。個人的には、アダプティブしきい値を使用する@fraxelのアイデアが好きです。これは、高速で信頼性が高く、かなり堅牢です。次に、楕円軸の単純な比率テストを使用して、最終輪郭(たとえば、Huモーメントを使用)またはフィッティングをさらにテストできます。 if((min(size)/ max(size))> 0.7)。
これまでと同じように、Computer Visionには、プラグマティズム、原則、およびs音の間の緊張があります。私は、CVは簡単だと思う人に話すのが好きなので、そうではありません。実際、有名なのは AI complete 問題です。これ以外でよく期待できるのは、ほとんどの場合うまくいくものです。
コードを見ると、次のことに気付きました。
グレースケール変換。私はあなたがそれをしている理由を理解していますが、あなたがそこに情報を捨てていることを理解しています。 「後処理」画像に見られるように、黄色の円は背景と同じ強度であり、色が異なっています。
ノイズ除去後のエッジ検出(消去/膨張)。これは必要ないはずです。キャニーはこれを大事にするべきです。
キャニーエッジの検出。 「開いた」円には、内側と外側の2つのエッジがあります。それらはかなり近いため、Canny gaussフィルターはそれらを一緒に追加する場合があります。そうでない場合は、2つのエッジが互いに近くなります。つまりキャニーの前に、あなたは開いて塗りつぶされた円を持っています。その後、それぞれ0/2と1つのエッジがあります。ハフは再びキャニーを呼び出すため、最初のケースでは、2つのエッジが(初期幅に応じて)滑らかになります。これが、コアハフアルゴリズムが開いた円と塗りつぶされた円を同じように処理できる理由です。
したがって、最初の推奨事項は、グレースケールマッピングを変更することです。強度を使用せず、色相/彩度/値を使用します。また、差分アプローチを使用します-エッジを探しています。したがって、HSV
変換を計算し、コピーを平滑化してから、元のコピーと平滑化されたコピーの差を取ります。これにより、dH, dS, dV
値(各点の色相、彩度、値の局所変動)。すべてのエッジ(内側と外側)の近くにピークがある、1次元画像を取得するために四角と足します。
2番目の推奨事項はローカル正規化ですが、それが必要かどうかはわかりません。アイデアは、出たEdge信号の正確な値を特に気にする必要はないということです。とにかく実際にはバイナリである必要があります(Edgeかどうか)。したがって、ローカル平均(ローカルはEdgeサイズの大きさ)で割ることにより、各値を正規化できます。
ハフ変換は、「モデル」を使用して、ご存知のように(通常)エッジ検出画像の特定の特徴を見つけます。 HoughCircles
の場合、そのモデルは完全な円です。これは、おそらく、誤検出の数を増やすことなく、画像内のより不規則で楕円形の円を検出するパラメーターの組み合わせが存在しないことを意味します。一方、基本的な投票メカニズムにより、閉じていない完全な円または「へこみ」のある完全な円が一貫して表示される場合があります。そのため、期待される出力に応じてこのメソッドを使用する場合と使用しない場合があります。
そうは言っても、この機能を使用する際に役立つ可能性のあることがいくつかあります。
HoughCircles
は内部でCanny
を呼び出します。そのため、その呼び出しは省略できます。param1
_(HIGH
と呼びます)は通常、_200
_の値を中心に初期化されます。 Canny
:cv.Canny(processed, cannied, HIGH, HIGH/2)
への内部呼び出しのパラメーターとして使用されます。このように自分でCanny
を実行すると、HIGH
の設定がHough変換で処理されるイメージにどのように影響するかを確認するのに役立つ場合があります。param2
_(LOW
と呼ぶ)は通常、値_100
_を中心に初期化されます。これは、ハフ変換のアキュムレーターの投票しきい値です。高く設定すると、偽陰性が多くなり、偽陽性が少なくなります。あなたがいじり始めたいのはこれが最初だと思います。参照: http://docs.opencv.org/3.0-beta/modules/imgproc/doc/feature_detection.html#houghcircles
更新:塗りつぶされた円:ハフ変換で円の形状が見つかったら、境界色をサンプリングして比較することで塗りつぶされているかどうかをテストできます想定される円内の1つまたは複数のポイントへ。または、想定される円内の1つ以上のポイントを特定の背景色と比較できます。前の比較が成功した場合は円が塗りつぶされ、失敗した場合は代替比較の場合は塗りつぶされます。
画像を見てください。 **Active Contours**
を使用することをお勧めします
この方法で問題を解決できることを願っています。