01/19/23 03:30:14 C:\Conv_Python\Conv_Test_24BPP_OK_3_channels.py
   1 
#=======================================================================
   2 
# Image filtering by convolution & kernels
   3 
#
   4 
# Cesare Brizio, 19 January 2023
   5 
#
   6 
# I did something similar around 15 years ago in Visual Basic,
   7 
# with the heavy burden of a Visual Studio installation.
   8 
# Thanks to Python, I can provide a working example with in a
   9 
# much lighter environment.
  10 
#
  11 
# The main purpose here is to illustrate the inner mechanics of
  12 
# a kernel convolution algorithm (see the nested loops) to allow
  13 
# a better understanding of the underlying logics.
  14 
#
  15 
# Inspired by a post by Dario Radečić
  16 
# (https://medium.com/@radecicdario)
  17 
# (https://towardsdatascience.com/tensorflow-for-computer-vision-how-to-implement-convolutions-from-scratch-in-python-609158c24f82)
  18 
# With some help by Mark Setchell
  19 
# (https://github.com/MarkSetchell)
  20 
#
  21 
# "cat image" 1.jpg is available as a part of the
  22 
#  Cats vs. Dogs dataset from Kaggle
  23 
# (https://www.kaggle.com/datasets/pybear/cats-vs-dogs?select=PetImages)
  24 
#
  25 
# Includes kernels from https://stackoverflow.com/questions/58383477/how-to-create-a-python-convolution-kernel
  26 
# Just a few of the kernels listed are used in the code, feel free
  27 
# to edit it as needed
  28 
#=======================================================================
  29 
 
  30 
import numpy as np
  31 
from PIL import Image, ImageOps
  32 
from matplotlib import pyplot as plt
  33 
from matplotlib import image as mpimg
  34 
from matplotlib import colors as mcolors
  35 
from numpy import asarray
  36 
import cv2
  37 
 
  38 
def plot_image(img: np.array):
  39 
    plt.figure(figsize=(6, 6), dpi=96)
  40 
    plt.title("Cat Image")
  41 
    plt.xlabel("X pixel scaling")
  42 
    plt.ylabel("Y pixels scaling")
  43 
    #plt.imshow(img, cmap='gray'); # no need for a color map
  44 
    plt.imshow(img);
  45 
    plt.show()
  46 
 
  47 
   
  48 
def plot_two_images(img1: np.array, img2: np.array, imm_name):
  49 
    _, ax = plt.subplots(1, 2, figsize=(12, 6), dpi=96)
  50 
    plt.title(imm_name)
  51 
    plt.xlabel("X pixel scaling")
  52 
    plt.ylabel("Y pixels scaling")   
  53 
    #ax[0].imshow(img1, cmap='gray')
  54 
    #ax[1].imshow(img2, cmap='gray');   
  55 
    ax[0].imshow(img1)
  56 
    ax[1].imshow(img2);
  57 
    plt.show()
  58 
 
  59 
sharpen = np.array([
  60 
    [0, -1, 0],
  61 
    [-1, 5, -1],
  62 
    [0, -1, 0]
  63 
])
  64 
 
  65 
blur = np.array([
  66 
    [0.0625, 0.125, 0.0625],
  67 
    [0.125,  0.25,  0.125],
  68 
    [0.0625, 0.125, 0.0625]
  69 
])
  70 
 
  71 
outline = np.array([
  72 
    [-1, -1, -1],
  73 
    [-1,  8, -1],
  74 
    [-1, -1, -1]
  75 
])
  76 
 
  77 
laplacian = np.array([
  78 
    [0, 1, 0],
  79 
    [1, -4, 1],
  80 
    [0, 1, 0]
  81 
])
  82 
 
  83 
emboss = np.array([
  84 
    [-2, -1, 0],
  85 
    [-1, 1, 1],
  86 
    [0, 1, 2]
  87 
])
  88 
 
  89 
bottom_sobel = np.array([
  90 
    [-1, -2, -1],
  91 
    [0, 0, 0],
  92 
    [1, 2, 1]
  93 
])
  94 
 
  95 
left_sobel = np.array([
  96 
    [1, 0, -1],
  97 
    [2, 0, -2],
  98 
    [1, 0, -1]
  99 
])
 100 
 
 101 
right_sobel = np.array([
 102 
    [-1, 0, 1],
 103 
    [-2, 0, 2],
 104 
    [-1, 0, 1]
 105 
])
 106 
 
 107 
top_sobel = np.array([
 108 
    [1, 2, 1],
 109 
    [0, 0, 0],
 110 
    [-1, -2, -1]
 111 
])
 112 
 
 113 
 
 114 
def calculate_target_size(img_size: int, kernel_size: int) -> int:
 115 
    print(f'calculate_target_size({img_size}, {img_size})')   
 116 
    num_pixels = 0
 117 
   
 118 
    # From 0 up to img size (if img size = 224, then up to 223)
 119 
    for i in range(img_size):
 120 
        # Add the kernel size (let's say 3) to the current i
 121 
        added = i + kernel_size
 122 
        # It must be lower than the image size
 123 
        if added <= img_size:
 124 
            # Increment if so
 125 
            num_pixels += 1
 126 
 
 127 
    print(f'calculate_target_size returns {num_pixels}')           
 128 
    return num_pixels
 129 
 
 130 
