Python 如何从这种图像中删除背景?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29313667/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 04:23:11  来源:igfitidea点击:

How do I remove the background from this kind of image?

pythonopencvimage-processingscikit-image

提问by hans-t

Image_1

图片_1

I want to remove the background of this image to get the person only. I have thousand of images like this, basically, a person and a somewhat whitish background.

我想删除此图像的背景以仅获取此人。我有数千张这样的图像,基本上,一个人和一个有点发白的背景。

What I have done is to use edge detector like canny edge detector or sobel filter (from skimagelibrary). Then what I think possible to do is, whiten the pixels within the edges and blacken the pixels without. Afterwards, the original image can be mask to get the picture of the person only.

我所做的是使用像canny边缘检测器或sobel过滤器(来自skimage库)这样的边缘检测器。然后我认为可以做的是,将边缘内的像素变白,而将边缘内的像素变黑。之后,可以对原始图像进行蒙版以仅获取人的照片。

However, it's hard to get a closed boundary using canny edge detector. Result using Sobel filter is not that bad, however I don't how to proceed from there.

然而,使用canny边缘检测器很难得到一个封闭的边界。使用 Sobel 过滤器的结果还不错,但是我不知道如何从那里开始。

Sobel_result

Sobel_result

EDIT:

编辑:

Is it possible to also remove the background between the right hand and the skirt and between hairs?

是否也可以去除右手和裙子之间以及头发之间的背景?

采纳答案by jedwards

The following code should get you started. You may want to play around with the parameters at the top of the program to fine-tune your extraction:

以下代码应该可以帮助您入门。您可能希望使用程序顶部的参数来微调您的提取:

import cv2
import numpy as np

#== Parameters =======================================================================
BLUR = 21
CANNY_THRESH_1 = 10
CANNY_THRESH_2 = 200
MASK_DILATE_ITER = 10
MASK_ERODE_ITER = 10
MASK_COLOR = (0.0,0.0,1.0) # In BGR format


#== Processing =======================================================================

