Reputation: 543
I'm trying to make the background transparent in the following image. See images below.
Using the Opencv and matplotlib, I was able to achieve this.
import cv2
import numpy as np
from matplotlib import pyplot as plt
#== Parameters =======================================================================
BLUR = 21
CANNY_THRESH_1 = 10
CANNY_THRESH_2 = 200
MASK_DILATE_ITER = 10
MASK_ERODE_ITER = 10
MASK_COLOR = (0.0,0.0,1.0) # In BGR format
#== Processing =======================================================================
#-- Read image -----------------------------------------------------------------------
img = cv2.imread('/home/hasher/Documents/30302649.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#-- Edge detection -------------------------------------------------------------------
edges = cv2.Canny(gray, CANNY_THRESH_1, CANNY_THRESH_2)
edges = cv2.dilate(edges, None)
edges = cv2.erode(edges, None)
#-- Find contours in edges, sort by area ---------------------------------------------
contour_info = []
_, contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
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]
#-- Create empty mask, draw filled polygon on it corresponding to largest contour ----
# Mask is black, polygon is white
mask = np.zeros(edges.shape)
cv2.fillConvexPoly(mask, max_contour[0], (255))
#-- Smooth mask, then blur it --------------------------------------------------------
mask = cv2.dilate(mask, None, iterations=MASK_DILATE_ITER)
mask = cv2.erode(mask, None, iterations=MASK_ERODE_ITER)
mask = cv2.GaussianBlur(mask, (BLUR, BLUR), 0)
mask_stack = np.dstack([mask]*3) # Create 3-channel alpha mask
#-- Blend masked img into MASK_COLOR background --------------------------------------
mask_stack = mask_stack.astype('float32') / 255.0 # Use float matrices,
img = img.astype('float32') / 255.0 # for easy blending
masked = (mask_stack * img) + ((1-mask_stack) * MASK_COLOR) # Blend
masked = (masked * 255).astype('uint8') # Convert back to 8-bit
# plt.imsave('/home/hasher/Documents/girl_blue.png', masked)
# split image into channels
c_red, c_green, c_blue = cv2.split(img)
# merge with mask got on one of a previous steps
img_a = cv2.merge((c_blue, c_green, c_red, mask.astype('float32') / 255.0))
# show on screen (optional in jupiter)
#%matplotlib inline
plt.imshow(img_a)
plt.show()
# save to disk
# cv2.imwrite('/home/hasher/Documents/girl_1.png', img_a*255)
# or the same using plt
plt.imsave('/home/hasher/Documents/transparent.png', img_a)
# cv2.imshow('img', masked) # Displays red, saves blue
cv2.waitKey()
I was able to convert image to After conversion (See image). But there are minor issues in code. There is some extra details in borders of converted image. I'm not able to figure out. Any help is appreciated.
Samples before conversion. sample-1 sample-2 sample-3 sample-4
Upvotes: 0
Views: 2114
Reputation: 18341
Task: Convert
JPEGs with specific color background
intotransparent PNGs
.(1) JPEGs
(2) For these jpegs, convert them into HSV and split channels. Then We can seperate the target in the V channel because the background is most different with other channels.
(3) Threshold the V channel and do morph-op, then we can get a alpha mask and the png.
The code:
import cv2
import numpy as np
fname = "alpha.jpg"
img = cv2.imread(fname)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
v = hsv[:,:,2]
th, threshed = cv2.threshold(v, 100, 255, cv2.THRESH_OTSU|cv2.THRESH_BINARY_INV)
threshed[-1] = 255
cnts = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2]
mask = np.zeros_like(threshed)
cv2.drawContours(mask, cnts, -1, (255, 0, 0), -1, cv2.LINE_AA)
mask = cv2.erode(mask, np.ones((3,3), np.int32), iterations=1)
png = np.dstack((img, mask))
cv2.imwrite("alpha.png", png)
Upvotes: 1