K-means e paleta de cores

Por Daniel 22/04/2017

Uma aplicação interessante de algoritmos de clusterização é a obtenção de paletas de cores a partir de imagens. Veja como isso pode ser feito usando o R.

Em primeiro lugar, vamos ler a imagem como uma matriz para o R. Existem diversas bibliotecas para carregar as imagens, vamos usar aqui a jpeg. Para esse caso ela é melhor porque já lê a imagem no formato que precisamos.

library(jpeg)
library(magrittr)
img <- readJPEG("img/david-bowie.jpg")

A imagem lida pelo pacote jpeg é representada por um array com dimensões: c(altura, largura, n_bandas) que no nosso caso é c(1100, 727, 3). O número de bandas é 3: R, G e B.

Podemos exibir a imagem no R, convertendo o array, primeiro em um obheto do tipo raster e depois simplesmente usando a função plot.

plot(as.raster(img))

O problema de obter a paleta de cores de uma imagem pode ser formulado como um problema de clusterização: “obter grupos de individuos que possuem a menor diferença dentro de cada um e a maior diferença possível entre os grupos de acordo com algumas características das unidades amostrais”.

Nesse caso, os indivíduos são os pixels da imagem e as características que estamos interessados são os valores de R, de G e de B (valores que representam a cor do pixel). Para o algortimos de clusterização, precisamos de uma matriz com as 3 colunas R, G e B e largura*altura (numero de pixels) linhas representado os indivíduos. É exatamente essa conversão que o trecho de código a seguir realiza.

img_matrix <- apply(img, 3, as.numeric)

Agora temos uma matriz com 3 colunas e 799.700 linhas. Vamos aplicar agora o algoritmo k-means, para organizar cada um desses pixels em um grupo. O K-means pede o número de grupos como input, vamos começar com 6.

km <- kmeans(img_matrix, centers = 6)

O objeto gerado pela função kmeans armazena um vetor chamado cluster (do tamanho do número de linhas da matriz) com um identificador do grupo de cada observação da matriz.

A cor que representa cada um dos grupos é representada pelo vetor c(r, g, b) com a média de todas as observações de cada um dos grupos. Podemos obter isso com algumas manipulações usando o dplyr.

library(tibble)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
img_df <- tibble(
  r = img_matrix[,1], 
  g = img_matrix[,2], 
  b = img_matrix[,3],
  cluster = km$cluster
  )
centroides <- img_df %>%
  group_by(cluster) %>%
  summarise_all(mean)

centroides
## # A tibble: 6 x 4
##   cluster         r          g         b
##     <int>     <dbl>      <dbl>     <dbl>
## 1       1 0.0996826 0.04366903 0.2398452
## 2       2 0.2721465 0.36412137 0.4901613
## 3       3 0.7195362 0.54881786 0.4098359
## 4       4 0.8857289 0.77915257 0.6688482
## 5       5 0.3789830 0.11857741 0.1558412
## 6       6 0.6239192 0.28335790 0.1897367

Também transformamos uma cor r, g e b em uma representação hexadecimal. Assim conseguimos um vetor de caracteres que representa a a paleta de cores.

centroides <- centroides %>%
  mutate(cor = rgb(r, g, b))
centroides$cor
## [1] "#190B3D" "#455D7D" "#B78C69" "#E2C7AB" "#611E28" "#9F4830"

Para exibir a paleta vamos usar a seguinte função que foi copiada e levemente modificada daqui

exibir <- function(x) {
  n <- length(x)
  old <- par(mar = c(0.5, 0.5, 0.5, 0.5))
  on.exit(par(old))

  image(1:n, 1, as.matrix(1:n), col = x,
        ylab = "", xaxt = "n", yaxt = "n", bty = "n")
}
exibir(sort(centroides$cor))

Assim obtivemos uma paleta de cores da imagem que mostramos anteriormente. Vamos colocar todo o código que fizemos passo a passo aqui em uma única função para podermos facilmente criar a paleta de cores para outras imagens.

criar_paleta <- function(img, num_cores){
  # transforma a imagem em uma matriz
  img_matrix <- apply(img, 3, as.numeric)
  # treina o algoritmo de k médias
  km <- kmeans(img_matrix, centers = num_cores)
  img_df <- tibble(
    r = img_matrix[,1], 
    g = img_matrix[,2], 
    b = img_matrix[,3],
    cluster = km$cluster
  )
  # calcula os centroides dos grupos
  centroides <- img_df %>%
    group_by(cluster) %>%
    summarise_all(mean)
  # transforma a cor em hexadecimal
  centroides <- centroides %>%
    mutate(cor = rgb(r, g, b))
  # vetor de cores
  sort(centroides$cor)
}

Vejamos agora o que acontece com essa bela imagem do filme Moonrise Kingdom do Wes Anderson, que é famoso por fazer filmes com belas paletas de cores.

moonrise <- readJPEG("img/moonrise-kingdom.jpg")
plot(as.raster(moonrise))

paleta <- criar_paleta(moonrise, 6)
exibir(paleta)

É isso. Se você gostou, tente fazer com outras imagens e compartilhe com a gente os resultados.

comments powered by Disqus