What does bitwise_and operator exactly do in openCV?

Viewed 47953

I did not exactly understand what the "bitwise_and" operator does when used in openCV. I would also like to know about it's parameters.

6 Answers

The general usage is that you want to get a subset of an image defined by another image, typically referred to as a "mask".

So suppose you want to "grab" the top left quadrant of an 8x8 image. You could form a mask that looks like:

1 1 1 1 0 0 0 0
1 1 1 1 0 0 0 0
1 1 1 1 0 0 0 0
1 1 1 1 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

You could produce the above image with Python with:

import numpy as np

mask = np.zeros(shape=(8,8), dtype=bool)
mask[0:4,0:4] = True

Then suppose you had an image like:

1 0 1 0 1 1 1 1
0 1 0 1 0 0 0 0
1 0 1 0 1 1 1 1
0 1 0 1 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0

For concreteness, imagine that the above image is a simplified representation of the U.S.A. flag: stars in the top left, bars everywhere else. Suppose you wanted to form the above image. You could use the mask, and bitwise_and and bitwise_or to help you.

imageStars = np.ones(shape=(8,8), dtype=bool)
for r, row in enumerate(imageStars):
    for c, col in enumerate(row):
        if r % 2 != c % 2: # even row, odd column, or odd row, even column
            imageStars[r,c] = False

imageBars = np.zeros(shape=(8,8), dtype=bool)
for r, row in enumerate(imageStars):
    if r % 2 == 0:
        imageBars[r,:] = True

Now you have an image of stars:

1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1    
1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1    
1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1    
1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1

And an image of bars:

1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0

And you want to combine them in a particular way, to form the flag, with the stars in the upper left quadrant and the bars everywhere else.

imageStarsCropped = cv2.bitwise_and(imageStars, mask)

imageStarsCropped will look like:

1 0 1 0 0 0 0 0
0 1 0 1 0 0 0 0    
1 0 1 0 0 0 0 0
0 1 0 1 0 0 0 0  
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

Do you see how it was formed? The bitwise_and returns 1 at every pixel where imageStars is 1 AND mask is 1; else, it returns 0.

Now let's get imageBarsCropped. First, let's reverse the mask:

maskReversed = cv2.bitwise_not(mask)

bitwise_not turns 1's into 0's and 0's into 1's. It "flips the bits". maskReversed will look like:

0 0 0 0 1 1 1 1
0 0 0 0 1 1 1 1
0 0 0 0 1 1 1 1
0 0 0 0 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1

Now, we will use maskReversed to "grab" the portion of imageBars that we want.

imageBarsCropped = cv2.bitwise_and(imageBars, maskReversed)

imageBarsCropped will look like:

0 0 0 0 1 1 1 1
0 0 0 0 0 0 0 0
0 0 0 0 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0

Now, let's combined the two "cropped" images to form the flag!

imageFlag = cv2.bitwise_or(imageStarsCropped, imageBarsCropped)

imageFlag will look like:

1 0 1 0 1 1 1 1
0 1 0 1 0 0 0 0
1 0 1 0 1 1 1 1
0 1 0 1 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0

Do you see why? bitwise_or returns 1 whenever imageStarsCropped[r,c]==1 OR imageBarsCropped[r,c]==1.

Well, I hope this helps you to understand bitwise operations in OpenCV. These properties have a one-to-one correspondence with bitwise operations with binary numbers that the computer does to do arithmetic.

What does the operator do?

bitwise_and, bitwise_or and bitwise_xor perform a bitwise operation on elements taken from two arrays, src1 nd src2. bitwise_not is similar. Bitwise means the boolean operation is done between each bit of the values, bit per bit.

What is the mask parameter?

It's actually not a Boolean mask (and a Boolean array will be rejected). It's a uint8 array type where values are checked as being 0 or not. The "mask" has the same x,y shape than the images, but its elements are scalar, that is for an image 100 x 50 x 3 of uint8, the mask must be an array 100 x 50 of uint8.

How is the mask used?

This mask determines whether the operation will be performed on the pair of pixels at location x,y. If the element at position x,y in the mask is 0, no operation is performed, and the pixel in the resulting array is 0,0,0 (black). If the mask element at position x,y is not null then the bitwise operation determines the value in the resulting array.

