第 17 章: 画像セグメンテーションの抽出

画像処理のプロセスでは、多くの場合、画像から前景オブジェクトを抽出する必要があります。例えば、ビデオ監視では、私たちが観察するのは固定された背景の下でのビデオコンテンツですが、私たちは背景そのものには興味がなく、背景に映る車両や歩行者などの物体に興味があります。これらのオブジェクトをビデオから抽出し、オブジェクトが背景に入らないビデオ コンテンツを無視したいと考えています。

1. 分水嶺アルゴリズムを使用して画像のセグメンテーションと抽出を実現します。

画像のセグメンテーションは、画像処理において非常に重要な操作です。流域アルゴリズムは、画像を地理上の地形表面と比較して画像のセグメンテーションを実現するもので、非常に効果的です。

1. アルゴリズム原理:

グレースケール画像はすべて地理的な地形面とみなすことができ、グレースケール値が高い領域は山の頂上、グレースケール値が低い領域は谷とみなすことができます。下の図に示すように、左側の画像は元の画像、右側の画像はそれに対応する「地形面」です。

画像-20211209201008028

それぞれの谷に異なる色の水を「注入」するとします (ここでは OpenCV 公式 Web サイトの表現が使用されていますが、ゴンザレスは注入を谷に穴を開け、その穴から均一な速度で水を上昇させることと表現しています) 。その後、水位が上昇し続けると、さまざまな谷からの水が集まります。この過程では、異なる谷からの水が合流するのを防ぐために、水の流れが合流する場所に堤防を築く必要があります。このプロセスにより、画像が集水域と流域線という 2 つの異なるセットに分割されます。私たちが構築する堤防は分水嶺であり、元の画像を分割したものです。これが分水界アルゴリズムです。

下図の左の画像は元の画像、右の画像は分水嶺アルゴリズムを使用して得られた画像分割結果です。CMM の Web サイトではサンプル画像だけでなく、アニメーションのデモンストレーション効果も提供されています。興味のある読者は Web サイトにアクセスしてご覧ください。

画像-20211209201347233

しかし、ノイズやその他の要因の影響により、上記の基本的な分水界アルゴリズムでは過剰なセグメント化が発生することがよくあります。過度にセグメンテーションすると、画像が密な独立した小さなブロックに分割され、セグメンテーションが無意味になってしまいます。下の画像は、過剰にセグメント化された画像を示しています。左の画像は電気泳動の画像、右の画像は過分割の結果画像であり、過分割の現象が非常に深刻であることがわかります。

画像-20211209202055976

画像セグメンテーション効果を改善するために、マスクに基づいた改良された分水界アルゴリズムが提案されています。改良された分水界アルゴリズムにより、ユーザーは同じセグメント化された領域とみなされるものをマークアウトすることができます (マークされた部分はマスクと呼ばれます)。このようにして、流域アルゴリズムが処理する際、ラベル付けされた部分を同じセグメント化された領域として処理します。

下の図で、左側の図はラベルを付けた元の画像で、暗いとマークされた 3 つの小さなカラー ブロックは、マスク ウォーターシェッド アルゴリズムを使用すると、これらの部分に含まれる色が同じ領域に分割されることを示しています。 。マスク流域アルゴリズムを使用して得られたセグメンテーションの結果を図の右の図に示します。

画像-20211209202809422

改良された分水界アルゴリズムを使用して、図の左側の電気泳動画像をマスクし、右側のセグメンテーション結果を取得します。セグメンテーションの結果が大幅に改善されていることがわかります。

画像-20211209202919067

2. 関連機能の紹介:

OpenCV では、関数 cv2.watershed() を使用して分水嶺アルゴリズムを実装できます。特定の実装プロセスでは、形態学的関数と距離変換関数 cv2. distanceTransform() および cv2.connectedComponents() を使用して画像のセグメンテーションを完了することも必要です。以下は、分水界アルゴリズムで使用される関数の簡単な説明です。

  • ウォーターシェッド アルゴリズムを使用する前に、画像の前処理が必要です。画像の前処理の手順は次のとおりです。
  • つまり、画像のセグメンテーションは分水界アルゴリズムを使用して実行できますが、分水界アルゴリズムを使用するには画像を前処理する必要があります。
  • OpenCV の Watershed 関数によって実装される Watershed アルゴリズムは、「マーキング」に基づくセグメンテーション アルゴリズムであり、従来の Watershed アルゴリズムの過剰セグメンテーションの問題を解決するために使用されます。

