web-dev-qa-db-ja.com

opencv Pythonを使用して画像の背景を削除します

2つの画像があります。1つは背景のみで、もう1つは背景+検出可能なオブジェクト(私の場合は車)です。以下は画像です

enter image description here

結果の画像に車しか入っていないように、背景を削除しようとしています。以下は、私が望んでいる結果を得ようとしているコードです

import numpy as np
import cv2


original_image = cv2.imread('IMG1.jpg', cv2.IMREAD_COLOR)
gray_original = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
background_image = cv2.imread('IMG2.jpg', cv2.IMREAD_COLOR)
gray_background = cv2.cvtColor(background_image, cv2.COLOR_BGR2GRAY)

foreground = np.absolute(gray_original - gray_background)
foreground[foreground > 0] = 255

cv2.imshow('Original Image', foreground)
cv2.waitKey(0)

2つの画像を減算して得られる画像は

enter image description here

ここに問題があります。予想される結果の画像は車のみです。また、2つの画像を詳しく見ると、それらがまったく同じではないことがわかります。つまり、カメラが少し動いたため、背景が少し乱れていました。私の質問は、これらの2つの画像を使用して、背景をどのように減算できるかです。現時点ではgrabCutやbackgroundSubtractorMOGアルゴリズムを使用したくないのは、それらのアルゴリズムの内部で何が起こっているのか今はわからないからです。

私がやろうとしているのは、次の結果の画像を取得することです enter image description here

また、可能であれば、この特定の場合だけでなく、これを行う一般的な方法を教えてください。つまり、1つの画像に背景があり、2番目の画像に背景+オブジェクトがあります。これを行う最良の方法は何でしょうか。長い質問をしてすみません。

16
muazfaiz

OpenCVの watershed アルゴリズムを使用して問題を解決しました。流域の理論と例を見つけることができます こちら

まず、いくつかのポイント(マーカー)を選択して、保持したいオブジェクトがどこにあるのか、背景はどこにあるのかを指定しました。この手順は手動で行われ、画像ごとに大きく異なる場合があります。また、目的の結果が得られるまで、ある程度の繰り返しが必要です。ツールを使用してピクセル座標を取得することをお勧めします。次に、車の画像のサイズでゼロの空の整数配列を作成しました。そして、マーカー位置のピクセルにいくつかの値(1:background、[255,192,128,64]:car_parts)を割り当てました。

注:私はあなたの画像をダウンロードしたとき、車のあるものを得るためにそれをトリミングしなければなりませんでした。トリミング後、画像のサイズは400x601になります。これは、画像のサイズとは異なる場合があるため、マーカーはオフになります。

その後、流域アルゴリズムを使用しました。 1番目の入力は画像で、2番目の入力はマーカー画像です(マーカー位置を除くすべての場所でゼロ)。結果は以下の画像に示されています。 after watershed

1〜255(車)を超える値を持つすべてのピクセルを設定し、残り(背景)をゼロに設定します。次に、取得した画像を3x3カーネルで膨張させ、車の輪郭に関する情報が失われないようにしました。最後に、cv2.bitwise_and()関数を使用して、膨張したイメージを元のイメージのマスクとして使用しました。結果は次のイメージにあります。 final cropped image

ここに私のコードがあります:

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

# Load the image
img = cv2.imread("/path/to/image.png", 3)

# Create a blank image of zeros (same dimension as img)
# It should be grayscale (1 color channel)
marker = np.zeros_like(img[:,:,0]).astype(np.int32)

# This step is manual. The goal is to find the points
# which create the result we want. I suggest using a
# tool to get the pixel coordinates.

# Dictate the background and set the markers to 1
marker[204][95] = 1
marker[240][137] = 1
marker[245][444] = 1
marker[260][427] = 1
marker[257][378] = 1
marker[217][466] = 1

# Dictate the area of interest
# I used different values for each part of the car (for visibility)
marker[235][370] = 255    # car body
marker[135][294] = 64     # rooftop
marker[190][454] = 64     # rear light
marker[167][458] = 64     # rear wing
marker[205][103] = 128    # front bumper

# rear bumper
marker[225][456] = 128
marker[224][461] = 128
marker[216][461] = 128

# front wheel
marker[225][189] = 192
marker[240][147] = 192

# rear wheel
marker[258][409] = 192
marker[257][391] = 192
marker[254][421] = 192

# Now we have set the markers, we use the watershed
# algorithm to generate a marked image
marked = cv2.watershed(img, marker)

# Plot this one. If it does what we want, proceed;
# otherwise edit your markers and repeat
plt.imshow(marked, cmap='gray')
plt.show()

# Make the background black, and what we want to keep white
marked[marked == 1] = 0
marked[marked > 1] = 255

# Use a kernel to dilate the image, to not lose any detail on the outline
# I used a kernel of 3x3 pixels
kernel = np.ones((3,3),np.uint8)
dilation = cv2.dilate(marked.astype(np.float32), kernel, iterations = 1)

# Plot again to check whether the dilation is according to our needs
# If not, repeat by using a smaller/bigger kernel, or more/less iterations
plt.imshow(dilation, cmap='gray')
plt.show()

# Now apply the mask we created on the initial image
final_img = cv2.bitwise_and(img, img, mask=dilation.astype(np.uint8))

# cv2.imread reads the image as BGR, but matplotlib uses RGB
# BGR to RGB so we can plot the image with accurate colors
b, g, r = cv2.split(final_img)
final_img = cv2.merge([r, g, b])

# Plot the final result
plt.imshow(final_img)
plt.show()

多数の画像がある場合は、おそらくマーカーをグラフィカルに注釈するツールを作成するか、マーカーを自動的に検出するアルゴリズムを作成する必要があります。

15
TasosGlrs

問題は、unsigned8ビット整数の配列を減算していることです。この操作はオーバーフローする可能性があります。

実証する

_>>> import numpy as np
>>> a = np.array([[10,10]],dtype=np.uint8)
>>> b = np.array([[11,11]],dtype=np.uint8)
>>> a - b
array([[255, 255]], dtype=uint8)
_

OpenCVを使用しているため、目標を達成する最も簡単な方法は cv2.absdiff() を使用することです。

_>>> cv2.absdiff(a,b)
array([[1, 1]], dtype=uint8)
_
6
Dan Mašek

OpenCVのグラブカットアルゴリズムを使用することをお勧めします。最初に前景と背景に数本の線を引き、前景が背景から十分に分離されるまでこれを続けます。ここで説明します: https://docs.opencv.org/trunk/d8/d83/tutorial_py_grabcut.html およびこのビデオ: https://www.youtube。 com/watch?v = kAwxLTDDAw

0
wordsforthewise