def convolve(img: np.array, kernel: np.array) -> np.array:
 131 
    # Assuming a rectangular image
 132 
    tgt_size = calculate_target_size(
 133 
        img_size=img.shape[0],
 134 
        kernel_size=kernel.shape[0]
 135 
    )
 136 
    # To simplify things
 137 
    k = kernel.shape[0]
 138 
   
 139 
    # This will hold our 3-channel RGB result
 140 
    convolved = np.zeros(shape=(tgt_size, tgt_size,3))  
 141 
   
 142 
    # Iterate over the rows
 143 
    for i in range(tgt_size):
 144 
        # Iterate over the columns
 145 
        for j in range(tgt_size):
 146 
            # Iterate over channels
 147 
            for c in range(3):
 148 
                mat = img[i:i+k, j:j+k, c]
 149 
                # Apply the convolution - element-wise multiplication and summation of the result
 150 
                # Store the result to i-th row and j-th column of our convolved_img array
 151 
                convolved[i, j, c] = np.sum(np.multiply(mat, kernel))
 152 
 
 153 
    # Clip result array to range 0..255 and make into uint8
 154 
    result = np.clip(convolved, 0, 255).astype(np.uint8)
 155 
    print(f'{convolved.dtype}, {convolved.shape}')
 156 
    print(f'Rmax: {np.max(result[...,0])}, Rmin: {np.min(result[...,0])}')
 157 
    print(f'Gmax: {np.max(result[...,1])}, Gmin: {np.min(result[...,1])}')
 158 
    print(f'Bmax: {np.max(result[...,2])}, Bmin: {np.min(result[...,2])}')
 159 
 
 160 
    return result
 161 
 
 162 
# ----------------------------------------------------
 163 
# The following is currently useless and is kept for
 164 
# reference purposes (np.clip takes care of clipping)
 165 
# ----------------------------------------------------
 166 
#def negative_to_zero(img: np.array) -> np.array:
 167 
#    img = img.copy()
 168 
#    img[img < 0] = 0
 169 
#    return img
 170 
 
 171 
#===========================================================
 172 
# Open image as PIL Image and make Numpy array version too
 173 
#===========================================================
 174 
pI = Image.open('C:/Conv_Python/images/1.jpg')
 175 
img = np.array(pI)
 176 
 
 177 
plot_image(img=img) 
 178 
#------------------> don't use a cmap such as cmap='gray_r' as 3rd parameter
 179 
plt.imsave(fname='_original.png', arr=img, format='png')
 180 
 
 181 
#===================================
 182 
#  S H A R P E N E D
 183 
#===================================
 184 
Curr_Title="Cat Image - Sharpened"
 185 
img_sharpened = convolve(img=img, kernel=sharpen)
 186 
plt.imsave(fname='_sharpened.png', arr=img_sharpened, format='png')
 187 
 
 188 
plot_two_images(
 189 
    img1=img,
 190 
    img2=img_sharpened,
 191 
    imm_name=Curr_Title
 192 
)       
 193 
 
 194 
#===================================
 195 
#  S H A R P E N E D
 196 
#        vs.
 197 
#  SHARPENED AND NORMALIZED
 198 
#===================================
 199 
# Now useless, images are normalized in the
 200 
# convolve() function
 201 
#
 202 
# NORMALIZE
 203 
#img_shar_nor = cv2.normalize(img_sharpened,  None, 0, 255, cv2.NORM_MINMAX)
 204 
 
 205 
#plot_two_images(
 206 
#    img1=img_sharpened,
 207 
#    img2=img_shar_nor
 208 
#
 209 
 
 210 
#===================================
 211 
#  B L U R R E D
 212 
#===================================
 213 
Curr_Title="Cat Image - Blurred"
 214 
img_blurred = convolve(img=img, kernel=blur)
 215 
plt.imsave(fname='_blurred.png', arr=img_blurred, format='png')
 216 
 
 217 
plot_two_images(
 218 
    img1=img,
 219 
    img2=img_blurred,
 220 
    imm_name=Curr_Title
 221 
)
 222 
 
 223 
#===================================
 224 
#  O U T L I N E D
 225 
#===================================
 226 
Curr_Title="Cat Image - Outlined"
 227 
img_outlined = convolve(img=img, kernel=outline)
 228 
#plt.imsave(fname='_outlined.png', arr=img_outlined, cmap='gray_r', format='png')
 229 
plt.imsave(fname='_outlined.png', arr=img_outlined, format='png')
 230 
 
 231 
plot_two_images(
 232 
    img1=img,
 233 
    img2=img_outlined,
 234 
    imm_name=Curr_Title
 235 
)
 236 
 
 237 
#===================================
 238 
#  NEG_TO_ZERO OUTLINED
 239 
#===================================
 240 
#img_neg_to_z_OUT = negative_to_zero(img=img_outlined)
 241 
#plt.imsave(fname='_neg_to_z_OUT.png', arr=img_neg_to_z_OUT, format='png')
 242 
#
 243 
#plot_two_images(
 244 
#    img1=img,
 245 
#    img2=img_neg_to_z_OUT
 246 
#)