(1) 形態学的機能の検討:

分水界アルゴリズムを使用して画像をセグメント化する前に、画像に対して単純な形態学的処理を実行する必要があります。まず、形態学の基本的な操作を確認します。

  • オープニング操作:オープニング操作は、最初に浸食し、次に拡張する操作であり、オープニング操作により画像内のノイズを除去できます。たとえば、下の図では、左側の画像が最初に侵食されると中央の画像が取得され、次に中央の画像が拡張されて右の画像が取得されます。画像を開くと画像内のノイズを除去できます。ウォーターシェッド アルゴリズムを使用して画像を処理する前に、画像セグメンテーションに対するノイズによって引き起こされる可能性のある干渉を回避するために、オープン操作を使用して画像内のノイズを除去する必要があります。

    画像-20211209204019537

  • 画像の境界を取得する:画像の境界は、形態学的演算と減算演算によって取得できます。たとえば、下の画像では、左側の画像が元の画像、中央の画像がそれを腐食して得られた画像、右側の画像がその 2 つを差し引いたものです。観察すると、右の画像が左の画像の境界であることがわかります。

    画像-20211209204320121

    上記の分析から、画像の境界情報はモルフォロジー演算と減算演算を使用して取得できることがわかります。ただし、形態学的操作は比較的単純な画像にのみ適用できます。画像の前景オブジェクトが接続されている場合、形態学的演算を使用して各サブ画像の境界を正確に取得することはできません。

(2) 距離変換関数 distanceTransform:

画像内のサブグラフが接続されていない場合、形態的浸食操作を直接使用して前景オブジェクトを決定できますが、画像内のサブグラフが互いに接続されている場合、前景オブジェクトを決定することは困難です。この時点で、前景オブジェクトは距離変換関数 cv2. distanceTransform() を使用して簡単に抽出できます。

距離変換関数 cv2. distanceTransform() は、バイナリ イメージ内のすべての点から最も近い背景点までの距離 (つまり、イメージ内の非ゼロ値ピクセルから最も近いゼロ値ピクセルまでの距離) を計算します。もちろん、ピクセル自体の値が 0 であれば、この距離も 0 になります。

距離変換関数 cv2. distanceTransform() の計算結果は、画像内の各ピクセルと背景 (値が 0 のピクセル) との距離関係を反映します。いつもの:

  • 前景オブジェクトの中心 (重心) が値 0 のピクセルから遠く離れている場合、より大きな値が取得されます。
  • 前景オブジェクトのエッジが値 0 のピクセルに近い場合、より小さな値が取得されます。

上記の計算結果を二値化すると、画像内の前景オブジェクトの中心や骨格などの情報が得られます。距離変換関数 cv2. distanceTransform() は、オブジェクトの中心を計算することができ、輪郭を洗練したり、画像の前景を取得したりすることもでき、多くの機能を持っています。

距離変換関数 cv2. distanceTransform() の構文形式は次のとおりです。

  • dst=cv2. distanceTransform(src, distanceType,maskSize[,dstType]])

    • src: は、8 ビットのシングルチャネルのバイナリ イメージです。

    • distanceType: 距離タイプのパラメータであり、その具体的な値と意味は表に示されています。

      画像-20211209210708877

      画像-20211209210725379

    • MaskSize: マスクのサイズであり、その可能な値が表に示されています。distanceType=cv2.DIST_L1 または cv2.DIST_C の場合、maskSize は強制的に 3 に設定されることに注意してください (3 に設定することと 5 以上に設定することの間に違いはないため)。

      画像-20211209210856176

    • dstType: ターゲット画像のタイプ。デフォルト値は CV_32F です。

    • dst: 計算されたターゲット イメージを示す戻り値。8 ビットまたは 32 ビットの浮動小数点数にすることができ、サイズは src と同じです。

例: 距離変換関数 cv2. distanceTransform() を使用して、画像の特定の前景を計算し、その効果を観察します。

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('img.jpg')
# img = cv2.imread('../sugar.tiff')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 阈值处理
rst, thresh = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# 开运算
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

# 图像距离计算
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)

# 获取前景对象的中心
rst, front = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)

