mirror of
https://github.com/wassname/scikit-image.git
synced 2026-06-30 22:03:45 +08:00
Merge pull request #625 from JDWarner/random_noise
FEAT: generator to add various types of random noise to images
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from .dtype import (img_as_float, img_as_int, img_as_uint, img_as_ubyte,
|
||||
img_as_bool, dtype_limits)
|
||||
from .shape import view_as_blocks, view_as_windows
|
||||
from .noise import random_noise
|
||||
|
||||
import numpy
|
||||
ver = numpy.__version__.split('.')
|
||||
@@ -20,4 +21,5 @@ __all__ = ['img_as_float',
|
||||
'dtype_limits',
|
||||
'view_as_blocks',
|
||||
'view_as_windows',
|
||||
'pad']
|
||||
'pad',
|
||||
'random_noise']
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import numpy as np
|
||||
from .dtype import img_as_float
|
||||
|
||||
|
||||
__all__ = ['random_noise']
|
||||
|
||||
|
||||
def random_noise(image, mode='gaussian', seed=None, **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.
|
||||
m : float
|
||||
Mean of random distribution. Used in 'gaussian' and 'speckle'.
|
||||
v : float
|
||||
Variance of random distribution. Used in 'gaussian' and 'speckle'.
|
||||
Note: variance = (standard deviation) ** 2
|
||||
d : float
|
||||
Proportion of image pixels to replace with noise on range [0, 1].
|
||||
Used in 'salt', 'pepper', and 'salt & pepper'.
|
||||
p : float
|
||||
Proportion of salt vs. pepper noise for 's&p' on range [0, 1].
|
||||
Higher values represent more salt.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray
|
||||
Output floating-point image data on range [0, 1].
|
||||
|
||||
"""
|
||||
mode = mode.lower()
|
||||
image = img_as_float(image)
|
||||
if seed is not None:
|
||||
np.random.seed(seed=seed)
|
||||
|
||||
allowedtypes = {
|
||||
'gaussian': 'gaussian_values',
|
||||
'poisson': '',
|
||||
'salt': 'sp_values',
|
||||
'pepper': 'sp_values',
|
||||
's&p': 's&p_values',
|
||||
'speckle': 'gaussian_values'}
|
||||
|
||||
kwdefaults = {
|
||||
'm': 0.,
|
||||
'v': 0.01,
|
||||
'd': 0.05,
|
||||
'p': 0.5}
|
||||
|
||||
allowedkwargs = {
|
||||
'gaussian_values': ['m', 'v'],
|
||||
'sp_values': ['d'],
|
||||
's&p_values': ['d', 'p']}
|
||||
|
||||
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['m'], kwargs['v'] ** 0.5, image.shape)
|
||||
out = np.clip(image + noise, 0., 1.)
|
||||
|
||||
elif mode == 'poisson':
|
||||
# Generating noise for each unique value in image.
|
||||
out = np.zeros_like(image)
|
||||
for val in np.unique(image):
|
||||
# Generate mask for a unique value, replace w/values drawn from
|
||||
# Poisson distribution about the unique value
|
||||
mask = image == val
|
||||
out[mask] = np.poisson(val, mask.sum())
|
||||
|
||||
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, d=kwargs['d'], p=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, d=kwargs['d'], p=0)
|
||||
|
||||
elif mode == 's&p':
|
||||
out = image.copy()
|
||||
|
||||
# Salt mode
|
||||
num_salt = np.ceil(kwargs['d'] * image.size * kwargs['p'])
|
||||
coords = [np.random.randint(0, i - 1, num_salt)
|
||||
for i in image.shape]
|
||||
out[coords] = 1
|
||||
|
||||
# Pepper mode
|
||||
num_pepper = np.ceil(kwargs['d'] * image.size * (1. - kwargs['p']))
|
||||
coords = [np.random.randint(0, i - 1, num_pepper)
|
||||
for i in image.shape]
|
||||
out[coords] = 0
|
||||
|
||||
elif mode == 'speckle':
|
||||
noise = np.random.normal(kwargs['m'], kwargs['v'] ** 0.5, image.shape)
|
||||
out = np.clip(image + image * noise, 0., 1.)
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,87 @@
|
||||
from numpy.testing import assert_array_equal, assert_allclose
|
||||
|
||||
import numpy as np
|
||||
from skimage.data import camera
|
||||
from skimage.util import random_noise, img_as_float
|
||||
|
||||
|
||||
def test_set_seed():
|
||||
seed = 42
|
||||
cam = camera()
|
||||
test = random_noise(cam, seed=seed)
|
||||
assert_array_equal(test, random_noise(cam, seed=seed))
|
||||
|
||||
|
||||
def test_salt():
|
||||
seed = 42
|
||||
cam = img_as_float(camera())
|
||||
cam_noisy = random_noise(cam, seed=seed, mode='salt', d=0.15)
|
||||
saltmask = cam != cam_noisy
|
||||
|
||||
# Ensure all changes are to 1.0
|
||||
assert_allclose(cam_noisy[saltmask], np.ones(saltmask.sum()))
|
||||
|
||||
# Ensure approximately correct amount of noise was added
|
||||
proportion = float(saltmask.sum()) / (cam.shape[0] * cam.shape[1])
|
||||
assert 0.11 < proportion <= 0.18
|
||||
|
||||
|
||||
def test_pepper():
|
||||
seed = 42
|
||||
cam = img_as_float(camera())
|
||||
cam_noisy = random_noise(cam, seed=seed, mode='pepper', d=0.15)
|
||||
peppermask = cam != cam_noisy
|
||||
|
||||
# Ensure all changes are to 1.0
|
||||
assert_allclose(cam_noisy[peppermask], np.zeros(peppermask.sum()))
|
||||
|
||||
# Ensure approximately correct amount of noise was added
|
||||
proportion = float(peppermask.sum()) / (cam.shape[0] * cam.shape[1])
|
||||
assert 0.11 < proportion <= 0.18
|
||||
|
||||
|
||||
def test_salt_and_pepper():
|
||||
seed = 42
|
||||
cam = img_as_float(camera())
|
||||
cam_noisy = random_noise(cam, seed=seed, mode='s&p', d=0.15, p=0.25)
|
||||
saltmask = np.logical_and(cam != cam_noisy, cam_noisy == 1.)
|
||||
peppermask = np.logical_and(cam != cam_noisy, cam_noisy == 0.)
|
||||
|
||||
# Ensure all changes are to 0. or 1.
|
||||
assert_allclose(cam_noisy[saltmask], np.ones(saltmask.sum()))
|
||||
assert_allclose(cam_noisy[peppermask], np.zeros(peppermask.sum()))
|
||||
|
||||
# Ensure approximately correct amount of noise was added
|
||||
proportion = float(
|
||||
saltmask.sum() + peppermask.sum()) / (cam.shape[0] * cam.shape[1])
|
||||
assert 0.11 < proportion <= 0.18
|
||||
|
||||
# Verify the relative amount of salt vs. pepper is close to expected
|
||||
assert 0.18 < saltmask.sum() / float(peppermask.sum()) < 0.32
|
||||
|
||||
|
||||
def test_gaussian():
|
||||
seed = 42
|
||||
data = np.zeros((128, 128)) + 0.5
|
||||
data_gaussian = random_noise(data, seed=seed, v=0.01)
|
||||
assert 0.008 < data_gaussian.var() < 0.012
|
||||
|
||||
data_gaussian = random_noise(data, seed=seed, m=0.3, v=0.015)
|
||||
assert 0.28 < data_gaussian.mean() - 0.5 < 0.32
|
||||
assert 0.012 < data_gaussian.var() < 0.018
|
||||
|
||||
|
||||
def test_speckle():
|
||||
seed = 42
|
||||
data = np.zeros((128, 128)) + 0.1
|
||||
np.random.seed(seed=42)
|
||||
noise = np.random.normal(0.1, 0.02 ** 0.5, (128, 128))
|
||||
expected = np.clip(data + data * noise, 0, 1)
|
||||
|
||||
data_speckle = random_noise(data, mode='speckle', seed=seed, m=0.1,
|
||||
v=0.02)
|
||||
assert_allclose(expected, data_speckle)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
np.testing.run_module_suite()
|
||||
Reference in New Issue
Block a user