Convert a Photo to Black and White in Python

Black and white images aren’t for everyone. I personally like to play around with them as you can sometimes take a boring photo and turn it into something dramatic. I have also rescued a drab photo by turning it black and white. If you want to change a photo that you took into a black and white photo programmatically, the Pillow package has you covered. In this article we will look at the two simple ways to convert a photo to black and white and then we will also learn how to make a sepia-toned photo.


Making it Black and White

The first obstacle is finding a photo that you would like to edit. For this example, we will use the following fuzzy caterpillar:

Now we just need to create a simple function that can turn our full color photo into a black and white one:

from PIL import Image

def black_and_white(input_image_path,
    output_image_path):
   color_image = Image.open(input_image_path)
   bw = color_image.convert('L')
   bw.save(output_image_path)

if __name__ == '__main__':  
    black_and_white('caterpillar.jpg',
        'bw_caterpillar.jpg')

The function above takes two arguments: the input image’s file path and the path we want to save the output to. The piece we really care about in this script is this function. It contains a call to open the Image which will return an Image object. We then use that object’s convert method to transform the image to black and white by passing it the string ‘L’. This may seem a bit strange, so let’s look at the documentation.

Here you will find that the first parameter to the convert() method is the mode. Pillow supports several modes including: ‘P’, ‘L’ and ‘1’. The mode we care about at the moment is ‘L’. The documentation states “When translating a color image to black and white (mode “L”), the library uses the ITU-R 601-2 luma transform: L = R * 299/1000 + G * 587/1000 + B * 114/1000” where the RGB maps to Red, Green and Blue. Let’s see what kind of output our code generated:

That looks pretty nice if I do say so myself. The Pillow project also supports creating black and white images with dithering, which is basically adding noise to the image. Let’s take a look at how that changes the code:

from PIL import Image

def black_and_white_dithering(input_image_path,
    output_image_path,
    dithering=True):
    color_image = Image.open(input_image_path)
    if dithering:
        bw = color_image.convert('1')  
    else:
        bw = color_image.convert('1', dither=Image.NONE)
    bw.save(output_image_path)

if __name__ == '__main__':
    black_and_white_dithering(
        'caterpillar.jpg',
        'bw_caterpillar_dithering.jpg')

The only difference between this function and the previous one is that we added a dithering argument and we also called the convert() method with a ‘1’ (one) instead of ‘L’. You can do dithering with the ‘L’ mode, but I wanted to show what happens when you use ‘1’. Let’s take a look at the output:

This one looks a lot different when you have the image at full size because you can see the white noise a lot easier. However since I am using a small version here, the change is pretty subtle.

Now let’s call the function with dithering set to False. If you do that, you will see the following image:

This one is a bit abstract looking, but also kind of interesting. It’s almost an ink-blot! As you can see, when it comes to creating black and white images, ‘L’ is probably the mode you will want to focus on.


Creating a Sepia Toned Image

Now that we’ve learned how to make our images black and white, I wanted to talk about how to add a sepia toning to your image. Sepia toned images are pretty popular and give your image that old fashioned yellowed look. I went digging around the web and found an article on Fredrik Lundh’s site that talked about how to do this trick.

Let’s take a look at the code:

from PIL import Image

def make_sepia_palette(color):
    palette = []
    r, g, b = color
    for i in range(255):
        palette.extend((r*i/255, g*i/255, b*i/255))
        
    return palette

def create_sepia(input_image_path,
    output_image_path):
    whitish = (255, 240, 192)
    sepia = make_sepia_palette(whitish)
    
    color_image = Image.open(input_image_path)
    
    # convert our image to gray scale
    bw = color_image.convert('L')
    
    # add the sepia toning
    bw.putpalette(sepia)
    
    # convert to RGB for easier saving
    sepia_image = bw.convert('RGB')
    
    sepia_image.save(output_image_path)

if __name__ == '__main__':
    create_sepia('caterpillar.jpg',
                 'sepia_caterpillar.jpg')

Here we create two functions. One for creating the sepia palette and the other for applying it. To create the palette, we need an off-white color created, so we do that by iterating over a tuple that is off-white and then iterating over all 255 variations of the RGB color spectrum. This creates a good sized tuple of various RGB values that represents out sepia palette.

Next we convert our image to black and white (or gray scale depending on how you look at it). Then we apply our sepia palette using the putpalette() method of the image object. Finally we convert the image back to ‘RGB’ as according to Lundh, this allows us to save the image as a Jpeg. I didn’t do any digging to see if this is still required in Pillow or not. Finally we save the image and this is what I got:

Pretty neat and the code runs quite fast too!


Wrapping Up

Now you know how to use the Pillow package to create several variations of black and white photos. You also have discovered how easy it is to apply a palette to your black and white images to add sepia toning. You can go and play around with other colors to make your black and white photos change their look considerably. Have fun and happy coding!


Related Readng