4  Transformacje geometryczne

Obrazy cyfrowe mogą zostać poddane różnego rodzaju transformacjom. Oto kilka najważniejszych z nich:

Skalowanie obrazu to proces zmiany rozmiaru obrazu. Może to być zwiększenie lub zmniejszenie wielkości obrazu. Skalowanie może być wykonywane przy użyciu różnych algorytmów, takich jak skalowanie proporcjonalne, skalowanie nieproporcjonalne, skalowanie bilinearne, skalowanie bikubiczne itp. Skalowanie proporcjonalne zachowuje proporcje pikseli i jakość obrazu, natomiast skalowanie nieproporcjonalne nie zachowuje proporcji i może prowadzić do zniekształceń obrazu. Skalowanie bilinearne i bikubiczne są bardziej zaawansowanymi metodami skalowania, które pozwalają na uzyskanie lepszej jakości obrazu po skalowaniu.

4.1 Interpolacje liniowa i kubiczna

Jak widać z powyższych przykładów interpolacja obrazów jest kluczową techniką w wizji komputerowej, szczególnie przy operacjach takich jak obrót i skalowanie obrazów. Pozwala na wygładzenie obrazu i zminimalizowanie efektów zniekształceń. Dwie powszechnie stosowane metody interpolacji, które zostaną bliżej opisane, to interpolacja bilinearna i bikubiczna. Obie metody różnią się złożonością obliczeniową i jakością wynikowego obrazu.

4.1.1 Interpolacja bilinearna

Skalowanie bilinearne to metoda skalowania obrazu, która polega na interpolacji między pikselami. W tym procesie, dla każdego nowego piksela wynikowego obrazu, algorytm oblicza wartość piksela poprzez interpolację między czterema najbliższymi pikselami z oryginalnego obrazu. Konkretniej, dla każdego nowego piksela, algorytm określa współrzędne na oryginalnym obrazie, i następnie interpoluje wartości pikseli z czterech najbliższych sąsiednich pikseli w oryginalnym obrazie. Interpolacja ta jest wykonywana przez przemnożenie każdej z wartości pikseli przez odpowiedni współczynnik interpolacji i następnie sumowanie ich. W ten sposób, skalowanie bilinearne pozwala na uzyskanie obrazu o wyższej jakości niż skalowanie prostopadłe, ponieważ interpoluje wartości pikseli między pikselami oryginalnego obrazu, zamiast przydzielać nowym pikselom wartości pikseli z oryginalnego obrazu.

Załóżmy, że chcemy obliczyć wartość piksela \(P(x,y)\) w obrazie docelowym. Niech \(Q_{11}, Q_{12}, Q_{21}, i Q_{22}\) będą wartościami czterech najbliższych pikseli w obrazie źródłowym, położonymi odpowiednio w lewym górnym, prawym górnym, lewym dolnym i prawym dolnym rogu kwadratu, w którym znajduje się punkt \(P\). Wówczas wartość \(P\) można obliczyć jako:

\[ P(x,y) = \frac{1}{(x_2-x_1)(y_2-y_1)} \left[(x_2-x)(y_2-y)Q_{11} + (x-x_1)(y_2-y)Q_{21} + (x_2-x)(y-y_1)Q_{12} + (x-x_1)(y-y_1)Q_{22}\right] \]

gdzie \(x_1, x_2, y_1, y_2\) to odpowiednio współrzędne poziome i pionowe granic kwadratu zawierającego punkt \(P\).

4.1.2 Interpolacja bikubiczna

Skalowanie bikubiczne jest bardziej zaawansowaną metodą skalowania obrazów niż skalowanie bilinearne. Podobnie jak skalowanie bilinearne, skalowanie bikubiczne również polega na interpolacji między pikselami, jednak jest bardziej złożone i dokładniejsze. W skalowaniu bikubicznym, dla każdego nowego piksela, algorytm określa współrzędne na oryginalnym obrazie, a następnie interpoluje wartości pikseli z szesnastu najbliższych sąsiednich pikseli w oryginalnym obrazie, zamiast czterech jak w skalowaniu bilinearnym. Interpolacja ta jest wykonywana za pomocą funkcji bikubicznej, która jest bardziej złożona niż funkcja liniowa używana w skalowaniu bilinearnym. Wartość każdego nowego piksela jest obliczana przez wielokrotne mnożenie wartości pikseli z oryginalnego obrazu przez odpowiednie współczynniki interpolacji i sumowanie ich. Dzięki zastosowaniu bardziej złożonej funkcji interpolacji i większej liczby pikseli z oryginalnego obrazu, skalowanie bikubiczne pozwala na uzyskanie jeszcze lepszej jakości obrazu po skalowaniu, niż skalowanie bilinearne.

Wartość piksela \(P(x,y)\) w interpolacji bikubicznej obliczana jest za pomocą wzoru:

\[ P(x,y) = \sum_{i=-1}^{2} \sum_{j=-1}^{2} a_{ij} \cdot (x-x_1)^i \cdot (y-y_1)^j \]

