Programmazione di applicazioni multimediali in Processing

Programmazione di
applicazioni multimediali
in Processing
Progettazione e produzione multimediale - 2008
Walter Nunziati - [email protected]
Sommario
Introduzione al linguaggio e all’ambiente di
sviluppo Processing
Elaborazione e analisi di immagini e video in
Processing
Esercitazioni pratiche
Introduzione
La programmazione è diventata un metodo
per creare contenuti multimediali.
In particolare, le Rich Internet Application
(RIA) prevedono una complessa interazione
con l’utente, che richiede l’uso di strumenti
di programmazione al fianco dell’XHTML.
Vari ambienti di programmazione sono oggi
usati dai progettisti di contenuti a fianco dei
tradizionali strumenti di authoring.
Processing
Processing is an open source programming language
and environment for people who want to program
images, animation, and sound. It is used by students,
artists, designers, architects, researchers, and
hobbyists for learning, prototyping, and production. It is
created to teach fundamentals of computer
programming within a visual context and to serve as a
software sketchbook and professional production tool.
Processing is developed by artists and designers as
an alternative to proprietary software tools in the same
domain.
Cosa è Processing in
pratica?
Un linguaggio di programmazione derivato da
Java
Un insieme di librerie dedicate in particolare
allo sviluppo di applicazioni multimediali
(gestione suono, immagini, video, primitive
grafiche, ecc.)
Un ambiente di sviluppo semplice e intuitivo
Riferimenti
Sito web:
www.processing.org/learning/index.html
www.processing.org/reference/index.html
Libri:
Processing: A Programming Handbook for Visual Designers and Artists
Casey Reas and Ben Fry, MIT Press 2007
Processing: Creative Coding and Computational Art (Foundation)
Ira Greenberg
Quick tour
Esempio: helloworld
Why processing?
Open-source, gratuito e multi piattaforma
Ampiamente supportato
Non richiede un ambiente di run-time esterno
(es. il browser)
Nasconde molti “aspetti noiosi” del processo
di sviluppo
Produce eseguibili multi piattaforma e applet
web
Struttura di un
programma processing
I programmi processing sono composti da
moduli chiamati Sketch
Ogni modulo è essenzialmente un file di testo,
e contiene istruzioni e funzioni del
programma
Ogni programma ha un modulo principale, che
viene lanciato al momento dell’esecuzione
Ogni programma contiene (nel modulo
principale) tipicamente le seguenti funzioni (in
continuous programming mode):
setup() - eseguita all’inizio del programma
per le operazioni di inizializzazione
draw() - eseguita continuamente in loop (è il
ciclo principale del programma)
Tale comportamento può essere alterato
attraverso le funzioni loop(), noloop(), e
redraw().
Esempio: ContinuousModeExample1
Cenni di programmazione
strutturata
Sintassi
Dichiarazione variabili
Tipi di dato e scope delle variabili
Istruzioni di assegnamento
Costrutti di selezione e iterazione
Dichiarazione e uso di funzioni
Sintassi (base)
Ogni riga che inizia con // è un commento
Ogni blocco compreso tra /* e */ è un
commento
Ogni istruzione deve terminare con ;
Le istruzioni sono case-sensitive
Istruzioni
Un programma è composto da una sequenza
di istruzioni. Si hanno:
Istruzioni di assegnamento
Istruzioni di selezione
Istruzioni di iterazione
Variabili e istruzioni di
assegnamento
Usate per memorizzare dei valori
Hanno un nome e un valore
Case-sensitive
Usate nomi descrittivi, e siate coerenti
con il naming!
String name = "Daniel"; // Declare and
assign
int number = 32; // Declare and assign
int counter = 12; // Assign variable
print(number);
print(name);
Tipi di dato di base
int //Integer: 1, 2, 3, ...
float //0.1, 2.747, ...
char // “$”, “A”, stores one character.
String //”Daniel”, stores a series of
characters.
boolean //true or false.
// ARRAY (VETTORI):
int[] vettore = new int[];
Nota: non tutti i linguaggi sono “tipizzati”
Scope delle variabili
Una variabile dichiarata all’interno di una
funzione è visibile solo in quella funzione!
Una variabile dichiarata all’esterno di tutte le
funzioni, e visibile a tutto il programma (si
dice GLOBALE)
E’ buona norma non abusare delle variabili
globali!
Tipi non primitivi
Accanto ai tipi primitivi che abbiamo visto
all’inizio (int,char,float,ecc.), ne esistono molti
altri definiti all’interno di varie librerie.
Ad esempio, una variabile di tipo PImage è
utilizzata in processing per memorizzare
immagini
L’uso dei tipi non primitivi segue (quasi) le
stesse regole di dichiarazione dei tipi
primitivi:
PImage img;
Array
Un array (vettore) è un contenitore di oggetti
dello stesso tipo, indicizzati da un numero
intero.
Il primo elemento ha indice 0. L’ultimo n-1, dove
n è il numero di elementi dell’array
Dichiarazione:
int[] myArray = new int[10];
int[] myArray = {1919, 1940, 1975, 1976, 1990};
Un’array può essere inizializzato al momento
della dichiarazione:
int myArray = {100,200,400};
Si accede all’elemento i-esimo dell’array
usando le parentesi quadre:
int a = myArray[5];
Es (disegno di una forma):
Funzioni
Definizione di funzioni
Le istruzioni vengono tipicamente raggruppate in funzioni, che
eseguono in blocco una particolare sequenza di istruzioni ogni
volta che vengono chiamate Una funzione si DEFINISCE con
tiporitornato nomefunzione(par1,pa2,..) {
// corpo della funzione
}
ES (dichiarazione):
int addizione(int i1, int i2) {
int risultato = i1 + i2;
return risultato;
}
Chiamata di funzioni
Una funzione precedentemente definita si richiama
con:
valore-ritornato = nome-funzione(par1,pa2,..);
Es:
int valoreSomma = addizione(v1,v2);
Operatori logici
&& (and)
|| (or)
! (not)
Es.
if (a==true && i>10) {
}
Selezione - if/then/else
// statements within the if condition
are only
// executed when if true
if (i < 35)
{
line( 30, i, 80, i );
}
else
{
line( 20, i, 90, i );
}
// else is executed if condition is
false
Iterazione (for)
// for() loop: repeat with the defined
conditions
// parameters: init, test, update
for(int i=0; i<10; i++) {
line(30, i, 80, i);
}
Iterazione (while)
// while() loop: repeat as long as
condition is true
int i=0;
while(i<10) {
line(30, i, 80, i);
i++;
}
Sistema di coordinate
Processing uses a Cartesian coordinate system with the
origin in the upper-left corner. If your program is 320
pixels wide and 240 pixels high, coordinate [0, 0] is the
upper-left pixel and coordinate [320, 240] is in the lowerright. The last visible pixel in the lower-right corner of the
screen is at position [319,239]
Disegno di primitive
grafiche
//draws a point at x=10 and y=20
point(10, 20);
// draws a line from x1, y1 to x2, y2
line(20, 20, 160, 120);
// draws a rectangle: x1, y1, width, height
rect(20, 20, 160, 120);
// draws an ellipse: x, y, width, height
ellipse(20, 20, 40, 40);
// draws a four sided polygon x1, y1, x2,
y2, x3, y3, x4, y4
quad(38, 31, 86, 20, 69, 63, 30, 76)
Pennello e riempimento
stroke(120); // gray stroke
stroke(0, 0, 255); // blue stroke
stroke (255, 0, 0, 122); // red
transparent
noStroke(); // no Stroke
fill(100);
fill(255, 0, 0);
fill(200, 200, 200, 100); // grey
transparent
noFill();
Note: A fourth parameter for fill and stroke sets the
transparency of the color value.
Trasformazioni del
sistema di riferimento
Le funzioni translate(), rotate() e scale()
permettono di applicare delle trasformazioni
euclidee al sistema di riferimento (e quindi
alle figure disegnate)
rect(0, 0, 50, 50);
translate(30, 20);
fill(0);
rect(0, 0, 50, 50);
fill(102);
rect(15, 10, 50, 50);
Esempio: TranslateExample
Push/Pop Matrix
pushMatrix() e popMatrix() permettono di
isolare le trasformazioni e raggrupparle in
uno stack (matrix stack)
In questo modo, è possibile memorizzare e
richiamare lo stato del sistema di
riferimento.
Esempio: MatrixStackExample
Esercizio - drawing shapes
int i = 0;
int windowSize = 400;
void setup() {
size(windowSize, windowSize);
background(0);
frameRate(10);
}
void draw() {
i++;
fill(255);
float posX = width/2;
float posY = height/2;
float radius = random(0, 100);
ellipse(posX, posY, radius, radius);
}
Esempio: Esercizio1
Scopi del primo esercizio
Familiarizzare con le funzioni setup(), draw(),
frameRate(), size(), background(), fill(),
random(), ellipse().
Uso delle COSTANTI built-in (width, height)
Usare il costrutto di selezione per controllare
l’evoluzione delle forme disegnate.
“Building on existing code”
Esplorare le funzioni di disegno primitive
grafiche.
Image processing and
analysis
Processing: applicare algoritmi a un’immagine
per ottenere degli effetti (aka photoshopping)
Analysis: applicare algoritmi a un’immagine
per capirne il contenuto
Caricamento di
un’immagine
Le immagini sono memorizzate all’interno di
variabili di tipo PImage (tipo non primitivo)
Per aggiungere un’immagine allo sketch,
basta trascinarla sulla finestra di editing
Per caricare un’immagine nel programma, si
usa la funzione loadImage():
PImage img;
img = loadImage(“nomefile.jpg”);
Display di un’immagine
Per mostrare un’immagine a schermo, si usa
la funzione image(). Es:
image(img, 0, 0);
Mostra l’immagine contenuta in img a partire
dall’angolo in alto a sinistra.
•
tint() colora l’immagine del colore specificato.
Elaborazione di immagini
Le funzioni get() e set() possono essere usate
per uno sporadico accesso ai pixel
Per un uso più intensivo, si usano le funzioni
loadPixels() e updatePixels()
loadPixels() ha l’effetto di mettere tutti i
pixel dell’immagine in un array di dimensione
ROWS*COLS, di nome pixels[].
updatePixels() “restituisce” i pixel
all’immagine
Struttura di
elaborazione tipica
// carica l’immagine
PImage myImage = loadImage("topanga.jpg");
image(myImage, 0, 0);
//elabora i pixel
loadPixels();
for (int i = 0; i < width*height; i++) {
pixels[i] = color(200,20,30);
}
updatePixels();
Esempio: ImageExample
La funzione filter
filter(MODE) (o filter(MODE, level)) applica un
algoritmo all’immagine
Es. filter(THRESHOLD, 0.5) converte
l’immagine in bianco e nero
Filtri disponibili: THRESHOLD, GRAY, INVERT,
POSTERIZE, BLUR, OPAQUE, ERODE, DILATE
La funzione blend
Sovrappone una regione di una immagine su
un’altra (o su se stessa)
blend(x, y, width, height, dx, dy, dwidth,
dheight, MODE)
blend(srcImg, x, y, width, height, dx, dy,
dwidth, dheight, MODE)
MODE: BLEND, ADD, SUBTRACT, LIGHTEST,
DARKEST..
Eventi
Sono una importante funzionalità messa a
disposizione dell’ambiente processing
Un evento è una funzione che viene chiamata
al verificarsi di certe condizioni
Es. mousePressed() è chiamata ogni volta che
viene premuto un bottone del mouse
void setup()
{
size(200,200);
fill(0);
}
void draw() {}
void mousePressed()
{
rect(mouseX,mouseY,20,20);
}
Esempio: MousePressedExample
Eventi disponibili (input)
mouseDragged()
mouseMoved()
mouseReleased()
mousePressed()
keyReleased()
keyPressed()
Variabili predefinite
mouseX, mouseY: coordinate del mouse nel
frame corrente
pmouseX, pmouseY: coordinate del mouse nel
frame precedente
mouseButton: LEFT, RIGHT, CENTER
mousePressed: vera se il mouse è
correntemente premuto. NB: si chiama come
la funzione mousePressed()!!!
Analoghe variabili sono disponibili per gli
input da tastiera
Uso dei font
I font devono preventivamente essere
“creati” usando l’apposito strumento dal menu
Tools/Create fonts..
Una volta creato, il font viene messo nella
cartella “data” dello sketch
Per utilizzarlo, si usa il tipo PFont:
PFont font = loadFont(“Courier-48.vlw”);
Video
Le funzioni per l’uso dei video sono in
processing.video.*;
I video vengono gestiti attraverso il tipo di variabile
Movie
I video devono essere in formato mov. Non sono molti
i codec supportati (mpeg4 ok, ffmpeg può essere
usato per la conversione)
Ad ogni frame viene chiamato l’evento movieEvent()
I singoli frame possono essere trattati e elaborati
come immagini.
Riproduzione di un filmato:
import processing.video.*;
Movie myMovie;
void setup() {
size(360,240);
myMovie = new Movie(this, "ronaldinho.avi");
myMovie.loop();
}
void draw() {
image(myMovie, 0,0);
myMovie.read();
}
Esempio: VideoExample1
Elaborazione dei frame
void draw() {
// Read the new frame from the movie
video.read();
// make its pixels array available
video.loadPixels();
for (int i = 0; i < pixels.length; i++) {
color currColor = video.pixels[i];
// Render the grayscale image to the screen
video.pixels[i] = color(brightness(currColor));
}
video.updatePixels();
}
Esempio: VideoExample2
Video from camera
Sono supportate le telecamere USB e
Firewire, ma il supporto varia a seconda dei
sistemi operativi (viene utilizzato QuickTime
per l’acquisizione, linux è MOLTO
penalizzato)
La classe utilizzata è Capture
Capture.list() mostra i dispositivi di input
video disponibili.
La gestione è simile a quella dei video,
seppure (ovviamente) con meno controlli.
movieEvent e
captureEvent
Chiamati ad ogni frame decodificato
void movieEvent(Movie m) {
m.read();
}
void captureEvent(Capture myCapture) {
myCapture.read();
}
Classi e oggetti
Non tutte le funzionalità e le librerie di
Processing possono essere utilizzate tramite
l’approccio “a funzioni”.
Alcune parti sono implementate dal linguaggio
secondo un modello “a oggetti”, con cui a
volte è necessario avere a che fare.
In generale, tutto ciò che sta fuori da
Processing-core deve essere usato “a
oggetti”.
Classe
Una classe definisce un tipo di dato non
primitivo, sia in termine dei dati che contiene,
che delle operazioni che si possono svolgere
su di esso.
Es. PImage è una classe, che serve per
gestire le immagini
Variabili e oggetti
Quando si dichiara una variabile di un tipo di
dato non primitivo, si dice che si definisce un
oggetto di quella classe. Es.
PImage img;
Dichiara un’oggetto di classe (tipo) PImage.
L’operatore new
Per creare (istanziare) un’oggetto, ci sono di
solito due modi:
Usare una funzione che “fa il lavoro” per noi.
Es.
PImage img = loadImage(“path”);
Usare l’operatore new:
PImage img = new PImage();
Metodi
Gli oggetti hanno tipicamente delle funzioni
che possono essere invocate su di essi. Tali
funzioni si chiamano metodi. Es:
//
dichiarazione e creazione di una variabile di classe Movie
Movie movie = new Movie(this, “videofile”);
// Chiamata del metodo play();
movie.play();
Librerie
Built-In
Video
Networking
Serial Communication
Importing XML, SVG
Exporting PDF, DXF, etc.
External Contributions
Sound: Ess, Sonia
Computer Vision: JMyron, ReacTIVision,
BlobDetection
Interface: proCONTROLL, Interfascia
...
Installazione di librerie
Le librerie aggiuntive devono essere scaricate
a parte
Tipicamente, è sufficiente copiare il file .jar
fornito all’interno della cartella libraries
contenuta nell’installazione del processing
In alcuni casi vengono forniti anche dei
componenti nativi (.dll, .so) che devono essere
aggiunte al library path di java
Uso di librerie
L’istruzione import all’inizio di un modulo
permette di utilizzare le librerie definite
nella clausola import in quel modulo.
Es. per importare la libreria del video:
import processing.video.*;
Ess library (sound)
Ess r2 is a sound library that allows sound
sample data to be loaded or streamed (AIFF,
WAVE, AU, MP3), generated in real-time
(sine, square, triangle and sawtooth waves,
white and pink noise), manipulated (raw or via
built-in filters), saved (AIFF, WAVE), analyzed
(FFT) or simply played back.
ESS - generazione
Classi usate:
Ess (metodi statici per il controllo generale
del device audio)
AudioStream (Stream audio di output
monofonico)
SineWave (generatore di forma d’onda)
Esempio: EssExample1
ESS - riproduzione
Per aprire un file mp3 è necessario scaricare
e installare 3 componenti aggiuntivi (3 file
jar relativi a librerie java per l’mp3).
La classe che permette di aprire un file mp3
è AudioFile
Esempio: EssExample2
Svg library
Scalable Vector Graphics (SVG) is an XML
specification and file format for describing
two-dimensional vector graphics, both static
and animated. SVG can be purely declarative
or may include scripting. Images can contain
hyperlinks using outbound simple XLinks. It is
an open standard created by the World Wide
Web Consortium's SVG Working Group.
Apertura e
manipolazione di un file
E’ garantito il funzionamento con file creati
da AI.
Viene usata la classe SVG
SVG svgImage = new SVG(this, “file.svg”);
Il metodo draw() disegna l’immagine a
schermo
Essendo un immagine vettoriale, può essere
trasformata in modo molto efficiente.
Esempio: SvgExample
Interfascia
Libreria per la realizzazione di interfacce
grafiche.
Fornisce numerosi controlli: pulsanti, radio
buttons, check box, slider, ecc.
Gestisce automaticamente l’interazione
attraverso un meccanismo a eventi.
Esempio - pulsante
Ogni widget è gestito da un tipo di variabile:
IFButton, IFLabel, ecc.
L’oggetto GUIController supervisiona il
meccanismo degli eventi. Deve essere
costruito a partire dalla applet che deve
ricevere gli eventi (tipicamente, this):
GUIController controller = new GUIController(this)
Esempio: InterfasciaExample
Listener e EventHandler
Ad ogni controllo, deve essere associato un
oggetto actionListener. In pratica, questo
associa un contenitore (che è di soltio la
applet stessa) al controllo.
Quando avviene un evento, viene chiamato
automaticamente l’eventHandler
actionPerformed, con il parametro GUIEvent
che contiene varie informazioni tra cui
l’oggetto che ha scatenato l’evento.
Gestione dell’evento
La funzione actionPerformed contiene le
istruzioni che devono essere eseguite quando
si scatena l’evento.
Il parametro GUIEvent ha un metodo
getSource() che viene utilizzato per capire
qual’è il controllo che ha originato l’evento.
BlobDetection
Trova i “blob” presenti nell’immagine. Un blob
è una regione a intensità costante presente
nell’immagine.
L’immagine viene prima convertita in
un’immagine b/n (attraverso un’operazione di
sogliatura), e successivamente elaborata per
trovare tutte le regioni a intensità costante.
L’algoritmo restituisce i bordi e la bounding
box di ogni regione trovata.
Può servire come primo stadio di un sistema
di rilevamento/riconoscimento oggetti.
La classe BlobDetection fa tutto il lavoro.
Il metodo setThreshold(float) setta il valore
da utilizzare per la sogliatura
Il metodo computeBlobs(PImage) esegue il
calcolo dei blob
Il risultato è una lista di oggetti Blob, che
contengono le informazioni di ogni regione
trovata.
L’algoritmo non è molto efficiente, e funziona
solo su immagini di piccole dimensioni.
Esempio: BolobDetectionExample
JMyron library
Una interessante alternativa per l’analisi del
video da telecamera è la libreria JMyron
Non dipende da qt4java, e implementa un
certo numero di algoritmi per l’analisi video
[vedi reference]
Hardware processing
Esistono delle schede hardware (wiring, $80, arduino, $30) a
micrcontrollore programmabili tramite un linguaggio (wiring) che è
costruito a partire dal processing.
Wiring condivide molte delle feature del processing, in più mette a
disposizione gli strumenti per generare il codice nativo per i
microcontrollori, e per scaricare il codice oggetto sulle schede.
Mobile processing
Esiste un versione del processing
(mobile.processing.org) che può essere
eseguita su qualsiasi dispositivo mobile dotato
di java virtual machine.
Processing mette a disposizione un simulatore
di per sviluppare su tali dispositivi. Lo sviluppo
prevede l’utilizzo di un subset delle feature
del java.
Temi per elaborati
Sviluppo di un modulo per l’acquisizione video
da telecamera IP per flusso di tipo mjpeg
(funzionante con telecamere axis/sony)
Sviluppo di un modulo per l’acquisizione video
sotto linux da file o da sorgenti v4l/v4l2
tramite ffmpeg.