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
How do I remove the background from this kind of image?
提问by hans-t
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 skimage
library). 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 过滤器的结果还不错,但是我不知道如何从那里开始。
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:
输出:
回答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
回答by Andrey Smorodov
回答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
Working 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)