mirror of
https://github.com/wassname/scikit-image.git
synced 2026-08-01 12:50:48 +08:00
180 lines
6.7 KiB
Python
180 lines
6.7 KiB
Python
import numpy as np
|
|
from .dtype import img_as_float
|
|
|
|
|
|
__all__ = ['random_noise']
|
|
|
|
|
|
def random_noise(image, mode='gaussian', seed=None, clip=True, **kwargs):
|
|
"""
|
|
Function to add random noise of various types to a floating-point image.
|
|
|
|
Parameters
|
|
----------
|
|
image : ndarray
|
|
Input image data. Will be converted to float.
|
|
mode : str
|
|
One of the following strings, selecting the type of noise to add:
|
|
|
|
'gaussian' Gaussian-distributed additive noise.
|
|
'poisson' Poisson-distributed noise generated from the data.
|
|
'salt' Replaces random pixels with 1.
|
|
'pepper' Replaces random pixels with 0.
|
|
's&p' Replaces random pixels with 0 or 1.
|
|
'speckle' Multiplicative noise using out = image + n*image, where
|
|
n is uniform noise with specified mean & variance.
|
|
seed : int
|
|
If provided, this will set the random seed before generating noise,
|
|
for valid pseudo-random comparisons.
|
|
clip : bool
|
|
If True (default), the output will be clipped after noise applied
|
|
for modes `'speckle'`, `'poisson'`, and `'gaussian'`. This is
|
|
needed to maintain the proper image data range. If False, clipping
|
|
is not applied, and the output may extend beyond the range [-1, 1].
|
|
mean : float
|
|
Mean of random distribution. Used in 'gaussian' and 'speckle'.
|
|
Default : 0.
|
|
var : float
|
|
Variance of random distribution. Used in 'gaussian' and 'speckle'.
|
|
Note: variance = (standard deviation) ** 2. Default : 0.01
|
|
amount : float
|
|
Proportion of image pixels to replace with noise on range [0, 1].
|
|
Used in 'salt', 'pepper', and 'salt & pepper'. Default : 0.05
|
|
salt_vs_pepper : float
|
|
Proportion of salt vs. pepper noise for 's&p' on range [0, 1].
|
|
Higher values represent more salt. Default : 0.5 (equal amounts)
|
|
|
|
Returns
|
|
-------
|
|
out : ndarray
|
|
Output floating-point image data on range [0, 1] or [-1, 1] if the
|
|
input `image` was unsigned or signed, respectively.
|
|
|
|
Notes
|
|
-----
|
|
Speckle, Poisson, and Gaussian noise may generate noise outside the valid
|
|
image range. The default is to clip (not alias) these values, but they may
|
|
be preserved by setting `clip=False`. Note that in this case the output
|
|
may contain values outside the ranges [0, 1] or [-1, 1]. Use with care.
|
|
|
|
Because of the prevalence of exclusively positive floating-point images in
|
|
intermediate calculations, it is not possible to intuit if an input is
|
|
signed based on dtype alone. Instead, negative values are explicity
|
|
searched for. Only if found does this function assume signed input.
|
|
Unexpected results only occur in rare, poorly exposes cases (e.g. if all
|
|
values are above 50 percent gray in a signed `image`). In this event,
|
|
manually scaling the input to the positive domain will solve the problem.
|
|
|
|
The Poisson distribution is only defined for positive integers. To apply
|
|
this noise type, the number of unique values in the image is found and
|
|
the next round power of two is used to scale up the floating-point result,
|
|
after which it is scaled back down to the floating-point image range.
|
|
|
|
To generate Poisson noise against a signed image, the signed image is
|
|
temporarily converted to an unsigned image in the floating point domain,
|
|
Poisson noise is generated, then it is returned to the original range.
|
|
|
|
"""
|
|
mode = mode.lower()
|
|
|
|
# Detect if a signed image was input
|
|
if image.min() < 0:
|
|
low_clip = -1.
|
|
else:
|
|
low_clip = 0.
|
|
|
|
image = img_as_float(image)
|
|
if seed is not None:
|
|
np.random.seed(seed=seed)
|
|
|
|
allowedtypes = {
|
|
'gaussian': 'gaussian_values',
|
|
'poisson': 'poisson_values',
|
|
'salt': 'sp_values',
|
|
'pepper': 'sp_values',
|
|
's&p': 's&p_values',
|
|
'speckle': 'gaussian_values'}
|
|
|
|
kwdefaults = {
|
|
'mean': 0.,
|
|
'var': 0.01,
|
|
'amount': 0.05,
|
|
'salt_vs_pepper': 0.5}
|
|
|
|
allowedkwargs = {
|
|
'gaussian_values': ['mean', 'var'],
|
|
'sp_values': ['amount'],
|
|
's&p_values': ['amount', 'salt_vs_pepper'],
|
|
'poisson_values': []}
|
|
|
|
for key in kwargs:
|
|
if key not in allowedkwargs[allowedtypes[mode]]:
|
|
raise ValueError('%s keyword not in allowed keywords %s' %
|
|
(key, allowedkwargs[allowedtypes[mode]]))
|
|
|
|
# Set kwarg defaults
|
|
for kw in allowedkwargs[allowedtypes[mode]]:
|
|
kwargs.setdefault(kw, kwdefaults[kw])
|
|
|
|
if mode == 'gaussian':
|
|
noise = np.random.normal(kwargs['mean'], kwargs['var'] ** 0.5,
|
|
image.shape)
|
|
out = image + noise
|
|
|
|
elif mode == 'poisson':
|
|
# Determine unique values in image & calculate the next power of two
|
|
vals = len(np.unique(image))
|
|
vals = 2 ** np.ceil(np.log2(vals))
|
|
|
|
# Ensure image is exclusively positive
|
|
if low_clip == -1.:
|
|
old_max = image.max()
|
|
image = (image + 1.) / (old_max + 1.)
|
|
|
|
# Generating noise for each unique value in image.
|
|
out = np.random.poisson(image * vals) / float(vals)
|
|
|
|
# Return image to original range if input was signed
|
|
if low_clip == -1.:
|
|
out = out * (old_max + 1.) - 1.
|
|
|
|
elif mode == 'salt':
|
|
# Re-call function with mode='s&p' and p=1 (all salt noise)
|
|
out = random_noise(image, mode='s&p', seed=seed,
|
|
amount=kwargs['amount'], salt_vs_pepper=1.)
|
|
|
|
elif mode == 'pepper':
|
|
# Re-call function with mode='s&p' and p=1 (all pepper noise)
|
|
out = random_noise(image, mode='s&p', seed=seed,
|
|
amount=kwargs['amount'], salt_vs_pepper=0.)
|
|
|
|
elif mode == 's&p':
|
|
# This mode makes no effort to avoid repeat sampling. Thus, the
|
|
# exact number of replaced pixels is only approximate.
|
|
out = image.copy()
|
|
|
|
# Salt mode
|
|
num_salt = np.ceil(
|
|
kwargs['amount'] * image.size * kwargs['salt_vs_pepper'])
|
|
coords = [np.random.randint(0, i - 1, int(num_salt))
|
|
for i in image.shape]
|
|
out[coords] = 1
|
|
|
|
# Pepper mode
|
|
num_pepper = np.ceil(
|
|
kwargs['amount'] * image.size * (1. - kwargs['salt_vs_pepper']))
|
|
coords = [np.random.randint(0, i - 1, int(num_pepper))
|
|
for i in image.shape]
|
|
out[coords] = low_clip
|
|
|
|
elif mode == 'speckle':
|
|
noise = np.random.normal(kwargs['mean'], kwargs['var'] ** 0.5,
|
|
image.shape)
|
|
out = image + image * noise
|
|
|
|
# Clip back to original range, if necessary
|
|
if clip:
|
|
out = np.clip(out, low_clip, 1.0)
|
|
|
|
return out
|