Programmazione

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

Stella inattivaStella inattivaStella inattivaStella inattivaStella inattiva
::cck::79::/cck::
::introtext::

Tutorial di Programmazione Avanzata in ambiente Linux

In questo Tutorial video si esplora uno studio per combinare un programma scritto in C (che esegue la logica di un qualche algoritmo) con un programma scritto in Java che implementa separatamente una gui per gli input del programma C.

Guarda il VIDEO TUTORIAL COMPLETO

Argomenti trattati nel video tutorial:

  1. Programmazione concorrente in C uso delle funzioni fork() per creare figli del programma corrente
  2. Programmazione avantata in Ambiente Linux, uso di exec() per sovrascrivere un programma in memoria figlio di un programma C con una istanza di un programma .class che mostra una Gui per l'input utente
  3. Sincronizzazione tra padre e figlio mediante la funzione waitpid()
  4. Accesso a File in C 
  5. Creazione di una GUI in Java mediane l'implementazione di una classe che estende JFrame
  6. Gestione degli eventi dei componenti di una gui mediante l'implementazione di un oggetto ActionListener e del relativo metodo actionPerformed
  7. Accesso a File da Java in particolare OutputStrem
::/introtext::
::fulltext::

 

Ciò si realizza tramite un esempio di programmazine concorrente in C nella quale il programma scritto in C si duplica in due programmi concorrenti, il Padre e il Figlio che condividono un file e si sincronizzano su di esso in modo opportuno.

Il pradre si mette in attesa del completamento del Figlio prima di avviare il suo algoritmo.

Il Figlio esegue una classe java e si fa sovrascrivere dall'eseguibile .class nel contesto di menoria, pur restando con il pid creato dal padre, e raccoglie l'input utente inviandolo in un file condiviso con il padre prima di uscire.

Il padre quindi riprende la sua esecuzione leggendo gli input prodotti dal Figlio e li elabora.

Codice Sorgente:

il file C compilare con gcc prog.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
/*
@author Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.
Questo programma crea due processi concorrenti, il padre e un figlio.
Il padre resta in attesa che il figlio raccolga degli input dell utente ed esca, dopodichè
il padre uscito dall'attesa elabora tali input per generare un output.
Il figlio per raccogliere gli input dall'utente si sovrascrive eseguendo una GUI scritta in java
che mostra una interfaccia grafica con caselle di testo e pulsanti dinamicamente create a run time per
consentire all'utente di immettere dei valori, dopodichè salva tali valori in file condiviso con il padre.
Scopo del programma è offrire agli utenti del laboratorio di informatica uno strumento e un tutorial
per integrare GUI scritte in java in algoritmi scritti in C
*/
int main(int argc, char** argv){
    pid_t pid_figlio;
    pid_t pid;
    int status;
    FILE *in;
    
    //creo un figlio
    pid_figlio=fork();
    pid=getpid();
    if(pid_figlio!=0){
        
        printf("[%d] Padre:> attendo l'output del figlio...\n",pid);
        waitpid(pid_figlio, &status, WUNTRACED | WCONTINUED);
        in=fopen("file.dat","r");
        int a,b;
        char str[100];
        fscanf(in,"%d %d %s",&a,&b,&str);
        
        printf("[%d] Padre:> output ricevuto... inizio elaborazione\n",pid);
        printf("Risultato è: %d+%d=%d e la stringa è %s\n",a,b,(a+b),str);
    }else{
        
        in=fopen("input.dat","w");
        fprintf(in,"10 2");
        
        printf("[%d] Figlio:> attendo l'input dell'utente\n",pid);
        //lancio la GUI JAVA con parametri per label e campi
        execlp("java","java","InputForm","Inserisci un valore","Secondo input","Inserisci una stringa",0);
        
    }
    
}


  

 Il file Java. Compilare con javac InputForm.java

