0
votes
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.

1
Can you describe exactly what you mean by "stuck".Simon P
i get an error - File "std_img.py", line 26, in <module> std_blue = np.append(std_blue, np.append(lst), axis = 0) File "<__array_function__ internals>", line 4, in append TypeError: _append_dispatcher() missing 1 required positional argument: 'values'arbel
But I tried the same function on a small array and it worked. So i think maybe the picture is too big to deal with in this way but i dont know what else i can doarbel
What is STD please?Mark Setchell
Its a short for standard deviation. There is a function in python for this (like the way i did in my code)arbel

1 Answers

1
votes

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