Project 2

Part 1: Fun with Filters

Part 1.1: Convolutions from Scratch!

Naive approach with 4 for loops


def convovle_4(img, kernel,  mode='constant'):
    h,w = img.shape
    kernel = np.flip(kernel, (0, 1)) #flip the kernel
    k_h, k_w = kernel.shape
    convolved_img = np.copy(img)
    # padding
    pad_w= (k_w - 1) // 2
    pad_h = (k_h - 1) // 2
    img = np.pad(img, pad_width=((pad_h, pad_h), (pad_w, pad_w)), mode=mode)
    
    for i in range(w):
        for j in range(h):
            convolved_img[j][i] = 0
            for p in range(-pad_w, pad_w + 1):
                for q in range(-pad_h, pad_h + 1):
                    convolved_img[j][i] += kernel[q + pad_h][p + pad_w] * img[j + q + pad_h][i + p + pad_w]
    return convolved_img
                

Naive approach with 2 for loops


def patch(img, i, j, kw, kh):
    pad_w = (kw - 1) //2
    pad_h = (kh - 1) //2
    return img[j -pad_h:j+pad_h+1, i -pad_w:i+pad_w+1]
    
def convovle_2(img, kernel, mode='constant'):
    h,w = img.shape
    kernel = np.flip(kernel, (0, 1)) #flip the kernel
    k_h, k_w = kernel.shape
    convolved_img = np.copy(img)
    # zero padding
    pad_w= (k_w - 1) // 2
    pad_h = (k_h - 1) // 2

    img = np.pad(img, pad_width=((pad_h, pad_h), (pad_w, pad_w)), mode=mode)
    kernel_flat = kernel.ravel()
    for i in range(w):
        for j in range(h):
            curr_patch = patch(img, i+pad_w, j+pad_h, k_w, k_h)
            curr_patch_flat = curr_patch.ravel()
            convolved_img[j][i] = curr_patch_flat @ kernel_flat
    return convolved_img
                

The following is the code to construct kernels


                box_filter_9 = 1/81 * np.ones((9,9))
                Dx = np.array([[1, 0, -1]])
                Dy = np.array([[1], [0], [-1]])
            

The convolution with 4 for loops takes 18 seconds to run.

The convolution with 2 for loops takes 0.8 seconds to run.

The scipy_convovle_img = signal.convolve2d(im, box_filter_9, mode='same') takes 0.1 seconds to run.

The less for loop we have in the implementation, the faster the convolution is.

I used zero-padding to handle boundary. To apply kernel the pixels on the boundary, I manually add (kernel_width - 1) // 2 pixels with zero values on left and right side, and (kernel_height - 1) // 2 zero-value pixels for top and bottom.

selfie
original image
selfie
blurred image
selfie
image convolve with dx kernel
selfie
image convolve with dy kernel

Part 1.2: Finite Difference Operator

selfie
Original Images
selfie
With Dx kernel
selfie
With Dy kernel
selfie
gradient Magnitude
selfie
Binary Edges

For binary edge image, to remove most of noise in the background, I chose threshold = 0.3. This will make image lost most edges in the buildings in the background and weaker edge on cameraman. But this threshold can remove most noise in the sky and land. This threshold can make sure I get most camerman edges with little noise.

Part 1.3: Derivative of Gaussian (DoG) Filter

One way to create DoG filter: Convolve the gaussian kernel with D_x and D_y. The other way is create a blurred version of the original image by convolving with a gaussian and then apply Dx and Dy on the blured image then create binary edge image.

Here is visualization of DoG filter with Dx and Dy filter. To make effect obvious I used kernel size (70,70) and sigma 30

selfie
Gaussian kernel
selfie
Gaussian kernel after convovle with Dx
selfie
Gaussian kernel after convovle with Dy

Visualization for kernel size (3,3) and sigma 1, this the kernel I actually used for DoG filter implementation

selfie
Gaussian kernel
selfie
Gaussian kernel after convovle with Dx
selfie
Gaussian kernel after convovle with Dy
selfie
Binary Edges, threshold: 0.3
selfie
Gaussian then taking Dx Dy and Binary Edges, threshold: 0.21
selfie
DoG Filter then Binary Edges, threshold: 0.21

From above images, we can see using DoG filter we can remove most of noise in the background and preserve the main edges of cameraman. Using DoG filter can remove more noise and preserve more detail than using Finite Difference Operator directly.

