VETLib Video Elaboration & Transmission LIBrary 1.0.2 Manual.it
User Manual:
Open the PDF directly: View PDF .
Page Count: 150
Download | |
Open PDF In Browser | View PDF |
Abstract Il progetto VETLib si propone di creare una libreria di processing, elaborazione e trasmissione di segnali video digitali destinata all’implementazione di filtri, codificatori e decodificatori software. La libreria è scritta in ANSI C++ ed è basata su un’architettura modulare tramite la dichiarazione di classi astratte standard (interfacce) estese con implementazioni specifiche e templates anche per applicazioni real-time. La dipendenza da device, sistemi operativi e codifiche specifiche è ristretta a singoli moduli e trasparente a livello di applicazione secondo la logica OOP (Object Oriented Programming). La Release corrente offre un’interfaccia base a Video4Linux ed a Microsoft DirectX (DirectShow) per l’acquisizione di dati; due interfacce di visualizzazione per ambienti Linux tramite le librerie QT/GTK (adatte per video, nei tests: massimo 33 fps) e una per ambienti Windows tramite le API di GDI (adatta a immagini statiche); inoltre sfrutta librerie esterne quali libmpeg3, xvidcore, quicktime4linux per la (de)codifica video e imageMagick per la (de)codifica di immagini (praticamente tutti i formati). In seguito ad un progetto esterno è stato sviluppato anche un complesso modulo di acquisizione e preview live (tramite DirectX) per dispositivi ad alta fedeltà sia in formato non compresso (RAW o DV) che con codifica hardware MPEG2. Gli strumenti forniti da VETLib consentono lo sviluppo di applicazioni in ambienti RAD (Rapid Application Development) e l’estensione dei componenti inclusi per soluzioni proprietarie (principalmente tramite l’ereditarietà), l’utilizzo pratico di ciascun oggetto è ampliamente dimostrato con i progetti di test per i casi più banali (ogni componente ha un rispettivo progetto che ne evidenzia le caratteristiche e l’utilizzo), inoltre il software VETLib WorkShop è un applicazione completa molto strutturata basata interamente sulla libreria. Lo sviluppo di nuovi moduli (basati su interfacce standard) è stato semplificato anche grazie allo strumento Package Studio (sviluppato in .NET C++), in grado di automatizzare la creazione di progetti personalizzati per i sistemi di sviluppo più diffusi (Microsoft Visual Studio, Borland C++ Builder, Make), il software è altamente strutturato ed elastico, si basa su una serie di moduli predefiniti e su files di configurazione in formato XML, quindi l’aggiornamento delle classi base e dei progetti predefiniti non implica la ricompilazione del programma. Durante la fase finale del progetto è stata sviluppata un applicazione (WorkShop) per sistemi Windows in Managed C++ (Framework .NET) in grado di testare i componenti esistenti e di gestire dinamicamente nuovi plugins tramite DLL standard. WorkShop dimostra le potenzialità della libreria integrando in modo statico e dinamico tutti i moduli progettati per sistemi Windows, è possibile modificare in modo visuale i parametri di lavoro e costruire più catene di filtri. Contents Abstract .............................................................................................................. IV Acknowledgments............................................................................................VIII Summary ............................................................................................................ IX VETLib FRAMEWORK ................................................................................... 11 1.1 - Overview................................................................................................. 11 1.2 - Modular Software Architecture .............................................................. 15 1.3 - Framework Design ................................................................................. 16 1.4 - vetFrame ................................................................................................. 21 1.4.1 - vetFrameYUV420............................................................................ 25 1.4.2 - vetFrameRGB24 .............................................................................. 26 1.4.3 - vetFrameT........................................................................ 27 1.4.4 - vetFrameRGBA32 ........................................................................... 29 1.4.5 - vetFrameGrey .................................................................................. 30 1.4.6 - vetFrameRGB96 .............................................................................. 30 1.4.7 - vetFrameHSV .................................................................................. 31 1.4.8 - libETI support.................................................................................. 31 1.5 - vetInput ................................................................................................... 32 1.6 - vetOutput ................................................................................................ 35 1.7 - vetFilter................................................................................................... 36 1.8 - vetCodec ................................................................................................. 41 1.9 - vetVision................................................................................................. 43 1.10 - vetBuffer ............................................................................................... 44 1.11 - vetObject............................................................................................... 46 1.12 - vetException ......................................................................................... 46 1.13 - Directory structure ................................................................................ 47 1.14 - Builts..................................................................................................... 49 1.15 - Documentation...................................................................................... 50 1.16 - VETLib Online ..................................................................................... 51 VETLib IMPLEMENTATION.......................................................................... 53 2.1 - Inputs ...................................................................................................... 53 vetNoiseGenerator ...................................................................................... 53 vetPlainFrameGenerator ............................................................................. 53 vetVideo4Linux .......................................................................................... 54 vetDirectXInput .......................................................................................... 54 vetDirectXInput2 ........................................................................................ 54 2.2 - Outputs.................................................................................................... 55 vetWindowQT............................................................................................. 55 vetWindowGTK.......................................................................................... 55 vetWindow32 .............................................................................................. 55 vetDoctor..................................................................................................... 55 vetOutputVoid............................................................................................. 55 2.3 - Codecs..................................................................................................... 56 vetCodec_BMP ........................................................................................... 56 vetCodec_IMG............................................................................................ 56 vetCodec_MPEG ........................................................................................ 56 V vetCodec_MOV .......................................................................................... 56 vetCodec_XVID.......................................................................................... 56 2.4 - Filters ...................................................................................................... 57 vetFilterGeometric ...................................................................................... 57 vetFilterColor .............................................................................................. 57 vetMultiplexer............................................................................................. 57 vetFilterNoiseChannel ................................................................................ 57 2.5 - Buffers .................................................................................................... 58 vetBufferArray ............................................................................................ 58 vetBufferLink.............................................................................................. 58 2.6 - Other Modules ........................................................................................ 59 vetHist ......................................................................................................... 59 vetThread .................................................................................................... 59 vetUtility ..................................................................................................... 59 vetMatrix..................................................................................................... 59 vetDFMatrix................................................................................................ 59 2.7 - Vision...................................................................................................... 60 vetMotionLame........................................................................................... 60 Using and Extending VETLib............................................................................ 62 3.1 - Overview................................................................................................. 62 3.2 - Samples................................................................................................... 65 3.3 - Tools of the Trade................................................................................... 68 3.4 - VETLib Component Conventions .......................................................... 69 3.5 - Package Development ............................................................................ 70 3.5.1 - Working with Frames ...................................................................... 71 3.5.2 - Internal buffering ............................................................................. 73 3.5.3 - Parameters for Filters and Codecs ................................................... 76 3.5.4 - Platform specific.............................................................................. 79 3.5.5 - Templates......................................................................................... 80 3.5.6 - Threading......................................................................................... 81 3.6 - VETLib Package Starter Kit................................................................... 83 3.7 - Releasing VETLib .................................................................................. 89 VETLib WorkShop & PlugIns........................................................................... 93 4.1 - Overview................................................................................................. 93 4.2 - How It Works ......................................................................................... 95 4.3 - Dynamic PlugIn System ....................................................................... 100 4.4 - WorkShop PlugIn Development........................................................... 104 ReadMes........................................................................................................... 107 ./README.................................................................................................... 107 ./USE ............................................................................................................. 109 ./COMPILE ................................................................................................... 111 ./FAQS .......................................................................................................... 115 ./EXTEND..................................................................................................... 116 ./ChangeLog.................................................................................................. 118 ./TODO ......................................................................................................... 119 ./BUGS.......................................................................................................... 120 ./AUTHORS.................................................................................................. 120 ./lib/README .............................................................................................. 121 Headers............................................................................................................. 124 VI vetDefs.h ....................................................................................................... 126 vetException.h............................................................................................... 126 vetFrame.h..................................................................................................... 127 vetFrameRGB24.h ........................................................................................ 128 vetFrameYUV420.h...................................................................................... 129 vetFrameT.h .................................................................................................. 130 vetFrameRGBA32.h ..................................................................................... 131 vetFrameRGB96.h ........................................................................................ 132 vetFrameHSV.h............................................................................................. 133 vetFrameGrey.h............................................................................................. 134 vetOutput.h.................................................................................................... 135 vetInput.h ...................................................................................................... 136 vetFilter.h ...................................................................................................... 137 vetBuffer.h .................................................................................................... 138 vetCodec.h..................................................................................................... 139 vetVision.h .................................................................................................... 140 vetObject.h .................................................................................................... 141 Sample Applications ........................................................................................ 143 License ............................................................................................................. 147 Bibliography..................................................................................................... 152 VII Acknowledgments R o a D v I . g t T e u W a o r E W G A T E I h v N e g B N b l O S C A R L O y k T c i u o P T f U m P h E S M u y L A n C u q E r G N U L b A t o r N U R e c f h O A s a S E r t e r C E R I t o P s u p a l A T C A q d L I N U X j C k E N s r U g q u 8 P I P P O i w S n a z 6 g S a x p Ringrazio quindi il mio relatore Prof. Francesco De Natale per l’attenzione e la costante disponibilità. Ritengo inoltre necessario sottolineare la fondamentale importanza di Internet, la più grande fonte di informazioni del nostro secolo, grazie alla quale l’accesso e la distribuzione di conoscenza sono a portata di mouse. Difficilmente avrei potuto portare a termine questo lavoro senza il contributo materiale e affettivo della mia famiglia e dei miei amici a cui dedico questa tesi. VIII Summary Questo documento vuole presentare il progetto VETLib agli utenti e soprattutto agli sviluppatori, segue una breve descrizione dei contenuti: Il capitolo primo riassume le caratteristiche di VETLib, analizza la struttura della libreria in modo astratto e con esempi pratici, presenta le interfacce, gli oggetti principali e il sistema di interazione tra i componenti. L’ultima parte elenca la struttura delle cartelle e della documentazione integrata. Il capitolo secondo descrive brevemente i moduli inclusi in questa release, mentre gli headers e una breve descrizione dei programmi dimostrativi sono disponibili in appendice. Il capitolo terzo è focalizzato sull’utilizzo pratico e sull’estensione di VETLib, l’implementazione di filtri è analizzata nel dettaglio anche con esempi pratici, sono inclusi vari suggerimenti agli sviluppatori neofiti, la parte finale del capitolo presenta lo strumento VETLib Package Studio (utile per lo sviluppo di componenti) e i passi da seguire per rilasciare una nuova versione della libreria (anche con il software VETLib Distribution Manager). Il capitolo quarto analizza il software VETLib Workshop e il sistema di gestione dei plugins (componenti dinamici), l’ultima sezione spiega i passi necessari per convertire un modulo VETLib in un componente di WorkShop (creazione di una DLL con Visual Studio), lo strumento Package Studio è in grado di generare il progetto e i principali files necessari automaticamente. Le appendici includono i file readmes, gli headers delle classi base e una breve descrizione dei programmi dimostrativi più significativi. La Licenza di distribuzione è la classica General Public License, riportata per intero dal testo ufficiale. Il supporto multimediale allegato (CD o DVD) contiene l’ultima distribuzione rilasciata, la classificazione delle cartelle è riportata alla fine del capitolo primo, è consigliabile iniziare la consultazione dal il sito web (directory ./Website oppure online http://lnx.ewgate.net/vetlib). Una versione aggiornata di questo documento è disponibile online all’indirizzo http://lnx.ewgate.net/vetlib/distr/docs/pdf/VETLib-1.0.2-Manual.it.pdf IX VETLib FRAMEWORK Chapter I 1.1 - Overview Una libreria è l’unione di risorse, classi e metodi in un unico object molto simile all’output di un compilatore C++ generico (classicamente .obj), in ambienti Windows le librerie sono contraddistinte dall’estensione .lib, mentre in ambienti *NIX lo standard prevede estensione .a. Le librerie sono ampliamente diffuse nella programmazione odierna che è quasi totalmente orientata agli oggetti (Object Oriented Programming), quando una libreria offre un set di oggetti e strumenti dedicato allo sviluppo di software si parla di Application Programming Interface. I vantaggi sono molteplici e variano dalla chiarezza nello stile del sorgente alla facilità di distribuire aggiornamenti, in VETLib si sfrutta soprattutto la possibilità intrinseca di interazione tra le estensioni e le applicazioni della libreria. Ci si potrebbe chiedere perché creare una libreria di video processing, in effetti esistono poche librerie open source dedicate a questo campo, molte delle quali sono finalizzate a scopi relativamente ristretti e difficilmente estensibili verso altre frontiere. L’elaborazione video, come molti altri contesti, coinvolge numerosi sotto-processi (spesso modulabili) e necessita di molti oggetti e metodi standard che spesso lo sviluppatore deve implementare una tantum (ad esempio: oggetti frame, funzioni matematiche, accesso ai dati e visualizzazione), questa implementazione preliminare è costosa (per complessità e tempo) e soprattutto propedeutica al “vero” cuore del sistema che si vuole sviluppare (sul quale ci di vorrebbe concentrare), il risultato è che spesso algoritmi e filtri efficienti devono essere analizzati, estratti dal contesto e modificati per adattarsi alle esigenze di ogni singola applicazione, ovviamente ciò è tremendamente inefficiente. Il problema principale è il compromesso tra prestazione e flessibilità, ottimizzare il codice per uno scopo o una piattaforma (device) specifica paga moltissimo in prestazioni, ma limita l’applicazione a quel contesto e allunga i tempi di sviluppo, un’evidente esempio è la diffusione negli ultimi anni di software ludici sulla piattaforma Windows (32bit), queste applicazioni non interagiscono direttamente con i dispositivi grafici (video card), ma accedono ad un framework (DirectX/OpenGL) che fornisce un interfaccia standard di accesso. Un altro esempio di quanto sia importante la portabilità anche a discapito delle prestazione è la diffusione del linguaggio Java. Il video processing mette in crisi le capacità hardware anche dei sistemi più costosi, non si tratta solo di gestire grandi volumi di dati in un tempo limitato ma di elaborare lo stream con operazioni matematiche più o meno complesse, che spesso devono essere approssimate per ottenere prestazioni accettabili, è evidente che in un contesto simile sia un algoritmo che un accesso dati migliore incrementa sensibilmente la performance del processo. 11 Inoltre se in questo complesso contesto si include anche la presentazione del video si aggiunge il problema della continuità temporale accettabile dall’utente, il tempo di elaborazione di uno o più frame, tipicamente variabile, deve essere normalizzato per garantire la fluidità delle immagini; inoltre e nel caso dello streaming “reale” (trasmissione su canali non ideali) attraverso reti comunemente utilizzate (IP) il sistema di trasmissione applica ritardi variabili anche ai sotto-blocchi dei frames (lo stream è diviso in pacchetti). L’obbiettivo che il progetto VETLib persegue è sviluppare un “laboratorio software” di sviluppo per filtri e (de)codificatori in grado di interagire col maggior numero di device e codifiche in modo trasparente ed astratto. La mia prima analisi ha considerato quali strumenti e quali componenti una simile libreria dovrebbe fornire agli sviluppatori: 9 9 9 9 9 9 9 Oggetti di base: frames, matrici, vettori; Sistema(i) per caricare, acquisire e salvare video e immagini; Sistema(i) per visualizzare video e immagini; Sistema(i) per trasmettere video e immagini tramite classiche reti informatiche; Funzioni matematiche (DCT, statistiche); Un set di filtri più comuni; Utilità varie (conversione di colore, ecc); La definizione di una serie di interfacce standard consente l’interazione tra oggetti e moduli ma garantisce un alto fattore di personalizzazione (ereditarietà e composizione di classi), la versione corrente (1.0.2.25) non è ancora matura per una distribuzione ufficiale, esclusivamente per la scarsa presenza di filtri e supporti matematici necessari, il lavoro si è infatti focalizzato sulla standardizzazione degli oggetti (e la loro interazione) e sull’acquisizione/visualizzazione dei dati, lo sviluppo del progetto è stato analizzato accuratamente più volte per aggiornare l’ordine di priorità dei componenti, gradualmente nuovi moduli e metodi saranno integrati nella libreria e disponibili agli sviluppatori. Il linguaggio di programmazione scelto è ANSI C++ (ISO/IEC 14882:2003), di cui si sfrutta pesantemente l’astrazione e la modulazione del codice tramite classi e templates che si accordano con l’architettura concettuale del framework, la semplicità con cui lo sviluppatore cliente può accedere ai metodi e alle strutture della libreria a livello di applicazione è indiscutibilmente maggiore rispetto alla soluzione in linguaggio C (meno modulare, maggiore complessità sintattica), inoltre i compilatori più recenti hanno assottigliato il divario prestazionale tra i due linguaggi. Gli oggetti base e la maggior parte dei filtri sono completamente portabili poiché basati esclusivamente sulla Standard C++ Library, mentre i componenti legati a particolari hardware o librerie (acquisizione, visualizzazione, ecc) sono stati implementati in modo simmetrico per sistemi Windows e sistemi *NIX (Linux, Unix). 12 In ambienti *NIX (sviluppo su Linux 2.4.29 SlackWare 10.1) la libreria è compilata con il classico GNU C++ Compiler (GCC 3.3.4) ed è presente nella directory radice di VETLib il file di configurazione dell’utility Make (Makefile), le attuali funzionalità di VETLib sono: 9 9 9 9 9 Acquisire da device compatibili con video4linux (usb cams, tv tuners, ..); Decodificare video in formato MPEG1-2, MPEG4 (XVID); (de)Codificare video in formato Quicktime (MOV); (de)Codificare quasi tutti i formati immagine esistenti; Visualizzare immagini e video in qualunque Desktop Environment (es. KDE, GNOME); In ambienti Windows (sviluppo su Windows 2000 SP4), VETLib è costruita con il software Microsoft Visual C++ 6.0 (con Borland C++ Builder 6.0 non sono disponibili tutti i componenti rilasciati), i file di progetto sono situati in ./lib/mvc (e ./lib/bcb/), la libreria completa è in grado di: 9 Acquisire immagini e video da device compatibili con DirectX (low fps); 9 Acquisire e salvare video in formato non compresso da device compatibili con DirectX con preview in overlay (high fps) (anche IEEE1394 OHCI e device MPEG2); 9 Caricare video tramite DirectX (qualunque codifica supportata dal sistema); 9 Decodificare stream MPEG4 (XVID); 9 (de)Codificare quasi tutti i formati immagine esistenti; 9 Visualizzare immagini (GDI, librerie esterne: QT, GTK); 9 VETLib WorkShop (VETLib front-end); 9 VETLib Package Studio (Developers’ Tool). Le differenze sono dovute alla portabilità delle librerie esterne su cui sono basati alcuni moduli, le due applicazioni VETLib WorkShop e VETLib Package Studio sono sviluppate in Managed C++ .NET e quindi disponibili solo per ambienti Windows, il primo è uno strumento per il test, la verifica e la dimostrazione didattica di componenti (es. filtri), il secondo è un utilissimo software che automatizza la fase (iniziale) dello sviluppo di nuovi componenti, consente di personalizzare il progetto e si basa su moduli template e file di configurazione XML facilmente aggiornabili; non si prevede lo sviluppo degli analoghi softwares per Linux. Entrambe le distribuzioni (*NIX e Windows) sono suddivise in special builts ed una versione completa (full), è consigliato fare riferimento sempre alla versione completa ma questa classificazione può essere utile agli addetti ai lavori ed agli sviluppatori che intendono compilare VETLib sul proprio sistema, ad esempio si può evitare di compilare e gestire librerie esterne non coinvolte nel proprio progetto. Gli strumenti e gli oggetti forniti da VETLib sono integrabili direttamente in applicazioni finali, ma lo sviluppatore può anche estenderli con implementazioni specifiche ed ottimizzate, le interfacce a contesti OS / device dependent sono ristrette a singoli moduli e quando possibile sono disponibili diverse implementazioni della medesimo modulo (es. Threading). Alcune possibili applicazioni sono: 9 Strumenti e algoritmi di video e image processing a scopo didattico e commerciale; 9 Applicazioni di video sorveglianza con la possibilità di creare moduli “leggeri” per software distribuiti (ad esempio su Linux Embedded su FPGA); 9 Applicazioni inerenti la Computer Vision per ambienti domotici casalinghi e industriali. 13 Lo sviluppo della libreria in futuro dovrà concentrarsi sui seguenti aspetti: Interfaccia a Video4Linux2; Interfaccia a DirectX 10 (attualmente non ancora rilasciato); Interfaccia diretta a dispositivi FireWire su linux (libraw1394, libdv, libavc1394); Interfaccia alla libreria MPEG4IP; Interfaccia per la codifica MPEG2-4 (FFMPEG); Implementare dei moduli driver di sorgente e rendering per DirectX e VideoForWindow; Sviluppo di un nuovo sistema di interfacce vetInput2, vetOutput2, vetFilter2, vetProcess in grado di gestire catene di moduli con collegamenti intelligenti (analogo a DirectShow), il nuovo sistema è analogo all’hardware, i moduli si interfacciano tramite una serie di Pin e i collegamenti (Link) minimizzano le operazioni di color-space conversion e di buffering; Implementare un bridge (eventualmente in vetFilter2) per condividere filtri con DirectShow; Ottimizzare il modulo vetThread ed integrarlo nel moduli esistenti; Implementare i filtri più comuni, relativi plugins ed applicazioni dimostrative; Implementare moduli dedicati alla trasmissione e ricezione di streams su reti; Implementazione di un sub-framework per contenuti multimediali su PDA (Java e .NET); Implementare moduli per il controllo/acquisizione dati di basso livello (seriale e usb); Implementazione di un sistema di scripting per il test e dimostrazioni didattiche; Implementazione di moduli per accesso/controllo da remoto (HTTP, TCP telnet like); Fornire un accesso alla libreria simile a Java e .NET, tramite la gerarchia e la composizione di namespaces ed una serie di metodi statici; Proseguire lo sviluppo dei software Package Studio e Distribution Manager. 14 1.2 - Modular Software Architecture Una libreria modulare ed estensibile permette di inglobare standards e formati specifici, servizi di basso livello e librerie esterne in modo trasparente a livello di applicazione. I moduli intrinsecamente legati a fattori hardware o che interagiscono con servizi di basso livello possono avere più implementazioni specifiche (ad esempio rispetto al sistema operativo) che forniscono la stessa interfaccia garantendo una ottima portabilità. Il modello è diviso in 3 livelli: Application Layer Il livello di applicazione si interfaccia al layer inferiore che fornisce metodi e strutture standard e indipendenti dai livelli inferiori. Lo sviluppatore può utilizzare supporti gia esistenti o estendere gli oggetti con implementazioni specifiche tramite l’ereditarietà e la composizione di classi. Middleware Layer (VETLib) Questo livello si occupa di rendere scalabili e portabili metodi e servizi specifici, di offrire strutture e funzioni comuni al livello superiore. Kernel Services Il livello più basso è costituito dai servizi che offre il kernel del sistema operativo, le librerie (anche la C++ Standard Library ovviamente) e le interfacce (drivers) a device e standard specifici (codecs). Fig. 1.1 - VETLib modular software architecture La libreria (non tutti i moduli) è compatibile con ambienti Windows e *NIX, un applicazione sviluppata in ANSI C++ e che si basa solo sui moduli multi-piattaforma di VETLib, mantiene la portabilità del codice, ad esempio il modulo vetThread nasconde allo sviluppatore la complessità e la grande differenza nella gestione dei processi dei sistemi operativi Windows e *NIX, l’implementazione di vetThread contenuta in VETLib.a (libreria statica per linux) è ovviamente diversa alle *.lib (libreria statica per windows), ma fornisce gli stessi metodi e segue un eguale comportamento. 15 1.3 - Framework Design Una libreria di video processing deve fornire un set di strumenti e oggetti generici e specifici per gli utilizzi più comuni, in modo puramente astratto possiamo sintetizzare un processo di elaborazione: Questa analisi distingue 3 oggetti principali: Decoder Il decodificatore si occupa di tradurre una codifica specifica (ex. MPEG) in uno standard riconosciuto da sistema di filtri, i dati possono essere caricati da uno stream live (VideoIn o Network) oppure da una fonte statica (memoria), in uscita si ha uno stream on demand (cioè è il modulo successivo o l’applicazione a richiedere i dati). Filtering Process È il cuore del sistema, non conosce le caratteristiche dello stream originale in ingresso ed in uscita, ma interagisce con l’input (decoder) e l’output (encoder) tramite uno standard definito, analogamente gli altri due oggetti non conoscono il sistema di filtri. Encoder Il codificatore converte lo stream in uscita dal sistema di filtri in una codifica specifica, un successivo oggetto si occuperà di memorizzare i dati o inviarli ad un dispositivo (VideoOut, Network). Questa visione astratta è realizzata in VETLib nelle due super classi principali: vetInput e vetOutput, sono classi astratte che definiscono l’interfaccia ad uno standard di comunicazione interno definito, questo standard è basato su tre classi: vetFrameRGB24, vetFrameYUV420 ed il template vetFrameT , le caratteristiche dei singoli oggetti saranno affrontate più avanti in questo capitolo, lo scopo di questa sezione è un analisi di alto livello. Segue uno schema del data-flow in ingresso: L’oggetto vetInput corrisponde al decodificatore astratto presentato nello schema precedente, in uscita ovviamente abbiamo un oggetto vetOutput: In termini pratici l’oggetto vetInput è una sorgente di frames e definisce un interfaccia che li fornisce, vetOutput definisce un interfaccia di acquisizione di frames poiché è un uscita dati. 16 La composizione astratta (e pratica) di questi due oggetti è un buffer: i frames vengono importati da una sorgente, memorizzati e indirizzati al successivo oggetto vetOutput in uscita; ma un buffer può elaborare i dati e “diventare” un filtro, vetFilter è l’oggetto che implementa entrambe le interfacce (tramite ereditarietà) e si pone al centro del nostro data-flow: La classificazione input/output in VETLib può inizialmente destare confusione, come metodo pratico si possono distinguere facilmente osservando il data-flow dal punto di vista del filtro: - In ingresso al filtro (oggetto che fornisce i frames) ci sarà un implementazione dell’interfaccia vetInput che definisce le funzioni extractTo(vetFrame..). In uscita al filtro (oggetto che riceve i frames) ci sarà un implementazione di vetOutput che definisce le funzioni importFrom(vetFrame..). Ovviamente gli oggetti vetFilter possono comunicare con tutte le implementazioni di vetInput/vetOuput e quindi anche con altri oggetti vetFilter. In prima analisi si può considerare una sorgente statica di un singolo frame, lo stream che parte da un implementazione di vetInput è di fatto un oggetto che rappresenta un’immagine in modo digitale. Il formato immagine (la definizione dell’oggetto frame) deve essere riconosciuto da tutti i componenti ed è uno standard in VETLib (vetFrame..), il caso di sequenze video è una banale estensione nel tempo. Lo stream non è prettamente dinamico, ad ogni chiamata delle funzioni di interfaccia (extractTo/importFrom oppure operatori di streaming) segue il caricamento di un singolo frame, il controllo del flusso dati è gestito dal livello applicazione o da moduli appositi (vetProcess) che possono ovviamente eseguire cicli di indirizzamento/estrazione dei frames. Questa situazione classica e semplice può apparentemente complicarsi nel caso un filtro abbia la necessità di lavorare su n frames (motion detection, tracking ..), anche in questo caso non vi è alcuna differenza, il filtro sarà dotato di un multiplo buffer interno (basato sui buffer integrati nella libreria o implementazioni proprietarie) e ci sarà semplicemente un ritardo tra il primo frame in ingresso al filtro e il primo frame estratto (gap di n-1 frames), è compito del filtro gestire eventuali richieste di estrazione (da un oggetto vetOutput) non valide (ad esempio non si è raggiunto il numero di frame minimi per il processing). L’astrazione teorica è proiettata nel livello di programmazione tramite l’ereditarietà e la composizione di classi, l’implementazione di una delle interfacce base (vetInput, vetOutput, vetFilter, ..) prevede lo sviluppo di una classe che eredita (in modo pubblico) la classe astratta di riferimento, ricordo che una classe è astratta se almeno uno dei suoi metodi è puramente virtuale: class myStorageInterface { public: myInterface() {}; virtual ~myInterface() {}; virtual bool AddItem(void*) = 0; virtual int getItemsCount(void) = 0; }; 17 Questa classe è puramente astratta (pure abstract), chiaramente il compilatore non può istanziare una classe astratta poiché i metodi sono stati dichiarati ma non vi è alcuna implementazione, la seguente classe invece è un oggetto conforme a myStorageInterface e che può essere istanziato: class myLameStorage : public myStorageInterface { public: myObject() { i_c = 0; }; ~myObject() { }; bool AddItem(void* newItem) { if ( i_c => MAX_ITEMS ) return false; data[i_c++] = newItem; return true; }; int getItemsCount(void) { return i_c; }; protected: int i_c; void* data[MAX_ITEMS]; //static }; Questa strategia di programmazione è l’applicazione pratica dell’analisi astratta precedentemente illustrata, in questo modo pur non conoscendo il funzionamento interno e l’obbiettivo di un oggetto, è possibile interagire tramite le proprietà che necessariamente deve implementare (visto che eredita una classe base). Riconducendosi al precedente esempio: class myDynamicArrayStorage : public myStorageInterface { public: myObject() { i_c = 0; dataSize = 10; data = new void*[dataSize]; }; ~myObject() { delete data; }: // [..] bool AddItem(void* newItem) { if ( i_c => dataSize ) { } else // [..] realloc array [current + REALLOC_STEP] data[i_c++] = newItem; data[i_c++] = newItem; return true; }; int getItemsCount(void) { return i_c; }; protected: }; int i_c; int dataSize; void** data; //dynamic 18 Le due implementazioni sono chiaramente differenti ma entrambe rispondono ai metodi dichiarati nella classe madre myStorageInterface, questa interfaccia comune garantisce il livello di astrazione tramite un classico casting: int main() { myStorageInterface* storage; // [..] int choice = 0; cout << “Select storage system {0, 1}: ”; cin >> choice; if ( choice ) storage = new myDynamicArrayStorage(); else storage = new myLameArrayStorage(); // [..] for (int i=0; i<100; i++) { int* newItem = new int; *newItem = i; storage->AddItem( static_cast (newItem) ); } // [..] cout << “There are ” << storage->getItemsCount() << “ items.”; }; // delete objects (…in this way they are lost, but it’s a sample) delete storage; // [..] In questo esempio la scelta della classe che memorizza gli oggetti è effettuata dall’utente ed è creata all’interno della stessa funzione, è il caso più banale visto che si conoscevano le due classi già durante la compilazione, in generale non è cosi e si ha la definizione della sola interfaccia che implementano. Oltre all’ereditarietà possiamo sfruttare anche la composizione di classi: class dataSource { public: virtual int getItem(Item* newItem) = 0; } class dataOutput { public: virtual int setItem(Item* newItem) = 0; } class dataProxy : public dataSouce, public dataOutput { public: int setItem(Item* newItem) { .. }; int getItem(Item* newItem) { .. }; } 19 La classe dataProxy implementa entrambe le interfacce e se si considera dataSource come una sorgente di oggetti Item e dataOuput come una destinazione di oggetti (si può pensare due classi che rispettivamente leggono e scrivono da file), allora possiamo pensare all’oggetto dataProxy come un filtro o un buffer: // [..] create or load instances.. dataSource* myInput; // .. myInput is an implementation of dataSource dataProxy* myFilter; // .. myFilter is an implementation of dataProxy dataOutput* myOutput; // .. myOutput is an implementation of dataOutput Item* currItem = new Item(); int I = 0; // the buffer object while (i++ < 100 && !myInput->EOF() ) { myInput->getItem(currItem); // myFilter->setItem(currItem); // myFilter->getItem(currItem); // myOutput->setItem(currItem); // } load the new object from source push it to data proxy extract last item push to final output L’oggetto dataProxy può essere un semplice buffer oppure può modificare l’oggetto, ma qualunque implementazione delle interfacce base consente l’interazione tramite oggetti Item. L’analogia con VETLib è semplicissima: dataSource è paragonabile alla classe vetInput, dataOutput è analoga all’interfaccia vetOutput e dataProxy corrisponde a vetFilter. L’oggetto Item corrisponde agli oggetti che rappresentano un frame: vetFrameRGB24, vetFrameYUV420 oppure vetFrameT . Prima di analizzare le interfacce base e i principali oggetti è opportuno puntualizzare alcune definizioni contenute nell’header vetDefs.h che è incluso in tutte le classi della libreria, l’operato di un metodo è spesso comunicato alla funzione chiamante tramite il valore di ritorno (classico bool, int, HRESULT), in VETLib questo stile è spesso imposto e sempre suggerito, il tipo standard è: typedef int VETRESULT; ed i valori di ritorno standard sono i seguenti, qualora necessario definire valori superiori a 8200: #define #define #define #define VETRET_OK VETRET_PARAM_ERR VETRET_INTERNAL_ERR VETRET_ILLEGAL_USE 0 1 2 4 /* /* /* /* no errors found */ illegal parameter(s) */ internal routine error */ illegal use of function */ #define VETRET_DEPRECATED_ERR #define VETRET_OK_DEPRECATED 8 16 /* old version, removed? */ /* old version, ex.bad conversion */ #define VETRET_NOT_IMPLEMENTED 666 /* not supported/implemented */ Inoltre sono definite una serie di MACRO utili durante lo sviluppo (definire il flag per abilitarle): #ifdef __VETLIB_DEBUGMODE__ #include #define INFO(x) printf("_NFO: %s\n"); #define DEBUG(x) printf("_DBG: %s = %p \n", #x, x); #define DEBUGMSG(msg, x) printf("_DBG: %s %s = %p \n", msg, #x, x); #else #define INFO(x) ; #define DEBUG(x) ; #define DEBUGMSG(msg, x) ; #endif //__VETLIB_DEBUGMODE__ 20 1.4 - vetFrame Pure Abstract Class vetFrame.h Un video digitale (anche analogico) è campionato nel tempo ed è classicamente rappresentato da una sequenza di immagini, la rapidità con cui le immagini si susseguono (frame per second) dà allo spettatore l’illusione della continuità (PAL: 25fps, NTSC: 30fps). Le principali informazioni legate a un immagine digitale sono: - VETCLASS_TYPE_FRAME Header @ pg. 127 Risoluzione (width, height) Rappresentazione del Pixel (pel) Un singolo frame è rappresentato da una matrice bidimensionale di pixels, l’oggetto pixel definisce il colore di un punto, chiaramente sono possibili molteplici rappresentazioni del colore (Color Space), le tecniche di compressione sono basate anche sulla correlazione spaziale oltre che sul sistema di rappresentazione, ma durante il processing i dati sono normalmente non compressi visto che l’accesso ai pixel deve essere standard e ottimizzato. La sintesi di un colore in una memoria digitale è ovviamente legata al numero di bit a disposizione: Bits per pixel Numero valori (colori) rappresentabili 1 2 4 8 16 24 2 4 16 256 65,536 16,777,216 Calcolo 2^1 2^2 2^4 2^8 2^16 2^24 Esempio con pixel a 4 bit: 0 0 0 0 0 0 0 0 0 3 Dec Bin 00 0 3 1 3 1 3 1 1 1 3 0 0 3 1 3 1 3 1 3 3 3 1 01 2 10 3 11 0 3 1 3 1 3 1 1 3 3 0 3 2 1 2 3 1 3 3 3 0 3 3 1 3 3 1 1 1 3 0 2 3 3 3 3 3 3 3 3 0 3 2 2 2 2 2 2 3 3 0 3 3 3 3 3 3 2 1 3 0 3 3 3 3 3 3 3 2 1 Color Palette Questa matrice può essere memorizzata in un array monodimensionale in sequenza raster: image { 0, 0, …} 0, 3, 0, 1, 0, 3, 0, 1, 0, 3, 0, 1, 0, 1, 0, 1, 3, 3, Cioè in bits: {00 00 00 00 00 00 00 00 00 11 00 11 01 11 01 11 01 01 01 11 …} Finora si è considerato il caso monocromatico, il colore è un argomento molto più ampio di quello che si crede ed è estremamente legato alla pura matematica (comunemente la conversione tra spazi colore diversi viene fatta attraverso calcoli matriciali), il colore percepito dipende dall’assorbimento e dalla rifrazione dei materiali, l’analisi più diffusa e intuitiva distingue i tre colori base: rosso, verde e blu, attraverso la sintesi additiva (composizione lineare) è possibile sintetizzare qualunque colore partendo da questo insieme base. Gli spettri sono parzialmente sovrapposti e la nostra capacità di percepire le diverse frequenze non è costante, in particolare l’occhio umano ha una buona capacità di vedere la luminosità e i contrasti (scala di grigio), lo studio del sistema visivo è importante proprio perché l’imperfezione con cui percepiamo stimoli visivi è ampliamente sfruttata nelle tecniche di compressione (il medesimo concetto è espresso in campo uditivo, ad esempio, con 21 il formato MP3: percepiamo suoni nel range 20÷20.000 Hz, quindi applico un filtro passa-banda e quantizzo diversamente). Seguono tre grafici dello spettro luminoso visibile che sottolineano la frequenza “centrale” per ognuno dei colori base: È possibile rappresentare un colore in molti modi, ma esistono essenzialmente due grandi macrogruppi differenti: RGB e YUV. Il formato RGB (Red, Green, Blue) è stato introdotto con le considerazioni precedenti ed è diffuso nel processing digitale di immagini, il sistema di rappresentazione coincide con quello presentato all’inizio di questa sezione moltiplicato per tre canali, il risultato finale è la sovrapposizione (somma) dei tre livelli (layers), il formato più comune rappresenta i pixel con 24 bits, 8 bits per canale (3 char, cioè 3 bytes). In seguito alle considerazioni sul sistema visivo e alla nascita del broadcasting televisivo (PAL, NTSC) si è consolidato un sistema di rappresentazione alternativo: YUV, un tempo utile per la compatibilità con i vecchi apparecchi (tv in bianco e nero), oggi utile per minimizzare la memoria, la diffusione dello spazio YUV è anche dovuta all’importanza predominante della luminosità rispetto all’informazione colore nella maggior parte degli algoritmi di processing video, lo spazio RGB infatti non prevede un canale che sintetizzi le informazioni sulla luminosità dell’immagine che dovrebbe essere estratta attraverso una composizione lineare (tempo di processing!). Lo spazio YUV distingue tre canali: uno per la luminosità (Luminance) e due per il colore (Crominance1, Crominance2), vi sono molte rappresentazioni diverse, la maggior parte sottocampiona orizzontalmente e/o verticalmente i valori delle crominanze diminuendo la memoria necessaria. Ad esempio il formato YUV 4:2:0 (vetFrameYUV420) memorizza due valori di crominanza (Cb e Cr) per ogni gruppo di quattro valori di luminosità. A prescindere dallo spazio colore scelto, le informazioni (i valori) possono essere raggruppati in pixels (o nel caso YUV in macropixels) disposti in sequenza, in un array di lunghezza width*heigth, oppure in modo planare (planar), questa disposizione prevede di disassemblare l’entità pixel negli n valori (tipicamente tre) e disporli in sequenza raster divisi per canale, segue la rappresentazione di quattro pixel bianchi: Entrambe le soluzioni offrono vantaggi e svantaggi, lo spazio colore RGB è rappresentato di norma in modo packed, mentre lo spazio YUV massimizza i vantaggi con sistema planare, moltissimi filtri lavorano solo sulla luminosità dell’immagine e quindi nel caso dell’RGB, ad esempio, oltre che gestire memoria inutilmente si richiedono calcoli aggiuntivi per l’estrazione del piano luminosità dai tre canali. 22 VETLib propone alcune implementazioni di frame (vetFrameYUV420, vetFrameRGB24, ..) basate sugli standard di rappresentazione più comuni e un particolare oggetto vetFrameT in grado di adattarsi a necessità specifiche dello sviluppatore. Tutto gli oggetti frame implementano l’interfaccia vetFrame (VETCLASS_TYPE_FRAME) nella quale sono definiti una serie di prototipi ed alcune variabili pubbliche: unsigned int unsigned int long width; height; timeStamp; I due interi descrivono le dimensioni dell’immagine e la variabile timeStamp è dedicata a contesti real-time e identifica il tempo di creazione del frame (serve ai controlli di PlayOut), sono direttamente implementate le funzioni getHeight e getWidth (anche const). Il metodo standard di (ri)allocazione della memoria è imposto dal prototipo: virtual void reAllocCanvas(unsigned int w, unsigned int h) = 0; La funzione reAllocCanvas, chiamata anche dai costruttori, sostituisce i metodi setWidth e setHeight, da notare che i dati precedenti non sono (necessariamente) mantenuti o copiati nel nuovo canvas (il piano immagine), più avanti in questo capitolo sono riportate le implementazioni di ogni oggetto immagine. Le informazioni sul sistema di rappresentazione adottato dagli oggetti frame sono accessibili tramite i metodi imposti da vetFrame: virtual VETFRAME_PROFILE getProfile() = 0; virtual VETFRAME_CHANNEL_TYPE getChannelType() = 0; che definisce le due enumerazioni: enum VETFRAME_CHANNEL_TYPE { VETFRAME_CT_NONE, VETFRAME_CT_PIXELPACKED, VETFRAME_CT_PLANAR, VETFRAME_CT_CUSTOM }; e enum VETFRAME_PROFILE { VETFRAME_NONE, //empty VETFRAME_BITPLANE //bool {0, 1} VETFRAME_MONO, //grayscale // RGB formats VETFRAME_RGB24, VETFRAME_BGR24, VETFRAME_RGB96, VETFRAME_BGR96, //standard //also standard //standard //also standard VETFRAME_RGB565, VETFRAME_BGR565, VETFRAME_RGB555, VETFRAME_BGR555, //pixel //pixel //pixel //pixel 16bit! 16bit! 16bit! 16bit! VETFRAME_ARGB32, VETFRAME_ABGR32, VETFRAME_RGBA32, VETFRAME_BGRA32, //alpha + . //alpha + . //. + alpha //. + alpha 23 // YUV formats VETFRAME_I420, VETFRAME_YV12, VETFRAME_YUY2, VETFRAME_UYVY, VETFRAME_YVYU, VETFRAME_AYUV, VETFRAME_CUSTOM }; //4:2:0 //4:2:0 //4:2:2 //4:2:2 //4:2:2 planar planar packed packed packed (=IYUV) (common in MPEG: NxM Y + N/2*M/2 V U ) (common in AVI and hardware devices) (radius cinepack, mpeg codecs) //alpha + 4:4:4 planar //not listed here.. Sono stati definiti una serie di codici standard univoci per identificare i formati possibili, questo codice (int) è detto FOURCC e classicamente è riportato negli headers (primi 4 bytes), il sito http://www.fourcc.org analizza nel dettaglio la maggior parte dei formati esistenti, gli oggetti frame di VETLib identificano il proprio codice con il prototipo imposto da vetFrame: virtual int getFOURCC() = 0; La classe vetFrame impone anche l’implementazione dei due prototipi dedicati all’accesso diretto: virtual void* dump_buffer() = 0; virtual unsigned int getBufferSize() = 0; //in bytes in tutti gli oggetti frame implementati il buffer è chiamato data ed il codice è banalmente un casting inline nello stile C++: void* dump_buffer() { return static_cast (data); }; oltre all’accesso al puntatore dei dati, spesso può essere utile anche un metodo che estragga la luminosità (brightness) in un formato classico: virtual VETRESULT extractBrightness( unsigned char* buffer, unsigned int* size = NULL ) = 0; la memoria deve essere allocata dalla funzione chiamante (unsigned char[size]), la dimensione può essere letta con i metodi getWidth e getHeight oppure effettuando una chiamata precedente del metodo con il parametro buffer impostato a NULL (come lo stile della programmazione avanzata per Windows): unsigned int size = 0; vetFrameRGB24->extractBrightness(NULL, &size); if ( size == 0 ) return; unsigned char* greyBuffer = new unsigned char[size]; vetFrameRGB24->extractBrightness(greyBuffer, NULL); L’allocazione del buffer negli oggetti frame di VETLib non prevede l’inizializzazione dei pixel (spesso è solo una perdita di tempo), i seguenti prototipi aggiornano il canvas (piano immagine) rispettivamente con la minima e la massima luminosità, i valori dipendono dalla rappresentazione: virtual VETRESULT setBlack() = 0; virtual VETRESULT setWhite() = 0; L’accesso alla memoria può essere ottimizzato in modo molto semplice, un ciclo for che azzera i pixel è tremendamente inefficiente rispetto a metodi di sistema come memset, consultare la sezione “Working with Frames” nel capitolo terzo per dettagli ed esempi pratici. 24 1.4.1 - vetFrameYUV420 vetFrameYUV420.h VETCLASS_TYPE_FRAME Questo oggetto frame è uno dei tre standard I/O di VETLib, i dati (pixels) VETFRAME_I420 VETFRAME_CT_PLANAR sono rappresentati secondo il formato YUV 4:2:0 planare, si ottimizza la 0x30323449 memoria occupata sotto-campionando spazialmente nelle due direzione Header @ pg. 129 l’informazione associata al colore (le due crominanze U e V). I singoli valori (sia luminanza Y che U e V) sono memorizzati come unsigned char (8 bits) in un array di lunghezza complessiva width * height * 1.5, dove i primi width * height bytes (caratteri: un byte = 8 bits) corrispondono ai valori della luminanza in sequenza raster e i seguenti width / height / 2 caratteri sono divisi in due piani consecutivi e rappresentano i valori U e V rispettivamente. Il buffer è dichiarato public: unsigned char *data; I seguenti puntatori sono ausiliari e devono essere usati semplicemente come scorciatoia per l’accesso ai tre canali: unsigned char *Y; // = data; unsigned char *U; // = data + width * height; unsigned char *V; // = data + (int)( width * height * 1.25 ); Il metodo (imposto da vetFrame) designato ad (ri)allocare il buffer è riportato per intero: void vetFrameYUV420::reAllocCanvas(unsigned int w, unsigned int h) { if (data != NULL) { delete [] data; data = NULL; } Y = NULL; U = NULL; width = w; height = h; V = NULL; if ( width != 0 && height != 0) { data = new unsigned char[ width * height * 1.5 ]; Y = data; U = data + width * height; V = data + (int)( width * height * 1.25 ); } } La maggior parte dei filtri operano sulla brightness (luminosità) dell’immagine, sia per la quantità di informazione associata sia per ridurre il numero di operazioni, questo formato è particolarmente adatto all’accesso veloce e sequenziale a questi valori (piano Y) pur conservando le informazioni sul colore (qualità ridotta ma accettabile). Segue la banale implementazione del prototipo imposto da vetFrame: virtual VETRESULT extractBrightness( unsigned char* buffer, unsigned int* size = NULL ) { if (buffer == NULL) { if ( size == NULL) return VETRET_PARAM_ERR; *size = width*height; return VETRET_OK; } memcpy (data, buffer, width*height); // plane Y = luminance return VETRET_OK; } 25 1.4.2 - vetFrameRGB24 vetFrameRGB24.h VETCLASS_TYPE_FRAME La classica rappresentazione del colore nei canali Rosso, Verde e Blu (in VETFRAME_RGB24 VETFRAME_CT_PACKED questo ordine) è implementata in questo oggetto immagine con un array 0x32424752 monodimensionale (data) di PixelRGB24, ogni istanza di PixelRGB24 è un Header @ pg. 128 singolo pixel a cui corrispondono i tre valori memorizzati nel tipo standard unsigned char. L’array data contiene quindi width * height pixel in sequenza raster, ogni pixel è costituito da tre caratteri (8 bits ciascuno, un byte), i valori spaziano nel range [0,256[ (è unsigned), secondo la convenzione classica il valore zero corrisponde al nero ed il valore 255 è la massima luminosità (sintesi additiva), la combinazione dei tre canali permette di rappresentare 16,777,216 colori (SVGA). Questo formato corrisponde al codice FOURCC 0x32424752 (che non dipende dal numero di bit), il profilo (VETFRAME_PROFILE) è VETFRAME_RGB24 e naturalmente il sistema di memorizzazione (VETFRAME_CHANNEL_TYPE) è di tipo VETFRAME_CT_PACKED. L’allocazione del buffer è estremamente banale: void vetFrameRGB24::reAllocCanvas(unsigned int w, unsigned int h) { if (data != NULL) delete [] data; height = h; width = w; data = NULL; if ( w != 0 && h!= 0) data = new PixelRGB24[w * h]; } Si potrebbe allocare anche in modo diretto poiché in effetti l’assembly equivale a data = new unsigned char[width * height * 3] ed infatti la seguente conversione (casting) è corretta: PixelRGB24 *pBuffer; unsigned char *rawBuffer rawBuffer = (unsigned char*)pBuffer[0]; // or better: rawBuffer = static_cast (pBuffer[0]); // C style // C++ style 26 1.4.3 - vetFrameT Questa classe è la più generica e complessa struttura dati che rappresenta un frame, lo sviluppatore può configurare l’oggetto in modo da ottimizzare la memoria ed implementare strutture di pixel e metodi di accesso specifici. Analizziamo le principali variabili (dichiarate public e quindi accessibili): Template Class vetFrameT.h VETCLASS_TYPE_FRAME VETFRAME_CUSTOM VETFRAME_CT_ CUSTOM variable Header @ pg. 130 • unsigned int width; Valore intero positivo che indica la larghezza (x) dell’immagine. • unsigned int height; Valore intero positivo che indica l’altezza (y) dell’immagine. • T *data; Array monodimensionale di oggetti T (template) contenente i pixel (ex. char, PixelRGB24). • bool autoFreeData; Indica se l’oggetto deve liberare i dati quando viene distrutto (distruttore), è utile per ottimizzare la memoria nel caso un cui si voglia “riciclare” un buffer esistente. Il valore predefinito è TRUE. Ad esempio: unsigned char* buffer = new unsigned char[width*height*3]; // fill the buffer…. // [..] vetFrameT * fakeFrame = new vetFrameT (); // no data allocated, it’s an empty object // setup the fake vetFrameT object fakeFrame->autoFreeData = false; fakeFrame->width = width; fakeFrame->height = height; fakeFrame->data = dynamic_cast< PixelRGB24* >(buffer); //we force the buffer //call an external function which require a vetFrameT . processFunct(fakeFrame, output); // [..] delete fakeFrame; // buffer has NOT been deleted • enum VETFRAME_PROFILE profile; Questa variabile dichiara il sistema corrente di rappresentazione del pixel, ovviamente è necessario che un algoritmo (una funzione) conosca il modo di rappresentare l’immagine per poter elaborare i dati. Le rappresentazioni possibili sono definite in vetFrame, la funzione getProfile(), restituisce il formato corrente. • enum VETFRAME_CHANNEL_TYPE dataType; Definisce il modo si immagazzinare i pixel (vedere vetFrame), ovviamente questo parametro è legato al formato utilizzato (VETFRAME_PROFILE), alcuni ammettono entrambe le rappresentazioni (RGB), mentre altri definiscono intrinsecamente anche la disposizione dei pixel, in tal caso il parametro prioritario è il profilo (altrimenti viene considerato prima il profile e poi il dataType). 27 La classe offre una serie di metodi classici per l’accesso ai pixel e la gestione della memoria, tali implementazioni sono fortemente legate la formato scelto, i profili definiti in vetFrame (VETFRAME_PROFILE) non sono necessariamente tutti supportati (anche perché potrebbero essere aggiornati in modo asincrono) quindi il metodo: bool isBuiltInSupportedProfile(VETFRAME_PROFILE pr); certifica se il profilo richiesto è supportato dai metodi di base (setPixel, getPixel, reAllocCanvas, ..), lo sviluppatore può comunque accedere alle variabili principali e quindi implementare soluzioni proprietarie utilizzando la classe esclusivamente come struttura dati, lo stile di VETLib prevede l’uso classico dei costruttori (cioè gestione memoria affidata all’oggetto frame, classicamente con il metodo reAllocCanvas) e l’accesso diretto ai pixel tramite il buffer pubblico (si consiglia di evitare l’uso di setPixel, getPixel) Il codice di identificazione del formato FOURCC dipende chiaramente dal profilo corrente ed è implementato in questo modo: int getFOURCC() { switch( profile ) { case case case case case vetFrame::VETFRAME_MONO: vetFrame::VETFRAME_RGB24: vetFrame::VETFRAME_BGR24: vetFrame::VETFRAME_RGB32: vetFrame::VETFRAME_BGR32: return 0x32424752; // [..] case vetFrame::VETFRAME_I420: return 0x30323449; } } // [..] Lo stile switch(profile) {..} è altamente consigliato ed è attualmente presente in tutti i metodi che coinvolgono l’oggetto vetFrameT. La configurazione predefinita (costruttore default) è la seguente: width height data autoFreeData dataType profile = = = = = = 0; 0; NULL; true; vetFrame::VETFRAME_CT_NONE; vetFrame::VETFRAME_NONE; L’utilizzo pratico di una classe template può essere focalizzato su un tipo in particolare: vetFrameRGB24* source = new vetFrameRGB24(320, 240); // .. fill source.. vetFrameT *buff; buff = new vetFrameT (); //it’s: w=0; h=0; data=NULL buff->width = source.width; buff->height = source.height; buff->profile = vetFrame::VETFRAME_RGB24,; buff->dataType = vetFrame::VETFRAME_CT_PACKED; buff->autoFreeData = false; buff->data = (unsigned char)source->data[0]; myTemplateBasedMethod(buff); delete buff; //source data (vetFrameRGB24) was updated through vetFrameT proxy 28 Oppure si può mantenere l’astrazione in un metodo: template static int doProcessing( vetFrameT &source, vetFrameT &dest, vetDFMatrix& kernel { switch( profile ) { // [..] dest.data[y * src_w + x] = (S)numb; // [..] } } ) In questo caso si ammette una conversione di tipo automatica, l’astrazione rischia di portare ad errori ed aberrazioni cromatiche (ex. overflow), lo sviluppatore deve assumersi il compito di eventuali conversioni dello spazio colore. Come analizzeremo nel dettaglio più avanti in questo capitolo uno degli standard I/O tra i componenti (vetInput, vetOutput) è proprio l’oggetto vetFrameT: VETRESULT extractTo(vetFrameT& img); La scelta è caduta sul tipo unsigned char per due ragioni: non era possibile mantenere l’astrazione template poiché il C++ non ammette metodi virtual tempate (inoltre sarebbe stato abbastanza problematico gestire l’oggetto nei filtri), il tipo (unsigned) char è lo standard di accesso alla memoria per C e C++, quindi si può “ufficiosamente” utilizzare un altro tipo e accedere ai dati tramite il puntatore char (l’operatore new è basato su malloc che come ben sanno i programmatori C, alloca solo caratteri). Virtualmente non vi sono limitazioni al formato rappresentato, ciò rispecchia gli obbiettivi di portabilità e scalabilità di VETLib, i metodi (come extractTo) avranno comportamenti diversi a seconda del formato (vetFrameT::profile). Alcuni esempi di utilizzo dei template in C++ sono trattati nel capitolo terzo, sezione “Templates”. 1.4.4 - vetFrameRGBA32 vetFrameRGBA32.h Questo oggetto frame differisce dagli altri standard per il numero di canali VETCLASS_TYPE_FRAME VETFRAME_RGBA32 gestiti, si è aggiunto l’Alpha Channel che indica la trasparenza del pixel, i VETFRAME_CT_PLANAR singoli valori sono memorizzati come unsigned char e quindi un pixel 0x41424752 riempie i classici registri da 32 bits, le schede grafiche di ultima generazione e Header @ pg. 131 il framework DirectDraw hanno reso questo formato molto diffuso soprattutto in campo ludico. La disposizione dei dati è di tipo planare (quattro piani consecutivi di width*height bytes). I calcoli possono spesso essere ottimizzati con le seguenti maschere da applicare ad un pixel 32bit: Red Green Blue Alpha 0xFF000000 0x00FF0000 0x0000FF00 0x000000FF 29 1.4.5 - vetFrameGrey vetFrameGrey.h VETCLASS_TYPE_FRAME Questa classe rappresenta immagini (mono-canale) a scala di grigio VETFRAME_MONO VETFRAME_CT_PACKED (brightness) e fornisce metodi del tutto simili agli oggetti frame 0 precedentemente analizzati, il pixel è PixelGrey, definito come unsigned char Header @ pg. 134 (8 bits = 1 byte), il profilo è VETFRAME_MONO, la struttura è definita come VETFRAME_CT_PACKED, ma in realtà potrebbe essere definita anche come VETFRAME_CT_PLANAR visto che un pixel è rappresentato con un singolo valore, il codice FOURCC non è definito. L’allocazione del buffer è banale (data = new PixelGrey[w * h];), segue un frammento più interessante che converte l’immagine in formato YUV: vetFrameGrey& vetFrameGrey::operator >> (vetFrameYUV420& img) { if ( width == 0 || height == 0 ) throw "Cannot do that with empty image (no size)"; if ( width != img.width || height != img.height) img.reAllocCanvas(width, height); // valid because pixelgrey is uchar! memcpy(img.data, data, width * height); memset(img.U, '\0', width * height / 2); // u,v set to 0 // not optimized: // img.setBlack(); // const unsigned int size = width * height; // for (unsigned int i=0; i < size; i++) // img.data[i] = (unsigned char)( data[i] ); return *this; } 1.4.6 - vetFrameRGB96 vetFrameRGB96.h VETCLASS_TYPE_FRAME La classe è pressoché identica a vetFrameRGB24, differisce solo per la VETFRAME_RGB96 rappresentazione dei pixel, in questo caso si tratta di PixelRGB96 che VETFRAME_CT_PACKED 0x32424752 memorizza ogni valore (r, g, b) come integer (32 bits, quindi 96 bits per pixel), l’occupazione di memoria è sovradimensionata (si usano i valori Header @ pg. 132 [0,256[), ma talvolta si trovano librerie e applicazioni in cui è utile questo formato. Nel caso di sequenze video la quantità di memoria necessaria è proibitiva (anche un frame piccolo 320*240 richiede un’occupazione di memoria di 900Kb) e si preferisce utilizzare vetFrameRGB24. La selezione del canale, richiesta in alcuni metodi, viene effettuata attraverso un parametro di tipo enum ChannelRGB { RED, GREEN, BLUE } (e quindi vetFrameRGB96::RED), il profilo (VETFRAME_PROFILE) è naturalmente VETFRAME_RGB96 con una disposizione in memoria (VETFRAME_CHANNEL_TYPE) di tipo VETFRAME_CT_PACKED, il codice FOURCC è il medesimo di vetFrameRGB24. 30 1.4.7 - vetFrameHSV vetFrameHSV.h VETCLASS_TYPE_FRAME L’oggetto vetFrameHSV rappresenta le immagini secondo lo standard HSV, VETFRAME_HSV VETFRAME_CT_PACKED diffuso nell’ambito dell’image processing, i tre valori che contraddistinguono 0 il pixel sono: Hue, che corrisponde un angolo e varia nel range [0, 360[, Header @ pg. 133 Saturation e Value che indicano le due coordinate e possono assumete i valori [0,256[. La scelta del range non è univoca, spesso nei calcoli si considerano i valori normalizzati, ma per memorizzare i dati è più conveniente la scelta di VETLib: unsigned short int hue; unsigned char sat; unsigned char vat; //16bit //8bit //8bit | |= 32bit | L’accesso al pixel è diverso dagli altri casi poiché l’array non è uniforme, le tre variabili sono dichiarate pubbliche e quindi il codice diventa: data[offset].hue = hue; data[offset].sat = sat; data[offset].val = val; Questo oggetto è stato creato per compatibilità ma di fatto attualmente non è usato in alcun modulo della libreria. 1.4.8 - libETI support Le due classi vetFrameRGBETI e vetFrameGreyETI sono il punto di contatto tra VETLib e la vecchia libreria di elaborazione immagini (libETI), la gestione della memoria è affidata alle relative classi (libETI::picture e libETI::image, rispettivamente). L’uso di questi oggetti è deprecato, l’implementazione delle estensioni di ETILib non consente prestazioni accettabili per il processing di più immagini, i pixel sono memorizzati esclusivamente in canali RGB planari di floating point, non accessibili direttamente, inoltre le classiche operazioni sono effettuate su una copia dell’immagine e non sull’originale. Purtroppo la scarsa portabilità di ETILib e la diversità intrinseca rispetto a VETLib non consentono di convertire i lavori svolti in maniera semplice, è necessario modificare abbondantemente il codice a mano. 31 1.5 - vetInput Abstract Class vetInput.h Questa interfaccia definisce lo standard di acquisizione dati di VETLib, le VETCLASS_TYPE_INPUT implementazioni specifiche si occupano di decodificare i dati da uno stream Header @ pg. 136 dinamico o statico (prelevato ad esempio da un device) e convertirli nel formati standard di VETLib: vetFrameYUV420, vetFrameRGB24 e possibilmente vetFrameT. L’interfaccia prevede l’implementazione dei prototipi: virtual VETRESULT extractTo(vetFrameYUV420& img) = 0; virtual VETRESULT extractTo(vetFrameRGB24& img) = 0; virtual VETRESULT extractTo(vetFrameT & img) = 0; Gli operatori di streaming sono implementati in modo da gestire (se richiesto) una frame rate costante in uscita, l’utilizzo pratico è banale (vetNoiseGenerator >> bufferImg): vetInput& operator >> (vetFrameYUV420& img); vetInput& operator >> (vetFrameRGB24& img); vetInput& operator >> (vetFrameT & img); vetInput& vetInput::operator >> (vetFrameRGB24& img) { setElaborationStart(); extractTo(img); if ( v_sleeptime && v_sleeptime > (long)getElaborationTime()) vetSleep( v_sleeptime - (long)getElaborationTime() ); // ms return *this; } In questo modo, per i casi più elementari, è sufficiente implementare l’algoritmo di estrazione e preoccuparsi solo di minimizzare il tempo di esecuzione, se la frame rate impostata fosse minore infatti, vetSleep(long millisec) allinea la restituzione del processo con il tempo previsto. Le sorgenti più complesse possono sovra-scrivere gli operatori. Questa soluzione può essere utile in un contesto di testing e di debug, ovviamente è deprecabile abbassare la frame rate all’inizio di una catena di sistemi. Normalmente si può ignorare completamente la gestione della frame rate (v_sleeptime = 0). float getFrameRate() const { return v_framerate; }; VETRESULT setFrameRate(float fps) { // [..] v_sleeptime = (long)( (float)1000 / fps ); // milliseconds // [..] } La libreria standard di I/O ci suggerisce che è molto utile disporre di una funzione che restituisca l’attuale stato dello stream, l’algoritmo che gestisce il flusso di dati potrebbe (verosimilmente) cambiare il suo comportamento quando non ci sono più nuovi frame da caricare, in alcuni casi si potrebbe addirittura incorrere in errori grossolani (ad esempio un sistema di video sorveglianza che non si accorge dello spegnimento della telecamera potrebbe valutare sempre l’ultimo frame, ovviamente uguale al precedente), il metodo da implementare è il classico acronimo di End Of File: virtual bool EoF() = 0; che restituisce false se l’estrazione di frame procede correttamente ed è disponibile il prossimo fotogramma, true se lo stream è terminato. Un altro metodo molto comune nella programmazione a oggetti è l’azzeramento dei parametri correnti e l’impostazione della configurazione predefinita, il prototipo è virtual VETRESULT reset() = 0; 32 L’interfaccia impone anche di implementare i seguenti due metodi per la lettura della risoluzione: virtual unsigned int getHeight() const = 0; virtual unsigned int getWidth() const = 0; Il costruttore predefinito (pubblico) permette anche di impostare una frame rate (il valore fps va ad inizializzare v_sleeptime), tipicamente vengono inizializzati eventuali buffer a NULL e si delega alla funzione reset() il compito di impostare i parametri predefiniti. Ad esempio: vetVideo4Linux::vetVideo4Linux(float fps) : vetInput(fps) { fd = -1; videoBuffer = NULL; // it’s a char buffer reset(); } Dove reset è implementato: VETRESULT reset() { disconnect(); if (videoBuffer != NULL) delete [] videoBuffer; videoBuffer fd win.height win.width win.height vpic.palette } = = = = = = NULL; -1; 0; 0; 0; 0; return VETRET_OK; Suggerisco di portare attenzione all’ordine di inizializzazione dei parametri sia in fase di creazione dell’oggetto che durante il processo, in questo caso se non si fosse impostato il puntatore videoBuffer a NULL nel costruttore si sarebbe incorsi in un eccezione di accesso alla memoria. Inoltre è buona norma impostare sempre il valore di un puntatore a NULL dopo aver liberato la memoria. Seguono alcuni esempi semplificati estratti da classi di tipo vetInput: VETRESULT vetPlainFrameGenerator::extractTo(vetFrameRGB24& img) { memcpy( (unsigned char*) img.data[0], (unsigned char*) bufferRGB->data[0], width * height * 3 * sizeof(unsigned char) ); return VETRET_OK; } VETRESULT { unsigned unsigned unsigned vetNoiseGenerator::extractTo(vetFrameRGB24& img) int size = img.width * img.height; int perc = (unsigned int)( (float)(size) * spread); int noiseco; for ( unsigned int i = 0; i < perc; i++ ) { noiseco = rand() * rand() % size; img.data[noiseco][0] = (unsigned char)( rand() % 255 ); } } // [..] same code for the 3 channels 33 Nella maggior parte dei casi il formato di rappresentazione della sorgente implica conversioni dello spazio colore, è utile ricordare che ad ogni conversione si degrada l’informazione originale, lo stile di VETLib prevede che lo sviluppatore ottimizzi un formato (es. YUV420 o RGB24) e non applichi conversioni automatiche per supportare gli altri, eventualmente deve occuparsene l’applicazione che utilizza il modulo (cosi come il flusso dati). Il seguente esempio, estratto dal modulo vetVideo4Linux, estrae lo stream dal framework v4l attraverso la classica funzione read, la struttura vpic (precedentemente inizializzata) contiene le informazioni riguardo al formato nativo, e qualora sia compatibile con il formato richiesto in uscita (img.profile) i dati vengono estratti direttamente nel frame uscita: VETRESULT vetVideo4Linux::extractTo(vetFrameT & img) { if (videoBuffer == NULL); return VETRET_INTERNAL_ERR; if (win.width != img.width || win.height != img.height) img.reAllocCanvas(win.width, win.height); if ( (vpic.palette { == VIDEO_PALETTE_RGB24 && img.profile == vetFrame::VETFRAME_BGR24 ) || (vpic.palette == VIDEO_PALETTE_RGB565 && img.profile == vetFrame::VETFRAME_BGR565) || (vpic.palette == VIDEO_PALETTE_RGB555 && img.profile == vetFrame::VETFRAME_BGR555) ) read( fd, img.data[0], img.getBufferSize() ); return VETRET_OK; } return VETRET_NOT_IMPLEMENTED; } In questo caso possiamo notare che i dati non vengono bufferizzati, lo sviluppatore può implementare il sistema di estrazione che preferisce, ma ovviamente le operazioni di allocazione e di copia inutili sono da evitare. Le attuali implementazioni sono situate in ./source/inputs/ 34 1.6 - vetOutput Pure Abstract Class vetOutput.h vetOutput è una classe puramente astratta e definisce l’interfaccia standard VETCLASS_TYPE_OUTPUT di uscita dati di VETLib, le implementazioni ad esempio si occupano di Header @ pg. 135 codificare i dati e trasmetterli ad un device di visualizzazione o ad un file. Le funzioni di lettura dei frame sono definite in modo simmetrico all’interfaccia di estrazione vetInput: virtual VETRESULT importFrom(vetFrameYUV420& img) = 0; virtual VETRESULT importFrom(vetFrameRGB24& img) = 0; virtual VETRESULT importFrom(vetFrameT & img) = 0; gli operatori sono infatti definiti rispettivamente come: void operator << (vetFrameYUV420& img) { importFrom(img); }; void operator << (vetFrameRGB24& img) { importFrom(img); }; void operator << (vetFrameT & img) { importFrom(img); }; l’utilizzo è equivalente, ma nonostante la scrittura con gli operatori sia più leggibile, la chiamata diretta restituisce un valore di riferimento sul risultato del processo. I seguenti prototipi sono destinati all’aggiornamento della risoluzione in uscita: virtual VETRESULT setHeight(unsigned int value) = 0; virtual VETRESULT setWidth(unsigned int value) = 0; Questa interfaccia è implementata da tutti i moduli che si occupano della visualizzazione, quali vetWindowGTK (visualizzatore per Linux) e vetWindow32 (visualizzatore per Windows), anche in questo caso (come vetInput) sorge il problema della conversione di spazio colore tra i formati possibili in ingresso e il formato di uscita (verosimilmente solo uno oppure multiplo con selettore), visto lo scopo della classe è opportuno prediligere (ottimizzare) un formato in ingresso (quello più compatibile con l’uscita) e applicare conversioni automatiche (tramite vetUtility) per supportare il maggior numero di formati possibile, segue un esempio estratto dal modulo vetWindowGTK: VETRESULT vetWindowGTK::importFrom(vetFrameRGB24& img) { if (image == NULL) return VETRET_INTERNAL_ERR; gdk_draw_rgb_image( return VETRET_OK; } image->window, image->style->fg_gc[image->state], 0,0, img.width,img.height, currDith, (guchar*)img.data[0], img.width*3 ); VETRESULT vetWindowGTK::importFrom(vetFrameYUV420& img) { if (image == NULL) return VETRET_INTERNAL_ERR; vetFrameRGB24 tmp(img.width, img.height); img >> tmp; VETRETULT ret = importFrom(tmp); if ( ret == VETRET_OK) else } return VETRET_OK_DEPRECATED; return ret; Le attuali implementazioni sono situate in ./source/outputs/ 35 1.7 - vetFilter Abstract Class vetFilter.h Come visto nelle sezioni precedenti l’interazione tra componenti è possibile VETCLASS_TYPE_FILTER grazie alle interfacce vetInput e vetOutput, attraverso i tre oggetti frame Header @ pg. 137 vetFrameRGB24, vetFrameYUV420 e vetFrameT . La classe vetFilter “implementa” entrambe le interfacce e quindi è un filtro (o un buffer) in grado di interagire in un processo complesso (catene di filtri), lo sviluppatore può ovviamente integrare le due interfacce principali in un proprio oggetto con caratteristiche specifiche, ma nella maggior parte dei casi VETLib fornisce questo oggetto per standardizzare e semplificare la creazione di filtri digitali generici. I seguenti prototipi sono ereditati dall’interfaccia vetInput e devono essere implementati dallo sviluppatore, chiaramente si occupano di leggere il frame in ingresso e verosimilmente modificarlo: virtual VETRESULT importFrom(vetFrameYUV420& img) = 0; virtual VETRESULT importFrom(vetFrameRGB24& img) = 0; virtual VETRESULT importFrom(vetFrameT & img) = 0; mentre si offre un implementazione dei metodi: VETRESULT extractTo(vetFrameYUV420& img); VETRESULT extractTo(vetFrameRGB24& img); VETRESULT extractTo(vetFrameT & img); bool vetFilter::EoF() VETRESULT vetFilter::setHeight(unsigned int value); VETRESULT vetFilter::setWidth(unsigned int value); unsigned int vetFilter::getHeight() const; unsigned int vetFilter::getWidth() const; L’ingresso di dati nel filtro consiste nella lettura di un frame da un oggetto vetInput tramite vetFilter::importFrom(..), mentre l’uscita dati è l’estrazione del frame verso un oggetto vetOutput, naturalmente il frame deve essere memorizzato in un buffer temporaneo all’interno del filtro in attesa che sia estratto (tramite vetFilter::extractTo(..)), il seguente codice chiarisce il motivo per il quale è necessario il buffering, il flusso dati è: vetFrameRGB24 vetInput vetFilter vetOutput globalBuffer; dataSource; imageFlipper; visualizationWindow; dataSource imageFlipper imageFlipper visualizationWindow >> << >> << globalBuffer; globalBuffer; globalBuffer; globalBuffer; L’oggetto imageFlipper deve bufferizzare il frame modificato (rovesciato) in attesa che sia estratto (penultima linea), la classe vetFilter offre un sistema integrato di gestione dei tre buffer (le interfacce base ammettono l’interazione tramite tre oggetti frame differenti), ma solo uno dei buffer può essere attivo (gli altri sono impostati a NULL): vetFrameYUV420 *bufferYUV; vetFrameRGB24 *bufferRGB; vetFrameT *bufferTuC; i puntatori sono dichiarati protetti, ma sono accessibili tramite i metodi: vetFrameRGB24* vetFrameYUV420* vetFrameT * dump_buffer_RGB() { return bufferRGB; }; dump_buffer_YUV() { return bufferYUV; }; dump_buffer_TuC() { return bufferTuC; }; 36 Il buffer in uso può essere individuato grazie a uno dei tre metodi isBuffer*, l’implementazione è estremamente banale e simile per i tre metodi, si riporta solo il controllo del buffer YUV420: bool isBufferYUV() { if ( bufferYUV != NULL ) return true; } return false; I buffer possono essere istanziati anche da oggetti esterni (verosimilmente durante l’inizializzazione del sistema), ma è essenziale che nella fase di acquisizione di un nuovo frame si effettui un controllo ed eventualmente un aggiornamento per allineare il buffer alla risoluzione finale, i tre metodi useBuffer* si occupano di selezionare il formato richiesto e ridimensionarlo se necessario, segue il codice relativo al buffer YUV420, gli altri due casi sono similari: void vetFilter::useBufferYUV(unsigned int width, unsigned int height) { if ( bufferYUV == NULL ) bufferYUV = new vetFrameYUV420(width, height); else if ( bufferYUV->width != width || bufferYUV->height != height ) bufferYUV->reAllocCanvas(width, height); if ( getFilterParameters() != NULL ) getFilterParameters()->currentBuffer = vetFilterParameters::YUV; if ( bufferRGB != NULL ) { delete bufferRGB; bufferRGB = NULL; } if ( bufferTuC != NULL ) { delete bufferTuC; bufferTuC = NULL; } } Nel caso del buffer vetFrameT è necessario specificare anche il formato di rappresentazione, i parametri sono passati direttamente al classico costruttore : void useBufferTuC( unsigned int width, unsigned int height, vetFrame::VETFRAME_PROFILE profile ); Il metodo imposto da vetInput: bool EoF() è direttamente implementato in vetFilter per semplificare lo sviluppo dei filtri più semplici, sarebbe ottimale aggiornare continuamente lo stato: bool vetFilter::EoF() { if (bufferYUV != NULL || bufferRGB != NULL || bufferTuC != NULL) return false; return true; }; Il sistema di buffering integrato influenza i metodi di estrazione dei frame, convenuto che il processing è implementato nelle funzioni di input (importFrom) e che il risultato è memorizzato in uno dei buffer, risulta evidente che le funzioni extractTo devono semplicemente copiare il buffer nell’immagine output: VETRESULT vetFilter::extractTo(vetFrameYUV420& img) { if ( !isBufferYUV() ) return VETRET_ILLEGAL_USE; img = *bufferYUV; return VETRET_OK; } 37 Si suppone che i frame in ingresso e in uscita siano nel medesimo formato ed in generale non si prevede di integrare la conversione (ad esempio tramite vetUtility) qualora i formati non corrispondino, la selezione del buffer dovrebbe avvenire anche in maniera automatica quando un frame viene importato. Le interfacce vetInput e vetOutput impongono alcuni metodi per la lettura e la modifica della risoluzione, nella maggior parte dei casi si tratta solo di modificare il buffer interno e quindi vetFilter implementa direttamente queste funzioni chiamando il metodo reAllocCanvas del buffer attivo, sono stati ripostati due dei quattro metodi, il codice è simmetrico per la larghezza: VETRESULT vetFilter::setHeight(unsigned int value) { if (bufferYUV != NULL) { bufferYUV->reAllocCanvas(bufferYUV->width, value); return VETRET_OK; } else if (bufferRGB != NULL) { bufferRGB->reAllocCanvas(bufferRGB->width, value); return VETRET_OK; } else if (bufferTuC != NULL) { bufferTuC->reAllocCanvas(bufferTuC->width, value); return VETRET_OK; } return VETRET_NOT_IMPLEMENTED; }; unsigned int vetFilter::getHeight() const { if (bufferYUV != NULL) return bufferYUV->height; else if (bufferRGB != NULL) return bufferRGB-> height; else if (bufferTuC != NULL) return bufferTuC-> height; return 0; }; Se l’aggiornamento della risoluzione implica altre procedure legati al filtro è ovviamente possibile sovrascrivere (override) queste implementazioni, ad esempio: VETRESULT vetFilterImplementation::setHeight(unsigned int value) { doMyStuffWithHeightValue(value); // [..] vetFilter::setHeight(value); // call original method } Questo sistema di supporto per i casi più comuni è integrato anche nell’inizializzazione e nella distruzione della classe vetFilter, in particolare il costruttore è: vetFilter::vetFilter(float fps) : vetInput(fps), vetObject() { bufferYUV = NULL; bufferRGB = NULL; bufferTuC = NULL; setName("Abstract Filter"); setDescription("Abstract Class"); setVersion(0.0); }; 38 I metodi set* sono implementati dalla classe vetObject, la descrizione approfondita è disponibile più avanti in questo capitolo, il risultato è che l’oggetto astratto vetFilter ha un nome, una descrizione e una versione di riferimento. Il distruttore della classe vetFilter rilascia la memoria del buffer con una chiamata al metodo: void vetFilter::releaseBuffers() { if (bufferYUV != NULL) delete bufferYUV; if (bufferRGB != NULL) delete bufferRGB; if (bufferTuC != NULL) delete bufferTuC; bufferYUV = NULL; bufferRGB = NULL; bufferTuC = NULL; if ( getFilterParameters() != NULL ) getFilterParameters()->currentBuffer = vetFilterParameters::NONE; } Con questo esempio si è introdotta la classe vetFilterParameters, i parametri di lavoro del filtro dovrebbero essere memorizzati (dinamicamente) nell’implementazione di in questa classe ausiliaria (ad esempio vetFilterGeometricParameters), i prototipi di accesso da vetFilter sono definiti: virtual VETRESULT setFilterParameters (vetFilterParameters* params) = 0; virtual vetFilterParameters* getFilterParameters () = 0; La classe vetFilterParameters definisce anche un enumerazione per la selezione del buffer: enum BUFFER_TYPE { NONE, YUV, RGB, TuC }; la classe vetFilter implementa un metodo scorciatoia (protetto) per l’allocazione del buffer: void allocateBuffer(vetFilterParameters::BUFFER_TYPE bType); utile nell’implementazione di setFilterParameters e del prototipo imposto per l’azzeramento della configurazione (e dei buffers): virtual VETRESULT reset() = 0; Inoltre vetFilterParameters impone l’implementazione di due funzioni dedicate alla serializzazione dei parametri: virtual VETRESULT loadFromStreamXML(FILE *fp) = 0; virtual VETRESULT saveToStreamXML(FILE *fp) = 0; queste funzioni possono essere utilizzate direttamente o indirettamente tramite i rispettivi metodi che istanziano lo stream di lettura/scrittura (già implementati): VETRESULT saveToXML(const char* filename); VETRESULT loadFromXML(const char* filename); int vetFilterParameters::loadFromXML(const char* filename) { FILE *fp; if ( ( fp = fopen(filename, "r") ) == NULL ) return VETRET_PARAM_ERR; //[..] ret = loadFromStreamXML(fp); //[..] fclose(fp); return ret; } 39 Seguono alcuni esempi estratti dai filtri inclusi in questa release: VETRESULT vetDigitalFilter::importFrom(vetFrameRGB24& img) { int ret = VETRET_OK; if ( !isBufferRGB() ) { } useBufferRGB(img.width, img.height); ret = VETRET_OK_DEPRECATED; if (myParams->currentKernel == NULL) { } else *bufferRGB = img; return ret; ret += doProcessing( return ret; } img, *bufferRGB, *myParams->currentKernel, myParams->clampNegative ); VETRESULT vetFilterGeometric::importFrom(vetFrameT & img) { switch ( myParams->runMode ) { case vetFilterGeometricParameters::DO_NOTHING: if ( !isBufferTuC() ) { useBufferTuC(img.width, img.height, img.profile); *bufferTuC = img; return VETRET_OK_DEPRECATED; } else { *bufferTuC = img; return VETRET_OK; } case vetFilterGeometricParameters::ROTATE90: return rotate90(img); default: return VETRET_PARAM_ERR; } } Lo sviluppo di filtri è lo scopo principale della libreria, il capitolo terzo analizza nel dettaglio le convenzioni adottate, le modalità di creazione e gli strumenti a disposizione. Le attuali implementazioni sono situate in ./source/filters/ 40 1.8 - vetCodec Abstract Class vetCodec.h Questa interfaccia è la composizione delle classi vetInput e vetOutput (inoltre VETCLASS_TYPE_CODEC eredita vetObject), di fatto è estremamente simile alla precedente classe Header @ pg. 139 vetFilter, ma l’obbiettivo è più specifico: il componente è un codificatore e/o un decodificatore. Il buffering è completamente delegato all’implementazione specifica e nella maggior parte dei casi è integrato in una libreria esterna di riferimento (che contiene i veri algoritmi di (de)codifica). Oltre ai prototipi imposti dalle due interfacce base (vedi sezioni precedenti), i moduli di tipo vetCodec devono implementare anche una serie di metodi legati all’accesso e alla gestione degli streams: virtual bool isEncodingAvailable() = 0; virtual bool isDecodingAvailable() = 0; virtual bool hasAudio(int stream = -1) = 0; virtual bool hasVideo(int stream = -1) = 0; virtual int getAudioStreamCount(int stream = -1) = 0; virtual int getVideoStreamCount(int stream = -1) = 0; virtual long getVideoStreamLength(int stream = -1) = 0; virtual long getAudioStreamLength(int stream = -1) = 0; virtual VETRESULT load(char *filename, int stream = -1) = 0; virtual VETRESULT save(char *filename, int stream = -1) = 0; virtual VETRESULT close() = 0; I prototipi isEncodingAvaiable e isDecodingAvaiable identificano le capacità del modulo e saranno implementati inline, ad esempio il modulo vetCodec_MPEG non è in grado di codificare e quindi: bool vetCodec_MPEG::isEncodingAvailable() { return false; }; bool vetCodec_MPEG::isDecodingAvailable() { return true; }; Gli altri metodi identificano alcune caratteristiche dello stream, la convenzione prevede di ritornare il valore -1 se il metodo o la caratteristica non è supportata, i video di ultima generazione (MPEG2, MPEG4, MOV) sono in grado di supportare più stream (esempio un video e due tracce audio), il parametro stream identifica la traccia se il valore è diverso da -1 (valore predefinito). I tre metodi save, load e close si occuperanno di istanziare, caricare e chiudere, rispettivamente, un file video. 41 Esempio : VETRESULT vetCodec_MPEG::extractTo(vetFrameRGB24& img) { if (file == NULL) return VETRET_ILLEGAL_USE; if (width != img.width || height != img.height) img.reAllocCanvas(width, height); // read the frame to output image mpeg3_read_frame(file, (unsigned char*)img.data[0], 0, 0, width, height, width, height, MPEG3_RGB888, 0); return VETRET_OK; } VETRESULT vetCodec_MOV::extractTo(vetFrameRGB24& img) { if (file == NULL) return VETRET_ILLEGAL_USE; if (width != img.width || height != img.height) img.reAllocCanvas(width, height); // read the frame to output image quicktime_decode_video(file, (unsigned char**)img.data, 0); myParams->frameIndex++; if(myParams->frameIndex > lenghtFrames) streamEOF = true; return VETRET_OK; } 42 1.9 - vetVision Abstract Class vetVision.h Questa interfaccia è direttamente derivata da vetOutput, ma sono state VETCLASS_TYPE_VISION aggiunte una serie di funzionalità per semplificare l’estrazione di informazioni Header @ pg. 140 ed un sistema di callback. L’estrazione di informazioni da singole immagini o sequenze video è un ambito estremamente importante e sono ancora in corso molti studi (anche integrando l’AI) per risolvere problemi come la Computer Vision (classificazione e l’indipendenza di robots) e la video sorveglianza (riconoscimento e sicurezza). Nella libreria è incluso un banale modulo di Motion Detection basato sulla DFD (Frame Difference), il ciclo a livello di applicazione indirizza semplicemente frame tramite l’interfaccia di vetOutput (importFrom): while (i++ < 100) { myCameraSource >> globalBuffer; motionEstimationModule << globalBuffer; } L’evento di allerta (in questo caso viene alzato quando la differenza con il frame precedente supera una certa soglia) è gestito attraverso una i metodi offerti da vetVision: void setAlertCall(void* (*functionCall)(void*) ); void setAlertCallArgument(void* arg); void* getAlertCallArgument(); L’applicazione deve definire una funzione di allerta (potrebbe ad esempio accendere una sirena) che rispetti il prototipo void* (*alertCall)(void* argument), cioè, ad esempio: void* myAlertFunction(void* myArgumentStruct) { printf("\n -> Image has changed!\n"); return NULL; } Il parametro puntatore a myArgumentStruct è memorizzato nella variabile alertCallArgument e viene passato alla funzione di alert dal metodo: void vetVision::doAlert() { if (alertCall != NULL) alertCall(alertCallArgument); } La configurazione quindi è: motionEstimationModule.setAlertCall(myAlertFunction); motionEstimationModule.setAlertCallArgument(NULL); motionEstimationModule.getParameters().setDoAlert(true); L’evento non è asincrono, il threading deve essere eventualmente gestito a livello di applicazione (nella funzione di alert), nel caso trattato infatti, il controllo sul frame successivo ad un evento viene analizzato solo quando il controllo è restituito dalla funzione myAlertFunction. I moduli che devono disporre anche di un uscita frame sincrona (ad esempio sottolineano l’oggetto in movimento) dovrebbero implementare anche l’interfaccia vetInput, la sintassi è banale: class myMotioDetection: public vetVision, public vetOutput { Se si volesse sfruttare il sistema di buffering integrato in vetFilter è ovviamente possibile sostituire vetOutput con vetFilter nella dichiarazione precedente, la doppia definizione dell’interfaccia di acquisizione (vetInput) non crea alcun problema. 43 1.10 - vetBuffer Abstract Class vetBuffer.h La classe definisce l’interfaccia di un generico buffer, i metodi imposti VETCLASS_TYPE_BUFFER forniscono l’accesso ai singoli oggetti e il supporto di un indice del frame Header @ pg.138 corrente. La classe è un template (vetBuffer ), le implementazioni possono mantenere questa astrazione o specificare un tipo definito. Funzioni di configurazione del buffer: void setDoDataCopy(bool value = true) bool isDataCopyEnabled() const { return copyData; }; virtual int reset() = 0; Funzioni di gestione dei frames: virtual virtual virtual virtual virtual virtual VETRESULT VETRESULT VETRESULT VETRESULT VETRESULT VETRESULT addFrame(T* newFrame) = 0; insertFrame(long index, T* newFrame) = 0; updateFrame(long index, T* newFrame) = 0; removeFrame(long index, bool freeData = false) = 0; removeFrame(T* frameToDel, bool freeData = false) = 0; deleteFrames() = 0; long getFramesCount() const { return v_fcount; }; Funzioni di estrazione e posizionamento del frame corrente: virtual T* getFrame(long index) = 0; virtual long getCurrentFrameIndex() const = 0; virtual virtual virtual virtual virtual virtual VETRESULT VETRESULT VETRESULT VETRESULT VETRESULT VETRESULT virtual virtual virtual virtual virtual T* T* T* T* T* goToNextFrame() = 0; goToPreviousFrame() = 0; goToFirstFrame() = 0; goToLastFrame() = 0; goToFrame(long index) = 0; goToStepFrame(long offset) = 0; getLastFrame() = 0; getFirstFrame() = 0; getNextFrame() = 0; getPreviousFrame() = 0; getCurrentFrame() = 0; È opportuno sottolineare che la configurazione predefinita di un vetBuffer non prevede la copia ne la distruzione degli oggetti memorizzati, la struttura gestisce i puntatori degli oggetti contenuti, il flag copyData abilita la creazione di nuovi frames nella fase di aggiunta/inserimento. Inoltre solo la funzione deleteFrames() elimina completamente gli oggetti, nel caso della distruzione del buffer classica (chiamata al distruttore) gli oggetti non vengono cancellati e lo sviluppatore dell’applicazione deve liberare la memoria manualmente. Chiaramente ciò può essere fatto solo conoscendo l’indirizzo degli oggetti (puntatore) che viene perduto quando si distrugge il buffer. 44 template class vetBufferArray : public vetBuffer { protected: // brief data pointers storage. T** v_frames; //frames* array // [..] VETRESULT addFrame(T* newFrame) { if (v_fcount+1 > VETDEF_FCOUNT_MAX) return VETRET_ILLEGAL_USE; if (v_frames == NULL) // this one will be the first frame { v_storageLenght = VETDEF_STACK_FRAMECOUNT; // allocate a new frames array v_frames = new T*[v_storageLenght]; // first frames array allocation v_fcount = 1; // add the new frame if ( copyData ) v_frames[0] = new T(*newFrame); else v_frames[0] = newFrame; } else if (v_fcount < (long)v_storageLenght) // there's enough space { if ( copyData ) v_frames[v_fcount] = new T(*newFrame); else v_frames[v_fcount] = newFrame; // update frame count to VetInfo ++v_fcount; } else { // recalculates new storage (array) length using default step size (>1) v_storageLenght = v_fcount + VETDEF_STACK_FRAMEREALLOCSTEP; // allocate a new frames array T** new_frames = new T*[v_storageLenght]; // copy frames* to the new array for (int i=0; i /, le classi astratte e i moduli più generici sono disponibili direttamente in ./source/. 2.1 - Inputs vetNoiseGenerator Implements: vetInput Serializable È un implementazione estremamente banale di un generatore di rumore, /inputs/vetNoiseGenerator.h offre una serie di metodi statici per la generazione di pixel casuali e la classica interfaccia di estrazione frames derivata da vetInput, ad ogni canale (RGB) è associato un valore casuale. Questa versione non supporta le classiche informazioni (valor medio, potenza), i parametri di configurazione sono: normalizzazione (pel_value = rand()%NORM), spread (numero di cicli di aggiunta rumore, 1.0 = image size), la posizione del pixel rumoroso è casuale ed è possibile che un pixel sia aggiornato più volte. Il modulo non è stato sviluppato per applicazioni reali, ma per alcuni test durante lo sviluppo di VETLib, i parametri configurabili e la risposta del generatore (fps) sono inefficienti, in futuro si prevede una versione più funzionale. vetPlainFrameGenerator Implements: vetInput Serializable Questo modulo genera frame monotematici, il codice è estremamente /inputs/vetPlainFrameGenerator.h semplice e può essere analizzato dagli sviluppatori neofiti per comprendere l’implementazione di sorgenti dati in VETLib. I pixel sono aggiornati direttamente nell’immagine output (passata come parametro alle funzioni extractTo(vetFrame..) ), non è stato implementato un buffer interno per velocizzare il processo, poiché è stato implementato principalmente per scopo didattico. 53 vetVideo4Linux Implements: vetInput for Linux only Questa classe interfaccia VETLib al sistema di acquisizione dei sistemi NIX /inputs/vetVideo4Linux.h v4l (ver. 1), ovviamente è necessario che la libreria Video4Linux riconosca l’hardware collegato tramite i rispettivi driver, ciò è trasparente al modulo garantendo un ottimo livello di astrazione (simile a DirectX). I test sono stati eseguiti con una Webcam Logitech Express USB, ottenendo una frame rate limitata solo dall’hardware (17 fps). Le funzioni connect(char* inputFile = "/dev/video0") e disconnect(), inizializzano e chiudono lo streaming (il device di acquisizione predefinito su Linux è appunto /dev/video0), le funzioni get*() e set*() permettono rispettivamente di leggere e modificare i principali parametri di lavoro (anche contrasto, saturazione, colore), l’estrazione dei dati è conforme allo standard VETLib tramite gli operatori di streaming e le classiche funzioni extractTo(vetFrame..). vetDirectXInput Implements: vetInput for Windows32 only, Integrated in WorkShop Tramite le API di DirectX questo modulo è in grado di enumerare i dispositivi di acquisizione disponibili e convertirne lo stream nel formato /codecs/vetDirectXInput.h nativo di VETLib. A livello di compilazione è necessario disporre del DirectX SDK (versione 8 o 9, mentre la versione 10, attualmente in uscita sul mercato, non è compatibile con le precedenti), a livello di esecuzione il sistema deve disporre dei file di run-time (diffusi poiché utilizzati nelle applicazioni ludiche). Le funzioni enumerateDevices() e getDevice*() forniscono le informazioni su device di acquisizioni disponibili, tramite connectTo(int) e disconnect(), rispettivamente, si inizializza e si scioglie il graph di DirectShow (il sub-framework di DirectX destinato al video I/O) costruito come segue: in Sample Grabber out in out Capture source NULL Filter VETLib frame format Sample Grabber è una classe interna al modulo, la risoluzione imposta è RGB 24 bit. vetDirectXInput2 Implements: vetInput for Windows32 only, Questo modulo è un’estensione complementare di vetDirectXInput, il Integrated in WorkShop grafo è molto più complesso e consente tre operazioni: visualizzare la /coders/vetDirectXInput2.h preview (overlay mode), effettuare il rendering dello stream in formato non compresso (tutte le modalità supportate da DirectX), inoltre è possibile abilitare il grabbing (asincrono) dei frames che viene effettuato con un callback senza buffering (anche vetDirectXInput usa il callback ma questa versione ha prestazioni non paragonabili alla precedente). Il componente è anche in grado di acquisire lo stream da device MPEG2 (ex. ADC con codificatore hardware) compatibili con DirectX (in questo caso il grafo è costruito manualmente). 54 2.2 - Outputs vetWindowQT Implements: vetOutput Questa classe interfaccia VETLib con la GUI di Linux (gnome, kde) tramite /outputs/vetWindowQT.h la libreria QT della Trolltech (gratuita, inclusa nella maggior parte delle distribuzioni di linux), dopo aver inizializzato l’istanza di un applicazione QT, è sufficiente indirizzare i frames tramite gli operatori di ingresso o i metodi importFrom(vetFrame_*) per visualizzarli in una finestra, la frame rate di lavoro è accettabile per fasi di testing anche nel caso di streaming video a bassa qualità/dimensione oltre che per immagini statiche. È stata testata con la classe vetVideo4Linux che si occupa dell’acquisizione e con i decodificatori video implementati, il risultato del re-indizzamento dei frames da una Webcam alla finestra è soddisfacente e limitato dall’hardware con una risposta di circa 15 frame al secondo; con una sorgente statica (da file) e decodifica (MPEG2 e QuickTime) in real time si è ottenuto una risposta di circa 33 frame al secondo (streaming video 320 x 240 x 24b). vetWindowGTK Implements: vetOutput Questo modulo, molto simile a vetWindowQT, permette di visualizzare /outputs/vetWindowGTK.h immagini e video tramite la libreria GTK (gratuita, inclusa nella maggior parte delle distribuzioni di linux). L’utilizzo della classe è banale, dopo aver creato un istanza, indirizzare i frames tramite gli operatori di streaming in ingresso oppure i metodi importFrom(vetFrame_*) per visualizzarli in una finestra, la creazione di una nuova applicazione nell’ambiente grafico è automatica (al contrario di vetWindowQT). È stata testata con tutte le sorgenti dati di VETLib (su Linux), ha ottenuto risultati di poco migliori rispetto alla gemella vetWindowQT (34 fps). vetWindow32 Implements: vetOutput La classe vetWindow32 consente la visualizzazione di frames anche con i for Widows32 only sistemi operativi Windows a 32 bit, utilizza direttamente le API di /outputs/vetWindow32.h Windows disegnando i pixel tramite le GDI. Questa soluzione non è assolutamente adatta a visualizzare video ma solo immagini statiche o alla fase di testing/debugging, per ottenere una performance paragonabile alle rispettive classi su Linux è necessario interfacciarsi a DirectX. vetDoctor Implements: vetOutput Integrated in WorkShop Durante l’implementazione di filtri o (de)codificatori è spesso necessario avere riscontri sul tempo di esecuzione, sulla frame rate ed eventualmente /outputs/vetDoctor.h alcune statistiche comuni ricavate dai frames. vetDoctor fornisce gli strumenti basilari per questi controlli e una serie di metodi statici multipiattaforma per i casi più comuni. vetOutputVoid Implements: vetOutput È una banale classe sviluppata per le fasi di test, non esegue alcuna /outputs/vetOutputVoid.h operazione sui frame in ingresso ma ha un tempo di risposta che si avvicina a zero, può essere utile per misurare la frame rate ideale di un filtro o di una sorgente. 55 2.3 - Codecs vetCodec_BMP Implements: vetCodec, vetFrameRGB Questa classe interfaccia VETLib allo standard Bitmap, è in grado di Serializable leggere e scrivere file bmp, implementa le classi astratte vetInput (read) e vetOutput (write), il buffer interno è ereditato dalla classe vetFrameRGB e /codecs/vetCodec_BMP.h può essere disattivato per diminuire il tempo di esecuzione. vetCodec_BMP supporta sia in ingresso che in uscita l’accesso a file multipli, incrementando progressivamente il contatore che identifica il file (image13.bmp -> image14.bmp), ciò è particolarmente utile per automatizzare il salvataggio di sequenze. vetCodec_IMG Implements: vetCoder Serializable La libreria ImageMagick (gratuita, molto diffusa) consente a questo /codecs/vetCoder_BMP.h modulo di leggere e scrivere la maggior parte degli standard di codifica immagini esistenti (più di 90 formati diversi), il prezzo da pagare è la dimensione degli eseguibili che cresce drasticamente a causa della complessità di ImageMagick. Il modulo sarà aggiornato in futuro per diminuire questo inconveniente. vetCodec_MPEG Implements: vetCodec for Linux only, Serializable Questo modulo utilizza la libreria libmpeg3 (open source) disponibile per /codecs/vetCodec_MPEG.h ambienti Linux per decodificare lo standard MPEG 1-2. Nonostante la codifica non sia abilitata si è scelto di implementare vetCodec invece di vetOutput. Le prestazioni sono eccellenti e consentono largamente il playback tramite una delle interfacce di visualizzazione (vetWindow*). vetCodec_MOV Implements: vetCodec Tramite la libreria quicktime4linux questa classe è in grado di decodificare for Linux only, Serializable e codificare stream in formato QuickTime (file .mov). /codecs/vetCodec_MOV.h Sono stati riscontrati problemi con alcune versioni della libreria, si consiglia agli sviluppatori di installare la versione inclusa e leggere il file ./support/NOTES. vetCodec_XVID Implements: vetCodec Lo standard open source MPEG4 più diffuso è comunemente noto come Serializable XVID, questo modulo sfrutta la libreria ufficiale xvidcore per codificare e Integrated in WorkShop decodificare lo stream. /codecs/vetCodec_XVID.h Le prestazioni della decodifica sono paragonabili al modulo vetCodec_MPEG, da sottolineare il fatto che la libreria è disponibile sia per ambienti Linux che Windows. 56 2.4 - Filters vetFilterGeometric Questo filtro applica le operazioni geometriche più comuni sui frame in ingresso (rotazione, ridimensionamento, specchio), utilizza un buffer che può contenere un singolo frame, il buffer predefinito è vetFrameCache ma è possibile forzare l’utilizzo di un buffer a 24 bit per incrementare la frame rate. Sono disponibili anche una serie di metodi statici. Implements: vetFilter Serializable /filters /vetFilterGeometric.h vetFilterColor Questo filtro applica le più comuni operazioni sul colore, utilizza un buffer che può contenere un singolo frame, il buffer predefinito è vetFrameCache ma è possibile forzare l’utilizzo di un buffer a 24 bit per incrementare la frame rate. Implements: vetFilter Serializable /filters/vetFilterColor.h vetMultiplexer È l’implementazione software di un (de)multiplexer, la configurazione predefinita supporta 12 input e 12 output (il numero è definito come macro), è possibile selezionare la sorgente e la destinazione corrente ed eseguire cicli personalizzati di estrazione e caricamento di frames. Attualmente non utilizza alcun threading. Implements: vetFilter Serializable /filters/vetMultiplexex.h vetFilterNoiseChannel Questo modulo è l’implementazione di un canale non ideale, i frame in ingresso vengono perturbati con rumore (creato dal filtro o da una sorgente esterna) e memorizzati nel buffer (un frame) in attesa dell’estrazione. Implements: vetFilter Serializable /filters/vetFilterNoiseChannel.h 57 2.5 - Buffers vetBufferArray Questa classe è un’implementazione della classica struttura dati basata su array, può contenere oggetti di qualunque tipo poiché è una classe template. La dimensione dell’array viene incrementata automaticamente quando necessario, è il buffer più indicato per medio-piccole quantità di oggetti. Implements: vetBuffer Template Class [Data Structure] available as WorkShop PlugIn /buffers/vetBufferArray.h vetBufferLink Implements: vetBuffer Il modulo implementa i comuni metodi di vetBuffer con una struttura Template Class [Data Structure] dati di tipo Double Linked List, la gestione dei frame è ottimizzata e solo available as WorkShop PlugIn l’accesso ai frame tramite indice implica una parziale scansione della /buffers/vetBufferLink.h lista (viene cominciata dalla testa, dalla coda oppure dal frame corrente in base alla distanza minima), il numero di oggetti massimo è indipendente dal modulo. 58 2.6 - Other Modules vetHist Questo modulo facilita il calcolo dell’istogramma di immagini (o dati), vengono memorizzate le frequenze e successivamente le probabilità dei valori inseriti (0-255), è anche possibile serializzare i dati e realizzare un grafico, ovviamente per immagini multi-canale l’operazione va eseguita più volte. vetThread for Windows32 only, Serializable Integrated in WorkShop /vetHist.h Different Implementations Questo modulo fornisce un threading di alto livello indipendente dal sistema for Windows32, Linux operativo, l’interfaccia base è comune alle due diverse implementazioni /vetThread.h (Windows 32 e *NIX) e quindi multi-piattaforma, ma sono disponibili anche metodi e caratteristiche legate al sistema operativo (ovviamente queste non sono condivise). vetUtility /vetUtility.h Questo modulo incorpora una serie di metodi comuni dichiarati static (accessibili anche senza un istanza della classe: vetUtility::method() ), in particolare sono implementate alcune funzioni dedicate alla gestione del tempo e alcune conversioni di colore classiche, la maggior parte dei metodi accetta parametri template al fine di garantire un’alta elasticità ed ottimizzare gli algoritmi. vetMatrix Serializable Questa classe template memorizza una matrice bidimensionale di oggetti, è ./vetMatrix.h possibile serializzare i dati in formato XML (i valori sono salvati come floating point). L’utilizzo più probabile per questa classe è la memorizzazione di kernel per operazioni matematiche, per questo scopo la classe aggiorna automaticamente il valore somma (normalizzazione). vetDFMatrix Serializable Questa classe è stata implementata per i filtri digitali, rappresenta una matrice ./vetDFMatrix.h NxN+1 di valori char (-127÷127), l’ultimo valore è il divisore della normalizzazione, la dimensione è gestita dinamicamente, eventuali ottimizzazioni possono sfruttare il metodo dup_data() che restituisce il buffer interno. Sono stati integrati in maniera statica una serie di kernel predefiniti (3x3) accessibili tramite il metodo statico vetDFMatrix* createKernel_3x3(int index), il parametro index indica il codice univoco che identifica il kernel richiesto, i codici sono definiti con un tag user-friendly all’inizio dell’header (VETDF_3x3_ ), mentre i dati dei kernel (valori della matrice, char ) sono definiti nel file ./source/filters/vetDigitalFilters.def. 59 2.7 - Vision vetMotionLame Implements: vetMotion Serializable Questo modulo è stato sviluppato per dimostrare l’utilizzo della classe /vision/vetMotionLame.h base vetMotion, l’algoritmo è una banale Frame Difference con soglia, la risposta predefinita dei moduli vision consiste nella chiamata di una funzione data (callback), gli esempi dimostrano il funzionamento sia in ambiente Windows che *NIX. 60 Using and Extending VETLib Chapter III Questo capitolo è focalizzato sull’utilizzo ed in particolare sull’estensione della libreria, le interfacce e gli oggetti principali sono stati presentati nel capitolo primo, la seguente analisi assume che il lettore conosca queste informazioni, mentre alcune, le più importanti, saranno ripetute ed approfondite con esempi pratici, il lettore “frettoloso” dovrebbe quantomeno analizzare gli headers degli oggetti principali prima di procedere. 3.1 - Overview I corsi di programmazione spesso non approfondiscono uno degli aspetti più importanti della programmazione odierna: l’utilizzo di librerie, tutti i software complessi sono basati su una serie di librerie statiche e/o dinamiche più o meno standard e complesse. La libreria VETLib non è differente dai casi classici, si tratta di includere (linking) i binari statici (.lib o .a) nella linea di comando del linker e ovviamente di includere gli headers necessari nei file sorgente, gli sviluppatori Windows sono avvantaggiati da due aspetti: le interfacce grafiche (GUI) dei software di sviluppo (Visual Studio, Borland Builder, ..) consentono di aggiungere la libreria in modo semplice e visuale (generalmente con il comando “Add to Project”), inoltre la libreria VETLib*.lib ingloba anche le librerie di supporto necessarie, si tratta semplicemente di includere la versione preferita della libreria (vedi capitolo primo sezione Builts). Il caso di piattaforme NIX è apparentemente differente, ove richiesto si devono aggiungere anche le librerie esterne richieste dal proprio progetto e dai moduli inclusi in VETLib, il comando di compilazione (e linking) più semplice, dove non sono richieste altre librerie, è di questo tipo: g++ myMain.cpp ../lib/VETLib.a -o myOut.out quando sono necessarie librerie esterne (MPEG, XVID, MOV, V4L, ..) il comando si complica ed è necessario consultare la documentazione delle singole librerie per i parametri di compilazione, ad esempio il programma dimostrativo vetLinuxMPEGPlayerGTK , incluso nella distribuzione e posizionato in ./tests/ necessita dei seguenti parametri: g++ app_vetLinuxMPEGPlayerGTK.cpp ../lib/VETLib.a -L/usr/lib/ -lpthread -lmpeg3 -o app_vetLinuxMPEGPlayerGTK.out `pkg-config --cflags --libs gtk+-2.0` Questo semplice programma è basato su due moduli: vetCodec_MPEG e vetWindowGTK, la seconda e l’ultima linea di parametri include le librerie esterne necessarie: libmpeg3 e gtk2, in pratica il linking viene fatto su due livelli: il primo dall’oggetto del sorgente principale (contenente 62 la funzione main, in questo caso app_vetLinuxMPEGPlayerGTK.cpp) che fa riferimento a metodi contenuti in VETLib (../lib/VETLib.a), a sua volta i riferimenti non risolti dalla libreria (ma indirettamente richiesti dal progetto) sono risolti nelle librerie esterne passate come parametro al linker, il seguente frammento chiarisce il concetto: #include “myReader.h” int main(…) { class myReader; myReader->read(); } main.cpp class myReader { myReader(); ~myReader(); int read(); } myReader.h #include “myReader.h” #include “./mySupportLibPath/RawReadLibrary.h” //[..] int myReader::read() { file_open_thread(file, access); //..a method from external library } myReader.cpp (included as object in myReader.a) g++ main.cpp myReader.a -L/usr/lib/ -lRawReadLibrary -o main.out In questo caso si suppone che la libreria esterna RawReadLibrary sia disponibile nella directory /usr/lib/ e che sia conforme allo stile dei nomi classico (il file deve essere libRawReadLibrary.a). Si è scelto di non includere le librerie esterne direttamente nella libreria per favorire l’indipendenza degli aggiornamenti e minimizzare la dimensione dei binari, può inizialmente sembrare una scelta controversa per i neofiti (sia della libreria che di Linux), ma tutte le distribuzioni software seguono questa prassi (basta considerare le dimensioni di librerie come GTK e v4l). Consultare la sezione “External Libraries” nel file testo ./USE ed eventualmente le note aggiuntive nel file ./support/NOTES per maggiori informazioni, per esempi pratici visualizzare il Makefile della libreria (./Makefile) e quello dei tests (./tests/Makefile). La documentazione di VETLib (i file readme sono inclusi in tutti gli archivi) è stata descritta nel capitolo primo, in caso di contraddizioni (a causa di aggiornamenti ad esempio) fare sempre riferimento ai file di testo, in particolare ./ChangeLog elenca tutti gli aggiornamenti effettuati divisi per versione, gli aggiornamenti che rompono la continuità con i vecchi oggetti sono segnalati con un punto esclamativo, se si decide di aggiornare la versione inclusa nel proprio progetto con una più recente (banalmente si sostituisce il file statico e gli headers) occorre prestare attenzione a tutte le 63 possibili implicazioni, gli header di ogni classe aggiornata descrivono la natura dei cambiamenti e le ripercussioni pratiche. Si consiglia di implementare i nuovi moduli partendo dalla libreria statica precompilata (eventualmente una special built), questo è anche il sistema predefinito dello strumento VETLib Package Studio (presentato più avanti in questo capitolo), tuttavia è ovviamente possibile compilare la libreria sul proprio sistema e aggiungere il nuovo modulo direttamente al progetto, in questo caso per testare il componente è necessario creare un nuovo progetto di test simile a quelli forniti in ./tests/ (ovviamente il progetto di creazione della libreria non ha una funzione di sistema come int main() e non costruisce un eseguibile ma un oggetto statico). La libreria include un applicazione dimostrativa sviluppata in C++ .NET: VETLib Workshop, si tratta di un software completo, visuale, per ambienti Windows 32 (o superiori), realizzato per dimostrare le capacità della libreria e l’inclusione della stessa in un progetto complesso. WorkShop è in grado di gestire dinamicamente catene di filtri, consente di modificare i parametri in maniera visuale e veloce, gli oggetti possono essere caricati come plugins (DLL), è un ottimo laboratorio per il test preliminare e dimostrazioni didattiche nel campo del video/image processing. Il capitolo quarto è focalizzato su questo software e sulla creazione dei plugins. La licenza di distribuzione del progetto (disponibile in appendice) è la classica General Public License che consente l’estensione e il riutilizzo del codice in contesto open source (mantenendo i crediti), ma limita l’integrazione in prodotti commerciali previa diretta autorizzazione. Gli sviluppatori interessati all’estensione di VETLib devono accettare e confermare questa licenza, qualora nel progetto siano incluse librerie esterne, anch’esse devono essere open source, la convenzione prevede di precisare note o licenze diversa nel file .License. 64 3.2 - Samples I componenti (alias moduli) della libreria sono corredati di un progetto di test e dimostrazione delle caratteristiche situato nella directory ./tests/, il sorgente è di tali applicazioni può essere estremamente utile per introdurre l’utilizzo di un oggetto nel proprio software, fare riferimento all’appendice per ulteriori informazioni sul progetti inclusi in questa release. Seguono alcuni estratti utili per comprendere le potenzialità e la semplicità del framework VETLib: #include "../source/vetFrameRGB24.h" #include "../source/outputs/vetWindowGTK.h" #include "../source/codecs/vetCodec_MPEG.h" int main(int argc, char* argv[]) { int i = 0; long time = 0; float fps = 0 ; vetCodec_MPEG mpegSource; int ret = mpegSource.load("football.mpg"); // // // // // // // // // Video Video Video Video Video Audio Audio Audio Audio StreamS Count: Stream [0] Frame Rate: Stream [0] Frame Count: Stream [0] Width: Stream [0] Height: StreamS Count: Stream [0] Channels: Stream [0] Sample Rate: Stream [0] Sample Count: mpegSource.getVideoStreamCount() mpegSource.getVideoFrameRate() mpegSource.getVideoStreamLength() mpegSource.getWidth() mpegSource.getHeight() mpegSource.getAudioStreamCount() mpegSource.getAudioChannels() mpegSource.getAudioSampleRate() mpegSource.getAudioStreamLength() vetWindowGTK myWin (mpegSource.getWidth(), mpegSource.getHeight()); vetFrameRGB24 img24 (mpegSource.getWidth(), mpegSource.getHeight()); myWin.show(); long sleeptime = (long) (1000 / mpegSource.getVideoFrameRate()) - 10; while (i++ < 100)// 100 frames { offset = vetUtility::getTime_usec(); mpegSource >> img24; myWin << img24; } vetUtility::vetSleep( sleeptime - (long)(vetUtility::getTime_usec()-offset)/1000 ); return 0; } app_vetLinuxMPEGPlayerGTK.cpp La libreria di supporto libmpeg3 è disponibile solo per piattaforme *NIX, i parametri per la compilazione sono: g++ app_vetLinuxMPEGPlayerGTK.cpp ../lib/VETLib.a -L/usr/lib/ -lpthread -lmpeg3 -o app_vetLinuxMPEGPlayerGTK.out `pkg-config --cflags --libs gtk+-2.0` 65 vetVideo4Linux cap("/dev/video0"); vetFrameRGB24 img24; // // // // // // // Stream Width: cap.getWidth() Stream Height: cap.getHeight() Stream Color Depth: cap.getColorDepth() Stream Palette: cap.getPalette() cap.setBrightness( value ); cap.setContrast( value ); cap.setHue( value ); QApplication app(argc, argv); vetWindowQT *myapp = new vetWindowQT(cap.getWidth(), cap.getHeight() ); myapp->show(); app.setMainWidget(myapp); int i = 0; while (i++ < 100) { cap >> img24; *myapp << img24; } app_vetVideo4LinuxPlayer.cpp Anche in questo caso il progetto è disponibile solo per piattaforme *NIX e si basa sulle librerie di acquisizione v4l (video4linux) e di visualizzazione QT, la compilazione è molto semplice, basta includere la libreria: g++ -g -Wall -O3 test_vetVideo4Linux.cpp ../lib/VETLib.a -o test_ vetVideo4Linux.out vetDirectXInput cap; cap.enumerateDevices(); printf("Devices Count: %d\n", cap.getDeviceCount() ); for (int i=0; i getWidth(); h = dataSource->getHeight(); globalBuffer->reAllocCanvas(w, h); imageFlipper->useBufferRGB(w, h); imageMirror->useBufferRGB(w, h); visualizationWindow->setWidth(w); visualizationWindow->setHeight(h); while ( !dataSource->EoF() && (i++ < iMaxLoop) ) { dataSource->extractTo( *globalBuffer ); imageFlipper->importFrom( *globalBuffer ); imageMirror->importFrom( *imageFlipper->dump_buffer_RGB() ); visualizationWindow->importFrom( *imageMirror->dump_buffer_RGB() ); } Il sistema di estrazione classico dei filtri (extractTo) copia semplicemente il buffer interno al filtro nel buffer globale: imageFlipper->extractTo(globalBuffer), ma i dati sono gia disponibili nel buffer interno e quindi si tratta di un passaggio deprecabile che allunga il tempo di esecuzione, chiaramente il vantaggio consiste nell’astrazione e nella gestione di errori. Tramite l’accesso diretto al buffer è possibile eliminare questa operazione e guadagnare qualche decina di millisecondi per ogni frame, il codice risulta più “pericoloso” ed è consigliabile solo in applicazioni real-time. Un’ultima nota sul controllo del flusso, in questo caso si suppone che dataSource implementi adeguatamente il prototipo End Of File, il secondo controllo deve essere sempre aggiunto per garantire che il ciclo non sia eterno (ad esempio se dataSource fosse vetVideo4Linux o vetDirectXInput l’unica causa che può fermare il loop è disconnettere il device via hardware!). Lo sviluppatore deve accertarsi che i filtri siano compatibili con il formato scelto e preoccuparsi di eventuali conversioni dello spazio colore, inoltre i buffers dovrebbero essere inizializzati prima del ciclo per non intaccare il tempo di esecuzione sul primo frame (si suppone che il filtro adatti automaticamente il buffer al formato in ingresso). Il tempo di processing, tipicamente variabile, può essere reso costante attraverso un buffer FIFO (First in First Out) posizionato al termine della catena. 67 3.3 - Tools of the Trade VETLib è sviluppata in ANSI C++, quindi è necessario un qualunque compilatore che rispetti questo standard, lo sviluppo è stato testato con Borland C++ Builder 6.0 e Microsoft Visual Studio 6.0 su ambienti Windows e il classico GNU Compiler su Linux, i progetti per compilare la libreria e i tests (sample applications) per i compilatori citati sono disponibili. Suggerisco agli sviluppatori Windows interessati a compilare la libreria in locale di utilizzare Visual Studio per problemi di compatibilità soprattutto con le librerie esterne (DirectX, xvidcore, ImageMagick), la versione 6.0 necessita di alcuni aggiornamenti disponibili in ./Tools, (consultare il file di testo ./support/NOTES per informazioni dettagliate). Gli sviluppatori *NIX devono installare le librerie esterne necessarie al proprio software, la soluzione più pratica è scaricare ed installare i pacchetti precompilati per il proprio sistema, ovviamente si preferisce la compilazione locale. (note in ./support/NOTES) Come descritto nella prima sezione, sia l’utilizzo che l’estensione della libreria non richiedono la compilazione in locale, in questo caso gli unici files necessari sono i binari compilati (.a o .lib) e gli headers, sono disponibili numerosi archivi complementari sia per piattaforma Windows che *NIX, si possono ottenere dal sito di VETLib, è consigliabile scaricare la distribuzione completa più recente (VETLib-x.x.x.zip/tar.gz), altrimenti si può scaricare uno degli archivi di categoria SDK, destinati allo sviluppo di applicazioni basate su VETLib (applicazioni per l’utente finale, altre librerie, estensioni di VETLib): 9 VETLib-SDK-x.x.x.tar.gz (.zip) 9 VETLib-SDK-LNX-x.x.x.tar.gz 9 VETLib-SDK-WIN-x.x.x.zip Completo Solo i progetti e i moduli per Linux Solo i progetti e i moduli per Windows Inoltre è disponibile l’archivio VETLib Package Starter Kit (VETLib-PSK-x.x.x.zip) destinato esclusivamente allo sviluppo di nuovi moduli, contiene: 9 9 9 9 Moduli Vuoti Moduli Esempio Il Tool Package Studio Muduli Template necessari per vetPS ./packages/ Empty/ ./packages/ Sample/ ./packages/vetps.exe ./packages/templates/ Il contenuto e l’utilizzo dell’archivio Package Starter Kit saranno analizzati nel dettaglio più avanti in questo capitolo. Consultare la pagina downloads.html per la lista dei file di ogni archivio. 68 3.4 - VETLib Component Conventions Gli sviluppatori che vogliono estendere la libreria con nuovi moduli devono seguire le seguenti convenzioni ideate per mantenere VETLib uniforme, chiara e funzionale: Il nome della classe (cioè del modulo) deve essere formattato come segue: ModuleName.h (.cpp) Il prefisso è il nome della classe base (ex. vetFilterColor, ..) Il nome del file header e sorgente deve essere identico alla classe (come Java) I nomi riservati (non disponibili) sono elencati nel file ./packages/NAMESPACE Qualora siano necessarie più classi, dichiararle nell’header se utili all’utente finale, nel file sorgente se sono interne al modulo (quindi sono invisibili all’utente che include gli headers). Soprattutto nel caso di librerie esterne includere gli headers nel sorgente per massimizzare la trasparenza. I moduli aggiuntivi devono essere situati in ./packages/ , quando completi possono essere inviati ad uno sviluppatore designato che può decidere di includerli nella successiva distribuzione. Documentare il codice secondo lo standard utilizzato (doxygen) (sia header che possibilmente il sorgente), commentare i passaggi più complessi chiaramente. La documentazione (e i commenti) devono essere in lingua inglese. Se applicabile, seguire lo standard adottato per la gestione dei parametri (vet<..>Parameters) e implementare I/O per serializzare la classe in formato XML. (Ex. vetFilter) Scrivere un programma per il test/debugging e per la dimostrazione dei metodi e delle caratteristiche del modulo, nominarlo test_ .cpp, per programmi dimostrativi più complessi formattare il nome come app_ .cpp. Dichiarare le variabili interne protected (non pubbliche o private), ciò garantisce un corretto stile nell’interazione con gli altri moduli e la facilità di estensione in futuro. La maggior parte delle funzioni deve restituire il tipo VETRESULT (definito come intero) che indica il risultato del processo, i valore di ritorno possibili sono definiti in vetDefs.h (es. VETRET_OK), ricordo che la convenzione è comunque: 0 → OK, errore altrimenti. Se si desidera implementare un plugin di VETLib WorkShop è comunque necessario sviluppare prima un modulo funzionante e solo successivamente convertirlo, il capitolo successivo è focalizzato sul funzionamento e sulla creazione dei plugins. I moduli non coerenti con queste specifiche non saranno presi in considerazione 69 3.5 - Package Development Lo sviluppo di componenti (alias moduli, packages), ovvero l’estensione della libreria, prevede di norma l’implementazione di una delle interfacce base: - vetInput (.h) vetOutput (.h) vetFilter (.h) vetCodec (.h) vetVision (.h) vetBuffer (.h) vetFrame (.h) vetObject (.h) (data server) (data client) (data bridge) (data bridge) (data client) (data storage) (frame object) (general object) (components in ./inputs) (components in ./outputs) (components in ./filters) (components in ./codecs) (components in ./vision) (components in ./buffers) (components in ./) (components in ./) I componenti che forniscono algoritmi e operazioni matematiche non hanno restrizioni e sono situati in ./source/math. La sintassi (codice) non si discosta dalla classica ereditarietà e composizione di classi del C++, la struttura modulare del framework semplifica al massimo questo processo, gli sviluppatori che vogliono proporre una nuova “categoria” di moduli possono contattare vetlib@ewgate.net. La fase preliminare prevede una serie di operazioni che possono essere automatizzate con lo strumento vetPS.exe incluso nel Package Starter Kit, il software sarà analizzato più avanti in questo capitolo. Un modulo standard comprende i seguenti files: .h .cpp test_ .cpp Makefile / BCB-MVC project files {..} .Readme .License (must be open source) I primi due file sono il cuore del progetto, contengono rispettivamente la dichiarazione del componente e l’implementazione dei metodi, ovviamente non è presente una funzione main nel file sorgente, quindi per semplificare (velocizzare) lo sviluppo è stato incluso il file test_vetClassName.cpp che permette di testare direttamente il componente e i suoi metodi. La convenzione (del Package Studio) prevede la seguente gerarchia: any directory (VETLib root) /lib/ {VETLib.a, VETLib.lib, ..} /source/ {buffers, inputs, filters, math, ..} /packages/ / La directory lib contiene i binari statici della libreria (posizione legata ai riferimenti dei progetti predefiniti), la directory source contiene gli headers dei componenti rilasciati e delle interfacce principali (contiene anche il sorgente ma non è necessario), la directory packages contiene il vostro progetto (i files citati sopra). Questa struttura delle directories offre due vantaggi: consente all’utente di sviluppare più componenti in cartelle separate e garantisce che l’inclusione degli headers (usare relative paths) sia valida anche spostando i due file principali (vetClassName.h, cpp) nelle sottodirectory di source (fa parte del processo di rilascio dei componenti). 70 3.5.1 - Working with Frames Questa sezione presenta una serie di suggerimenti e consigli pratici relativi all’accesso a oggetti frame, alcuni potranno sembrare banali ma è utile sottolinearli per gli sviluppatori neofiti. Innanzitutto è opportuno prestare molta attenzione all’accesso diretto ai buffer (dichiarati pubblici), soprattutto nei cicli, il puntatore deve essere sempre copiato per non modificare l’originale: class vetFrameSample { int width; int height; unsigned char* data; }; void BADcutValue (vetFrameSample& img, unsigned char value) { for (int i=0; i value ) *img.data = value; img.data++; } } void OK1cutValue (vetFrameSample& img, unsigned char value) { unsigned char* dataPtr = img.data; for (int i=0; i value ) *dataPtr = value; dataPtr++; } } Il problema del primo metodo è che il puntatore img.data viene modificato e punterà al valore blu dell’ultimo pixel perdendo l’indirizzo fisico del primo pixel, la seconda funzione invece modifica la variabile temporanea dataPtr e non l’originale, anche l’accesso tramite l’operatore [ ] è corretto: void OK2cutValue (vetFrameSample& img, unsigned char value) { for (int i=0; i value ) img.data[i] = value; } } Di fatto il compilatore genera lo stesso assembly per le due funzioni corrette. Alcune funzioni fornite dalla libreria standard (C++) possono essere molto utili per ottimizzare gli algoritmi, ad esempio la seguente implementazione dell’operatore di streaming estratto da vetFrameGrey.cpp ottimizza il processo di copia grazie alle funzione memset e memcpy: vetFrameGrey& vetFrameGrey::operator >> (vetFrameYUV420& img) { if ( width == 0 || height == 0 ) throw "Cannot do that with empty image (no size)"; if ( width != img.width || height != img.height) img.reAllocCanvas(width, height); memcpy(img.data, data, width * height); memset(img.U, '\0', width * height / 2); // u+v set to 0 } return *this; 71 Il precedente codice è di fatto valido poiché entrambi i buffer sono array di unsigned char, mentre il codice non ottimizzato sarebbe stato: vetFrameGrey& vetFrameGrey::operator >> (vetFrameYUV420& img) { if ( width == 0 || height == 0 ) throw "Cannot do that with empty image (no size)"; if ( width != img.width || height != img.height) img.reAllocCanvas(width, height); img.setBlack(); const unsigned int size = width * height; for (unsigned int i=0; i < size; i++) img.data[i] = data[i]; // implicit cast } return *this; Questo metodo sarebbe valido anche se img.data (cioè il buffer YUV) fosse di interi o floating point, ma il ciclo for è estremamente più lento della copia diretta. I controlli sono molto importanti per non incorrere in errori fatali (come l’accesso non valido alla memoria), questo frammento dimostra un errore comune: char* buffer = new char[255]; // [..] if (value) delete buffer; // error, should be ‘{ delete buffer; buffer = NULL; }’ // [..] if ( buffer == NULL ) // if ( buffer == 0 ) || { // never executed ! buffer = new char[255]; } if (!buffer) La versione corretta imposta il buffer a NULL dopo aver liberato la memoria, questo errore è comune soprattutto se non si inizializzano i puntatori dopo la dichiarazione, il valore associato è di fatto casuale, diverso da NULL, quindi i controlli tentano di cancellare una zona di memoria non allocata: class badAccess { char* buffer; badAccess() { // correct code: initialize pointer here (buffer = NULL) reset(); } void reset() { // [..] width = 0; height = 0; ...… if ( buffer != NULL ) delete buffer; } } buffer = new char[256]; Questo problema è molto comune quando si tenta di delegare ad un'unica funzione (reset) l’inizializzazione, la versione proposta (corretta) è valida anche dal punto di vista dello stile. 72 3.5.2 - Internal buffering Il sistema di buffering predefinito, presentato nel capitolo primo (sezione vetFilter), semplifica incredibilmente lo sviluppo di filtri, questa sezione sottolinea alcuni aspetti importanti e presenta una serie di esempi pratici. I componenti di tipo vetInput e vetOutput in generale non prevedono il buffering interno dei dati, ma si utilizza direttamente il frame in uscita o ingresso (rispettivamente) come sorgente o destinazione, ad esempio: VETRESULT vetVideo4Linux::extractTo(vetFrameT & img) { if ( fd == -1 ) return VETRET_ILLEGAL_USE; if (win.width != img.width || win.height != img.height) img.reAllocCanvas(win.width, win.height); if ( (vpic.palette == VIDEO_PALETTE_RGB24 && img.profile == vetFrame::VETFRAME_BGR24 ) { read( fd, img.data[0], //it’s a BGR source not RGB… win.width * win.height * 3 * sizeof(unsigned char) ); return VETRET_OK; } // [..] } VETRESULT vetWindowGTK::importFrom(vetFrameRGB24& img) { if (image == NULL) return VETRET_INTERNAL_ERR; gdk_draw_rgb_image( return VETRET_OK; } image->window, image->style->fg_gc[image->state], 0, 0, img.width,img.height, currDith, (guchar*)img.data[0], img.width*3 ); I componenti derivati da vetCodec sono generalmente basati su librerie esterne che gestiscono il buffering internamente, a causa della stretta dipendenza con la libreria di (de)codifica questi componenti non hanno alcun imposizione (o supporto) riguardo al buffering, inoltre il formato dati dello stream può non essere direttamente compatibile con alcun oggetto frame base, ad esempio: VETRESULT vetCodec_MPEG::extractTo(vetFrameRGB24& img) { if (file == NULL) return VETRET_ILLEGAL_USE; if (width != img.width || height != img.height) img.reAllocCanvas(width, height); mpeg3_read_frame( file, buff, 0, 0, width, height, width, height, MPEG3_RGB888, 0); vetUtility::conv_rgb24_rgb96( buff, (unsigned char*)img.data[0], width, height); return VETRET_OK; } 73 Il caso dei filtri (derivati da vetFilter) è differente, un frame viene importato tramite la funzione vetFilter::importFrom(vetFrame..) che si occupa (verosimilmente attraverso altri metodi) di elaborare il frame e poi memorizza temporaneamente il risultato in un buffer, quando l’applicazione chiama il metodo vetFilter::extractTo(vetFrame..), il frame modificato (temporaneo, nel buffer) viene semplicemente copiato nel frame output, il flusso dati descritto è classico ed e realizzato: while ( i++ < iMax ) { mySource >> bufferGlobal; vetFilterGeometric << bufferGlobal; vetFilterGeometric >> bufferGlobal; //same as importFrom() //same as extractTo() myOutput << bufferGlobal; } Questo sistema è leggermente complicato dal fatto che ci sono tre possibili formati di frame ingresso (vetFrameRGB24, vetFrameYUV420 e vetFrameT ), oltre ai metodi preposti all’accesso e alla gestione dei tre buffer si sono incluse direttamente in vetFilter anche le implementazioni delle funzioni di estrazione (imposte da vetInput): VETRESULT vetFilter::extractTo(vetFrameYUV420& img) { INFO("VETRESULT vetFilter::extractTo(vetFrameYUV420& img) [pushing data]") if ( !isBufferYUV() ) return VETRET_ILLEGAL_USE; img = *bufferYUV; return VETRET_OK; } Il metodo relativo a vetFrameRGB24 è similare, mentre per quanto riguarda vetFrameT la fase di controllo prende in considerazione anche il profilo (vetFrameT::profile distingue il formato). Come si può facilmente notare l’estrazione è valida solo se i formati corrispondono, la conversione automatica è stata deprecata, un generico filtro potrebbe essere in grado di gestire solo alcuni formati, le funzioni di acquisizione dei frame devono garantire la capacità di trattare quel formato ed in generale devono aggiornare lo stato del buffer interno a seconda del formato in ingresso, nel seguente esempio il filtro è in grado di processare frame di tipo YUV ma non oggetti vetFrameT: VETRESULT vetDigitalFilter::importFrom(vetFrameYUV420& img) { int ret = VETRET_OK; if ( !isBufferYUV() ) { } useBufferYUV(img.width, img.height); ret = VETRET_OK_DEPRECATED; if (myParams->currentKernel == NULL) { } else *bufferYUV = img; return ret; ret += doProcessing( return ret; } img, *bufferYUV, *myParams->currentKernel, myParams->clampNegative ); 74 VETRESULT vetDigitalFilter::importFrom(vetFrameT & img) { return VETRET_NOT_IMPLEMENTED; } Il codice relativo al controllo !isBufferYUV() consente di impostare automaticamente il buffer corretto (se non è YUV, viene inizializzato con le dimensioni previste), ovviamente il metodo importFrom(vetFrameRGB24) effettua il controllo con sul buffer RGB. In questo caso il filtro effettua una convoluzione con il kernel corrente (currentKernel) nella funzione doProcessing, lo stile di delegare il processo a funzioni statiche non è obbligatorio ma altamente consigliato, in particolare le funzioni che contentono l’algoritmo dovrebbero essere il più indipendenti possibile (come vetFilterGeometric::doProcessing). Il sistema predefinito consente comunque allo sviluppatore di implementare una propria strategia di buffering, come in ogni altro processo di eredità in C++ si possono sovrascrivere (override) le funzioni gia implementate e modificarne il comportamento, ad esempio se un filtro lavora su n frame, si potrebbe sostituire i 3 puntatori con tre array (in modo estremamente banale): void vetFilter::useBufferYUV(unsigned int width, unsigned int height) { if ( bufferYUV == NULL ) bufferYUV = new vetFrameYUV420(width, height)[n]; else if ( bufferYUV->width != width || bufferYUV->height != height ) for (unsigned int i=0; i < n; i++) bufferYUV[i].reAllocCanvas(width, height); if ( bufferRGB != NULL ) { delete [] bufferRGB; bufferRGB = NULL; } //[..] }; Ovviamente anche gli altri metodi devono essere aggiornati, ma le modifiche sono banali e sporadiche, ad esempio le implementazioni di isBuffer*, getHeight, getWidth, EoF sono ancora compatibili, mentre è necessario aggiungere dei loop alle funzioni setHeight, setWidth e modificare come sopra (delete []) anche relaseBuffers. 75 3.5.3 - Parameters for Filters and Codecs La serializzazione dei moduli è una caratteristica molto utile, quando è possibile i parametri di lavoro del modulo devono essere memorizzati direttamente nella classe vet<..>Parameters che quindi si può facilmente occupare anche di salvarli e caricarli in formato XML tramite i prototipi: virtual VETRESULT saveToStreamXML(FILE *fp) = 0; virtual VETRESULT loadFromStreamXML(FILE *fp) = 0; La classe base vetFilterParameters fornisce l’implementazione dei metodi di accesso diretto a file tramite le funzioni di sistema fopen e fclose: int saveToXML(const char* filename); int loadFromXML(const char* filename); L’implementazione inclusa in vetFilterGeometric crea il seguente XML: La formattazione del testo è semplificata dale funzioni di sistema fprintf e fscanf, la cui documentazione è reperibile online o nei manuali di C++, il sistema più pratico è di spiare i filtri esistenti e modificare il codice, la serializzazione deve comunque includere un selettore del buffer in uso, è sufficiente inserire il seguente estratto: if ( fprintf(fp, " \n", (int)currentBuffer) == EOF) return VETRET_INTERNAL_ERR; La viariabile (enumerazione) currentBuffer è dichiarata nella classe madre vetFilterParameters e viene trattata come intero (0 significa NONE, 3 significa TuC cioè il frame template), la lettura è: int cB = (int)currentBuffer; if ( fscanf(fp, " \n", &cB) == EOF ) throw "error in XML file, unable to import data."; currentBuffer = (BUFFER_TYPE)cB; in questo caso si usa una variabile temporanea ed un casting per evitare il warning di alcuni compilatori. La convenzione prevede la dichiarazione del puntatore myParams nel componente: class vetFilterGeometric : public vetFilter { protected: vetFilterGeometricParameters* myParams; L’accesso deve essere gestito dai due prototipi imposti (da vetFilter) che devono essere implementati in questo modo: VETRESULT setFilterParameters (vetFilterParameters* initParams) { return setParameters(static_cast (initParams)); }; vetFilterParameters* getFilterParameters () { return static_cast (myParams); }; 76 Dove la classe vetFilterGeometricParameters è l’implementazione di vetFilterParameters per la classe vetFilterGeometric, la funzione di accesso ai parametri (senza casting) è banalmente: vetFilterGeometricParameters& getParameters() { return *myParams; }; Mentre la funzione che imposta realmente i parametri è VETRESULT vetFilterGeometric::setParameters(vetFilterGeometricParameters* initParams) { if (initParams != NULL && myParams == initParams) return VETRET_PARAM_ERR; if ( initParams == NULL ) { if ( myParams != NULL ) reset(); else myParams = new vetFilterGeometricParameters(); } else { if ( myParams != NULL ) delete myParams; } myParams = initParams; allocateBuffer(myParams->currentBuffer); return VETRET_OK; } Le uniche differenze (da modificare) sono in grassetto, i nomi delle funzioni (setParameters, getParameters) non sono imposti dalla sintassi, ma è altamente consigliato mantenere questo stile. Si può notare che quando il parametro in ingresso (initParams) è NULL, viene creata una nuova istanza della classe oppure vengono resettati i parametri, ovviamente anche il costruttore predefinito della classe vetFilterGeometricParameters inizializza le variabili tramite il costruttore: vetFilterGeometricParameters(RUNMODE mode = DO_NOTHING) : vetFilterParameters() { runMode = mode; } // where super-class constructor is: vetFilterParameters() { reset(); }; Il metodo void reset() è imposto (virtual) dalla classe vetFilterParameters, e imposta le variabili ai valori predefiniti: void vetFilterGeometricParameters::reset() { runMode = vetFilterGeometricParameters::DO_NOTHING; par_Rotation = 0; par_ResizeWidth = 0; par_ResizeHeight = 0; par_forzeSize = false; currentBuffer = vetFilterParameters::NONE; } 77 Il metodo di reset della classe vetFilter è praticamente standard, l’implementazione comune a tutti i filtri rilasciati è: VETRESULT vetFilterGeometric::reset() { releaseBuffers(); if (myParams != NULL) { } else myParams->reset(); allocateBuffer(myParams->currentBuffer); setParameters(NULL); return VETRET_OK; } Qualora presenti, le altre variabili dichiarate nel filtro devono essere inizializzate in questo metodo, occorre fare molta attenzione all’ordine di accesso soprattutto quando il reset viene chiamato indirettamente dal costruttore, in particolare i puntatori devono essere impostati a NULL prima di effettuare controlli (infatti il costruttore vetFilter inizializza i puntatori ai tre buffer). Il costruttore dei filtri deve (non per sintassi ma per convenzione) deve accettare un puntatore alla propria classe di parametri, di solito con valore predefinito NULL, il compito che deve assolvere è di inizializzare le variabili ad un valore predefinito (impostate dalle due funzioni reset) oppure di impostare i parametri richiesti, la tipica implementazione è banale: vetFilterGeometric(vetFilterGeometricParameters* initParams = NULL) : vetFilter(0) { myParams = NULL; setParameters(initParams); } setName("Geometric Editing Filter"); setDescription("Resize, Crop, Rotation, Flip"); setVersion(1.0); Si può verificare che l’ordine di esecuzione delle chiamate garantisce la corretta inizializzazione dei parametri in entrambi i casi. Come suggerito nella sezione dedicata a vetInput, si è scelto di ignorare la gestione della frame rate con la chiamata vetFilter(0) che come si può facilmente notare dal codice precedente passa il parametro (0) al costruttore di vetInput (e quindi sleeptime = 0). Infine l’istanza della classe vetFilter<..>Parameters viene liberata dal distruttore, invece i buffers sono liberati dal distruttore della classe madre ~vetFilter, chiamato in maniera automatica dal compilatore secondo l’ordine dell’ereditarietà, se l’applicazione gestisce più istanze di parametri (ad esempio, risulta utile per i test) si deve occupare anche di liberare la memoria al termine, considerando che nella maggior parte dei casi la classe parametri è istanziata dal modulo (chiamata del metodo setParameters(NULL)) è necessario eliminare l’istanza nel distruttore: vetFilterGeometric::~vetFilterGeometric() { if (myParams != NULL) delete myParams; } 78 3.5.4 - Platform specific Qualora siano necessarie implementazioni specifiche a seconda della piattaforma, mantenere comunque un solo header (ed un solo file sorgente) e sfruttare le macro di pre-compilazione: #if defined(sun) || defined(__sun) || defined(linux) || defined(__linux) \ || defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__OPENBSD__) \ || defined(__MACOSX__) || defined(__APPLE__) || defined(sgi) || defined(__sgi) // linux specific code here class vetMyFilter { //[..] int universalMethod(vetFrameCache24) { // here we may use linux-only services } void* linuxSpecificMethod(char* buffer) { } }; #elif defined(_WIN32) || defined(__WIN32__) // windows specific code here class vetMyFilter { //[..] int universalMethod(vetFrameCache24) { // here we may use linux-only services } HWND windowsSpecificMethod(LPSTR buffer) { } }; #endif esempio in ./source/vetThread.h Ovviamente la portabilità di un codice equivale alla più stretta portabilità degli oggetti inclusi (tipi/classi), il codice che include l’oggetto vetMyFilter e accede al metodo universalMethod è compatibile con sistemi NIX e Windows, mentre l’utilizzo dei metodi specifici legati alla piattaforma restringe la portabilità del software. Ricordo che le principali differenze tra sistemi operativi che non sono completamente risolte dalle Standard C++ Libraries sono: 9 9 9 9 Accesso binario a file Device I/O Threads Sockets 79 3.5.5 - Templates La sintassi C++ di funzioni template è: T resultGlobal; template T* myFunction(T& data) { resultGlobal = data; }; return &resultGlobal; mentre per intere classi è: template class myTemplateClass { T* data; T meanValue; }; unsigned int size; ed se si eredita da una classe template: template class myTemplateClassInherit : public myTemplateClass { }; oppure si può restringere l’astrazione ad un tipo (o classe) definito: class myTemplateClassInstance : public myTemplateClass { }; Lo stile C++ prevede di definire direttamente nell'header sia funzioni che classi di questo tipo per ovviare a frequenti problemi di compilazione. Esempi completi si possono trovare nelle implementazioni di vetBuffer (./source/buffers). La classe vetFrameT è una struttura dati basata su un array di oggetti , nel caso di immagini multi-canale è necessario gestire autonomamente la separazione dei canali ed impostare l’enumerazione channelType {VETFRAMET_CT_PIXELPACKED, VETFRAMET_CT_CHANNELPACKED}. Una descrizione più accurata della class vetFrameT è disponibile nel capitolo primo. L’utilizzo di una oggetto come vetFrameT è assolutamente identico a qualunque altro oggetto C++, si può pensare che il precompilatore sostituisca tutte le occorrenze della classe template (class T) con il tipo previsto (unsigned char), in pratica la prima funzione proposta diventa: unsigned char* myFunction(unsigned char &data) { unsigned char* resultGlobal = new unsigned char; resultGlobal = data; }; return &resultGlobal; 80 3.5.6 - Threading La maggior parte degli algoritmi di video processing operano ancora off-line, ovvero hanno un tempo di esecuzione superiore al tempo concesso dalla frame rate dello stream, ciò è prassi nella maggior parte dei codificatori software che per rispondere alla necessità del mercato di buon un compromesso tra qualità percepita e banda occupata (soprattutto nelle infrastrutture wireless) devono effettuare calcoli sempre più complessi. Le operazioni coinvolte in questi processi sono legate alla teoria, cioè ad algoritmi matematici, più o meno approssimati, e all’hardware specifico della piattaforma; ottimizzare la performance di un processo simile prevede quindi sia uno studio teorico dell’algoritmo e delle approssimazioni accettabili che un’analisi relativamente pratica delle implementazioni specifiche per l’hardware e la codifica scelta. La capacità di gestire la quantità di dati (bytes) su cui gli algoritmi operano è intrinsecamente legata all’hardware e all’I/O di basso livello. Oltre alla grande mole di dati il problema è la quantità di calcoli matematici effettuati dalla CPU ed eventuali sub-processori (matematico o grafici), il dato statistico di riferimento è il numero di operazioni in un determinato tempo. I sistemi mono-processore funzionano intrinsecamente in modo seriale, i servizi di basso livello del sistema operativo emulano la capacità di gestire più processi in parallelo tramite il classico TDM (Time Division Multiplexing), mentre i sistemi dual core o distribuiti sono molto più complessi e non sono stati presi in considerazione in questo ambito. Le prestazioni reali della piattaforma ovviamente sono costanti, il multi threading permette solo di ottimizzare l’utilizzo delle risorse, risulta evidente che eseguire troppi processi contemporaneamente sarebbe comunque controproducente. L’aspetto ostico del threading è proprio la gestione dell’intera esecuzione e la suddivisione delle singole procedure che in generale non saranno completamente indipendenti, la correlazione dei threads può essere legata al tempo oppure ai dati elaborati, lo sviluppatore deve avere ben chiaro il percorso dell’Instruction Pointer (il puntatore alla corrente istruzione assembler e quindi alla successiva) soprattutto quando l’oggetto interagisce con altri oggetti e processi. Alcuni contesti offrono terreno fertile al threading che risponde con ottime prestazioni, ad esempio operazioni su singoli pixel o blocchi di pixel indipendenti possono essere raggruppate (per righe o blocchi) ed eseguite come threads separati, quando l’ultimo thread ha concluso il controllo può essere restituito alla funzione chiamante. Di contro vi sono operazioni come la convoluzione che non possono essere scomposte (se non il calcolo matriciale) e sono solitamente approssimate diminuendo le dimensioni del kernel (3x3 → 10 * pel * channels * size Op.). La gestione di più processi (threads) è spesso complessa e prevede un sistema di sincronizzazione molto robusto, tuttavia ciò è ampliamente giustificato dalla performance risultante. Nel caso più comune, lo sviluppo di un filtro, è opportuno ricordare che se si esegue un multi-threading interno e si restituisce l'instruction pointer (il controllo) alla funzione chiamante, essa è autorizzata a ritenere che il processo sia completo e ad estrarre i dati. 81 Ad Esempio: int main() { vetFrameRGB24 temp(320,240); vetFilterBadSync myF; //[..] load data to temp myF << temp; // appena l'operatore di ingresso restituisce il controllo, copio i dati dal buffer myF >> temp; // se i processi interni al filtro non avessero tutti concluso i ciclo vitale una parte dei dati potrebbe essere il residuo di un precedente processing. } //[..] save data from temp class vetFilterBadSync { struct myThreadDataStr { void* data; unsigned int width; unsigned int row; } vetFrameRGB24* buffer; // supponiamo il buffer sia inizializzato dal costruttore myThreadDataStr* pData; // supponiamo che la struttura dati sia inizializzata dal costruttore //[..] int importFrom(vetFrameRGB24& img) { pData->width = img.width; pData->data = img.data[0][0]; for (unsigned int i=0; i< img.height; i++) { pData->row = i; vetThread myThread(myFunction, pData); } // Qui è necessario controllare che tutti i processi siano completati, // altrimenti i dati estratti dal buffer non sono completamente aggiornati. // In questo particolare caso il codice funziona, poiché gli oggetti vetThread sono creati all'interno di questo scope e vengono automaticamente cancellati tramite il relativo distruttore, come si può notare nell'implementazione di vetThread i distruttori cercano di attendere che il processo sia completato, in altre parole la sincronizzazione è automatica. // Utilizzare i threads in questo modo è rischioso, scrivere le routine di sincronizzazione esplicitamente, sono disponibili funzioni che semplificano il meccanismo. } } Notare che in questo caso le funzioni lavoravano su ogni riga dell'immagine in modo indipendente, infatti processi che coinvolgono più pixel e sono correlati tra loro, come ad esempio la convoluzione, non possono essere ottimizzati tramite questo sistema, comunemente si ovvia al problema tramite algoritmi approssimati o kernel piccoli. Consultare la documentazione della classe vetThread (./source/vetThread.h) per maggiori informazioni. 82 3.6 - VETLib Package Starter Kit I principali componenti VETLib (filtri, codecs, inputs, outputs, ..) derivano direttamente dalle relative interfacce e devono rispondere ad un comportamento standard, la parte iniziale dello sviluppo prevede quindi l’analisi accurata delle funzioni da realizzare e la stesura dello scheletro che le implementa, secondo la filosofia Extreme Programming (write tests first) è anche necessario un file sorgente che verifichi le funzionalità della classe durante lo sviluppo e le dimostri all’utente quando il componente viene distribuito, sono inoltre necessari alcuni file di progetto (per Visual Studio, Borland, il Makefile). Generalmente lo sviluppatore “frettoloso” e spesso anche quello esperto tende a cercare il progetto più coerente con il proprio e (..attraverso decine di backup..) modificalo ad hoc, questo processo è stato completamente automatizzato dal software VETLib Package Studio (ex. vetPSK), tutti gli sviluppatori che intendono estendere la libreria con un nuovo componente dovrebbero scaricare l’archivio VETLib Package Starter Kit (VETLib-PSK-x.x.x.zip), il contenuto è il seguente: 9 Moduli vuoti 9 Moduli Esempio ./packages/ Empty/ ./packages/ Sample/ 9 Il software vetPS 9 Moduli template necessari al tool vetPS ./packages/vetps/ ./packages/templates/ L’archivio è disponibile anche come pacchetto di installazione (.MSI), il software è disponibile solo per ambienti Windows (è scritto in Managed C++ .NET), i requisiti necessari al corretto funzionamento sono tre: 9 I files contenuti nella directory templates, sono inclusi negli archivi; 9 Gli headers della libreria (./source/*.h); 9 I binari statici della libreria (./lib/VETLib.lib) La ricerca delle directory presume che esista una directory X (root di VETLib) contenente il file source/vetDefs.h, la ricerca avviene dal percorso corrente fino alla root in modo recursivo (ad ogni ciclo si cerca nella directory superiore, e se non viene trovata è richiesta la locazione all’utente), una volta stabilito il percorso della root di VETLib il software suppone che contenga /lib, /source, /packages e soprattutto /package/templates, quest’ultima cartella contiene infatti i moduli base e i file di configurazione XML che vengono caricati all’avvio, se non fossero presenti il software chiede di selezionarli manualmente: vetGroups.xml, vetTypes.xml. Nella directory /packages risiede inoltre il file NAMESPACE, questo file di testo elenca tutti i nomi riservati della libreria, il software valida il nome attraverso il prefisso predefinito dell’interfaccia e controllando la lista dei nomi riservati. Package Studio è ancora in fase di sviluppo, ma la sua principale funzionalità è attiva, lo sviluppatore può utilizzare il Generator Wizard per creare i files principali e i file dei progetti selezionati, sono disponibili una serie di opzioni che vanno modificate raramente, nella maggior parte dei casi è sufficiente compilare la prima scheda selezionando la tipologia del componente e inserendo il nome della classe, le interfacce supportate sono: vetInput, vetOutput, vetFilter, vetCodec, vetVision, vetBuffer, vetFrame, vetObject ed una classe vuota. 83 Segue la lista dei file di un progetto generico, completo, il tag è il nome della classe convalidato con una serie di restrizioni (vedi Package Conventions): .h .cpp Dichiarazione della classe; Implementazione della classe; test_ .cpp File sorgente contenente il metodo main del progetto; Makefile test_ .bpr test_ .dsp test_ .dsw File di configurazione dell’utility Make; File del progetto per Borland C++ Builder 6.0; File di progetto per Microsoft Visual Studio 6.0; File del Workspace per il progetto (Visual Studio); .License .Readme Licenza del progetto; Informazioni sul progetto; Opzionali (WorkShop PlugIn project): ws_ .h ws_ .cpp ws_plugin_def.h ws_plugin_func.h ws_plugin_src.cpp ws_plugin.def ws_plugin.dsp ws_plugin.dsw Header dell’interfaccia tra componente e plugin; Sorgente dell’interfaccia tra componente e plugin; File di sistema (WS PlugIn); File di sistema (WS PlugIn); File di sistema (WS PlugIn); File di sistema (WS PlugIn); File del Progetto (Visual Studio); File di WorkSpace (Visual Studio); Lo sviluppo (conversione) dei plugins per il software WorkShop è analizzato nel dettaglio nel prossimo capitolo. La parte restante di questa sezione presenta il funzionamento del software e la formattazione dei file di configurazione, gli sviluppatori di pacchetti non sono tenuti a conoscere il meccanismo e possono saltare fin d’ora la lettura del capitolo. Il progetto generico che include tutti i files elencati (sopra) è diviso sia nell’interfaccia (nel Project Manager), sia nella fase di creazione in sotto-progetti, ogni sotto-progetto è gestito da un istanza della classe vetPkgProject che racchiude una serie di informazioni (ID, friendlyName, OS, comment) e un array di oggetti di tipo vetPKGFile (massimo 32). La classe vetPKGFile rappresenta completamente un generico file (sono trattati tutti allo stesso modo) e implementa alcuni metodi per l’accesso e il parsing (sostituzione di tags), ad ogni file output è quindi associato un file sorgente template, una serie di proprietà (description, myForm) ed in particolare un oggetto vetTagHash contenente la lista dei tags (stringhe univoche) e dei relativi valori da aggiornare (ad esempio il tag %CLASSNAME% è sostituito con il nome della classe), in generale si condivide un'unica lista per tutti i progetti e files. In termini semplici Package Studio seleziona i progetti e i files sorgente (moduli template) e sostituisce una lista di tags con i valori corretti in base alle scelte dell’utente, questo processo è realizzato con un sistema elastico e molto modulato, i files e i progetti base sono aggiornabili in modo (parzialmente) indipendente con il software. 84 I sotto-progetti sono definiti nel file vetGroups.xml e caricati dinamicamente all’avvio, la modifica delle proprietà di un progetto non implica alcun aggiornamento del software, ma non è attualmente possibile aggiungere nuovi progetti poiché la selezione visuale è statica (nel Generation Wizard). vetGroups.xml È possibile visualizzare i progetti predefiniti (template) caricati attraverso il comando “view current”, “vetPkgProjects” nel menu “Packages”, oltre a questi progetti che sono aggiunti al progetto principale a seconda della selezione nella seconda scheda del “Generation Wizard”, viene creato un nuovo sotto-progetto contenente i tre files sorgente {..} BCB project for developing/testing Your module. .h, .cpp e il file di test (infatti sono sempre presenti e sono i file da modificare..). La configurazione minima prevede di selezionare la tipologia dei componente ed il nome dello stesso (prima scheda del “Generation Wizard”), il tipo di componente scelto ha delle ripercussioni sul nome del componente (Package Conventions) e soprattutto sui file (sorgente) template che saranno modificati tramite la vetTagHash del progetto. I tipi supportati sono descritti nel file vetTypes.xml e sono caricati dinamicamente all’avvio, in questo caso è possibile sia modificare le proprietà dei nodi vetClassType che aggiungerne di nuovi, segue un estratto del file XML: {..} vetTypes.xml La classe vetClassType rappresenta un singolo tipo e si occupa di caricare le informazioni dai nodi XML, il comando “view current”, “vetClassType” nel menu “Packages” consente di visualizzare i tipi disponibili. 85 WS PLUGIN 6400 8000 10 4000 1000 7000 3200 2000 0 V B I Contenuto della directory ./packages/templates/ vetGroups.xml vetTypes.xml Definizione dei sotto-progetti disponibili; Definizione dei tipi di componenti (fully editable); Template.License Template.Readme Licenza predefinita (GPL) del componente; Leggimi preformattato del componente; Makefile test_common.bpf test_Template.bpr test_Template.dsp test_Template.dsw File di configurazione di Make preformattato; File di Progetto Borland C++ Builder 6.0; File di progetto Borland C++ Builder 6.0; File di progetto Visual C++ 6.0; File di workspace Visual C++ 6.0; vetTemplate.cpp vetTemplate.h vetTemplate_test.cpp vetBufferTemplate.cpp vetBufferTemplate.h vetBufferTemplate_test.cpp vetCodec_Template.cpp vetCodec_Template.h vetCodec_Template_test.cpp vetFilterTemplate.cpp vetFilterTemplate.h vetFilterTemplate_test.cpp vetFrameTemplate.cpp vetFrameTemplate.h vetFrameTemplate_test.cpp vetInputTemplate.cpp vetInputTemplate.h vetInputTemplate_test.cpp vetObjectTemplate.cpp vetObjectTemplate.h vetObjectTemplate_test.cpp vetOutputTemplate.cpp vetOutputTemplate.h vetOutputTemplate_test.cpp vetVisionTemplate.cpp vetVisionTemplate.h vetVisionTemplate_test.cpp Sorgente di una generica classe; Header di una generica classe; Sorgente di test di una generica classe; Sorgente di un componente Buffer; Header di un componente Buffer; Sorgente di test di un componente Buffer; Sorgente di un componente Codec; Header di un componente Codec; Sorgente di test di un componente Codec ; Sorgente di un componente Filter; Header di un componente Filter; Sorgente di test di un componente Filter; Sorgente di un componente Frame; Header di un componente Frame; Sorgente di test di un componente Frame; Sorgente di un componente Input; Header di un componente Input; Sorgente di test di un componente Input; Sorgente di un componente Object; Header di un componente Object; Sorgente di test di un componente Object; Sorgente di un componente Output; Header di un componente Output; Sorgente di test di un componente Output; Sorgente di un componente Vision; Header di un componente Vision; Sorgente di test di un componente Vision; WS_DLL_13.cpp WS_DLL_13.h WS_DLL_13.dsp WS_DLL_13.dsw ws_plugin_def.h ws_plugin_exp.def ws_plugin_func.h ws_plugin_src.cpp Header dell’interfaccia tra il componente e il plugin; Sorgente dell’interfaccia tra il componente e il plugin; File del Progetto (Visual Studio); File di WorkSpace (Visual Studio); File di sistema (WS PlugIn); File di sistema (WS PlugIn); File di sistema (WS PlugIn); File di sistema (WS PlugIn); WARNING File di testo che suggerisce di non modificare i file. 86 Il seguente estratto imposta le principali proprietà del progetto e crea la lista dei tags: vetDirectories* dirs = dynamic_cast{..} (this->MdiParent)->Directories; vetClassType* vct = dynamic_cast (lB_type->SelectedItem); Array* vpp = dynamic_cast (this->MdiParent)->vetTemplateProjects; vetPkgProject* newProject = new vetPkgProject(); newProject->FriendlyName = S"Main Component Files"; String* pathProject; if ( tB_dir->Text->Length > 1 ) pathProject = tB_dir->Text; else pathProject = String::Concat(dirs->packages, tB_name->Text, S"\\"); if ( !Directory::Exists(pathProject) ) Directory::CreateDirectory(pathProject); if ( !Directory::Exists(pathProject) ) { this->Cursor = Cursors::Default; MessageBox::Show(this, S"Error", S"Directory name is NOT valid!"); return; } vetTagHash* prjHash = new vetTagHash(); prjHash->loadDefaultHash(); prjHash->disableAll(); prjHash->editSimpleTag( prjHash->editSimpleTag( prjHash->editSimpleTag( prjHash->editSimpleTag( prjHash->editSimpleTag( prjHash->editSimpleTag( prjHash->editSimpleTag( prjHash->editSimpleTag( prjHash->editSimpleTag( S"%CLASSNAME%", tB_name->Text, true); S"%CLASSDEFINE%", tB_name->Text->ToUpper(), true); S"%VETTYPE%" vct->FriendlyName, true); S"%VERSION%", tB_version->Text, true); S"%AUTHOR%", tB_authors->Text, true); S"%FILENAME%", tB_name->Text, true); S"%TODAY%", DateTime::Now.ToString(), true); S"%SOURCEDIR%", dirs->vetSource, true); S"%LIBDIR%", dirs->vetBinaries, true); prjHash->editDoubleTag( prjHash->editDoubleTag( prjHash->editDoubleTag( prjHash->editDoubleTag( S"%VFI_START%", S"%EFI_START%", S"%DOCVAR%", S"%DOCFUN%", cB_funcv->Checked); cB_funce->Checked); cB_docs_v->Checked); cB_docs_f->Checked); newProject->TagHash = prjHash; wizardForm.cpp :: Generate function Il metodo vetTagHash::loadDefaultHash() carica la lista predefinita (i tag sono disabilitati), le chiamate multiple di vetTagHash::editSimpleTag(String*, String*, bool) aggiornano i valori da sostituire ai tag e li attivano, nonostante sia possibile impostare una tabella di tags diversa per ogni file dei progetto, si condivide sempre una singola tabella. 87 Il seguente estratto (direttamente consecutivo al precedente) si occupa di creare i due files principali (header e sorgente della classe) ed il file sorgente di test (contenente il main), le tre istanze vengono quindi aggiunte al progetto e tramite le funzioni vetPkgProject::ApplyHashes() e vetPkgProject::SaveOutputs() si effettua la sostituzione dei tag ed il salvataggio dei files (la stringa “Template” nel nome dei files predefiniti, se presente, è sostituita con il nome della classe). La seconda parte del codice analizza la selezione dell’utente in base ai progetti caricati dal sistema (vetGroups.xml), la configurazione dei ogni (sotto)progetto consiste nell’impostazione della tabella di tag corrente, della directory del progetto e del nome della classe (per la rinominazione). vetPKGFile* fileH = new vetPKGFile( String::Concat(dirs->packagesTemplate,vct->TemplateFileName, S".h"), String::Concat(pathProject, tB_name->Text, S".h") ); vetPKGFile* fileS = new vetPKGFile( String::Concat(dirs->packagesTemplate, vct->TemplateFileName, S".cpp"), String::Concat(pathProject, tB_name->Text, S".cpp") ); vetPKGFile* fileT = new vetPKGFile( String::Concat(dirs->packagesTemplate, vct->TemplateFileName, S"_test.cpp"), String::Concat(pathProject, S"test_", tB_name->Text, S".cpp") ); newProject->addFile(fileH); newProject->addFile(fileS); newProject->addFile(fileT); newProject->ApplyHashes(); newProject->SaveOutputs(); prjManForm* newPrj = new prjManForm(); newPrj->MdiParent = this->MdiParent; newPrj->AddProject(newProject); IEnumerator* en = vpp->GetEnumerator(); while ( en->MoveNext() ) { vetPkgProject* obj = __try_cast ( en->Current ); if (obj == NULL) continue; if ( ( ( ( ( ( ( obj->ID obj->ID obj->ID obj->ID obj->ID obj->ID == == == == == == 1 2 4 8 16 32 && && && && && && cB_prj_1->Checked ) || cB_prj_2->Checked ) || cB_prj_4->Checked ) || cB_prj_8->Checked ) || cB_prj_16->Checked )|| cB_prj_32->Checked ) ) { vetPkgProject* nPrj = new vetPkgProject( obj ); nPrj->TagHash = prjHash; nPrj->Directory = String::Copy( pathProject ); nPrj->SetOutputNameFromClassName( tB_name->Text ); nPrj->loadFiles(); nPrj->ApplyHashes(); nPrj->SaveOutputs(); newPrj->AddProject(nPrj); } } wizardForm.cpp :: Generate function 88 3.7 - Releasing VETLib Nonostante l’integrazione di un nuovo pacchetto nelle distribuzioni ufficiali sia un operazione relativamente semplice, il processo può complicarsi drasticamente sia a causa di conflitti con librerie esterne o interne che per la negligenza dell’autore del pacchetto, è quindi complesso automatizzare l’intero processo di controllo e aggiunta di nuovi moduli. Nei casi più semplici è sufficiente aggiungere il file sorgente al progetto (con Borland Builder o Visual Studio: Project→”Add file to Project..”, su Linux aggiungere una nuova linea alla lista dei sorgenti), includere l’header in ./include/VETLib.h, aggiornare i test (Makefile e progetti). Segue un elenco diviso in 2 fasi delle operazioni da svolgere per aggiungere un nuovo componente: Fase A: (Compilazione) Spostare i file (.h, .cpp) nella directory ./source/ (modificare i relative paths); Se il pacchetto dipende da librerie esterne aggiungerle in ./support/ ; Spostare il file di test in ./tests (modificare i relative paths); Aggiungere il sorgente ai progetti BCB / MVC / Makefile; Aggiornare i progetti del programma di test ./tests/Makefile, test_ModuleName.dsp (.bpr); Verificare le funzionalità tramite i programmi di test; Compilare la libreria ed eventualmente le special builts. Fase B: (Documentazione) Compilare la documentazione con il software Doxygen (setup file: ./docs/Doxygen); Aggiornare i file READMEs: o ./README o ./ChangeLog o ./packages/NAMESPACE o Eventualmente ./AUTHORS, ./USE, ./EXTEND, ./support/NOTES Creare gli archivi aggiornati; Aggiornare il sito con i nuovi files rilasciati: o ./downloads/ o ./documentation/ o ./distr/ o Il numero di versione in tutte le pagine; o Aggiornamento di packages.html, downloads.html E’ attualmente in corso lo sviluppo del software VETLib Distribution Manager che automatizza la gestione della libreria ed il processo di creazione degli archivi da distribuire. La configurazione è basata sui files XML: vetArchives.xml (caratteristiche di ogni archivio), vetBuilts.xml (progetti per costruire VETLib) e vetDocumentation.xml (proprietà e formati della documentazione). L’utilizzo del software richiede ovviamente la versione più completa della libreria (tutti i Tools e le librerie esterne). 89 Segue la lista degli archivi da distribuire: COMPLETE Lib VETLib-x.x.x.tar.gz (.zip) ./*.* ./docs/*.* ./include/*.* ./lib/*.* [exclusions: *.~*, *.ncb, *.opt, *.plg, *.pch] ./packages/*.* ./source/*.* ./support/*.* [exclusions: *.obj, *.~*, *.ncb, *.opt, *.plg, *.pch] ./tests/*.* BINARIES only VETLib-x.x.x.tar.gz (.zip) ./*.* ./include/*.* ./lib/*.* [exclusions: sub-folders] SOURCE only VETLib-SRC-x.x.x.tar.gz (.zip) ./*.* ./include/*.* ./lib/bcb/*.* [exclusions: *.~*] ./lib/mvc/*.* [exclusions: *.ncb, *.opt, *.plg, *.pch] ./source/*.* SOURCE + Support Libs VETLib-SRCx-x.x.x.tar.gz (.zip) ./*.* ./include/*.* ./lib/bcb/*.* [exclusions: *.~*] ./lib/mvc/*.* [exclusions: *.ncb, *.opt, *.plg, *.pch] ./source/*.* ./support/*.* SDK VETLib-SDK-x.x.x.tar.gz (.zip) ./*.* ./docs/html/*.* ./include/*.* ./lib/*.* [exclusions: *.~*, *.ncb, *.opt, *.plg, *.pch] ./source/*.* ./tests/*.* [exclusions: *.obj, *.exe, *.out, *.~*, *.ncb, *.opt, *.plg, *.pch] 90 SDK Windows ONLY VETLib-SDK-WIN-x.x.x.zip ./*.* [exclusions: Makefile.*] ./docs/html/*.* ./include/*.* ./lib/*.* [exclusions: *.a, *.~*, *.ncb, *.opt, *.plg, *.pch] ./source/*.* ./tests/*.* [exclusions: Makefile.*, *.obj, *.exe, *.out, *.~*, *.ncb, *.opt, *.plg, *.pch] SDK Linux ONLY VETLib-SDK-LNX-x.x.x.tar.gz ./*.* ./docs/html/*.* ./include/*.* ./lib/*.* [exclusions: *.lib, sub-folders] ./source/*.* ./tests/*.* [exclusions: sub-folders] DOCUMENTATION (all) VETLib-DOCS-x.x.x.tar.gz (.zip) [exclusions: Makefile.*] ./*.* ./docs/*.* ./Website/*.* [exclusions: sub-folders] ./Website/html/*.* ./Website/tutorials/*.* ./Website/screenshots/*.* HTML DOCUMENTATION VETLib-DOCS-HTML-x.x.x.tar.gz (.zip) ./*.* [exclusions: Makefile.*] ./docs/html/*.* PACKAGE STARTER KIT VETLib-PSK-x.x.x.tar.gz (.zip) ./packages/*.* [exclusion: released packages] SAMPLES VETLib-SAMPLES-x.x.x.tar.gz (.zip) ./tests/*.* PACKAGES ./packages/ VETLib-PACKAGES-x.x.x.tar.gz (.zip) [include only released packages, it is probably empty if packages are included in official built.] 91 VETLib WorkShop & PlugIns Chapter IV 4.1 - Overview VETLib WorkShop è un applicazione sviluppata in C++ .NET (Managed C++) per sistemi Windows, è destinata all’utilizzo pratico e al test di componenti VETLib (e future estensioni), in particolare ha la capacità di gestire in modo dinamico catene di filtri e sorgenti multiple, infatti verificare via codice (ad esempio in un main()) la funzionalità di una serie di filtri può risultare problematico soprattutto nella fase di ottimizzazione (o deduzione) dei parametri, il principale vantaggio di WorkShop è l’accesso rapido ai parametri di ogni componente e la possibilità di visualizzare in tempo reale sia la sequenza finale che i passi intermedi. L’interfaccia grafica è di tipo MDI (Multiple Document Interface), i child forms (finestre figlie) sono le interfacce visuali dei componenti VETLib, queste finestre sono quasi completamente indipendenti, il componente vero può essere compilato in modo statico (linkato con il software) oppure può essere implementato in modo indipendente e caricato durante l’esecuzione (sono comunemente definiti plugins), i principali componenti gia integrati (statici) sono: 9 Sorgente statica (immagini, formati più comuni) Converte nello standard VETLib vetFrameRGB24 un immagine caricata tramite il componente System.Drawing.Bitmap di .NET framework. 9 Sorgente Playback video in formato MPEG4 (XVID) Interfaccia grafica del componente vetCodec_XVID. 9 Capture/Grabbing/Preview Real-Time DirectX (VideoIn, IEEE1394, CAMs) Interfaccia grafica del componente vetDirectXInput2. 9 Sorgente Playback (tutte codifiche supportate dal sistema) Interfaccia grafica del componente vetDXMovieLoader. 9 Sorgente (grabbing) tramite DirectX (CAMs) Interfaccia grafica del componente vetDirectXInput. 9 Finestra di Visualizzazione Converte i frame dallo standard vetFrameRGB24 al formato supportato da .NET (GDI+). WorkShop è in grado di caricare dinamicamente nuovi componenti incapsulati in file DLL (plugins), analizzeremo più avanti nel dettaglio le modalità di creazione dei plugins e l’implementazione del sistema, la versione corrente include alcuni plugins derivati dai componenti vetFilterGeometric, vetDigitalFilter, vetBufferArray , vetBufferLink . Attualmente WorkShop supporta solo lo standard I/O vetFrameRGB24, si prevede di superare questa limitazione nel prossimo aggiornamento attraverso l’utilizzo del componente vetProcess (non ancora rilasciato). La piattaforma .NET introduce un innovativo sistema di gestione delle eccezioni, qualora non siano gestite esplicitamente dal programma è spesso possibile continuare l’esecuzione ed almeno salvare il lavoro svolto. I componenti direttamente integrati nel WorkShop sono incapsulati staticamente dalla libreria completa compilata con Microsoft Visual Studio 6.0 (VETLib_full_vc6.lib), quindi eventuali aggiornamenti di questi moduli implicano anche la ricompilazione dell’applicazione (diversamente dai plugins). I plugins sono incapsulati in DLL (Dynamic Link Library) standard (senza estensioni), simili alle DLL del kernel di Windows, è possibile caricare al massimo n componenti, un istanza ciascuno, con 93 n definito alla compilazione (attualmente 64). In futuro si prevede di aggiornare il sistema di gestione dei plugins in modo da supportare più istanze di un singolo plugin e anche DLL .NET, ciò necessita un ambiente di sviluppo Microsoft di ultima generazione anche per lo sviluppatore della DLL, ma consente interessanti sviluppi come un interfaccia grafica di controllo del componente estremamente semplice da sviluppare e perfettamente integrata in WorkShop. WorkShop è distribuito in due diversi formati: un archivio compresso ZIP e un pacchetto di installazione per Windows (.msi), il pacchetto è creato con il sistema di deployment integrato in Microsoft Visual Studio 7.0 (.NET), il file progetto è vetWS3_Setup.vdproj ed è incluso nel progetto principale di WorkShop vetWS3.vcproj, le dipendenze sono gestite automaticamente. La directory di installazione predefinita è \VETLib\WorkShop, i files distribuiti sono: data layout plugins reference samples shots ChangeLog.txt README ToDo vetWS3.log vetWS3.exe File essenziali ed opzionali per l’esecuzione del software; File di configurazione del layout di WorkShop (background, ..); I plugins inclusi nella distribuzione corrente (DLL); La documentazione di WorkShop in formato RTF; Contiene una serie di immagini e video per i tests; Cartella designata a memorizzare gli screenshots; Lista degli aggiornamenti divisi per built; Informazioni preliminari; Lista degli aggiornamenti futuri; File log delle sessioni (nuove sessioni aggiunte al termine); Eseguibile principale. WorkShop è open source, la licenza è la classica General Public License (in appendice). Informazioni, screenshots e links sono raccolti nella pagina ./Website/workshop.html disponibile online http://lnx.ewgate.net/vetlib/workshop.html. 94 4.2 - How It Works Il linguaggio Managed C++ consente solo parzialmente l’interazione tra oggetti classici e oggetti Managed (cioè gestiti dal garbage collector), quindi per non incorrere in scomode limitazioni (specialmente in futuro), si sono definite due nuove interfacce similari a vetInput e vetOutput (nonostante le relazioni tra i componenti siano praticamente identiche al framework VETLib). Segue la definizione delle interfacce: public __gc __interface sourceInterface { VETRESULT extractTo(vetFrameRGB24* img); vetProcess* getMyProcess(); //currently not implemented outputInterface* getOutputInterface(); System::Object __gc* getObjectInstance(); void viewsUpdate(void); }; public __gc __interface outputInterface { VETRESULT importFrom(vetFrameRGB24* img); vetProcess* getMyProcess(); //currently not implemented sourceInterface* getSourceInterface(); System::Object __gc* getObjectInstance(); void sourcesUpdate(void); int setSource(sourceInterface* sF); }; Come si può notare attualmente lo standard I/O interno è solo vetFrameRGB24, i due metodi che consentono ai componenti di ricevere e trasmettere frames sono gli omonimi del framework VETLib, mentre gli altri prototipi sono di supporto, queste interfacce sono implementate da forms (praticamente finestre) definiti come child dell’applicazione principale (MDI), segue un estratto della GUI (Graphic User Interface) del componente vetDirectXInput: public __gc class dxinputForm : { public: dxinputForm(void); public System::Windows::Forms::Form, public sourceInterface void Init() { myCap = new vetDirectXInput(); } VETRESULT extractTo(vetFrameRGB24* img) { if (img != NULL && myCap->getCurrentDevice() != -1) return myCap->extractTo( *img ); return VETRET_ILLEGAL_USE; } void dxinputForm::Dispose(Boolean disposing) { delete myCap; } private: vetDirectXInput* myCap; private: outputInterface* vF; public: outputInterface* getOutputInterface() { return vF; } public: System::Object __gc* getObjectInstance() { return this; }; // [..] 95 Il componente standard per la visualizzazione dei frame è viewForm, l’implementazione dell’interfaccia outputInterface è molto semplice poiché non c’è nessun controllo sul flusso dati e l’immagine non viene bufferizzata ma copiata direttamente nel buffer grafico di Windows: public __gc class viewForm : { public: public System::Windows::Forms::Form, public outputInterface viewForm(void) { InitializeComponent(); bm = new Bitmap(400, 400); // will be resized on first call pictureBoxBuffer->Image = dynamic_cast (bm); } VETRESULT importFrom(vetFrameRGB24* img) { if (img == NULL) return VETRET_PARAM_ERR; if (img->width == 0 || img->height == 0 || img->data[0] == NULL) return VETRET_PARAM_ERR; if (img->width != bm->Width || img->height != bm->Height) { bm->Dispose(); bm = new Bitmap(img->width, img->height); pictureBoxBuffer->Image = dynamic_cast (bm); } BitmapData* bitData; Rectangle rec(0,0, img->width, img->height); bitData = bm->LockBits( rec, ImageLockMode::WriteOnly, PixelFormat::Format24bppRgb ); unsigned char* dest; dest = static_cast ( bitData->Scan0.ToPointer() ); // this would be great but micro$oft LIES about it.. // memcpy( dest, img.data[0], // img.height * img.width * 3 * sizeof(unsigned char) ); // // in fact it's BGR not RGB.. vetUtility::conv_bgr_rgb( dest, (unsigned char*)img->data[0], img->width, img->height); bm->UnlockBits(bitData); pictureBoxBuffer->Refresh(); } return VETRET_OK; void sourcesUpdate(void) { } int setSource(sourceInterface* sF) { return 0; } L’accesso al buffer GDI+ è ottimizzato tramite le pericolose funzioni di accesso al puntatore (LockBits e UnlockBits), le prestazioni sono più che accettabili ed attualmente superiori alle necessità dei processi standard, la massima frame rate supportata da un comune PC domestico è circa 20-25 fps. 96 La gestione del flusso dati (vetFrameRGB24) non è ottima, sia per il controllo sulla frame rate (tempo di elaborazione non considerato) sia per l’assenza di un vero multi-threading, il vantaggio è che è possibile controllare il flusso dati in modo estremamente elastico grazie all’opzione AutoPush disponibile nei filtri (filterForm.cpp): VETRESULT filterForm::importFrom(vetFrameRGB24* img) { VETRESULT ret = VETRET_OK; if ( img != NULL && buffer != NULL && myFilter != NULL ) { ret += myFilter->importFrom(*img); ret += myFilter->extractTo(*buffer); } if (cBautoEx->Checked) button1_Click(NULL, NULL); return ret; } System::Void filterForm::button1_Click(…) { if (vF && buffer != NULL) vF->importFrom(buffer); } Il sistema è stato implementato in questo modo come soluzione temporanea, nell’attesa dello sviluppo completo del componente vetProcess. Questo oggetto si occupa (occuperà) di gestire una catena di filtri in modo ottimizzato e semi-automatico attraverso threads e l’accesso diretto ai buffer per minimizzare le inutili operazioni di copia, come si può notare dall’estratto di codice, WorkShop non sfrutta il sistema di accesso diretto al buffer interno (dump_buffer*). Dopo l’elaborazione, il valore del checkbox cBautoEx abilita la redirezione del un frame al successivo componente (qualora esista), vF è il puntatore a un implementazione dell’interfaccia di output e quindi si tratta di un altro filtro oppure di un componente di tipo vetOuput (finestra di visualizzazione). Le finestre di tipo sorgente (per esempio dxinputForm.h) e di tipo filtro (filterForm.h) condividono alcuni controlli del flusso dati (in fondo alla finestra), la lista scorrevole cbViews (combo box) elenca i nomi delle possibili destinazioni (in pratica aggiorna il puntatore vF), il prototipo viewsUpdate() imposto dall’interfaccia sourceInterface è chiamato dal framework (WorkShop) quando viene istanziata una nuova finestra, l’implementazione che segue è estratta dalla classe filterForm (filterForm.h) ed aggiorna la lista: void viewsUpdate(void) { int oldSel = cBviews->SelectedIndex; cBviews->Items->Clear(); cBviews->Items->Add( new System::String(S"NULL Output") ); for (int i=0; i MdiParent->MdiChildren->Count; i++) { if ( this->MdiParent->MdiChildren->get_Item(i)->GetType()->GetInterface("outputInterface") && !this->MdiParent->MdiChildren->get_Item(i)->Equals(this) } ) cBviews->Items->Add((static_cast
Source Exif Data:
File Type : PDF File Type Extension : pdf MIME Type : application/pdf PDF Version : 1.5 Linearized : Yes Page Count : 150 Has XFA : No XMP Toolkit : XMP toolkit 2.9.1-13, framework 1.6 About : uuid:05f02a75-7ce6-4619-a247-d88c1510707a Modify Date : 2006:03:15 17:05:09+01:00 Create Date : 2006:03:09 07:09:24+01:00 Metadata Date : 2006:03:15 17:05:09+01:00 Document ID : uuid:525a5b31-a90e-49ea-adf9-b7659722b664 Format : application/pdf Title : VETLib - Video Elaboration & Transmission LIBrary Description : Image & Video Processing FrameWork Creator : Alessandro Polo Author : Alessandro Polo Subject : Image & Video Processing FrameWork Producer : Acrobat Distiller 6.0 (Windows)EXIF Metadata provided by EXIF.tools