Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenCV remove background

Tags:

python

opencv

I am trying to remove the background of some images, tweaking some values and using some methods like morphologyEx gives me an aceptable result but some holes still remaining, in this last case, the holes doesn't fill even iterating on every contour and drawing it with -1. I can see that threshold image is really good, making the whole shape with lines, but I don't know how to continue...

Update I've changed my code so I get better results but I'm still getting some holes... If I could fill theese holes, the script would be perfect.

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 Original

enter image description here

1 Threshold

enter image description here

2 Holes

enter image description here

3 Final Image

enter image description here

like image 446
Robert W. Hunter Avatar asked Jun 30 '15 09:06

Robert W. Hunter


Video Answer


4 Answers

As I was tackling the same issue, and found a solution in Python (with opencv2), thought of just sharing this here as well. Hope it helps.

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()
like image 196
Gazmend Ajrulovski Avatar answered Oct 21 '22 08:10

Gazmend Ajrulovski


Iteratively perform a morphological closing of your holes image using a kernel of increasing size. But, before doing this I suggest you resize the holes image (using nearest-neighbor interpolation) so you don't have to use huge kernels. In the following code (C++), I resized the holes image to 25% of its original dimensions.

To reduce the effects on borders add a constant border of zeros using copyMakeBorder before you apply the iterative closing. As we are using 15 iterations here, make the border around the image larger than 15.

So the steps are

  • Resize the holes image
  • Add a zero border
  • Iteratively close the image with a kernel of increasing size
  • Remove the border
  • Now we have a small mask. Resize this mask to original image size

The code is in C++. I'm not very familiar with 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);

The output (not to original scale) looks fine except that it takes the background region at the bottom as foreground.

enter image description here

like image 35
dhanushka Avatar answered Oct 21 '22 06:10

dhanushka


@dhanushka's method works fine. Here's my pythonic version:

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

like image 24
Alexander Lutsenko Avatar answered Oct 21 '22 06:10

Alexander Lutsenko


@grep, according to a post by Alexander Lutsenko, for python 3.6.3, to make the code work, you need to add one more returned value to the findContours() as follows:

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)
like image 39
Supp. Avatar answered Oct 21 '22 06:10

Supp.