Reputation: 348
I am trying to add noise into image to imitate real world noise created by having high ISO settings in camera.
from skimage.util import random_noise
import random
val = random.uniform(0.036, 0.107)
noisy_img = random_noise(im_arr, mode='gaussian', var=val ** 2)
noisy_img = (255 * noisy_img).astype(np.uint8)
That code works fine, but the size of the noise grain is always 1 pixel. I really want to have a varying size of the noise grain. How can I achieve that?
Upvotes: 1
Views: 2859
Reputation: 348
Rotem's answer is the best implementation.
I (the original poster) use the following code to expand on his implementation for colored images and using PIL as import, just in case anyone need it later:
from skimage.transform import resize
import numpy as np
from skimage.util import random_noise
from PIL import Image
def gen_noise_mask(rows, cols):
val = 0.036 # random.uniform(0.036, 0.107) # Use constant variance (for testing).
# Full resolution
noise_im1 = np.zeros((rows, cols))
noise_im1 = random_noise(noise_im1, mode='gaussian', var=val ** 2, clip=False)
# Half resolution
noise_im2 = np.zeros((rows // 2, cols // 2))
noise_im2 = random_noise(noise_im2, mode='gaussian', var=(val * 2) ** 2, clip=False) # Use val*2 (needs tuning...)
noise_im2 = resize(noise_im2, (rows, cols)) # Upscale to original image size
# Quarter resolution
noise_im3 = np.zeros((rows // 4, cols // 4))
noise_im3 = random_noise(noise_im3, mode='gaussian', var=(val * 4) ** 2, clip=False) # Use val*4 (needs tuning...)
noise_im3 = resize(noise_im3, (rows, cols)) # What is the interpolation method?
noise_im = noise_im1 + noise_im2 + noise_im3 # Sum the noise in multiple resolutions (the mean of noise_im is around zero).
return noise_im
def noiseGenerator(im):
im_arr = np.asarray(im)
rows, cols, depth = im_arr.shape
rgba_array = np.zeros((rows, cols, depth), 'float64')
for d in range(0, depth):
rgba_array[..., d] += gen_noise_mask(rows, cols)
noisy_img = im_arr / 255 + rgba_array # Add noise_im to the input image.
noisy_img = np.round((255 * noisy_img)).clip(0, 255).astype(np.uint8)
return Image.fromarray(noisy_img)
Upvotes: 1
Reputation: 12026
Your question suggests that you want spatially correlated noise, whereby neighboring pixels share some information. If you don't really care about what that correlation structure looks like, you can use a simple smoothing kernel to generate noise with coarser granularity.
One way to achieve that would be:
from skimage.data import shepp_logan_phantom
from skimage.util import random_noise
from scipy.ndimage import correlate
import numpy as np
# Granularity = 1
im_arr = shepp_logan_phantom()
val = 0.05
noisy_img = random_noise(im_arr, mode='gaussian', var=val)
# Correlated noise to increase granularity
# Generate random noise like skimage's random_noise does
noise = np.random.normal(scale=np.sqrt(val), size=im_arr.shape)
# Create a smoothing kernel
weights = np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0]]) / 5
# Apply it to the noise
noise_corr = correlate(noise, weights)
# Apply noise to image and clip
noisy_img_corr = np.clip(im_arr + noise_corr, 0, 1)
fig, (ax1, ax2) = plt.subplots(ncols=2)
ax1.imshow(noisy_img)
ax1.set_title("Uncorrelated noise")
ax1.axis("off")
ax2.imshow(noisy_img_corr)
ax2.set_title("Correlated noise")
ax2.axis("off")
Or you could come up with better noise model from first principles if you know where the noise in your camera is coming from. There are some ideas here: https://graphics.stanford.edu/courses/cs178-10/lectures/noise-27apr10-150dpi-med.pdf .
Upvotes: 1
Reputation: 32094
It's very challenging to imitate the varying grain size noise of high ISO settings.
One of the reasons is that the source of the varying grain is not purely physical effect.
Some of the grain comes from digital noise reduction (image processing) artifacts that are different from camera to camera.
I thought about a relatively simple solution:
A lot of tuning is required - selecting the resolutions, setting different noise to different resolutions, select the resizing interpolation method...
I don't think it's going to be exactly what you are looking for, but it applies "noise with varying grain size", and may give you a lead.
Code sample:
from skimage.util import random_noise
from skimage.io import imsave
from skimage.transform import resize
import random
import numpy as np
im_arr = np.full((256, 320), 0.5) # Original image - use gray image for testing
rows, cols = im_arr.shape
val = 0.036 #random.uniform(0.036, 0.107) # Use constant variance (for testing).
# Full resolution
noise_im1 = np.zeros((rows, cols))
noise_im1 = random_noise(noise_im1, mode='gaussian', var=val**2, clip=False)
# Half resolution
noise_im2 = np.zeros((rows//2, cols//2))
noise_im2 = random_noise(noise_im2, mode='gaussian', var=(val*2)**2, clip=False) # Use val*2 (needs tuning...)
noise_im2 = resize(noise_im2, (rows, cols)) # Upscale to original image size
# Quarter resolution
noise_im3 = np.zeros((rows//4, cols//4))
noise_im3 = random_noise(noise_im3, mode='gaussian', var=(val*4)**2, clip=False) # Use val*4 (needs tuning...)
noise_im3 = resize(noise_im3, (rows, cols)) # What is the interpolation method?
noise_im = noise_im1 + noise_im2 + noise_im3 # Sum the noise in multiple resolutions (the mean of noise_im is around zero).
noisy_img = im_arr + noise_im # Add noise_im to the input image.
noisy_img = np.round((255 * noisy_img)).clip(0, 255).astype(np.uint8)
imsave('noisy_img.png', noisy_img)
Upvotes: 3