gdzie \(a_{ij}\) są współczynnikami uzyskanymi z warunków brzegowych i charakterystyk interpolowanych punktów, a \(x_1\) i \(y_1\) są współrzędnymi punktu w obrazie źródłowym, najbliższego lewemu górnemu rogowi obszaru interpolacji.

Obie metody mają swoje zastosowania w zależności od wymagań dotyczących jakości i wydajności. Interpolacja bilinearna jest często stosowana w przeglądarkach internetowych i aplikacjach graficznych, gdzie szybkość jest kluczowa, natomiast interpolacja bikubiczna znajduje zastosowanie w bardziej zaawansowanych edytorach grafiki, gdzie priorytetem jest jakość obrazu.

Obrót obrazu polega na przekształceniu obrazu przez obrócenie go o określony kąt względem pewnego punktu, zwykle środka obrazu. Ten proces polega na transformacji współrzędnych pikseli na obrazie i może być wykonywany za pomocą różnych algorytmów. Jednym z najczęściej stosowanych algorytmów jest algorytm obrotu o kąt \(\theta\), który polega na przekształceniu każdego piksela \((x, y)\) na \((x', y')\) za pomocą następujących równań:

\[ \begin{align} x' &= x \cos(\theta) - y \sin(\theta)\\ y' &= x \sin(\theta) + y \cos(\theta), \end{align} \]

gdzie \(\theta\) jest kątem obrotu w radianach.

Jeśli obraz jest skalowany podczas obrotu, to mogą pojawić się brakujące lub zbędne piksele w obrazie wynikowym, w związku z tym trzeba zastosować odpowiednie metody interpolacji takie jak skalowanie bilinearne czy bikubiczne.

Obrót obrazu jest przydatny w wielu aplikacjach, takich jak analiza obrazów, przetwarzanie obrazów i uczenie maszynowe, ponieważ pozwala na zmianę orientacji obrazu, co może być konieczne do prawidłowego przetwarzania i analizy.

Przesunięcie obrazu o wektor to proces przemieszczenia obrazu o określoną odległość w pionie i/lub poziomie. W tym procesie, każdy piksel w obrazie jest przesunięty o taki sam wektor \((dx, dy)\) w pionie i poziomie. Wektor przesunięcia może być dodatni lub ujemny, co oznacza przesunięcie obrazu w górę lub w dół oraz w lewo lub w prawo. Przesunięcie obrazu o wektor może być wykonywane za pomocą różnych algorytmów, ale jednym z najprostszych jest przekształcenie każdego piksela \((x, y)\) na \((x+dx, y+dy)\).

Jeśli przesunięcie prowadzi do utraty części obrazu, to trzeba zastosować odpowiednie metody interpolacji, takie jak skalowanie bilinearne lub bikubiczne, aby uzupełnić brakujące piksele. Przesunięcia są również ważne w kontekście uczenia maszynowego.

Przekształcenie afiniczne (afiniczne transformacje obrazów) to rodzaj przekształcenia obrazu, które polega na zmianie rozmiaru, kształtu i położenia obrazu za pomocą macierzy afinicznej. Macierz afiniczna jest to macierz 3x3, która zawiera informacje o skali, obrocie, translacji i odkształceniu obrazu.

Przekształcenie afiniczne pozwala na wykonywanie operacji takich jak:

  • skalowanie, czyli zmiana rozmiaru obrazu,
  • obrót, czyli zmiana orientacji obrazu,
  • translacja, czyli przesunięcie obrazu w płaszczyźnie,
  • odkształcenie, czyli zmiana kształtu obrazu.

Przekształcenie afiniczne jest często używane w przetwarzaniu obrazów do uzyskania lepszego dopasowania obrazów do siebie, np. w celu wykonania mapowania międzyobrazowego lub w celu korygowania perspektywy obrazów.

Progowanie obrazu polega na zmianie poziomów jasności pikseli w obrazie na podstawie określonego progu. Próg jest wartością graniczną, po której piksele o niższej jasności są zmieniane na czarne, a piksele o jasności powyżej progu są zmieniane na białe. W ten sposób obraz jest konwertowany na obraz czarno-biały, co może ułatwić dalsze operacje na nim, takie jak analiza obrazu.

Filtracja obrazów to proces przetwarzania obrazów polegający na przepuszczeniu obrazu przez filtr, który modyfikuje wartości pikseli. Cel filtracji może być różny, np. usunięcie szumów, wzmocnienie krawędzi, rozmycie obrazu.

Istnieje wiele różnych rodzajów filtrów. Wśród nich można wymienić:

  • filtry lokalne, które modyfikują każdy piksel na podstawie jego otoczenia,
  • filtry globalne, które modyfikują cały obraz jednocześnie,
  • filtry medianowe, które służą do usuwania szumów poprzez zastąpienie każdego piksela mediana jego otoczenia.

Filtracja jest ważnym krokiem w przetwarzaniu obrazów, ponieważ pozwala na usunięcie niepożądanych cech obrazu lub wzmocnienie istotnych cech obrazu, co ułatwia dalsze analizy.

