CIKMeans Filter

Overview

CIKMeans filter “creates a palette of the most common colors found in the image“. It does so by applying K-means clustering algorithm to partition all colors contained within the input image (clipped by the rectangle you specified) into K clusters and outputs K colors on its output where each color is the mean color (centroid) of the corresponding cluster.

The output of the filter (as is the case with the most filters in the Reduction category this filter belongs to) is a CIImage 1 pixel high and K pixels long, where each pixel’s color represents one of the computed colors.

The algorithm uses an iterative refinement technique: at each iteration the centers of the clusters are recalculated with a goal of accomplishing more or less even distribution of colors between the clusters. The algorithm is computationally intensive and its complexity increases with the number of clusters (K) and the number of iterations (passes). You can play with Count and Passes sliders in the FilterMagic app to get a feel of the computational impact: the more to the right you move either of sliders the longer it stays at the Rendering spinner.

In practice the filter is hardly used on its own. The most common use case is to use its output as an input to other filters, such as CIPaletteCentroid  and CIPalettize filters.

Inputs

kCIInputImageKey – input image. Majority of the CIFIlters have that input since after all they are meant to be used for processing images.

kCIInputExtentKey – the rectangular portion of the input image to be processed by the filter.

"inputCount" – the number of colors to produce (the K). Maximum allowed number is 128. 0 is legal but produces empty output.

"inputPasses" – number of iterations that the algorithm should run before producing output. The bigger the number the longer it runs and the more representative the resulting colors will be (generally). While 0 seems to be a legal number you’ll get some garbage out. The maximum is 20.

"inputMeans" – you have an option of providing your own colors as a starting point for the algorithm. Normally you don’t need to do it – the algorithm is smart enough to figure it out on its own. Moreover, with enough iterations the algorithm will converge on more or less the same colors regardless of its starting point. You can see it for yourself by picking colors of your choosing in the app. But who knows, maybe there are some special cases where it can be useful.

You provide the colors as an CIImage, 1 pixel high and K pixels long, where color of each pixels represents one of the colors you want to provide as a seed. The output will contain exactly the same number of colors, e.i. the dimensions of the output palette image will be exactly the same as the dimensions of that color image. Another way to think of it is to say that the filter will output the modified version of that image by replacing the color of every pixel with one of the colors it computed.

Ignore the documentation where it says that the colors can be passed “either as an image or an array of colors”. In my experimentation the CIImage was the only acceptable way to package the colors for this input.

This input is mutually exclusive with the inputCount and takes precedence – as long as you provide an image at this input the inputCount will be ignored.

"inputPerceptual" – we can only guess what this input means given total lack of documentation other than the description we can retrieve from the input’s info dictionary. Verbatim it says

“Specifies whether the k-means palette should be computed in the perceptual color space”.

Most likely when computing in perceptual color space the filter uses lightness, chroma, and hue values of the colors versus the RGB components as the basis for clustering.

Another possible (though not necessarily mutually exclusive) explanation is that when in perceptual color space the sizes of the clusters are made larger towards the brighter end of the gamut, taking into account how human eye perceives colors (you can read more about it here or just google for Perceptual Uniform Color Space or Gamma Correction). Specifically, we humans can discern more shades of darker colors than the brighter colors, so to speak, therefore the brighter colors convey less information to the human eye and can be lumped together into larger clusters. Still not clear why would you want to do that given that normally the CoreImage converts images to the linear RGB color space under the hood before they enter any CIFilter.

You can experiment by toggling this input on or off in the FilterMagic app – the colors will come out different, lighter overall and more washed out when the switch is set to YES.

In summary:

InputSwift TypeDescription
kCIInputImageKeyCIImageInput image
kCIInputExtentKeyCIVectorPortion of the input image to process
"inputCount"Intnumber of colors to compute (K)
"inputPasses"IntNumber of algorithm iterations
"inputMeans"CIImage (1 x K pixels)K seed colors (overrides “inputCount“)
"inputPerceptual"BoolCompute in perceptual color space

More Information

  1. To get hands on with the filter download the sample project source code from here. You will see how to visualize one-pixel high image by applying a chain of Core Image filters to the output of the KMeans filter. Likewise you will learn how to create a one-pixel high image for the inputMeans input from an array of colors.
  2. If you want to follow me along in writing this sample app read this post for a complete walk through.