Programmazione

Sezione sulla programmazione Object Oriented in C++, C#, e Java

Informativa privacy e cookie

Visione robotica in Java parte II

Stella inattivaStella inattivaStella inattivaStella inattivaStella inattiva
 
::cck::16::/cck::
::introtext::

In questo articolo sulla visione robotica vedremo come realizzare un rilevatore di colori e come progettare una macchina agente (robot) che lo utilizza per seguire oggetti colorati.
Esempi di visione robotica in Java parte II
di Giuseppe Giannini

::/introtext::
::fulltext::

Nell'articolo scorso abbiamo visto come sia possibile elaborare un'immagine tramite trasformazioni e filtri, abbiamo visto la convoluzione matematica e come usare delle maschere mobili per elaborare i pixel al fine di ottenere delle informazioni sull'ambiente da un'immagine. Abbiamo visto come realizzare un componente di rilevamento movimenti confrontando due fotogrammi consecutivi in flusso video proveniente da una web cam, e abbiamo usato JMF per ottenere tale flusso video. Continuiamo, con questo articolo, il nostro viaggio, realizzando un componente che usando i concetti introdotti, prende in input uno stream video, e rileva oggetti di un determinato colore definito dall'utente, e quindi notifica tale evento alle classi interessate a gestirlo, nel nostro caso un robot il cui compito sarà inseguirlo.

Rilevatore di colori


Questo componente di visione robotica è più semplice da realizzare rispetto al precedente rilevatore di movimenti, e il suo semplice codice è riportato nel listato 1.

1 public void rilevaColore(int[][] img,int in_coloreRif,int in_soglia,int[] out_colorePrevalente,int[] out_numPix){

2      int r=0; int g=0; int b=0;

3      int r1,g1,b1;

4      r1=(int)in_coloreRif & 0xff;

5      g1=(int)(in_coloreRif>>8)& 0xff;

6      b1=(int)(in_coloreRif>>16)& 0xff;

7      int pixel;

8      out_numPix[0]=0;

9      for (int x  = 0; x < img.length; x++) {

10        for (int y  = 0; y < img[0].length; y++) {

11         pixel=img[x][y];

12          r=(int)pixel & 0xff;

13          g=(int)(pixel>>8)& 0xff;

14          b=(int)(pixel>>16)& 0xff;

15          if (Math.abs(r1 - r) <= in_soglia &&

16              Math.abs(g1 - g) <= in_soglia &&

17              Math.abs(b1 - b) <= in_soglia) {

18              out_numPix[0]++;

19              img[x][y]=0xffffff;

20          }

21        }

22      }

23      pixel=img[img.length/2][img[0].length/2];

24      r=(int)pixel & 0xff;

25      g=(int)(pixel>>8)& 0xff;

26      b=(int)(pixel>>16)& 0xff;

27      out_colorePrevalente[0]= 255 << 24 |

                            (int) (b & 0xff) <<16 |//b

                            (int) (g & 0xff) <<8  |//g

                            (int) (r & 0xff);      //r

28    }

 

