Summary: Nell'elaborazione di segnali catturati da sensori c'è necessità di effettuare alcune operazioni ricorrenti, quali il filtraggio o la sogliatura.
Note: Your browser may not currently support MathML. See our browser support page for additional details. You can always view the correct math in the PDF version.
L'isteresi
è quella proprietà dei sistemi che li fa reagire con un certo
ritardo alle forze ad essi applicate. Ad esempio, all'atto di
sedersi su una poltrona imbottita, la relazione tra forza e
compressione del cuscino può essere di forma
isteretica, ed evidenza di ciò si può avere
dall'impronta lasciata nel momento in cui ci si rialza.
Nel design dell'interazione, l'isteresi viene introdotta
deliberatamente per ritardare le azioni del sistema rispetto
all'accadere di eventi. Per esempio, il click del mouse in una
certa area può attivare un menu, la cui scomparsa viene però
ritardata rispetto all'uscita da quella regione, in modo da
consentire all'utente di esplorare le voci del menu anche con
percorsi del mouse imperfetti.
Nei regolatori automatici, quale è ad esempio il termostato
per la regolazione di temperatura, è spesso presente un
elemento isteretico per evitare commutazioni spurie, ad
esempio dovute ai rimbalzi degli interruttori. Il
termostato, in particolare, attiva il bruciatore quando la
temperatura scende al di sotto di una soglia
(threshold) boolean acceso e due
soglie basso e alto:
if (acceso) {
if (signal < basso) {
// operazioni di spegnimento
acceso = false;
}
}
else //spento
if (signal > alto) {
// operazioni di accensione
acceso = true;
}
Si scriva un programma Processing che disegni la curva della pressione acustica catturata dal microfono. A basse pressioni la curva sia disegnata in verde. Quando la pressione acustica supera una soglia superiore la curva deve diventare di colore rosso, e tornare ad essere verde quando torna al di sotto di una soglia inferiore.
Una soluzione che fa uso della libreria audio Minim è la seguente:
import ddf.minim.*;
AudioInput in;
int WIDTH=400, HEIGHT=200;
int x;
int level, prevLevel;
color colorLine=color(0,255,0);
boolean acceso=false;
int basso=80;
int alto=120;
Minim minim;
void setup()
{
size(WIDTH, HEIGHT);
minim = new Minim(this);
// get a line-in from Minim: mono, 512 sample buffer
// default sampling rate is 44100, default bit-depth is 16
in = minim.getLineIn(Minim.MONO, 512);
}
void draw()
{
if (x==0) {
background(0);
stroke(255,0,0); line(0,HEIGHT-alto,WIDTH,HEIGHT-alto);
stroke(0,255,0); line(0,HEIGHT-basso,WIDTH,HEIGHT-basso);
}
stroke(colorLine);
level = int(in.left.level()*HEIGHT*10);
line(x, HEIGHT - prevLevel, (x+1)%WIDTH, HEIGHT - level);
x = (x+1)%WIDTH;
prevLevel = level;
if (acceso) {
if (level < basso) {
colorLine = color(0, 255, 0);
acceso = false;
}
}
else //spento
{
if (level > alto) {
colorLine = color(255, 0, 0);
acceso = true;
}
}
}
void stop()
{
// always stop Minim
minim.stop();
super.stop();
}
Si modifichi il codice di Exercise 1 in modo da ottenere la curva di livello di un noise gate. Quando il livello scende al di sotto della soglia verde, si applica uno smorzamento che gradualmente forza il segnale ad essere inudibile. Quando il livello supera la soglia rossa, si applica una amplificazione che riporta il segnale al livello originale.
Nella soluzione seguente il segnale viene abbattuto,
quando scende al di sotto della soglia inferiore,
mediante moltiplicazioni successive per un coefficiente
gDown, il cui valore (sempre minore di uno)
viene impostato in base al tempo desiderato di
smorzamento ed al frame rate. Lo smorzamento si
considera esaurito quando il segnale di ingresso viene
ad essere moltiplicato per gUp, di valore sempre maggiore di uno.
import ddf.minim.*;
AudioInput in;
int WIDTH=400, HEIGHT=200;
int x;
int level, prevLevel, levelG;
color colorLine=color(0,255,0);
boolean acceso=false;
int basso=30;
int alto=50;
float damp=1.0;
float gain = 1.0;
Minim minim;
float decayTime = 10.0; // in seconds
float raiseTime = 2.0;
float gDown = pow(10,-2/frameRate/decayTime);
float gUp = pow(10,2/frameRate/raiseTime);
void setup()
{
println("gDown = " + gDown + "gUp = " + gUp);
size(WIDTH, HEIGHT);
minim = new Minim(this);
// get a line-in from Minim: mono, 512 sample buffer
// default sampling rate is 44100, default bit-depth is 16
in = minim.getLineIn(Minim.MONO, 512);
}
void draw()
{
if (x==0) {
background(0);
stroke(255,0,0); line(0,HEIGHT-alto,WIDTH,HEIGHT-alto);
stroke(0,255,0); line(0,HEIGHT-basso,WIDTH,HEIGHT-basso);
}
stroke(colorLine);
level = int(in.left.level()*HEIGHT);
gain = gain*damp;
//println(gain);
if (gain > 1.0) gain = 1.0;
if (gain < 0.01) gain = 0.01;
levelG = int(level*gain);
line(x, HEIGHT - prevLevel, (x+1)%WIDTH, HEIGHT - levelG);
x = (x+1)%WIDTH;
prevLevel = levelG;
if (acceso) {
if (level < basso) {
colorLine = color(0, 255, 0);
damp = gDown;
acceso = false;
}
}
else //spento
{
if (level > alto) {
colorLine = color(255, 0, 0);
damp = gUp;
acceso = true;
}
}
}
void stop()
{
// always stop Minim
minim.stop();
super.stop();
}
Gli esempi di sogliatura, isteresi, e gating visti nel caso dell'elaborazione del segnale audio sono di utilità generale per qualsiasi flusso di segnale proveniente da un sensore e, in particolare, anche nell'elaborazione di segnale video.
Nella maggior parte delle applicazioni che trattano flussi di
segnale provenienti da sensori, c'è la necessità di addolcire
(smoothing) il profilo dei segnali
stessi, in modo da evitare picchi improvvisi e rumore a rapida
variabilità. Ciò si può fare con i filtri.
Il più semplice filtro di smoothing è il filtro di media , il quale
restituisce ad ogni istante il valor medio tra due campioni
temporalmente contigui del segnale di ingresso. Interpretando
i segnali nel dominio delle frequenze, l'operazione di
smoothing corrisponde ad un filtraggio passa-basso, cioè ad
una attenuazione delle frequenze elevate. Infatti, le
variazioni repentine corrispondono a componenti di frequenza
elevata.
Le capacità di smoothing del filtro di media sono limitate, e
per ottenere degli smoothing più decisi (maggiore attenuazione
delle alte frequenze) bisogna fare medie pesate di un numero
maggiore di campioni del segnale di ingresso. Un filtro molto
utile allo scopo è il filtro del secondo ordine simmetrico.
Qualunque sia l'ordine del filtro, la sua realizzazione come
media pesata di campioni del segnale di ingresso può essere
effettuata convenientemente appoggiandosi alla realizzazione a
buffer
circolare. In questo caso, la lettura avverrà da un
certo numero di celle contigue a quella puntata da
OUT, queste letture saranno pesate per i
coefficienti del filtro, e il puntatore IN
coinciderà con il puntatore OUT.
Si modifichi il codice di Exercise 1 pre-filtrando il segnale di livello con un filtro del secondo ordine simmetrico.
Nel codice qui proposto si possono sperimentare diversi
livelli di smoothing, variando i valori di
a0 e a1. Più questi valori
sono vicini, maggiore è l'entità dello smoothing. Per
non alterare con il filtraggio l'ampiezza generale del
segnale è opportuno che il doppio di a0,
sommato ad a1, dia come risultato
import ddf.minim.*;
AudioInput in;
int WIDTH=400, HEIGHT=200;
int x;
int level, prevLevel;
color colorLine=color(0,255,0);
boolean acceso=false;
int basso=80;
int alto=120;
Minim minim;
float GAIN = 10;
int BUFLEN = 5;
float[] buffer = new float[BUFLEN];
int bufPoint=2, bufPoint1=1, bufPoint2=0;
float a0 = 0.3, a1 = 0.4; //smoothing
//float a0 = 0, a1 = 1; // no smoothing
void setup()
{
size(WIDTH, HEIGHT);
minim = new Minim(this);
// get a line-in from Minim: mono, 512 sample buffer
// default sampling rate is 44100, default bit-depth is 16
in = minim.getLineIn(Minim.MONO, 512);
}
void draw()
{
if (x==0) {
background(0);
stroke(255,0,0); line(0,HEIGHT-alto,WIDTH,HEIGHT-alto);
stroke(0,255,0); line(0,HEIGHT-basso,WIDTH,HEIGHT-basso);
}
stroke(colorLine);
buffer[bufPoint] = in.left.level();
level = int((a0*buffer[bufPoint] + a1*buffer[bufPoint1] + a0*buffer[bufPoint2])*GAIN*HEIGHT);
bufPoint = (bufPoint+1)%BUFLEN; bufPoint1 = (bufPoint1+1)%BUFLEN; bufPoint2 = (bufPoint2+1)%BUFLEN;
line(x, HEIGHT - prevLevel, (x+1)%WIDTH, HEIGHT - level);
x = (x+1)%WIDTH;
prevLevel = level;
if (acceso) {
if (level < basso) {
colorLine = color(0, 255, 0);
acceso = false;
}
}
else //spento
{
if (level > alto) {
colorLine = color(255, 0, 0);
acceso = true;
}
}
}
void stop()
{
// always stop Minim
minim.stop();
super.stop();
}
Come si può dedurre dalla sperimentazione proposta in Exercise 3, prendendo medie pesate di pochi campioni di segnale l'effetto di smoothing non è macroscopico. Per avere un filtraggio più radicale bisogna usare filtri di ordine elevato (elaborazione di molti campioni di segnale) ovvero ricorrere a filtri ricorsivi.
Un filtro è un oggetto che, preso un segnale di ingresso, produce un segnale di uscita. Quando la produzione di un campione del segnale di uscita si ottiene elaborando campioni precedenti del segnale di uscita stesso (oltre che del segnale di ingresso) si dice che il filtro è ricorsivo. La ricorsività consente filtraggi radicali a complessità modeste. Ad esempio, il filtro del primo ordine rappresentato dall'equazione alle differenze
Si modifichi il codice di Exercise 1 pre-filtrando il segnale di livello con un filtro ricorsivo del primo ordine.
Si provino diversi valori di a nel codice qui proposto, mantenendo il vincolo di stabilità.
import ddf.minim.*;
AudioInput in;
int WIDTH=400, HEIGHT=200;
int x;
int level, prevLevel;
color colorLine=color(0,255,0);
boolean acceso=false;
int basso=80;
int alto=120;
Minim minim;
float GAIN = 10;
float currentLev;
float prevLev = 0;
float a = 0.95; // the closer to 1, the smoother the signal
void setup()
{
size(WIDTH, HEIGHT);
minim = new Minim(this);
// get a line-in from Minim: mono, 512 sample buffer
// default sampling rate is 44100, default bit-depth is 16
in = minim.getLineIn(Minim.MONO, 512);
}
void draw()
{
if (x==0) {
background(0);
stroke(255,0,0); line(0,HEIGHT-alto,WIDTH,HEIGHT-alto);
stroke(0,255,0); line(0,HEIGHT-basso,WIDTH,HEIGHT-basso);
}
stroke(colorLine);
currentLev = a*prevLev + (1 - a)*in.left.level();
prevLev = currentLev;
level = int(currentLev*GAIN*HEIGHT);
line(x, HEIGHT - prevLevel, (x+1)%WIDTH, HEIGHT - level);
x = (x+1)%WIDTH;
prevLevel = level;
if (acceso) {
if (level < basso) {
colorLine = color(0, 255, 0);
acceso = false;
}
}
else //spento
{
if (level > alto) {
colorLine = color(255, 0, 0);
acceso = true;
}
}
}
void stop()
{
// always stop Minim
minim.stop();
super.stop();
}
Particolare importanza rivestono i filtri ricorsivi del secondo ordine, i quali fungono da modello per la realizzazioni di risonanze. Una risonanza elementare si manifesta come oscillazione smorzata. Se i coefficienti del filtro del secondo ordine sono opportunamente scelti in modo da produrre una risposta ai limiti della stabilità, quello che si ottiene è una realizzazione dell'oscillatore, alternativa a quelle proposte in oscillazioni, ritardi, e fluttuazioni del tempo.