Cześć!
Dziś arcyciekawy temat :)
Czym jest przetwarzanie obrazów? Są to wszystkie operacje jakie wpływają na to co widzimy. Zmiany kolorów, odcienie szarości, sepia, model barw RGB, HSV, koloryzacja, nasycenie i jeszcze wiele, wiele innych terminów określa własnie to, czym jest przetwarzanie obrazów.
W bardziej zaawansowanych algorytmach szuka się np. sposobów wykrywania twarzy.
Mówię cały czas o statycznych obrazach. Jeśli chcemy przejśc na dynamiczne, to zaczniemy mówić oczywiście o np. obrazach z kamerek. Ale dziś zostaniemy przy obrazach statycznych.
Zacznijmy od podstaw. Czym jest piksel? Jeden piksel w odniesieniu do monitorów to bardzo mały kwadrat (często spotykana szerokość boku to 0,28 mm) lub prostokąt widzialny z odległości użytkowej jako wypełniony jednolitym kolorem – czyli najmniejszy element obrazu, który jest określany przez jeden kolor. Kolor jednego piksela określany jest przez 3 subpiksele – czerwony, zielony i niebieski – czyli RGB. To wypadkowa kolorów które przyjmują subpiksele określa kolor piksela.
Ja sam, podzieliłbym algorytmy które rządzą przetwarzaniem obrazów na 3 grupy:
– iteracyjne
– oparte o tablice LUT
– numeryczne
Dziś zajmiemy się tą pierwszą grupą. Na dwie kolejne przyjdzie czas zapewne – ciężko ominać te arcyciekawe podtematy ;)
Ok, skoro już wiemy że obrazy to zbiory pikseli, które określają praktycznie całość wyglądu obrazu, to logiczne jest że zmiana wartości poszczególnych wartości RGB – zmieni wygląd obrazu, prawda? Ok, to przejdźmy do 1 algorytmu iteracyjnego.
1. Całościowa zmiana koloru.
Tak na serio dysponujemy 3 kolorami – czerwonym, zielonym i niebieskim. Możemy więc łatwo przetworzyć obraz na jeden z tych kolorów, prawda? Algorytm:
1 2 3 4 5 6 7 8 9 10 11 12 | for i=0 to i<szerokosc_ekranu for j=0 to j<wysokosc_ekranu usuwaj_skladowa_inna niz_wybrana(piksel[i][j],wybrana); end end |
Przetworzony obraz dla zielonego:
Przetworzony obraz dla czerwonego:
Przetworzony obraz dla niebieskiego:
2. Odcień szarości
Prosty, iteracyjny algorytm, polecający na przeliczaniu wartość wg wzoru. Czesto jest podstawą do bardziej złożonych algorytmów:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | for i=0 to i<szerokosc_ekranu for j=0 to j<wysokosc_ekranu piksel[i][j].red = 0.299*piksel[i][j].red_pierwotny+0.587*piksel[i][j].green_pierwotny+0.114*piksel[i][j].blue_pierwotny; piksel[i][j].green= 0.299*piksel[i][j].red_pierwotny+0.587*piksel[i][j].green_pierwotny+0.114*piksel[i][j].blue_pierwotny; piksel[i][j].blue= 0.299*piksel[i][j].red_pierwotny+0.587*piksel[i][j].green_pierwotny+0.114*piksel[i][j].blue_pierwotny; end end |
No i efekt:
I efekt
3. Sepia
Sepia jest jednym z tych algorytmów, które wymagają pierw konwersji w odcień szarości. Algorytm również iteracyjny:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | for i=0 to i<szerokosc_ekranu for j=0 to j<wysokosc_ekranu piksel[i][j].red = (0.393*piksel[i][j].red_pierwotny+0.769*piksel[i][j].green_pierwotny+0.189*piksel[i][j].blue_pierwotny)/1.351; piksel[i][j].green= (0.349*piksel[i][j].red_pierwotny+0.686*piksel[i][j].green_pierwotny+0.186*piksel[i][j].blue_pierwotny)/1.203; piksel[i][j].blue= (0.272*piksel[i][j].red_pierwotny+0.534*piksel[i][j].green_pierwotny+0.131*piksel[i][j].blue_pierwotny)/2.140; end end |
No i efekt.. Dla porównania zawsze będę obraz oryginalny umieszał:
4. Zmiana jasności
Najłatwiej jest to zrealizować zmieniając wartość RGB na HSV (hue, saturation, value) i zwiększenie wartości ostatniej.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | for i=0 to i<szerokosc_ekranu for j=0 to j<wysokosc_ekranu piksel[i][j] = rgb2hsv(piksel[i][j]); piksel[i][j].value = piksel[i][j].value+howMore>240?240:piksel[i][j].value+howMore; piksel[i][j] = hsv2rgb(piksel[i][j]); ustaw_piksel(piksel[i][j]); end end |
efekt jest tutaj zależny od wartość howMore.
Oto obrazek oryginalny:
Dla wartości 20:
Dla wartości 70:
Dla wartości 120:
Dla wartości 170:
Dla wartości 220:
Zmieniając tylko ostatnia wartość nie uzyskamy koloru białego. Zdecydowanie lepiej skorzystać z tablicy LUT.
5. Koloryzacja
Czyli zmiana HS w modelu HSV.Tutaj warto być ostrożnym, bo zmiany niektóre mogą dać nieoczekiwane efekty. Algorytmu nie podaję bo jest analogiczny do tego wyżej tylko że zmieniamy HS a nie V.
No i dla dodania 60 do H i 120 do S:
60 do H 0 do S:
90 do H 0 do S:
120 do H 0 do S:
150 do H 0 do S:
180 do H 0 do S:
210 do H 0 do S:
240 do H 0 do S:
5. Zdjęcia czarno-białe (nie odcień czarości)
Ustawiamy tzw próg. Jest to wartość która mówi które wartości mają być czarne a które białe. Jesli V jesli wieksze od progu to dajemy kolor czarny, jeśli mniejsze to biały. Robimy to na HSV, poczym konwertujemy na RGB. Algorytm jest chyba najprostszy z tych tutaj opisanych.
Oto obrazek oryginalny:
Dla wartości progu 100:
Dla wartości progu 150:
Dla wartości progu 200:
Dla wartości progu 250:
Chyba najfajniej wygląda to dla progu 200.
5. Zdjęcia czarno-białe (nie odcień czarości) z modulacją progu
Modyfikacja wymagająca pierw przekonwertowania obrazu na odcień szarości. Oraz ustawieniu tzn modulacji kanału, czyli okreslenie przedziału -+10% progu do którego będa łapac się wartości podobne do siebie kolorystcznie (a więc i wartościowo).
Rezultaty:
Dla progu 50:
Dla progu 100:
Dla progu 150:
Dla progu 200:
Dla progu 250:
Poniżej animacja która pokazuje jak tworzy się obraz tej metodzie:
Pokazałem przykłady, algorytmy i nawet animację zrobiłem. Czas na kod! A nim też jest zawarty algorytm przeliczania HSV na RGB i odwrotnie.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package tests; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Random; import javax.imageio.ImageIO; /** * * @author Matt * @see http://mmazur.eu.org/podstawy-przetwarzania-obrazow-java/ */ public class Graphics { private BufferedImage im=null; private WritableRaster raster=null; private int width = 0; private int height = 0; private int pixels[] = new int[3]; /** * Create new instance of class * * @param fileName file from disk which is base to run class * @throws IOException */ public Graphics(String fileName) throws IOException { im=ImageIO.read(new File(fileName)); raster = im.getRaster(); width = raster.getWidth(); height = raster.getHeight(); } /** * Method to change colour from picture to one of selected * * @param color 1-red, 2-green, 3-blue */ public void changeColor(int color){ Random r = new Random(); int ww[]=new int[3]; if(color>3 || color<1) throw new IllegalArgumentException("Unsupported argument. Value must be <1:3>"); for(int i=0;i<width;i++) { for(int j=0;j<height;j++) { raster.getPixel(i, j, pixels); ww[0] = r.nextInt((int)((color==1?1:0)*pixels[0])+1); ww[1] = r.nextInt((int)((color==2?1:0)*pixels[1])+1); ww[2] = r.nextInt((int)((color==3?1:0)*pixels[2])+1); raster.setPixel(i, j, ww); } } } /** * Method to change colour from picture to grey scale * */ public void greyScale(){ double ww[]=new double[3]; for(int i=0;i<width;i++) { for(int j=0;j<height;j++) { raster.getPixel(i, j, pixels); ww[0] = 0.299*pixels[0]+0.587*pixels[1]+0.114*pixels[2]; ww[1] = 0.299*pixels[0]+0.587*pixels[1]+0.114*pixels[2]; ww[2] = 0.299*pixels[0]+0.587*pixels[1]+0.114*pixels[2]; raster.setPixel(i, j, ww); } } } /** * Method to change colour from picture to sepia * */ public void sepia(){ double ww[]=new double[3]; this.greyScale(); for(int i=0;i<width;i++) { for(int j=0;j<height;j++) { raster.getPixel(i, j, pixels); ww[0] = (pixels[0] * 0.393 + pixels[1] * 0.769 + pixels[2] * 0.189 ) / 1.351; ww[1] = (pixels[0] * 0.349 + pixels[1] * 0.686 + pixels[2] * 0.186 ) / 1.203; ww[2] = (pixels[0] * 0.272 + pixels[1] * 0.534 + pixels[2] * 0.131 ) / 2.140; raster.setPixel(i, j, ww); } } } /** * Method to change image's brightness * * @param howMore How more you want to add brightness * @throws IllegalAccessException */ public void brightness(int howMore) throws IllegalAccessException { if(howMore<0) throw new IllegalAccessException("Only positive values!"); double hsv[]; double ww[]; for(int i=0;i<width;i++) { for(int j=0;j<height;j++) { raster.getPixel(i, j, pixels); hsv=rgb2hsv(pixels[0], pixels[1], pixels[2]); hsv[2]=hsv[2]+howMore>240?240:hsv[2]+howMore; ww=hsv2rgb(hsv[0], hsv[1], hsv[2]); raster.setPixel(i, j, ww); } } } /** * Method to change image exaggeration * * @param h how much you want to add to H i HSV model * @param s how much you want to add to S i HSV model * @throws IllegalArgumentException */ public void exaggeration(int h, int s) throws IllegalArgumentException { if(h<0 || s<0) throw new IllegalArgumentException("Only positive values!"); if(h>360 || s>240) throw new IllegalArgumentException("H must be <0:360>, S<0:240>"); double hsv[]; double ww[]; for(int i=0;i<width;i++) { for(int j=0;j<height;j++) { raster.getPixel(i, j, pixels); hsv=rgb2hsv(pixels[0], pixels[1], pixels[2]); hsv[0]=hsv[0]+h>360?360:hsv[0]+h; hsv[1]=hsv[1]+s>240?240:hsv[1]+s; ww=hsv2rgb(hsv[0], hsv[1], hsv[2]); raster.setPixel(i, j, ww); } } } /** * Method to save image to disc, if file exists - overwrite file. * * @param type type of saved file ex. PNG, JPG * @param fileName filename of file which will be saved * @throws IOException */ public void save(String type, String fileName) throws IOException{ ImageIO.write(im,type,new File(fileName)); } /** * Method to calculate HSV to RGB * * @param hue H in HSV model * @param sat S in HSV model * @param val V in HSV model * @return void */ private double[] hsv2rgb(double hue, double sat, double val) { double red = 0, grn = 0, blu = 0; double i, f, p, q, t; double result[] = new double[3]; if(val==0) { red = 0; grn = 0; blu = 0; } else { hue/=60; i = Math.floor(hue); f = hue-i; p = val*(1-sat); q = val*(1-(sat*f)); t = val*(1-(sat*(1-f))); if (i==0) {red=val; grn=t; blu=p;} else if (i==1) {red=q; grn=val; blu=p;} else if (i==2) {red=p; grn=val; blu=t;} else if (i==3) {red=p; grn=q; blu=val;} else if (i==4) {red=t; grn=p; blu=val;} else if (i==5) {red=val; grn=p; blu=q;} } result[0] = red; result[1] = grn; result[2] = blu; return result; } /** * Method to calculate RGB to HSV * * @param red R in RGB model * @param green G in RGB model * @param blue B in RGB model * @return void */ private double[] rgb2hsv(double red, double grn, double blu) { double hue, sat, val; double x, f, i; double result[] = new double[3]; x = Math.min(Math.min(red, grn), blu); val = Math.max(Math.max(red, grn), blu); if (x == val){ hue = 0; sat = 0; } else { f = (red == x) ? grn-blu : ((grn == x) ? blu-red : red-grn); i = (red == x) ? 3 : ((grn == x) ? 5 : 1); hue = ((i-f/(val-x))*60)%360; sat = ((val-x)/val); } result[0] = hue; result[1] = sat; result[2] = val; return result; } /** * Method to change image to black and white mode * * @param ton how much sensitive it will be */ public void blackAndWhite(int ton) { double hsv[]; double ww[]; for(int i=0;i<width;i++) { for(int j=0;j<height;j++) { raster.getPixel(i, j, pixels); hsv=rgb2hsv(pixels[0], pixels[1], pixels[2]); if(hsv[2]>ton) { ww=hsv2rgb(hsv[0], hsv[1], hsv[2]); Arrays.fill(ww, 0); } else { ww=hsv2rgb(hsv[0], hsv[1], hsv[2]); Arrays.fill(ww, 255); } raster.setPixel(i, j, ww); } } } /** * Modifitied method to change image to black and white mode * * @param ton how much sensitive it will be * @param better if true you will turn on modulate of ton */ public void blackAndWhite(int ton, boolean better) { double hsv[]; double ww[]; Random r = new Random(); double min=-(0.15*ton); double max=0.15*ton; int ton2=0; this.greyScale(); for(int i=0;i<width;i++) { for(int j=0;j<height;j++) { raster.getPixel(i, j, pixels); hsv=rgb2hsv(pixels[0], pixels[1], pixels[2]); if(better){ ton2=ton+(int)(min + (int)(Math.random() * ((max - min) + 1))); } else ton2=ton; if(hsv[2]>ton2) { ww=hsv2rgb(hsv[0], hsv[1], hsv[2]); Arrays.fill(ww, 0); } else { ww=hsv2rgb(hsv[0], hsv[1], hsv[2]); Arrays.fill(ww, 255); } raster.setPixel(i, j, ww); } } } } |
Do kodu dołączam dokumentację: Dokumentacja do klasy Graphics
Pozdrawiam!
Mateusz Mazurek
Dzieki za fajny wpis.
Pozdrawiam