Il funzionamento di tale componente è il seguente: dato un colore di riferimento, e una porzione dell'immagine da analizzare, il componente notificherà la presenza di oggetti il cui colore è simile a quello di riferimento. Il grado di similarità è definibile tramite una soglia passata come parametro (int in_soglia) dell'utente. Il metodo, come il precedente rilevatore di movimenti, elabora il flusso video generato da una web cam, (per comodità ogni fotogramma di tale stream viene convertito in una matrice di interi "int[][] img" come mostrato nell'articolo scorso).

Si differenzia dal rilevatore di movimenti perché non usa la convoluzione per tracciare i bordi degli oggetti rilevati, e non esegue il confronto tra due fotogrammi consecutivi, ma si limita a scorrere l'intera immagine listato 1 riga 9 (o una porzione definita dall'utente), e per ogni pixel (r,g,b) effettua un confronto con il colore da rilevare (il colore di riferimento definito dall'utente "int in_coloreRif"), servendosi in tale confronto della soglia di similarità. Se la differenza in valore assoluto tra i due pixel è compresa nella soglia allora il pixel letto viene considerato simile al colore di riferimento (Listato1 riga 15) e viene conteggiato (una variabile viene incrementata Listato1 riga 18) come pixel ricercato. Come potete notare, una particolarità di tale metodo, è che non restituisce nulla, tuttavia avendo passato come parametro in input la variabile "int[] out_numPix" per riferimento e non per valore, le modifiche al suo contenuto sono visibili anche al ritorno dalla funzione. Praticamente e come se avessi scritto in C int* out_numPix. Abbiamo anche colto l'occasione per simulare l'uso dei puntatori in Java, dove passare int[] significa passare per riferimento il contenuto dell'array puntato dall'int[]. Se alla fine della scansione dell'immagine il numero di pixel conteggiati è sufficientemente elevato, cioè superiore ad un limite definito dall'utente, la classe notifica l'evento a tutti gli interessati, utilizzando lo stile dei listener che abbiamo visto nell'articolo scorso.

E' molto delicata la questione della soglia. Prima di tutto diciamo a cosa ci serve un valore di soglia. Immaginiamo di voler rilevare gli oggetti di colore blu, per esempio una palla, e immaginiamo di trovarci in una stanza illuminata da luci al neon. Il colore blu percepito dal robot in questo ambiente non è identico allo stesso oggetto posizionato in un'altra stanza illuminata da una diversa luce per esempio la luce diretta del sole. Sebbene si tratti dello stesso oggetto colorato, i valori numerici del colore variano al variare della luminosità ambientale, quindi occorre utilizzare un valore di soglia per introdurre una certa "tolleranza alle variazioni di luminosità" e asserire così che i colori simili al colore di riferimento possono essere accettati entro tale intervallo. Come il componente di rilevamento movimenti, anche questa classe ha la necessità di notificare eventi.
Gli eventi che vogliamo notificare sono:
⦁ coloreRilevato (RilevatoreColoreEvento)
⦁ coloreNonRilevato(RilevatoreColoreEvento)

Possiamo come di consueto realizzare un'interfaccia software (un listener) che dichiari tali metodi e lasci l'implementazione alle classi interessate a gestirlo.
Chi implementa questi metodi esegue il proprio codice al verificarsi di tali eventi per effetto del polimorfismo.
Nel prossimo paragrafo vedremo come strutturare un'applicazione robotica. Occorre sottolineare che l'approccio dell'applicazione che usa il rilevatore di colori al posto del rilevatore di movimenti (visto nell'articolo scorso) può essere riprogettato (ottimizzandolo da un certo punto di vista).


