The median cut algorithm is a method for discretizing a continuous color space. That is, taking an image that consists of many colors, and reducing it into fewer colors. Broadly, the idea is to take the red, green, and blue components of an image, visualize each of them as an axis in 3-dimensional space, and then partitioniong that space into chunks, each consisting of as few distinct colors as possible. Here, we will explore and implement the median cut algorithm in R.
The implementation here, with some modification, is available in the RImagePalette package, and can be explored interactively here!
Let’s start by coding a graphical representation of this 3-dimensional RGB color space. We will work throughout this post with a colorful image of a delicious and healthy snack:
It’s going to be convenient to work with the image as a list of three 2D matrices, rather than a single 3D matrix, so we will split it and name each matrix appropriately:
Let’s look at how the values in each of the R, G, and B components are distributed:
We note several things:
The distributions of each color each have a unique shape
Certain color values are more present in the image than other colors
It will be our task to extract those colors which are most highly represented in the image. This is where the median cut algorithm comes in.
The Median Cut Algorithm
The median cut algorithm can be stated as follows:
Create a “cube” of the colors in the pixels of an image by using each color component (R, G, and B) as an axis (e.g. x,y,z)
A: Calculate the range of each color component (R, G, and B)
B: For the component with the largest range, C, calculate the median value, M
C: Split the “cube” of colors:
one cube containing the RGB values of all pixels where the C component is greater than M
one cube containing the RGB values of all pixels where the C component is less than M
- D: If the number of cubes is equal to our chosen number of desired colors, exit the loop
- E: For each color cube, calculate the range of each component, choose the cube which containts the largest range, and repeat
For each cube, apply some function (mean, median, mode, etc) to the value of each component, and combine into a new RGB value
Let’s walk through this for our chosen image.
Walking Through the Median Cut
Step 1:
We first create a cube of the RGB components. A sample of 2500 pixels from the image along the R, G, and B axes is visualized using the code below:
In the image we can get an idea of the most common colors present in the image. The blue in the upper are from the plate, the orange in the bottom from the carrots, etc.
Step 1A - Calculate the Range of Each Component
To choose how to split this cube up, we require the range of each component. Since we will be doing this a number of times, we will write a convenience function:
Red
Green
Blue
Original
0 - 255
0 - 255
0 - 255
Step 1B - Median of the Component with the Largest Range
In this case, each axis extends from 0 to 255. Since there is no single component with the largest range, me must make a choice. There are a number of different methods for making this choice, however for simplicity we will choose randomly. Let’s arbitrarily choose the red axis.
Step 1C - Split the Cube Along the Median
Iteration 1
Using the median we calculated in Step 1B, we will extract all of the pixels from the original image above, and below that value:
Step 1D and Step 1E - Calculate Range of Each New Cube
We now have two cubes, each with their own distribution of colors, and each with a different dominant color. We will go on in this post to extract 6 colors from the image, so we need to repeat the process. To choose the next cube to cut, we calculate the ranges of pixels in each cube:
Red
Green
Blue
Cube 1
118 - 255
0 - 255
0 - 255
Cube 2
0 - 117
0 - 243
0 - 255
The maximum range out of any of the components is still 255, and both cubes have at least one axis that covers the full range. Again, we arbitrarily choose, randomly selecting Cube 2.
Loop Back
Iteration 2
In Cube 2, we select the component with the largest range to cut across, in this case, the Blue axis:
Red
Green
Blue
Cube 1
118 - 255
0 - 255
0 - 255
Cube 2a
15 - 117
53 - 243
132 - 255
Cube 2b
0 - 117
0 - 217
0 - 131
We are left with three cubes now, still not the desired 6, so we need to repeat the algorithm 3 more times. I will spare you the code (although you can see it in the reproducible document), and present the plots. The next cut will be along the Green axis of Cube 1, which has a maximum range of 255.
Repeat
Iteration 3
Iteration 4
Iteration 5
And we are now left with 6 cubes, each of which we wish to extract the dominant color from.
Step 2 - Extracting the Colors
Finally, we have the desired number of colors, and can exit the loop!
We have 6 color cubes now, each representing a different set of colors. Ideally, each cube contains a relatively distinct set of colors, but in practice it may take a larger number of iterations for the cubes to be small enough. To extract the color that each cube represents, we must apply some sort of color extraction function to each cube. For this, we have several options:
Extract the mean R, G, and B value from each cube, and combine them into a single RGB value
Extract the median R, G, and B value from each cube, and combine them into a single RGB value
Extract the most common R, G, and B value from each cube, and combine them into a single RGB value
Convert each R, G, B set into a hex value, and extract the most common value (this ensures that the extracted colors are present in the image)
Below, the final option is demonstrated, and the extracted palette visualized:
Conclusion
The extracted palette represents the dominant colors in the image quite well. We can see the sauce, the plate, the background, celery, and carrot colors are all present in the final palette. To note, I have implemented the median cut algorithm in an R package, RImagePalette. Using the package to produce palettes from images is super easy!