In https://keras.io/preprocessing/image/#imagedatagenerator-class brightness_range is not documented.
Wouldn't that just be:
brightness_range
: Tuple of floats; range to pick a brightness value from.
If yes, more than happy to make the change in the documentation. 👍
@dynamicwebpaige, I find brightness_range
supports both tuple and list type (source)
Would it be more clear like:
brightness_range
: Tuple or list of two floats; range to pick a brightness value from.
it will be even clearer if the actual valid range is documented. Internally, it uses PIL ImageEnhance module and the documentation [there] is: (https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html#PIL.ImageEnhance.Brightness)
"An enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the original image."
@assaflehr
I thought it just scales the brightness of image via PIL ImageEnhance module, but if so, it should return the original image when I give brightness_range=(1.0, 1.0)
, however, my experiment showed that brightness_range = (1.0, 1.0)
gave more brighter images than brightness_range = None
(default)
Actually the manual in keras.io says
brightness_range: Tuple or list of two floats. Range for picking a brightness shift value from.
that 'shift' may have different meaning from scale, I guess.
The term 'shift' is ambiguous here. When shifting I'd expect the value 0 to mean 'leave as is', a value > to make it brigther and < 0 darker. But as @assaflehr pointed out, here 1.0 means 'leave as is'. The behaviour noted by @jaewooklee93 is odd, did you maybe do any other preprocessing steps that might mess with the brightness?
I propose changing the documentation of brightness_range
to
Tuple or list of two floats. Range for picking the new brightness (relative to the original image), with 1.0 being the original's brightness. Must not be smaller than 0.
@thunfischtoast
I tried more debugging and I almost figured out why that phenomenon happens. The below is a minimally working example code.
import numpy as np
import matplotlib.pyplot as plt
from keras.preprocessing.image import *
img = np.random.rand(1, 500, 500, 3)
fig, ax = plt.subplots(1, 5, figsize=(20, 10))
ax = ax.ravel()
ax[0].imshow(img[0])
ax[1].imshow(next(ImageDataGenerator().flow(img))[0])
ax[2].imshow(next(ImageDataGenerator(brightness_range=(0., 0.)).flow(img))[0])
ax[3].imshow(next(ImageDataGenerator(brightness_range=(1., 1.)).flow(img))[0])
ax[4].imshow(next(ImageDataGenerator(brightness_range=(1., 1.)).flow(img))[0] / 255)
Output:
By default, if brightness_range
is not specified, ImageDataGenerator
returns [0..1] ranged float images from [0..1] ranged images. (as you can see at ax[1]
)
However, when it comes to brightness_range=(1., 1.)
, it returns [0..255] ranged float images even from [0..1] ranged images, and it's quite not conventional to handle float image with [0..255] ranged-values.
Still, the term 'shift' is ambiguous as you mentioned, so both docs and the behavior of function itself should be updated, I guess.
I agree. This is also the case for other arguments of the image data generator. There needs to be a description of the valid ranges.
I have also experienced that if I am using brightness augementation during training, I need to use brightness_range=(1., 1.) during inference as well, or I would get worse results. Anyone else experienced this phenomenon?
@AzharSultan, I found that Keras use the library keras_preprocessing to make the data augmentation.
Here is the code for random brightness
https://github.com/keras-team/keras-preprocessing/blob/master/keras_preprocessing/image/affine_transformations.py#L215
x = array_to_img(x)
x = imgenhancer_Brightness = ImageEnhance.Brightness(x)
x = imgenhancer_Brightness.enhance(brightness)
x = img_to_array(x)
and the first line will transform a numpy ndarry type to a PIL Image instance -- x = array_to_img(x)
the last line will return to an array -- x = img_to_array(x)
But the problem is that returned x will be between 0 and 255, and the dtype is float32
.
so you need to divide by 255 manually or change the dtype to uint8
As I found, 0.5 is remain unchange, 1 for totally white and 0 for totally dark, so I guess it's a linear project?
import numpy as np
import matplotlib.pyplot as plt
from keras.preprocessing.image import *
img =128 + np.zeros([1, 500, 500, 3])
fig, ax = plt.subplots(1, 5, figsize=(20, 10))
ax = ax.ravel()
ax[0].imshow(img[0].astype(int))
ax[1].imshow(next(ImageDataGenerator(brightness_range=(0.2, 0.2)).flow(img))[0].astype(int))
ax[2].imshow(next(ImageDataGenerator(brightness_range=(0., 0.)).flow(img))[0].astype(int))
ax[3].imshow(next(ImageDataGenerator(brightness_range=(0.5, 0.5)).flow(img))[0].astype(int))
ax[4].imshow(next(ImageDataGenerator(brightness_range=(1., 1.)).flow(img))[0].astype(int))
@zlw21gxy
That's weird...
If you use a real image, then < 1 makes it darker. > 1 makes it brighter.
For example, try to change the 4th line of your code by:
from skimage.data import astronaut
img = astronaut()[np.newaxis].astype(int)
EDIT: it seems like the brightness operator doesn't work well for single-color images like the one from @zlw21gxy. For example, it works well for a synthetic image created like this:
img = np.zeros([1, 500, 500, 3])
img[:, :250, :250, 0] = 128
img[:, 250:, :250, 0] = 255
img[:, :250, 250:, :] = 128
img[:, 250:, 250:, :] = 255
Yes, that’s really weird,I try to generate random number for every pixel
and it behave different with using a gray picture, if I use gray picture
0.5 is stay unchanged, if use real images or random number, 1 is stay
unchanged, that is so weird....
On Tue, May 28, 2019 at 10:01 PM Ricardo Cruz notifications@github.com
wrote:
@zlw21gxy https://github.com/zlw21gxy
That's weird...
If you use a real image, then < 1 makes it darker. > 1 makes it brighter.For example, try to change the 4th line of your code by:
from skimage.data import astronaut
img = astronaut()[np.newaxis].astype(int)[image: a]
https://user-images.githubusercontent.com/11894291/58483661-fa700680-8160-11e9-91a8-6c02e35656e5.png—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/keras-team/keras/issues/10366?email_source=notifications&email_token=AK7ZPCQ4TDFRZNBZUOLUNW3PXU3LRA5CNFSM4FDSASI2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODWMG3IQ#issuecomment-496528802,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AK7ZPCS3WNNYBVN4ZJ2TFSTPXU3LRANCNFSM4FDSASIQ
.
@rpmcruz
hey bro, did u figure out the reason?
"I try to generate random number for every pixel and it behave different with using a gray picture, if I use gray picture 0.5 is stay unchanged, if use real images or random number, 1 is stay unchanged"
I just ran across this thread after wondering why my model training went horribly badly after adding image augmentation. Based on the little documentation in TF 2.0.0b0, "Range for picking a brightness shift value from" I naiively used [-0.2, 0.2]. So yeah, I +1 any of the above documentation improvements that state the ranges allowed.
I also wonder why I didn't get an exception or assertion for using a negative value.
@zlw21gxy @rpmcruz this is a bug of keras
or should say of the keras_preprocessing
package.
I am testing on the lastest verson Keras-Preprocessing==1.1.0
If you go to the code with regard of brightness, you will see
def apply_brightness_shift(x, brightness):
#...
x = array_to_img(x) # !!!note this!!!
x = imgenhancer_Brightness = ImageEnhance.Brightness(x)
x = imgenhancer_Brightness.enhance(brightness)
x = img_to_array(x)
return x
then you go to the definition of array_to_img
:
def array_to_img(x, data_format='channels_last', scale=True, dtype='float32')
As you can see, array_to_img
actually has a parameter of scale, yet not set when called in apply_brightness_shift
.
As a result, a gray image (value = 128) will be scale to a white one (value = 255).
To vertify, if you simply change the range of values in your image (e.g. even one single channel of one single pixel), you can get everything what you supposed:
import numpy as np
import matplotlib.pyplot as plt
from keras.preprocessing.image import *
img =128 + np.zeros([1, 500, 500, 3])
img[0,0,0,0] = 255 # !!! just add this or similar !!!
fig, ax = plt.subplots(1, 5, figsize=(20, 10))
ax = ax.ravel()
ax[0].imshow(img[0].astype(int))
ax[1].imshow(next(ImageDataGenerator(brightness_range=(0.2, 0.2)).flow(img))[0].astype(int))
ax[2].imshow(next(ImageDataGenerator(brightness_range=(0., 0.)).flow(img))[0].astype(int))
ax[3].imshow(next(ImageDataGenerator(brightness_range=(0.5, 0.5)).flow(img))[0].astype(int))
ax[4].imshow(next(ImageDataGenerator(brightness_range=(1., 1.)).flow(img))[0].astype(int))
Is this resolved yet?
I'm also curious if this was resolved.
Looking trough this, this looks like it should be removed, because it is too buggy to be used as is. And too scary to use. Looks like it behaves in very complex ways with various other parameters and also dependent on the image. As such I would refrain from using it. Although the idea of varying the brightness is very useful. But with this parameter one has to wade through dozens of other lines of code and do a fair bit of testing before being able to use it.
Just a note that brightness_range
for ImageDataGenerator
is defined differently to tf.image.adjust_brightness
.
brightness_range
applies a multiplication operation, so 1 is no change. (At least theoretically, other commenters here have pointed out that there are changes sometimes.)
adjust_brightness
applies an addition operation, so 0 is no change.
Most helpful comment
@thunfischtoast
I tried more debugging and I almost figured out why that phenomenon happens. The below is a minimally working example code.
Output:

By default, if
brightness_range
is not specified,ImageDataGenerator
returns [0..1] ranged float images from [0..1] ranged images. (as you can see atax[1]
)However, when it comes to
brightness_range=(1., 1.)
, it returns [0..255] ranged float images even from [0..1] ranged images, and it's quite not conventional to handle float image with [0..255] ranged-values.Still, the term 'shift' is ambiguous as you mentioned, so both docs and the behavior of function itself should be updated, I guess.