Python OpenCV: Crop image to contents, and make background transparent
Question:
I have the following image:
I want to crop the image to the actual contents, and then make the background (the white space behind) transparent. I have seen the following question: How to crop image based on contents (Python & OpenCV)?, and after looking at the answer, and trying it, I got the following code:
img = cv.imread("tmp/"+img+".png")
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (55,55,110,110)
cv.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()
But when I try this code, I get the following result:
Which isn’t really the result I’m searching for, expected result:
Answers:
Here is one way to do that in Python/OpenCV.
As I mentioned in my comment, your provided image has a white circle around the cow and then a transparent background. I have made the background fully white as my input.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('cow.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# invert gray image
gray = 255 - gray
# threshold
thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY)[1]
# apply close and open morphology to fill tiny black and white holes and save as mask
kernel = np.ones((3,3), np.uint8)
mask = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
# get contours (presumably just one around the nonzero pixels)
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
cntr = contours[0]
x,y,w,h = cv2.boundingRect(cntr)
# make background transparent by placing the mask into the alpha channel
new_img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
new_img[:, :, 3] = mask
# then crop it to bounding rectangle
crop = new_img[y:y+h, x:x+w]
# save cropped image
cv2.imwrite('cow_thresh.png',thresh)
cv2.imwrite('cow_mask.png',mask)
cv2.imwrite('cow_transparent_cropped.png',crop)
# show the images
cv2.imshow("THRESH", thresh)
cv2.imshow("MASK", mask)
cv2.imshow("CROP", crop)
cv2.waitKey(0)
cv2.destroyAllWindows()
Thresholded image:
Mask:
Cropped result with transparent background:
Given that the background to be converted to transparent has its BGR channels white (like in your image), you can do:
import cv2
import numpy as np
img = cv2.imread("cat.png")
img[np.where(np.all(img == 255, -1))] = 0
img_transparent = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
img_transparent[np.where(np.all(img == 0, -1))] = 0
cv2.imshow("transparent.png", img_transparent)
Input image:
Output image:
We can tell the second image is transparent by clicking on it; the transparent background will show up a grey (in Firefox, at least).
What worked to me is:
original_image = cv2.imread(path)
#Converting the bgr image to an image with the alpha channel
original_image = cv2.cvtColor(original_image, cv2.BGR2BGRA)
#Transforming every alpha pixel to a transparent pixel.
original_image[np.where(np.all(original_image == 255, -1))] = 0
And then writing the image.
I have the following image:
I want to crop the image to the actual contents, and then make the background (the white space behind) transparent. I have seen the following question: How to crop image based on contents (Python & OpenCV)?, and after looking at the answer, and trying it, I got the following code:
img = cv.imread("tmp/"+img+".png")
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (55,55,110,110)
cv.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()
But when I try this code, I get the following result:
Which isn’t really the result I’m searching for, expected result:
Here is one way to do that in Python/OpenCV.
As I mentioned in my comment, your provided image has a white circle around the cow and then a transparent background. I have made the background fully white as my input.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('cow.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# invert gray image
gray = 255 - gray
# threshold
thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY)[1]
# apply close and open morphology to fill tiny black and white holes and save as mask
kernel = np.ones((3,3), np.uint8)
mask = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
# get contours (presumably just one around the nonzero pixels)
contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
cntr = contours[0]
x,y,w,h = cv2.boundingRect(cntr)
# make background transparent by placing the mask into the alpha channel
new_img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
new_img[:, :, 3] = mask
# then crop it to bounding rectangle
crop = new_img[y:y+h, x:x+w]
# save cropped image
cv2.imwrite('cow_thresh.png',thresh)
cv2.imwrite('cow_mask.png',mask)
cv2.imwrite('cow_transparent_cropped.png',crop)
# show the images
cv2.imshow("THRESH", thresh)
cv2.imshow("MASK", mask)
cv2.imshow("CROP", crop)
cv2.waitKey(0)
cv2.destroyAllWindows()
Thresholded image:
Mask:
Cropped result with transparent background:
Given that the background to be converted to transparent has its BGR channels white (like in your image), you can do:
import cv2
import numpy as np
img = cv2.imread("cat.png")
img[np.where(np.all(img == 255, -1))] = 0
img_transparent = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
img_transparent[np.where(np.all(img == 0, -1))] = 0
cv2.imshow("transparent.png", img_transparent)
Input image:
Output image:
We can tell the second image is transparent by clicking on it; the transparent background will show up a grey (in Firefox, at least).
What worked to me is:
original_image = cv2.imread(path)
#Converting the bgr image to an image with the alpha channel
original_image = cv2.cvtColor(original_image, cv2.BGR2BGRA)
#Transforming every alpha pixel to a transparent pixel.
original_image[np.where(np.all(original_image == 255, -1))] = 0
And then writing the image.