Immagini M odelli del colore Visualizzazione di immagini Cattura dei pixel di una immagine Creazione di nuove Cenni sui formati delle immagini Immagini a colori • • Visione Artificiale 08/09 Le immagini a colori nella loro forma più semplice sono costituiti da pixel che memorizzano 3 valori distinti (uno per la componente rossa, uno per la componente verde, il terzo per il blu) Normalmente per ogni colore si utilizza un byte per cui sono rappresentabili 256x256x256 colori diversi (circa 16 milioni) Operazioni puntuali 2 Immagini a colori Immagine originale Il canale verde Visione Artificiale 08/09 Operazioni puntuali Il canale rosso Il canale blu 3 Immagini a colori e Java • Java normalmente utilizza un int per memorizzare un pixel – il byte meno significativo per la componente blu, poi la verde, quindi la rossa – Il byte più significativo (componente alfa) è il grado di trasparenza del pixel (255 pixel opaco, 0 pixel trasparente cioè invisibile) – int pixel = 0XAARRGGBB; Visione Artificiale 08/09 Operazioni puntuali 4 Immagini a colori e Java • Dato il valore (V) di un pixel per ottenere le singole componenti normalmente si procede: – R = (V >> 16) & 255;// R = (V/0x10000) & 255 – G = (V >> 8) & 255; – B = (V) & 255; • In modo analogo l’operazione inversa – V = (R<<16) | (G<<8) | (B) | 0xff000000; – l’ultimo termine è necessario per avere un pixel opaco – ovviamente si deve avere: 0 ≤ R,G,B ≤ 255) Visione Artificiale 08/09 Operazioni puntuali 5 Immagini a toni di grigio • Un immagine a toni di grigio è una immagine in cui le tre componenti sono uguali – Un metodo intuitivo per operare la trasformazione da colore a grigio è Grigio = (R+G+B)/3 – Dato che l’occhio ha una sensibilità diversa per i diversi colori si preferisce una media pesata • Una scelta frequente è Grigio = 0.299*R + 0.587*G + 0.114*B Visione Artificiale 08/09 Operazioni puntuali 6 Immagini e Java • Le immagini sono solitamente trattate tramite la classe java.awt.Image – È una classe astratta per cui normalmente si utilizzano classi derivate – Per la visualizzazione si può utilizzare la classe javax.swing.ImageIcon – Oppure utilizzare il metodo drawImage della classe Graphics Visione Artificiale 08/09 Operazioni puntuali 7 Semplice visualizzazione package va.imageframe; import javax.swing.*; import java.awt.Image; public class ImageFrame extends JFrame { public ImageFrame(String imageName) { super(); ImageIcon ii = new ImageIcon(imageName); initFrame(ii.getImage(), imageName, ii); } protected void initFrame(Image img, String title, ImageIcon ii) { setTitle(title); this.img = img; getContentPane().add(new JLabel(ii)); setSize(img.getWidth(this), img.getHeight(this)); pack(); setDefaultCloseOperation(defaultCloseOperation); show(); } Visione Artificiale 08/09 Operazioni puntuali 8 Semplice visualizzazione (2) public static void main(String[] imageFileNames) { for(int i=0; i<imageFileNames.length; i++) { new ImageFrame(imageFileNames[i]); } } public ImageFrame(Image img, String title) { super(); initFrame(img, title, new ImageIcon(img)); } protected Image img; static int defaultCloseOperation = EXIT_ON_CLOSE; protected ImageFrame() {} } Visione Artificiale 08/09 Operazioni puntuali 9 Uso di ImageIO package va.imageframe; import import import import java.awt.image.BufferedImage; java.io.File; java.io.IOException; javax.imageio.ImageIO; public class ImageIOTester { public static void main(String[] imageFileNames) throws IOException { for(int i=0; i<imageFileNames.length; i++) { BufferedImage img = ImageIO.read( new File(imageFileNames[i])); new ImageFrame(img, imageFileNames[i]); } } } Visione Artificiale 08/09 Operazioni puntuali 10 Uso di PixelGrabber package va.imageframe; public GrabImage(Image img, String title) { super(img, title); } import java.awt.*; import java.awt.image.*; public int[] grabPixels() { while ( img.getWidth(null) < 0 || img.getHeight(null) < 0); int w = img.getWidth(null); int h = img.getHeight(null); int[] pixels = new int[w*h]; PixelGrabber pg = new PixelGrabber(img, 0, 0, w, h, pixels, 0, w); try { pg.grabPixels(); } catch (InterruptedException e) { throw new RuntimeException("interrupted waiting for pixels!"); } if ((pg.getStatus() & ImageObserver.ABORT) != 0) { throw new RuntimeException("image fetch aborted or errored"); } return pixels; } public class GrabImage extends ImageFrame { public static void main(String[] imageFileNames) { Toolkit tk = Toolkit.getDefaultToolkit(); for(int i=0; i<imageFileNames.length; i++) { Image img = tk.createImage(imageFileNames[i]); GrabImage f = new GrabImage(img, imageFileNames[i]); int w = img.getWidth(f); int h = img.getHeight(f); int[] pix = f.grabPixels(); for(int j=0; j<pix.length; j++) pix[j] ^= 0xffffff; MemoryImageSource mis = new MemoryImageSource(w, h, pix, 0, w); img = tk.createImage(mis); new ImageFrame(img, "negativo: "+imageFileNames[i]); } } Visione Artificiale 08/09 } Operazioni puntuali 11 Uso di BufferedImage package va.imageframe; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; static private BufferedImage negativo(BufferedImage img) { int w = img.getWidth(); int h = img.getHeight(); int[] pix = img.getRGB(0, 0, w, h, null, 0, w); for(int j=0; j<pix.length; j++) pix[j] ^= 0xffffff; BufferedImage neg = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR); neg.setRGB(0, 0, w, h, pix, 0, w); return neg; } } public class BufferedImageTester { public static void main(String[] imageFileNames) throws IOException { for(int i=0; i<imageFileNames.length; i++) { BufferedImage img = ImageIO.read(new File(imageFileNames[i])); new ImageFrame(img, imageFileNames[i]); BufferedImage neg = negativo(img); String name = imageFileNames[i].substring(0, imageFileNames[i].lastIndexOf('.'))+"-neg"; new ImageFrame(neg, name]); ImageIO.write(neg, "PNG", new File(name+".png")); } } Visione Artificiale 08/09 Operazioni puntuali 12 Sistema di riferimento convenzionale 0 j W-1 0 i I(i,j) Cattura una immagine di larghezza W e altezza H H-1 Cattura una porzione di immagine di larghezza w e altezza h int[] pix = img.getRGB(startX, startY, w, h, null, offset, scansize ); int pixel = pix[offset + (y-startY)*scansize + (x-startX)]; Numero di pixel allocati (offset + (h-startY)*scansize) int[] pix = img.getRGB(0, 0, W, H, null, 0, W ); int pixel = pix[y*W + x]; (W*H) Visione Artificiale 08/09 Operazioni puntuali 13 Modelli di colore • • Sono stati proposti molti modi alternativi per rappresentare i colori I modelli di colore sono in genere relativi all’applicazione – RGB - monitor – CMYK – cyan, magenta, yellow, black – stampanti Visione Artificiale 08/09 Operazioni puntuali 14 Modelli di colore – YIQ – luminance, inphase, quadrature – tv color – HIS – hue, saturation, intensity – HSV – hue, saturation, value – HSB – hue, saturation, brightness Visione Artificiale 08/09 Operazioni puntuali 15 Altri modelli per il colore 0.114 R Y 0.299 0.587 I = 0.596 − 0.274 − 0.322 G Q 0.211 − 0.523 0.312 B U = 0.493(B-Y) V = 0.877(R-Y) Visione Artificiale 08/09 0,299 -0,147 0,615 Operazioni puntuali 0,587 -0,289 -0,515 0,114 0,437 -0,100 16 Altri modelli per il colore • HSI – H è il colore dominante • 0° rosso, 120° verde, 240° blu – S (saturazione – cioè intensità di colore) • 0 è un tono di grigio I = (R + G + B) / 3 0.5[ ( R - G ) + ( R - B ) ] B < G acos 2 ( R − G ) + ( R − B )( G − B ) H= 0.5[ ( R - G ) + ( R - B ) ] B > G - acos 2 ( R − G ) + ( R − B )( G − B ) S = 1 − min ( R, G, B ) / I Visione Artificiale 08/09 Operazioni puntuali 17 HSV Verde V 0°: 255, 0, 0 Giallo 60°: 255, 255, 0 Ciano Bianco Blu Rosso 120°: 0, 255, 0 180°: 0, 255, 255 Magenta 240°: 0, 0, 255 H Bianco: H indefinito, 255, 255, 255 Visione Artificiale 08/09 Operazioni puntuali 300°: 255, 0, 255 S 18 Altri modelli per il colore • rgb – r = R/(R+G+B) – g = G/(R+G+B) – b = B/(R+G+B) • È una rappresentazione normalizzata rispetto all’intensità luminosa (r+g+b=1) – Uno dei tre valori è ridondante Visione Artificiale 08/09 Operazioni puntuali 19 Modello HSB … static private BufferedImage HSB(BufferedImage img) { int w = img.getWidth(), h = img.getHeight(); BufferedImage hsb = new BufferedImage(w,h,BufferedImage.TYPE_4BYTE_ABGR); float[] a = new float[3]; for(int i=0; i<h; i++) { for(int j=0; j<w; j++) { int rgb = img.getRGB(j, i); int r= (rgb >> 16) & 255, g = (rgb >> 8) & 255, b = (rgb) & 255; Color.RGBtoHSB(r, g, b, a); float H = a[0], S = a[1], B = a[2]; if(B<0.1) { B = 0; } else if(S<0.1) { S = 0; B = 1; } else { S = 1; B = 1; } hsb.setRGB(j, i, Color.HSBtoRGB(H, S, B)); } } return hsb; … Visione} Artificiale 08/09 Operazioni puntuali 20 Gamma di colori visualizzabili • Visione Artificiale 08/09 Operazioni puntuali I colori rappresentabili in un monitor, non coincidono con i colori stampabili 21 Immagini a colori • • Visione Artificiale 08/09 Spesso per limitare l’occupazione di memoria si utilizzano un numero limitato di colori, riuscendo quindi a memorizzare l’immagine con un minore numero di bit per pixel (8, 4, 1) In questo caso però l’immagine deve memorizzare anche la LUT (Look Up Table) dei colori Operazioni puntuali 22 Immagini a colori Immagine originale Immagine a 256 colori Immagine a Immagine a 16 colori 8 colori Visione Artificiale 08/09 Operazioni puntuali 23 LUT dei colori valore pixel R1 G1 B1 R2 G2 B2 R3 G3 B3 R4 G4 B4 R5 G5 B5 R6 G6 B6 R7 G6 B7 Valore effettivamente visualizzato (R5, G5, B5) … final static ColorModel cm; static { byte[] r = new byte[256], g = new byte[256], b = new byte[256]; for(int i=1; i<128; i++) { g[256-i] = b[i] = r[i] = (byte) (i*2+1); } cm = new IndexColorModel(8, 256, r, g, b, 128); } … Visione Artificiale 08/09 Operazioni puntuali 24 Immagini bitmap (bmp) typedef struct { short magic; /* "BM" */ long file_dim; /* dimensione file */ long l0; /* 0 */ Struttura del file: long header_dim; /* dimensione header */ long l40; /* 40 */ long xsize; /* numero colonne */ long ysize; /* numero righe */ short nchan; /* 1 */ intestazione, lut dei colori utilizzati dati dell’immagine per righe (le righe devono essere di dimensione multipla di 4 byte) short zsize; /* 1-4-8-24 */ long compression; /* 0 -> non compresso */ long data_dim; /* dimensione dati */ long xppi; long yppi; long colors; /* dimensione lut */ long colors1; } bmp_header; Visione Artificiale 08/09 Operazioni puntuali 25 Immagini portable gray map (pgm) • Struttura del file: prima riga “P5” un numero arbitrario (anche 0) di righe di commento che iniziano con “#” una riga contenente numero di colonne e numero di righe un numero arbitrario (anche 0) di righe di commento che iniziano con “#” una riga contenente il valore massimo del range (normalmente 255) i dati dell’immagine (1 byte per pixel) Visione Artificiale 08/09 Operazioni puntuali 26 Immagini portable gray map (pgm) • Struttura del file: prima riga “P2” un numero arbitrario (anche 0) di righe di commento che iniziano con “#” una riga contenente numero di colonne e numero di righe un numero arbitrario (anche 0) di righe di commento che iniziano con “#” una riga contenente il valore massimo del range (normalmente 255) i dati dell’immagine (interi in formato testo compresi fra 0 e “valore massimo”) Visione Artificiale 08/09 Operazioni puntuali 27 Immagini portable pixel map (ppm) • Struttura del file: prima riga “P6” un numero arbitrario (anche 0) di righe di commento che iniziano con “#” una riga contenente numero di colonne e numero di righe un numero arbitrario (anche 0) di righe di commento che iniziano con “#” una riga contenente il valore massimo del range (normalmente 255) i dati dell’immagine (3 byte per pixel, terne RGB: valore per il colore rosso, verde, blu) Visione Artificiale 08/09 Operazioni puntuali 28 Immagini portable pixel map (ppm) • Struttura del file: prima riga “P3” un numero arbitrario (anche 0) di righe di commento che iniziano con “#” una riga contenente numero di colonne e numero di righe un numero arbitrario (anche 0) di righe di commento che iniziano con “#” una riga contenente il valore massimo del range (normalmente 255) i dati dell’immagine (3 interi per pixel, terne RGB: valore per il colore rosso, verde, blu) Visione Artificiale 08/09 Operazioni puntuali 29 Immagini gif Permettono di memorizzare immagini con al più 256 colori diversi Iniziano con “GIF89a” Segue un header con le informazioni sulla struttura dell’immagine (righe, colonne, numero di colori, …) Una tabella di lut con le terne corrispondenti ai colori utilizzati i dati dell’immagine (logicamente 1 byte per pixel) in formato compresso Visione Artificiale 08/09 Operazioni puntuali 30 Immagini gif • • Immagine “ppm” circa 290 Kb Immagine “gif” circa 53 Kb • N.B. a partire dalla “gif” è possibile riottenere l’immagine “ppm” di partenza (se il numero di colori è limitato) – Non si ha perdita di informazione Visione Artificiale 08/09 Operazioni puntuali 31 Immagini jpg • • Visione Artificiale 08/09 Si scompone l’immagine a colori in blocchi di 16x16 pixel, si fa una analisi in frequenza del blocco e si eliminano le alte frequenze (che l’occhio umano non percepisce bene) Se le immagini devono essere utilizzate da un osservatore umano il risultato è generalmente ottimo: buon aspetto e pochi dati da trasmettere/memorizzare Operazioni puntuali 32 Immagini jpg • • Immagine “ppm” circa 290 Kb Immagine “jpg” circa 25 Kb • N.B. a partire dalla “jpg” non è possibile riottenere l’immagine “ppm” di partenza – Si ha perdita di informazione • Il grado di compressione (ma quindi anche la perdita di informazione) è un parametro del processo di trasformazione Visione Artificiale 08/09 Operazioni puntuali 33 Istogramma • • • Un primo esempio di elaborazione di immagini è l’istogramma L’istogramma trasforma l’immagine in un vettore Ogni elemento del vettore contiene il numero delle volte che il valore v è presente nell’immagine (o se il vettore è normalizzato la sua frequenza). – Dal punto di vista della programmazione significa scandire l’intera immagine e per ogni pixel incrementare un elemento opportuno del vettore • (C o Java) istogramma[ immagine[i][j] ]++ Visione Artificiale 08/09 Operazioni puntuali 34 Istogramma package va.imageframe; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import javax.imageio.ImageIO; public class Istogramma { public static void main(String[] args) throws IOException { BufferedImage img = ImageIO.read(new File(args[0])); new ImageFrame(img, args[0]); int w = img.getWidth(), h = img.getHeight(); int[] B = MemoryImageSourceTester.getB(img); int[] istogramma = new int[256]; for(int i=0; i<B.length; i++) { istogramma[B[i]]++; B[i] = 0xff000000 | B[i] * 0x10101; } BufferedImage brightness = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR); brightness.setRGB(0, 0, w, h, B, 0, w); new ImageFrame(brightness, "Brightness").setLocation(100, 300); String out = ""; for(int i=0; i<istogramma.length; i++) out += i + " " + istogramma[i] + "\n"; Frame f = new Frame("Istogramma"); f.add(new TextArea(out)); f.setBounds(600, 100, 150, 400); f.setVisible(true); } } Visione Artificiale 08/09 Operazioni puntuali 35 Istogramma Visione Artificiale 08/09 Operazioni puntuali 36