Add, blend, mask and threshold: changing each pixel on its own, and the overflow traps along the way.
Pixel operations modify pixel values directly.
Each output pixel depends only on the corresponding input pixel. This deck.
The output depends on a neighbourhood. Covered in filtering.
add, subtract, multiply, divide
AND, OR, XOR, NOT
binary, adaptive
rescaling pixel values
result = img1 + img2
Brightens the image. Careful of overflow!
result = img1 - img2
Finds differences: motion detection.
result = img * scalar
Contrast adjustment, masking.
Important: use cv2.add() instead of + to prevent overflow.
255 + 10 = 9
Wraps around. Fast, but beware overflow.
cv2.add(255, 10) = 255
Clips to the max. Safe: it saturates.
import cv2
import numpy as np
# numpy addition (overflow)
img = np.array([[250]], dtype=np.uint8)
result = img + np.array([[10]], dtype=np.uint8)
# ans: (250 + 10) % 256 = 4 (overflow!)
# opencv addition (saturation)
result = cv2.add(img, np.array([[10]], dtype=np.uint8))
# ans: min(250 + 10, 255) = 255 (clips to max)
# brighten image (add constant)
bright = cv2.add(img, np.array([50])) # ans:
# ans: adds 50 to all pixels (saturates at 255)
# darken image (subtract constant)
dark = cv2.subtract(img, np.array([50])) # ans:
# ans: subtracts 50 (saturates at 0)
# alternative using numpy (watch overflow!)
bright = np.clip(img.astype(int) + 50, 0, 255).astype(np.uint8)
# ans: clip prevents overflow (do the sum in a wider type first)
A scalar passed as np.array([50]) is added to the first channel only on a colour image; use (50, 50, 50, 0) to shift all three.
The bright sky wraps past 255 into dark garbage; the saturated version just turns white.
# weighted sum of two images
img1 = cv2.imread('image1.jpg')
img2 = cv2.imread('image2.jpg')
# alpha blending: result = α*img1 + β*img2 + γ
blended = cv2.addWeighted(img1, 0.7, img2, 0.3, 0) # ans:
# ans: 70% img1 + 30% img2
# must satisfy: α + β = 1 for proper blending
# ans: ensures output stays in the valid range
# bitwise AND (intersection)
result = cv2.bitwise_and(img1, img2)
# ans: keeps pixels where both are non-zero
# bitwise OR (union)
result = cv2.bitwise_or(img1, img2)
# ans: combines regions from both images
# bitwise NOT (invert)
result = cv2.bitwise_not(img)
# ans: inverts all bits (negative image)
# bitwise XOR (difference)
result = cv2.bitwise_xor(img1, img2)
# ans: highlights differences
# simple thresholding
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# ans: pixels > 127 become 255, others become 0
# inverse threshold
ret, inv = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
# ans: pixels > 127 become 0, others become 255
# ret is the threshold value used
# ans: useful for automatic thresholding methods (Otsu)
# create circular mask
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.circle(mask, (center_x, center_y), radius, 255, -1)
# ans: the circular region is 255, the rest is 0
# apply mask to image
result = cv2.bitwise_and(img, img, mask=mask)
# ans: keeps only the circular region, the rest is black
# inverse masking
inv_mask = cv2.bitwise_not(mask)
background = cv2.bitwise_and(img, img, mask=inv_mask)
# ans: keeps everything except the circle
# normalize to 0-255 range
normalized = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX)
# ans: scales the min value to 0, the max to 255
# why normalize?
# ans: improves contrast, standardizes the range
# ans: useful before visualization or further processing
# manual normalization
norm = ((img - img.min()) / (img.max() - img.min()) * 255).astype(np.uint8) # ans:
# ans: same effect as cv2.normalize
Answer first, then press → to check.
# what's the difference?
result1 = img + 10
result2 = cv2.add(img, 10)
# ans: result1 wraps (modulo), result2 saturates
# when does overflow occur?
img = np.array([[250]], dtype=np.uint8)
result = img + np.array([[20]], dtype=np.uint8)
# ans: (250+20) % 256 = 14, overflow!
# what is the alpha blending formula?
result = cv2.addWeighted(img1, 0.7, img2, 0.3, 0)
# ans: result = 0.7*img1 + 0.3*img2 + 0
# what should α + β equal?
# ans: 1.0 for proper blending (preserves intensity)
# what does bitwise_and do with masks?
result = cv2.bitwise_and(img, img, mask=mask)
# ans: keeps pixels where mask=255, others become 0
# what does thresholding do?
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# ans: converts to binary (only 0 or 255)
# why normalize images?
# ans: improves contrast, standardizes the range
# what is image inversion?
inv = cv2.bitwise_not(img)
# ans: dark becomes light, light becomes dark
# brighten image by 30
bright = cv2.add(img, np.array([30])) # ans:
# ans: adds 30, saturates at 255
# darken image by 50
dark = cv2.subtract(img, np.array([50])) # ans:
# ans: subtracts 50, saturates at 0
# blend two images 50-50
blended = cv2.addWeighted(img1, 0.5, img2, 0.5, 0) # ans:
# ans: equal mix of both images
# create negative image
negative = cv2.bitwise_not(img) # ans:
# ans: 255 - pixel_value for all pixels
# threshold at 100
ret, binary = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY) # ans:
# ans: > 100 becomes 255, ≤ 100 becomes 0
# create rectangular mask
mask = np.zeros((480, 640), dtype=np.uint8) # ans:
mask[100:300, 200:400] = 255 # ans:
# ans: rows 100-300, columns 200-400 are white
# what is the output?
img = np.array([[200]], dtype=np.uint8)
result = cv2.add(img, np.array([[100]], dtype=np.uint8))
# ans: 255 (saturates at max)
# XOR of image with itself?
result = cv2.bitwise_xor(img, img)
# ans: all zeros (anything XOR itself = 0)
# normalize image to 0-1 range
norm = img.astype(float) / 255.0 # ans:
# ans: scales to the [0.0, 1.0] range
# apply mask to extract ROI
roi = cv2.bitwise_and(img, img, mask=mask) # ans:
# ans: region of interest extracted, the rest is black
# what happens here?
img = np.array([[100, 200]], dtype=np.uint8)
result = img * 2
# ans: [200, 400 % 256] = [200, 144] (overflow on the 2nd)
# using cv2.multiply instead?
result = cv2.multiply(img, np.array([[2, 2]], dtype=np.uint8))
# ans: [200, 255] (saturates at 255)
# blend with a gamma
blended = cv2.addWeighted(img1, 0.6, img2, 0.4, 10)
# ans: 0.6*img1 + 0.4*img2 + 10 (adds 10 to all pixels)
# invert only specific channel
img = cv2.imread('img.jpg')
img[:, :, 0] = cv2.bitwise_not(img[:, :, 0])
# ans: inverts the blue channel only (OpenCV stores BGR)
# threshold with different max value
ret, binary = cv2.threshold(gray, 127, 200, cv2.THRESH_BINARY)
# ans: pixels > 127 become 200 (not 255)
# combine two masks
mask3 = cv2.bitwise_or(mask1, mask2)
# ans: union of both masks