Analisi di immagini

Il caso d'uso dell'analisi delle immagini fornisce alla tua app un'immagine accessibile dalla CPU su cui puoi eseguire l'elaborazione delle immagini, la visione artificiale o l'inferenza del machine learning. L'applicazione implementa un metodo analyze() che viene eseguito su ogni frame.

Per scoprire come integrare ML Kit di Google con la tua app CameraX, consulta ML Kit Analyzer.

Modalità operative

Quando la pipeline di analisi dell'applicazione non è in grado di soddisfare i requisiti di frequenza fotogrammi di CameraX, CameraX può essere configurata per rilasciare i frame in uno dei seguenti modi:

  • non bloccante (impostazione predefinita): in questa modalità, l'esecutore memorizza sempre nella cache l'immagine più recente in un buffer immagine (simile a una coda con profondità di uno), mentre l'applicazione analizza l'immagine precedente. Se CameraX riceve una nuova immagine prima che l'applicazione termini l'elaborazione, la nuova immagine viene salvata nello stesso buffer, sovrascrivendo l'immagine precedente. Tieni presente che ImageAnalysis.Builder.setImageQueueDepth() non ha alcun effetto in questo scenario e che i contenuti del buffer vengono sempre sovrascritti. Puoi attivare questa modalità non di blocco chiamando setBackpressureStrategy() con STRATEGY_KEEP_ONLY_LATEST. Per saperne di più sulle implicazioni per gli esecutori, consulta la documentazione di riferimento per STRATEGY_KEEP_ONLY_LATEST.

  • Blocco: in questa modalità, l'esecutore interno può aggiungere più immagini alla coda di immagini interna e inizia a eliminare i frame solo quando la coda è piena. Il blocco si verifica nell'intero ambito del dispositivo della fotocamera: se il dispositivo della videocamera ha più casi d'uso associati, questi casi d'uso verranno tutti bloccati durante l'elaborazione di queste immagini da parte di CameraX. Ad esempio, quando sia l'anteprima sia l'analisi delle immagini sono associate a un dispositivo Fotocamera, anche l'anteprima viene bloccata mentre CameraX elabora le immagini. Per attivare la modalità di blocco, passa STRATEGY_BLOCK_PRODUCER a setBackpressureStrategy(). Puoi anche configurare la profondità della coda delle immagini utilizzando ImageAnalysis.Builder.setImageQueueDepth().

Grazie a un analizzatore a bassa latenza e ad alte prestazioni in cui il tempo totale per analizzare un'immagine è inferiore alla durata di un fotogramma di CameraX (ad esempio 16 ms per 60 f/s), entrambe le modalità operative offrono un'esperienza generale omogenea. La modalità di blocco può comunque essere utile in alcuni scenari, ad esempio in caso di tremolio di sistema molto brevi.

Con uno strumento di analisi ad alte prestazioni e ad alta latenza, è necessaria la modalità di blocco con una coda più lunga per compensare la latenza. Tuttavia, l'applicazione può ancora elaborare tutti i frame.

