Using PIL to turn a RGB image into a pure black and white image
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)
Solution 1:
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
Solution 2:
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:
You can click on the images to view the unscaled versions.
Solution 3:
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
:
Solution 4:
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()