Binarize image with Python, NumPy, OpenCV
This post describes how to binarize an image into black and white with a threshold.
There are two ways: one is to use OpenCV function
cv2.threshold(), and the other is to process
ndarray with a basic operation of NumPy. OpenCV is not necessary in the latter case.
- Image binarization with OpenCV:
- Automatic image thresholding (Otsu's method etc.)
- Image binarization with NumPy (without OpenCV)
- For grayscale image
- For color image
In the following sample code , OpenCV version is
4.2. Note that the behavior may be different with different versions. You can get the official documentation of each version at the following.
Image binarization with OpenCV: cv2.threshold()
Take the following image as an example.
import cv2 im = cv2.imread('data/src/lena_square_half.png')
You can binarize an image with
- OpenCV: Miscellaneous Image Transformations - threshold()
retval, dst = cv2.threshold(src, thresh, maxval, type)
type is set to
cv2.THRESH_BINARY, any value greater than the threshold
thresh is replaced with
maxval and the other values are replaced with
In case of color images, each color (channel) is processed separately. If you want black and white images, convert them to grayscale first as in the example of
cv2.THRESH_OTSU described later.
th, im_th = cv2.threshold(im, 128, 255, cv2.THRESH_BINARY) print(th) # 128.0 cv2.imwrite('data/dst/opencv_th.jpg', im_th)
A tuple of used threshold and processed array (output image) is returned. It can be stored in each variable as in the example above.
- Related: Unpack a tuple / list in Python
type is set to
cv2.THRESH_TOZERO, the value greater than the threshold
thresh remains the same, and the other values are replaced with
th, im_th_tz = cv2.threshold(im, 128, 255, cv2.THRESH_TOZERO) print(th) # 128.0 cv2.imwrite('data/dst/opencv_th_tz.jpg', im_th_tz)
See the official documentation below for the values you can specify for
maxval is not used with
thresh is not used with
cv2.THRESH_TRIANGLE described later, but they cannot be omitted.
Automatic image thresholding (Otsu's method etc.)
type is set to
cv2.THRESH_OTSU, the threshold is automatically selected by the Otsu's method, and if it is set to
cv2.THRESH_TRIANGLE, the threshold is automatically selected by the triangle method.
cv2.THRESH_TRIANGLE only support 8-bit single channel images as of version
4.2.0. An error will occur if a color image (three-dimensional array) is specified.
# th, im_th_otsu = cv2.threshold(im, 128, 192, cv2.THRESH_OTSU) # error: OpenCV(4.2.0) /tmp/opencv-20200105-17262-cwpzm4/opencv-4.2.0/modules/imgproc/src/thresh.cpp:1529: error: (-215:Assertion failed) src.type() == CV_8UC1 in function 'threshold'
Convert to grayscale and then use
im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) th, im_gray_th_otsu = cv2.threshold(im_gray, 128, 192, cv2.THRESH_OTSU) print(th) # 117.0 cv2.imwrite('data/dst/opencv_th_otsu.jpg', im_gray_th_otsu)
Values greater than the automatically selected threshold are replaced with
maxval, and other values are replaced with
0. In the above example,
maxval is set to
192 for explanation. If you want to binarize to black and white, you can set it to
Image binarization with NumPy (without OpenCV)
If you just want to binarize to black and white with threshold values, you can do it with basic NumPy operations.
For grayscale image
As a simple example, binarize a grayscale image.
import numpy as np from PIL import Image im_gray = np.array(Image.open('data/src/lena_square_half.png').convert('L')) print(type(im_gray)) # <class 'numpy.ndarray'>
Here, as an example without OpenCV, the image is read by Pillow and converted to
Of course, there is no problem reading images with OpenCV. Note that the order of colors is different when reading a color image with OpenCV.
- Relaterd: Image processing with Python, NumPy
Using the comparison operator on a NumPy array
ndarray returns a boolean
ndarray comparing each element of the array.
thresh = 128 im_bool = im_gray > thresh print(im_bool) # [[ True True True ... True True False] # [ True True True ... True True False] # [ True True True ... True False False] # ... # [False False False ... False False False] # [False False False ... False False False] # [False False False ... False False False]]
True is regarded as
False is regarded as
0, when multiplied by
255 which is the Max value of
255 (white) and
maxval = 255 im_bin = (im_gray > thresh) * maxval print(im_bin) # [[255 255 255 ... 255 255 0] # [255 255 255 ... 255 255 0] # [255 255 255 ... 255 0 0] # ... # [ 0 0 0 ... 0 0 0] # [ 0 0 0 ... 0 0 0] # [ 0 0 0 ... 0 0 0]] Image.fromarray(np.uint8(im_bin)).save('data/dst/numpy_binarization.png')
The above example corresponds to
If you multiply the boolean
ndarray of the comparison result by the original
ndarray, the pixel value of
True remains original and the pixel value of
0 (black), which corresponds to
im_bin_keep = (im_gray > thresh) * im_gray print(im_bin_keep) # [[162 161 156 ... 169 169 0] # [162 161 156 ... 169 169 0] # [164 155 159 ... 145 0 0] # ... # [ 0 0 0 ... 0 0 0] # [ 0 0 0 ... 0 0 0] # [ 0 0 0 ... 0 0 0]] Image.fromarray(np.uint8(im_bin_keep)).save('data/dst/numpy_binarization_keep.png')
For color image
By applying different values to each RGB color, you can create a colorful image.
Generate a three-dimensional empty
np.empty() and store results of multiplying each color (each channel) by each values.
(height, width) obtained by
shape is expanded by
* and specified in
- Related: Get image size (width, height) with Python, OpenCV, Pillow (PIL)
- Related: Expand and pass list, tuple, dict to function arguments in Python
im_bool = im_gray > 128 im_dst = np.empty((*im_gray.shape, 3)) r, g, b = 255, 128, 32 im_dst[:, :, 0] = im_bool * r im_dst[:, :, 1] = im_bool * g im_dst[:, :, 2] = im_bool * b Image.fromarray(np.uint8(im_dst)).save('data/dst/numpy_binarization_color.png')
It is also possible to apply the negation operator
~ to the boolean
im_bool = im_gray > 128 im_dst = np.empty((*im_gray.shape, 3)) r, g, b = 128, 160, 192 im_dst[:, :, 0] = im_bool * r im_dst[:, :, 1] = ~im_bool * g im_dst[:, :, 2] = im_bool * b Image.fromarray(np.uint8(im_dst)).save('data/dst/numpy_binarization_color2.png')
Note that when saving an image with the OpenCV function
cv2.imwrite(), it is necessary to set the color sequence to BGR.
So far, it has been processed based on the grayscale image, but it is also possible to process the color image like
cv2.threshold() with the same idea as the above example.
Generate an empty
ndarray and store each result in each color (each channel). Since the original is a color image (three-dimensional array),
np.empty_like() is used.
im = np.array(Image.open('data/src/lena_square_half.png')) im_th = np.empty_like(im) thresh = 128 maxval = 255 for i in range(3): im_th[:, :, i] = (im[:, :, i] > thresh) * maxval Image.fromarray(np.uint8(im_th)).save('data/dst/numpy_binarization_from_color.png')
More flexible processing than
cv2.threshold() is possible, such as changing the threshold value or changing the replacement value for each color. You can write neatly by using a list (or tuple) and
l_thresh = [64, 128, 192] l_maxval = [64, 128, 192] for i, thresh, maxval in zip(range(3), l_thresh, l_maxval): im_th[:, :, i] = (im[:, :, i] > thresh) * maxval Image.fromarray(np.uint8(im_th)).save('data/dst/numpy_binarization_from_color2.png')