Let's say we want to extract pixels within a circle and make other pixels white. This can be done using to bitwise operation with complementary masks:

openCV bitwise_and and bitwise_or

import numpy as np
import cv2
import matplotlib.pyplot as plt

images = 'images/'

# Read image 330 x 379 x 3 uint8
img = cv2.imread(images + 'sample.jpg')

# Reorder channels as display is done with pyplot
img[:,:,[0,1,2]] = img[:,:,[2,1,0]]

# Create white image, 3 channels 330 x 379 x 3 uint8
w_3c = np.full_like(img, fill_value=(255,255,255))

# Define disk elements
center = (img.shape[1]//2, img.shape[0]//2)
radius = int(min(center) * .9)

# Create white disk, 3 channels 330 x 379 x 3 uint8
# and single channel 330 x 379 uint8
d_3c = np.zeros_like (img[:,:], dtype='uint8')
cv2.circle(d_3c, center, radius, [255]*3, thickness=cv2.FILLED)
d_1c = d_3c[:,:,0]

# Extract pixels disk using white disk single channel
masked = cv2.bitwise_and(img, w_3c, mask=d_1c)

# Add white background
d_3c_i = ~d_3c
final = cv2.bitwise_or(img, d_3c_i)

# Prepare to plot
to_plot = [[(img,'img'),
            (w_3c,'w_3c')],
           [(d_3c,'d_3c'),
            (d_1c,'d_1c'),
            (d_3c_i,'d_3c_i')],
           [(masked,'img & w_3c mask d_1c'),
            (final,'img | d_3c_i)')]]
r = len(to_plot)
c = max([len(l) for l in to_plot])

# Show everthing
fig,ax = plt.subplots(r,c, tight_layout=True)
for i in range(r):
    for j in range(c):
        axij = ax[i,j]
        if j < len(to_plot[i]):
            dims = to_plot[i][j][0].ndim
            if dims <= 3:
                axij.imshow(to_plot[i][j][0], cmap='gray')
            else:
                axij.imshow(to_plot[i][j][0])
            axij.set_title(to_plot[i][j][1])
            axij.set_xticks([])
            axij.set_yticks([])
        else:
            axij.set_axis_off()

plt.ioff()
plt.show()

The code can probably be improved.

Just an elaboration to the above answers - not a full answer. In real life image arrays will have values from 0 to 255, and not just 0s and 1s. What bitwise_or does in this case is to convert each corresponding number in the two images to its binary form, and then do an or or and or other operation.

Example:

Consider two values 233 and 180. A bitwise_or of these numbers gives us 253 (using cv2.bitwise_or(np.array([233]), np.array([180])). The binary equivalents of 233 and 180 are 11101001 and 10110100. Doing a bitwise_or gives us 11111101 which is the same as 253. This number was obtained by doing an or for each digit in the binary equivalents of 233 and 180 (you can verify)

bin(233) #0b in the output just means it is a binary number
0b11101001

bin(180)
0b10110100

bin(253)
0b11111101

cv2.bitwise_or(np.array([233]), np.array([180]))
array([[253]], dtype=int32)

and, not and xor work similarly as or by doing the logic operations on the bits of the numbers.

The answer by @mannyglover with the US flag example is a great one! I would also just quickly add that for actual images, any pixel value at 0 (pitch black) would be 'false'. Since false & anything = false, any other pixel with value above 0 would become 0.

This means that after applying bitwise_and, pitch black pixels of the mask would turn respective pixels in the original image pitch black.

Here is an example, done in OpenCV:

Original image: stack.jpg

Mask: mask.jpg

After applying result = cv2.bitwise_and(stack, stack, mask=mask) : result.jpg

To someone who is looking for an easy explanation about bitwise_or

Lets assume we are going to code cv2.bitwise_or, we have to:

  1. Perform bitwise OR of src1 array with src2 array at only those locations where mask is non zero

so assume mask is 100x100 shape with only mak1 = 1 and others all 0. Also src1 is 600x600x3 array all of value 255 and src2 is same as src1.

So after our cv2.bitwise_or operation, we get an output array of shape 600x600x3 with output1 = 255 (as this is the only place we bit wise OR'ed src1 and src2) and the rest of the locations of output array will be 0s.

Related