イメージ内の円形オブジェクトの検出と測定
この例では、イメージ内の円または円形オブジェクトを自動的に検出し、検出された円を可視化する方法を説明します。
手順 1: イメージの読み込み
さまざまな色の円形プラスチック チップのイメージを読み取って表示します。検出対象となるたくさんの円があるだけでなく、円を検出するという観点でこのイメージを捉えると、いくつかの興味深い点が見えてきます。
さまざまな色のチップがあり、これらは背景とのコントラストも異なっています。青と赤のチップはこの背景とのコントラストがはっきりとしています。一方、一部の黄色のチップは背景とのコントラストがあいまいです。
他のチップの上に重なっているチップもあれば、お互い触れそうなくらいに近いものもあります。オブジェクトの境界の重なりとオブジェクトのオクルージョンは、オブジェクト検出においては常に難題です。
rgb = imread("coloredChips.png");
imshow(rgb)
手順 2: 円を探索するための半径の範囲の判別
関数 drawline
を使用して、円の適切な半径の範囲を検索します。チップのおおよその直径の上に線を描画します。
d = drawline;
ライン ROI の長さはチップの直径です。通常のチップの直径は 40 ~ 50 ピクセルの範囲にあります。
pos = d.Position; diffPos = diff(pos); diameter = hypot(diffPos(1),diffPos(2))
diameter = 45
手順 3: 円を検出する最初の試行
関数 imfindcircles
により、ある半径の範囲を持つ円を検索します。20 ~ 25 ピクセルの範囲の半径を持つ円を検索します。その前に、背景と比較してオブジェクトが明るいか暗いかを確認しておきます。これを確認するために、このイメージのグレースケール バージョンを見てみましょう。
gray_image = im2gray(rgb); imshow(gray_image)
背景がかなり明るく、ほとんどのチップが背景より暗いことがわかります。しかし、既定では、imfindcircles
は背景よりも明るい円形オブジェクトを検出します。したがって、暗い円を検索するには、imfindcircles
で名前と値の引数 ObjectPolarity
を "dark"
に指定します。
[centers,radii] = imfindcircles(rgb,[20 25],ObjectPolarity="dark")
centers = [] radii = []
出力 centers
と radii
が空になっていますが、これは円が検出されなかったことを表しています。このような結果になることはよくあります。これは、imfindcircles
が円の "検出器" であり、多くの検出器と同じように、imfindcircles
には感度を決定する内部の "検出しきい値" があるためです。つまり、検出器の特定の (円の) 検出が "有効" であると見なされるには、その検出に対する信頼度が一定のレベルを超えていなければなりません。imfindcircles
には、この内部しきい値 (つまりアルゴリズムの感度) を制御するために使用できる、名前と値の引数 Sensitivity
があります。Sensitivity
の値が高いほど、検出しきい値が低く設定され、より多くの円が検出されます。これは、ホーム セキュリティ システムで使用されるモーション検出器での感度制御と似ています。
手順 4: 検出感度を上げる
チップのイメージに戻ります。既定の感度レベルではすべての円が内部しきい値より低く、そのために円が検出されなかった可能性があります。Sensitivity
は 0 ~ 1 の数値で、既定では 0.85 に設定されています。Sensitivity
を 0.9 に上げます。
[centers,radii] = imfindcircles(rgb,[20 25],ObjectPolarity="dark", ... Sensitivity=0.9)
centers = 8×2
146.1895 198.5824
328.8132 135.5883
130.3134 43.8039
175.2698 297.0583
312.2831 192.3709
327.1316 297.0077
243.9893 166.4538
271.5873 280.8920
radii = 8×1
23.1604
22.5710
22.9576
23.7356
22.9551
22.9995
22.9055
23.0298
今回は、imfindcircles
が円をいくつか検出しました。正確には、8 つです。centers
には円の中心の場所が格納されており、radii
にはこれらの円の推定半径が格納されています。
手順 5: イメージ上での円の描画
関数 viscircles
を使用すると、イメージ上に円を描画できます。imfindcircles
からの出力変数 centers
と radii
は、viscircles
に直接渡すことが可能です。
imshow(rgb) h = viscircles(centers,radii);
円の中心は正しく配置されており、対応する半径も実際のチップとうまく一致しているようです。しかし、まだ検出されていないチップがかなりあります。Sensitivity
をもう少し高い 0.92 に上げてみましょう。
[centers,radii] = imfindcircles(rgb,[20 25],ObjectPolarity="dark", ... Sensitivity=0.92); length(centers)
ans = 16
Sensitivity
を上げると、さらに多くの円が検出されます。これらの円をイメージ上にもう一度プロットします。
delete(h) % Delete previously drawn circles
h = viscircles(centers,radii);
手順 6: 円の検出に 2 つ目の方法 (2 段階) を使用
この方法の方が、よい結果になります。imfindcircles
には円を検出するための方法が 2 つあります。ここまでは、円を検出するために "位相符号化" メソッドという既定のメソッドを使用してきました。imfindcircles
には、一般に "2 段階" メソッドと呼ばれているもう 1 つのメソッドがあります。2 段階メソッドを使用して結果を表示します。
[centers,radii] = imfindcircles(rgb,[20 25],ObjectPolarity="dark", ... Sensitivity=0.92,Method="twostage"); delete(h) h = viscircles(centers,radii);
0.92 の Sensitivity
で、2 段階メソッドのほうが多くの円を検出しています。一般的に、これら 2 つのメソッドはそれぞれ異なる長所をもっており、補完しあっています。通常、位相符号化メソッドは 2 段階メソッドよりも速く、ノイズに対するロバスト性がわずかに優れています。しかし、2 段階メソッドと同じ数を検出するには、Sensitivity
レベルを高くしなければなりません。たとえば、位相符号化メソッドで Sensitivity
レベルを 0.95 に上げると、同じ数のチップが検出されます。
[centers,radii] = imfindcircles(rgb,[20 25],ObjectPolarity="dark", ... Sensitivity=0.95); delete(h) viscircles(centers,radii);
imfindcircles
の両方のメソッドが、一部だけ見えている (隠れている) チップの中心と半径を正確に検出することに注意してください。
手順 7: まだ検出されない円がある理由
最後の結果を見てみると、面白いことに、imfindcircles
はイメージ内の黄色のチップを検出していません。黄色のチップは、背景とのコントラストが強くありません。それどころか、背景とかなり近い強度をもっているようです。黄色のチップは、想定していたほど背景より暗くないということでしょうか。これを確認するために、このイメージのグレースケール バージョンをもう一度表示します。
imshow(gray_image)
手順 8: イメージ内の明るい円の検出
黄色のチップは、背景と比較すると、ほとんど同じ強度です。背景より明るいかもしれません。そこで、黄色のチップを検出するために、ObjectPolarity
を "bright"
に変更します。
[centersBright,radiiBright] = imfindcircles(rgb,[20 25], ... ObjectPolarity="bright",Sensitivity=0.92);
手順 9: 異なる色での明るい円の描画
viscircles
の名前と値の引数 Color
を変更して、異なる色で明るい円を描画します。
imshow(rgb)
hBright = viscircles(centersBright,radiiBright,Color="b");
検出されていなかった黄色のチップが 3 つ検出されましたが、黄色のチップがまだ 1 つ抜け落ちています。これらの黄色のチップは、この背景では他のチップのように際立っていないため、検出が困難です。
手順 10: EdgeThreshold
の値を下げる
imfindcircles
には EdgeThreshold
という別の名前と値の引数があり、ここで役に立ちます。円を検出するために、imfindcircles
はイメージ内のエッジ ピクセルのみを使用します。これらのエッジ ピクセルは、基本的には高い勾配値をもつピクセルです。名前と値の引数 EdgeThreshold
は、あるピクセルがエッジ ピクセルであると見なされ計算に組み込まれるには、そのピクセルでの勾配値がどの程度 "高く" なければならないかを制御します。このパラメーターに高い (1 に近い) 値を指定すると、強いエッジ (高い勾配値) のみが計算に組み込まれ、低い (0 に近い) 値を指定すると、許容範囲が広くなり、弱いエッジ (低い勾配値) も計算に組み込まれます。検出されない黄色のチップの場合は、コントラストが低いため、チップ外周の境界ピクセルの一部は低い勾配値をもつことが予想されます。したがって、EdgeThreshold
の値を下げて、黄色のチップのほとんどのエッジ ピクセルが確実に計算に組み込まれるようにします。
[centersBright,radiiBright,metricBright] = imfindcircles(rgb,[20 25], ... ObjectPolarity="bright",Sensitivity=0.92,EdgeThreshold=0.1); delete(hBright) hBright = viscircles(centersBright,radiiBright,Color="b");
手順 11: 暗い円と明るい円を同時に描画
これで、imfindcircles
はすべての黄色のチップと 1 つの緑のチップを検出しました。これらのチップは青で描画し、先に検出済みの他のチップ (ObjectPolarity
を "dark"
に設定して検出したもの) は赤で描画します。
h = viscircles(centers,radii);
すべての円が検出されています。最後に、注意の必要な点があります。パラメーターを検出率が高くなるような値に変更すると、検出される円は増えますが、円を誤って検出する可能性も高くなります。正しく検出できる円の数 (検出率) と誤って検出される円の数 (誤認率) の間にはトレードオフがあります。
円探しをお楽しみください!
参考
imfindcircles
| circles2mask
| viscircles