前回からダラダラと特定の条件下にある細胞の検出を試みていますが、ようやくゴールに近づいてきそうなのでこれまでの処理を整理してみたいと思います。
目次
輪郭か、円検出か
findCountoursメソッドを使用した場合。
以下の記事で、二値化した画像からfindContoursメソッドを利用して輪郭を検出することで画像中の円を検出したことにしていましたが、輪郭はあくまでも輪郭なので少しでも形が崩れている円や被りが出た際には誤検出が多発してしまいます。
例えば、以下のように細胞を見事に避けてゲルの輪郭を検出してしまっています。

これではゲルにトラップされた細胞を識別することはできないので、新たな方法を考える必要がありそうです。
Hough変換
輪郭がダメなら、円を前提として画像中の円のようなものを検出すれば良いという魂胆です。
これなら、3つのパラメータのみ(中心座標と半径)で円のようなものを綺麗な円として補完できるはずです。
Hough変換の基本原理は、直線なら直交座標上の点全てをρ-θ空間に写し、0<θ<πの範囲で変化させることによってできた曲線が交差する点を元に推定するというものです。
とはいえ、opencvにはhough変換を一行で行ってくれるメソッドが用意されているのでそれを使うだけです。
Hough変換の実装
二値化画像のdenoise
hough変換は多数決でもっともらしいオブジェクトを検出するため、ノイズに強い側面はありますが、できる限りノイズを消去してから検出処理を行います。
import cv2
import numpy as np
def main():
img = cv2.imread("img.png")
bimg=cv2.adaptiveThreshold(cv2.cvtColor(img,cv2.COLOR_BGR2GRAY), 255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,13,2 )
contours,hierarchy = cv2.findContours( bimg, cv2.RETR_EXTERNAL | cv2.RETR_TREE , cv2.CHAIN_APPROX_NONE)
cv2.drawContours(img,[i for i in contours if abs(cv2.contourArea(i))<= 300], -1,(0,0,0),-1)
cv2.imwrite("img_denoised.png",img)
if __name__ == '__main__':
main()輪郭を描画する際に、ある一定以下の面積であるオブジェクトは二値化の背景色と同じ(0,0,0)とすることで、ノイズを削除した二値化画像を取得することができます。
以下がdenoise前の画像(左)とdenoise後の画像(右)です。


ノイズ除去と言いながら、必要な部分まで消してしまっていないか心配ですが、、、消す対象の面積の閾値は1ピクセル単位で設定できるため、場合に応じて最適化が容易にできます。
円の検出
円の検出にはHoughCirclesメソッドを使います。
HoughCircles(image, method, dp, minDist, param1, param2, minRadius, maxRadius)引数が大量にあって大変そうですが、一つ一つみていきます。
imageにはシングルチャネルの画像を入力します。二値化が完了しているのでそれを入れるだけでokです。
methodではhough変換の手法を指定しますが、基本的にHOUGH_GRADIENTで問題ないです。
dpは1と2の二種類ありますが、ブラー処理などを済ませているので1(解像度変更なし)で読み込むと良いと思います。
minDistは円の中心同士の最小距離を指定します。低くすればするほど、円が一部分被っている場合の検出に強くなります。
param1はエッジ検出に使用される閾値の上側の値です。低くすればするほど、たくさんの円を検出してくれますが、誤検出につながります。
param2は円の中心を検出する際の投票数の閾値を指定するため、高くすればするほど円検出の精度は上がりますが、”柔軟な検出”ができなくなるためほどほどにしておいた方が良さそうです。
minRadius,maxRadiusは文字通り、半径の閾値を指定します。輪郭検出の際は面積を閾値としていたのに対して、半径をそのまま使用できるため、不完全な円に強いです。
import cv2
import numpy as np
img = cv2.imread('img_denoised.png')
img_raw = cv2.imread('img_raw.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20,param1=120,param2=11,minRadius=0,maxRadius=19)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
cv2.circle(img_raw,(i[0],i[1]),i[2]+5,(0,255,0),2)
cv2.circle(img_raw,(i[0],i[1]),2,(0,0,255),1)
cv2.imwrite('circles.png',img_raw)
for ループでρ-θ空間での投票結果から返ってきた値を中心座標および半径の3次元配列として取り出しています。
(hough関数の返り値であるリストが3次元構造になっている理由はわかりませんでした。)
以下が円を検出した結果です。

中心点および、輪郭線の色や太さは調整できます。
無事円の検出ができたことで、特殊なゲル球の座標を取得することができました。
次回の記事では、その座標と細胞の座標を照らし合わせて、重なっている部分を検出してみようと思います。