/**
@author Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.
Classe che implementa una GUI invocabile anche da un programma C esterno
E' stata realizzata per scopi didattici e di tutorial per gli utenti del laboratorio di informatica
E' utilizzabile invocandola da un programma C per richiedere l'imput all'utente non da riga di comando
ma da una Form con campi di testo e pulsanti definibili dal file C e visualizzabili nella GUI Java.
Può essere usata dal sorgente C creando in esso un processo concorrente (figlio) e sul quale
il programma principale (padre) resta in attesa finche il figlio non ha raccolto tutto l'input
dell'utente tramite la GUI Java.
Quindi con una fork si genera un figlio che sovrascrive il proprio codice con quello della GUI
invocandola con una exec(nomeProgramma,argomenti) in particolare usare la seguente:
execlp("java","java","InputForm","Prima Etichetta Valore","Seconda etichetta","ecc ecc",0);
Il padre quindi resta in attesa mediante una wait(pid_figlio) finché il figlio non ha prodotto
l'output
Padre e Figlio condivideranno il file che questa classe InputForm produrrà alla pressione del tasto
OK

*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

public class InputForm extends JFrame implements ActionListener{
JPanel pannelloComandi;
JButton okButton;
String output;
JTextField[] textField;

public InputForm(String[] args){
super("Input Form");
this.setSize(640,480);
pannelloComandi=new JPanel();
pannelloComandi.setLayout(new BoxLayout(pannelloComandi,BoxLayout.Y_AXIS));
textField=new JTextField[args.length];
for(int i=0;i<args.length;i++){
JPanel rigo=new JPanel();
rigo.setLayout(new BoxLayout(rigo,BoxLayout.X_AXIS));
rigo.add(new JLabel(args[i]));
textField[i]=new JTextField();
rigo.add(textField[i]);
pannelloComandi.add(rigo);
}
okButton=new JButton("OK, Invia Input e Chiudi Finestra");
okButton.addActionListener(this);
pannelloComandi.add(okButton);
this.add(pannelloComandi);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}

public static void main(String[] args){
InputForm f=new InputForm(args);
}

public void actionPerformed(ActionEvent e){
output="";
for(int i=0;i<textfield.length;i++){
output+=textField[i].getText()+" ";
}
try{
FileOutputStream out=new FileOutputStream("file.dat");
PrintStream s=new PrintStream(out);
s.println(output);
s.close();
out.close();
}catch(IOException ec){
ec.printStackTrace();
}
//System.out.println(output); 
System.exit(1);
}
}
::/fulltext::

Write comment (0 Comments)

Stella inattivaStella inattivaStella inattivaStella inattivaStella inattiva

 

Questo programma simula la dinamica di un fluido non comprimibile. Nel Fluido agiscono delle forze di velocità (implementate da un campo di flusso) che alimentano delle correnti interne al fluido. Tali forze possono essere generate dall'utente muovendo il mouse nel fluido o da degli emettitori che possono essere aggiunti a piacere dall'utente.

Il simulatore, (ancora in pre beta e verrà aggiornato) mostra l'effetto che le correnti hanno su un colorante che l'utente può aggiungere nel fluido e vederlo diluirsi e spostarsi nel fluido in funzione della densità, della viscosità e di altre caratteristiche.

Ho scritto sia il codice della simulazione che traduce il modello matematico (i link ai modelli matematici e agli articoli scientifici usati sono in coda a questo post) in un algoritmo funzionante scritto in Java e eseguibile anche direttamente sul web (vedi seguito per usare il simulatore direttamente nel browser)

Ho implementato anche il codice per consentire all'utente di interaggire con la simulazione usando sia il mouse che comandi testuali. A tal scopo ho creato una classe apposita che implementa una console testuale che può essere anche usata in altri progetti (in seguito farò un post solo per la console).

Ho inoltre implementato anche diversi dipi di renderizzatori, usando il polimorfismo del linguaggio Java ho progettato una interfaccia RenderizzatoreFluido che le classi che vogliono renderizzare il fluido devono implementare (vale lo stesso anche per i ModificatoriFluido). Quelle che ho creato consentono di disegnare:

1) il colorante che si diluisce nel fluido animandosi seguendo le correnti generate sia dall'utente che da emettitori draggabbili

2)le correnti stesse sotto forma di vettori orientati nel verso della velocità del campi di fluisso che governa il fluido

2.5)le correnti visualizzate in modo raster dove il colore giallo è il massimo della velocita, e miscelazioni di colore particolari rendono graficamente l'effetto delle onde di propagazione delle forze nel fluido

3)un renderizzatore artistico mostra il colorante sotto forma di retino

 

Il programma è scritto in Java e di seguito potete usarne una versione per il web e in coda trovate il listato completo.

Clicca nella immaggine sottostante per fare il focus al programma.

 

USO:

  • Muovi il mouse tenendo premuto il tasto destro per aggiungere colorante.
  • Muovi il mouse tenendo premuto il tasto sinistro per aggiungere velocita e miscelare il colorante nel fluido.
  • Premi c Per aprire la console testuale

I comandi della console sono i seguenti: 

man (apre la guida) close man (la chiude)

dens //disenga le densità del colorante

vel vet //disegna le velocità come vettori di direzione

vel sfum //disegna le velocità come sfumature di colore

ret //disegna un retino artistico

color //aggiunge un emettitore colore in posizione casuale

force //aggiunge un emettitore di forza basata sul perlinNoise

vortice //aggiunge un emettitore circolare per creare un vortice

author //mostra informazioni sull'autore

close console //chiude la console

esempio per visualizzare le velocità come vettori digita:

vel vet 

e poi premi INVIO

Per disattivare il renderizzatore ridigita vel vet 

 

NOTA: i renderizzatori vengono applicati in pila uno sull'altro quindi se ad esempio hai digitato dens e poi vel sfum il renderizzatore dens non si vedra perché starà sotto. L'ordine ideale per i renderizzatori è il seguente: dens poi vel sfum e sopra a tutti vel vet

 Guida sugli emettiroti colore (comando: color), velocità (comando: force) e vortice (comando: vortice).

Questi emettitori possono essere manipolati con il mouse dopo essere stati aggiunti. Potete spostarli con il mouse usando il pulsante sinistro e potete ridimensionarli usando il pulsante destro (decrementa)<----->(incrementa).

L'emettitore colore incorpora già un emettitore forza lineare che viene abilitato se fai click sul colore e poi orienti il getto in una direzione.

L'emettitore forza usa il PerlinNoise per creare una movimento non lineare delle correnti emesse.

L'emettitore vortice crea una emettitore di forze circolari orientate verso il centro dell'emettitore che generano un effetto vortice, puoi ridimensionalro con il mouse e maggiore è il raggio maggiore è la forza attrattiva.

Alcuni ritagli schermata del programma.

simFluid2

simFluid3

vortice11

 

 

 

 

 

 

 

Guarda un video tutorial su come si usa il simulatore:  Avvia Video

 

 

Codice Sorgente:  

FluidSimulation:

public class FluidSimulation implements Gi_ConsoleObserver{
public Fluido f;
Console console;
int iterazioni;
public ArrayList<DisegnatoreFluido> disegnatori=new ArrayList<DisegnatoreFluido>();
public ArrayList<AggiornatoreFluido> aggiornatori=new ArrayList<AggiornatoreFluido>();
DisegnatoreDensitaFluido disegnatoreDensita;
DisegnatoreRetinoFluido disegnatoreRetino;
DisegnatoreVelocitaFluido disegnatoreVelocitaVettoriali;
DisegnatoreVelocitaSfumateFluido disegnatoreVelocitaSfumate;
public ArrayList<String> log=new ArrayList<String>();
boolean apriGuida=false;
public FluidSimulation(float dt,float diffusione,float viscosita,int size,int scala,int iterazioni){
f=new Fluido( dt, diffusione, viscosita, size,scala);
this.iterazioni=iterazioni;
disegnatori.add((disegnatoreDensita=new DisegnatoreDensitaFluido()));
aggiornatori.add(new MousePaint());
aggiornatori.add(new EmettitoreColorante((size*scala)/2,(size*scala)/2));
aggiornatori.add(new EmettitoreForza((size*scala)/2,(size*scala)/2));
disegnatoreRetino=new DisegnatoreRetinoFluido();
disegnatoreVelocitaVettoriali=new DisegnatoreVelocitaFluido();
disegnatoreVelocitaSfumate=new DisegnatoreVelocitaSfumateFluido();
console=new Console(10,size*scala-12*13,12,10);
console.addObserver(this);
}
public void nextStep(){
f.step(iterazioni);
}
public String getName(){
return "Simulation";
}
public void disegna(){
for(DisegnatoreFluido d : disegnatori){
f.disegna(d);
}
for(AggiornatoreFluido a : aggiornatori){
f.aggiorna(a);
}
this.nextStep();
drawHelp();
}
public String analizzaComando(String command){
String messageForUser=null;
if(command.equals("ret")){
//esegui il codice del comando localmente
if(disegnatori.contains(disegnatoreRetino)){
disegnatori.remove(disegnatoreRetino);
messageForUser="render retino rimosso retype ret per aggiungerlo";
}else{
disegnatori.add(disegnatoreRetino);
messageForUser="render retino aggiunto retype ret per rimuoverlo";
}
}else if(command.equals("vel vet")){
//esegui il codice del comando localmente
if(disegnatori.contains(disegnatoreVelocitaVettoriali)){
disegnatori.remove(disegnatoreVelocitaVettoriali);
messageForUser="render velocità rimosso retype vel per aggiungerlo";
}else{
disegnatori.add(disegnatoreVelocitaVettoriali);
messageForUser="render velocità aggiunto retype vel per rimuoverlo";
}
}else if(command.equals("vel sfum")){
//esegui il codice del comando localmente
if(disegnatori.contains(disegnatoreVelocitaSfumate)){
disegnatori.remove(disegnatoreVelocitaSfumate);
messageForUser="render velocità rimosso retype vel sfum per aggiungerlo";
}else{
disegnatori.add(disegnatoreVelocitaSfumate);
messageForUser="render velocità aggiunto retype vel sfum per rimuoverlo";
}
}else if(command.equals("dens")){
//esegui il codice del comando localmente
if(disegnatori.contains(disegnatoreDensita)){
disegnatori.remove(disegnatoreDensita);
messageForUser="render densità rimosso retype dens per aggiungerlo";
}else{
disegnatori.add(disegnatoreDensita);
messageForUser="render densità aggiunto retype dens per rimuoverlo";
}
}else if(command.equals("color")){
int x=int(random(1,width-1));
int y=int(random(1,height-1));
messageForUser="Emettitore aggiunto in pos("+x+":"+y+") usa il mouse per spostarlo/ridimensionarlo/orientarlo";
//esegui il codice del comando localmente
aggiornatori.add(new EmettitoreColorante(x,y));
}else if(command.equals("force")){
int x=int(random(1,width-1));
int y=int(random(1,height-1));
messageForUser="Emettitore aggiunto in pos("+x+":"+y+") usa il mouse per spostarlo";
//esegui il codice del comando localmente
aggiornatori.add(new EmettitoreForza(x,y));
}else if(command.equals("close console")){
messageForUser="command ok. Console closing.";
//esegui il codice del comando localmente
console.deactivate();
apriGuida=false;
}else if(command.equals("man")){
messageForUser="Guida aperta. type close man per chiuderla";
//esegui il codice del comando localmente
//console.deactivate();
apriGuida=true;
//drawHelp(apriGuida);
}else if(command.equals("close man")){
messageForUser="command ok. Guida chiusa";
//esegui il codice del comando localmente
//console.deactivate();
apriGuida=false;
//drawHelp(apriGuida);
}
return messageForUser;
}
public void elabKey(char key,int keyCode){
console.elabKey(key,keyCode);
}
public void drawHelp(){
int x=1;
int y=10;
int alt=16;
pushStyle();
fill(0,0,0);
text("press c for open console and type: man for open manual",x,y);
if(key=='c'){
console.activate();
}
if(apriGuida){
pushStyle();
noStroke();
fill(0,0,255,100);
rect(x,y,500,alt*12,5,5,5,5);
fill(255,255,255);
text("Manuale Comandi Simulazione:",x,y+=alt);
text("Usa Mouse: trascina con PULSANTE DESTRO per aggiungere COLORE nel fluido",x,y+=alt);
text("Usa Mouse: trascina con PULSANTE SINISTRO per aggiungere FORZA nel fluido",x,y+=alt);
text("COMANDI TESTUALI:",x,y+=alt);
text("> close man [INVIO] chiudi la guida",x,y+=alt);
text("> close console [INVIO] chiudi console",x,y+=alt);
text("> vel vet [INVIO] abilita/disabilita renderizzatore velocità vettoriali",x,y+=alt);
text("> vel sfum [INVIO] abilita/disabilita renderizzatore velocità raster",x,y+=alt);
text("> dens [INVIO] abilita/disabilita renderizzatore densità colorante",x,y+=alt);
text("> ret [INVIO] abilita/disabilita renderizzatore artistico tipo retino",x,y+=alt);
text("> color [INVIO] aggiunge un emettitore di colori",x,y+=alt);
text("> force [INVIO] aggiunge un amettitore di forza",x,y+=alt);
text("Programmato da: GiuseppeGi < www.giuseppegi.it >",x,y+=alt);
popStyle();
}
popStyle();
console.display();
}
}

Altre classi che ho scritto il cui codice è disponibile su richiesta contattandomi:

Gi.Console

Gi.ConsoleOBserver

Gi.MouseGUI

Gi.MousePaint

Gi.EmettitoreColorante

Gi.EmettitoreForza

Gi.Fluido

Gi.DisegnatoreVelocitaSfumateFluido

Gi.DisegnatoreDensitaFluido

Gi.DisegnatoreRetinoFluido

Gi.Main

 

Modelli Matematici e riferimenti e riconoscimenti per lavori di ricerca su cui si basa la mia implementazione

Navier–Stokes equations

https://en.wikipedia.org/wiki/Navier%E2%80%93Stokes_equations

Jos Stam, "Fluidi stabili", SIGGRAPH 1999: Atti di conferenza di 
Navier-Stokes

http://www.karlsims.com/fluid-flow.html

https://www.mikeash.com/thesis/

 

 

Write comment (0 Comments)

Stella inattivaStella inattivaStella inattivaStella inattivaStella inattiva
::cck::62::/cck::
::introtext::

Il progetto, attualmente in fase pre realese, con solo scopo di studio e nessun fine commerciale, vuole simulare l'evoluzione di creature virtuali ispirate alle stelle marine e immerse in un ambiente virtuale 3D dotato di fonti di cibo e di predatori, mediante l'utilizzo degli algoritmi genetici e della programmazione evolutiva. Nel seguito del post potete usare on line il simulatore, monitorare l'evoluzione della popolazione di stelle marine, modificare alcuni paramentri come il mutation rate, la finestra di accoppiamento, i pesi per il fitnes dell'algoritmo e altro.

Programma scritto in C# con il motore grafico Unity3D versione personal. Non scopi commerciali ma solo studio.

::/introtext::
::fulltext::

Attendi l'avvio del programma poi interagisci con il mouse usando i pannelli dell'interfaccia per cambiare visuale da 3D panoramica a 3D Top a 3D segui.

Puoi usare le interfacce per aprire il "monitor Stelle" e osservare verso quale tipo di Stella l'evoluzione converge oppure modificare i paramentri dagli altri pannelli di controllo ecc.

 

Introduzione

In questo articolo si esplora una tecnica di programmazione, che trae ispirazione dell'evoluzione naturale delle forme di vita organica.

Questa tecnica di "programmazione evolutiva" ricerca una "soluzione"che adatta se stessa per migliorarsi rispetto a un certo "problema da risolvere".La soluzione al problema quindi si ottiene dopo un certo  numero di "generazioni di soluzioni" che hanno percorso una certa evoluzione per risolvere il problema dato (il quale problema a sua volta potrebbe anche non essere statico ma variare nel tempo e il sistema fornisce semprenuove soluzioni che si adattano anche a tale cambiamento).

Questo modo di procedere fa emergere una "evoluzione selettiva delle soluzioni" che si manifesta quando si scelgono, tra tutte le soluzioni di una data generazione, solo quelle il cui errore rispetto al problema da risolvere è il minore possibile, o in parole analoghe, solo quelle che si avvicinano di più alla soluzione "giusta". Dato che nella maggior parte dei casi non si conosce a priori una soluzione ottima, valutare quanto le soluzioni trovate dall'Algoritmo Genetico siano vicine a quella ottima può essere complicato, ma esiste un metodo con il quale si può assegnare un "fattore di idoneità" di una soluzione a risolvere il problema che prende il nome di "fitness" che permette di valutare quanto, ad ogni generazione, le soluzioni trovate siano adatte. e quelle con fitness maggiore vengono ricombinate tra loro per creare una nuova generazione di soluzioni possibili, che produrranno a loro volta, soluzioni sempre più adatte a risolvere il problema. Intuitivamente si comprende che è un modello semplificato dell'evoluzione darwiniana delle specie, le quali migliorano se stesse in funzione delle caratteristiche del proprio ambiente per sopravvivere in esso.

Questa tecnica di programmazione si chiama Programmazione Genetica o anche Programmazione Evolutivaedun Algoritmo Genetico è quel metodo con il quale si può implementare una tale evoluzione delle soluzioni a un dato problema.

Se sostituiamo quindi la parola "soluzione" con "creatura" e le parole "problema da risolvere" con "sopravvivere nell'ambiente in cui la creatura è immersa" ciò che otteniamo è un sistema nel quale creature virtuali si evolvono per adattarsi al proprio ambiente virtuale. Creature dotate in un proprio DNA digitale che codifica il genotipo della soluzione ovvero una descrizione binaria delle caratteristiche della creatura. Tale descrizione numerica farà emergere, a seconda di come la creatura digitale decodificherà tali geni per assemblarsi in una creatura fisica, un fenotipo anche molto più complesso della semplice rappresentazione numerica. Tale fenotipo potrà esprimersi sia in caratteristiche fisiche sia in caratteristiche comportamentali. L'osservazione di tale evoluzione virtuale all'interno di un sistemadi simulazione di vita artificiale però non vuole fornire le risposte ai quesiti sull'evoluzione del mondo reale, cioè al perché nel mondo tangibilecerte creature adottano determinate soluzioni che ritroviamo in natura. Piuttosto tale sistema virtuale vuole fornire una chiave di lettura e una consapevolezza delfunzionamento dei meccanismi e degli equilibri che qualsiasi sistema (reale o virtuale) tende a raggiungere spontaneamente quando possiede una certa dinamicità e una certainterattività tra le sue componenti, per bilanciare le proprie variabili quando queste sono dipendenti le une dalle altre (es. Predatori,cibo,risorse, Stelle Marineecc).

In un sistema inerme, statico, non vi può essere evoluzione. Ma in un sistema dinamico, composto da svariate entità indipendenti, che possono anche completamente ignorare il funzionamento delle une verso le altre, ma che hanno ognuna propri scopi, proprie regole interne,propri comportamenti locali, specifiche caratteristiche sensoriali e motorie;In un sistema dinamico come questo, in cui tutte le entità vengono immerse e lasciate interagire tra di loro si potrà osservare il manifestarsi spontaneo non solo di una certa evoluzione ma anche di certi "comportamentiglobali emergenti" non programmati alla sorgentema che emergono spontanei in quanto somma dei "singoli comportamenti locali" delle entità nella simulazione, in parole più dirette un sistema del genere farà emergere una certa selezione evolutiva delle creature virtualie un determinato comportamento, sia locale delle singole entità sia globale di tutto il sistema,in quanto frutto delle interazioni delle sue singole parti in modi anche non previsti dal programmatore o dall'osservatore del sistema.

Questo sistema ovviamente, come già detto, non è finalizzato a risolvere o svelare i quesiti sull’evoluzione del mondo tangibile, in quanto occorrerebbe simulare ad un livello di dettaglio tale che la simulazione stessa diventerebbe un mondo intrigato come il mondo reale che simula, ma questo sistema vuole indagare su quali siano e come sono relazionati tra loro i meccanismi che regolano l'evoluzione in una direzione piuttosto che un'altra. In particolare in questo studio, si cerca di indagare su quale equilibrio raggiunga una creatura virtuale, che si ispira a una creatura del mondo tangibile come una stella Marina, in un ambiente dotato di cibo limitato, predatori e topologia del fondale. Ciò che ci incuriosisce è verificare quale sia la configurazione migliore che le stelle marine virtuali autonomamente trovano evolvendosi, in termini di numero di punte, forma delle punte (lunghezza e spessore), dimensione del disco interno e del disco esterno, e ci interessa anche analizzare e studiare i rapporti tra tali caratteristiche, per esempio emerge spontanea una sezione aurea tra il disco interno e il disco esterno? O emerge spontanea tra l’angolo delle punte o la loro lunghezza e spessore?

Quindi nel seguito illustreremo come progettiamo una Stella Marina Virtuale, il suo DNA digitale, la sua popolazione e l'algoritmo genetico che farà evolvere la specie,immersa in un ambiente marino virtuale, dotato di cibo, predatori e topologia simile a un fondale marino tangibile.

L'algoritmo genetico che implementeremo per far evolvere popolazioni di Stelle Marine Virtuali ci mostrerà, seadottando per evoluzioneuna certa forma e un certo numero di punte, riuscirà a sopravvivere in quel ambiente e proveremo a indagare se la formae il numero di punte possano avere una qualche ragione geometrica particolare per determinare il successo di una tipologia di stella (per esempio una se una stella a 5 punte con determinate proporzioni auree tra raggio interno e raggio esternorispetto ad altre emerga spontanea nel simulatore come è accaduto in natura)o se il numero di punte che emergeranno sia il naturalecompromesso tra beneficio che produce una punta e costo in termini di energia per gestirla, e in questo caso, cosi come in natura, variando la tipologia dell'ambiente, varierà anche soluzione adottata dalla stella marina virtuale. Nei prossimi paragrafi verrà quindimodellata l'entità Stella Marina Virtuale scendendo nel dettaglio, descrivendo anche il codice che traduce il genotipo nel fenotipo, e parleremo di comedalla forma e dal numero delle punte e del disco interno della stella possano emergere caratteristiche come velocità movimento, forza apertura cibo, velocità di nutrimento, consumo di energia, capacità di sopravvivere a attacchi stancando una delle punte, capacita percettive ecc.Verranno illustrati anche gli altri elementi del simulatore e il funzionamento dell'algoritmo genetico.

::/fulltext::

Write comment (0 Comments)

Stella inattivaStella inattivaStella inattivaStella inattivaStella inattiva
::cck::45::/cck::
::introtext::

copertinaMeshDeform

::/introtext::
::fulltext::

Studio di classi che consentono la modifica dei vertici una mesh a run time utile nei casi in cui un'oggetto 3D come ad esempio un veicolo deve simulare la deformazione della sua carrozzeria a seguito di una collisione oppure un fluido deve simulare la deformazione della sua superficie a seguito del passaggio di un altro oggetto per poi ritornare nella forma iniziale.

Attendere il caricamento dell'applicazione 3D seguente e usare il mouse per modificare le superfici alla pressione del tasto SX su di esse.

In coda il codice sorgente delle classi scritte. 

 


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent (typeof(MeshFilter))]
[RequireComponent (typeof(MeshCollider))]

public class MeshDeformer : MonoBehaviour {

Mesh deformingMesh;
MeshCollider meshCollider;

Vector3[] originalVertices, displacedVertices;
//velocità di spostamento dei vertici
Vector3[] vertexVelocity;
public float springForce = 20f;
public float damping=5f;

private float uniformScale = 1f;
//if true return original form else no
public bool isElastic = true;

// Use this for initialization
void Start () {
deformingMesh = this.GetComponent<MeshFilter> ().mesh;
meshCollider = this.GetComponent<MeshCollider> ();
originalVertices = deformingMesh.vertices;
displacedVertices = new Vector3[originalVertices.Length];
for (int i = 0; i < displacedVertices.Length; i++) {
displacedVertices [i] = originalVertices [i];
}
vertexVelocity=new Vector3[displacedVertices.Length];
}

// Update is called once per frame
void Update () {
updateMeshVertex ();
}

public void AddDeformingForce(Vector3 puntoImpatto,float forza){
//il puntoImpatto ha coordinale globali devo convertirle nelle coordinate locali dell'oggetto
puntoImpatto=this.transform.InverseTransformPoint(puntoImpatto);

for (int i = 0; i < displacedVertices.Length; i++) {
this.addDeformingForceSingleVertex (i, puntoImpatto, forza);
}

Debug.DrawLine (Camera.main.transform.position, transform.InverseTransformPoint(puntoImpatto));
}

protected void addDeformingForceSingleVertex(int idVerticeCorrente, Vector3 puntoImpattoMesh, float forzaImpatto){
//calcolo il vettore tra il vertice della mesh e il punto di impatto quale differenza tra i due
Vector3 vettorePuntoToVertice = displacedVertices [idVerticeCorrente] - puntoImpattoMesh;
//scalo questo vettore sulla base di un eventuale ridimensionamento della mesh
vettorePuntoToVertice *= uniformScale;
//maggiore è la distanza tra il vertice e il punto di impatto minore è la forza di spostamento
float forzaAttenuata= forzaImpatto - (1f + vettorePuntoToVertice.sqrMagnitude);
float velocita = forzaAttenuata * Time.deltaTime;
//calcolo la velocità e la direzione di spostamento del vertice in base alle normali e conservo questa forza nel vettore
vertexVelocity[idVerticeCorrente]+=vettorePuntoToVertice.normalized*velocita;
}

/**Dal vettore delle velocità dei vertici sposto i vertici della mesh deformata*/
protected void updadeVertexPosition(int idVertice){
Vector3 velocita = this.vertexVelocity [idVertice];
Vector3 spostamento = displacedVertices [idVertice] - originalVertices [idVertice];
//lo springForce offre una sesistenza all'impatto riportando i vertici in posizione originale
velocita -= spostamento * springForce * Time.deltaTime;
//per prevenire una oscillazione eterna si forzano i vertici al damping nella posizione originale
velocita *= 1f - damping * Time.deltaTime;

vertexVelocity [idVertice] = velocita;
displacedVertices[idVertice]+= velocita * Time.deltaTime;
}

