Hasan's Post

Tutorial repository

View on GitHub
5 September 2021

Spatial Operation using Pillow and Opencv

by Hasan

Spatial operation uses pixels in neighborhood to determine the present pixel values. There are many applications of spatial transformations in computer vision

Here we will be working on

Download Image

As usual we will download our required images

!wget https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-CV0101EN-SkillsNetwork/images%20/images_part_1/cameraman.jpeg
!wget https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-CV0101EN-SkillsNetwork/images%20/images_part_1/lenna.png
!wget https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-CV0101EN-SkillsNetwork/images%20/images_part_1/barbara.png   
--2021-08-20 09:25:05--  https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-CV0101EN-SkillsNetwork/images%20/images_part_1/cameraman.jpeg
Resolving cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud (cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud)... 169.63.118.104
Connecting to cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud (cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud)|169.63.118.104|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7243 (7,1K) [image/jpeg]
Saving to: 'cameraman.jpeg.1’

cameraman.jpeg.1    100%[===================>]   7,07K  --.-KB/s    in 0s      

2021-08-20 09:25:06 (1,92 GB/s) - 'cameraman.jpeg.1’ saved [7243/7243]

--2021-08-20 09:25:06--  https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-CV0101EN-SkillsNetwork/images%20/images_part_1/lenna.png
Resolving cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud (cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud)... 169.63.118.104
Connecting to cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud (cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud)|169.63.118.104|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 473831 (463K) [image/png]
Saving to: 'lenna.png.1’

lenna.png.1         100%[===================>] 462,73K   639KB/s    in 0,7s    

2021-08-20 09:25:08 (639 KB/s) - 'lenna.png.1’ saved [473831/473831]

--2021-08-20 09:25:08--  https://cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud/IBMDeveloperSkillsNetwork-CV0101EN-SkillsNetwork/images%20/images_part_1/barbara.png
Resolving cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud (cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud)... 169.63.118.104
Connecting to cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud (cf-courses-data.s3.us.cloud-object-storage.appdomain.cloud)|169.63.118.104|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 185727 (181K) [image/png]
Saving to: 'barbara.png.1’

barbara.png.1       100%[===================>] 181,37K   600KB/s    in 0,3s    

2021-08-20 09:25:09 (600 KB/s) - 'barbara.png.1’ saved [185727/185727]
import numpy as np
from PIL import Image
import cv2
from PIL import ImageOps
import matplotlib.pyplot as plt
%matplotlib inline
%load_ext autoreload
%autoreload 2

We will first copy our previously created functions

def show_image(im,cmap=None):
    fig, ax = plt.subplots(figsize=(10, 10))
    ax.imshow(im, cmap=cmap)
    ax.axis('off')



def side_by_side(im1, im2, 
                 im1_title='original',
                 im2_title=None, cmap1=None, cmap2=None):
    """Plot two images side  by side

    Args:
        im1 ([PIL:Opencv image]): [one image]
        im2 ([PIL:Opencv imag]): [another image]


    return None
    """
    fig, ax = plt.subplots(1, 2, figsize=(10, 10))
    axes = ax.ravel()
    ax[0].imshow(im1, cmap=cmap1)
    ax[0].axis('off')
    ax[0].set_title(im1_title)
    ax[1].imshow(im2, cmap=cmap2)
    ax[1].axis('off')
    ax[1].set_title(im2_title,fontweight='bold')

Linear Filtering:PIL

lena_pil = Image.open('lenna.png')
show_image(lena_pil)

svg

So we will see how to remove noise from an image. To remove noise, we need a noisy image. We will create a noisy image from this image

rows, cols = lena_pil.size
noise = np.random.normal(0, 15, (rows, cols, 3)).astype(np.uint8)
noisy_image = lena_pil + noise
noisy_image = Image.fromarray(noisy_image)
side_by_side(im1=lena_pil, im2=noisy_image,
             im1_title='Original Image',
             im2_title='Noisy Image')


svg

Filtering Noise

First we need to import ImageFilter to use the predefined filters

Smoothing filters average out the Pixels within neighbourhood, they are sometimes called low pass filter. For mean filtering kernel just averages out the kernels in a neighbourhood

from PIL import ImageFilter 
kernel = np.ones((5, 5))/36 # Creating 5 by 5 array where each value 1/36
kernel_filter = ImageFilter.Kernel((5, 5), kernel.flatten())
image_filtered = noisy_image.filter(kernel_filter)
side_by_side(im1=noisy_image, im2=image_filtered, im1_title='Noisy Image', im2_title='Filtered Image')

svg

A smaller kernel make the image sharp, but filter less noise. Now we will use 3x3 kernel

kernel = np.ones((3, 3))/36
kernel_filter = ImageFilter.Kernel((3, 3), kernel.flatten())
image_filtered_sm = noisy_image.filter(kernel_filter) 

We want to see side by side all 3 images

fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(18,10))
axes = ax.ravel()
axes[0].imshow(noisy_image)
axes[0].axis('off')
axes[0].set_title('Noisy Image')
axes[1].imshow(image_filtered)
axes[1].axis('off')
axes[1].set_title('kernel size 5x5')
axes[2].imshow(image_filtered_sm)
axes[2].axis('off')
axes[2].set_title('kernel size 3x3')

