What does bitwise_and operator exactly do in openCV?
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:
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 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.