#-- Read image -----------------------------------------------------------------------
img = cv2.imread('C:/Temp/person.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#-- Edge detection -------------------------------------------------------------------
edges = cv2.Canny(gray, CANNY_THRESH_1, CANNY_THRESH_2)
edges = cv2.dilate(edges, None)
edges = cv2.erode(edges, None)

#-- Find contours in edges, sort by area ---------------------------------------------
contour_info = []
_, contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# Previously, for a previous version of cv2, this line was: 
#  contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
# Thanks to notes from commenters, I've updated the code but left this note
for c in contours:
    contour_info.append((
        c,
        cv2.isContourConvex(c),
        cv2.contourArea(c),
    ))
contour_info = sorted(contour_info, key=lambda c: c[2], reverse=True)
max_contour = contour_info[0]

#-- Create empty mask, draw filled polygon on it corresponding to largest contour ----
# Mask is black, polygon is white
mask = np.zeros(edges.shape)
cv2.fillConvexPoly(mask, max_contour[0], (255))

#-- Smooth mask, then blur it --------------------------------------------------------
mask = cv2.dilate(mask, None, iterations=MASK_DILATE_ITER)
mask = cv2.erode(mask, None, iterations=MASK_ERODE_ITER)
mask = cv2.GaussianBlur(mask, (BLUR, BLUR), 0)
mask_stack = np.dstack([mask]*3)    # Create 3-channel alpha mask

#-- Blend masked img into MASK_COLOR background --------------------------------------
mask_stack  = mask_stack.astype('float32') / 255.0          # Use float matrices, 
img         = img.astype('float32') / 255.0                 #  for easy blending

masked = (mask_stack * img) + ((1-mask_stack) * MASK_COLOR) # Blend
masked = (masked * 255).astype('uint8')                     # Convert back to 8-bit 

cv2.imshow('img', masked)                                   # Display
cv2.waitKey()

#cv2.imwrite('C:/Temp/person-masked.jpg', masked)           # Save

Ouput: enter image description here

输出: 在此处输入图片说明

回答by Eugene Lisitsky

If you wish to fill background not with a red color but make it transparent, you may add following lines to solution:

如果您希望不使用红色填充背景而是使其透明,您可以在解决方案中添加以下几行:

# split image into channels
c_red, c_green, c_blue = cv2.split(img)

# merge with mask got on one of a previous steps
img_a = cv2.merge((c_red, c_green, c_blue, mask.astype('float32') / 255.0))

# show on screen (optional in jupiter)
%matplotlib inline
plt.imshow(img_a)
plt.show()

# save to disk
cv2.imwrite('girl_1.png', img_a*255)

# or the same using plt
plt.imsave('girl_2.png', img_a)

If you wish you may tweak some png compression parameters to make file smaller.

如果您愿意,可以调整一些 png 压缩参数以使文件更小。

Image on a white background below. Or on a black one - http://imgur.com/a/4NwmH

下图为白色背景。或黑色 - http://imgur.com/a/4NwmH

enter image description here

在此处输入图片说明

回答by Andrey Smorodov

As an alternative, you can use neural networks like this one: CRFRNN.

作为替代方案,您可以使用这样的神经网络:CRFRNN

It gives the result like this:

它给出了这样的结果:

enter image description here

在此处输入图片说明

回答by Sneaky Polar Bear

  • After obtaining your incomplete edges (as you have), you can run a closing morphology (a sequence of dilate and erode) (will have to set size and iterations based on needs/state of edges).

  • Now assuming that you have a constant edge all the way around the subject, use any type of fill algorithm (blob) to combine all points outside the edged object, then take the negative of that to give you the mask of the inside of the object.

  • 获得不完整的边缘(如您所见)后,您可以运行闭合形态(一系列膨胀和侵蚀)(必须根据需要/边缘状态设置大小和迭代)。

  • 现在假设您在主体周围一直有一个恒定的边缘,使用任何类型的填充算法(blob)组合边缘对象外的所有点,然后取负数,为您提供对象内部的掩码.

回答by Tzvi Gregory Kaidanov

enter image description hereWorking example with vs2017.
Sets the red background but saves blue..
Also added the transperent example in.

在此处输入图片说明vs2017 的工作示例。
设置红色背景但保存蓝色..
还添加了透明示例。

How can I remove the girls body and leave only the dress in the picture? Any ideas?

如何去除女孩的身体,只留下照片中的裙子?有任何想法吗?

# == https://stackoverflow.com/questions/29313667/how-do-i-remove-the-background-from-this-kind-of-image

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

#== Parameters =======================================================================
BLUR = 21
CANNY_THRESH_1 = 10
CANNY_THRESH_2 = 200
MASK_DILATE_ITER = 10
MASK_ERODE_ITER = 10
MASK_COLOR = (0.0,0.0,1.0) # In BGR format


#== Processing =======================================================================

#-- Read image -----------------------------------------------------------------------
img = cv2.imread('img/SYxmp.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#-- Edge detection -------------------------------------------------------------------
edges = cv2.Canny(gray, CANNY_THRESH_1, CANNY_THRESH_2)
edges = cv2.dilate(edges, None)
edges = cv2.erode(edges, None)

#-- Find contours in edges, sort by area ---------------------------------------------
contour_info = []
_, contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
for c in contours:
    contour_info.append((
        c,
        cv2.isContourConvex(c),
        cv2.contourArea(c),
    ))
contour_info = sorted(contour_info, key=lambda c: c[2], reverse=True)
max_contour = contour_info[0]

#-- Create empty mask, draw filled polygon on it corresponding to largest contour ----
# Mask is black, polygon is white
mask = np.zeros(edges.shape)
cv2.fillConvexPoly(mask, max_contour[0], (255))



#-- Smooth mask, then blur it --------------------------------------------------------
mask = cv2.dilate(mask, None, iterations=MASK_DILATE_ITER)
mask = cv2.erode(mask, None, iterations=MASK_ERODE_ITER)
mask = cv2.GaussianBlur(mask, (BLUR, BLUR), 0)

mask_stack = np.dstack([mask]*3)    # Create 3-channel alpha mask

#-- Blend masked img into MASK_COLOR background --------------------------------------
mask_stack  = mask_stack.astype('float32') / 255.0          # Use float matrices, 
img         = img.astype('float32') / 255.0                 #  for easy blending

masked = (mask_stack * img) + ((1-mask_stack) * MASK_COLOR) # Blend
masked = (masked * 255).astype('uint8')                     # Convert back to 8-bit 

plt.imsave('img/girl_blue.png', masked)
# split image into channels
c_red, c_green, c_blue = cv2.split(img)

# merge with mask got on one of a previous steps
img_a = cv2.merge((c_red, c_green, c_blue, mask.astype('float32') / 255.0))

# show on screen (optional in jupiter)
#%matplotlib inline
plt.imshow(img_a)
plt.show()

# save to disk
cv2.imwrite('img/girl_1.png', img_a*255)

# or the same using plt
plt.imsave('img/girl_2.png', img_a)

cv2.imshow('img', masked)                                   # Displays red, saves blue

cv2.waitKey()

回答by Huy

According to @jedwards answer, when using with opencv4, you will have this error:

根据@jedwards 的回答,与 opencv4 一起使用时,会出现此错误:

Traceback (most recent call last):
  File "save.py", line 26, in <module>
    _, contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
ValueError: not enough values to unpack (expected 3, got 2)

The function cv2.findContours()has been changed to return only the contours and the hierarchy

该函数cv2.findContours()已更改为仅返回轮廓和层次结构

You should change to this:

你应该改成这样:

contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)