import numpy as np
import cv2
import matplotlib.pyplot as plt
from latex import latexify
= 2)
latexify(columns %matplotlib inline
%config InlineBackend.figure_format = 'retina'
Helper Function for Plotting
def show_image_grid(images, M, N, title='Title', figsize=8):
# Assuming 'images' is a numpy array of shape (num_images, height, width, channels)
if M==1:
= figsize
row_size = figsize//4
col_size elif N==1:
= figsize//4
row_size = figsize
col_size else:
= figsize, figsize
row_size, col_size
= plt.subplots(M, N, figsize=(row_size, col_size))
fig, axes
if len(images.shape) < 4:
= np.expand_dims(images.copy(), axis=0)
images
fig.suptitle(title)for i in range(M):
for j in range(N):
if M==1 and N==1:
= axes
ax elif M == 1 or N==1:
= axes[max(i, j)]
ax else:
= axes[i, j]
ax = i * N + j
index if index < images.shape[0]:
ax.imshow(cv2.cvtColor(images[index], cv2.COLOR_BGR2RGB))'off')
ax.axis(
plt.tight_layout() plt.show()
Feathered Mask
def get_feathered_mask(image, mid, feather):
"""
image (np.ndarray): The image to be masked
mid : Defines the midline of the mask
feather : Defines the feather width of the mask - region where there is a smooth transition from 0 to 255
"""
= image.shape[:2]
height, width
= int(width / mid)
midline = int(width / feather)
feather_width
= np.zeros((height, width), dtype=np.uint8)
mask - feather_width // 2] = 255
mask[:, :midline
for i in range(midline - feather_width // 2, midline + feather_width // 2):
= (midline + feather_width // 2 - i) / feather_width
feather_factor = np.clip(255 * feather_factor, 0, 255)
mask[:, i]
return mask
= cv2.imread('./images/orange.png', 1)
orange = cv2.imread('./images/apple.png', 1)
apple
# Feathered Mask
= get_feathered_mask(orange, 2, 10)
mask print(orange.shape, apple.shape, mask.shape)
(448, 624, 3) (448, 624, 3) (448, 624)
=(5, 5))
plt.figure(figsize"Mask", fontsize = 14)
plt.title(
plt.imshow(cv2.cvtColor(mask, cv2.COLOR_BGR2RGB))"off")
plt.axis( plt.show()
= plt.subplots(1, 2, figsize=(10, 4))
fig, axes 'Images to Blend', fontsize = 16)
fig.suptitle(0].imshow(cv2.cvtColor(orange, cv2.COLOR_BGR2RGB))
axes[0].axis('off')
axes[1].imshow(cv2.cvtColor(apple, cv2.COLOR_BGR2RGB))
axes[1].axis('off')
axes[
plt.tight_layout() plt.show()
Helper Functions to Display the Gaussian Pyramid
def pad_to_original_size(image, original_shape):
= original_shape[:2]
original_h, original_w = image.shape[:2]
h, w
= max((original_h - h) // 2, 0)
pad_h_top = max(original_h - h - pad_h_top, 0)
pad_h_bottom = max((original_w - w) // 2, 0)
pad_w_left = max(original_w - w - pad_w_left, 0)
pad_w_right
if image.ndim == 2:
= np.pad(image,
padded_image
((pad_h_top, pad_h_bottom),
(pad_w_left, pad_w_right)), ='constant', constant_values=50)
modeelse:
= np.pad(image,
padded_image
((pad_h_top, pad_h_bottom),
(pad_w_left, pad_w_right), 0, 0)),
(='constant', constant_values=50)
modereturn padded_image
def display_pyramid(pyramid_list, title='Pyramid'):
= pyramid_list[0].shape
original_shape
= [pad_to_original_size(img, original_shape) for img in pyramid_list]
padded_images
= np.hstack(padded_images)
pyramid_display
if pyramid_display.ndim == 2:
=(8, 8))
plt.figure(figsize= 14)
plt.title(title, fontsize ='gray')
plt.imshow(pyramid_display, cmapelse:
=(8, 8))
plt.figure(figsize= 14)
plt.title(title, fontsize
plt.imshow(cv2.cvtColor(pyramid_display, cv2.COLOR_BGR2RGB))
'off')
plt.axis( plt.show()
Gaussian Pyramid and Laplacian Pyramid
Great Source: https://becominghuman.ai/image-blending-using-laplacian-pyramids-2f8e9982077f
def gaussian_pyramid_opencv(image, levels, req_blur = False):
if req_blur:
= cv2.GaussianBlur(image, (3, 3), 1)
image = [image]
pyramid for _ in range(levels - 1):
= image.shape[:2]
h, w = cv2.resize(image, (w // 2, h // 2), interpolation=cv2.INTER_AREA)
image
pyramid.append(image)return pyramid
def laplacian_pyramid(image, levels):
= gaussian_pyramid_opencv(image, levels)
gaussian_pyr = []
laplacian_pyr = []
upsampled_pyr
for i in range(levels - 1):
= gaussian_pyr[i].shape[:2]
h, w = cv2.resize(gaussian_pyr[i + 1], (w, h), interpolation=cv2.INTER_LINEAR)
upsampled = cv2.GaussianBlur(upsampled, (3, 3), 1)
upsampled
= cv2.subtract(gaussian_pyr[i], upsampled)
laplacian
laplacian_pyr.append(laplacian)
upsampled_pyr.append(upsampled)
return laplacian_pyr, upsampled_pyr
= gaussian_pyramid_opencv(orange, levels=6)
gaussian_py_orange = gaussian_pyramid_opencv(apple, levels=6)
gaussian_py_apple = gaussian_pyramid_opencv(mask, levels=6)
gaussian_py_mask
= laplacian_pyramid(apple, levels=6)
laplacian_py_apple, upsampled_py_apple = laplacian_pyramid(orange, levels=6)
laplacian_py_orange, upsampled_py_orange = laplacian_pyramid(mask, levels=6)
laplacian_py_mask, upsampled_py_mask
'Orange Gaussian Pyramid')
display_pyramid(gaussian_py_orange, 'Orange Upsampled Pyramid')
display_pyramid(upsampled_py_orange, 'Orange Laplacian Pyramid')
display_pyramid(laplacian_py_orange,
'Apple Gaussian Pyramid')
display_pyramid(gaussian_py_apple, 'Apple Upsampled Pyramid')
display_pyramid(upsampled_py_apple, 'Apple Laplacian Pyramid')
display_pyramid(laplacian_py_apple,
'Mask Gaussian Pyramid')
display_pyramid(gaussian_py_mask, 'Mask Upsampled Pyramid') display_pyramid(upsampled_py_mask,
print(f"Gaussian Pyramid -> {[img.shape for img in gaussian_py_orange]}")
print(f"Upsampled Pyramid -> {[img.shape for img in upsampled_py_orange]}")
print(f"Laplacian Pyramid -> {[img.shape for img in laplacian_py_orange]}")
print(f"Gaussian Pyramid (Mask) -> {[img.shape for img in gaussian_py_mask]}")
Gaussian Pyramid -> [(448, 624, 3), (224, 312, 3), (112, 156, 3), (56, 78, 3), (28, 39, 3), (14, 19, 3)]
Upsampled Pyramid -> [(448, 624, 3), (224, 312, 3), (112, 156, 3), (56, 78, 3), (28, 39, 3)]
Laplacian Pyramid -> [(448, 624, 3), (224, 312, 3), (112, 156, 3), (56, 78, 3), (28, 39, 3)]
Gaussian Pyramid (Mask) -> [(448, 624), (224, 312), (112, 156), (56, 78), (28, 39), (14, 19)]
The Laplacian Blending
def reconstruct_image_from_pyramid(reconstructed_image, laplacian_pyr):
= [reconstructed_image]
reconstructions
for i in range(len(laplacian_pyr) - 1, -1, -1):
= laplacian_pyr[i].shape[:2]
h, w = cv2.resize(reconstructed_image, (w, h), interpolation=cv2.INTER_LINEAR)
upsampled = cv2.GaussianBlur(upsampled, (3, 3), 1)
upsampled = cv2.add(upsampled, laplacian_pyr[i])
reconstructed_image
reconstructions.append(reconstructed_image)
return reconstructions
def laplacian_pyramid_blend_images(image_a, image_b, mask, levels):
# Gaussian pyramids for the mask
= gaussian_pyramid_opencv(mask, levels)[:-1]
gaussian_mask_pyr
# Laplacian pyramids for the two images
= laplacian_pyramid(image_a, levels)
laplacian_pyr_a, upsampled_a = laplacian_pyramid(image_b, levels)
laplacian_pyr_b, upsampled_b
# as stated in the source, the last step is a bit different requiring to add 3 images
= upsampled_a[-1], upsampled_b[-1]
final_upsampled_a, final_upsampled_b = gaussian_mask_pyr[-1]
final_gaussian_mask = final_gaussian_mask * final_upsampled_a + (1.0 - final_gaussian_mask) * final_upsampled_b
final_part
= []
blended_pyr
# Blend each level of the Laplacian pyramids
for lap_a, lap_b, mask in zip(laplacian_pyr_a, laplacian_pyr_b, gaussian_mask_pyr):
= lap_a * mask + lap_b * (1.0 - mask)
blended_lap
blended_pyr.append(blended_lap)
= cv2.add(final_part, blended_pyr[-1])
reconstructed_image
# Reconstruct the final blended image from the blended pyramid
= reconstruct_image_from_pyramid(reconstructed_image, blended_pyr[:-1])
blended_images = [np.clip(img, 0, 255).astype(np.uint8) for img in blended_images]
blended_images
return blended_images
= get_feathered_mask(orange, 2, 10)
mask = mask / 255.0
mask = np.expand_dims(mask, axis=-1) # (h, w, 1)
mask = np.repeat(mask, 3, axis=-1) # (h, w, 3)
mask print(mask.shape)
= laplacian_pyramid_blend_images(apple, orange, mask, levels=6)
blended_images = blended_images[-1]
blended_image print(blended_image.shape)
(448, 624, 3)
(448, 624, 3)
print([img.shape for img in blended_images])
[(28, 39, 3), (56, 78, 3), (112, 156, 3), (224, 312, 3), (448, 624, 3)]
-1], 'Blended Images') display_pyramid(blended_images[::
=(6, 6))
plt.figure(figsize
plt.imshow(cv2.cvtColor(blended_image, cv2.COLOR_BGR2RGB))'off')
plt.axis( plt.show()
Gamma Correction
= 1.3
gamma = np.power(blended_image / 255.0, gamma) * 255
corrected_image = np.clip(corrected_image, 0, 255).astype(np.uint8)
corrected_image
=(6, 6))
plt.figure(figsize
plt.imshow(cv2.cvtColor(corrected_image, cv2.COLOR_BGR2RGB))'off')
plt.axis( plt.show()
= np.array([apple, orange, corrected_image])
collage 1, 3, title='Blended Image', figsize=10) show_image_grid(collage,
Some More Examples for Blending
Image Source: https://github.com/max-kazak/CP_Pyramid_Blending
= cv2.imread('./images/Doll_White.jpg', 1)
doll_white = cv2.imread('./images/Doll_Black.jpg', 1)
doll_black = cv2.imread('./images/Mask_Doll.jpg', 0)
mask_doll print(doll_white.shape, doll_black.shape, mask_doll.shape)
(812, 679, 3) (812, 679, 3) (812, 679)
= plt.subplots(1, 3, figsize=(10, 5))
fig, axes 'Images to Blend with the Mask', fontsize = 16)
fig.suptitle(0].imshow(cv2.cvtColor(doll_white, cv2.COLOR_BGR2RGB))
axes[0].axis('off')
axes[1].imshow(cv2.cvtColor(mask_doll, cv2.COLOR_BGR2RGB))
axes[1].axis('off')
axes[2].imshow(cv2.cvtColor(doll_black, cv2.COLOR_BGR2RGB))
axes[2].axis('off')
axes[
plt.tight_layout() plt.show()
= mask_doll / 255.0
mask = np.expand_dims(mask, axis=-1)
mask = np.repeat(mask, 3, axis=-1)
mask = laplacian_pyramid_blend_images(doll_white, doll_black, mask, levels=5)
blended_images = blended_images[-1] blended_image
print([img.shape for img in blended_images[::-1]])
-1], 'Blended Images') display_pyramid(blended_images[::
[(812, 679, 3), (406, 339, 3), (203, 169, 3), (101, 84, 3)]
=(6, 6))
plt.figure(figsize
plt.imshow(cv2.cvtColor(blended_image, cv2.COLOR_BGR2RGB))'off')
plt.axis( plt.show()
= 1.5
gamma = np.power(blended_image / 255.0, gamma) * 255
corrected_image = np.clip(corrected_image, 0, 255).astype(np.uint8)
corrected_image
=(6, 6))
plt.figure(figsize
plt.imshow(cv2.cvtColor(corrected_image, cv2.COLOR_BGR2RGB))'off')
plt.axis( plt.show()
= np.array([doll_white, doll_black, corrected_image])
collage 1, 3, 'Blended Image Pair', figsize=16) show_image_grid(collage,
= cv2.imread('./images/Puppy_Black.jpg', 1)
dog_black = cv2.imread('./images/Kitty_White.jpg', 1)
cat_white # Feathered Mask
= get_feathered_mask(dog_black, 1.7, 20)
mask_dc print(dog_black.shape, cat_white.shape, mask_dc.shape)
(600, 800, 3) (600, 800, 3) (600, 800)
= plt.subplots(1, 3, figsize=(10, 3))
fig, axes 'Images to Blend with the Mask', fontsize = 16)
fig.suptitle(0].imshow(cv2.cvtColor(dog_black, cv2.COLOR_BGR2RGB))
axes[0].axis('off')
axes[1].imshow(cv2.cvtColor(mask_dc, cv2.COLOR_BGR2RGB))
axes[1].axis('off')
axes[2].imshow(cv2.cvtColor(cat_white, cv2.COLOR_BGR2RGB))
axes[2].axis('off')
axes[
plt.tight_layout() plt.show()
= mask_dc / 255.0
mask = np.expand_dims(mask, axis=-1)
mask = np.repeat(mask, 3, axis=-1)
mask = laplacian_pyramid_blend_images(dog_black, cat_white, mask, levels=5)
blended_images = blended_images[-1] blended_image
print([img.shape for img in blended_images[::-1]])
-1], 'Blended Images') display_pyramid(blended_images[::
[(600, 800, 3), (300, 400, 3), (150, 200, 3), (75, 100, 3)]
=(6, 6))
plt.figure(figsize
plt.imshow(cv2.cvtColor(blended_image, cv2.COLOR_BGR2RGB))'off')
plt.axis( plt.show()
= 1.3
gamma = np.power(blended_image / 255.0, gamma) * 255
corrected_image = np.clip(corrected_image, 0, 255).astype(np.uint8)
corrected_image
=(6, 6))
plt.figure(figsize
plt.imshow(cv2.cvtColor(corrected_image, cv2.COLOR_BGR2RGB))'off')
plt.axis( plt.show()
= np.array([dog_black, cat_white, corrected_image])
collage 1, 3, 'Blended Image Pair', figsize=16) show_image_grid(collage,