W tej pracy została przedstawiona przykładowa analiza zastosowania analizy głównych składowych (PCA) do stratnej kompresji grafiki komputerowej. PCA to technika statystyczna wykorzystywana do redukcji wymiarów z zachowaniem jak największej informacji o danych. W grafice daje to możliwość zmniejszenia rozmiaru pliku kosztem utraty części detali. Wykorzystany format grafiki PNG, oprócz zwykłych kanałów kolorów R G i B, zawiera też kanał A (alpha), w którym znajduje się informacja o nieprzezroczystości danego piksela. Oprócz kompresji wszystkich kanałów została także przedstawiona analiza kompresji pojedynczych kanałów, dostosowana do wybranego obrazka.
Zostały wykorzystane biblioteki magick (do wizualizacji obrazków) oraz png (do wczytywania i zapisywania).
library(magick)
library(png)
Na początku analizy wczytywany jest obraz (zdjęcie własne).
img_path <- "data_2.png"
img <- readPNG(img_path)
plot(image_read(img))
Wybrane dane mają wymiar 1024 x 1024 x 4 - rozdzielczość 1024x1024 pikseli i cztery kanały obrazu.
d <- dim(img)
d
## [1] 1024 1024 4
Dla uproszczenia dalszego kodu każdy kanał jest wydzielany do osobnej macierzy. Ponadto jest tworzona macierz czarnego kanału do późniejszych wizualizacji.
r <- img[,,1]
g <- img[,,2]
b <- img[,,3]
a <- img[,,4]
black <- matrix(0, nrow = d[1], ncol = d[2])
Na wykresie można zaobserwować rozkład jasności pikseli w poszczególnych kanałach. Kanałami z największym znaczeniem są tutaj czerwony i zielony, lecz choć rozkłady i mediany ich wartości są bardzo zbliżone, to jednak z uwagi na znacznie wyższą postrzeganą jasność zielonego koloru jest on na obrazku dominujący, co można zauważyć na obrazku podzielonym na kanały. Kanał niebieski ma mały udział na obrazku i występuje tylko w pewnej jego części. Wybrany obrazek jest całkowicie nieprzezroczysty, więc jego kanał alfa ma wartość 1 dla wszystkich pikseli i jest pomijany w analizie.
boxplot(as.numeric(r), as.numeric(g), as.numeric(b), as.numeric(a),
names = c("R", "G", "B", "A"), col = c("red", "green", "blue", "gray"),
main = "Rozkład wartości poszczególnych kanałów")
par(mfrow = c(1, 3), mar = c(0, 1, 0, 1))
channel_r <- array(c(r, black, black, a), dim = d)
plot(image_read(channel_r))
channel_g <- array(c(black, g, black, a), dim = d)
plot(image_read(channel_g))
title("\n\n\n\n\n\nObrazek podzielony na kanały")
channel_b <- array(c(black, black, b, a), dim = d)
plot(image_read(channel_b))
Dla każdego kanału koloru jest przeprowadzany algorytm PCA.
pca_r <- prcomp(r, center = FALSE, scale. = FALSE)
pca_g <- prcomp(g, center = FALSE, scale. = FALSE)
pca_b <- prcomp(b, center = FALSE, scale. = FALSE)
W analizie zostanie wykorzystana funkcja do kompresji kanałów za pomocą PCA, przyjmująca jako parametry dane PCA oraz liczbę składowych do wykorzystania. Oprócz standardowego mnożenia macierzy do uzyskania skompresowanego kanału, wynik jest ograniczany do przedziału od 0 do 1.
compress_channel <- function(pca, n) {
value <- pca$x[, 1:n] %*% t(pca$rotation[, 1:n])
value[value < 0] <- 0
value[value > 1] <- 1
return(value)
}
Analiza zostanie przeprowadzona dla pięciu różnych liczb składowych, z każdą iteracją czterokrotnie mniejszych - dla wybranego obrazka będą to 1024, 256, 64, 16 i 4 składowe.
ns = c(d[1], d[1] / 4, d[1] / 16, d[1] / 64, d[1] / 256)
ns = round(ns)
Na początku przeprowadzana jest kompresja dla wszystkich kanałów tą samą liczbą składowych. Pierwsze dwa obrazki niewiele się od siebie różnią, na trzecim jest widoczne rozmycie zielonych traw w środku wysokości obrazka oraz tła, natomiast przy dwóch ostatnich widać wyraźne pogorszenie jakości.
for (n in ns){
r_comp <- compress_channel(pca_r, n)
g_comp <- compress_channel(pca_g, n)
b_comp <- compress_channel(pca_b, n)
img_comp <- array(c(r_comp, g_comp, b_comp, a), dim = d)
plot(image_read(img_comp))
title(paste0(n, " principal components RGB"))
}
W następnych krokach przeprowadzane są po kolei kompresje tylko wybranych kanałów. W przypadku kompresji tylko kanału czerwonego, pierwsze trzy przypadki są praktycznie nierozróżnialne, a dopiero na dwóch ostatnich widoczne jest rozmycie czerwonego koloru, zwłaszcza w obszarach z żółtymi i różowymi kwiatami. Ogólny poziom detali jest jednak zachowany z uwagi na brak kompresji pozostałych kanałów.
par(mfrow = c(1, 2), mar = c(0, 1, 0, 1))
for (n in ns){
r_comp <- compress_channel(pca_r, n)
img_comp <- array(c(r_comp, g, b, a), dim = d)
plot(image_read(img_comp))
title(paste0("\n\n", n, " principal components R"))
channel <- array(c(r_comp, black, black, a), dim = d)
plot(image_read(channel))
}