protected void updateMeshVertex(){
//uniformo la scala dell'oggetto in modo che oggetti ridimensionati funzionino in scala
uniformScale=transform.localScale.x;

for (int i = 0; i < displacedVertices.Length; i++) {
updadeVertexPosition (i);
}

//assegno i vertici deformati alla mesh
deformingMesh.vertices = displacedVertices;
deformingMesh.RecalculateNormals ();

//se la mesh non è elastica resta nella nuova forma quindi azzero il vettore velocita vertici e assegno la nuova forma diventa quella definitiva
if (!isElastic) {
originalVertices = displacedVertices;
vertexVelocity=new Vector3[displacedVertices.Length];
this.meshCollider.sharedMesh = deformingMesh;


}
}
}


 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MeshDeformerCollisionInput : MonoBehaviour {
//forza spostamento
public float force = 100.0f;

// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {

}

void OnCollisionEnter(Collision c){
Vector3 puntoMedioImpatto = Vector3.zero;
Vector3 puntoMedioNormal = Vector3.zero;

foreach (ContactPoint cp in c.contacts) {
Debug.DrawRay (cp.point,cp.normal,Color.white);
puntoMedioImpatto += cp.point;
puntoMedioNormal += cp.normal;

}


MeshDeformer meshDeformer=this.GetComponent<MeshDeformer>();
if (meshDeformer) {

meshDeformer.AddDeformingForce (puntoMedioImpatto, force);
Debug.DrawRay (puntoMedioImpatto,puntoMedioImpatto+c.relativeVelocity,Color.yellow);
}
}
}


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/**questa classe implementa l'input da parte dell'utente per deformare una mesh
in alternativa invece dell'input utente si può implementare una classe che prende l'imput da una collisione
della mesh con un altra per deformarla.
Si può aggangiare questo script a una camera e usare il RayCasting per deformare una mesh in base all'input del mouse*/

