Skip to content

Commit

Permalink
High ISO noise (#277)
Browse files Browse the repository at this point in the history
Added ISO Noise
  • Loading branch information
BloodAxe authored Jul 4, 2019
1 parent 2c1a148 commit 2e25667
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Pixel-level transforms will change just an input image and will leave any additi
- [IAAEmboss](https://albumentations.readthedocs.io/en/latest/api/augmentations.html#albumentations.imgaug.transforms.IAAEmboss)
- [IAASharpen](https://albumentations.readthedocs.io/en/latest/api/augmentations.html#albumentations.imgaug.transforms.IAASharpen)
- [IAASuperpixels](https://albumentations.readthedocs.io/en/latest/api/augmentations.html#albumentations.imgaug.transforms.IAASuperpixels)
- [ISONoise](https://albumentations.readthedocs.io/en/latest/api/augmentations.html#albumentations.augmentations.transforms.ISONoise)
- [InvertImg](https://albumentations.readthedocs.io/en/latest/api/augmentations.html#albumentations.augmentations.transforms.InvertImg)
- [JpegCompression](https://albumentations.readthedocs.io/en/latest/api/augmentations.html#albumentations.augmentations.transforms.JpegCompression)
- [MedianBlur](https://albumentations.readthedocs.io/en/latest/api/augmentations.html#albumentations.augmentations.transforms.MedianBlur)
Expand Down
52 changes: 50 additions & 2 deletions albumentations/augmentations/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ def wrapped_function(img, *args, **kwargs):


def preserve_shape(func):
"""Preserve shape of the image."""
"""
Preserve shape of the image
"""
@wraps(func)
def wrapped_function(img, *args, **kwargs):
shape = img.shape
Expand All @@ -45,7 +48,10 @@ def wrapped_function(img, *args, **kwargs):


def preserve_channel_dim(func):
"""Preserve dummy channel dim."""
"""
Preserve dummy channel dim.
"""
@wraps(func)
def wrapped_function(img, *args, **kwargs):
shape = img.shape
Expand Down Expand Up @@ -924,6 +930,48 @@ def brightness_contrast_adjust(img, alpha=1, beta=0):
return _brightness_contrast_adjust_non_uint(img, alpha, beta)


@clipped
def iso_noise(image, color_shift=0.05, intensity=0.5, random_state=None, **kwargs):
"""
Apply poisson noise to image to simulate camera sensor noise.
Args:
image: Input image, currently, only RGB, uint8 images are supported.
intensity: Multiplication factor for noise values. Values of ~0.5 are produce noticeable,
yet acceptable level of noise.
random_state:
**kwargs:
Returns:
Noised image
"""
assert image.dtype == np.uint8, 'Image must have uint8 channel type'
assert image.shape[2] == 3, 'Image must be RGB'

if random_state is None:
random_state = np.random.RandomState(42)

one_over_255 = float(1. / 255.)
image = np.multiply(image, one_over_255, dtype=np.float32)
hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
mean, stddev = cv2.meanStdDev(hls)

luminance_noise = random_state.poisson(stddev[1] * intensity * 255, size=hls.shape[:2])
color_noise = random_state.normal(0, color_shift * 360 * intensity, size=hls.shape[:2])

hue = hls[..., 0]
hue += color_noise
hue[hue < 0] += 360
hue[hue > 360] -= 360

luminance = hls[..., 1]
luminance += (luminance_noise / 255) * (1.0 - luminance)

image = cv2.cvtColor(hls, cv2.COLOR_HLS2RGB) * 255
return image.astype(np.uint8)


def to_gray(img):
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
return cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB)
Expand Down
41 changes: 39 additions & 2 deletions albumentations/augmentations/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
'Resize', 'RandomSizedCrop', 'RandomBrightnessContrast',
'RandomCropNearBBox', 'RandomSizedBBoxSafeCrop', 'RandomSnow',
'RandomRain', 'RandomFog', 'RandomSunFlare', 'RandomShadow', 'Lambda',
'ChannelDropout',
'ChannelDropout', 'ISONoise'
]


Expand Down Expand Up @@ -1794,6 +1794,43 @@ def get_transform_init_args_names(self):
return ('var_limit',)


class ISONoise(ImageOnlyTransform):
"""
Apply camera sensor noise.
Args:
color_shift (float, float): variance range for color hue change.
Measured as a fraction of 360 degree Hue angle in HLS colorspace.
intensity ((float, float): Multiplicative factor that control strength
of color and luminace noise.
p (float): probability of applying the transform. Default: 0.5.
Targets:
image
Image types:
uint8
"""

def __init__(self, color_shift=(0.01, 0.05), intensity=(0.1, 0.5), always_apply=False, p=0.5):
super(ISONoise, self).__init__(always_apply, p)
self.intensity = intensity
self.color_shift = color_shift

def apply(self, img, color_shift=0.05, intensity=1.0, random_state=None, **params):
return F.iso_noise(img, color_shift, intensity, np.random.RandomState(random_state))

def get_params(self):
return {
'color_shift': random.uniform(self.color_shift[0], self.color_shift[1]),
'intensity': random.uniform(self.intensity[0], self.intensity[1]),
'random_state': random.randint(0, 65536)
}

def get_transform_init_args_names(self):
return ('intensity', 'color_shift')


class CLAHE(ImageOnlyTransform):
"""Apply Contrast Limited Adaptive Histogram Equalization to the input image.
Expand Down Expand Up @@ -1849,7 +1886,7 @@ def __init__(self, channel_drop_range=(1, 1), fill_value=0, always_apply=False,

self.fill_value = fill_value

def apply(self, img, channels_to_drop=(0, ), **params):
def apply(self, img, channels_to_drop=(0,), **params):
return F.channel_dropout(img, channels_to_drop, self.fill_value)

def get_params_dependent_on_targets(self, params):
Expand Down
6 changes: 5 additions & 1 deletion tests/test_augmentations.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
Cutout, CoarseDropout, Normalize, ToFloat, FromFloat,
RandomBrightnessContrast, RandomSnow, RandomRain, RandomFog,
RandomSunFlare, RandomCropNearBBox, RandomShadow, RandomSizedCrop,
ChannelDropout)
ChannelDropout, ISONoise)


@pytest.mark.parametrize(['augmentation_cls', 'params'], [
Expand All @@ -41,6 +41,7 @@
[RandomSunFlare, {}],
[RandomShadow, {}],
[ChannelDropout, {}],
[ISONoise, {}],
])
def test_image_only_augmentations(augmentation_cls, params, image, mask):
aug = augmentation_cls(p=1, **params)
Expand Down Expand Up @@ -97,6 +98,7 @@ def test_image_only_augmentations_with_float_values(augmentation_cls, params, fl
[CenterCrop, {'height': 10, 'width': 10}],
[RandomCrop, {'height': 10, 'width': 10}],
[RandomSizedCrop, {'min_max_height': (4, 8), 'height': 10, 'width': 10}],
[ISONoise, {}],
])
def test_dual_augmentations(augmentation_cls, params, image, mask):
aug = augmentation_cls(p=1, **params)
Expand Down Expand Up @@ -188,6 +190,7 @@ def test_imgaug_dual_augmentations(augmentation_cls, image, mask):
[RandomSunFlare, {}],
[RandomShadow, {}],
[ChannelDropout, {}],
[ISONoise, {}],
])
def test_augmentations_wont_change_input(augmentation_cls, params, image, mask):
image_copy = image.copy()
Expand Down Expand Up @@ -334,6 +337,7 @@ def test_augmentations_wont_change_shape_grayscale(augmentation_cls, params, ima
[RandomSunFlare, {}],
[RandomShadow, {}],
[ChannelDropout, {}],
[ISONoise, {}],
])
def test_augmentations_wont_change_shape_rgb(augmentation_cls, params, image, mask):
aug = augmentation_cls(p=1, **params)
Expand Down

0 comments on commit 2e25667

Please sign in to comment.