Using PIL to turn a RGB image into a pure black and white image
Asked Answered
R

6

94

I'm using the Python Imaging Library for some very simple image manipulation, however I'm having trouble converting a greyscale image to a monochrome (black and white) image. If I save after changing the image to greyscale (convert('L')) then the image renders as you would expect. However, if I convert the image to a monochrome, single-band image it just gives me noise as you can see in the images below. Is there a simple way to take a colour png image to a pure black and white image using PIL / python?

from PIL import Image 
import ImageEnhance
import ImageFilter
from scipy.misc import imsave
image_file = Image.open("convert_image.png") # open colour image
image_file= image_file.convert('L') # convert image to monochrome - this works
image_file= image_file.convert('1') # convert image to black and white
imsave('result_col.png', image_file)

Original Image Converted Image

Raimondo answered 29/2, 2012 at 21:23 Comment(1)
From the PIL documentation: """When converting to a bilevel image (mode "1"), the source image is first converted to black and white. Resulting values larger than 127 are then set to white, and the image is dithered. To use other thresholds, use the point method.""" This sounds related, but I'm not familiar with PIL and image manipulation.Clute
L
114
from PIL import Image 
image_file = Image.open("convert_image.png") # open colour image
image_file = image_file.convert('1') # convert image to black and white
image_file.save('result.png')

yields

enter image description here

Lawrence answered 29/2, 2012 at 21:32 Comment(4)
very bad output. Text is not recognizable(Lacombe
This is a grayscale image, not a bw image.Confirmation
@Confirmation it is definitely black and white only. Dithering is not the same thing as grayscale; the image is created using 1-bit per pixel. grayscale typically uses 8 bits per pixel to give you shades between white (off) and black (on).Zach
@MartijnPieters You're right! It is a BW image. Didn't notice it cuz it looks gray when zoomed out :PConfirmation
D
91

A PIL only solution for creating a bi-level (black and white) image with a custom threshold:

from PIL import Image
img = Image.open('mB96s.png')
thresh = 200
fn = lambda x : 255 if x > thresh else 0
r = img.convert('L').point(fn, mode='1')
r.save('foo.png')

With just

r = img.convert('1')
r.save('foo.png')

you get a dithered image.

From left to right the input image, the black and white conversion result and the dithered result:

Input Image Black and White Result Dithered Result

You can click on the images to view the unscaled versions.

Demit answered 29/4, 2018 at 19:47 Comment(4)
This should probably be the accepted answer-- it economically achieves the requested result without additional libraries.Scree
what if instead of white pixels we wanted them to be transparent?Embankment
@ShanerM13 A black-and-white image has one bit for each pixel's color value: it can be 1 or 0. That leaves room for black and white but not a third option such as transparent (unless you're in charge of how the images get handled and can define one of those options to mean transparent in your application).Fenestra
@Fenestra maybe a better question for me to ask, at least now, is how do I make the "background" white instead of black as the default?Embankment
E
29

Another option (which is useful e.g. for scientific purposes when you need to work with segmentation masks) is simply apply a threshold:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Binarize (make it black and white) an image with Python."""

from PIL import Image
from scipy.misc import imsave
import numpy


def binarize_image(img_path, target_path, threshold):
    """Binarize an image."""
    image_file = Image.open(img_path)
    image = image_file.convert('L')  # convert image to monochrome
    image = numpy.array(image)
    image = binarize_array(image, threshold)
    imsave(target_path, image)


def binarize_array(numpy_array, threshold=200):
    """Binarize a numpy array."""
    for i in range(len(numpy_array)):
        for j in range(len(numpy_array[0])):
            if numpy_array[i][j] > threshold:
                numpy_array[i][j] = 255
            else:
                numpy_array[i][j] = 0
    return numpy_array


def get_parser():
    """Get parser object for script xy.py."""
    from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
    parser = ArgumentParser(description=__doc__,
                            formatter_class=ArgumentDefaultsHelpFormatter)
    parser.add_argument("-i", "--input",
                        dest="input",
                        help="read this file",
                        metavar="FILE",
                        required=True)
    parser.add_argument("-o", "--output",
                        dest="output",
                        help="write binarized file hre",
                        metavar="FILE",
                        required=True)
    parser.add_argument("--threshold",
                        dest="threshold",
                        default=200,
                        type=int,
                        help="Threshold when to show white")
    return parser


if __name__ == "__main__":
    args = get_parser().parse_args()
    binarize_image(args.input, args.output, args.threshold)

It looks like this for ./binarize.py -i convert_image.png -o result_bin.png --threshold 200:

enter image description here

Extemporize answered 28/5, 2016 at 10:38 Comment(4)
A one-liner for binarize_array (quicker too I guess) : numpy.where(numpy_array > threshold=200, 255, 0)Patristic
This works perfectly for my google search of converting an image to black and white only... Thanks!Forerun
You don't need numpy for the binarization - PIL is sufficient for this.Demit
This is a very slow solution for large datasets.Odisodium
G
7

As Martin Thoma has said, you need to normally apply thresholding. But you can do this using simple vectorization which will run much faster than the for loop that is used in that answer.

The code below converts the pixels of an image into 0 (black) and 1 (white).

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

#Pixels higher than this will be 1. Otherwise 0.
THRESHOLD_VALUE = 200

#Load image and convert to greyscale
img = Image.open("photo.png")
img = img.convert("L")

imgData = np.asarray(img)
thresholdedData = (imgData > THRESHOLD_VALUE) * 1.0

plt.imshow(thresholdedData)
plt.show()
Greenock answered 8/3, 2017 at 15:22 Comment(1)
thresholdedData was giving me a black image. I replaced it with: thresholdedData = np.where(imgData > THRESHOLD_VALUE, 255, 0). You can then use Image.fromarray() (I think it was) to load the image in PIL, then do <your pil object>.save("<name of file>.bmp") to save it to your file system. So matplotlib is not required.Embankment
D
2

A simple way to do it using python :

Python
import numpy as np
import imageio

image = imageio.imread(r'[image-path]', as_gray=True)

# getting the threshold value
thresholdValue = np.mean(image)

# getting the dimensions of the image
xDim, yDim = image.shape

# turn the image into a black and white image
for i in range(xDim):
    for j in range(yDim):
        if (image[i][j] > thresholdValue):
            image[i][j] = 255
        else:
            image[i][j] = 0

Disobedient answered 1/7, 2019 at 19:2 Comment(0)
B
1

this is how i did it its havd better results like a gray filter

from PIL import Image
img = Image.open("profile.png")
BaW = img.convert("L")
BaW.save("profileBaW.png")
BaW.show()
Brackish answered 1/6, 2021 at 11:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.