public class MeshDeformerUserInput : MonoBehaviour {

//forza spostamento
public float force = 10.0f;
//scostamento spostamento
public float forceOffset=0.1f;

// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {
if (Input.GetMouseButton (0)) {
this.gestisciInput ();
}
}

public void gestisciInput(){
Ray raggioInput = Camera.main.ScreenPointToRay (Input.mousePosition);
RaycastHit oggettoColpito;
if(Physics.Raycast(raggioInput,out oggettoColpito)){
MeshDeformer meshDeformabile=oggettoColpito.collider.GetComponent<MeshDeformer>();
if(meshDeformabile!=null){
Vector3 point=oggettoColpito.point;
point += oggettoColpito.normal*forceOffset;
meshDeformabile.AddDeformingForce(point,force);
}else{
print(oggettoColpito.collider.name+" non ha uno script mesh deformabile\n");
}
}
}
}

 


::/fulltext::

Write comment (0 Comments)

Stella inattivaStella inattivaStella inattivaStella inattivaStella inattiva
::cck::17::/cck::
::introtext::

In quest'articolo vedremo come realizzare applicazioni di visione robotica, realizzeremo una classe riutilizzabile che preso in input uno stream video rileva oggetti in movimento,e notifica tale evento ad altre classi interessate a gestirlo, come un robot.

