Extracting dominant colours from pictures

by  @LazarusAlon

We will use k-means in the RGB space as way a to find the most common colours in a picture. Clusters with the largest amount of elements will correspond to the dominant colours. In Julia this is done as follows.

Simple case, one picture

We load all the necessary packages.

"$VERSION"
0.5s
"1.3.1"
using Pkg
Pkg.add("Suppressor") # if something doesn't work, just add it. 
1.5s
using Plots, Images, Random, ImageMagick, Clustering
using HTTP, Base64, Suppressor
86.2s
using Plots.PlotMeasures
0.2s

And define the following function, whose output will be the first 5 more dominant colours, and their corresponding order from less common to most common.

function thiefcolors(image; ncolors = 5)
    img_CHWa = channelview(image)
    img_CHW = permutedims(img_CHWa, (1,3,2))
    testmat = reshape(img_CHWa, (3, size(image)[1]*size(image)[2]))#input shape
    sol = kmeans(testmat, ncolors)
    csize = counts(sol) # get the cluster sizes
    colores = sol.centers # get the cluster centers, dominat colors
    indxc = sortperm(csize)
    colores, indxc
end
0.5s
thiefcolors (generic function with 1 method)

Example

For testing we will use an image directly from a link.

url = "https://images.wallpaperscraft.com/image/autumn_drawing_walking_82963_320x480.jpg"
imgtest = HTTP.download(url)
imgtest = RGB.(load(imgtest))
11.7s
Random.seed!(1021)
colores, indxc = thiefcolors(imgtest)
11.1s
(Float32[0.672349 0.216812 … 0.368779 0.630687; 0.889334 0.135243 … 0.482385 0.235814; 0.932598 0.180176 … 0.652542 0.15553], [3, 5, 4, 1, 2])

Plotting the image and the first 5 dominant colours we get the following:

plot(imgtest, aspect_ratio = 1, grid = false, 
    axis = :off, size = (400, 400))
plot!(fill(400, 5), collect(0:100:400) .+ 40, m = (:rect,15,stroke(0.1)),
    c = [RGB(colores[:,indxc[i]]...) for i in 1:5], leg = false)
27.4s

Multiple pictures

The links are in a separate file.

linksPics.jl
reflinks = 
linksPics.jl
0.2s
"/.nextjournal/data-named/QmbjR8J31y3E9xEXVR1pmpJsB6CgMHdiN7F4GKoe6i7BJo/linksPics.jl"
include(reflinks);
0.6s

Downloading images...

imgs = []
@suppress begin
    for i in 1:length(urlinks)
        img = HTTP.download(urlinks[i])
        push!(imgs, RGB.(load(img)))
    end
end
1.8s

Extracting color and plotting...

Random.seed!(1021)
imagenes = []
for indx in 1:15
    @suppress begin 
        global colores, indxc
        colores, indxc = thiefcolors(imgs[indx])
    end
    p = plot(imgs[indx], aspect_ratio = 1, grid = false, axis = :off, size = (300, 300))
    plot!(fill(400, 5), collect(0:100:400) .+ 40, m = (:rect,10, stroke(0.1)),
        c = [RGB(colores[:,indxc[i]]...) for i in 1:5], leg = false)
    push!(imagenes, p)
end
24.2s
plot(imagenes..., layout = (3,5), size = (1000, 600))
3.7s

A simple application

First, let's download some popular images from anime.

Downloading from links in file...

imgsAnime = []
@suppress begin
    for i in 1:length(urlTop)
        img = HTTP.download(urlTop[i])
        push!(imgsAnime, RGB.(load(img)))
    end
end
2.3s

and plotting...

Random.seed!(1021)
imagenesAn = []
for indx in 1:20
    @suppress begin 
        global colores, indxc
        colores, indxc = thiefcolors(imgsAnime[indx])
    end
    p = plot(imgsAnime[indx], aspect_ratio = 1, grid = false, axis = :off, size = (300, 300))
    plot!(fill(400, 5), collect(0:100:400) .+ 40, m = (:d,10, stroke(0.1)),
        c = [RGB(colores[:,indxc[i]]...) for i in 1:5], leg = false)
    push!(imagenesAn, p)
end
22.6s
plot(imagenesAn..., layout = (4,5), rigth_margin = -10mm, left_margin = -10mm, size = (1000, 900))
4.4s

Ratings...

# fullmetal, one punch man, attack on titan, code geass, naruto, bleach , elfen lied, evangelion
indx_anime = [2, 16, 1, 8, 3, 7, 18, 19]
ratings = [9.1, 8.8, 8.8, 8.6, 8.5, 8.1, 8.0, 8.5];
0.2s

Selecting 2 colours out of 5. The first and second most dominant colours.

Random.seed!(1021)
picsRGBs = []
for indx in indx_anime
    @suppress begin
        colores, indxc = thiefcolors(imgsAnime[indx])
        push!(picsRGBs, [imgsAnime[indx], colores[:,indxc[4:5]]])
    end
end
cborders = [RGB(picsRGBs[i][2][:,2]...) for i in 1:length(picsRGBs)]
cfillrect = [RGB(picsRGBs[i][2][:,1]...) for i in 1:length(picsRGBs)];
11.6s

and plotting with the corresponding picture for each anime, the results are

figratings = plot(ratings, st = :bar, c = cfillrect, line = (1, cborders), bar_width = 0.7, alpha = 0.9, ylab = "Rating", xticks = false, 
 leg =false, size = (800, 400),ylim = (7, 10))
plot!(picsRGBs[1][1], inset = [(1, bbox(0.055, 0.05, 0.08, 0.22))], subplot=2, axis =false, bg = :transparent)
for p in 1:7 
    plot!(picsRGBs[p+1][1], inset = [(1, bbox(0.055+0.17*p -p*0.055,  0.05, 0.08, 0.22))], subplot=p+2, axis =false, bg = :transparent)
end
figratings
5.3s

Once you know how to do it, it's simple. Find me on twitter as @LazarusAlon

Runtimes (1)