2

I am trying to remove the black background from an image using OpenCV, but I am unable to remove the pixels to capture just the main imagery without the black background. Here is the code I am using, along with the original input image.

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

img = cv2.imread('C:\\Users\\mdl518\\Desktop\\input.png')
mask = np.zeros(img.shape[:2],np.uint8)

bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)

rect = (0,0,1035,932)  # image width/height re-formatted as (x,y,width,height)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)

mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]

plt.imshow(img)
plt.savefig('C:\\Users\\mdl518\\Desktop\\output.png')

Input Image

I am essentially re-formatting the code outlined here (https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_grabcut/py_grabcut.html) that illustrates forefront extraction using OpenCV. However, I am still unable to crop the surrounding background pixels from the input image while preserving the integrity of the image itself in the output image. Is there an easier way to go about this? I also tried to crop/remove the background using cv2.thresholding and contours but still couldn't figure it out. Any assistance is most appreciated!

mdl518
  • 327
  • 3
  • 14
  • Are you trying to crop the image or make the black background transparent? If cropping to a rectangle, you would be cutting off considerable amount of data so not preserving your image. Please clarify what you want. Making the background transparent, should no be that hard. Threshold, clean up with morphology, get the outer contour and draw it as filled white on a black background. Then put that into the alpha channel of the image. – fmw42 Aug 21 '20 at 18:41
  • fmw42 - I am trying to crop/remove the black background altogether without creating a rectangle to lose data in the original image. I want to be able to open the image in a basic photo browser and not see the black background, so a clear/non-existent background is the goal. Thanks again for your help! :) – mdl518 Aug 22 '20 at 19:10

1 Answers1

3

Here is one approach to make your background transparent in Python/OpenCV.

  • Read the input
  • Convert to gray
  • Threshold
  • Apply morphology to clean extraneous spots
  • Get external contours
  • Find the largest contour
  • Draw the contour as white filled on a black background as a mask
  • Antialias the mask
  • Put that into the alpha channel of the input image
  • Save the result

Input:

enter image description here

import cv2
import numpy as np
import skimage.exposure

# load image
img = cv2.imread('aerial_image.jpg')

# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# threshold
thresh = cv2.threshold(gray, 11, 255, cv2.THRESH_BINARY)[1]

# apply morphology to clean small spots
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, borderType=cv2.BORDER_CONSTANT, borderValue=0)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel, borderType=cv2.BORDER_CONSTANT, borderValue=0)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(morph, cv2.MORPH_ERODE, kernel, borderType=cv2.BORDER_CONSTANT, borderValue=0)

# get external contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)

# draw white filled contour on black background as mas
contour = np.zeros_like(gray)
cv2.drawContours(contour, [big_contour], 0, 255, -1)

# blur dilate image
blur = cv2.GaussianBlur(contour, (5,5), sigmaX=0, sigmaY=0, borderType = cv2.BORDER_DEFAULT)

# stretch so that 255 -> 255 and 127.5 -> 0
mask = skimage.exposure.rescale_intensity(blur, in_range=(127.5,255), out_range=(0,255))

# put mask into alpha channel of input
result = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
result[:,:,3] = mask

# save output
cv2.imwrite('aerial_image_thresh.png', thresh)
cv2.imwrite('aerial_image_morph.png', morph)
cv2.imwrite('aerial_image_contour.png', contour)
cv2.imwrite('aerial_image_mask.png', mask)
cv2.imwrite('aerial_image_antialiased.png', result)


# Display various images to see the steps
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('contour', contour)
cv2.imshow('mask', mask)
cv2.imshow('result', result)

cv2.waitKey(0)
cv2.destroyAllWindows()

Threshold image:

enter image description here

Morphology cleaned image:

enter image description here

Contour image:

enter image description here

Mask image:

enter image description here

Result with transparent background:

enter image description here

fmw42
  • 46,825
  • 10
  • 62
  • 80
  • fmv42 - That is really awesome!! Thank you for showing the steps and logic to get the end result. I ran your script on my end, and it mostly works on my end but it still cuts out some of the pixels from the original image and replacing them with white pixels in their place. In your output image, you can also see a couple cut outs (white pixels) in the bottom right of the image. I'm assuming this is due to a threshold setting that can be tweaked, where there specific parameters you may recommend? Thanks again for your help, most appreciated!! – mdl518 Aug 23 '20 at 12:32
  • Yes cutout would be expected. Not sure you can do much about them without bringing some black in. But you could use morphology close with a round kernel just larger than the cut-outs. It would have been better if you could have reserved pure black in your image to the background region and made any black pixels in the data at value of gray 1. Was this a rectified image or processed in any way from a rectangular original? If so, replace black in the data in the original with 1, so that you can reserver pure black for the background after rectifying. – fmw42 Aug 23 '20 at 17:32
  • fmv42 - The image above is a point cloud image embedded within a document, so the image shown was generated from another and hence pre-processed. Your solution works perfectly on the corresponding EO image which is also in .png format but even for this image saved as a .png from within the source document I am still getting the cut-outs for some pixels. Have you tried similar techniques previously using contours/grabcut in OpenCV? The solution is nearly bullet-proof but I will keep troubleshooting on my end, thanks again! – mdl518 Aug 24 '20 at 21:16