Nella seconda parte dell'articolo vedremo invece un ulteriore algoritmo di visione robotica che ci permetterà di rilevare oggetti di un determinato colore.
Esempi di visione robotica in Java parte I
di Giuseppe Giannini

::/introtext::
::fulltext::

In questo articolo ci occuperemo dell'elaborazione delle immagini per estrapolarne informazioni che un robot può utilizzare nei suoi compiti. Vedremo alcuni algoritmi di visione robotica, e come possono essere implementati in Java nel dettaglio. Prima di procedere però è opportuno richiamare alcuni concetti fondamentali, e precisamente che cos'è e come viene rappresentata un'immagine.

 

Un'immagine f è una matrice n*m di punti colorati. Un punto colorato (su un monitor) prende il nome di pixel.
Perché un pixel ci appaia colorato occorre che sia definito da almeno tre componenti di colore separate che, quando fuse tra loro, generino tutti i colori visibili. Tali componenti sono il rosso, il verde, il blu. In alcuni casi un ulteriore valore viene mantenuto per rappresentare la trasparenza (alfa) di quel pixel rispetto allo sfondo, ma per semplicità lo tralasceremo per ora. Per rappresentare un pixel quindi ci serve un vettore di c=3 elementi o un altro tipo di contenitore. Nel caso decidessimo di usare un vettore di 3 elementi, f diventa una matrice tridimensionale n*m*c. Se invece vogliamo usare un contenitore alternativo, possiamo considerare che una singola componente di colore può essere agevolmente rappresentata da un byte, e che disponendo di un intero a 24 bit possiamo inserirvici tre ottetti, ognuno rappresentante una componente diversa. Se prendiamo questa decisione, allora possiamo rappresentare f come una matrice bidimensionale n*m di interi più agevole e conveniente da mantenere, ma dobbiamo ricordarci di estrarre da ogni intero i tre byte per le tre componenti rosso, verde e blu (da ora in poi r,g,b), vedremo a tal scopo come usare l'operatore shift. Un'ulteriore rappresentazione di f potrebbe essere quella di mantenerla in un array monodimensionale di byte, dove ogni tre byte consecutivi rappresentano un pixel. In questo caso dobbiamo mantenere separata l'informazione della larghezza dell'immagine e usare una semplice formula per convertire l'indice dell'array nella coordinata x,y di un pixel. La scelta su quale rappresentazione usare dipende dallo scopo. Nel nostro caso per rendere semplice la spiegazione useremo ,per il momento, una rappresentazione di f come matrice n*m.
Fatte queste preliminari considerazioni, nel paragrafo seguente vengono approfonditi alcuni concetti fondamentali sull'elaborazione di un'immagine prima di procedere con la spiegazione degli algoritmi.

Trasformazioni e Filtri
Un’immagine può contenere molte informazioni, e per estrarle, occorre elaborarla sottoponendola a delle trasformazioni e applicandovi dei filtri.
Una trasformazione T è un operatore che prende in input un'immagine f e restituisce in output una nuova immagine g.

 

g(x,y)=T[f(x,y)]

