OpenCV 4.13.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 4.13.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
Watershedアルゴリズムによる画像セグメンテーション

目的

  • watershedアルゴリズムを用いたマーカーベースの画像セグメンテーション (領域分割) の使い方を学ぶ。
  • 次を学ぶ: cv.watershed()

理論

任意のグレースケール画像は、高い輝度が山や丘を、低い輝度が谷を表す地形面として見ることができる。まず、孤立した各谷(局所的最小値)に異なる色の水(ラベル)を満たしていく。水位が上がるにつれて、近くの山(勾配)の状況に応じて、異なる谷からの水(当然ながら異なる色)が合流し始める。それを避けるため、水が合流する位置に障壁を築く。すべての山が水没するまで、水を満たし障壁を築く作業を続ける。そうして作った障壁がセグメンテーション結果を与える。これがwatershedの背後にある「考え方」である。いくつかのアニメーションを使って理解するには、watershedに関するCMMのウェブページ を参照するとよい。

しかしこのアプローチでは、ノイズや画像中のその他の不規則性により過分割された結果が得られる。そこでOpenCVは、どの谷点をマージしどれをマージしないかを指定するマーカーベースのwatershedアルゴリズムを実装している。これは対話的な画像セグメンテーションである。やることは、わかっている物体に異なるラベルを付けることである。前景や物体であると確信できる領域をある色(または輝度)でラベル付けし、背景や物体でないと確信できる領域を別の色でラベル付けし、最後に何であるか確信できない領域を 0 でラベル付けする。これがマーカーである。そしてwatershedアルゴリズムを適用する。すると、与えたラベルでマーカーが更新され、物体の境界は -1 の値を持つことになる。

コード

以下では、互いに接触している物体を分割するために、距離変換 (Distance Transform) とwatershedを併用する例を見ていく。

下のコイン画像を考える。コインは互いに接触している。たとえしきい値処理をしても、コインは互いに接触したままになる。

まず、コインのおおよその推定を見つけることから始める。そのために、大津の二値化 (Otsu's binarization) を使うことができる。

試してみよう

次に、画像中の小さな白いノイズを除去する必要がある。そのためにはモルフォロジーのオープニングを使うことができる。物体中の小さな穴を除去するには、モルフォロジーのクロージングを使うことができる。これで、物体の中心近くの領域が前景であり、物体から十分離れた領域が背景であることは確実にわかる。確信できないのはコインの境界領域だけである。

そこで、コインであると確信できる領域を抽出する必要がある。収縮は境界ピクセルを除去する。したがって残ったものは、確実にコインであるといえる。これは物体同士が接触していなければ機能する。しかし接触しているため、もう1つの良い選択肢は距離変換を求めて適切なしきい値を適用することである。次に、コインでないと確信できる領域を見つける必要がある。そのために結果を膨張させる。膨張は物体の境界を背景へと広げる。こうすることで、境界領域が除去されるため、結果における背景領域が本当に背景であることを確実にできる。下の画像を参照のこと。

試してみよう

残りの領域は、コインなのか背景なのか判断がつかない部分である。これはwatershedアルゴリズムが見つけるべき領域である。これらの領域は通常、前景と背景が接するコインの境界付近(あるいは異なる2つのコインが接する箇所)に存在する。これを境界 (border) と呼ぶ。これは sure_bg 領域から sure_fg 領域を引くことで得られる。

次の関数を使用する: cv.distanceTransform (src, dst, distanceType, maskSize, labelType = cv.CV_32F)

引数
src8ビット、シングルチャンネル(2値)の入力画像。
dst計算された距離を格納する出力画像。src と同じサイズの8ビットまたは32ビット浮動小数点のシングルチャンネル画像である。
distanceType距離の種類 (cv.DistanceTypes を参照)。
maskSize距離変換マスクのサイズ (cv.DistanceTransformMasks を参照)。
labelType出力画像の型。cv.CV_8U または cv.CV_32F を指定できる。型 cv.CV_8U は、関数の最初のバリアントかつ distanceType == DIST_L1 の場合にのみ使用できる。

試してみよう

しきい値処理後の画像では、確実にコインであると分かるコインの領域がいくつか得られ、それらは今や分離されている。(場合によっては、互いに接触している物体を分離することではなく、前景のセグメンテーションだけに関心があるかもしれない。その場合、距離変換を使う必要はなく、収縮だけで十分である。収縮は確実な前景領域を抽出するためのもう1つの方法に過ぎない、それだけのことである。)

試してみよう

これで、どこが確実にコインの領域で、どこが背景なのかが分かった。そこでマーカー(元画像と同じサイズだが int32 型の配列)を作成し、その中の領域にラベルを付ける。確実に分かっている領域(前景か背景かを問わず)には任意の正の整数を、ただし異なる整数を付け、確実には分からない領域は0のままにしておく。これには cv.connectedComponents() を使う。この関数は画像の背景を0でラベル付けし、その他の物体には1から始まる整数でラベルを付ける。

ただし、背景を0でマークすると、watershedはそれを未知の領域とみなしてしまう。そのため、背景は別の整数でマークしたい。代わりに、unknown で定義した未知領域を0でマークすることにする。

これでマーカーの準備が整った。いよいよ最終ステップ、watershedの適用である。すると、マーカー画像が変更される。境界領域は -1 でマークされる。

次の関数を使用する: cv.connectedComponents (image, labels, connectivity = 8, ltype = cv.CV_32S)

引数
imageラベル付けの対象となる8ビットシングルチャンネル画像。
labelsラベル付けされた出力画像 (cv.CV_32SC1 型)。
connectivity8連結または4連結に対してそれぞれ 8 または 4 を指定する。
ltype出力画像のラベル型。現在、cv.CV_32S および cv.CV_16U がサポートされている。

次の関数を使用する: cv.watershed (image, markers)

引数
image入力する8ビット3チャンネル画像。
markersマーカーの入出力32ビットシングルチャンネル画像 (マップ)。image と同じサイズでなければならない。

試してみよう