46

How would I take an RGB image in Python and convert it to black and white? Not grayscale, I want each pixel to be either fully black (0, 0, 0) or fully white (255, 255, 255).

Is there any built-in functionality for getting it done in the popular Python image processing libraries? If not, would the best way be just to loop through each pixel, if it's closer to white set it to white, if it's closer to black set it to black?

Tom
  • 4,422
  • 3
  • 24
  • 36
  • 2
    I don't know Python, but [threshold](http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html?highlight=threshold#threshold) and [this example](http://docs.opencv.org/doc/tutorials/imgproc/threshold/threshold.html) may be helpful – WangYudong Sep 13 '13 at 03:41
  • @WangYudong OpenCV is mad overkill for this – Nick T Sep 13 '13 at 04:05
  • @NickT I was actually hoping to figure it out with OpenCV because the rest of my script is using OpenCV for Hough line transform. Still haven't figured out how to do that in PIL... Or how to convert between PIL and OpenCV for that matter. – Tom Sep 13 '13 at 04:07
  • 1
    @Tom Does `img = opencv.adaptors.PIL2Ipl(pilimg)` work for converting to an opencv image you can use? – Kyle Kelley Sep 13 '13 at 16:33

8 Answers8

99

Scaling to Black and White

Convert to grayscale and then scale to white or black (whichever is closest).

Original:

meow meow tied up cat

Result:

Black and white Cat, Pure

Pure Pillow implementation

Install pillow if you haven't already:

$ pip install pillow

Pillow (or PIL) can help you work with images effectively.

from PIL import Image

col = Image.open("cat-tied-icon.png")
gray = col.convert('L')
bw = gray.point(lambda x: 0 if x<128 else 255, '1')
bw.save("result_bw.png")

Alternatively, you can use Pillow with numpy.

Pillow + Numpy Bitmasks Approach

You'll need to install numpy:

$ pip install numpy

Numpy needs a copy of the array to operate on, but the result is the same.

from PIL import Image
import numpy as np

col = Image.open("cat-tied-icon.png")
gray = col.convert('L')

# Let numpy do the heavy lifting for converting pixels to pure black or white
bw = np.asarray(gray).copy()

# Pixel range is 0...255, 256/2 = 128
bw[bw < 128] = 0    # Black
bw[bw >= 128] = 255 # White

# Now we put it back in Pillow/PIL land
imfile = Image.fromarray(bw)
imfile.save("result_bw.png")

Black and White using Pillow, with dithering

Using pillow you can convert it directly to black and white. It will look like it has shades of grey but your brain is tricking you! (Black and white near each other look like grey)

from PIL import Image 
image_file = Image.open("cat-tied-icon.png") # open colour image
image_file = image_file.convert('1') # convert image to black and white
image_file.save('/tmp/result.png')

Original:

meow meow color cat

Converted:

meow meow black and white cat

Black and White using Pillow, without dithering

from PIL import Image 
image_file = Image.open("cat-tied-icon.png") # open color image
image_file = image_file.convert('1', dither=Image.NONE) # convert image to black and white
image_file.save('/tmp/result.png')
Kyle Kelley
  • 13,804
  • 8
  • 49
  • 78
  • 9
    Must admit that the cat is much cuter without dithering. – Kyle Kelley Sep 13 '13 at 04:31
  • 3
    You were just lucky that the levels worked out, this could have been very ugly indeed. And I'm sure PIL (and pillow?) have a way to do thresholding without resorting to Numpy. – Mark Ransom Sep 13 '13 at 04:57
  • 2
    There's got to be and boy am I a glutton for refactoring code. Feel free to edit to your heart's content if you find it. :) – Kyle Kelley Sep 13 '13 at 04:58
  • 2
    @MarkRansom - Nice edit! Think it's ok to move your tighter code up to the top and only provide the numpy part for flexibility? – Kyle Kelley Sep 13 '13 at 05:16
  • The `lambda` approach is going to make a Python function call for every pixel of the image, it's going to be unnecessarily slow when compared to numpy's boolean masking. – Jaime Sep 13 '13 at 13:07
  • 2
    @Jaime, no it doesn't - the `point` function caches the return values from the function, so it will only be called 256 times at most. – Mark Ransom Sep 13 '13 at 13:23
  • @Kyle, it's your answer, do whatever you want! – Mark Ransom Sep 13 '13 at 13:24
  • Well now you've nerd sniped me and I'm going to have to look at performance. Sadly the numpy one has to make a copy (asarray returns a readonly copy). – Kyle Kelley Sep 13 '13 at 13:26
  • @MarkRansom Ah, clever! – Jaime Sep 13 '13 at 14:59
  • You can use `.convert('1', dither=Image.NONE)` to get result without dithering :) – Pavel Oct 12 '16 at 21:47
  • I am not getting black and white but red and blue for the second approach, why is that? I did bw[bw<50] = 0 bw[bw>=50] = 255 mfile = Image.fromarray(bw) plt.imshow(mfile) – Meek Feb 14 '18 at 17:08
  • Almost asked another question but found your answer. Using `bw = im_gray.point(lambda x: 0 if x<255 else 255, '1')` does the job! – CopyPasteIt Jan 30 '19 at 17:40
