K-Means Segmentation
STEP 1 · THE BIG IDEA

Put similar values into groups

K-means is a clustering algorithm. It finds groups in data without being told the name of each group.

By the end: You will segment an image into a small number of colour groups using Python, NumPy, and OpenCV—and understand every line you write.
K

The number of groups we want.

Cluster

One group of similar values.

Center

The average value of a cluster. It is also called a centroid.

A tiny example

For 2, 3, 4, 20, 22, 24 and K = 2, we expect one group near 3 and another near 22.

Choose K centers
→
Assign nearest points
→
Average and repeat

What does K mean?

STEP 2 · DO IT BY HAND

Nearest center wins

Before using OpenCV, follow one round manually. This makes the library code much easier to understand.

Values: 2, 3, 4, 20, 22, 24
Starting centers: 3 and 20

For value 22:
Distance from 3  = |22 - 3|  = 19
Distance from 20 = |22 - 20| = 2

22 joins the second cluster.
K-means compares distances. The smaller distance decides the cluster.
Cluster 1

Values: 2, 3, 4
New center: (2 + 3 + 4) / 3 = 3

Cluster 2

Values: 20, 22, 24
New center: (20 + 22 + 24) / 3 = 22

THE REPEATING CYCLE

Assign every value to the nearest center → calculate new average centers → repeat until the centers barely change.

How is a new center calculated?

STEP 3 · PREPARE

Create a tiny Python project

We need one Python file, one image, OpenCV, and NumPy.

Project structure
k-means-project/
├── main.py
└── image.jpg
Install libraries
pip install opencv-python numpy
import cv2
import numpy as np
LineMeaning
import cv2Loads OpenCV so we can read, process, and display images.
import numpy as npLoads NumPy and gives it the shorter name np. NumPy works with the arrays inside images.
What is an image to Python?

An image is an array of numbers. A colour pixel has three values. OpenCV keeps them in BGR order: Blue, Green, Red.

STEP 4 · LOAD THE DATA

Read the image safely

OpenCV must turn image.jpg into a NumPy array before K-means can use its pixels.

image = cv2.imread("image.jpg")
cv2.imread() reads the file. The returned image array is stored in image.
if image is None:
    raise FileNotFoundError("Could not find image.jpg")
if image is None:

Checks whether OpenCV failed to read the image. The colon starts the indented block.

raise FileNotFoundError(...)

Stops the program and shows a useful message. This is clearer than allowing a confusing error later.

Image shape

A shape such as (400, 600, 3) means 400 rows, 600 columns, and 3 colour values per pixel.

What does image is None usually mean?

STEP 5 · RESHAPE AND CONVERT

Give K-means one pixel per row

OpenCV K-means accepts a 2D float32 array. Our normal colour image is a 3D array, so we must prepare it.

pixels = image.reshape(-1, 3)
PartMeaning
image.reshape(...)Changes the arrangement of the data. It does not delete pixels.
-1“NumPy, calculate the number of rows for me.”
3Keep three values in each row: B, G, and R.
pixels =Store the rearranged array in a new variable.
Image

(400, 600, 3)

→
Reshape

-1, 3

→
Pixel rows

(240000, 3)

pixels = np.float32(pixels)
OpenCV requires K-means input to use the float32 data type. Without this line, you may get type == CV_32F.

After reshaping, what does one row represent?

STEP 6 · KNOW WHEN TO STOP

Create the termination criteria

K-means repeats its assignment and averaging steps. The criteria tell OpenCV when it has done enough work.

criteria = (
    cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,
    100,
    0.2
)
Read the tuple as: (how to stop, maximum loops, minimum change).
CodeEasy meaning
criteria = (Create a tuple named criteria.
TERM_CRITERIA_EPSAllow stopping when the centers change by only a tiny amount.
+Combine both stopping conditions.
TERM_CRITERIA_MAX_ITERAllow stopping at the maximum number of iterations.
100Never repeat more than 100 times.
0.2The epsilon, or tiny-change limit.
)Finish the tuple.
PLAIN ENGLISH

Stop when the centers settle within 0.2, or after 100 repetitions—whichever happens first. It does not always run 100 times.

When can this K-means run stop?

STEP 7 · CLUSTER THE COLOURS

Ask for three colour groups

Now pass the prepared pixels and settings to cv2.kmeans().

k = 3

compactness, labels, centers = cv2.kmeans(
    pixels,
    k,
    None,
    criteria,
    10,
    cv2.KMEANS_PP_CENTERS
)
ArgumentWhat it tells OpenCV
pixelsGroup these BGR pixel values.
kCreate 3 clusters.
NoneWe are not supplying starting labels.
criteriaUse our stopping rules.
10Try the algorithm 10 times with different starting centers and keep the best result.
KMEANS_PP_CENTERSChoose smart, spread-out starting centers.
compactness

