Pixel Operations

What are Pixel Operations?

Pixel operations modify pixel values directly:

  • Point operations: Each output pixel depends only on corresponding input pixel
  • Local operations: Output depends on neighborhood (covered in filtering)

Common pixel operations:

  • Arithmetic: add, subtract, multiply, divide
  • Logical: AND, OR, XOR, NOT
  • Thresholding: binary, adaptive
  • Normalization: rescaling pixel values

Image Arithmetic

Addition: Brightens image (careful of overflow!)

Subtraction: Finds differences, motion detection

Multiplication: Contrast adjustment, masking

Important: Use cv2.add() instead of + to prevent overflow

NumPy vs OpenCV Arithmetic

NumPy (modulo arithmetic):

  1. 255 + 10 = 9 # wraps around

OpenCV (saturated arithmetic):

  1. cv2.add(255, 10) = 255 # clips to max

When to use:

  • NumPy +: Fast, but beware overflow
  • cv2.add(): Safe, prevents overflow (saturates)

Image Addition

  1. import cv2
  2. import numpy as np
  3. # numpy addition (overflow)
  4. img = np.array([[250]], dtype=np.uint8)
  5. result = img + np.array([[10]], dtype=np.uint8)
  6. #ans: (250 + 10) % 256 = 4 (overflow!)
  7. # opencv addition (saturation)
  8. result = cv2.add(img, np.array([[10]], dtype=np.uint8))
  9. #ans: min(250 + 10, 255) = 255 (clips to max)

Brightening & Darkening

  1. # brighten image (add constant)
  2. #ans: bright = cv2.add(img, np.array([50]))
  3. #ans: adds 50 to all pixels (saturates at 255)
  4. # darken image (subtract constant)
  5. #ans: dark = cv2.subtract(img, np.array([50]))
  6. #ans: subtracts 50 (saturates at 0)
  7. # alternative using numpy (watch overflow!)
  8. bright = np.clip(img + 50, 0, 255).astype(np.uint8)
  9. #ans: clip prevents overflow

Image Blending

  1. # weighted sum of two images
  2. img1 = cv2.imread('image1.jpg')
  3. img2 = cv2.imread('image2.jpg')
  4. # alpha blending: result = α*img1 + β*img2 + γ
  5. #ans: blended = cv2.addWeighted(img1, 0.7, img2, 0.3, 0)
  6. #ans: 70% img1 + 30% img2
  7. # must satisfy: α + β = 1 for proper blending
  8. #ans: ensures output stays in valid range

Bitwise Operations

  1. # bitwise AND (intersection)
  2. result = cv2.bitwise_and(img1, img2)
  3. #ans: keeps pixels where both are non-zero
  4. # bitwise OR (union)
  5. result = cv2.bitwise_or(img1, img2)
  6. #ans: combines regions from both images
  7. # bitwise NOT (invert)
  8. result = cv2.bitwise_not(img)
  9. #ans: inverts all bits (negative image)
  10. # bitwise XOR (difference)
  11. result = cv2.bitwise_xor(img1, img2)
  12. #ans: highlights differences

Binary Thresholding

  1. # simple thresholding
  2. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  3. ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
  4. #ans: pixels > 127 become 255, others become 0
  5. # inverse threshold
  6. ret, inv = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
  7. #ans: pixels > 127 become 0, others become 255
  8. # ret is the threshold value used
  9. #ans: useful for automatic thresholding methods

Image Masking

  1. # create circular mask
  2. mask = np.zeros(img.shape[:2], dtype=np.uint8)
  3. cv2.circle(mask, (center_x, center_y), radius, 255, -1)
  4. #ans: circular region is 255, rest is 0
  5. # apply mask to image
  6. result = cv2.bitwise_and(img, img, mask=mask)
  7. #ans: keeps only circular region, rest black
  8. # inverse masking
  9. inv_mask = cv2.bitwise_not(mask)
  10. background = cv2.bitwise_and(img, img, mask=inv_mask)
  11. #ans: keeps everything except circle

Normalization

  1. # normalize to 0-255 range
  2. normalized = cv2.normalize(img, None, 0, 255, cv2.NORM_MINMAX)
  3. #ans: scales min value to 0, max to 255
  4. # why normalize?
  5. #ans: improves contrast, standardizes range
  6. #ans: useful before visualization or further processing
  7. # manual normalization
  8. #ans: norm = ((img - img.min()) / (img.max() - img.min()) * 255).astype(np.uint8)
  9. #ans: same effect as cv2.normalize

