如何将图像中的人体与背景分开

How to separate human body from background in an image

我一直在尝试将图像中的人体与背景分开,但是我所见过的所有方法似乎都不适合我。

我收集了以下图片;

现在我想把背景中的人物剪掉。 我尝试使用 res = cv2.subtract(background, foreground) 从图像中减去背景图像(我是图像处理的新手)。

opencv 中的背景减法方法,如 cv2.BackgroundSubtractorMOG2()cv2.BackgroundSubtractorMOG2() 仅适用于视频或图像序列,而我看到的轮廓检测方法仅适用于实体形状。

grabCut 不太适合我,因为我想自动化该过程。

鉴于我有图像(背景图像和背景图像以及其中的人),有没有一种方法可以将人从背景中剪掉?

由于很容易找到包含大量人体的数据集,我建议您实现神经网络分割技术以完美提取人体。请检查 this link 以查看类似示例。

我不推荐神经网络来解决这个问题。对于您具有已知背景的这样的事情,这是很多工作。我将介绍我对这张图片进行背景分割所采取的步骤。

首先,我切换到 LAB 颜色 space 以获得一些耐光通道。我做了一个简单的前景和背景减法,并合并了 a 和 b 通道。

你可以看到,即使使用光敏感度较低的颜色通道,背景中仍然有明显的颜色变化。这可能是由于相机上的自动白平衡,当您进入视野时,您会看到一些背景颜色发生变化。

我采取的下一步是对这张图片进行阈值处理。最佳阈值可能并不总是相同,您必须调整到适合您的照片集的范围。

我使用 openCV 的 findContours 函数来获取每个 blob 的分割点,并按大小过滤可用的轮廓。我将大小阈值设置为 15000。供参考,图像中的人的像素面积为 27551。

那么只需要裁剪轮廓即可。

此技术适用于任何良好的阈值策略。如果您可以通过关闭自动设置来提高图片的一致性,并确保人与墙的对比度良好,那么您可以使用更简单的阈值策略并获得良好的结果。

纯属娱乐:

编辑:

我忘记在我使用的代码中添加:

import cv2
import numpy as np

# rescale values
def rescale(img, orig, new):
    img = np.divide(img, orig);
    img = np.multiply(img, new);
    img = img.astype(np.uint8);
    return img;

# get abs(diff) of all hue values
def diff(bg, fg):
    # do both sides
    lh = bg - fg;
    rh = fg - bg;

    # pick minimum # this works because of uint wrapping
    low = np.minimum(lh, rh);
    return low;

# load image
bg = cv2.imread("back.jpg");
fg = cv2.imread("person.jpg");
fg_original = fg.copy();

# blur
bg = cv2.blur(bg,(5,5));
fg = cv2.blur(fg,(5,5));

# convert to lab
bg_lab = cv2.cvtColor(bg, cv2.COLOR_BGR2LAB);
fg_lab = cv2.cvtColor(fg, cv2.COLOR_BGR2LAB);
bl, ba, bb = cv2.split(bg_lab);
fl, fa, fb = cv2.split(fg_lab);

# subtract
d_b = diff(bb, fb);
d_a = diff(ba, fa);

# rescale for contrast
d_b = rescale(d_b, np.max(d_b), 255);
d_a = rescale(d_a, np.max(d_a), 255);

# combine
combined = np.maximum(d_b, d_a);

# threshold 
# check your threshold range, this will work for
# this image, but may not work for others
# in general: having a strong contrast with the wall makes this easier
thresh = cv2.inRange(combined, 70, 255);

# opening and closing
kernel = np.ones((3,3), np.uint8);

# closing
thresh = cv2.dilate(thresh, kernel, iterations = 2);
thresh = cv2.erode(thresh, kernel, iterations = 2);

# opening
thresh = cv2.erode(thresh, kernel, iterations = 2);
thresh = cv2.dilate(thresh, kernel, iterations = 3);

# contours
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);

# filter contours by size
big_cntrs = [];
marked = fg_original.copy();
for contour in contours:
    area = cv2.contourArea(contour);
    if area > 15000:
        print(area);
        big_cntrs.append(contour);
cv2.drawContours(marked, big_cntrs, -1, (0, 255, 0), 3);

# create a mask of the contoured image
mask = np.zeros_like(fb);
mask = cv2.drawContours(mask, big_cntrs, -1, 255, -1);

# erode mask slightly (boundary pixels on wall get color shifted)
mask = cv2.erode(mask, kernel, iterations = 1);

# crop out
out = np.zeros_like(fg_original) # Extract out the object and place into output image
out[mask == 255] = fg_original[mask == 255];

# show
cv2.imshow("combined", combined);
cv2.imshow("thresh", thresh);
cv2.imshow("marked", marked);
# cv2.imshow("masked", mask);
cv2.imshow("out", out);
cv2.waitKey(0);