Text(0.5, 1.0, 'kernel size 3x3')

svg

So we can see that (5x5) kernel removes more noise, but it is somehow blurry. However (3x3) kernel image more noisy, but it is somehow more sharp. Depending on the use case one needs to use the filter

Gaussian Blurr

# Filter image for GaussianBlur
image_filtered = noisy_image.filter(ImageFilter.GaussianBlur)
side_by_side(im1=noisy_image,
             im2=image_filtered,
             im1_title='noiy image',
             im2_title='Guassian blurred image')

svg

Let’s change the default value to 4

image_filter_4 = noisy_image.filter(ImageFilter.GaussianBlur(4))

Again I would like to plot 3 images side_by_side, what we have done. let’s create another function, where we don’t need to tell how much images, we need to plot

def show_image_list(im_list,
                    title_list,
                    cmap_list = None):
    
    number_of_image = len(im_list)
    fig, ax = plt.subplots(nrows=1, ncols=number_of_image,
                           figsize=(18,10))
    axes = ax.ravel()
    for idx, i in enumerate(im_list):
        if cmap_list is not None:
            axes[idx].imshow(i, cmap=cmap_list[idx])
        else:
            axes[idx].imshow(i)
        axes[idx].axis('off')
        axes[idx].set_title(title_list[idx])


show_image_list([noisy_image, image_filtered, image_filter_4], ['Noisy image', 'Guassian blur with 2 kernel', 'Guassian blur with 4 kernel'] )

svg

So we have done blurring. Let’s do the opposite, sharpening.

Image Sharpening

kernel = np.array([[-1, -1, -1],
                   [-1, 9, -1],
                   [-1, -1, -1]
                   ])
kernel = ImageFilter.Kernel((3, 3), kernel.flatten())            
sharp_image = lena_pil.filter(kernel)
show_image_list([lena_pil, sharp_image],['Original Image', 'Sharpened Image'])

svg

Prevously we actually created out own filter. We can also use predefined filter

sharpened_image = lena_pil.filter(ImageFilter.SHARPEN)
show_image_list([lena_pil, sharpened_image], ['Orginal Image', 'Sharp Image'])

svg

show_image_list([image_filtered, 
                 image_filtered.filter(ImageFilter.SHARPEN),
                 image_filter_4,
                 image_filter_4.filter(ImageFilter.SHARPEN) ],
['Guassian Blur\n kernel size 2', 'sharp after Guassian blur\n kernel 2', 'Guassian blur\n  kernel size 4','sharp after Guassian blur\n kernel size 4'])

svg

Linear Filtering:Opencv

So in opencv we need to convert the image to rgb image, Now I am creating a function named as oc :opencv to convert that. Just don’t want to write everytime the same function

oc = lambda x:cv2.cvtColor(x, cv2.COLOR_BGR2RGB)
lena_cv = cv2.imread('lenna.png')
show_image(oc(lena_cv))

svg

So right now we will again create the noise and then an opencv noisy image

lena_cv.shape
(512, 512, 3)
noise = np.random.normal(0, 15, (lena_cv.shape)).astype(np.uint8)
noisy_image = lena_cv + noise
show_image_list([oc(lena_cv), oc(noisy_image)],['Actual Image','Noisy Image'])

svg

Filtering Noise

like previously we will create our kernel first and then apply kernel to filter the noise

kernel = np.ones((5, 5))/36

The function filter2D is similar to ImageFilter.kernel, here we have a src which is the image, and kernel will be applied in each channel independently. Parameter ddepth is responsible for output size, here we will apply -1, as we want to have same input and output size.

image_filter = cv2.filter2D(src=noisy_image,
                            ddepth=-1,
                            kernel=kernel)
show_image_list([oc(noisy_image), oc(image_filter)],['Original','filtered'])

svg

Last time we have seen the effect of bigger and smaller kernel size. Let’s whether it is same for opencv also. Acutally it should be same, becuase opencv and PIL are some tools to do things, main mathematical function is same.

kernel1 = np.ones((7, 7)) / 36
kernel2 = np.ones((4, 4)) / 16
lena_kernel1 = cv2.filter2D(src=noisy_image,
                          ddepth=-1,
                          kernel=kernel1)
lena_kernel2 = cv2.filter2D(src=noisy_image,
                          ddepth=-1,
                          kernel=kernel2)
show_image_list([oc(noisy_image),
                 oc(image_filter),
                 oc(lena_kernel1),
                 oc(lena_kernel2) ],
                 ['Noisy Image', 'kernel size 5\n value $1/36$', 'kernel size 7\n value$1/36$','kernel 4\n with value $(1/16)$'])

svg

We can see that kernel size 4 with value $1/16$ is sharp but more noise is there.

Gaussian Blur

# 4x4 kernel
image_filtered = cv2.GaussianBlur(noisy_image,
                                   (5, 5), sigmaX=4,
                                   sigmaY=4)
