diff --git a/imgaug/augmenters/overlay.py b/imgaug/augmenters/overlay.py index 3c3696a5f..ba769285e 100644 --- a/imgaug/augmenters/overlay.py +++ b/imgaug/augmenters/overlay.py @@ -28,7 +28,8 @@ # TODO tests class Alpha(Augmenter): """ - Augmenter to overlay two image sources with each other using an alpha value. + Augmenter to overlay two image sources with each other using an + alpha/transparency value. The image sources can be imagined as branches. If a source is not given, it is automatically the same as the input. @@ -44,7 +45,7 @@ class Alpha(Augmenter): Parameters ---------- - factor : float or iterable of two floats or StochasticParameter, optional(default=0) + factor : int or float or iterable of two floats or StochasticParameter, optional(default=0) Weighting of the results of the first branch. Values close to 0 mean that the results from the second branch (see parameter `second`) make up most of the final image. @@ -91,13 +92,13 @@ class Alpha(Augmenter): -------- >>> aug = iaa.Alpha(0.5, iaa.Grayscale(1.0)) - Converts the image to grayscale and overlays it by 50 percent with the + Converts each image to grayscale and overlays it by 50 percent with the original image, thereby removing about 50 percent of all color. This is equivalent to iaa.Grayscale(0.5). - >>> aug = iaa.Alpha((0, 1.0), iaa.Grayscale(1.0)) + >>> aug = iaa.Alpha((0.0, 1.0), iaa.Grayscale(1.0)) - Converts the image to grayscale and overlays it by a random percentage + Converts each image to grayscale and overlays it by a random percentage (sampled per image) with the original image, thereby removing a random percentage of all colors. This is equivalent to iaa.Grayscale((0.0, 1.0)). @@ -122,14 +123,19 @@ class Alpha(Augmenter): from (B). This is equivalent to `iaa.Sequential([iaa.Multiply(0.8), iaa.Alpha((0.0, 1.0), iaa.Add(10))])`. + >>> aug = iaa.Alpha(iap.Choice([0.25, 0.75]), iaa.MedianBlur((3, 7))) + + Applies a random median blur to each image and overlays the result with + the original image by either 25 or 75 percent strength. + """ def __init__(self, factor=0, first=None, second=None, per_channel=False, name=None, deterministic=False, random_state=None): super(Alpha, self).__init__(name=name, deterministic=deterministic, random_state=random_state) - if ia.is_single_float(factor): - assert 0 <= factor <= 1.0, "Expected factor to have range [0, 1.0], got value %.2f." % (factor,) + if ia.is_single_number(factor): + assert 0.0 <= factor <= 1.0, "Expected factor to have range [0, 1.0], got value %.2f." % (factor,) self.factor = Deterministic(factor) elif ia.is_iterable(factor): assert len(factor) == 2, "Expected tuple/list with 2 entries, got %d entries." % (len(factor),) @@ -306,3 +312,225 @@ def get_children_lists(self): if self.second is not None: result.append(self.second) return result + +class AlphaElementwise(Alpha): + """ + Augmenter to overlay two image sources with each other using pixelwise + alpha values. + + This is the same as `Alpha`, except that the transparency factor is + sampled per pixel instead of once per image (or a few times per image, if + per_channel is True). + + See `Alpha` for more description. + + Parameters + ---------- + factor : float or iterable of two floats or StochasticParameter, optional(default=0) + Weighting of the results of the first branch. Values close to 0 mean + that the results from the second branch (see parameter `second`) + make up most of the final image. + * If float, then that value will be used for all images. + * If tuple (a, b), then a random value from range a <= x <= b will + be sampled per image. + * If StochasticParameter, then that parameter will be used to + sample a value per image. + + first : None or Augmenter or iterable of Augmenter, optional(default=None) + Augmenter(s) that make up the first of the two + branches. + * If None, then the input images will be reused as the output + of the first branch. + * If Augmenter, then that augmenter will be used as the branch. + * If iterable of Augmenter, then that iterable will be converted + into a Sequential and used as the augmenter. + + second : None or Augmenter or iterable of Augmenter, optional(default=None) + Augmenter(s) that make up the second of the two + branches. + * If None, then the input images will be reused as the output + of the second branch. + * If Augmenter, then that augmenter will be used as the branch. + * If iterable of Augmenter, then that iterable will be converted + into a Sequential and used as the augmenter. + + per_channel : bool or float, optional(default=False) + Whether to use the same factor for all channels (False) + or to sample a new value for each channel (True). + If this value is a float p, then for p percent of all images + `per_channel` will be treated as True, otherwise as False. + + name : string, optional(default=None) + See `Augmenter.__init__()` + + deterministic : bool, optional(default=False) + See `Augmenter.__init__()` + + random_state : int or np.random.RandomState or None, optional(default=None) + See `Augmenter.__init__()` + + Examples + -------- + >>> aug = iaa.AlphaElementwise(0.5, iaa.Grayscale(1.0)) + + Converts each image to grayscale and overlays it by 50 percent with the + original image, thereby removing about 50 percent of all color. This + is equivalent to iaa.Grayscale(0.5). This is also equivalent to + iaa.Alpha(0.5, iaa.Grayscale(1.0)), as the transparency factor is the + same for all pixels. + + >>> aug = iaa.AlphaElementwise((0, 1.0), iaa.Grayscale(1.0)) + + Converts each image to grayscale and overlays it by a random percentage + (sampled per pixel) with the original image, thereby removing a random + percentage of all colors per pixel. + + >>> aug = iaa.AlphaElementwise((0.0, 1.0), iaa.Affine(rotate=(-20, 20)), per_channel=0.5) + + Rotates each image by a random degree from the range [-20, 20]. Then + overlays that new image with the original one by a random factor from the + range [0.0, 1.0], sampled per pixel. In 50 percent of all cases, the + overlay happens channel-wise and the factor is sampled independently per + channel. As a result, e.g. the red channel may look visible rotated (factor + near 1.0), while the green and blue channels may not look rotated (factors + near 0.0). NOTE: It is not recommended to use Alpha with augmenters that + change the positions of pixels if you *also* want to augment keypoints, as + it is unclear which of the two keypoint results (first or second branch) + should be used as the final result. + + >>> aug = iaa.AlphaElementwise((0.0, 1.0), first=iaa.Add(10), second=iaa.Multiply(0.8)) + + (A) Adds 10 to each image and (B) multiplies each image by 0.8. Then per + pixel an overlay factor is sampled from the range [0.0, 1.0]. If it is + close to 1.0, the results from (A) are mostly used, otherwise the ones + from (B). This is equivalent to + `iaa.Sequential([iaa.Multiply(0.8), iaa.AlphaElementwise((0.0, 1.0), iaa.Add(10))])`. + + >>> aug = iaa.AlphaElementwise(iap.Choice([0.25, 0.75]), iaa.MedianBlur((3, 7))) + + Applies a random median blur to each image and overlays the result with + the original image by either 25 or 75 percent strength (sampled per pixel). + + """ + + def __init__(self, factor=0, first=None, second=None, per_channel=False, + name=None, deterministic=False, random_state=None): + super(AlphaElementwise, self).__init__( + factor=factor, + first=first, + second=second, + per_channel=per_channel, + name=name, + deterministic=deterministic, + random_state=random_state + ) + + def _augment_images(self, images, random_state, parents, hooks): + result = images + nb_images = len(images) + seeds = random_state.randint(0, 10**6, (nb_images,)) + + if hooks.is_propagating(images, augmenter=self, parents=parents, default=True): + if self.first is None: + images_first = images + else: + images_first = self.first.augment_images( + images=images, + parents=parents + [self], + hooks=hooks + ) + + if self.second is None: + images_second = images + else: + images_second = self.second.augment_images( + images=images, + parents=parents + [self], + hooks=hooks + ) + else: + images_first = images + images_second = images + + for i in sm.xrange(nb_images): + image = images[i] + h, w, nb_channels = image.shape[0:3] + image_first = images_first[i] + image_second = images_second[i] + rs_image = ia.new_random_state(seeds[i]) + per_channel = self.per_channel.draw_sample(random_state=rs_image) + input_dtype = image.dtype + if per_channel == 1: + samples = self.factor.draw_samples((h, w, nb_channels), random_state=rs_image) + assert 0 <= samples.item(0) <= 1.0 # validate only first value + #for c, sample in enumerate(samples): + for c in sm.xrange(nb_channels): + samples_c = samples[..., c] + image[..., c] = samples_c * image_first[..., c] + (1.0 - samples_c) * image_second[..., c] + np.clip(image, 0, 255, out=image) + result[i] = image.astype(input_dtype) + else: + samples = self.factor.draw_samples((h, w), random_state=rs_image) + samples = np.tile(samples[..., np.newaxis], (1, 1, nb_channels)) + assert 0.0 <= samples.item(0) <= 1.0 + + image = samples * image_first + (1.0 - samples) * image_second + np.clip(image, 0, 255, out=image) + result[i] = image.astype(input_dtype) + return result + + def _augment_keypoints(self, keypoints_on_images, random_state, parents, hooks): + result = keypoints_on_images + nb_images = len(keypoints_on_images) + seeds = random_state.randint(0, 10**6, (nb_images,)) + + if hooks.is_propagating(keypoints_on_images, augmenter=self, parents=parents, default=True): + if self.first is None: + kps_ois_first = keypoints_on_images + else: + kps_ois_first = self.first.augment_keypoints( + keypoints_on_images=keypoints_on_images, + parents=parents + [self], + hooks=hooks + ) + + if self.second is None: + kps_ois_second = keypoints_on_images + else: + kps_ois_second = self.second.augment_keypoints( + keypoints_on_images=keypoints_on_images, + parents=parents + [self], + hooks=hooks + ) + else: + kps_ois_first = keypoints_on_images + kps_ois_second = keypoints_on_images + + for i in sm.xrange(nb_images): + kps_oi_first = kps_ois_first[i] + kps_oi_second = kps_ois_second[i] + rs_image = ia.new_random_state(seeds[i]) + h, w, nb_channels = kps_oi_first.shape[0:3] + + # keypoint augmentation also works channel-wise, even though + # keypoints do not have channels, in order to keep the random + # values properly synchronized with the image augmentation + per_channel = self.per_channel.draw_sample(random_state=rs_image) + if per_channel == 1: + samples = self.factor.draw_samples((h, w, nb_channels,), random_state=rs_image) + else: + samples = self.factor.draw_samples((h, w), random_state=rs_image) + assert 0.0 <= samples.item(0) <= 1.0 + sample = np.average(samples) + + # We cant choose "just a bit" of one keypoint augmentation result + # without messing up the positions (interpolation doesn't make much + # sense here), + # so if the alpha is >= 0.5 (branch A is more visible than + # branch B), the result of branch A, otherwise branch B. + if sample >= 0.5: + result[i] = kps_oi_first + else: + result[i] = kps_oi_second + + return result diff --git a/tests/check_visually.py b/tests/check_visually.py index aa842824b..7c8bd8187 100644 --- a/tests/check_visually.py +++ b/tests/check_visually.py @@ -117,14 +117,34 @@ def main(): first=iaa.Add(100), second=iaa.Dropout(0.5), per_channel=True, - name="AlphaChannelwise" + name="AlphaPerChannel" ), iaa.Alpha( factor=(0.0, 1.0), first=iaa.Affine(rotate=(-45, 45)), per_channel=True, name="AlphaAffine" - ) + ), + iaa.AlphaElementwise( + factor=(0.0, 1.0), + first=iaa.Add(50), + second=iaa.ContrastNormalization(2.0), + per_channel=False, + name="AlphaElementwise" + ), + iaa.AlphaElementwise( + factor=(0.0, 1.0), + first=iaa.Add(50), + second=iaa.ContrastNormalization(2.0), + per_channel=True, + name="AlphaElementwisePerChannel" + ), + iaa.AlphaElementwise( + factor=(0.0, 1.0), + first=iaa.Affine(rotate=(-45, 45)), + per_channel=True, + name="AlphaElementwiseAffine" + ), ] augmenters.append(iaa.Sequential([iaa.Sometimes(0.2, aug.copy()) for aug in augmenters], name="Sequential"))