COMPUTER VISION
HOMEWORK – 1
Laplacian Blending:
We begin by dividing the two input images, the apple and the orange, into separate channels
using a mask. Next, we apply blending techniques to each channel individually. To enhance the
blending effect, we create a Gaussian pyramid for the mask and a Laplacian pyramid for the
images. These pyramids are then combined and downsized to generate the final blended
image. The fundamental idea is that in order to create pyramids and then seamlessly combine
two separate images into one, we must employ Gaussian (low pass) and Laplacian (high pass)
filters.
By utilizing the provided Apple, Orange, and Mask images and applying the algorithm, I have
obtained the blended image.
OUTPUT:
      After implementing the algorithm, I understood that in order to blend/combine images
       they must have same dimensions in order to apply the algorithm and get the results. In
       Most cases it performed well.
      Also, I have tried on some images with two different colors where the results are pretty
       inconsistent in most cases. As colors in the image are completely different in each other.
Code used for implementation:
#install and import the libraries
import numpy as np
import scipy.signal as sig
from scipy import misc
import matplotlib.pyplot as plt
from scipy import ndimage
import cv2
import imageio
from google.colab.patches import cv2_imshow
# up-sampling the images
def upsample_image(image):
    # Create a new image with twice the dimensions of the input image
    image_upscale = np.zeros((2 * image.shape[0], 2 * image.shape[1]))
    # Copy the pixels from the original image to alternate pixels in the new
image
    image_upscale[::2, ::2] = image
    # Bi-linear interpolation kernel
    kernel = (1.0 / 256) * np.array(
        [[1, 4, 6, 4, 1], [4, 16, 24, 16, 4], [6, 24, 36, 24, 6], [4, 16, 24,
16, 4], [1, 4, 6, 4, 1]])
    # Convolve the up-sampled image with the kernel
    return ndimage.filters.convolve(image_upscale, 4 * kernel,
mode='constant')
# down-sampling the image
def downsample_image(image):
    # Down-sample the image by a factor of 2 using Gaussian blurring
    kernel = (1.0 / 256) * np.array(
        [[1, 4, 6, 4, 1], [4, 16, 24, 16, 4], [6, 24, 36, 24, 6], [4, 16, 24,
16, 4], [1, 4, 6, 4, 1]])
    # Apply Gaussian blur to the image
    image_downscale = ndimage.filters.convolve(image, kernel, mode='constant')
    # Down-sample the blurred image by selecting every alternate pixel
    return image_downscale[::2, ::2]
# Constructing image pyramids for the given images
def build_pyramids(image):
    # Build Gaussian and Laplacian pyramids for the given image
    G = [image] # Gaussian pyramid
    L = [] # Laplacian pyramid
    # Downsample the image
    while image.shape[0] >= 2 and image.shape[1] >= 2:
        image = downsample_image(image)
        # Add the downsampled image to the Gaussian pyramid
        G.append(image)
    # Upsample the next level of the Gaussian pyramid
    for i in range(len(G) - 1):
        interpolated = upsample_image(G[i + 1])
        # Calculate the Laplacian by subtracting the upsampled image from the
current level
        L.append(G[i] - interpolated[:G[i].shape[0], :G[i].shape[1]])
    return G[:-1], L
# Performing pyramid blending of two images using a mask
def pyramid_blending(A, B, mask):
    # Build Gaussian and Laplacian pyramids for images A and B
    [GA, LA] = build_pyramids(A)
    [GB, LB] = build_pyramids(B)
    [Gmask, LMask] = build_pyramids(mask)
    blend = []
    for i in range(len(LA)):
        # Blend the Laplacian layers based on the mask
        LS = Gmask[i] / 255 * LA[i] + (1 - Gmask[i] / 255) * LB[i]
        blend.append(LS)
    return blend
# Reconstruct the pyramids as well as upsampling and adding up with each level
def reconstruct_image(pyramid):
    # Reconstruct the image from the Laplacian pyramid
    rows, cols = pyramid[0].shape
    res = np.zeros((rows, cols + cols // 2), dtype=np.double)
    reversed_pyramid = pyramid[::-1]
    stack = reversed_pyramid[0]
    for i in range(1, len(reversed_pyramid)):
        # Upsample the previous level and add it to the current level
        stack_upsampled = upsample_image(stack)
        stack_upsampled_cropped =
stack_upsampled[:reversed_pyramid[i].shape[0], :reversed_pyramid[i].shape[1]]
        stack = stack_upsampled_cropped + reversed_pyramid[i]
    return stack
# Perform color blending of two images based on a mask
def perform_color_blending(img1, img2, mask):
    # Split the input images into their RGB channels
    img1_r, img1_g, img1_b = cv2.split(img1)
    img2_r, img2_g, img2_b = cv2.split(img2)
    # Check if any image dimensions mismatch and throw value error
    if mask.ndim == 2: # Grayscale mask
        mask_r = mask_g = mask_b = mask
    elif mask.ndim == 3: # Color mask
        mask_r, mask_g, mask_b = cv2.split(mask)
    else:
        raise ValueError("Invalid mask dimensions.")
    # Check dimensions
    if img1_r.shape != img2_r.shape or img1_r.shape != mask_r.shape:
        raise ValueError("Input image and mask dimensions do not match.")
    #   Perform pyramid blending on each color channel
    R   = reconstruct_image(pyramid_blending(img1_r, img2_r, mask))
    G   = reconstruct_image(pyramid_blending(img1_g, img2_g, mask))
    B   = reconstruct_image(pyramid_blending(img1_b, img2_b, mask))
    # Merge the color channels back into an RGB image
    output = cv2.merge((R, G, B))
    # Save and display the output image
    imageio.imsave("blended image.png", output)
    img = cv2.imread("blended image.png")
    cv2_imshow(img)
# Load input images and mask
apple = imageio.imread('apple.png')
orange = imageio.imread('orange.png')
mask = cv2.imread('mask.png', 0)
# Perform color blending and display the output
perform_color_blending(apple, orange, mask)
References:
       https://www.w3schools.com/python/
       https://docs.opencv.org/4.x/dc/dff/tutorial_py_pyramids.html
       http://persci.mit.edu/pub_pdfs/RCA84.pdf
       http://graphics.cs.cmu.edu/courses/15463/2005_fall/www/Lectures/
        Pyramids.pdf