Celem projektu jest analiza i porównanie palet
kolorów pięciu obrazów malarskich na podstawie ich wersji
cyfrowej.
W projekcie:
Tego typu podejście może być stosowane m.in. w rozpoznawaniu stylu, wyszukiwaniu podobnych obrazów czy kompresji kolorów w grafice komputerowej.
W tej części wczytuję pięć obrazów, sprawdzam ich wymiary oraz tworzę ramki danych zawierające współrzędne pikseli oraz wartości kanałów R, G, B.
# Wczytuję pięć obrazów JPG jako tablice RGB do dalszej analizy.
woman_with_flower <- readJPEG("WomanWithFlower.jpeg")
the_tragedy <- readJPEG("TheTragedy.jpeg")
the_old_guitarist <- readJPEG("TheOldGuitarist.jpeg")
portratit_of_dora_maar <- readJPEG("PortraitOfDoraMaar.jpeg")
still_life <- readJPEG("StillLife.jpeg")
# Sprawdzam klasy (powinny być typu array).
class(woman_with_flower)
## [1] "array"
class(the_tragedy)
## [1] "array"
class(the_old_guitarist)
## [1] "array"
class(portratit_of_dora_maar)
## [1] "array"
class(still_life)
## [1] "array"
# Sprawdzam wymiary każdego obrazu, aby poprawnie zbudować siatkę pikseli.
dm1 <- dim(woman_with_flower)
dm2 <- dim(the_tragedy)
dm3 <- dim(the_old_guitarist)
dm4 <- dim(portratit_of_dora_maar)
dm5 <- dim(still_life)
dm1; dm2; dm3; dm4; dm5
## [1] 254 198 3
## [1] 278 181 3
## [1] 275 183 3
## [1] 270 187 3
## [1] 249 202 3
# Konwertuję każdy obraz na ramkę danych z pozycją piksela (x, y) i wartościami RGB.
rgb_woman_with_flowers <- data.frame(
x = rep(1:dm1[2], each = dm1[1]),
y = rep(dm1[1]:1, times = dm1[2]),
r.value = as.vector(woman_with_flower[,,1]),
g.value = as.vector(woman_with_flower[,,2]),
b.value = as.vector(woman_with_flower[,,3])
)
rgb_the_tragedy <- data.frame(
x = rep(1:dm2[2], each = dm2[1]),
y = rep(dm2[1]:1, times = dm2[2]),
r.value = as.vector(the_tragedy[,,1]),
g.value = as.vector(the_tragedy[,,2]),
b.value = as.vector(the_tragedy[,,3])
)
rgb_the_old_guitarist <- data.frame(
x = rep(1:dm3[2], each = dm3[1]),
y = rep(dm3[1]:1, times = dm3[2]),
r.value = as.vector(the_old_guitarist[,,1]),
g.value = as.vector(the_old_guitarist[,,2]),
b.value = as.vector(the_old_guitarist[,,3])
)
rgb_portratit_of_dora_maar <- data.frame(
x = rep(1:dm4[2], each = dm4[1]),
y = rep(dm4[1]:1, times = dm4[2]),
r.value = as.vector(portratit_of_dora_maar[,,1]),
g.value = as.vector(portratit_of_dora_maar[,,2]),
b.value = as.vector(portratit_of_dora_maar[,,3])
)
rgb_still_life <- data.frame(
x = rep(1:dm5[2], each = dm5[1]),
y = rep(dm5[1]:1, times = dm5[2]),
r.value = as.vector(still_life[,,1]),
g.value = as.vector(still_life[,,2]),
b.value = as.vector(still_life[,,3])
)
head(rgb_woman_with_flowers)
## x y r.value g.value b.value
## 1 1 254 0.1137255 0.05490196 0.03529412
## 2 1 253 0.1137255 0.05490196 0.03529412
## 3 1 252 0.1137255 0.05490196 0.03529412
## 4 1 251 0.1137255 0.05490196 0.03529412
## 5 1 250 0.1137255 0.05490196 0.03529412
## 6 1 249 0.1137255 0.05490196 0.03529412
Tutaj sprawdzam, czy poprawnie zrekonstruowałam obrazy z ramek danych: każdy piksel jest rysowany jako punkt na płaszczyźnie (x, y) w swoim kolorze RGB.
par(mfrow = c(3, 2), mar = c(2, 2, 3, 1))
plot(
y ~ x,
data = rgb_woman_with_flowers,
main = "Woman with flowers",
col = rgb(rgb_woman_with_flowers$r.value,
rgb_woman_with_flowers$g.value,
rgb_woman_with_flowers$b.value),
asp = 1, pch = "."
)
plot(
y ~ x,
data = rgb_the_tragedy,
main = "The Tragedy",
col = rgb(rgb_the_tragedy$r.value,
rgb_the_tragedy$g.value,
rgb_the_tragedy$b.value),
asp = 1, pch = "."
)
plot(
y ~ x,
data = rgb_the_old_guitarist,
main = "The Old Guitarist",
col = rgb(rgb_the_old_guitarist$r.value,
rgb_the_old_guitarist$g.value,
rgb_the_old_guitarist$b.value),
asp = 1, pch = "."
)
plot(
y ~ x,
data = rgb_portratit_of_dora_maar,
main = "Portrait of Dora Maar",
col = rgb(rgb_portratit_of_dora_maar$r.value,
rgb_portratit_of_dora_maar$g.value,
rgb_portratit_of_dora_maar$b.value),
asp = 1, pch = "."
)
plot(
y ~ x,
data = rgb_still_life,
main = "Still Life",
col = rgb(rgb_still_life$r.value,
rgb_still_life$g.value,
rgb_still_life$b.value),
asp = 1, pch = "."
)
par(mfrow = c(1,1))
W tej części dobieram optymalną liczbę klastrów dla
każdego obrazu.
Dla k = 2, 3, …, 10 wykonuję algorytm CLARA na danych RGB danego obrazu
i zapisuję średnią szerokość sylwetki. Najwyższa wartość silhouette
sugeruje najlepszy k.
set.seed(123)
# Funkcja pomocnicza do obliczenia silhouette dla zakresu k
silhouette_for_k <- function(rgb_data, k_min = 2, k_max = 10){
sil <- numeric(k_max)
for (k in k_min:k_max) {
cl <- clara(rgb_data[, c("r.value", "g.value", "b.value")], k)
sil[k] <- cl$silinfo$avg.width
}
sil
}
n1 <- silhouette_for_k(rgb_woman_with_flowers)
n2 <- silhouette_for_k(rgb_the_tragedy)
n3 <- silhouette_for_k(rgb_the_old_guitarist)
n4 <- silhouette_for_k(rgb_portratit_of_dora_maar)
n5 <- silhouette_for_k(rgb_still_life)
par(mfrow = c(3, 2), mar = c(4,4,3,1))
k_vals <- 1:10
plot(
k_vals, n1,
type = "b",
main = "Optimal k – Woman with flowers",
xlab = "Number of clusters", ylab = "Average silhouette"
)
plot(
k_vals, n2,
type = "b",
main = "Optimal k – The Tragedy",
xlab = "Number of clusters", ylab = "Average silhouette"
)
plot(
k_vals, n3,
type = "b",
main = "Optimal k – The Old Guitarist",
xlab = "Number of clusters", ylab = "Average silhouette"
)
plot(
k_vals, n4,
type = "b",
main = "Optimal k – Portrait of Dora Maar",
xlab = "Number of clusters", ylab = "Average silhouette"
)
plot(
k_vals, n5,
type = "b",
main = "Optimal k – Still Life",
xlab = "Number of clusters", ylab = "Average silhouette"
)
par(mfrow = c(1,1))
Komentarz: Na podstawie powyższych wykresów wybieram liczbę klastrów dla każdego obrazu (np. 4 dla „Woman with flowers”, 2 dla „The Tragedy”, 2 dla „The Old Guitarist”, 2 dla „Portrait of Dora Maar”, 8 dla „Still Life”), kierując się maksymalną wartością silhouette oraz intuicyjną interpretacją obrazów.
Teraz wykonuję klasteryzację CLARA dla wybranych k dla każdego obrazu i tworzę „zredukowane” wersje obrazów, w których każdy piksel przyjmuje kolor medoidu swojego klastra. Dodatkowo wyświetlam próbnik (swatch) dominujących kolorów.
set.seed(123)
# Klasteryzuję kolory obrazu „Woman with flowers” na 4 klastry i wizualizuję wynik.
cl_woman <- clara(rgb_woman_with_flowers[, 3:5], k = 4)
plot(silhouette(cl_woman), main = "Silhouette – Woman with flowers (k = 4)")
colors_woman <- rgb(cl_woman$medoids[cl_woman$clustering, ])
plot(
y ~ x, data = rgb_woman_with_flowers,
main = "Woman with flowers – 4 colours",
col = colors_woman, pch = ".", cex = 2, asp = 1
)
swatch(rgb(cl_woman$medoids))
# Klasteryzuję kolory obrazu „The Tragedy” na 2 klastry i wizualizuję wynik.
cl_tragedy <- clara(rgb_the_tragedy[, 3:5], k = 2)
plot(silhouette(cl_tragedy), main = "Silhouette – The Tragedy (k = 2)")
colors_tragedy <- rgb(cl_tragedy$medoids[cl_tragedy$clustering, ])
plot(
y ~ x, data = rgb_the_tragedy,
main = "The Tragedy – 2 colours",
col = colors_tragedy, pch = ".", cex = 2, asp = 1
)
swatch(rgb(cl_tragedy$medoids))
# Klasteryzuję kolory obrazu „The Old Guitarist” na 2 klastry i wizualizuję wynik.
cl_guitarist <- clara(rgb_the_old_guitarist[, 3:5], k = 2)
plot(silhouette(cl_guitarist), main = "Silhouette – The Old Guitarist (k = 2)")
colors_guitarist <- rgb(cl_guitarist$medoids[cl_guitarist$clustering, ])
plot(
y ~ x, data = rgb_the_old_guitarist,
main = "The Old Guitarist – 2 colours",
col = colors_guitarist, pch = ".", cex = 2, asp = 1
)
swatch(rgb(cl_guitarist$medoids))
# Klasteryzuję kolory obrazu „Portrait of Dora Maar” na 2 klastry i wizualizuję wynik.
cl_portrait <- clara(rgb_portratit_of_dora_maar[, 3:5], k = 2)
plot(silhouette(cl_portrait), main = "Silhouette – Portrait of Dora Maar (k = 2)")
colors_portrait <- rgb(cl_portrait$medoids[cl_portrait$clustering, ])
plot(
y ~ x, data = rgb_portratit_of_dora_maar,
main = "Portrait of Dora Maar – 2 colours",
col = colors_portrait, pch = ".", cex = 2, asp = 1
)
swatch(rgb(cl_portrait$medoids))
# Klasteryzuję kolory obrazu „Still Life” na 8 klastrów i wizualizuję wynik.
cl_stilllife <- clara(rgb_still_life[, 3:5], k = 8)
plot(silhouette(cl_stilllife), main = "Silhouette – Still Life (k = 8)")
colors_stilllife <- rgb(cl_stilllife$medoids[cl_stilllife$clustering, ])
plot(
y ~ x, data = rgb_still_life,
main = "Still Life – 8 colours",
col = colors_stilllife, pch = ".", cex = 2, asp = 1
)
swatch(rgb(cl_stilllife$medoids))
W tej części chcę porównać kolorystykę pięciu
obrazów.
Dla porównywalności przyjmuję wspólną liczbę klastrów k =
2 i dla każdego obrazu wyznaczam paletę medoidów. Następnie
definiuję miarę odległości między dwiema paletami na podstawie średniej
odległości euklidesowej między najbliższymi kolorami.
# Funkcja wyznaczająca medoidy CLARA jako paletę kolorów przy zadanej liczbie klastrów.
extract_palette <- function(rgb_data, k){
cl <- clara(rgb_data[, c("r.value","g.value","b.value")], k)
return(cl$medoids)
}
# Ustalam wspólną liczbę klastrów (k = 2) do porównania palet.
k <- 2
# Wyznaczam palety medoidów (dominujące kolory) dla wszystkich pięciu obrazów.
palette_woman <- extract_palette(rgb_woman_with_flowers, k)
palette_tragedy <- extract_palette(rgb_the_tragedy, k)
palette_guitarist <- extract_palette(rgb_the_old_guitarist, k)
palette_portrait <- extract_palette(rgb_portratit_of_dora_maar, k)
palette_stilllife <- extract_palette(rgb_still_life, k)
# Funkcja obliczająca średnią odległość między dwiema paletami kolorów.
palette_distance <- function(p1, p2){
dists <- apply(p1, 1, function(row1){
min(apply(p2, 1, function(row2){
sqrt(sum((row1 - row2)^2))
}))
})
mean(dists)
}
# Tworzę wektor nazw obrazów i listę odpowiadających im palet.
img_names <- c("Woman", "Tragedy", "Guitarist", "Portrait", "Stilllife")
palette_list <- list(
Woman = palette_woman,
Tragedy = palette_tragedy,
Guitarist = palette_guitarist,
Portrait = palette_portrait,
Stilllife = palette_stilllife
)
# Buduję macierz odległości 5x5 między paletami pięciu obrazów.
n_img <- length(img_names)
dist_matrix <- matrix(0, nrow = n_img, ncol = n_img)
colnames(dist_matrix) <- rownames(dist_matrix) <- img_names
for(i in 1:(n_img-1)){
for(j in (i+1):n_img){
dist_matrix[i, j] <- palette_distance(palette_list[[i]], palette_list[[j]])
}
}
# Uzupełniam macierz do postaci symetrycznej.
dist_matrix <- dist_matrix + t(dist_matrix)
print("ŚREDNIA ODLEGŁOŚĆ MIĘDZY PALETAMI (im mniejsza, tym obrazy bardziej podobne):")
## [1] "ŚREDNIA ODLEGŁOŚĆ MIĘDZY PALETAMI (im mniejsza, tym obrazy bardziej podobne):"
round(dist_matrix, 3)
## Woman Tragedy Guitarist Portrait Stilllife
## Woman 0.000 0.347 0.356 0.308 0.241
## Tragedy 0.347 0.000 0.263 0.404 0.385
## Guitarist 0.356 0.263 0.000 0.404 0.255
## Portrait 0.308 0.404 0.404 0.000 0.369
## Stilllife 0.241 0.385 0.255 0.369 0.000
Najpierw rysuję każdą paletę jako prosty pasek kolorów, a następnie przedstawiam macierz odległości między obrazami w formie heatmapy.
# Funkcja rysująca paletę jako pasek kolorów.
plot_palette <- function(pal, title){
n <- nrow(pal)
barplot(
rep(1, n),
col = rgb(pal),
border = NA,
main = title,
yaxt = "n"
)
}
par(mfrow = c(5, 1), mar = c(2,2,3,1))
plot_palette(palette_woman, "Palette – Woman")
plot_palette(palette_tragedy, "Palette – Tragedy")
plot_palette(palette_guitarist, "Palette – Guitarist")
plot_palette(palette_portrait, "Palette – Portrait")
plot_palette(palette_stilllife, "Palette – Still life")
par(mfrow = c(1,1))
# Heatmapa odległości między paletami kolorów pięciu obrazów.
pheatmap(
dist_matrix,
cluster_rows = FALSE,
cluster_cols = FALSE,
display_numbers = TRUE,
number_format = "%.3f",
main = "Heatmapa odległości między paletami kolorów",
color = colorRampPalette(c("white", "orange", "red"))(100)
)
W projekcie przeanalizowałam pięć obrazów malarskich, traktując każdy z nich jako zbiór pikseli w przestrzeni RGB. Zastosowałam metodę CLARA, będącą odmianą algorytmu k-medoidów, aby zredukować liczbę kolorów i wyodrębnić dominujące palety barw w każdym obrazie.
Na podstawie wykresów silhouette dobrałam liczbę klastrów osobno dla każdego obrazu, a następnie porównałam obrazy między sobą za pomocą odległości między ich paletami medoidów. Heatmapa odległości pokazała, które obrazy są do siebie najbardziej podobne kolorystycznie, a które różnią się pod względem używanej gamy barw.
Takie podejście może być wykorzystane w zadaniach związanych z analizą stylu malarskiego, grupowaniem obrazów, wyszukiwaniem podobnych dzieł sztuki oraz w zastosowaniach praktycznych, takich jak kompresja kolorów czy projektowanie palet barw.