4.16. Anteprima immagine

Il pool di frame buffer è il luogo da cui l’applicazione legge i propri frame. Mentre l’applicazione lavora su quei frame, anche ciò che è collegato alla camera per visualizzarli in anteprima ha bisogno di una copia di ogni frame. La camera dispone di un secondo buffer dedicato a tale scopo, e di una singola regola per stabilire quando viene riempito: ogni volta che l’applicazione chiama snapshot(), il frame catturato precedente viene copiato nel buffer di anteprima prima che il nuovo frame venga restituito.

L’applicazione e il previewer non contendono mai per la stessa memoria. L’applicazione legge il proprio frame dal pool; il previewer legge il proprio frame dal buffer di anteprima. Entrambi avvengono in parallelo.

4.16.1. Lo stream framebuffer

Il buffer di anteprima – lo stream framebuffer – è una singola regione di RAM a dimensione fissa, separata dal pool di frame buffer. La sua dimensione è impostata in fase di build del firmware e non cambia con framesize() o pixformat(). Circa un megabyte è il valore tipico sulle OpenMV Cam recenti – abbastanza grande da contenere un’anteprima a risoluzione moderata, molto più piccolo di un frame a piena risoluzione alle dimensioni massime del sensore.

Il codice dell’applicazione non legge né scrive direttamente questo buffer; il driver della camera lo riempie come effetto collaterale di snapshot().

4.16.2. Cosa fa lo snapshot per l’anteprima

A ogni chiamata a snapshot(), prima che il driver rilasci nel pool il frame buffer precedente dell’applicazione e ne consegni uno nuovo, copia il frame precedente nel buffer di anteprima – con ancora sull’immagine tutto ciò che l’applicazione vi ha disegnato sopra durante l’elaborazione. Sono possibili due rami. Quale dei due viene eseguito è scelto dal previewer, non dalla camera: il consumatore che ha aperto l’anteprima comunica al driver se desidera l’immagine raw o un JPEG, e quale dimensione della finestra raw può accettare.

  • Copia raw ridimensionata in scala ridotta. Quando il previewer ha richiesto frame raw, il driver copia il frame precedente nel suo formato pixel nativo (RGB565, scala di grigi, ecc.). Se il frame è più grande della finestra raw richiesta dal previewer, il driver lo riduce in scala con filtraggio bilineare finché non vi rientra; altrimenti i pixel passano invariati. Nessun artefatto di compressione; il previewer vede gli stessi pixel su cui l’applicazione stava lavorando.

  • Compressione JPEG. Quando il previewer ha richiesto JPEG – oppure quando la copia raw non rientrerebbe affatto nello stream buffer – il driver comprime in JPEG il frame precedente alla sua piena risoluzione all’interno dello stream buffer. La qualità viene regolata in modo adattivo per ogni frame, così l’output compresso resta entro la capacità dello stream buffer. Quando un frame rientra, il driver alza gradualmente la qualità di un passo verso un tetto che dipende dalla dimensione in pixel del frame catturato (i frame più piccoli possono avere qualità maggiore; i frame più grandi sono limitati più in basso così da non poter andare in overflow per una piccola variazione di contenuto). Quando un frame non rientra, il driver dimezza la qualità corrente, la mantiene al livello ridotto per le successive diverse decine di frame in modo che la nuova impostazione abbia il tempo di stabilizzarsi, e scarta dall’anteprima il frame andato in overflow. Il loop dell’applicazione continua a girare senza essere influenzato; solo il previewer perde il frame scartato.

I frame che la camera produce in un formato già compresso (il formato pixel JPEG sui sensori che emettono JPEG direttamente) saltano entrambi i rami: il bitstream codificato viene copiato così com’è direttamente nel buffer di anteprima.

Il previewer interroga secondo la propria cadenza, generalmente molto più lenta della velocità di cattura della camera, quindi sotto-campiona il rate di cattura raw: vengono mostrati solo gli snapshot che riesce a leggere in tempo. Se un nuovo snapshot() arriva al buffer di anteprima prima che il previewer abbia letto il frame precedente, il buffer è ancora bloccato dal previewer e il nuovo aggiornamento dell’anteprima viene saltato – quella cattura è persa dallo stream di anteprima. Il pool di frame buffer proprio dell’applicazione non ne risente; il frame catturato arriva comunque normalmente all’applicazione.

4.16.3. Inviare manualmente l’ultimo frame

Poiché l’anteprima viene aggiornata come effetto collaterale di snapshot(), uno script che termina senza mai chiamare di nuovo snapshot lascia indefinitamente sul previewer qualunque cosa avesse inviato per ultima all’anteprima – il che, per uno script che svolge il proprio lavoro prima del primo snapshot e poi esce, è un’anteprima vuota. image.Image.flush() (o l’equivalente flush() sull’oggetto CSI) copia su richiesta il contenuto corrente del frame buffer dell’applicazione nello stream buffer, senza catturare un nuovo frame:

img = csi0.snapshot()
# process the image and draw on it
img.flush()                               # previewer sees the annotated frame

La stessa chiamata è utile anche quando un’operazione di lunga durata si interpone tra gli snapshot e il previewer mostrerebbe altrimenti per tutto il tempo un’anteprima obsoleta.

Nota

L’applicazione di anteprima deve leggere il frame dallo stream buffer prima che lo script esca. Un flush alla fine di uno script breve si limita a predisporre il frame; se lo script restituisce poi il controllo alla camera prima che il previewer abbia interrogato, il buffer viene riutilizzato alla successiva esecuzione e quel frame finale è perso. Per le anteprime di fine script, concedi al previewer un momento per prelevare il frame (un breve sleep dopo il flush, oppure semplicemente non uscire immediatamente) prima che lo script termini.