diff --git a/095_what_is_convolution.py b/095_what_is_convolution.py new file mode 100644 index 000000000..a5584bde8 --- /dev/null +++ b/095_what_is_convolution.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +__author__ = "Sreenivas Bhattiprolu" +__license__ = "Feel free to copy, I appreciate if you acknowledge Python for Microscopists" + +# https://youtu.be/1GUgD2SBl9A + +""" +Spyder Editor + +scipy.signal.convolve2d - https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.convolve2d.html +scipy.ndimage.filters.convolve +cv2.filter2D - https://docs.opencv.org/2.4/modules/imgproc/doc/filtering.html?highlight=filter2d#filter2d + +""" +import cv2 +import numpy as np +from scipy.signal import convolve2d +from scipy.ndimage.filters import convolve +from skimage import io, img_as_float + + +img_gaussian_noise = img_as_float(io.imread('images/BSE_25sigma_noisy.jpg', as_gray=True)) +img_salt_pepper_noise = img_as_float(io.imread('images/BSE_salt_pepper.jpg', as_gray=True)) + +img = img_salt_pepper_noise + +#convolving the image with a normalized box filter. +#It simply takes the average of all the pixels under kernel area +#and replaces the central element with this average. +#Can also use cv2.blur() to directly apply the convolution rather than defining +#kernel and applying it as a filter (or convolution) separately. +kernel = np.ones((5,5),np.float32)/25 #Averaging filter with 5x5 kernel +#Normalize by dividing with 25 so all numbers add to 1 +gaussian_kernel = np.array([[1/16, 1/8, 1/16], #3x3 kernel + [1/8, 1/4, 1/8], + [1/16, 1/8, 1/16]]) + +laplacian = np.array([[0.,1,0], [1,-4,1], [0,1,0]]) +gabor = cv2.getGaborKernel((5, 5), 1.4, 45, 5, 1) + +""" +Results from all approaches would be the same.. +Remember that if the padding (border) is not done then the output image +and values would be different + +""" + +conv_using_cv2 = cv2.filter2D(img, -1, kernel, borderType=cv2.BORDER_CONSTANT) +# when ddepth=-1, the output image will have the same depth as the source +#example, if input is float64 then output will also be float64 +# BORDER_CONSTANT - Pad the image with a constant value (i.e. black or 0) +#BORDER_REPLICATE: The row or column at the very edge of the original is replicated to the extra border. + +conv_using_scipy = convolve2d(img, kernel, mode='same') +#mode ="same" - pads image so the output is same as input + +conv_using_scipy2 = convolve(img, kernel, mode='constant', cval=0.0) +#mode=constant adds a constant value at the borders. + + +cv2.imshow("Original", img) +cv2.imshow("cv2 filter", conv_using_cv2) +cv2.imshow("Using scipy", conv_using_scipy) +cv2.imshow("Using scipy2", conv_using_scipy2) + +cv2.waitKey(0) +cv2.destroyAllWindows() + diff --git a/096_What is Gaussian denoising.py b/096_What is Gaussian denoising.py new file mode 100644 index 000000000..7e140ceac --- /dev/null +++ b/096_What is Gaussian denoising.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +__author__ = "Sreenivas Bhattiprolu" +__license__ = "Feel free to copy, I appreciate if you acknowledge Python for Microscopists" + +# https://youtu.be/xCHbcVUCYBI + +""" + +cv2.filter2D - https://docs.opencv.org/2.4/modules/imgproc/doc/filtering.html?highlight=filter2d#filter2d +cv2.GaussianBlur - https://www.tutorialkart.com/opencv/python/opencv-python-gaussian-image-smoothing/ +skimage.filters.gaussian - https://scikit-image.org/docs/dev/api/skimage.filters.html#skimage.filters.gaussian +""" +import cv2 +import numpy as np +from skimage import io, img_as_float +from skimage.filters import gaussian + +img_gaussian_noise = img_as_float(io.imread('images/BSE_25sigma_noisy.jpg', as_gray=True)) +img_salt_pepper_noise = img_as_float(io.imread('images/BSE_salt_pepper.jpg', as_gray=True)) + +img = img_gaussian_noise + +gaussian_kernel = np.array([[1/16, 1/8, 1/16], #3x3 kernel + [1/8, 1/4, 1/8], + [1/16, 1/8, 1/16]]) + + + +conv_using_cv2 = cv2.filter2D(img, -1, gaussian_kernel, borderType=cv2.BORDER_CONSTANT) +# when ddepth=-1, the output image will have the same depth as the source +#example, if input is float64 then output will also be float64 +# BORDER_CONSTANT - Pad the image with a constant value (i.e. black or 0) +#BORDER_REPLICATE: The row or column at the very edge of the original is replicated to the extra border. + +gaussian_using_cv2 = cv2.GaussianBlur(img, (3,3), 0, borderType=cv2.BORDER_CONSTANT) + +gaussian_using_skimage = gaussian(img, sigma=1, mode='constant', cval=0.0) +#sigma defines the std dev of the gaussian kernel. SLightly different than +#how we define in cv2 + + +cv2.imshow("Original", img) +cv2.imshow("cv2 filter", conv_using_cv2) +cv2.imshow("Using cv2 gaussian", gaussian_using_cv2) +cv2.imshow("Using skimage", gaussian_using_skimage) +#cv2.imshow("Using scipy2", conv_using_scipy2) + +cv2.waitKey(0) +cv2.destroyAllWindows() + diff --git a/097_What is Median denoising.py b/097_What is Median denoising.py new file mode 100644 index 000000000..381a59da8 --- /dev/null +++ b/097_What is Median denoising.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +__author__ = "Sreenivas Bhattiprolu" +__license__ = "Feel free to copy, I appreciate if you acknowledge Python for Microscopists" + +# https://youtu.be/StX_1iEO3ck + +""" +Spyder Editor + +cv2.medianBlur - https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_filtering/py_filtering.html +skimage.filters.median - https://scikit-image.org/docs/dev/api/skimage.filters.html#skimage.filters.median + +See how median is much better at cleaning salt and pepper noise compared to Gaussian +""" +import cv2 +import numpy as np +from scipy.ndimage.filters import convolve +from skimage import io +from skimage.filters import median + +#img = io.imread('images/einstein.jpg', as_gray=True) + +#Needs 8 bit, not float. +img_gaussian_noise = cv2.imread('images/BSE_25sigma_noisy.jpg', 0) +img_salt_pepper_noise = cv2.imread('images/BSE_salt_pepper.jpg', 0) + +img = img_salt_pepper_noise + + +median_using_cv2 = cv2.medianBlur(img, 3) + +from skimage.morphology import disk +median_using_skimage = median(img, disk(3), mode='constant', cval=0.0) + + +cv2.imshow("Original", img) +cv2.imshow("cv2 median", median_using_cv2) +cv2.imshow("Using skimage median", median_using_skimage) + +cv2.waitKey(0) +cv2.destroyAllWindows() + diff --git a/098_What is Bilateral denoising.py b/098_What is Bilateral denoising.py new file mode 100644 index 000000000..331c4cd0a --- /dev/null +++ b/098_What is Bilateral denoising.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +__author__ = "Sreenivas Bhattiprolu" +__license__ = "Feel free to copy, I appreciate if you acknowledge Python for Microscopists" + +# https://youtu.be/yenye2s90BA + +""" +Spyder Editor + +cv2.cv2.bilateralFilter - https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_filtering/py_filtering.html +skimage bilateral - https://scikit-image.org/docs/dev/auto_examples/filters/plot_denoise.html + +https://people.csail.mit.edu/sparis/bf_course/course_notes.pdf + +Bilateral is slow and not very efficient at salt and pepper +""" +import cv2 +import numpy as np +from scipy.ndimage.filters import convolve +from skimage import io +from skimage.filters import median + + +img_gaussian_noise = cv2.imread('images/BSE_25sigma_noisy.jpg', 0) +img_salt_pepper_noise = cv2.imread('images/BSE_salt_pepper.jpg', 0) + +img = img_salt_pepper_noise + +bilateral_using_cv2 = cv2.bilateralFilter(img, 5, 20, 100, borderType=cv2.BORDER_CONSTANT) + +#d - diameter of each pixel neighborhood used during filtering +# sigmaCOlor - Sigma of grey/color space. +#sigmaSpace - Large value means farther pixels influence each other (as long as the colors are close enough) + + +from skimage.restoration import denoise_bilateral +bilateral_using_skimage = denoise_bilateral(img, sigma_color=0.05, sigma_spatial=15, + multichannel=False) + +#sigma_color = float - Sigma for grey or color value. +#For large sigma_color values the filter becomes closer to gaussian blur. +#sigma_spatial: float. Standard ev. for range distance. Increasing this smooths larger features. + + + + +cv2.imshow("Original", img) +cv2.imshow("cv2 bilateral", bilateral_using_cv2) +cv2.imshow("Using skimage bilateral", bilateral_using_skimage) + +cv2.waitKey(0) +cv2.destroyAllWindows() + diff --git a/099_WHat is NLM.py b/099_WHat is NLM.py new file mode 100644 index 000000000..525ced378 --- /dev/null +++ b/099_WHat is NLM.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +__author__ = "Sreenivas Bhattiprolu" +__license__ = "Feel free to copy, I appreciate if you acknowledge Python for Microscopists" + +# https://youtu.be/Va4Rwoy1v88 + +""" + +https://scikit-image.org/docs/dev/auto_examples/filters/plot_nonlocal_means.html + +Works well for random gaussian noise but not as good for salt and pepper +https://www.iro.umontreal.ca/~mignotte/IFT6150/Articles/Buades-NonLocal.pdf + +The non-local means algorithm replaces the value of a pixel by an average +of a selection of other pixels values: small patches centered on the other +pixels are compared to the patch centered on the pixel of interest, and the +average is performed only for pixels that have patches close to the current patch. +""" +import cv2 +import numpy as np +from skimage import io, img_as_float +from skimage.restoration import denoise_nl_means, estimate_sigma + +img_gaussian_noise = img_as_float(io.imread('images/BSE_25sigma_noisy.jpg', as_gray=True)) +img_salt_pepper_noise = img_as_float(io.imread('images/BSE_salt_pepper.jpg', as_gray=True)) + +img = img_gaussian_noise + +sigma_est = np.mean(estimate_sigma(img, multichannel=True)) +#sigma_est = 0.1 + +denoise_img = denoise_nl_means(img, h=1.15 * sigma_est, fast_mode=True, + patch_size=5, patch_distance=3, multichannel=False) + +""" +When the fast_mode argument is False, a spatial Gaussian weighting is applied +to the patches when computing patch distances. When fast_mode is True a +faster algorithm employing uniform spatial weighting on the patches is applied. + +Larger h allows more smoothing between disimilar patches. + +""" + +#denoise_img_as_8byte = img_as_ubyte(denoise_img) + + + +cv2.imshow("Original", img) +cv2.imshow("NLM Filtered", denoise_img) +cv2.waitKey(0) +cv2.destroyAllWindows() + diff --git a/100_What is Total Variation denoising.py b/100_What is Total Variation denoising.py new file mode 100644 index 000000000..f5f843737 --- /dev/null +++ b/100_What is Total Variation denoising.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +__author__ = "Sreenivas Bhattiprolu" +__license__ = "Feel free to copy, I appreciate if you acknowledge Python for Microscopists" + +# https://youtu.be/G39dVoiivZk + +""" + +Works well for random gaussian noise but not as good for salt and pepper + +https://hal.archives-ouvertes.fr/hal-00437581/document +""" +import cv2 +import numpy as np +from skimage import io, img_as_float +from skimage.restoration import denoise_tv_chambolle +from matplotlib import pyplot as plt + +img = img_as_float(io.imread('images/BSE_25sigma_noisy.jpg', as_gray=True)) + + +plt.hist(img.flat, bins=100, range=(0,1)) #.flat returns the flattened numpy array (1D) + + +denoise_img = denoise_tv_chambolle(img, weight=0.1, eps=0.0002, n_iter_max=200, multichannel=False) + +""" +denoise_tv_chambolle(image, weight=0.1, eps=0.0002, n_iter_max=200, multichannel=False) +weight: The greater weight, the more denoising (at the expense of fidelity to input). +eps: Relative difference of the value of the cost function that determines the stop criterion. +n_iter_max: Max number of iterations used for optimization + +""" + + +plt.hist(denoise_img.flat, bins=100, range=(0,1)) #.flat returns the flattened numpy array (1D) + + +cv2.imshow("Original", img) +cv2.imshow("TV Filtered", denoise_img) +cv2.waitKey(0) +cv2.destroyAllWindows() + diff --git a/101-What is BM3D.py b/101-What is BM3D.py new file mode 100644 index 000000000..4579ab39e --- /dev/null +++ b/101-What is BM3D.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +__author__ = "Sreenivas Bhattiprolu" +__license__ = "Feel free to copy, I appreciate if you acknowledge Python for Microscopists" + +# https://youtu.be/aJrG8IH81SY + +""" + +http://www.cs.tut.fi/~foi/papers/ICIP2019_Ymir.pdf + +""" + +import matplotlib.pyplot as plt +from skimage import io, img_as_float +from skimage.metrics import peak_signal_noise_ratio +import bm3d +import cv2 + +noisy_img = img_as_float(io.imread("images/BSE_25sigma_noisy.jpg", as_gray=True)) + +BM3D_denoised_image = bm3d.bm3d(noisy_img, sigma_psd=0.2, stage_arg=bm3d.BM3DStages.HARD_THRESHOLDING) + +""" +bm3d library is not well documented yet, but looking into source code.... +sigma_psd - noise standard deviation +stage_arg: Determines whether to perform hard-thresholding or Wiener filtering. +stage_arg = BM3DStages.HARD_THRESHOLDING or BM3DStages.ALL_STAGES (slow but powerful) +All stages performs both hard thresholding and Wiener filtering. +""" + +cv2.imshow("Original", noisy_img) +cv2.imshow("Denoised", BM3D_denoised_image) +cv2.waitKey(0) +cv2.destroyAllWindows() \ No newline at end of file diff --git a/95_what_is_convolution.py b/95_what_is_convolution.py new file mode 100644 index 000000000..a5584bde8 --- /dev/null +++ b/95_what_is_convolution.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +__author__ = "Sreenivas Bhattiprolu" +__license__ = "Feel free to copy, I appreciate if you acknowledge Python for Microscopists" + +# https://youtu.be/1GUgD2SBl9A + +""" +Spyder Editor + +scipy.signal.convolve2d - https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.convolve2d.html +scipy.ndimage.filters.convolve +cv2.filter2D - https://docs.opencv.org/2.4/modules/imgproc/doc/filtering.html?highlight=filter2d#filter2d + +""" +import cv2 +import numpy as np +from scipy.signal import convolve2d +from scipy.ndimage.filters import convolve +from skimage import io, img_as_float + + +img_gaussian_noise = img_as_float(io.imread('images/BSE_25sigma_noisy.jpg', as_gray=True)) +img_salt_pepper_noise = img_as_float(io.imread('images/BSE_salt_pepper.jpg', as_gray=True)) + +img = img_salt_pepper_noise + +#convolving the image with a normalized box filter. +#It simply takes the average of all the pixels under kernel area +#and replaces the central element with this average. +#Can also use cv2.blur() to directly apply the convolution rather than defining +#kernel and applying it as a filter (or convolution) separately. +kernel = np.ones((5,5),np.float32)/25 #Averaging filter with 5x5 kernel +#Normalize by dividing with 25 so all numbers add to 1 +gaussian_kernel = np.array([[1/16, 1/8, 1/16], #3x3 kernel + [1/8, 1/4, 1/8], + [1/16, 1/8, 1/16]]) + +laplacian = np.array([[0.,1,0], [1,-4,1], [0,1,0]]) +gabor = cv2.getGaborKernel((5, 5), 1.4, 45, 5, 1) + +""" +Results from all approaches would be the same.. +Remember that if the padding (border) is not done then the output image +and values would be different + +""" + +conv_using_cv2 = cv2.filter2D(img, -1, kernel, borderType=cv2.BORDER_CONSTANT) +# when ddepth=-1, the output image will have the same depth as the source +#example, if input is float64 then output will also be float64 +# BORDER_CONSTANT - Pad the image with a constant value (i.e. black or 0) +#BORDER_REPLICATE: The row or column at the very edge of the original is replicated to the extra border. + +conv_using_scipy = convolve2d(img, kernel, mode='same') +#mode ="same" - pads image so the output is same as input + +conv_using_scipy2 = convolve(img, kernel, mode='constant', cval=0.0) +#mode=constant adds a constant value at the borders. + + +cv2.imshow("Original", img) +cv2.imshow("cv2 filter", conv_using_cv2) +cv2.imshow("Using scipy", conv_using_scipy) +cv2.imshow("Using scipy2", conv_using_scipy2) + +cv2.waitKey(0) +cv2.destroyAllWindows() + diff --git a/images/BSE_25sigma_noisy.jpg b/images/BSE_25sigma_noisy.jpg new file mode 100644 index 000000000..a736ae1fc Binary files /dev/null and b/images/BSE_25sigma_noisy.jpg differ diff --git a/images/BSE_salt_pepper.jpg b/images/BSE_salt_pepper.jpg new file mode 100644 index 000000000..10cf4dda1 Binary files /dev/null and b/images/BSE_salt_pepper.jpg differ