Infatti, nell'applicazione per rilevare i movimenti, l'approccio non è ad "eventi" (un comportamento ad eventi si ha quando il robot esegue direttamente un'azione se si verifica un certo evento) ma è di tipo "monitoraggio continuo", cioè il robot esegue in continuazione azioni, leggendo il proprio vettore di caratteristiche X periodicamente oppure può decidere di non leggerlo per un pò perchè sta eseguendo un'azione che può influenzare una certa percezione. Quindi il vettore di cratteristiche viene costruito separatamente da dei thread che ricevono degli eventi. Vedremo tra breve cos'è un vettore di caratteristiche nel dettaglio, per ora consideriamolo come una qualche rappresentazione dell'ambiente del robot. Nel rilevatore di colori invece i thread oltre a costruire il vettore di caratteristiche a seguito di notifiche da parte dei rilevatiri di colori, propagano tali eventi notificandoli direttamente robot che va a leggere il vettore aggiornato. In ogni caso occorre sviluppare politiche di sincronizzazione sul vettore condiviso tra gli scrittori (i rilevatori di colori) e i lettori (il decisore delle azioni). Questo secondo approccio è più adatto a questo tipo di percezione (colore), è va bene per esempio in applicazioni in cui il robot deve seguire un bersaglio (di un certo colore) che si muove. Se invece usassimo questo stile di programmazione anche con i rilevatori di movimento, il robot muovendosi riceverebbe eventi continui perché l'intero ambiente gli parrebbe in movimento (movimento relativo), e quindi non sarebbe adatto. Suggerisco per chi fosse interessato ad approfondire tali tematiche a riflettere sui sistemi di visione dei ragni e di alcune rane.

 

Agenti Stimolo-Risposta


Prima di procedere, vorrei presentarvi un possibile modello architetturale del nostro robot. Un agente S-R
(Stimolo Risposta) è una macchina che reagisce in un determinato modo se e quando si verifica un certo evento percepito nell’ambiente in cui si trova. Per esempio, il nostro robot deve essere capace di rilevare un oggetto in movimento che entra nel suo campo visivo, e se di colore blu inseguirlo. Per ottenere questo risultato possiamo rappresentare l’ambiente del robot (nel nostro caso l’immagine che percepisce) come un vettore di quattro elementi denotati con s1, s2, s3, s4 che, rispettivamente rappresentano la porzione sinistra dell’immagine, quella centrale superiore quella centrale inferiore e quella destra. Quindi ciò che facciamo è suddividere l’immagine percepita in celle che abiliteremo a percepire eventi sotto il controllo di thread dedicati. Chiamiamo questo vettore input sensoriale (Figura1).

figura1
Il valore di queste celle può essere un intero, che rappresenta l’intensità di colore prevalente nella rispettiva porzione dell’immagine, oppure informare sulla presenza in quella porzione di un oggetto in movimento. Ciò che il robot deve fare, ora che ha un vettore di percezione, è usare una funzione che selezioni un’azione appropriata alla configurazione dell’input sensoriale. In alcune applicazioni, l’input sensoriale viene analizzato separatamente dalla funzione di selezione azione, e in questo caso viene generato un nuovo vettore intermedio tra percezione e azione. Tale vettore denotato con X prende il nome di vettore di caratteristiche, e va pensato ad un livello di astrazione maggiore rispetto al vettore sensoriale. Contiene di solito descrizioni categoriche di qualità dell’ambiente, oppure altri tipi di dati scelti dal progettista. Per esempio un elemento del vettore potrebbe essere di tipo booleano e rispondere con true o false alla caratteristica dell’ambiente “Esiste un oggetto in movimento di colore blu di fronte?”. Con una sola domanda catturiamo tre diverse percezioni; movimento, colore e posizione. Il vettore di caratteristiche viene generato dall’elaborazione del vettore sensoriale, e la sua produzione è visibile nella Figura 2.

Figura2


Nel nostro esempio vogliamo che il robot segua oggetti in movimento di colore blu. Il vettore X di caratteristiche potrebbe contenere quindi le seguenti categorie di input sensoriali.

1. x1 :: Oggetto blu in movimento a destra
2. x2 :: Oggetto blu in movimento a sinistra
3. x3 :: Oggetto blu in allontanamento (quando si rileva in s2)
4. x4:: Oggetto blu in avvicinamento (quando si rileva in s3)
5. x5 :: Oggetto blu fermo
6. x6 :: Oggetto non blu in movimento

La funzione di azione=f(X) potrebbe essere:

⦁ avanti se x3=1
⦁ indietro se x4=1
⦁ null se x5=1 or x6=1
⦁ sinistra se x1=1
⦁ destra se x2=1

Questo semplice modello fornisce una base per programmare macchine che, nonostante non siano dotate di intelligenza artificiale, possono comportarsi in maniera anche molto complessa, specie se invece di limitarsi ad eseguire azioni l’agente invoca sottoprogrammi o routine.
Un modo per selezionare l’azione appropriata da eseguire in base ad una configurazione del vettore di caratteristiche è il sistema a produzione. In tale sistema vi sono un certo numero di regole scritte nella forma:
à .
La regola è , mentre, l’azione che sarà compiuta se la regola viene soddisfatta è .
Nella figura 3 potete osservare lo schema del nostro robot modellato sulle macchine S-R con sistema a produzione.

figura3


In questa figura le transizioni sono numerate, e una dettagliata spiegazione di ciò che accade è riportata di seguito:
1. Il sensore legge dall’ambiente ciò che è abilitato a rilevare, per esempio una web-cam rileva una o più immagini, un sensore di distanza una certa misura ecc.
2. L’agente non riceve direttamente in input gli output dei sensori, bensì un vettore di attributi che hanno senso ai fini del suo funzionamento. Ciò oltre a renderlo indipendente dal sensore, lo rende indipendente dall’ambiente, cioè lo stesso programma scritto per funzionare in un simulatore può funzionare nel mondo reale.
3. Il vettore di caratteristiche va pensato come una lista di qualità del tipo: “esiste un oggetto blu nell’ambiente”? vero/falso oppure “ci sono oggetti in movimento rilevati a nord?” vero/falso. L’agente non sa quale sensore abbia riempito tali campi del vettore, né se l’abbia fatto scandendo il mondo reale o una rappresentazione simulata.
4. Tale vettore viene dato ad un comportamento che l’agente può caricare anche a run-time. Il comportamento è la funzione di selezione azione.
5. L’azione potrebbe essere un comando o una funzione.

 

Il robot completo


Ora che abbiamo tutti gli ingredienti costruiamo il componente che pilota il robot in base alle percezioni della webcam, uno schema completo dell'architettura del progetto è visibile nella figura 4.

 

figura4

 

L'applicazione client che risiede sul robot dipende dalla scheda elettronica che controlla i servocomandi del robot stesso. Di seguito vi mostro un frammento di codice in linguaggio basic, che personalmente trovo inadatto ad applicazioni serie. Tuttavia e molto diffuso su schede per robot economiche, mentre le più recenti schede che supportano java hanno lo svantaggio di essere più costose.

'{$STAMP BS2}
comando var byte
main:
debug "valore iniziale comando=",comando,CR
serin 16,16468,[comando]
debug "Ricevuto comando=",comando,CR
if comando = "1" then cameraSu
if comando = "2" then cameraGiu
if comando = "3" then cameraSx
if comando = "4" then cameraDx
goto main
sub cameraGiu: pulsout 13,250 return
sub cameraSu: pulsout 13,1000 return
sub cameraDx: pulsout 12,250 return
sub cameraSx: pulsout 12,1000 return

In questo frammento di codice, l'applicazione non fa altro che eseguire i comandi ricevuti da un host (il nostro pc) tramite la porta seriale (oppure tramite un ricevitore radio seriale). Sul host risiede l'applicazione server che esegue i rilevatori di colori e dopo aver elaborato il vettore di caratteristiche prodotto scrive sulla porta seriale il comando giusto in base alla sua configurazione. Nell'articolo precedente abbiamo mostrato anche un frammento di codice per scrivere byte sulla porta seriale da Java, se ve lo siete perso, potete contattarmi.

In figura 5 infine, è possibile notare un'istanza di un rilevatore colori in azione con il pannelo di controllo aperto per settare la soglia e il colore target da rilevare.

 

figura5

 

 

 Conclusioni


Non è stato semplice farci entrare tutto in questi due articoli, e molte cose sono state semplificate. Tuttavia gli spunti non credo che vi manchino per costruire vostre applicazioni di visione robotica, e se le abbinate alle applicazioni di navigazione robotica su grafi (viste in passato sempre su questa rivista) potrete davvero realizzare macchine interessanti.

::/fulltext:: ::cck::16::/cck::

Comments powered by CComment

© 2018 sito prototipale studio di GiuseppeGi