L’operatore T viene progettato a seconda del risultato che si vuole ottenere. Esistono trasformazioni che operano nel dominio delle frequenze di f, e trasformazioni che operano nel dominio spaziale di f cioè sui suoi pixel. Qui vedremo operatori T del secondo tipo.
Le tecniche utilizzate per la trasformazione dell’immagine f sono diverse, ne elenco le principali, e sottolineo quelle che affronteremo in questo articolo per la realizzazione degli algoritmi di visione artificiale.

⦁ Pixel Operation:

g(x,y)=T[f(x,y)]

Il pixel elaborato g(x,y) è funzione dell’operatore T che agisce su un singolo pixel di f.

⦁ Local Operation:

g(x,y)=T[f(x1,y1)...(xm,ym)]

Il pixel elaborato g(x,y) è funzione di T che agisce su di un intorno del pixel f(x,y)

⦁ Object Operation

G=T[f(xi,yj)!(xm,ym) E Oggetto]

G è un oggetto e non un'immagine. G è generato dall'operatore T che opera su un sottoinsieme di pixel di f che definiscono un oggetto appartenente a una categoria.

⦁ Global Operation

g(x,y)=T[f(x,y)...f(xn,ym)]

Il pixel g(x,y) è ottenuto da tutta l'immagine f.

Per quanto riguarda la Local Operation, occorre approfondire il concetto di intorno di un pixel, e di come può essere usato da T nelle elaborazioni su f nel dominio spaziale.
Sia = f(x,y) un pixel. Un suo intorno può essere definito dai pixel a esso adiacenti per esempio nella tabella 1

=f(x-1,y-1) =f(x,y-1) =f(x+1,y-1)
= f(x-1,y) = f(x,y) = f(x+1,y)
= f(x-1,y+1) = f(x,y+1) = f(x+1,y+1)

possiamo osservare un possibile intorno.
L'operatore T, nel caso della Local Operation, usa un filtro che applica a tale intorno per generare il nuovo pixel g(x,y).
Un filtro è una matrice W in cui ogni elemento rappresenta un peso da associare al rispettivo pixel dell'intorno di f. Nella tabella 2 è mostrato un esempio di filtro costituito da una maschera n*n.

 

w0 w1 w2
w3 w4 w5
w6 w7 w8


Tale maschera viene fatta scorrere su tutta l'immagine f centrandola sul pixel f(x,y) e applicandola al suo intorno. Per ogni pixel di f viene quindi generato un pixel in g. Tale pixel viene settato al valore R, ottenuto come segue:

<img null>


O in forma esplicita:

formula

Questo procedimento è noto con il nome di Convoluzione Matematica, e a seconda dei valori che si assegnano ai pesi si può elaborare l'immagine in maniera diversa. Per esempio il filtro per rilevare i contorni di un oggetto è visibile in Tabella 3.

-1 0 -1
0 4 0
-1 0 -1

 


Mentre un filtro per aumentarne il contrasto è visibile in tabella 4:

-1 0 -1
0 5 0
-1 0 -1

 


Gli algoritmi che vedremo tra breve adottano, di base, questa tecnica.

JMF
Per scrivere un componente di visione robotica, ci serve prima di tutto un flusso video da elaborare. Java Media Framework è un package Java che consente, tra le altre cose, di riconoscere ed utilizzare nelle proprie applicazioni Java (stand-alone e Applet) una web-cam e catturarne il flusso video. Una volta scaricato il pacchetto da internet (www.sun.com), l'installazione è molto semplice sotto qualsiasi sistema operativo. L'unica procedura da eseguire manualmente, se si utilizzano ambienti di sviluppo integrati, è quella di settare il path dove i file *jar (contenenti le classi di JMF) sono stati installati, per poterle cosi importare nei propri sorgenti.
Per esempio in JBuilder bisogna, a tal scopo, settare le proprietà del progetto in corso e creare una nuova libreria contenente tali archivi jar.
Concettualmente uno stream video è come un torrente di byte che dalla sorgente (la web-cam) arriva alla destinazione (il render GDI) come visibile in Figura 1.

Figura1


Noi possiamo realizzare classi che prendono in input tale flusso di byte, lo elaborano o lo analizzano e lo riversano in output. Sempre in Figura 1 la classe intermedia è il nostro primo componente. Tali classi per potersi inserire nel flusso video devono implementare l'interface Effect, che ha molti metodi (per i quali rimando alle api JavaDoc di JMF), il metodo da implementare che ci interessa particolarmente è :

public int process(Buffer inBuffer,Buffer outBuffer)

I parametri inBuffer e outBuffer sono rispettivamente la nostra traccia video sorgente e destinazione. Possiamo ottenere facilmente un array monodimensionale di byte contenenti, in ogni elemento, una singola componente di colore di un pixel invocando il metodo inBuffer.getData(). Ogni tre elementi dell'array restituito viene definito un singolo pixel. Il seguente frammento di codice mostra come ottenere un array monodimensionale di singole componenti di colore e come convertirlo in una matrice bidimensionale di pixel.

public static int[][] buffer2IntArray(Buffer data){
byte[] dati=(byte[])data.getData();
RGBFormat formatoVideo=(RGBFormat)data.getFormat();
Dimension size=formatoVideo.getSize();
int[][] returnData=new int[size.width][size.height];
int i=0;
for(int y=size.height-1;y>=0;y--){
for(int x=0;x<size.width;x++){
returnData[x][y]=255 << 24 |
(int) (dati[i] & 0xff) << 16 |
(int) (dati[i + 1] & 0xff) << 8 |
(int) (dati[i + 2] & 0xff);
i+=3;
}
}
return returnData;
}

Le dimensioni (width,height) dell'immagine vengono restituite dalla classe RGBFormat. Da notare che nel ciclo for (più esterno) l'immagine viene capovolta per poterla vedere correttamente nel render, e un pixel viene costruito usando i valori di tre elementi consecutivi dell'array dati usando l'operatore shift <<.
Nel seguente frammento di codice è invece possibile vedere un esempio di un possibile uso delle varie classi di JMF ottenere uno stream nel quale inserire nostre classi che elaborano inBuffer e lo copiano su outBuffer. Il seguente esempio non è compilabile come invece lo sono gli altri esempi di quest'articolo perché, per motivi di spazio, sono stati omessi i blocchi try/catch per gestire le eccezioni ed è finalizzato a mostrarvi le classi che occorrono, i cui dettagli d'uso sono dettagliatamente documentati nel Java Doc di JMF.

String cam = "vfw:Logitech USB Video Camera:0";
CaptureDeviceInfo dev=CaptureDeviceManager.getDevice(cam);
MediaLocator ml = dev.getLocator();
DataSource ds = Manager.createCloneableDataSource(
Manager.createDataSource(ml)
);
Processor p = Manager.createProcessor(ds);
p.configure();
/*se abbiamo realizzato degli effeti da applicare
al flusso video implementando l'interface Effecs possiamo
inserirli nel flusso come segue*/
if(listaEffetti.size()>0){
Codec codec[] = new Codec[listaEffetti.size()];
for(int i=0;i<codec.length;i++){
codec[i]=(Effect)listaEffetti.elementAt(i);
}
videoTrack.setCodecChain(codec);
}

