Iteration 2: Cluster, Merge, Pick
Five colors a human would pick from a photograph is a clustering problem. We group pixels that read as the same color, then pick the best representative of each group. From my last blog post, you can see I’ve already analyzed the image in the OKLCH color space. The move from HSL S to OKLCH C was was a better measure of “how colorful”. For example, rgb(8, 9, 6) is almost black, but S is 20% while C is 0. HSL S is a ratio that blows up near black. But OKLCH C is a distance from the achromatic axis. I could cut on a chroma threshold more reliably than on saturation.
Since I want a five-color palette, unless there are fewer than five good options in the image, I’m using K-means++ with an overshoot of K=10. I think it would be better to find ten color regions and merge down to fewer than to aim for exactly five and miss an important color, pick duplicates, etc. The ten starters will be seeded deterministically from a hash of the input pixels.
My first "would a human consider these the same color" threshold is a merge distance of .07, but I expect to iterate. Two near-identical greens should collapse, while a red and a blue shouldn’t.
Next, if there are more than five clusters, closest-pair logic that ignores the distance threshold merges the two clusters that are closest, even if they're not strictly near-duplicates. If there are fewer than five clusters, a rescue pass walks every pixel to look for a color region that K-means missed initially. The farthest color from any existing centroid is checked for meeting the merge distance minimum from every existing cluster centroid and having at least 0.1% of the image's pixels within the same radius of it. We stop when we find five or nothing new to promote.
Finally, for each cluster, the actual highest-chroma pixel within the cluster's typical radius of the centroid becomes the cluster’s swatch.
For twelve test images, here are the palettes chosen with this algorithm. Some palettes are defensible, but these definitely need tuning.