Różne rodzaje przekształceń obrazów 2D

Translacja (przesunięcie) i rotacja nie zmieniają rozmiarów i orientacji obrazu, natomiast skalowanie zmienia rozmiar obrazu ale zachowuje orientację i kąty. Przekształcenia afiniczne zachowują jedynie równoległość par prostych. Projekcje są przekształceniami polegającymi na zmianie perspektywy patrzenia na obraz dlatego jedyną własnością, która jest zachowana w transformowanym obrazie to linie proste.

4.2 Przykłady różnych transformacji

Kod
library(magick)
frink <- image_read("https://jeroen.github.io/images/frink.png")
frink

Frink w oryginalnym rozmiarze
Kod
image_scale(frink, "250%x250%")

Frink powiększony o 150%
Kod
image_scale(frink, "300x100!")

Frink poszerzony do proporcji 3:1
Kod
image_rotate(frink, 90)

Frink obrócony o 90 stopni

4.3 Rotacje obrazów

Obroty o inne kąty niż pełne wielokrotności 90\(\degree\) powodują pewne problemy, ponieważ po rotacji powstają piksele, które nie pokrywają żadnej wartości z oryginalnego obrazu, a część z nich zawiera kilka wartości (Rysunek 4.1). Podobne zjawisko może powstać w sytuacji zmiany rozmiaru obrazów. W tych sytuacjach konieczna jest interpolacja pikseli zarówno “pustych”, jak i “wypełnionych”.

Rysunek 4.1: Przykład rotacji obrazu
Kod
library(imager)
library(keras)

data <- dataset_mnist()
im <- t(data$train$x[2023,,])
im <- as.cimg(im)

im_list <- map_il(0:6, ~imrotate(im, # Nearest Neighbour interp.
                                 angle = 15*.x, 
                                 interpolation = 0)) 
row1 <- imappend(im_list[1:4], axis = "x")  
row2 <- imappend(im_list[5:7], axis = "x")
imappend(list(row1,row2), "y") |> 
  plot(interp=F) # antialiasing off

im_list <- map_il(0:6, ~imrotate(im, # linear interpolation
                                 angle = 15*.x, 
                                 interpolation = 1)) 
row1 <- imappend(im_list[1:4], axis = "x")  
row2 <- imappend(im_list[5:7], axis = "x")
imappend(list(row1,row2), "y") |> 
  plot(interp=F)

im_list <- map_il(0:6, ~imrotate(im, # cubic interpolation
                                 angle = 15*.x, 
                                 interpolation = 2)) 
row1 <- imappend(im_list[1:4], axis = "x")  
row2 <- imappend(im_list[5:7], axis = "x")
imappend(list(row1,row2), "y") |> 
  plot(interp=F) 
(a) Nearest Neighbour interpolation
(b) Linear interpolation
(c) Cubic interpolation
Rysunek 4.2: Przykład rotacji o wielokrotnośc \(15\degree\)

Jak widać z powyższych wykresów rotacja wpływa na jakość obrazu, dlatego zaleca się nie stosować kilku występujących po sobie rotacji o kąty niebędące wielokrotnościami \(90\degree\).

Kod
im2 <- imlist(im)
for(i in 1:6){
  im2[[i+1]] <- imrotate(im2[[i]], angle = 15)
}
imappend(im2, axis = "x") |> plot(interp=F)
Rysunek 4.3: Kilka iteracji rotacji obrazu. Każdy nastepny powstaje jako rotacja poprzedniego.

4.4 Zmiana rozmiaru obrazu

W przypadku gdy zmieniamy rozmiar obrazu w proporcji 0.5, 2, 3 funkcja imresize korzysta z algorytmu opisanego na stronie http://www.scale2x.it/algorithm.html.

Kod
im_list <- map_il(c(0.5, 1, 2), ~imresize(im, 
                                         scale = .x)) 
imappend(im_list, "x") |>
  plot(interp = F) # antialiasing off
Rysunek 4.4: Zmiana rozmiaru obrazu

W innych przypadkach wykorzystuje jedną z 7 opcji interpolacji:

  • brak interpolacji (opcja -1);
  • brak interpolacji ale dodatkowo powstała przestrzeń jest wypełniana zgodnie z warunkiem brzegowym boundary_conditions (opcja 0);
  • interpolacja metodą najbliższego sąsiada (opcja 1);
  • interpolacja metodą średniej kroczącej (opcja 2);
  • interpolacja liniowa (opcja 3);
  • interpolacja na siatce (opcja 4);
  • interpolacja kubiczna (opcja 5);
  • interpolacja Lanczosa (opcja 6).
Kod
im_list <- map_il(c(-1:6), ~imresize(im, 
                                     scale = 1.5,
                                     interpolation = .x))
row1 <- imappend(im_list[1:4], "x")
row2 <- imappend(im_list[5:8], "x")
imappend(list(row1, row2), "y") |>
  plot(interp = F)
Rysunek 4.5: Zastosowania różnych interpolacji