plt.subplot(161)
plt.imshow(rgb_img)
plt.title('img')
plt.axis('off')

plt.subplot(162)
plt.imshow(gray_img, cmap='gray')
plt.title('gray_img')
plt.axis('off')

plt.subplot(163)
plt.imshow(thresh, cmap='gray')
plt.title('thresh')
plt.axis('off')

plt.subplot(164)
plt.imshow(opening, cmap='gray')
plt.title('opening')
plt.axis('off')

plt.subplot(165)
plt.imshow(dist_transform, cmap='gray')
plt.title('dist_transform')
plt.axis('off')

plt.subplot(166)
plt.imshow(front, cmap='gray')
plt.title('front')
plt.axis('off')

plt.show()

画像-20211210171816909

(3) 未知の領域を決定します。

モルフォロジーを使用した拡張操作により、画像の前景を「膨張」させることができます。画像の前景を拡大すると背景が「圧縮」されるため、このとき得られる背景情報は実際の背景よりも小さくなければならず、前景の「確定した背景」は含まれません。以下、説明の便宜上、判定背景をBと呼ぶ。

距離変換関数 cv2. distanceTransform() は、画像の「中心」を取得し、「決定された前景」を取得できます。説明の便宜上、決定された前景をFと呼びます。

画像内で前景 F と背景 B が決定され、残りの領域が未知領域 UN になります。エリアのこの部分は、流域アルゴリズムによってさらに明確化されるエリアです

画像 O の場合、未知領域 UN は次の関係から取得できます。

  • 未知の領域 UN = 画像 O - 背景を決定 B - 前景 F を決定

上記の式を整理すると、次のようになります。

  • 未知領域 UN=(画像 O-決定された背景 B)-決定された前景 F

上式の「画像O決定背景B」は、画像に対して形態的拡張演算を施すことにより求めることができる。前景オブジェクトの膨張 = 画像 o - 背景 B を決定します

