Fondamenti di Programmazione

Liste e Tuple II

In questa lezione, faremo esempi su come manipolare immagini esistenti. Il materiale necessario per questa lezione si puo' scaricare in questo pacchetto.

Caricamento di Immagini

Per caricare le immagini, usiamo la funzione load(filename) inclusa nel pacchetto. Questa funzione ritorna l'immagine iniziale nel formato descritto in precedenza. Questo e' un esempio di leggere e scrivere la stessa immagine.

img = load('img_in_01.png')
save('img_out_01.png',img)

Copie

Possiamo aggioungere un bordo all'immagine nel modo seguente. Per fare questo esiamo la funzione che copia una porzione di una immagine su un'altra.

def copy(dst,src,dx,dy,sx,sy,w,h):
    for j in range(h):
        for i in range(w):
            di, dj = i+dx, j+dy
            si, sj = i+sx, j+sy
            if not inside(dst,di,dj): continue
            if not inside(src,si,sj): continue
            dst[dj][di] = src[sj][si]

def border_constant(img,s,c):
    ret = create(len(img[0])+s*2,len(img)+s*2,c)
    copy(ret,img,s,s,0,0,len(img[0]),len(img))
    return ret

Accesso ai Pixels

Possiamo routare un'immagine sui propri assi, scambiando i colori le cui coordinate variano solo su questi assi. Le funzioni di seguito routano l'immagine rispetto ai due assi.

def flip_horizontal(img):
    ret = create(len(img[0]),len(img),(0,0,0))
    for j in range(len(img)):
        for i in range(len(img[0])):
            fi = len(img[0])-1-i
            ret[j][i] = img[j][fi]
    return ret

def flip_vertical(img):
    ret = create(len(img[0]),len(img),(0,0,0))
    for j in range(len(img)):
        for i in range(len(img[0])):
            fj = len(img)-1-j
            ret[j][i] = img[fj][i]
    return ret

Per applicare l'operazione, carichiamo immagine e chiamiamo la funzione. Date che quest'ultima non modifica l'immagine direttamente, ma ne ritorna una nuova, boddiamo salvarla nella variabile prima di scrivere sul disco. Ecco il codice e le risultanti immagini.

img = load('img_in_01.png')
img = flip_horizontal(img)
save('img_out_fliph.png',img)

img = load('img_in_01.png')
img = flip_vertical(img)
save('img_out_flipv.png',img)

Infine, per la rotazione attorno all'angolo, dobbiamo invertire le coordinate.

def rotate_corner(img):
    ret = create(len(img),len(img[0]),(0,0,0))
    for j in range(len(img)):
        for i in range(len(img[0])):
            ret[i][j] = img[j][i]
    return ret

Modifica dei Colori

Possiamo modificare i colori di un'immagine per creare effetti interessanti o semplicemente per migliorare l'aspetto dell'immagine. Di seguito mostriamo il codice per invertire i colori e quello per modificare il contrasto (per valori di c 2 e 0.5).

def invert(img):
    ret = create(len(img[0]),len(img),(0,0,0))
    for j in range(len(img)):
        for i in range(len(img[0])):
            r, g, b = img[j][i]
            ret[j][i] = ( 255 - r, 255 - g, 255 - b)
    return ret

def _c(value,c):
    value = ((value - 128) * c) + 128
    return round( min(255,max(0,value)) )

def contrast(img,c):
    ret = create(len(img[0]),len(img),(0,0,0))
    for j in range(len(img)):
        for i in range(len(img[0])):
            r, g, b = img[j][i]
            ret[j][i] = (_c(r,c), _c(g,c), _c(b,c))
    return ret

Filtri

Possiamo creare un mosaico dell'immagine originale disegnando un qudratino per ogni gruppo di pixels, ma prendendo il colore dai pixels dell'immagine iniziale. Nella prima versione assegnamo il colore basato su un solo pixel dell'immagine originale. Segue il codice e il risulato per la dimensione 16 del quadratino.

def mosaic_nearest(img,s):
    ret = create(len(img[0]),len(img),(0,0,0))
    for jj in range(len(img)/s):
        for ii in range(len(img[0])/s):
            c = img[jj*s][ii*s]
            draw_quad(ret,ii*s,jj*s,s,s,c)
    return ret

Come si puo' notare l'immagine e' poco riconoscibile. Possiamo invece settare il colore di ogni quadratino basandoci sulle media dei valori nei pixels coperti nell'immagine iniziale.

def mosaic_average(img,s):
    ret = create(len(img[0]),len(img),(0,0,0))
    for jj in range(len(img)/s):
        for ii in range(len(img[0])/s):
            c = [0,0,0]
            for j in range(jj*s,(jj+1)*s):
                for i in range(ii*s,(ii+1)*s):
                    for idx in range(3):
                        c[idx] += img[j][i][idx]
            for idx in range(3):
                c[idx] = round(float(c[idx]) / float(s*s))
            draw_quad(ret,ii*s,jj*s,s,s,c)
    return ret

Infine, possiamo cambiare la dimensione dei quadratini in base al colore, invece di settare solo il colore.

def mosaic_size(img,s):
    ret = create(len(img[0]),len(img),(0,0,0))
    for jj in range(len(img)/s):
        for ii in range(len(img[0])/s):
            r = 0
            for j in range(jj*s,(jj+1)*s):
                for i in range(ii*s,(ii+1)*s):
                    for idx in range(3):
                        r += img[j][i][idx]
            r = int(round(s * float(r) / float(s*s*3*255)))
            draw_quad(ret,ii*s+(s-r)/2,jj*s+(s-r)/2,r,r,(255,255,255))
    return ret

Possiamo modificare i pixel scegliendone la posizione in modo casuale. Randomizzare il compartamneto di un programma e' motlo utile per ottenere effetti simili ma non ripetuti. Il modulo random contiene routines per la generazione di numeri casuali. In particolare, random.randint(a,b) e random.uniform(a,b) generano rispettivamente un intero e un numero reale random tra a e b. Per riprodurre i risulati si puo' usare random.seed(value) che inizializza la sequenza di numeri casuali. Notate l'uso delle espressioni combinate min a max per assicurare di accedere a posizioni ok del pixel. Seguono esempi per 4 e 16.

def scramble(img,s):
    random.seed(0)
    ret = create(len(img[0]),len(img),(0,0,0))
    for j in range(len(img)):
        for i in range(len(img[0])):
            si = i + random.randint(-s,s)
            sj = j + random.randint(-s,s)
            si = max(0,min(len(img[0])-1,si))
            sj = max(0,min(len(img)-1,sj))
            ret[j][i] = img[sj][si]
    return ret

Infine, per creare un effetto lente, basta cambiare la posizione lungo in raggio della lente centrata a (x,y) con raggio r e "power" p. Mostraiamo il codice seguito dai risultati per p uguale a 0.5 e 2.

def lens_paraboloid(img,x,y,r,p):
    ret = create(len(img[0]),len(img),(0,0,0))
    for j in range(len(img)):
        for i in range(len(img[0])):
            li, lj = i - x, j - y
            if li*li+lj*lj < r*r:
                rr = math.sqrt(li*li+lj*lj) / r
                if rr > 0: ratio = rr ** p / rr
                else: ratio = 1
                ret[j][i] = img[int((j-y)*ratio+y)][int((i-x)*ratio+x)]
            else:
                ret[j][i] = img[j][i]
    return ret