In questo frammento il nome del driver della web-cam è una stringa statica, ma potete usare un file di proprietà o XML per caricarlo dinamicamente, l'unica vera parte interessante e quella che riguarda l'inserimento di effetti nel flusso video utilizzando la classe Codec che è un array di riferimenti a oggetti di tipo Effect. L'array Codec va inserito nella catena delle tracce video, ma noi nel seguente paragrafo non ci limiteremo a realizzare una classe che elabora un effetto, bensì che analizza due fotogrammi consecutivi, rileva gli oggetti che si sono mossi, e li evidenzia disegnandogli intorno un mirino, o visualizzandoli su uno sfondo nero, e che lancia eventi ad altri oggetti interessati a riceverli, come per esempio un programma che pilota un robot remoto.

 

Rilevatore Di Movimenti


Il componente riutilizzabile che andiamo a sviluppare, permette di rilevare e marcare oggetti in movimento. Chiameremo questo componente RilevatoreMovimenti e lo inseriamo nella lista degli effetti della traccia video (Figura1) che abbiamo visto nel paragrafo precedente. RilevatoreMovimenti deve implementare l'interface Effect per prendere in input tramite il metodo processes un flusso video. Userà anche un valore di soglia (settato dall'utente tramite interfaccia grafica che possiamo facilmente realizzare con Javax.Swing) e tra breve vedremo a cosa serve. Nel metodo process che dobbiamo implementare, il rilevatore estrapola, in tempo reale, dal flusso video due immagini consecutive e se nelle due immagini (o in porzioni di esse) si verifica un cambiamento dei pixel, superiore alla soglia, il componente notifica l'evento a tutti gli oggetti interessati. Gli oggetti interessati alla notifica (per esempio robot o applicazioni di video sorveglianza) dovranno quindi implementare una nostra interfaccia software di comunicazione che permetta il passaggio dell'evento generato. Tale interface la chiameremo RilevatoreMovimentiListener, e passerà un oggetto che chiameremo MovimentoEvento. nel seguente frammento di codice possiamo vedere come sia semplice tale realizzazione:

public interface RilevatoreMovimentiListener{
public void MovimentoRilevato(MovimentoEvento);
public void MovimentoNoNRilevato(MovimentoEvento);
}