例: 画像の決定された前景、決定された背景、および未知の領域に注釈を付ける

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('img.jpg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 阈值分割
rst, thresh = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((3, 3), dtype=np.uint8)
# 开运算
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
# 膨胀
bg = cv2.dilate(opening, kernel, iterations=3)

# 距离计算
dist_tansform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
rst, fore = cv2.threshold(dist_tansform, 0.7 * dist_tansform.max(), 255, 0)
fore =  np.uint8(fore)
un = cv2.subtract(bg, fore)

plt.subplot(221)
plt.imshow(rgb_img)
plt.title('img')
plt.axis('off')

plt.subplot(222)
plt.imshow(bg)
plt.title('bg')
plt.axis('off')

plt.subplot(223)
plt.imshow(fore)
plt.title('fore')
plt.axis('off')

plt.subplot(224)
plt.imshow(un)
plt.title('un')
plt.axis('off')

plt.show()

画像-20211211184357636

図の右上隅の画像 bg に注意してください。

  • 手前の小さな丸は「元の画像 - 背景を決定する」部分であり、「決定された背景」ではありません。
  • その背景画像が「確定背景」です。

(4) 関数connectedComponentsオブジェクトのアノテーション:

決定された前景が決定された後、決定された前景画像にマークを付けることができる。OpenCV では、アノテーションに関数 cv2.connectedComponents() を使用できます。この関数は背景を 0 としてマークし、他のオブジェクトを 1 から始まる正の整数でマークします。

関数 cv2.connectedComponents() の構文形式は次のとおりです。

  • retval,labels=cv2.connectedComponents(画像)
    • image: ラベル付けされる 8 ビットのシングルチャネル画像。
    • retval: 返されたアノテーションの数。
    • ラベル: ラベル付けされた結果画像です。
import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('img.jpg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

rst, thresh = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((3, 3), dtype=np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

sure_bg = cv2.dilate(opening, kernel, iterations=3)
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, fore = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
fore = np.uint8(fore)

# 标注前景对象
rst, markers = cv2.connectedComponents(fore)
print(markers)

plt.subplot(131)
plt.imshow(rgb_img)
plt.title('img')
plt.axis('off')

plt.subplot(132)
plt.imshow(fore)
plt.title('fore')
plt.axis('off')

plt.subplot(133)
plt.imshow(markers)
plt.title('markers')
plt.axis('off')

plt.show()

画像-20211211214747631

  • 左の画像が元の画像です
  • 中央は、距離変換後に得られた前景画像の前方の中心点画像です。
  • 右側の画像は、前景画像の中心点画像にラベルを付けた後の結果の画像マーカーです。

前景画像の中心点が異なってマークされていることがわかります。

関数 cv2.connectedComponents() が画像をマークするとき、背景を 0 としてマークし、他のオブジェクトを 1 から始まる正の整数でマークします。具体的な対応関係は次のとおりです。

  • 値 0 は背景領域を表します。
  • 1 から始まる値は、異なる前景領域を表します。

分水界アルゴリズムでは、ラベル値 0 は未知の領域を表します。したがって、関数 cv2.connectedComponents() でマークされた結果を調整する必要があります。マークされた結果に値 1 を追加します。上記の処理後のラベル付け結果は次のようになります。

  • 値 1 は背景領域を表します。
  • 2 から始まる値は、異なる前景領域を表します。

分水界アルゴリズムを使用できるようにするには、元の画像内の未知の領域をマークし、計算された未知の領域を 0 としてマークする必要もあります。

ret,markers=cv2.connectedComponents(fore)
markers=markers+1
markers[未知区域]=0

例: cv2.connectedComponents() のラベル付け結果を修正する

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('img.jpg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

rst, thresh = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((3, 3), dtype=np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

sure_bg = cv2.dilate(opening, kernel, iterations=3)
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, fore = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
fore = np.uint8(fore)
rst, markers = cv2.connectedComponents(fore)

# 修正标注的前景对象
fore_adv = fore.copy()
unknown = cv2.subtract(sure_bg, fore_adv)
ret2, markers2 = cv2.connectedComponents(fore_adv)
markers2 += 1
markers2[unknown == 255] = 0

plt.subplot(141)
plt.imshow(rgb_img)
plt.title('img')
plt.axis('off')

plt.subplot(142)
plt.imshow(fore)
plt.title('fore')
plt.axis('off')

plt.subplot(143)
plt.imshow(markers)
plt.title('markers')
plt.axis('off')

plt.subplot(144)
plt.imshow(markers2)
plt.title('markers2')
plt.axis('off')

plt.show()

画像-20211211230305532

  • マーカー マップは、関数 cv2.connectedComponents() を使用して画像を直接マークした結果です。
  • マーカー 2 のグラフは、修正されたラベリング結果です。

左右の画像を比較すると、右の画像は前景画像のエッジ(未知の領域)にマークが付けられているため、決定された各前景にはマークされた未知の領域である黒いエッジが存在することがわかります。

(5) 関数 cv2.watershed():

OpenCV の Watershed 関数によって実装される Watershed アルゴリズムは、「マーキング」に基づくセグメンテーション アルゴリズムであり、従来の Watershed アルゴリズムの過剰セグメンテーションの問題を解決するために使用されます。

上記の画像前処理が完了した後、分水界アルゴリズムを使用して前処理結果画像をセグメント化できます。OpenCV では、ウォーターシェッド アルゴリズムを実装する関数は cv2.watershed() で、その構文形式は次のとおりです。

  • マーカー=cv2.watershed(画像,マーカー)

    • image: 入力イメージです。8 ビット 3 チャネル イメージである必要があります。

    • マーカー: これは 32 ビットの単一チャネルのラベル付け結果であり、画像と同じサイズを持つ必要があります。

      • cv2.watershed() 関数を使用して画像を処理する前に、画像を前処理する必要があり、画像内の予想されるセグメンテーション領域の輪郭が正の数で大まかに描かれている必要があります。セグメント化された各領域には、1、2、3 などのラベルが付けられます。決定されていない領域については、0 としてマークする必要があります。マークされた領域は、流域アルゴリズム セグメンテーションの「シード」領域として理解できます。

      • マーカーでは、各ピクセルは初期の「シード値」に設定されるか、境界の「-1」に設定されます。

      • このアルゴリズムは、マーカーによって渡された輪郭をシード (いわゆる注水ポイント) として使用し、分水嶺アルゴリズムの規則に従って画像上の他のピクセルを判断し、画像が処理されるまで各ピクセルの領域属性を描写します。すべてのピクセル。領域間の境界の値は区別するために「-1」に設定されています。

3. 分水界アルゴリズムの画像セグメンテーションの例

画像セグメンテーションに分水嶺アルゴリズムを使用する場合の基本的な手順は次のとおりです。

  1. 元の画像 O は、モルフォロジー オープニング操作によってノイズ除去されます。
  2. エッチング操作により「確定背景B」を取得します。ここでは「元の画像を取得する - 背景を決定する」だけで十分であることに注意してください。
  3. 距離変換関数 cv2. distanceTransform() を使用して元の画像を操作し、閾値処理を実行して「決定された前景 F」を取得します。
  4. 未知の領域 UN(UN=O –BF) を計算します。
  5. 関数 cv2.connectedComponents() を使用して、元のイメージ O に注釈を付けます。
  6. 関数 cv2.connectedComponents() のアノテーション結果を修正します。
  7. 画像のセグメンテーションは、ウォーターシェッド関数を使用して行われます。

1 ~ 6 は画像の前処理です。画像内の未知の領域が 0 としてマークされ、既知の領域が 1、2、3... としてマークされている限り、つまりシード領域がマークされています。7 番目のステップは、注釈に従って分水界アルゴリズムを使用して画像をセグメント化することです。

例:

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('img.jpg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
new_img = rgb_img.copy()

rst, thresh = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
sure_bg = cv2.dilate(opening, kernel, iterations=3)
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
ret, markers = cv2.connectedComponents(sure_fg)
markers += 1
markers[unknown == 255] = 0
markers = cv2.watershed(new_img, markers)
new_img[markers == -1] = [0, 255, 0]

plt.subplot(121)
plt.imshow(rgb_img)
plt.title('img')
plt.axis('off')

plt.subplot(122)
plt.imshow(new_img)
plt.title('rst')
plt.axis('off')

plt.show()

画像-20211211233842654

2. インタラクティブな前景抽出

古典的な前景抽出手法では、主に魔法の杖ツールなどのテクスチャ (カラー) 情報、またはスマート ハサミなどのエッジ (コントラスト) 情報が使用されます。2004 年、Microsoft Research (Cambridge) の Rother らは、「GrabCut: Interactive Foreground Extraction using Iterated Graph Cuts」という論文で対話型前景抽出テクノロジを提案しました。彼らが提案したアルゴリズムは、わずか数回の対話操作で前景画像を正確に抽出できます。

前景の抽出を開始するときは 上記の処理を行った後、前景の抽出効果が理想的ではなく、前景が抽出されなかったり、背景が前景として抽出されたりする場合がありますが、その際にはユーザーが抽出処理に介入する必要があります。 。ユーザーは、元の画像 (または元の画像と同じサイズの画像) のコピーにおいて、前景として抽出される領域を白でマークし、背景として使用される領域を黒でマークします。次に、ラベル付けされた画像をマスクとして使用して、アルゴリズムに前景の反復抽出を継続させ、最終結果を取得します。

たとえば、下図の左側の画像の場合、抽出する前景のレナを長方形の枠で囲み、前景画像と背景画像をそれぞれ白と黒でマークします。注釈が完了したら、インタラクティブな前景抽出アルゴリズムを使用して、右側に示す結果画像を取得します。

画像-20211212140453640

GrabCut アルゴリズムの具体的な実装プロセスを見てみましょう。

  1. 前景のおおよその位置を長方形でマークします。この時点では、長方形の枠は前景と背景の両方を含む前景のおおよその位置を示すだけであるため、この領域は実際には未決定の領域であることに注意してください。ただし、この領域の外側の領域は「確定した背景」とみなされます。

  2. 長方形の外側の「決定された背景」データに基づいて、長方形の領域内の前景と背景を区別します。

  3. 前景と背景はガウス混合モデル (GMM) でモデル化されます。GMM はユーザー入力に基づいて学習し、新しいピクセル分布を作成します。未分類のピクセル (背景または前景の可能性がある) は、既知の分類されたピクセル (前景と背景) との関係に従って分類されます。

  4. ピクセルの分布に従ってグラフが生成され、グラフ内のノードが各ピクセルになります。ピクセルに加えて、前景ノードと背景ノードの 2 つのノードがあります。すべての前景ピクセルは前景ノードに接続され、すべての背景ピクセルは背景ノードに接続されます。各ピクセルを前景ノードまたは背景ノードに接続するエッジの重みは、ピクセルが前景または背景である確率によって決定されます。

  5. グラフ内の各ピクセルは、前景ノードまたは背景ノードに接続されているだけでなく、相互にも接続されています。2 つのピクセルを接続するエッジの重み値は、その類似度によって決定され、2 つのピクセルの色が近いほど、エッジの重み値は大きくなります。

  6. ノードが接続されると、解決される問題は接続されたグラフになります。各エッジの重み関係に従ってグラフを切り出し、異なる点を前景ノードと背景ノードに分割する。

  7. 分類が収束するまで上記のプロセスを繰り返します。
    OpenCV の公式 Web サイトにはさらに詳細な情報が掲載されており (http://www.cs.ru.ac.za/research/g02m1682/)、読者は興味があればさらに詳しく知ることができます。

OpenCV では、対話型の前景抽出を実装する関数は cv2.grabCut() であり、その構文形式は次のとおりです。

  • マスク,bgdModel,fgdModel=cv2.grabCut(img,mask,rect,bgdModel,fgdModel,iterCount[,mode])

    • img: 入力画像は8ビット3チャンネルである必要があります。

    • マスク: マスク イメージです。8 ビットのシングル チャネルである必要があります。このパラメータは前景領域、背景領域、不確定領域を決定するために使用され、4 つの形式で設定できます。

      • cv2.GC_BGD: 背景が決定されていることを示し、値 0 で表すこともできます。
      • cv2.GC_FGD: 前景が決定されていることを示し、値 1 で表すこともできます。
      • cv2.GC_PR_BGD: 考えられる背景を示します。値 2 で表すこともできます。
      • cv2.GC_PR_FGD: 可能性のある見込み客を示します。値 3 で表すこともできます。

      なお、マスクはパラメータとして使用したマスク画像だけでなく、grabCut関数を処理した後の結果マスク画像も含まれますので、結果マスク画像に基づいて前景オブジェクトを抽出します。

      最終的にテンプレートを使用して前景を抽出すると、パラメータ値 0 と 2 は背景にマージされ (両方とも 0 として扱われます)、パラメータ値 1 と 3 は前景にマージされます (両方とも扱われます)。 1)として。通常の状況では、白のブラシと黒のブラシを使用してマスク イメージ上にマークを付け、変換によって白のピクセルを 0 に、黒のピクセルを 1 に設定します。

    • rect: 前景オブジェクトを含む領域を指し、領域の外側の部分は「確定した背景」とみなされます。そのため、選択する際には、rect で指定した範囲内に前景が含まれるようにしてください、そうでない場合は、rect の外側の前景部分は抽出されません。パラメータ rect は、パラメータ mode の値が長方形モード cv2.GC_INIT_WITH_RECT に設定されている場合にのみ意味を持ちます。その形式は (x, y, w, h) で、それぞれ領域の左上隅のピクセルの x 軸座標と y 軸座標、および領域の幅と高さを表します。前景が右下にあり、元の画像のサイズを判断したくない場合は、w と h に大きな値を直接使用できます。マスク モードを使用する場合は、この値を none に設定します。

    • bgdModel: アルゴリズム内で使用される配列については、サイズ (1,65) の numpy.float64 配列のみを作成する必要があります。

    • fgdModel: アルゴリズム内で使用される配列については、サイズ (1,65) の numpy.float64 配列のみを作成する必要があります。

    • iterCount: 反復回数を示します。

    • mode: 反復モードを示します。その可能な値と意味を表に示します。

      画像-20211212143424526

    • 関数の戻り値はmask、bgdModel、fgdModelです。

例 1: GrabCut アルゴリズムを使用して画像の前景を抽出し、抽出効果を観察します。

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('../lena512color.tiff')
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mask = np.zeros(img.shape[:2], dtype=np.uint8)
bgd_model = np.zeros((1, 65), dtype=np.float64)
fgd_model = np.zeros((1, 65), dtype=np.float64)
rect = (50, 50, 500, 500)

# 函数的返回值为mask,bgdModel,fgdModel
cv2.grabCut(img, mask, rect, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_RECT)
print(mask)
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')

rst = img * mask2[:, :, np.newaxis]
rst = cv2.cvtColor(rst, cv2.COLOR_BGR2RGB)

plt.subplot(121)
plt.imshow(rgb_img)
plt.title('img')
plt.axis('off')

plt.subplot(122)
plt.imshow(rst)
plt.title('rst')
plt.axis('off')

plt.show()

画像-20211212145057101

マスクが使用されていない場合(マスク値がデフォルト値の 0 に設定されている場合)、関数 cv2.grabCut() の処理効果があまり良くないことがわかります: 左側の前景を抽出するとき画像ではキャラクターの帽子が完全に抽出されていません。画像によっては、背景が誤って抽出される可能性もあります。

完全な前景オブジェクトを取得するには、いくつかの改善を行う必要があります。ここでは、元の画像にマークを付け、**残しておきたい部分を白、削除したい背景を黒に設定します。**マークされた画像をテンプレートとして使用し、関数 cv2.grabCut() を使用して前景の抽出を完了します。
このプロセスには主に次の手順が含まれます。

  1. 関数 cv2.grabCut() を使用して、cv2.GC_INIT_WITH_RECT モードで画像に対して予備的な前景抽出を実行し、予備的な抽出結果の画像 og を取得します。主に予備のマスクを入手するためです。
  2. Windows システムに付属のブラシ ツールを使用して画像を開き、lena などの前景を抽出します。
  3. 白いブラシを使用して、抽出する前景領域をマークします。
  4. 黒いブラシを使用して、削除する背景の領域をマークします。
  5. 現在設定されているレナ画像をテンプレート画像m0として保存します。
  6. テンプレート画像 m0 の白と黒の値をテンプレート m にマッピングします。テンプレート画像 m0 の白の値 (画素値 255) をテンプレート画像 m の決定された前景 (画素値 1) にマッピングし、テンプレート画像 m0 の黒の値 (画素値 0) をテンプレート画像 m にマッピングします。決定された背景 (ピクセル値は 0)。
  7. テンプレート画像 m を関数 cv2.grabCut() のテンプレート パラメーター (マスク) として使用して、画像 og の前景抽出を完了します。

なお、上記の手順において、ブラシでマークされたテンプレート画像m0をそのままテンプレート(パラメータマスク)として使用することはできない。関数 cv2.grabCut() では、パラメーター マスクの値が cv2.GC_BGD (決定された背景)、cv2.GC_FGD (決定された前景)、cv2.GC_PR_BGD (可能な背景)、cv2.GC_PR_FGD (可能な前景)、または0、1、2、3 の値。この時のテンプレート画像m0には[0,255]の値が存在するため、その値は関数cv2.grabCut()の要件を満たしておらず、そのままパラメータマスクとして使用することができません。まずテンプレート イメージ m0 の白の値と黒の値をテンプレート m にマッピングする必要があります。その後、テンプレート イメージ m が関数 cv2.grabCut() のテンプレート パラメーターとして使用されます。

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('lena512color.tiff')
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 对前景对象进行初步提取,获取初步mask
mask = np.zeros(img.shape[:2], dtype=np.uint8)
bgd = np.zeros((1, 65), dtype=np.float64)
fgd = np.zeros((1, 65), dtype=np.float64)
rect = (50, 50, 500, 500)
cv2.grabCut(img, mask, rect, bgd, fgd, 5, cv2.GC_INIT_WITH_RECT)

# 读取模板,根据模板设置得到的初始mask
mask2 = cv2.imread('m.tiff')
rgb_mask2 = cv2.cvtColor(mask2, cv2.COLOR_BGR2RGB)
gray_mask2 = cv2.cvtColor(mask2, cv2.COLOR_BGR2GRAY)
mask[gray_mask2 == 0] = 0
mask[gray_mask2 == 255] = 1

# 根据修改后的mask再次进行前景对象提取
cv2.grabCut(img, mask, None, bgd, fgd, 5, cv2.GC_INIT_WITH_MASK)
mask = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')

# 根据得到的mask提取前景对象
new_img = rgb_img.copy()
rst = new_img * mask[:, :, np.newaxis]

plt.subplot(131)
plt.imshow(rgb_img)
plt.title('img')
plt.axis('off')

plt.subplot(132)
plt.imshow(rgb_mask2)
plt.title('m')
plt.axis('off')

plt.subplot(133)
plt.imshow(rst)
plt.title('rst')
plt.axis('off')

plt.show()

画像-20211212162510096

おすすめ

転載: blog.csdn.net/weixin_57440207/article/details/122647056