Reputation: 5
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
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)
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:
into this:
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