在 Python 中添加具有不同粒度的噪声

Add noise with varying grain size in Python

我正在尝试向图像中添加噪点,以模仿在相机中设置高 ISO 时产生的真实世界噪点。

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)

该代码运行良好,但噪声颗粒的大小始终为 1 个像素。我真的想要不同大小的噪声颗粒。我怎样才能做到这一点?

模仿高 ISO 设置的不同粒度噪声非常具有挑战性。
原因之一是变化纹路的来源并非纯粹的物理效应。
一些颗粒来自不同相机的数字降噪(图像处理)伪像。

我想到了一个比较简单的解决方案:

  • 在不同的分辨率下添加随机噪声。
  • 将不同的分辨率调整为原始图像大小。
  • 将调整大小后的图像与“噪声图像”相加(均值为零)。
  • 将“噪声图像”添加到原始(干净)图像。

需要进行大量调整 - select调整分辨率,将不同的噪声设置为不同的分辨率,select 调整大小插值方法...

我认为它不会完全符合您的要求,但它应用了“具有不同粒度的噪声”,并且可能会给您带来领先。


代码示例:

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)

结果:

您的问题表明您需要空间相关的噪声,相邻像素由此共享一些信息。 如果你真的不关心那个相关结构是什么样子,你可以使用一个简单的平滑内核来生成更粗粒度的噪声。

实现这一目标的一种方法是:

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")

或者,如果您知道相机中的噪声来自何处,则可以根据第一原理提出更好的噪声模型。这里有一些想法:https://graphics.stanford.edu/courses/cs178-10/lectures/noise-27apr10-150dpi-med.pdf .

Rotem的答案是最好的实现。

我(原发布者)使用以下代码来扩展他对彩色图像的实现并使用 PIL 作为导入,以防万一以后有人需要它:


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)