Con un analizzatore dispendioso in termini di tempo e latenza elevata (l'analizzatore non è in grado di elaborare tutti i frame), potrebbe essere più appropriata una modalità non di blocco, in quanto i frame devono essere eliminati per il percorso di analisi, ma altri casi d'uso associati in contemporanea possono comunque vedere tutti i frame.

Implementazione

Per utilizzare l'analisi delle immagini nella tua applicazione, procedi nel seguente modo:

Subito dopo l'associazione, CameraX invia le immagini all'analizzatore registrato. Dopo aver completato l'analisi, chiama ImageAnalysis.clearAnalyzer() o svincola il caso d'uso ImageAnalysis per interrompere l'analisi.

Crea caso d'uso ImageAnalysis

ImageAnalysis connette l'analizzatore (un consumer di immagini) a CameraX, che produce immagini. Le applicazioni possono usare ImageAnalysis.Builder per creare un oggetto ImageAnalysis. Con ImageAnalysis.Builder, l'applicazione può configurare quanto segue:

Le applicazioni possono impostare la risoluzione o le proporzioni, ma non entrambe. La risoluzione esatta dell'output dipende dalle dimensioni (o proporzioni) richieste dell'applicazione e dalle funzionalità hardware e potrebbe essere diversa dalle dimensioni o dalle proporzioni richieste. Per informazioni sull'algoritmo di corrispondenza della risoluzione, consulta la documentazione per setTargetResolution()

Un'applicazione può configurare i pixel dell'immagine di output in modo che siano in spazi colore YUV (predefinito) o RGBA. Quando si imposta un formato di output RGBA, CameraX converte internamente le immagini dallo spazio colore YUV a RGBA e comprime i bit di immagine nel ByteBuffer del primo piano di ImageProxy (gli altri due piani non vengono utilizzati) con la seguente sequenza:

ImageProxy.getPlanes()[0].buffer[0]: alpha
ImageProxy.getPlanes()[0].buffer[1]: red
ImageProxy.getPlanes()[0].buffer[2]: green
ImageProxy.getPlanes()[0].buffer[3]: blue
...

Quando esegui analisi di immagini complesse in cui il dispositivo non riesce a tenere il passo con la frequenza fotogrammi, puoi configurare CameraX in modo che rilasci i frame con le strategie descritte nella sezione Modalità operative di questo argomento.

Crea l'analizzatore

Le applicazioni possono creare analizzatori implementando l'interfaccia ImageAnalysis.Analyzer e eseguendo l'override di analyze(ImageProxy image). In ogni analizzatore, le applicazioni ricevono un ImageProxy, che è un wrapper per Media.Image. Per eseguire query sul formato dell'immagine, puoi utilizzare ImageProxy.getFormat(). Il formato è uno dei seguenti valori forniti dall'applicazione con ImageAnalysis.Builder:

  • ImageFormat.RGBA_8888 se l'app ha richiesto OUTPUT_IMAGE_FORMAT_RGBA_8888.
  • ImageFormat.YUV_420_888 se l'app ha richiesto OUTPUT_IMAGE_FORMAT_YUV_420_888.

Consulta il caso d'uso Build ImageAnalysis per le configurazioni dello spazio colore e dove è possibile recuperare i byte in pixel.

All'interno di un analizzatore, l'applicazione deve:

  1. Analizza un determinato frame il più rapidamente possibile, preferibilmente entro il limite di tempo della frequenza frame specificato (ad esempio, meno di 32 ms per caso di 30 f/s). Se l'applicazione non è in grado di analizzare un frame abbastanza rapidamente, prendi in considerazione uno dei meccanismi di eliminazione dei frame supportati.
  2. Rilascia ImageProxy a CameraX chiamando il numero ImageProxy.close(). Tieni presente che non devi chiamare la funzione di chiusura dell'immagine Media.Image con wrapping (Media.Image.close()).

Le applicazioni possono utilizzare il Media.Image con wrapping direttamente in ImageProxy. Non chiamare Media.Image.close() nell'immagine a capo perché questo comprometterebbe il meccanismo di condivisione delle immagini all'interno di CameraX; utilizza invece ImageProxy.close() per rilasciare il valore Media.Image sottostante a CameraX.

Configura l'analizzatore per ImageAnalysis

Una volta creato un analizzatore, utilizza ImageAnalysis.setAnalyzer() per registrarlo e iniziare l'analisi. Al termine dell'analisi, utilizza ImageAnalysis.clearAnalyzer() per rimuovere l'analizzatore registrato.

È possibile configurare un solo analizzatore attivo per l'analisi delle immagini. La chiamata a ImageAnalysis.setAnalyzer() sostituisce l'analizzatore registrato, se esiste. Le applicazioni possono impostare un nuovo analizzatore in qualsiasi momento, prima o dopo l'associazione del caso d'uso.

Associare l'analisi delle immagini a un ciclo di vita

Ti consigliamo vivamente di associare ImageAnalysis a un ciclo di vita AndroidX esistente con la funzione ProcessCameraProvider.bindToLifecycle(). Tieni presente che la funzione bindToLifecycle() restituisce il dispositivo Camera selezionato, che può essere utilizzato per perfezionare le impostazioni avanzate, come l'esposizione e altre ancora. Consulta questa guida per ulteriori informazioni su come controllare l'output della videocamera.

L'esempio seguente combina tutto ciò dei passaggi precedenti, associando i casi d'uso di CameraX ImageAnalysis e Preview a un proprietario di lifeCycle:

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    // enable the following line if RGBA output is needed.
    // .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
    .setTargetResolution(Size(1280, 720))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()
imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { imageProxy ->
    val rotationDegrees = imageProxy.imageInfo.rotationDegrees
    // insert your code here.
    ...
    // after done, release the ImageProxy object
    imageProxy.close()
})

cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview)

Java

ImageAnalysis imageAnalysis =
    new ImageAnalysis.Builder()
        // enable the following line if RGBA output is needed.
        //.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
        .setTargetResolution(new Size(1280, 720))
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        .build();

imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() {
    @Override
    public void analyze(@NonNull ImageProxy imageProxy) {
        int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();
            // insert your code here.
            ...
            // after done, release the ImageProxy object
            imageProxy.close();
        }
    });

cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, imageAnalysis, preview);

Risorse aggiuntive

Per saperne di più su CameraX, consulta le seguenti risorse aggiuntive.

Codelab

  • Introduzione a FotocameraX
  • Esempio di codice

  • Esempi di app CameraX