Now after I deal with all this Python, OpenCV, Numpy thing for a while, I find out it's quite simple to implement this with code:
image[mask] = (0, 0, 255)
-------------- the original answer --------------
I solved this by the following code:
redImg = np.zeros(image.shape, image.dtype)
redImg[:,:] = (0, 0, 255)
redMask = cv2.bitwise_and(redImg, redImg, mask=mask)
cv2.addWeighted(redMask, 1, image, 1, 0, image)
Answer from Will Li on Stack OverflowNow after I deal with all this Python, OpenCV, Numpy thing for a while, I find out it's quite simple to implement this with code:
image[mask] = (0, 0, 255)
-------------- the original answer --------------
I solved this by the following code:
redImg = np.zeros(image.shape, image.dtype)
redImg[:,:] = (0, 0, 255)
redMask = cv2.bitwise_and(redImg, redImg, mask=mask)
cv2.addWeighted(redMask, 1, image, 1, 0, image)
The idea is to convert the mask to a binary format where pixels are either 0 (black) or 255 (white). White pixels represent sections that are kept while black sections are thrown away. Then set all white pixels on the mask to your desired BGR color.
Input image and mask
Result
Code
import cv2
image = cv2.imread('1.jpg')
mask = cv2.imread('mask.jpg', 0)
mask = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
image[mask==255] = (36,255,12)
cv2.imshow('image', image)
cv2.imshow('mask', mask)
cv2.waitKey()
python - OpenCV overlay 2 image based on image mask - Stack Overflow
opencv - How to overlay an image with another that has transparency using Python with OpenCv2 - Stack Overflow
How to use image masking to crop around a complex image? - Python - OpenCV
python - overlaying the ground truth mask on an image - Stack Overflow
Solved
def get_only_object(img, mask, back_img):
fg = cv2.bitwise_or(img, img, mask=mask)
#imshow(fg)
# invert mask
mask_inv = cv2.bitwise_not(mask)
#fg_back = cv2.bitwise_or(back_img, back_img, mask=mask)
fg_back_inv = cv2.bitwise_or(back_img, back_img, mask=mask_inv)
#imshow(fg_back_inv)
final = cv2.bitwise_or(fg, fg_back_inv)
#imshow(final)
return final
You need to convert the object image into an RGBA image where the alpha channel is the mask image you have created. Once you do this, you can paste it to the background image.
def convert_to_png(img, a):
#alpha and img must have the same dimenstons
fin_img = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
b, g, r, alpha = cv2.split(fin_img)
alpha = a
# plt.imshow(alpha);plt.title('alpha image');plt.show()
# plt.imshow(img);plt.title('original image');plt.show()
# plt.imshow(alpha);plt.title('fin alpha image');plt.show()
fin_img[:,:, 0] = img[:,:,0]
fin_img[:,:, 1] = img[:,:,1]
fin_img[:,:, 2] = img[:,:,2]
fin_img[:,:, 3] = alpha[:,:]
# plt.imshow(fin_img);plt.title('fin image');plt.show()
return fin_img
This function will combine the two images into an RGBA image.
y1, y2 = new_loc[1], new_loc[1] + img.shape[0]
x1, x2 = new_loc[0], new_loc[0] + img.shape[1]
alpha_s = img[:, :, 3] / 255.0
alpha_l = 1.0 - alpha_s
for c in range(0, 3):
fin_img[y1:y2, x1:x2, c] = (alpha_s * img[:, :, c] +
alpha_l * img[y1:y2, x1:x2, c])
And this will copy the Object image to the background image
I need to do similar things pretty often. In my favorite StackOverflow fashion, here is a script that you can copy and paste. I hope the code itself is self-explanatory. There are a few things that you can tune and try (e.g., color maps, overlay styles). It uses multiprocessing.Pool for faster batch-processing, resizes the mask to match the shape of the image, assumes the mask is in .png format, and depends on the file structure that you posted.
import os
from os import path
import cv2
import numpy as np
from argparse import ArgumentParser
from multiprocessing import Pool
def create_overlay(image, mask):
"""
image: H*W*3 numpy array
mask: H*W numpy array
If dimensions do not match, the mask is upsampled to match that of the image
Returns a H*W*3 numpy array
"""
h, w = image.shape[:2]
mask = cv2.resize(mask, dsize=(w,h), interpolation=cv2.INTER_CUBIC)
# color options: https://docs.opencv.org/4.x/d3/d50/group__imgproc__colormap.html
mask_color = cv2.applyColorMap(mask, cv2.COLORMAP_HOT).astype(np.float32)
mask = mask[:, :, None] # create trailing dimension for broadcasting
mask = mask.astype(np.float32)/255
# different other options that you can use to merge image/mask
overlay = (image*(1-mask)+mask_color*mask).astype(np.uint8)
# overlay = (image*0.5 + mask_color*0.5).astype(np.uint8)
# overlay = (image + mask_color).clip(0,255).astype(np.uint8)
return overlay
def process_video(video_name):
"""
Processing frames in a single video
"""
vid_image_path = path.join(image_path, video_name)
vid_mask_path = path.join(mask_path, video_name)
vid_output_path = path.join(output_path, video_name)
os.makedirs(vid_output_path, exist_ok=True)
frames = sorted(os.listdir(vid_image_path))
for f in frames:
image = cv2.imread(path.join(vid_image_path, f))
mask = cv2.imread(path.join(vid_mask_path, f.replace('.jpg','.png')), cv2.IMREAD_GRAYSCALE)
overlay = create_overlay(image, mask)
cv2.imwrite(path.join(vid_output_path, f), overlay)
parser = ArgumentParser()
parser.add_argument('--image_path')
parser.add_argument('--mask_path')
parser.add_argument('--output_path')
args = parser.parse_args()
image_path = args.image_path
mask_path = args.mask_path
output_path = args.output_path
if __name__ == '__main__':
videos = sorted(
list(set(os.listdir(image_path)).intersection(
set(os.listdir(mask_path))))
)
print(f'Processing {len(videos)} videos.')
pool = Pool()
pool.map(process_video, videos)
print('Done.')
Output:

EDIT: Made it work on Windows; changed pool.apply to pool.map.
This is not much different from @hkchengrex solution, so he deserves the credit, since his answer was first. I mainly wanted to point out the use of cv2.addWeighted
Here is one way to blend the image and ground truth in Python/OpenCV.
I would suggest resizing the ground truth once to the size of the images for all your video frames rather than resizing every video frame to the size of the ground truth.
One simple resizes the ground truth to the size of the image. Then colorize the ground truth using a color map. Then simply use cv2.addWeighted to blend the two for every frame of your video.
I leave it to you to read your video to access each frame. The following simply shows how to process any given frame
Input:

Ground Truth Overlay:

import cv2
import numpy as np
# read image
img = cv2.imread('bullfight.png')
hh, ww = img.shape[:2]
# read ground truth overlay
overlay = cv2.imread('truth.png')
# resize the overlay to match the size of the image
over_resize = cv2.resize(overlay, (ww,hh), fx=0, fy=0, interpolation=cv2.INTER_CUBIC)
# colorize the over_resized image
over_color = cv2.applyColorMap(over_resize, cv2.COLORMAP_HOT)
# blend over_color and image (adjust weights for different effects)
result = cv2.addWeighted(img, 1, over_color, 1, 0)
# save output image
cv2.imwrite('bullfight_overlay.png', result)
# display images
cv2.imshow('overcolor', over_color)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

