使用OpenCV Python去除图像背景

21

我有两张图片,一张只有背景,另一张则是背景加上可识别的物体(在我的情况下是一辆车)。以下是这些图片:

在此输入图片描述

我正在尝试去除背景,以便最终图像只显示汽车。以下是我尝试实现所需结果的代码:

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)

通过减去两张图片得到的图像是:

enter image description here

问题在于,期望得到的图像应该只有汽车。 此外,如果你仔细观察这两张图片,你会发现它们并不完全相同,也就是说,相机移动了一点,背景受到了一些干扰。我的问题是,如何用这两张图片减去背景。我现在不想使用grabCut或backgroundSubtractorMOG算法,因为我现在还不知道这些算法的具体情况。

我想做的是得到以下的结果图像:enter image description here

另外,如果可能的话,请指导我一般的方法,不仅适用于这种特定情况,即在一张图片中有背景,在第二张图片中有背景+物体。最好的方式是什么。抱歉问题这么长。


图片在像素级别上完全相同吗?尝试使用类似于“foreground[foreground > 20] = 255”的阈值,看看是否可以改善您的结果。 - MB-F
你能详细说明“它不起作用”吗?你具体遇到了什么问题? - ZdaR
1
我的猜测是你的减法运算并没有仅仅产生汽车,而是一些奇怪的汽车和背景的组合。对吗?如果是这样,你需要一个掩膜来应用到你的原始图像上。 - Peter Brittain
有一个类似的任务 - 追踪轮廓并去除背景 - https://dev59.com/g10b5IYBdhLWcg3wVv-9#41048793 如果您有没有物体的背景图像,您可以获得更好的结果:尝试使用去除背景清洁器,然后进行轮廓剪辑以使其更好。 - Eugene Lisitsky
你找到了好的解决方案吗? - Chogg
显示剩余5条评论
3个回答

24
我使用了OpenCV的watershed算法来解决您的问题。您可以在这里找到分水岭算法的理论和示例。
首先,我选择了几个点(标记)来指示我想要保留的对象和背景的位置。这一步是手动完成的,因此会因图像而异。此外,需要重复几次才能获得所需的结果。建议使用工具获取像素坐标。 然后,我创建了一个大小与汽车图像相同的空整数数组,并将一些值(1:背景,[255,192,128,64]:汽车部件)分配给标记位置处的像素。
注意:当我下载您的图像时,我不得不裁剪它以获得带有汽车的图像。裁剪后,图像的尺寸为400x601。这可能与您拥有的图像尺寸不同,因此标记会偏移。
之后,我使用了分水岭算法。第一个输入是您的图像,第二个输入是标记图像(除标记位置外处处为零)。结果显示在下面的图片中。 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()

如果你有很多图片,可能需要创建一个工具来以图形方式注释标记,或者甚至需要编写一个自动查找标记的算法。

这是一个很棒的结果。你会使用什么样的算法来自动查找标记?任何探究的途径都会有所帮助。干杯。 - Chogg
1
最简单的方法是创建一个注释工具(GUI),您可以在其中单击要捕获的图像部分。如果您有很多但相似的图像,则可以使用相同的标记点,然后纠正潜在的偏移量。完全自动化的系统需要对您要查找的内容有一定的了解,因此我会选择使用ML算法,它可以在给定一个词(即汽车)的情况下尝试给出图片中对象的标记。也许DL超过所有其他ML选项,因为您需要许多步骤才能实现这一点(例如图像分类,标记识别等)。 - Glrs
@TasosGlrs,这是一个非常聪明的解决方案。干得好。 - lucians
我该如何选择标记?请给出关于此的指示。 - Tran Nhut Le
Tasos,我正在尝试在Android上做同样的事情,你能帮我一下吗?谢谢。https://stackoverflow.com/questions/62008889/how-to-convert-a-targeting-code-in-python-to-kotlin - Tecnologia da Net

9
问题在于你正在减去无符号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)

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接