OpenCV去除背景

11
我正在尝试去除一些图像的背景,调整一些值并使用一些方法如morphologyEx给我一个可接受的结果,但仍有一些洞留下来。在最后这种情况下,即使迭代每个轮廓并使用-1进行绘制,洞也不会填补。我可以看到阈值图像非常好,用线条制成整个形状,但我不知道该如何继续...
更新 我已经修改了代码,以获得更好的结果,但仍然存在一些洞...如果我能填补这些洞,脚本将是完美的。
def get_contrasted(image, type="dark", level=3):
    maxIntensity = 255.0 # depends on dtype of image data
    phi = 1
    theta = 1

    if type == "light":
        newImage0 = (maxIntensity/phi)*(image/(maxIntensity/theta))**0.5
        newImage0 = array(newImage0,dtype=uint8)
        return newImage0
    elif type == "dark":
        newImage1 = (maxIntensity/phi)*(image/(maxIntensity/theta))**level
        newImage1 = array(newImage1,dtype=uint8)

        return newImage1

def sharp(image, level=3):
    f = cv2.GaussianBlur(image, (level,level), level)
    f = cv2.addWeighted(image, 1.5, f, -0.5, 0)
    return f

original_image = imread('imagen.jpg')
# 1 Convert to gray & Normalize
gray_img = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
gray_img = sharp(get_contrasted(gray_img))
gray_img = normalize(gray_img, None, 0, 255, NORM_MINMAX, CV_8UC1)
imshow("Gray", gray_img)

# 2 Find Threshold
gray_blur = cv2.GaussianBlur(gray_img, (7, 7), 0)
adapt_thresh_im = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 1)
max_thresh, thresh_im = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
thresh = cv2.bitwise_or(adapt_thresh_im, thresh_im)

# 3 Dilate
gray = cv2.Canny(thresh, 88, 400, apertureSize=3)
gray = cv2.dilate(gray, None, iterations=8)
gray = cv2.erode(gray, None, iterations=8)
imshow("Trheshold", gray)

# 4 Flood
contours, _ = cv2.findContours(gray, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contour_info = []
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]
holes = np.zeros(gray_img.shape, np.uint8)
drawContours(holes, max_contour, 0, 255, -1)
imshow("Holes", holes)

mask = cv2.GaussianBlur(holes, (15, 15), 0)
mask = np.dstack([mask] * 3)  # Create 3-channel alpha mask

mask = mask.astype('float32') / 255.0  # Use float matrices,
img = original_image.astype('float32') / 255.0  # for easy blending
masked = (mask * img) + ((1 - mask) * (0,0,1))  # Blend
masked = (masked * 255).astype('uint8')

imshow("Maked", masked)
waitKey()

0 原始图片

图片描述

1 阈值处理

图片描述

2 孔洞填充

图片描述

3 最终图像

图片描述


你的代码不是有效的Python代码。请发布没有语法错误的代码(即能够运行的代码)。 - boardrider
缺少括号,已更新。 - Robert W. Hunter
我已经阅读了一些教程,还有那个stackoverflow的问题,但是我无法让它工作。例如,一些教程是关于C++的,而我正在使用Python,我不知道如何在Python中实现这个,例如Mat(hsvImg.rows, hsvImg.cols, CV_8UC1, 200)和其他东西... - Robert W. Hunter
@RobertW.Hunter 然后看一下这个 - bad_keypoints
也看过那个,但它是用于与平均或“模型”图像一起使用的,以减去不在该“模型”上的任何内容,因此对于每个帧都具有相同“模型”图像然后某些东西移动的视频可以完美地去除该“移动”物体,但对于静态图像则无法告诉OpenCV什么是平均值。 - Robert W. Hunter
显示剩余4条评论
5个回答

11

我在解决同样的问题时,用 Python (带有 opencv2) 找到了一个解决方案。我想在这里分享一下,希望它能有所帮助。

import numpy as np
import cv2

cv2.namedWindow('image', cv2.WINDOW_NORMAL)

#Load the Image
imgo = cv2.imread('koAl2.jpg')
height, width = imgo.shape[:2]

#Create a mask holder
mask = np.zeros(imgo.shape[:2],np.uint8)

#Grab Cut the object
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)