La notifica dell'evento avviene, quindi, tramite la tecnologia dei listener di Java. Gli oggetti interessati alla notifica possono essere altre classi (per esempio controller di robot) che reagiscono all'evento in qualche modo personalizzato.
Infatti l'interfaccia software (RilevatoreMovimentiListener) fornisce le firme dei metodi che vengono invocati dal nostro RilevatoreMovimenti quando questi intercetta un oggetto in movimento (MovimentoRilevato) e quando nulla si muove (MovimentoNoNRilevato). Chi implementa tali metodi ereditati dall'interfaccia, e si inserisce nella lista dei listener del RilevatoreMovimenti, può eseguire il proprio codice quando l'evento viene notificato per effetto del polimorfismo. Possiamo dotare la classe, inoltre, di un pannello di comando per regolare manualmente alcuni parametri come la soglia e monitorare ciò che viene rilevato (un esempio è in Figura2d). Questo è lo scheletro del componente, ora passiamo al funzionamento dell'algoritmo di rilevamento movimenti nei suoi dettagli, il primo passo che compie è calcolare le intensità delle due immagini consecutive catturate dal flusso video nel modo riportato nel Listato1, dove potete notare anche la formula matematica per tale calcolo.
Nella formula che precede il Listato1, n rappresenta la dimensione di f e si ottiene da n=larghezza(f)*altezza(f) ; mentre c rappresenta il numero di componenti di colore che formano un singolo pixel (nel nostro caso c=3 perché un pixel è formato da tre elementi di colore distinti come accennato nell'introduzione ); e infine non rappresenta, in questo caso, un singolo pixel dell'immagine f ma la somma dei valori rosso verde e blu che compongono tale pixel.
Se rappresentiamo l'immagine f come una matrice tridimensionale, possiamo ottenere p nel seguente modo:

Dove x e y sono gli indici della posizione del pixel e l'indice della componente di colore. In alternativa, sapendo che per rappresentare una componente di colore bastano 8 bit (un byte) per avere ben gradazioni distinte, e che un intero di 24 bit può contenere 3 ottetti (byte), possiamo continuare a gestire f come una matrice bidimensionale, ma dobbiamo, in fase d'implementazione, lavorare sui singoli byte per ottenere il valore corretto di p. Nel Riquadro 1 è possibile notare un esempio per estrarre da un intero, che rappresenta un pixel, le tre componenti di colore distinte badando a non perdere l'informazione sul segno.
In seguito, per avere p come valore di intensità del pixel si sommeranno i valori r,g,b. I valori r,g,b, inoltre, devono essere tutti unsigned ed è per questo che l'AND binario è necessario, ancora nel Riquadro 1 si può notare l'uso dell'operatore.
L'algoritmo dopo aver calcolato le intensità delle due immagini consecutive da confrontare, calcola un valore di correzione ottenuto dalla differenza in valore assoluto delle due intensità, visibile ancora verso la fine del Listato 1. Tale valore di correzione viene utilizzato dall'algoritmo di rilevamento movimenti per ritoccare i valori rosso, verde e blu di un singolo pixel per poi utilizzarli per generare il nuovo pixel nell'immagine in output g(x,y). La formula per la generazione del nuovo pixel è la seguente e viene realizzata sempre nel Listato 1 tramite la funzione java.lang.Math.sqrt che implementa quanto segue:

Dove r,g,b sono le componenti di colore di un singolo pixel opportunamente corrette con il valore di correzione precedentemente calcolato. Tale correzione è avvenuta nel Listato 1 nel modo seguente:

⦁ componente colore = 0 se componente colore < (del fattore di correzione)
⦁ componente colore = componente colore - (fattore di correzione) altrimenti

Ora che l'algoritmo ha il risultato R dell'elaborazione, lo confronta (vedi frammento codice nel Listato 2) con il valore di soglia che l'utente gli ha passato come parametro in input. Se R > soglia allora il nuovo pixel g(x,y) verrà colorato di bianco, altrimenti gli sarà assegnato proprio il valore di R (naturalmente R sarà numericamente minore del bianco=0xffffff). Abbiamo così un'immagine in memoria (e non verrà visualizzata così com'è adesso) che contiene la seguente informazione:
⦁ Tutti i pixel bianchi sono pixel che rappresentano oggetti immobili nella scena;
⦁ Tutti i pixel di colore diverso (numericamente minori del bianco=0xffffff) sono pixel di oggetti che si sono mossi.
A questo punto l'algoritmo deve eseguire un'ultima operazione; contare il numero di pixel che risultano cambiati e, se tale numero, è superiore alla soglia notificare l'evento agli oggetti registrati nel proprio listener.
Tale conteggio è molto semplice e viene eseguito considerando il fatto che tutti i pixel g(x,y) di colore minore del bianco sono cambiamenti nelle immagini confrontate. Basta contare quindi tali pixel (e magari cambiare il colore degli altri in nero per far risaltare solo i pixel di colore R vedi Figura 2b). Opzionalmente possiamo usare contemporaneamente la convoluzione matematica ed evidenziare l'oggetto che si è spostato colorandone l'interno di un colore particolare, magari di blu o di giallo, per comunicare anche visivamente tale evento e disegnarvi poi un rettangolo o mirino di contenimento, vedi Figura 2c e il codice nel Listato 2.
La conseguenza di questa procedura è molto interessante. Avremo, infatti, un'immagine g totalmente nera se non vi sono oggetti in movimento nel video (tutti i pixel bianchi appartenenti ad oggetti immobili vengono settati a nero). Mentre avremo degli aloni luminescenti intorno e all'interno degli oggetti in movimento (i loro pixel hanno valore R Figura 2b), che però non si muovono abbastanza da superare il valore di soglia, mentre, contemporaneamente, avremo oggetti colorati di blu e con contorni bianchi (conseguenza della convoluzione Listato 2) se il loro movimento supera il valore di soglia (Figura 2c). In quest'ultimo caso l'evento verrà notificato a tutte le classi interessate tramite la seguente procedura:

for(int i=0;i<listererSize;i++){
RilevatoreMovimenti x=(RilevatoreMovimenti)listener[i];
MovimentoEvento evt=new MovomentoEvento( ... );
x.movimentoRilevato(evt);
}

Nella Figura 2a è visibile l'oggetto in movimento quando nel rilevatore l'utente disabilita la visione dell'immagine elaborata, mentre nella Figura 2d notiamo l'interfaccia con una barra orizzontale che evidenzia la quantità dei pixel cambiati.

Figura2

 


Un'applicazione d'esempio


In questo paragrafo verrà illustrato, per mezzo di un esempio, come usare il rilevatore di movimenti per applicazioni robotiche. L'applicazione consiste nel programmare un robot (reale) a rilevare oggetti in movimento nel suo campo visivo, capirne la posizione, e voltarsi verso l'oggetto fino a centrarlo nel suo campo visivo. Il robot verrà pilotato (tramite comunicazione seriale) dal computer sul quale è in esecuzione un'applicazione che istanzia Rilevatori di Movimenti. La web cam ovviamente sarà montata sul robot. Il robot dell'esempio è stato assemblato usando componenti facilmente reperibili in commercio, in particolare si consiglia di dare uno sguardo al sito della Parallax (www.parallax.com) dove è possibile trovare kit robotici programmabili sia in Java che in altri linguaggi. Il mostro robot è costituito da una scheda elettronica Basic Stamp, una web-cam e due servo motori che muovono la web-cam. Il programma che risiede sul nostro robot si limita ad attendere comandi tramite la porta seriale per poi eseguirli. I comandi sono inviati dalla nostra applicazione remota che risiede sul computer. Essa invierà i seguenti comandi: muovi la camera a destra, a sinistra, in alto, in basso. Per questo esempio non è necessario implementare un protocollo di comunicazione per il controllo del flusso dei dati inviati, come per esempio lo stop-and-wait oppure go-back-n o altri, ma potrebbe essere un'ottima idea per progetti specifici. Per approfondire il tema consultare bibliografia[3].
Nella Figura 3 possiamo intuire il funzionamento dell'applicazione.

Figura3


In questa figura infatti troviamo al centro l'immagine percepita dal robot, suddivisa in cinque aree ( le zone scure ). L'area al centro è abilitata a rilevare i contorni degli oggetti e ne parleremo un prossimo articolo, in questo momento rileva i contorni di un piccolo veicolo. Le quattro zone laterali (in nero) sono quattro Rilevatori di Movimento distinti assegnati a Thread in esecuzione concorrente. Tali aree sono nere perché nessun oggetto è in movimento. Che nessun oggetto sia in movimento si può notare anche osservando i quattro pannelli che circondano la finestra principale. Ogni uno di questi pannelli appartiene ad un rilevatore di movimento, la barra nera sul loro fondo dei pannelli indica un eventuale movimento percepito.
Nella Figura 4 è possibile notare ciò che accade quando il veicolo che si trovava nell'area centrale si sposta verso il robot.

 

Figura4

Esso infatti viene percepito dal rilevatore di movimenti inferiore, che evidenzia il veicolo con un alone esterno e colorandolo internamente in blu (se il movimento come in questo esempio supera la soglia). Nel rispettivo pannello si nota l'illuminazione della barra posta sul fondo in giallo, che indica il superamento della soglia, e la quantità di pixel cambiati in azzurro. A questo punto il rilevatore notifica l'evento ai listener collegati, in questo esempio l'applicazione che controlla il robot tramite la porta seriale, sospende temporaneamente l'esecuzione dei rilevatori e comanderà il robot di attivare il motore che permette alla telecamera di abbassarsi, al fine di centrare nuovamente l'oggetto in movimento nel campo visivo per poi riattivare i rilevatori. Appare chiaro il comportamento che avrà il nostro robot:
⦁ Abbasserà la camera se l'oggetto muove verso il robot.
⦁ Alzerà la camera se l'oggetto si allontana.
⦁ Ruoterà a sinistra se sarà il rilevatore sinistro a percepire il movimento.
⦁ Ruoterà a destra se sarà il rilevatore destro a notificare l'evento.

Questo comportamento fa rientrare la nostra macchina nella categoria delle macchine S-R (stimolo risposta), per approfondire questo argomento vedi bibliografia[4].
Seguono alcuni frammenti di codice che illustrano come istanziare i rilevatori e come gestirne gli eventi.
Per creare un nuovo rilevatore in un Thread indipendente, definirne la zona dell'immagine su cui lavorare, e collegargli un gestore eventi, è sufficiente scrivere nell'applicazione preposta alla comunicazione con il robot quanto segue:

RilevatoreMovimenti[] rme=new RilevatoreMovimenti[4];
rme[0]=new RilevatoreMovimenti(0,240/3,320/3,240/3);
rme[0].nome="Rilevatore Movimenti SX";//opzionale
rme[0].addRilevatoreMovimentiListener(this);
Thread t=new Thread(rme[0]);
t.start();
...

Questo codice ovviamente va inserito in un metodo (magari il costruttore) di una classe che implementa un listener per il Rilevatore movimenti (RilevatoreMovimentiListener), come nell'esempio seguente:

public class VideoSorveglianzaRobotica
implements RilevatoreMovimentiListener{
...
}

Tale classe quindi deve implementare i metodi dell'interfaccia, come nell'esempio seguente:

public void movimentoRilevato(MovimentiEvento e){
Object sorg=e.getRilevatoreAttivo();
...//codice di temporizzazione o sincronizzazione opzionale
if(sorg==rme[i]){
System.out.println("Movimento rilevato a sx");
}
...
else if(sorg==rmeGIU){

System.out.println("Movimento rilevato giu");
}
}
}

public void movimentoNonRilevato(MovimentiEvento e){}
L'ultimo aspetto da considerare e come inviare i comandi dal pc al robot. Nel Listato 3 e possibile vedere un frammento di codice (commentato per riga per riga) che apre una connessione con un robot tramite la porta seriale e invia un comando sotto forma di stringa. Il robot può essere perciò dotato di qualsiasi tipo di scheda (per es. una Basic Stamp della Parallax) che possa essere programmata a ricevere comandi da seriale e quindi esegue l'azione adeguata attivando i servo motori.

Figura5

 

Conclusioni


Naturalmente ci sarebbero molti altri aspetti da approfondire, ma per il momento questi concetti di base e questi esempi possono darvi buoni spunti di partenza.
Nel prossimo articolo vedremo come realizzare un'applicazione che invece di oggetti in movimento rileva oggetti di un determinato colore, e modificheremo questa applicazione perché il robot tracci tali oggetti. Inoltre parleremo più in dettaglio di alcuni kit robotici e della loro programmazione.


::/fulltext::

Write comment (0 Comments)

Pagina 1 di 2

© 2018 sito prototipale studio di GiuseppeGi