Esercizio - Laboratorio 1 - FIR e immagini
Testo (laboratorio 1 del corso Multimedia Signal Processing, UniPD; tema "filtri, loro applicazioni e fondamenti di elaborazione delle immagini"; codice del corso in Python con numpy, scipy e matplotlib, anche in MATLAB). Due esperienze: (1) rilevamento dei bordi di un'immagine con la varianza locale calcolata su una finestra scorrevole, con soglia pari alla media della varianza (o scelta dall'utente, per esempio ); la domanda del testo: a che cosa assomiglia la procedura per la varianza locale? (2) eliminazione di un'eco da un segnale audio: stima del ritardo dall'autocorrelazione e ricostruzione con per ; la domanda del testo: è un filtro FIR?
Dati. I file stanno nella cartella del laboratorio 1 su Moodle: eleph2.jpg (una foto di elefanti, pixel; esercizio 1) e y_echo.mat (esercizio 2, campionato a Hz). I risultati qui sotto sono ottenuti eseguendo il codice proprio su questi file.
Teoria usata: Sistemi LTI e convoluzione discretaUn sistema lineare e tempo-invariante (LTI) è completamente descritto dalla sua risposta impulsiva h[n]: y[n] = Σ x[k] h[n-k] = x[n] * h[n] (somma di convoluzione). Si ricava scomponendo x in impulsi traslati e usando linearità e invarianza. La convoluzione è commutativa, associativa e distributiva; una cascata di LTI equivale a un solo filtro con h = h1 * h2 e l'ordine non conta. Se x ha lunghezza N e h lunghezza L, y ha lunghezza N+L-1. Si calcola col metodo della finestra scorrevole o a tabella. I FIR sono LTI; altri sistemi (n·x[n], x[-n], x²) non lo sono.Sistemi LTI e convoluzione discreta → (filtraggio 2D, convoluzione, media mobile), Sistemi a tempo discreto e filtri FIRUn sistema a tempo discreto trasforma una sequenza x[n] in una sequenza y[n]. Il filtro FIR causale di ordine M calcola y[n] = Σ b_k x[n-k] (k = 0..M): è una media mobile pesata di L = M+1 campioni, la sua risposta impulsiva h[n] coincide con i coefficienti b_k e l'uscita ha supporto lungo N+M se l'ingresso è lungo N. La media mobile è un passa-basso che ritarda di M/2 campioni; la versione centrata non è causale. Gli schemi a blocchi usano solo moltiplicatori, sommatori e ritardi unitari, senza anelli (feed-forward).Sistemi a tempo discreto e filtri FIR → (media mobile, ritardo), Funzione di sistema, poli, zeri e stabilitàLa funzione di sistema H(z) è la trasformata zeta della risposta impulsiva: con ingresso z^n l'uscita è H(z) z^n. Per un FIR H(z) = Σ b_k z^{-k} è un polinomio con M zeri e M poli in z = 0; in generale H = B(z)/A(z) dall'equazione alle differenze. Sulla circonferenza unitaria H(e^{jω̂}) è la risposta in frequenza: |H| = prodotto delle distanze dagli zeri / prodotto delle distanze dai poli, quindi gli zeri bloccano frequenze e i poli le esaltano. Un LTI causale è BIBO stabile se e solo se tutti i poli hanno modulo < 1 (a meno di cancellazioni polo-zero); i FIR sono sempre stabili.Funzione di sistema, poli, zeri e stabilità → (FIR e IIR, poli e stabilità), Segnali e sistemi in Python - campioni, convoluzione e filtriAl calcolatore un segnale continuo è un vettore di campioni con un asse dei tempi. Con numpy si definiscono i segnali, si approssimano area ed energia con somme moltiplicate per il passo, si calcola la convoluzione continua con np.convolve(x, y)*dt (asse = somma degli istanti iniziali), si simulano filtri con equazioni alle differenze e si verificano numericamente linearità e tempo-invarianza.Segnali e sistemi in Python - campioni, convoluzione e filtri → (strumenti numpy).
Parte 1 - bordi con la varianza locale
Idea. In una zona dove l'immagine è piatta i pixel vicini hanno quasi lo stesso valore: la varianza calcolata su una piccola finestra è circa zero. Sui bordi la finestra contiene pixel scuri e chiari insieme, e la varianza è grande. Quindi la varianza locale è una mappa dei bordi, che si trasforma in immagine binaria con una soglia. Per ogni pixel , con le medie fatte su una finestra (per esempio ) centrata nel pixel: Si usa la formula , così bastano due medie locali.
A che cosa assomiglia? La media locale su una finestra è un filtro FIR bidimensionale (media mobile 2D): è la convoluzione dell'immagine con un nucleo con tutti i coefficienti uguali a , la versione 2D della media mobile di Sistemi a tempo discreto e filtri FIRUn sistema a tempo discreto trasforma una sequenza x[n] in una sequenza y[n]. Il filtro FIR causale di ordine M calcola y[n] = Σ b_k x[n-k] (k = 0..M): è una media mobile pesata di L = M+1 campioni, la sua risposta impulsiva h[n] coincide con i coefficienti b_k e l'uscita ha supporto lungo N+M se l'ingresso è lungo N. La media mobile è un passa-basso che ritarda di M/2 campioni; la versione centrata non è causale. Gli schemi a blocchi usano solo moltiplicatori, sommatori e ritardi unitari, senza anelli (feed-forward).Sistemi a tempo discreto e filtri FIR →. La varianza locale combina due filtraggi lineari (le due medie) con un'operazione non lineare (il quadrato prima della media, e quello della media): complessivamente il sistema non è LTI, ma i suoi mattoni lo sono.
Passi del codice. Si carica l'immagine, la si converte in scala di grigi e in float (per non avere overflow su interi a 8 bit), si calcolano le due medie locali, la varianza, la soglia e si disegna il risultato. Il codice del corso usa due cicli annidati con zero padding (qui con PIL al posto di OpenCV: la conversione in grigi, , dà gli stessi valori):
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
img_gray = np.asarray(Image.open("eleph2.jpg").convert("L")).astype(np.float64) # 800 x 1600
def local_mean(image, window_size=3):
if window_size % 2 == 0:
window_size += 1 # finestra dispari: pixel centrale
mean_matrix = np.zeros_like(image)
mn = window_size // 2
padded = np.pad(image, pad_width=mn, mode="constant", constant_values=0)
rows, cols = image.shape
for i in range(rows):
for j in range(cols):
mean_matrix[i, j] = np.mean(padded[i:i+window_size, j:j+window_size])
return mean_matrix
sz = 3
local_variance = local_mean(img_gray**2, sz) - local_mean(img_gray, sz)**2
threshold = np.mean(local_variance) # oppure, ad esempio, 400
edges = np.zeros_like(local_variance)
edges[local_variance < threshold] = 1
edges[local_variance >= threshold] = 0Risultati sull'immagine del corso (foto in grigi, pixel, livello medio ):
| finestra | padding | varianza media (soglia) | pixel di bordo | con soglia |
|---|---|---|---|---|
| zeri (corso) | () | |||
reflect |
() | |||
| zeri | ||||
| zeri |
- La soglia alla media taglia molto in alto. La varianza è molto asimmetrica: la mediana è , la media , il massimo . Quindi la soglia pari alla media vale circa volte la mediana e lascia come "bordi" solo i pixel con varianza alta ( dell'immagine). Il massimo possibile per pixel a bit in una finestra è (quattro o cinque pixel a e gli altri a ): il massimo misurato ne è l', cioè nelle zone più contrastate (per esempio le zanne e i contorni degli elefanti contro il cielo) la finestra vede quasi il contrasto massimo.
- Finestra più grande, bordi più spessi. Con e cresce la varianza media (ogni finestra raccoglie più contrasto) e crescono i pixel sopra la soglia fissa (da a ): i bordi diventano linee più larghe.
- Sulla seconda immagine della cartella (
test.jpg, , un ritratto) la varianza media è e il dei pixel supera la soglia: una foto con più dettagli fini ha varianza media più alta.
Osservazioni sulla soluzione del corso
- Colori invertiti. La soluzione mette (bianco) dove la varianza è sotto soglia, cioè sulle zone piatte, e (nero) sui bordi: i bordi risultano neri su fondo bianco. Per avere i bordi bianchi si scrive
edges = local_variance >= threshold. - Bordi finti sul perimetro dell'immagine. Lo zero padding aggiunge una cornice di zeri: le finestre che escono dall'immagine contengono pixel veri e zeri, e la varianza è alta anche senza nessun bordo. Sull'immagine del corso, con lo zero padding dei pixel del perimetro risultano "bordo" (quasi tutti, e sono falsi); con il riflesso (
mode="reflect") i falsi bordi scendono a , che sono per lo più bordi veri vicini ai lati dell'immagine. Cambia anche la soglia: con gli zeri, con il riflesso. - Velocità. I due cicli annidati su un'immagine fanno medie, ciascuna con una finestra: su questa immagine ( milioni di pixel) il codice del corso impiega circa secondi per le due medie. La stessa media mobile 2D è
scipy.ndimage.uniform_filter, che impiega circa secondi per ciascuna. Conmode="constant"dà lo stesso risultato dei cicli (verificato connp.allclosesu una porzione).
from scipy.ndimage import uniform_filter
def local_variance(img, size=3, mode="reflect"):
m = uniform_filter(img, size=size, mode=mode) # E[x]
m2 = uniform_filter(img**2, size=size, mode=mode) # E[x^2]
return m2 - m**2
v = local_variance(img_gray, 3, "reflect")
edges = v >= v.mean() # True = bordo
plt.imshow(edges, cmap="gray"); plt.show()Controllo a mano su un bordo ideale. Se un gradino separa una zona a da una a , per un pixel fuori dal gradino la finestra contiene pixel a e a : media , , varianza ; per un pixel dentro, a e a : media , , varianza . La varianza locale vale nelle zone piatte e sui due pixel che affiancano il gradino: un bordo diventa una striscia spessa pixel, e le zone di contorno risultano strisce di questo tipo.
Parte 2 - autocorrelazione ed eco
Modello. Il segnale registrato è il segnale originale più una copia ritardata e attenuata: con l'ampiezza dell'eco e il ritardo in campioni. Si assume per . È un FIR con (due soli coefficienti non nulli, di ordine ).
Correlazione. La correlazione di e è (convoluzione con la versione ribaltata); è una misura di somiglianza tra le sequenze. Con è l'autocorrelazione . Sostituendo , per bilinearità Se è "rumoroso" (per esempio un segnale audio, la cui autocorrelazione è concentrata attorno a ), ha un picco in e quindi ha picchi in (il più alto), e (da ): dal grafico di si legge come la posizione del picco a destra dello zero.
Il file del corso. loadmat('y_echo.mat') restituisce un dizionario con molte chiavi (Exercise1, Exercise2, Fs, N, data, x, y, y_echo, ...): il segnale con eco è solo y_echo, di campioni, cioè s a Hz, con valore massimo (è normalizzato) e valore efficace . Le altre variabili sono avanzi del file di lavoro di chi l'ha preparato: in particolare la chiave N vale ma non è il ritardo dell'eco di y_echo, che va misurato con l'autocorrelazione.
Lettura di . Con normalizzata ():
- il massimo sui ritardi positivi, preso senza cautele, non è l'eco: è nel lobo centrale e cade a campione (), perché un segnale audio è molto correlato con se stesso a pochi campioni di distanza;
- l'eco è il picco secondario lontano dallo zero. Cercando il massimo per ritardi maggiori di campioni si trova campioni, cioè s (), con altezza (i campioni accanto valgono e : il picco è netto); il picco successivo più alto vale solo (a , ancora sulle code del lobo di ). Il valore coincide con quello della soluzione del corso;
- l'altezza è vicina a , il valore che si avrebbe con un segnale "bianco" (senza correlazione tra campioni lontani): da con si ottiene . Qui è appena sopra, perché non è esattamente zero;
- a non c'è nessun picco (): c'è una sola eco, come nel modello.
Rimozione dell'eco. Dato che per , i primi campioni di coincidono con quelli di ( per ). Per , da si ricava , dove è già noto: si procede campione per campione.
from scipy.io import loadmat
from scipy.signal import correlate, lfilter
y = loadmat('y_echo.mat')['y_echo'].flatten() # Fs = 44100
Fs = 44100
r = correlate(y, y, mode="full", method="fft") # veloce: FFT
r = r / r.max()
lag = np.arange(-len(y) + 1, len(y))
lontano = lag > 2000 # si salta il lobo centrale
N = lag[lontano][np.argmax(r[lontano])] # picco a destra di 0: 22050
a = 0.75
x = np.zeros_like(y); x[:N] = y[:N] # i primi N campioni coincidono
for n in range(N, len(y)):
x[n] = y[n] - a * x[n - N] # filtraggio inversonp.correlate in modo full è lento: solo con campioni impiega circa s e il tempo cresce col quadrato della lunghezza (su campioni, circa minuti, per stima); scipy.signal.correlate con FFT impiega s (Trasformata di Fourier discreta (DFT) e convoluzione circolareLa DFT di N campioni x[0..N-1] è X[k] = sum x[n] e^{-j2pi kn/N}, k = 0..N-1 (IDFT: x[n] = (1/N) sum X[k] e^{j2pi kn/N}); vede il segnale come periodico di periodo N e dà N campioni equispaziati della DTFT, cioè X(z) valutata sulle radici N-esime dell'unità. Se x ha durata M <= N non c'è aliasing temporale e lo zero-padding (N > M) infittisce solo i campioni della stessa DTFT. Proprietà: traslazione circolare, simmetria coniugata X[k] = X*[N-k], prodotto = convoluzione circolare. La convoluzione circolare coincide con la lineare solo se N >= Dx + M - 1; altrimenti la coda si ripiega sull'inizio. La FFT calcola la DFT con (N/2) log2 N moltiplicazioni invece di N^2.Trasformata di Fourier discreta (DFT) e convoluzione circolare →). Il ciclo di ricostruzione impiega s.
Risultato della rimozione. L'autocorrelazione del segnale ricostruito a scende da a (l'eco è praticamente sparita), il valore efficace passa da a (sui primi campioni) e il massimo resta .
- Verifica che sia giusto. Il segnale originale dura campioni: gli ultimi campioni di contengono solo l'eco. Se è corretto, la ricostruzione deve dare zero dopo . Il valore efficace di quella coda è per , per , per : il minimo è proprio a (con una scansione fine). In alternativa, si annulla per .
- File
y_echo_1.mat(stessa cartella, campioni, s). Qui il picco dell'autocorrelazione è a ( s, altezza ); prima ci sono picchi ogni campioni (altezze ) dovuti alla periodicità del suono, non all'eco. Con la cancellazione è eccessiva (); la coda e la correlazione indicano -. Il valore della consegna vale solo pery_echo.mat.
È un filtro FIR? No: usa l'uscita passata (retroazione): è un filtro ricorsivo, quindi IIR. La sua funzione di sistema è , l'inverso del FIR dell'eco . I poli sono le radici di , tutte di modulo ; con e il raggio vale : i poli sono dentro il cerchio unitario e il filtro è stabile (per non lo sarebbe: l'eco compensata crescerebbe; con la coda sopra vale , molto più alta). Equivalentemente il sistema inverso si realizza in forma diretta con lfilter(b=[1], a=[1, 0, ..., 0, a]) (coefficiente in posizione ): sui dati veri il risultato coincide con il ciclo (differenza massima ), ma impiega circa s perché il denominatore ha coefficienti. Il raggio dei poli molto vicino a spiega perché l'eco si spegne lentamente: ogni ripetizione è attenuata di ogni campioni.
Versione ripasso
Parte 1 - bordi con la varianza locale. Per ogni pixel con medie su una finestra . La media locale è un FIR 2D con coefficienti , quindi la varianza combina due filtraggi lineari con un quadrato, cioè è non lineare. Sulle zone piatte la varianza è ; sui bordi è grande. Soglia: la media della varianza, oppure .
- Dati:
eleph2.jpg, pixel, in grigi. Con zero padding la soglia (varianza media) è e i pixel di bordo sono ; conmode="reflect"la soglia è e i bordi . Con soglia : (finestra ), (), (). - La varianza è asimmetrica (mediana , massimo su un massimo possibile di ): la soglia alla media è circa volte la mediana.
- Con lo zero padding dei pixel del perimetro risultano falsi bordi; con
reflectsono . - Il codice del corso (due cicli) impiega circa s;
uniform_filters a media.
Codice della parte 1. Immagine in scala di grigi e in float (per evitare overflow a 8 bit). Si calcolano le due medie locali con uniform_filter (size=3, mode="reflect"), poi e edges = v >= v.mean(). La versione del corso usa due cicli annidati con zero padding e ha i colori invertiti rispetto al bordo.
Parte 2 - eco. Il modello è , un FIR con . La correlazione è ; con scipy.signal.correlate in modo full e FFT ( s; np.correlate su soli campioni richiede s) si calcola , e è il picco a destra dello zero.
- Autocorrelazione: .
- File
y_echo.mat(chiavey_echo, campioni, Hz; la chiaveN=8820del file non è il ritardo). Il massimo dopo è nel lobo centrale (): si cerca per ritardi maggiori di . Picco a campioni, cioè s, altezza (vicina ad ); a nessun picco (). - Rimozione: per , con per . scende da a ; valore efficace da a .
- Controllo di : oltre i primi campioni la ricostruzione deve essere nulla; il valore efficace della coda è minimo () proprio a ( a , a ).
y_echo_1.mat: ( s), picco , - (con la cancellazione è eccessiva).- Non è FIR: la formula usa l'uscita passata, quindi è ricorsiva. , IIR, stabile perché (poli di modulo ).
Teoria: Sistemi LTI e convoluzione discretaUn sistema lineare e tempo-invariante (LTI) è completamente descritto dalla sua risposta impulsiva h[n]: y[n] = Σ x[k] h[n-k] = x[n] * h[n] (somma di convoluzione). Si ricava scomponendo x in impulsi traslati e usando linearità e invarianza. La convoluzione è commutativa, associativa e distributiva; una cascata di LTI equivale a un solo filtro con h = h1 * h2 e l'ordine non conta. Se x ha lunghezza N e h lunghezza L, y ha lunghezza N+L-1. Si calcola col metodo della finestra scorrevole o a tabella. I FIR sono LTI; altri sistemi (n·x[n], x[-n], x²) non lo sono.Sistemi LTI e convoluzione discreta →, Sistemi a tempo discreto e filtri FIRUn sistema a tempo discreto trasforma una sequenza x[n] in una sequenza y[n]. Il filtro FIR causale di ordine M calcola y[n] = Σ b_k x[n-k] (k = 0..M): è una media mobile pesata di L = M+1 campioni, la sua risposta impulsiva h[n] coincide con i coefficienti b_k e l'uscita ha supporto lungo N+M se l'ingresso è lungo N. La media mobile è un passa-basso che ritarda di M/2 campioni; la versione centrata non è causale. Gli schemi a blocchi usano solo moltiplicatori, sommatori e ritardi unitari, senza anelli (feed-forward).Sistemi a tempo discreto e filtri FIR →, Funzione di sistema, poli, zeri e stabilitàLa funzione di sistema H(z) è la trasformata zeta della risposta impulsiva: con ingresso z^n l'uscita è H(z) z^n. Per un FIR H(z) = Σ b_k z^{-k} è un polinomio con M zeri e M poli in z = 0; in generale H = B(z)/A(z) dall'equazione alle differenze. Sulla circonferenza unitaria H(e^{jω̂}) è la risposta in frequenza: |H| = prodotto delle distanze dagli zeri / prodotto delle distanze dai poli, quindi gli zeri bloccano frequenze e i poli le esaltano. Un LTI causale è BIBO stabile se e solo se tutti i poli hanno modulo < 1 (a meno di cancellazioni polo-zero); i FIR sono sempre stabili.Funzione di sistema, poli, zeri e stabilità →, Segnali e sistemi in Python - campioni, convoluzione e filtriAl calcolatore un segnale continuo è un vettore di campioni con un asse dei tempi. Con numpy si definiscono i segnali, si approssimano area ed energia con somme moltiplicate per il passo, si calcola la convoluzione continua con np.convolve(x, y)dt (asse = somma degli istanti iniziali), si simulano filtri con equazioni alle differenze e si verificano numericamente linearità e tempo-invarianza.Segnali e sistemi in Python - campioni, convoluzione e filtri →, Trasformata di Fourier discreta (DFT) e convoluzione circolareLa DFT di N campioni x[0..N-1] è X[k] = sum x[n] e^{-j2pi kn/N}, k = 0..N-1 (IDFT: x[n] = (1/N) sum X[k] e^{j2pi kn/N}); vede il segnale come periodico di periodo N e dà N campioni equispaziati della DTFT, cioè X(z) valutata sulle radici N-esime dell'unità. Se x ha durata M <= N non c'è aliasing temporale e lo zero-padding (N > M) infittisce solo i campioni della stessa DTFT. Proprietà: traslazione circolare, simmetria coniugata X[k] = X[N-k], prodotto = convoluzione circolare. La convoluzione circolare coincide con la lineare solo se N >= Dx + M - 1; altrimenti la coda si ripiega sull'inizio. La FFT calcola la DFT con (N/2) log2 N moltiplicazioni invece di N^2.Trasformata di Fourier discreta (DFT) e convoluzione circolare →.
Errori tipici:
- Confondere il filtro dell'eco (FIR) con il filtro che la rimuove (IIR).
- Usare lo zero padding nel bordo senza sapere che crea falsi bordi.
- Prendere il massimo dell'autocorrelazione dopo lo zero senza saltare il lobo centrale: non è l'eco.
- Scrivere senza il termine .
- Dire che la varianza è lineare: la sua media è lineare, la varianza no.