The total squared distance between pixels and their centers. Smaller means tighter groups.

labels

One cluster number—0, 1, or 2—for every pixel.

centers

The average BGR colour of every cluster.

K-means is not object recognition

It does not know “sky,” “grass,” or “car.” It only sees numbers and groups similar colours.

STEP 8 · TURN CLUSTERS INTO COLOURS

Replace every pixel with its center

The cluster result is still a list. We will convert it into a displayable image.

centers = np.uint8(centers)
K-means returns decimal center values. uint8 converts them to whole image-colour values from 0 to 255.
segmented_pixels = centers[labels.flatten()]
PartMeaning
labels.flatten()Turns the label column into a simple one-dimensional list.
centers[...]Uses each label as an index to pick that cluster's average colour.
segmented_pixels =Stores all replacement pixel colours.
segmented_image = segmented_pixels.reshape(image.shape)
Restore the original height, width, and three colour channels. The flat list becomes an image again.
Label 0

Use centers[0]

→
Replacement colour

Cluster 0's average BGR

→
Segmented pixel

What decides a pixel's replacement colour?

STEP 9 · SEE THE SEGMENTATION

Show both images

Display the original and segmented versions side by side so you can compare them.

cv2.imshow("Original Image", image)
cv2.imshow("K-Means Segmentation", segmented_image)

cv2.waitKey(0)
cv2.destroyAllWindows()
LineMeaning
cv2.imshow("Original Image", image)Open a window titled “Original Image” and show the original array.
cv2.imshow("K-Means Segmentation", segmented_image)Open another window and show the clustered image.
cv2.waitKey(0)Wait until any key is pressed. Zero means wait without a time limit.
cv2.destroyAllWindows()Close all OpenCV windows cleanly.
Optional: save the result
cv2.imwrite("segmented_image.jpg", segmented_image)

imwrite() creates the output file in your project folder.

Using Jupyter or Colab?

cv2.imshow() is meant for local desktop programs. In a notebook, display the image with Matplotlib or the notebook's image-display function.

STEP 10 · PUT EVERYTHING TOGETHER

Run the complete segmentation program

The comments summarize why each part exists. Compare this final program with every earlier step.

import cv2
import numpy as np

# Read the image from the project folder.
image = cv2.imread("image.jpg")

# Stop early if the image could not be read.
if image is None:
    raise FileNotFoundError("Could not find image.jpg")

# Turn the image into rows of BGR pixels.
pixels = image.reshape(-1, 3)

# OpenCV K-means requires float32 input.
pixels = np.float32(pixels)

# Stop after convergence or at most 100 iterations.
criteria = (
    cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,
    100,
    0.2
)

# Create three colour clusters.
k = 3

# Group similar pixel colours.
compactness, labels, centers = cv2.kmeans(
    pixels,
    k,
    None,
    criteria,
    10,
    cv2.KMEANS_PP_CENTERS
)

# Convert center colours to whole numbers from 0 to 255.
centers = np.uint8(centers)

# Replace every pixel with its cluster's center colour.
segmented_pixels = centers[labels.flatten()]

# Restore the original image dimensions.
segmented_image = segmented_pixels.reshape(image.shape)

# Display the original and segmented images.
cv2.imshow("Original Image", image)
cv2.imshow("K-Means Segmentation", segmented_image)

# Wait for a key, then close the windows.
cv2.waitKey(0)
cv2.destroyAllWindows()

Try it yourself

  1. Run the program with k = 2.
  2. Change it to k = 5.
  3. Change it to k = 10.
  4. Observe how a larger K keeps more colour detail.
  5. Save your preferred result using cv2.imwrite().

Common errors

type == CV_32F

Your pixels are not float32. Add pixels = np.float32(pixels) before calling K-means.

data0.dims <= 2

You probably passed the original 3D image. Pass image.reshape(-1, 3).astype(np.float32).

The output groups unrelated objects

This is expected. Plain K-means groups colours, not objects, and it ignores pixel position.

Completion checklist

  • I know that K is the number of clusters.
  • I can explain assignment, averaging, and repetition.
  • I know why the image becomes one pixel per row.
  • I know why OpenCV requires float32.
  • I understand the two stopping conditions.
  • I can explain labels and centers.
  • I can rebuild and display the segmented image.

Codelab complete! 🎉

You can now explain and implement colour-based K-means image segmentation one line at a time.