Using DoG filter to create bineary edge with one convolution and blurring image then apply Dx Dy on the blurred image can create same output image.

Part 2.1: Image "Sharpening"

implemenet the gaussian kernel then convolve image with the kernel gave us the blurring/low-pass filter function

 
                        g1d = cv2.getGaussianKernel(3,1)
                        g2d = np.outer(g1d, g1d)
                        def blur_image_color(img, kernel):
                            img_channels = []
                            for i in range(3):
                                img_c = img[:,:,i]
                                sharpened_img_c = signal.convolve2d(img_c, kernel, mode='same')
                                img_channels.append(sharpened_img_c)
                            img = np.dstack(img_channels)
                            return img
                        blur_image_color(img, g2d)
                    

To sharpen the image, you will need apply unsharp mask filter(gaussian filter) to create an image with low frequency and get high frequency image by subtracting low frequency image from original image. Then you add high frequency image back to original image to increase the intensity of the edge of high frequency and create sharpening effect. As a result, we get unsharp mask filter.

q2
Formula to sharpen image in one convolution

Using the formula above, we can finish sharpening in one convolution and can change sharpen intensity by changing alpha The higher the alpha value is,the stronger the sharpening effect is. Where f is the image, g is gaussian kernel, e is identity kernel, alpha is constant.

q2
Original taj image
selfie
Sharpen taj high frequency image
selfie
Sharpen taj image, alpha=2
selfie
Sharpen taj image, alpha=7
selfie
House image
selfie
House image high frequency
selfie
Sharpen House image, alpha=2
selfie
Sharpen House image, alpha=6
selfie
Original Campus image
selfie
blur campus image
selfie
resharp campus image, alpha=2
selfie
resharp campus image, alpha=10

obseravtion

By trying to blur the image and resharpen it, we can see it loses some details after resharpen. After resharp, we can see noise around the edges of the buildings because we overlap too many high frequency values. So resharpend image sometimes can't reproduce the origianl image.

Part 2.2: Hybrid Images

This part, I will go through the step to create hybrid image using following two image professor Derek and cat nutmeg
q2
Professor
q2
professor image frequency
q2
Cat
q2
original cat image frequency
Step 1: aligned the image

We will first need to align the image so two image can overlap well in hybrid image Here we will rotate nutmeg.jpg so the cat face and professor's face can align. I also adjust the sizes of two images.

q2
aligned cat image
q2
aligned cat image frequency
q2
aligned professor image
q2
aligned professor image frequency
Step 2: Filter Image

For image we want to see in close up, we will need to extract the high frequency of that image. To do that, we apply high-pass filter on one image. Here we will apply high-pass filter on the nutmeg.jpg. Then we apply low-pass filter on the other image.

low-pass filter can use gaussian filter

high-pass filter computed by subtracting the Gaussian-filtered image from the original.

q2
high-pass image
q2
low-pass image
q2
high-pass freq
q2
low-pass freq
Step 3: Merge two image

Finally we will add low-pass filtered image to high-pass filtered image to create hybrid image. Here I use sigma1 = 80, sigma2 = 30 for image 1 and image 2 respectively. From the fourier frequency image, we can see we merge the high-low freuqency from two images.

q2
Hybrid_image parameter: sigma1 = 80, sigma2 = 30
q2
hybrid freq
Other hybrid image examples
q2
fish
q2
submarine
q2
hybrid
q2
fish
q2
fish
q2
fish

2.3: Gaussian and Laplacian Stacks

Gaussian Stack for apple (sigma=100, ksize=15)
q2
q2
q2
q2
q2
q2
Gaussian Stack for orange (sigma=100, ksize=15)
q2
q2
q2
q2
q2
q2
Laplacian Stack for apple

Note: Laplacian Stack share same lowest level image as Gaussian Stack

q2
q2
q2
q2
q2
q2
Laplacian Stack for orange
q2
q2
q2
q2
q2
q2
Gaussian Stack for the mask
q2
q2
q2
q2
q2
q2

Now we can apply build gaussian stack for the mask. Then we apply each level's blurred mask to corresponding level of Laplacian stack of apple and orange.

q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2

Finally, we collpase the combined stack to create the final blending image.

q2

Part 2.4: Multiresolution Blending

q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2
q2