image.png— the generated test imageblue_mask.png— the binary segmentation maskblue_only.png— the masked result
Segment an image by colour
In this practical, we will generate our own image, identify blue pixels using a BGR range, create a binary mask, and apply that mask to keep only the blue parts.
- How OpenCV stores colour in BGR order
- What lower and upper colour bounds mean
- How
cv2.inRange()creates a mask - How
cv2.bitwise_and()applies a mask
The complete workflow
Shapes + colours
Lower + upper BGR
255 or 0
Keep matching pixels
Build a controlled image first
Instead of depending on a photograph, we create a predictable scene: three blue objects, one red object, and a white background.
import cv2
import numpy as np
image = np.full((400,600,3), 255, dtype=np.uint8)
# Three blue objects
cv2.circle(image, (120, 100), 50, (255, 0, 0), -1)
cv2.circle(image, (300, 250), 50, (255, 0, 0), -1)
cv2.rectangle(image, (450, 80), (550, 180), (255, 0, 0), -1)
# One red object
cv2.circle(image, (150, 300), 50, (0, 0, 255), -1)
cv2.imwrite("image.png", image)What the important lines mean
Preview of the generated scene
Checkpoint
Before segmentation, identify the exact BGR value of the blue objects: [255, 0, 0].
What counts as blue?
We do not require every pixel to be exactly one BGR value. Instead, we define a minimum and maximum accepted value for each channel.
Lower blue
[200, 0, 0]
Minimum accepted B, G, R values.
Upper blue
[255, 80, 80]
Maximum accepted B, G, R values.
| Channel | Accepted range | Meaning |
|---|---|---|
| Blue | 200–255 | Blue must be strong. |
| Green | 0–80 | Allow only a little green. |
| Red | 0–80 | Allow only a little red. |
Test the rule yourself
Move the B, G, and R sliders. The page will tell you whether that pixel would pass our blue range.
Turn colour similarity into labels
cv2.inRange() checks every pixel against the lower and upper BGR bounds and returns a binary mask.
import cv2
import numpy as np
image = cv2.imread("image.png")
# Define the BGR range for blue
lower_blue = np.array([200, 0, 0])
upper_blue = np.array([255, 80, 80])
# Create binary mask
mask = cv2.inRange(image, lower_blue, upper_blue)
cv2.imwrite("blue_mask.png", mask)The key line
mask = cv2.inRange(image, lower_blue, upper_blue)Read one pixel
OpenCV looks at its B, G, and R values.
Check all three channels
The pixel must be inside the allowed range for Blue, Green, and Red.
Write a mask value
If it passes, the mask gets 255 (white). Otherwise it gets 0 (black).
Original image → binary mask
[255,0,0] → white ✅
[0,0,255] → black ❌
[255,255,255] → black ❌
Why is this segmentation?
Every pixel has been assigned to one of two groups: blue foreground or not-blue background.
Keep only the selected pixels
The mask can now control which pixels from the original image survive in the result.
import cv2
import numpy as np
image = cv2.imread("image.png")
# Blue range
lower_blue = np.array([200, 0, 0])
upper_blue = np.array([255, 80, 80])
# Create mask
mask = cv2.inRange(image, lower_blue, upper_blue)
# Apply mask to original image
result = cv2.bitwise_and(image, image, mask=mask)
cv2.imwrite("blue_only.png", result)The key operation
result = cv2.bitwise_and(image, image, mask=mask)| Mask value | What happens? | Result |
|---|---|---|
255 | Keep the corresponding original pixel. | Blue objects remain blue. |
0 | The result pixel becomes zero. | That pixel becomes black. |
See the three stages together
[0,0,0] at those positions.From colour to segmentation
You have completed a full rule-based colour segmentation pipeline.
What does a white pixel in blue_mask.png mean?
Correct. White in this mask means the corresponding original pixel satisfied the selected blue range.
Why does the background become black after bitwise_and?
Correct. A zero mask rejects that pixel, so the output pixel becomes zero: black.
One-sentence summary
Colour segmentation groups pixels by numerical colour similarity, stores the grouping in a binary mask, and uses that mask to isolate the desired region.