show_image_list([oc(image_filtered), oc(noisy_image)],
                ['Gaussian Blurred Image', 'Noisy Image'])

svg

Sigma is like mean of the filter. Lets change it to see the effect of that

image_filtered1 = cv2.GaussianBlur(noisy_image,
                                   ksize=(11, 11),
                                   sigmaX=10,
                                   sigmaY=10)
show_image_list([oc(noisy_image), oc(image_filtered), oc(image_filtered1)],
                ['Noisy Image', 'GaussianBlur \nsigma =4 ','GaussianBlur \n$sigma =10$' ])

svg

Image Sharpening

# Common filtering for image sharpening
kernel = np.array([[-1, -1, -1],
                   [-1,  9, -1],
                   [-1, -1, -1]])
sharpen_cv = cv2. filter2D(lena_cv, -1, kernel)
show_image_list([oc(lena_cv), oc(sharpen_cv)],
                ['Original Image', 'Sharpen Image'])

svg

Edge Detection:PIL

barbara_pil = Image.open('barbara.png') 
# At firtst we enhance the edge so that 
# they can better picked up
barbara_edge_enhance = barbara_pil.filter(ImageFilter.EDGE_ENHANCE)
# Now finds the edges
barbara_edge = barbara_edge_enhance.filter(ImageFilter.FIND_EDGES)
show_image_list([barbara_pil,
                 barbara_edge_enhance,
                 barbara_edge],
                 ['Original Image',
                 'Enhanced Edge',
                 'Edged Image'],
                 ['gray','gray','gray','gray'])

svg

Edge Detection:OpenCv

barbara_cv = cv2.imread('barbara.png',
                        cv2.IMREAD_GRAYSCALE)
# We first smooth the image so that edge
# can be detected perfectly, otherwsie 
# there is chance that some noise will be there
barbara_smooth = cv2.GaussianBlur(barbara_cv, 
                                ksize=(3,3),
                                sigmaX=0.1,
                                sigmaY=0.1)
show_image_list([oc(barbara_cv),
                oc(barbara_smooth)],
                ['Orginal Image','Smoothed Image'])

svg

dx = 1 represents the derivative in the x-direction. The function approximates the derivative by convolving the image with the following kernel

$\begin{bmatrix} 1 & 0 & -1 \
2 & 0 & -2 \
1 & 0 & -1 \end{bmatrix}$

ddepth = cv2.CV_16S
# Apply this filter on the image in X direction
grad_x = cv2.Sobel(src=barbara_smooth,ddepth=ddepth,
                    dx=1,dy=0, ksize=3)
show_image_list([(barbara_smooth), (grad_x)],
                  ['Smoothed Image', ' Edge Approximation x direction'],
                  [ 'gray','gray' ])

svg

dy=1 represents the derivative in the y-direction. The function approximates the derivative by convolving the image with the following kernel $\begin{bmatrix} \ \ 1 & \ \ 2 & \ \ 1 \
\ \ 0 & \ \ 0 & \ \ 0 \
\ \ -1 & -2 & -1 \end{bmatrix}$

grad_y = cv2.Sobel(src=barbara_smooth,ddepth=ddepth,
                    dx=0,dy=1, ksize=3)
show_image_list([(barbara_smooth), (grad_x), grad_y],
                  ['Smoothed Image', ' Edge Approximation x direction',' Edge Approximation y direction'],
                  [ 'gray','gray','gray' ])

svg

# Converts the values back to a number  0 to 255
abs_grad_x = cv2.convertScaleAbs(grad_x)
abs_grad_y = cv2.convertScaleAbs(grad_y)

# Apply the function addWEighted to calcualte 
# the sum or two arrays 
grad = cv2.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0)


show_image_list([(barbara_smooth), (grad_x), grad_y, grad],
                  ['Smoothed Image', ' Edge Approximation x direction',' Edge Approximation y direction','Edge detection'],
                  [ 'gray','gray','gray','gray' ])

svg

Median: PIL

Medial filters find the median of all pixels under the kernel area and the center is replaced with the median value. It will help in segmentation task, as it blurs the background

cameraman = Image.open('cameraman.jpeg')
cameraman_median = cameraman.filter(ImageFilter.MedianFilter)
show_image_list([cameraman, cameraman_median],
                ['Oringinal','Medianfilter'])

svg

Median:Opencv

cameraman_cv = cv2.imread('cameraman.jpeg', cv2.IMREAD_GRAYSCALE)
# blurring with kernal size= 5
camera_median = cv2.medianBlur(cameraman_cv, 5)
show_image_list([cameraman_cv, camera_median],
                ['Original','blurred'],
                ['gray','gray'])

svg

ret, outs = cv2.threshold(src=camera_median,
                          thresh=0,
                          maxval=255,
                          type=cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
ret, outs1 = cv2.threshold(src=cameraman_cv,
                          thresh=0,
                          maxval=255,
                          type=cv2.THRESH_OTSU + cv2.THRESH_BINARY_INV)
show_image_list([cameraman_cv, outs, outs1],
                ['Origanl','Segmenation from blurred image','Segmenatation from actual image'],['gray','gray','gray'])

svg

References:

tags: