arbel
arbel

Reputation: 5

Smoothing Process Over an Image Using STD

import cv2

import numpy as np import scipy import matplotlib.pyplot as plot from PIL import Image from skimage import io import glob from skimage import io, img_as_float

img = cv2.imread(r"C:\Users\hoday\Desktop\GRAPE_IMG\GrapeBox.jpg", 1) img = np.float32(img)

blue_img, green_img, red_img = cv2.split(img) #split the image into 3 channels - BGR

channels = [blue_img, green_img, red_img] std_channels = []

for channel in channels:

lst = []           

img_row = (channel.shape)[0]
img_col = (channel.shape)[1]

for row in range(1, img_row-1): #for 5X5 1--->2
    for col in range(1, img_col-1): #for 5X5 1--->2
        mask = channel[row-1:row+2, col-1:col+2] #for 5X5 ---> mask = channel[row-2:row+3, col-2:col+3]
        lst.append(mask.std())
        
std_channel = np.array(lst)        
std_channel = std_channel.reshape(img_row-2, img_col-2) #for 5X5 2--->4
std_channels.append(std_channel)

img_merged = cv2.merge(std_channels) cv2.imwrite(r"C:\Users\hoday\Desktop\GRAPE_IMG\normalStd.png", img_merged)

Hi, I'm trying to go through the pixels of an image and take out a new image consisting of std Just like what was done in the next video :

https://www.youtube.com/watch?v=ZoaEDbivmOE&t=80s

I split the image into 3 channels and tried to update a new array for each channel but when I try to run the code I get stuck. For example Instead of print the value 5.58 for img[0:3, 0:3].std() like it is supposed to do, the value that it's print is 1.28 - now that is the value of std for one channel but i want the merge one with all the three ...

Probably i dont merge the channels in the right way but i have no idea how else i can do it.

Upvotes: 0

Views: 329

Answers (1)

Mark Setchell
Mark Setchell

Reputation: 207465

Updated Answer

You can get it down to under 1ms with Numba:

#!/usr/bin/env python3

import cv2
import numba as nb
import numpy as np
import math

@nb.jit(nopython=True, parallel=True, fastmath=True)
def numbaStd(im):
    # Allocate space for result
    res = np.zeros_like(im)
    h, w, c = im.shape
    for row in nb.prange(1,h-1):
        for col in range(1,w-1):
            for band in range(c):
                x0 = im[row-1,col-1,band]
                x1 = im[row-1,col  ,band]
                x2 = im[row-1,col+1,band]
                x3 = im[row  ,col-1,band]
                x4 = im[row  ,col  ,band]
                x5 = im[row  ,col+1,band]
                x6 = im[row+1,col-1,band]
                x7 = im[row+1,col  ,band]
                x8 = im[row+1,col+1,band]
                mu = (x0 + x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8) / 9
                S  = (x0 - mu)**2
                S += (x1 - mu)**2
                S += (x2 - mu)**2
                S += (x3 - mu)**2
                S += (x4 - mu)**2
                S += (x5 - mu)**2
                S += (x6 - mu)**2
                S += (x7 - mu)**2
                S += (x8 - mu)**2
                res[row,col,band] = math.sqrt(S/9)
    return res

# Open image and make into Numpy array
im  = cv2.imread('grapes.jpg', cv2.IMREAD_COLOR)

# Call Numba standard deviation
res = numbaStd(im)

# Optional block to contrast stretch result image
res[...,0] = 255.0*res[...,0]/res[...,0].max()
res[...,1] = 255.0*res[...,1]/res[...,1].max()
res[...,2] = 255.0*res[...,2]/res[...,2].max()

# Save result
cv2.imwrite("result.png", res)

# IPython timing
#%timeit numbaStd(im)


In [19]: %timeit numbaStd(im)
864 µs ± 20.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

enter image description here


Original Answer

You can more simply use the SciPy generic_filter to pass a 3x3 window over the image like this:

#!/usr/bin/env python3

import numpy as np
from PIL import Image
from scipy.ndimage import generic_filter

# Stddev filter
def stddev(P):
    """
    We receive P[0]..P[8] with the pixels in the 3x3 surrounding window.
    """
    return np.std(P)

# Open image and make into Numpy array
im = Image.open('paddington.png')
im = np.array(im)

# Run 3x3 stddev filter on one band/channel at a time
im[...,0] = generic_filter(im[...,0], stddev, (3, 3))
im[...,1] = generic_filter(im[...,1], stddev, (3, 3))
im[...,2] = generic_filter(im[...,2], stddev, (3, 3))

# Save result
Image.fromarray(im).save('result.png')

That turns this:

enter image description here

into this:

enter image description here


By the way, you can do the same thing, without writing any code, in Terminal with ImageMagick like this:

magick paddington.png -separate -statistic standarddeviation 3x3 -combine result.png

Upvotes: 2

Related Questions