1. ホーム
  2. opencv

OpenCVでWatershedのマーカーを定義するには?

2023-09-24 09:30:04

質問

OpenCVを使ってAndroid向けに書いています。私は、マーカー制御の分水嶺を使用して、ユーザーが手動で画像をマークすることなく、以下のような画像をセグメンテーションしています。私はマーカーとして領域最大値を使用することを計画しています。

minMaxLoc() と入力すれば値が得られますが、興味のあるblobに限定するにはどうしたらよいでしょうか?の結果を利用できますか? findContours() または cvBlob blob の結果を利用して ROI を制限し、各 blob に最大値を適用できますか?

どのように解決するのですか?

まず最初に:関数 minMaxLoc は与えられた入力に対してグローバルな最小値と最大値を見つけるだけなので、地域的な最小値や最大値を決定するのにはほとんど役に立ちません。しかし、あなたの考えは正しく、地域的な最小値/最大値に基づいてマーカーを抽出し、マーカーに基づいてWatershed Transformを実行することは全く問題ありません。ここで、Watershed Transformとは何か、そしてOpenCVに存在する実装をどのように正しく使うべきかを明らかにしてみたいと思います。

流域を扱った多くの論文では、以下のように説明されています(詳細は不明です)。あなたが知っているある地域の表面を考えてみましょう。それは谷と山(ここでは関係ない他の詳細と一緒に)を含んでいます。この地表の下には、色のついた水しかないとする。ここで、地表の各谷に穴を開けると、水はすべての領域を満たし始めます。すると、ある地点で色の違う水が合流するので、合流しないようにダムを作ります。最終的にはダムの集合体が、色の違う水を分ける分水嶺になります。

さて、もしその表面にあまりに多くの穴を開けると、あまりに多くの領域ができてしまいます:オーバーセグメンテーションです。もし、穴が少なすぎると、過小な分割になります。ですから、分水嶺の使用を提案するほぼすべての論文は、実際には、その論文が扱っているアプリケーションでこれらの問題を回避するためのテクニックを提示しています。

私がこのようなことを書いたのは(Watershed Transformが何であるかを知っている人にとってはナイーブすぎるかもしれませんが)、Watershed実装をどのように使うべきか(現在受け入れられている答えは完全に間違った方法で行っています)に直接反映させるためなのです。それでは、Pythonバインディングを使ったOpenCVの例から始めましょう。

問題で提示された画像は、ほとんどが近すぎて、いくつかのインスタンスで重なっている多くのオブジェクトで構成されています。ここでの流域の有用性は、これらのオブジェクトを1つのコンポーネントにグループ化するのではなく、正しく分離することです。そのため、各オブジェクトに少なくとも1つのマーカーと、背景の良いマーカーが必要です。例として、まず入力画像を大津で2値化し、小さな物体を除去するためのモルフォロジカルオープンを行う。その結果を下図に示します。次に、この2値画像に対して、距離変換を行うことを考えます。

距離変換の結果、背景から最も遠い領域のみを考慮するような閾値を考えることができる(下の左の画像)。こうすることで、先の閾値の後に異なる領域をラベル付けすることで、各オブジェクトのマーカーを得ることができます。ここで、上の左の画像の拡張版の境界も考慮して、マーカーを構成することができる。マーカの完成形が下の右図である(暗くて見えないマーカもあるが、左図の白い領域が右図で表現されている)。

ここにあるマーカーは、とても意味のあるものです。それぞれの colored water == one marker が領域を埋め始め、流域変換によってダムが建設され、異なる "色" が合流するのを阻止します。このような変換を行うと、左のような画像になります。元の画像と合成してダムだけを考えると、右のような結果になります。

import sys
import cv2
import numpy
from scipy.ndimage import label

def segment_on_dt(a, img):
    border = cv2.dilate(img, None, iterations=5)
    border = border - cv2.erode(border, None)

    dt = cv2.distanceTransform(img, 2, 3)
    dt = ((dt - dt.min()) / (dt.max() - dt.min()) * 255).astype(numpy.uint8)
    _, dt = cv2.threshold(dt, 180, 255, cv2.THRESH_BINARY)
    lbl, ncc = label(dt)
    lbl = lbl * (255 / (ncc + 1))
    # Completing the markers now. 
    lbl[border == 255] = 255

    lbl = lbl.astype(numpy.int32)
    cv2.watershed(a, lbl)

    lbl[lbl == -1] = 0
    lbl = lbl.astype(numpy.uint8)
    return 255 - lbl


img = cv2.imread(sys.argv[1])

# Pre-processing.
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)    
_, img_bin = cv2.threshold(img_gray, 0, 255,
        cv2.THRESH_OTSU)
img_bin = cv2.morphologyEx(img_bin, cv2.MORPH_OPEN,
        numpy.ones((3, 3), dtype=int))

result = segment_on_dt(img, img_bin)
cv2.imwrite(sys.argv[2], result)

result[result != 255] = 0
result = cv2.dilate(result, None)
img[result == 255] = (0, 0, 255)
cv2.imwrite(sys.argv[3], img)