Удалите фон изображения, используя opencv Python

У меня есть два изображения: один с фоном, а другой с фоном + обнаруживаемый объект (в моем случае его автомобиль). Ниже представлены изображения

введите описание изображения здесь

Я пытаюсь удалить фон, чтобы в полученном изображении был только автомобиль. Ниже приведен код, с помощью которого я пытаюсь получить желаемые результаты.

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)

Полученное изображение путем вычитания двух изображений

введите описание изображения здесь

Вот проблема. Ожидаемый результат должен быть только автомобилем. Кроме того, если вы глубоко взглянете на два изображения, вы увидите, что они не совсем такие же, как и на самом деле, камера немного сдвинулась, так что фон немного потревожился. Мой вопрос в том, что с этими двумя изображениями я могу вычесть фон. Я не хочу использовать алгоритм grabCut или backgroundSubtractorMOG прямо сейчас, потому что сейчас я не знаю, что происходит внутри этих алгоритмов.

То, что я пытаюсь сделать, - получить следующий результирующий образ ввести описание изображения здесь

Также, если это возможно, пожалуйста, расскажите мне об общем способе делать это не только в этом конкретном случае, то есть у меня есть фон в одном изображении и фон + объект во втором изображении. Каким может быть наилучший способ сделать это. Извините за такой длинный вопрос.

Ответ 1

Я решил вашу проблему, используя алгоритм OpenCV waterhed. Здесь вы можете найти теорию и примеры водораздела .

Сначала я выбрал несколько точек (маркеров), чтобы определить, где находится объект, который я хочу сохранить, и где находится фон. Этот шаг является ручным и может сильно варьироваться от изображения к изображению. Кроме того, это требует некоторого повторения, пока вы не получите желаемый результат. Я предлагаю использовать инструмент для получения координат пикселей. Затем я создал пустой целый массив нулей с размером изображения автомобиля. И затем я назначил некоторые значения (1: фон, [255,192,128,64]: car_parts) в пикселях в положениях маркера.

ПРИМЕЧАНИЕ.. Когда я загрузил изображение, мне пришлось обрезать его, чтобы получить его с автомобилем. После обрезки изображение имеет размер 400x601. Возможно, это не тот размер изображения, который у вас есть, поэтому маркеры будут отключены.

Впоследствии я использовал алгоритм водораздела. Первый вход - ваше изображение, а второй вход - изображение маркера (ноль везде, кроме позиций маркера). Результат показан на рисунке ниже. после водораздела

Я устанавливаю все пиксели со значением больше 1 - 255 (автомобиль), а остальные (фон) равны нулю. Затем я расширил полученное изображение ядром 3x3, чтобы избежать потери информации о контуре автомобиля. Наконец, я использовал расширенное изображение в качестве маски для исходного изображения, используя функцию cv2.bitwise_and(), и результат заключается в следующем изображении: окончательное обрезанное изображение

Вот мой код:

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()

Если у вас много изображений, вам, вероятно, понадобится создать инструмент для графического обозначения маркеров или даже алгоритма автоматического поиска маркеров.

Ответ 2

Проблема заключается в том, что вы вычитаете массивы из unsigned 8-битных целых чисел. Эта операция может переполняться.

Чтобы продемонстрировать

>>> 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)

Ответ 3

Я рекомендую использовать алгоритм захвата OpenCV. Сначала вы рисуете несколько линий на переднем и заднем плане и продолжаете делать это до тех пор, пока ваш передний план не будет достаточно отделен от фона. Он описан здесь: https://docs.opencv.org/trunk/d8/d83/tutorial_py_grabcut.html, а также в этом видео: https://www.youtube.com/watch?v=kAwxLTDDAwU.