Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

stough 202-

Preview: K-means iteration

Here we do k-means clustering on an image, to get representative colors for the image.

This is the same k-means algorithm as on the 2D toy data, applied to real data: every pixel becomes a point in 3D RGB space, and we look for K=16K=16 cluster centers among them. The centers become a 16-color palette, and replacing every pixel by its center’s color gives a 16-color version of the image. It’s the same idea as the color clustering we did for compression, except that here we write the k-means loop ourselves instead of calling scipy.

X has one row per pixel, its (R,G,B)(R, G, B) values: 687⋅1024≈700,000687 \cdot 1024 \approx 700{,}000 points in 3D. The position of each pixel is thrown away; k-means here only sees colors.

<Figure size 640x480 with 1 Axes>
array([[118., 57., 0.], [117., 55., 8.], [125., 64., 10.], [130., 70., 8.], [119., 58., 3.], [118., 57., 10.], [112., 52., 2.], [115., 57., 11.], [104., 52., 4.], [126., 61., 7.]])
array([[0.76003431, 0.56815207, 0.9851263 ], [0.85421562, 0.25820378, 0.30594259], [0.65376703, 0.44425113, 0.13264375], [0.95164059, 0.4366015 , 0.51904003], [0.10403741, 0.80160196, 0.68704969], [0.53130825, 0.79647325, 0.11111253], [0.62247207, 0.93161496, 0.90251527], [0.98172557, 0.92461134, 0.34947592], [0.71629537, 0.04163917, 0.93777949], [0.33731721, 0.50553171, 0.84051477], [0.35672791, 0.52013192, 0.60819852], [0.98595808, 0.04073001, 0.44196889], [0.98060839, 0.29174707, 0.24579029], [0.43551398, 0.88727809, 0.05929939], [0.37075392, 0.06199934, 0.97468878], [0.17794268, 0.17501959, 0.23886008]])

 

Pick some initial cluster centers.

As before, we start from KK randomly chosen pixels. Since every pixel is equally likely to be picked, the initial centers tend to come from the colors that cover the most area. In this image those are the warm reds, oranges, and browns.

array([[149., 62., 9.], [ 87., 37., 0.], [ 97., 45., 5.], [122., 59., 6.], [ 92., 48., 0.], [ 78., 65., 20.], [ 67., 56., 36.], [100., 55., 26.], [ 97., 48., 15.], [113., 61., 22.], [119., 51., 14.], [ 96., 112., 127.], [ 2., 120., 226.], [152., 66., 5.], [154., 117., 65.], [143., 101., 63.]])

 

The main Expectation-Maximization loop

Basically, we assign a cluster to each point, and then recompute the clusters based on that assignment.

Each pass computes all 700,000×16700{,}000 \times 16 pixel-to-center distances, which is why this takes a few seconds. We run a fixed MAXITER iterations rather than checking for convergence. The if np.any(...) guard skips any center that ends up with no pixels (an empty cluster), which would otherwise give an undefined mean.

<Figure size 900x300 with 3 Axes>

We can’t easily draw 700,000 points, so these scatters show a random sample, and only two of the three dimensions: red on the x-axis, green on the y-axis (blue is hidden). On the right, each sampled pixel is drawn in the color of its cluster center, and the centers are circled. The centers spread out to cover the cloud of colors, with more of them where the data is dense.

<Figure size 800x300 with 2 Axes>

Sixteen colors capture the warm architecture remarkably well. But look at the purple flowers and the blue accents in the middle: they’ve lost their color, mapped to a nearby gray or brown. They cover so few pixels that they don’t pull a center of their own. K-means minimizes total squared error, so it spends its 16 colors where most of the pixels are. Try increasing K, and watch when the purple comes back.

The color cubes below show the same thing in 3D: the original’s continuous cloud of colors collapses to just 16 points.

<Figure size 400x400 with 1 Axes>
<Figure size 400x400 with 1 Axes>