#Hard Coding the Rect The object must lie within this rect.
rect = (10,10,width-30,height-30)
cv2.grabCut(imgo,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img1 = imgo*mask[:,:,np.newaxis]

#Get the background
background = imgo - img1

#Change all pixels in the background that are not black to white
background[np.where((background > [0,0,0]).all(axis = 2))] = [255,255,255]

#Add the background and the image
final = background + img1

#To be done - Smoothening the edges

cv2.imshow('image', final )

k = cv2.waitKey(0)

if k==27:
    cv2.destroyAllWindows()

9

使用逐步增加的核对孔洞图像进行形态闭合。但在此之前,建议您对孔洞图像进行调整大小(使用最近邻插值),以便不必使用巨大的核。在下面的C ++代码中,我将孔洞图像调整为其原始尺寸的25%。

为了减少边界的影响,在应用迭代关闭之前,请使用copyMakeBorder添加零常数边框。由于我们在此处使用15个迭代,因此使图像周围的边框比15大。

所以步骤是

  • 调整孔洞图像的大小
  • 添加零边框
  • 使用逐步增加的核来迭代地关闭图像
  • 删除边框
  • 现在我们有一个小的掩模。将此掩模调整为原始图像大小

该代码是用C ++编写的。我对Python不太熟悉。

    // read the image and the holes
    Mat im = imread("koAl2.jpg");
    Mat holes = imread("GuICX.jpg", 0);
    // resize
    Mat small, bordered;
    resize(holes, small, Size(), .25, .25);
    // add a zero border
    int b = 20;
    copyMakeBorder(small, bordered, b, b, b, b, BORDER_CONSTANT, Scalar(0));
    // close
    for (int i = 1; i < 15; i++)
    {
        Mat kernel = getStructuringElement(MORPH_ELLIPSE, cv::Size(2*i+1, 2*i+1));
        morphologyEx(bordered, bordered, MORPH_CLOSE, kernel, Point(-1, -1), 1);
    }
    // remove border
    Mat mask = bordered(Rect(b, b, small.cols, small.rows));
    // resize the mask
    Mat largeMask;
    resize(mask, largeMask, Size(im.cols, im.rows));
    // the foreground
    Mat fg;
    im.copyTo(fg, largeMask);

输出结果(不按照原比例)看起来很好,只是将底部的背景区域视为前景。

1
我无法在Python上运行它,有两件事情我无法转换为Python:1:bordered 变成了一个方法?什么时候变的? bordered(Rect(b, b, small.cols, small.rows)) 而且 Rect 是什么?2:resize(holes, small, Size(), .25, .25) 我没有一个 Size() 对象,我想这是一个元组?但我不能使用空元组 ()3:morphologyEx(bordered, bordered, MORPH_CLOSE, kernel, Point(-1, -1), 1) 在Python中变成了 bordered = cv2.morphologyEx(bordered, cv2.MORPH_CLOSE, kernel, (-1, -1), 1) 但出现错误:new style getargs format but argument is not a tuple - Robert W. Hunter
Mat mask = bordered(Rect(b, b, small.cols, small.rows)); 将指定的感兴趣区域提取到 mask 中。这是 Mat 类的一个运算符。如果你专注于我给出的描述,而不是尝试将 C++ 代码一一映射到 Python 上,你就能让它正常工作。我添加了代码,希望它能让描述更清晰明了。 - dhanushka

4

@dhanushka的方法很好用。这是我的Python版本:

def get_holes(image, thresh):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

    im_bw = cv.threshold(gray, thresh, 255, cv.THRESH_BINARY)[1]
    im_bw_inv = cv.bitwise_not(im_bw)

    contour, _ = cv.findContours(im_bw_inv, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE)
    for cnt in contour:
        cv.drawContours(im_bw_inv, [cnt], 0, 255, -1)

    nt = cv.bitwise_not(im_bw)
    im_bw_inv = cv.bitwise_or(im_bw_inv, nt)
    return im_bw_inv


def remove_background(image, thresh, scale_factor=.25, kernel_range=range(1, 15), border=None):
    border = border or kernel_range[-1]

    holes = get_holes(image, thresh)
    small = cv.resize(holes, None, fx=scale_factor, fy=scale_factor)
    bordered = cv.copyMakeBorder(small, border, border, border, border, cv.BORDER_CONSTANT)

    for i in kernel_range:
        kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (2*i+1, 2*i+1))
        bordered = cv.morphologyEx(bordered, cv.MORPH_CLOSE, kernel)

    unbordered = bordered[border: -border, border: -border]
    mask = cv.resize(unbordered, (image.shape[1], image.shape[0]))
    fg = cv.bitwise_and(image, image, mask=mask)
    return fg


img = cv.imread('koAl2.jpg')
nb_img = remove_background(img, 230)

enter image description here


Python的版本是多少?如何导入cv? - grep
1
在 get_holes 函数的第 11 行, contour,_ = cv2.findContours(im_bw_inv, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) 出现了 ValueError: too many values to unpack 的错误。 - grep

2

@grep,根据Alexander Lutsenko的一篇帖子,在Python 3.6.3中,为使代码运行,您需要按以下方式向findContours()添加一个更多的返回值:

contour, _ = cv.findContours(im_bw_inv, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE)

to

_, contour, _ = cv.findContours(im_bw_inv, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE)

0

尝试在C++中使用形态学操作进行膨胀和侵蚀,以去除孔洞。

Mat erodeElement = getStructuringElement(MORPH_RECT, Size(4, 4));
morphologyEx(thresh, thresh, MORPH_CLOSE ,erodeElement);
morphologyEx(thresh, thresh, MORPH_OPEN, erodeElement);
morphologyEx(thresh, thresh, MORPH_CLOSE, erodeElement);
morphologyEx(thresh, thresh, MORPH_OPEN, erodeElement);
morphologyEx(thresh, thresh, MORPH_OPEN, erodeElement);

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