5

I would suggest converting to grayscale, then simply applying a threshold (halfway, or mean or meadian, if you so choose) to it.

from PIL import Image

col = Image.open('myimage.jpg')
gry = col.convert('L')
grarray = np.asarray(gry)
bw = (grarray > grarray.mean())*255
imshow(bw)
askewchan
  • 45,161
  • 17
  • 118
  • 134
3
img_rgb = cv2.imread('image.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
(threshi, img_bw) = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
monti
  • 734
  • 8
  • 16
1

Pillow, with dithering

Using pillow you can convert it directly to black and white. It will look like it has shades of grey but your brain is tricking you! (Black and white near each other look like grey)

from PIL import Image 
image_file = Image.open("cat-tied-icon.png") # open colour image
image_file = image_file.convert('1') # convert image to black and white
image_file.save('/tmp/result.png')

Original:

meow meow color cat

Converted:

meow meow black and white cat

Kyle Kelley
  • 13,804
  • 8
  • 49
  • 78
1

And you can use colorsys (in the standard library) to convert rgb to hls and use the lightness value to determine black/white:

import colorsys
# convert rgb values from 0-255 to %
r = 120/255.0
g = 29/255.0
b = 200/255.0
h, l, s = colorsys.rgb_to_hls(r, g, b)
if l >= .5:
    # color is lighter
    result_rgb = (255, 255, 255)
elif l < .5:
    # color is darker
    result_rgb = (0,0,0)
monkut
  • 42,176
  • 24
  • 124
  • 155
0

Using opencv You can easily convert rgb to binary image

import cv2
%matplotlib inline 
import matplotlib.pyplot as plt
from skimage import io
from PIL import Image
import numpy as np

img = io.imread('http://www.bogotobogo.com/Matlab/images/MATLAB_DEMO_IMAGES/football.jpg')
img = cv2.cvtColor(img, cv2.IMREAD_COLOR)
imR=img[:,:,0] #only taking gray channel
print(img.shape)
plt.imshow(imR, cmap=plt.get_cmap('gray'))

#Gray Image
plt.imshow(imR)
plt.title('my picture')
plt.show()

#Histogram Analyze

imgg=imR
hist = cv2.calcHist([imgg],[0],None,[256],[0,256])
plt.hist(imgg.ravel(),256,[0,256])

# show the plotting graph of an image

plt.show()

#Black And White
height,width=imgg.shape
for i in range(0,height):
  for j in range(0,width):
     if(imgg[i][j]>60):
        imgg[i][j]=255
     else:
        imgg[i][j]=0

plt.imshow(imgg)
shawon
  • 984
  • 7
  • 15
  • When using NumPy, you should avoid loops. It can be written as ``imgg[ imgg< 60 ] = 0`` and ``imgg[ imgg >= 60 ] = 255`` – Ömer Şayli Jan 12 '22 at 06:40
0

Here is the code for creating binary image using opencv-python :

img = cv2.imread('in.jpg',2)

ret, bw_img = cv2.threshold(img,127,255,cv2.THRESH_BINARY)

cv2.imshow("Output - Binary Image",bw_img)
Maifee Ul Asad
  • 3,992
  • 6
  • 38
  • 86
0

If you don't want to use cv methods for the segmentation and understand what you are doing, treat the RGB image as matrix.

image = mpimg.imread('image_example.png') # your image
R,G,B = image[:,:,0], image[:,:,1], image[:,:,2] # the 3 RGB channels
thresh = [100, 200, 50] # example of triple threshold

# First, create an array of 0's as default value
binary_output = np.zeros_like(R)
# then screen all pixels and change the array based on RGB threshold.
binary_output[(R < thresh[0]) & (G > thresh[1]) & (B < thresh[2])] = 255

The result is an array of 0's and 255's based on a triple condition.

Gabriel123
  • 426
  • 5
  • 11