note.nkmk.me

Binarize image with Python, NumPy, OpenCV

Posted: 2019-05-14 / Modified: 2020-10-09 / Tags: Python, OpenCV, Image Processing, NumPy

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: cv2.threshold()
  • 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.

Sponsored Link

Image binarization with OpenCV: cv2.threshold()

Take the following image as an example.

import cv2

im = cv2.imread('data/src/lena_square_half.png')

lena

You can binarize an image with cv2.threshold().

If 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 0.

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)

lena OpenCV threshold THRESH_BINARY

A tuple of used threshold and processed array (output image) is returned. It can be stored in each variable as in the example above.

When type is set to cv2.THRESH_TOZERO, the value greater than the threshold thresh remains the same, and the other values are replaced with 0.

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)

lena OpenCV threshold THRESH_TOZERO

See the official documentation below for the values you can specify for type.

maxval is not used with cv2.THRESH_TOZERO, and thresh is not used with cv2.THRESH_OTSU and cv2.THRESH_TRIANGLE described later, but they cannot be omitted.

Automatic image thresholding (Otsu's method etc.)

When 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.

Note that cv2.THRESH_OTSU and 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 cv2.threshold().

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)

lena OpenCV threshold THRESH_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 to192 for explanation. If you want to binarize to black and white, you can set it to 255.

Sponsored Link

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 ndarray.

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.

lena gray

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]]

Since True is regarded as 1 and False is regarded as 0, when multiplied by 255 which is the Max value of uint8, True becomes 255 (white) and False becomes 0 (black).

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')

lena NumPy binarization THRESH_BINARY

The above example corresponds to cv2.threshold() with cv2.THRESH_BINARY.

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 False is 0 (black), which corresponds to cv2.THRESH_TOZERO.

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')

lena NumPy binarization THRESH_TOZERO

For color image

By applying different values ​​to each RGB color, you can create a colorful image.

Generate a three-dimensional empty ndarray with np.empty() and store results of multiplying each color (each channel) by each values.

The size (height, width) obtained by shape is expanded by * and specified in np.empty().

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')

lena NumPy binarization color

It is also possible to apply the negation operator ~ to the boolean ndarray.

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')

lena NumPy binarization color2

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')

lena NumPy binarization from color

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 zip().

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')

lena NumPy binarization from color2

Sponsored Link
Share

Related Categories

Related Posts