Exercises - Part 1 (Concepts)

  1. # what's the difference?
  2. result1 = img + 10
  3. result2 = cv2.add(img, 10)
  4. #ans: result1 wraps (modulo), result2 saturates
  5. # when does overflow occur?
  6. img = np.array([[250]], dtype=np.uint8)
  7. result = img + np.array([[20]])
  8. #ans: (250+20)%256 = 14, overflow!
  9. # what is alpha blending formula?
  10. result = cv2.addWeighted(img1, 0.7, img2, 0.3, 0)
  11. #ans: result = 0.7*img1 + 0.3*img2 + 0
  12. # what should α + β equal?
  13. #ans: 1.0 for proper blending (preserves intensity)

Exercises - Part 2 (Concepts)

  1. # what does bitwise_and do with masks?
  2. result = cv2.bitwise_and(img, img, mask=mask)
  3. #ans: keeps pixels where mask=255, others=0
  4. # what does thresholding do?
  5. ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
  6. #ans: converts to binary (only 0 or 255)
  7. # why normalize images?
  8. #ans: improves contrast, standardizes range
  9. # what is image inversion?
  10. inv = cv2.bitwise_not(img)
  11. #ans: dark becomes light, light becomes dark

Exercises - Part 3 (Coding)

  1. # brighten image by 30
  2. #ans: bright = cv2.add(img, np.array([30]))
  3. #ans: adds 30, saturates at 255
  4. # darken image by 50
  5. #ans: dark = cv2.subtract(img, np.array([50]))
  6. #ans: subtracts 50, saturates at 0
  7. # blend two images 50-50
  8. #ans: blended = cv2.addWeighted(img1, 0.5, img2, 0.5, 0)
  9. #ans: equal mix of both images

Exercises - Part 4 (Coding)

  1. # create negative image
  2. #ans: negative = cv2.bitwise_not(img)
  3. #ans: 255 - pixel_value for all pixels
  4. # threshold at 100
  5. #ans: ret, binary = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
  6. #ans: > 100 becomes 255, ≤ 100 becomes 0
  7. # create rectangular mask
  8. #ans: mask = np.zeros((480, 640), dtype=np.uint8)
  9. #ans: mask[100:300, 200:400] = 255
  10. #ans: rectangle from (100,200) to (300,400) is white

Exercises - Part 5 (Coding)

  1. # what is the output?
  2. img = np.array([[200]], dtype=np.uint8)
  3. result = cv2.add(img, np.array([[100]]))
  4. #ans: 255 (saturates at max)
  5. # XOR of image with itself?
  6. result = cv2.bitwise_xor(img, img)
  7. #ans: all zeros (anything XOR itself = 0)
  8. # normalize image to 0-1 range
  9. #ans: norm = img.astype(float) / 255.0
  10. #ans: scales to [0.0, 1.0] range
  11. # apply mask to extract ROI
  12. #ans: roi = cv2.bitwise_and(img, img, mask=mask)
  13. #ans: region of interest extracted, rest black

Exercises - Part 6 (Mixed)

  1. # what happens here?
  2. img = np.array([[100, 200]], dtype=np.uint8)
  3. result = img * 2
  4. #ans: [200, 400%256] = [200, 144] (overflow on 2nd)
  5. # using cv2.multiply instead?
  6. result = cv2.multiply(img, np.array([[2]]))
  7. #ans: [200, 255] (saturates at 255)
  8. # blend with different gamma
  9. blended = cv2.addWeighted(img1, 0.6, img2, 0.4, 10)
  10. #ans: 0.6*img1 + 0.4*img2 + 10 (adds 10 to all pixels)

Exercises - Part 7 (Mixed)

  1. # invert only specific channel
  2. img = cv2.imread('img.jpg')
  3. img[:, :, 0] = cv2.bitwise_not(img[:, :, 0])
  4. #ans: inverts blue channel only
  5. # threshold with different max value
  6. ret, binary = cv2.threshold(gray, 127, 200, cv2.THRESH_BINARY)
  7. #ans: pixels > 127 become 200 (not 255)
  8. # combine two masks
  9. mask3 = cv2.bitwise_or(mask1, mask2)
  10. #ans: union of both masks

Google tag (gtag.js)