Alta gamma dinamica (HDR) e acquisizione di foto a bassa illuminazione

Questo articolo illustra come usare la classe AdvancedPhotoCapture per acquisire foto HDR (High Dynamic Range). Questa API consente anche di ottenere un frame di riferimento dall'acquisizione HDR prima del completamento dell'elaborazione dell'immagine finale.

Altri articoli correlati all'acquisizione HDR includono:

Nota

A partire da Windows 10, versione 1709, è supportata la registrazione di video e l'uso di AdvancedPhotoCapture contemporaneamente. Ciò non è supportato nelle versioni precedenti. Questa modifica significa che è possibile avere un LowLagMediaRecording preparato e AdvancedPhotoCapture contemporaneamente. Si può avviare o arrestare la registrazione video tra le chiamate a MediaCapture.PrepareAdvancedPhotoCaptureAsync e AdvancedPhotoCapture.FinishAsync. Si può anche chiamare AdvancedPhotoCapture.CaptureAsync durante la registrazione del video. Tuttavia, alcuni scenari AdvancedPhotoCapture, ad esempio l'acquisizione di una foto HDR durante la registrazione del video causano la modifica di alcuni fotogrammi video dall'acquisizione HDR, con conseguente un'esperienza utente negativa. Per questo motivo, l'elenco delle modalità restituite da AdvancedPhotoControl.SupportedModes sarà diverso durante la registrazione del video. È consigliabile controllare questo valore immediatamente dopo l'avvio o l'arresto della registrazione video per assicurarsi che la modalità desiderata sia supportata nello stato di registrazione video corrente.

Nota

A partire da Windows 10, versione 1709, quando AdvancedPhotoCapture è impostato sulla modalità HDR, l'impostazione della proprietà FlashControl.Enabled viene ignorata e il flash non viene mai attivato. Per altre modalità di acquisizione, se FlashControl.Enabled, eseguirà l'override delle impostazioni AdvancedPhotoCapture e causerà l'acquisizione di una foto normale con flash. Se Auto è impostato su true, AdvancedPhotoCapture potrebbe usare o meno il flash, a seconda del comportamento predefinito del driver della fotocamera per le condizioni nella scena corrente. Nelle versioni precedenti, l'impostazione flash AdvancedPhotoCapture sostituisce sempre l'impostazione FlashControl.Enabled.

Nota

Questo articolo si basa sui concetti e sul codice descritti in Acquisizione di foto, video e audio di base con MediaCapture, che descrive i passaggi necessari per implementare l'acquisizione di foto e video di base. È consigliabile acquisire familiarità con il modello di acquisizione multimediale di base in questo articolo prima di passare a scenari di acquisizione più avanzati. Il codice in questo articolo presuppone che l'app abbia già un'istanza di MediaCapture che è stata inizializzata correttamente.

Esiste un esempio di Windows universale che illustra l'uso della classe AdvancedPhotoCapture che si può usare per visualizzare l'API usata nel contesto o come punto di partenza per l'app. Per ulteriori informazioni, vedere Esempio di acquisizione avanzata della fotocamera.

Spazi dei nomi avanzati per l'acquisizione di foto

Gli esempi di codice in questo articolo usano le API negli spazi dei nomi seguenti oltre agli spazi dei nomi necessari per l'acquisizione multimediale di base.

using Windows.Media.Core;
using Windows.Media.Devices;

Acquisizione di foto HDR

Determinare se l'acquisizione di foto HDR è supportata nel dispositivo corrente

La tecnica di acquisizione HDR descritta in questo articolo viene eseguita usando l'oggetto AdvancedPhotoCapture. Non tutti i dispositivi supportano l'acquisizione HDR con AdvancedPhotoCapture. Determinare se il dispositivo in cui è attualmente in esecuzione l'app supporta la tecnica recuperando il VideoDeviceController dell'oggetto MediaCapture e quindi ottenendo la proprietà AdvancedPhotoControl. Controllare la raccolta SupportedModes del controller del dispositivo video per verificare se include AdvancedPhotoMode.Hdr. In caso affermativo, l'acquisizione HDR con AdvancedPhotoCapture è supportata.

bool _hdrSupported;
private void IsHdrPhotoSupported()
{
    _hdrSupported = _mediaCapture.VideoDeviceController.AdvancedPhotoControl.SupportedModes.Contains(Windows.Media.Devices.AdvancedPhotoMode.Hdr);
}

Configurare e preparare l'oggetto AdvancedPhotoCapture

Poiché si dovrà accedere all'istanza AdvancedPhotoCapture da più posizioni all'interno del codice, si deve dichiarare una variabile membro per contenere l'oggetto.

private AdvancedPhotoCapture _advancedCapture;

Nell'app, dopo aver inizializzato l'oggetto MediaCapture, creare un oggetto AdvancedPhotoCaptureSettings e impostare la modalità a AdvancedPhotoMode.Hdr. Chiamare il modo Configura dell'oggetto AdvancedPhotoControl, passando nell'oggetto AdvancedPhotoCaptureSettings creato.

Chiamare MediaCapture dell'oggetto PrepareAdvancedPhotoCaptureAsync, passando in un ImageEncodingProperties che specifica il tipo di codifica da usare per l'acquisizione. La classe ImageEncodingProperties fornisce metodi statici per la creazione delle codifiche delle immagini supportate da MediaCapture.

PrepareAdvancedPhotoCaptureAsync restituisce l'oggetto AdvancedPhotoCapture che si userà per avviare l'acquisizione di foto. Si può usare questo oggetto per registrare i gestori per OptionalReferencePhotoCaptured e AllPhotosCaptured descritti più avanti in questo articolo.

if (_hdrSupported == false) return;

// Choose HDR mode
var settings = new AdvancedPhotoCaptureSettings { Mode = AdvancedPhotoMode.Hdr };

// Configure the mode
_mediaCapture.VideoDeviceController.AdvancedPhotoControl.Configure(settings);

// Prepare for an advanced capture
_advancedCapture = 
    await _mediaCapture.PrepareAdvancedPhotoCaptureAsync(ImageEncodingProperties.CreateUncompressed(MediaPixelFormat.Nv12));

// Register for events published by the AdvancedCapture
_advancedCapture.AllPhotosCaptured += AdvancedCapture_AllPhotosCaptured;
_advancedCapture.OptionalReferencePhotoCaptured += AdvancedCapture_OptionalReferencePhotoCaptured;

Acquisire una foto HDR

Acquisire una foto HDR chiamando il metodo CaptureAsync dell'oggetto AdvancedPhotoCapture. Questo metodo restituisce un oggetto AdvancedCapturedPhoto che fornisce la foto acquisita nella relativa proprietà Frame.

try
{

    // Start capture, and pass the context object
    AdvancedCapturedPhoto advancedCapturedPhoto = await _advancedCapture.CaptureAsync();

    using (var frame = advancedCapturedPhoto.Frame)
    {
        // Read the current orientation of the camera and the capture time
        var photoOrientation = CameraRotationHelper.ConvertSimpleOrientationToPhotoOrientation(
            _rotationHelper.GetCameraCaptureOrientation());
        var fileName = String.Format("SimplePhoto_{0}_HDR.jpg", DateTime.Now.ToString("HHmmss"));
        await SaveCapturedFrameAsync(frame, fileName, photoOrientation);
    }
}
catch (Exception ex)
{
    Debug.WriteLine("Exception when taking an HDR photo: {0}", ex.ToString());
}

La maggior parte delle app fotografiche vuole codificare la rotazione di una foto acquisita nel file di immagine in modo che possa essere visualizzata correttamente da altre app e dispositivi. In questo esempio viene illustrato l'uso della classe Fotocamera RotationHelper per calcolare l'orientamento appropriato per il file. Questa classe è descritta ed elencata in modo completo nell'articolo Gestire l'orientamento del dispositivo con MediaCapture.

Il metodo SaveCapturedFrameAsync helper, che salva l'immagine su disco, viene illustrato più avanti in questo articolo.

Ottenere un frame di riferimento facoltativo

Il processo HDR acquisisce più fotogrammi e li composita in un'unica immagine dopo l'acquisizione di tutti i fotogrammi. È possibile accedere a un frame dopo l'acquisizione, ma prima che l'intero processo HDR venga completato gestendo l'evento OptionalReferencePhotoCaptured. Non si deve farlo se si è interessati solo al risultato finale della foto HDR.

Importante

OptionalReferencePhotoCaptured non viene generato nei dispositivi che supportano l'hardware HDR e pertanto non generano fotogrammi di riferimento. L'app deve gestire il caso in cui questo evento non viene generato.

Poiché il frame di riferimento arriva fuori dal contesto della chiamata a CaptureAsync, viene fornito un meccanismo per passare le informazioni di contesto al gestore OptionalReferencePhotoCaptured. Prima di tutto è necessario chiamare un oggetto che conterrà le informazioni di contesto. Il nome e il contenuto di questo oggetto sono a voi. In questo esempio viene definito un oggetto con membri per tenere traccia del nome file e dell'orientamento della fotocamera dell'acquisizione.

public class MyAdvancedCaptureContextObject
{
    public string CaptureFileName;
    public PhotoOrientation CaptureOrientation;
}

Creare una nuova istanza dell'oggetto di contesto, popolarne i membri e quindi passarla all'overload di CaptureAsync che accetta un oggetto come parametro.

// Read the current orientation of the camera and the capture time
var photoOrientation = CameraRotationHelper.ConvertSimpleOrientationToPhotoOrientation(
        _rotationHelper.GetCameraCaptureOrientation());
var fileName = String.Format("SimplePhoto_{0}_HDR.jpg", DateTime.Now.ToString("HHmmss"));

// Create a context object, to identify the capture in the OptionalReferencePhotoCaptured event
var context = new MyAdvancedCaptureContextObject()
{
    CaptureFileName = fileName,
    CaptureOrientation = photoOrientation
};

// Start capture, and pass the context object
AdvancedCapturedPhoto advancedCapturedPhoto = await _advancedCapture.CaptureAsync(context);

Nel gestore eventi OptionalReferencePhotoCaptured eseguire il cast dellaproprietà Context dell'oggetto OptionalReferencePhotoCapturedEventArgs context. alla classe dell'oggetto. In questo esempio viene modificato il nome del file per distinguere l'immagine del frame di riferimento dall'immagine HDR finale e quindi viene chiamato il metodo helper SaveCapturedFrameAsync per salvare l'immagine.

private async void AdvancedCapture_OptionalReferencePhotoCaptured(AdvancedPhotoCapture sender, OptionalReferencePhotoCapturedEventArgs args)
{
    // Retrieve the context (i.e. what capture does this belong to?)
    var context = args.Context as MyAdvancedCaptureContextObject;

    // Remove "_HDR" from the name of the capture to create the name of the reference
    var referenceName = context.CaptureFileName.Replace("_HDR", "");

    using (var frame = args.Frame)
    {
        await SaveCapturedFrameAsync(frame, referenceName, context.CaptureOrientation);
    }
}

Ricevere una notifica quando tutti i fotogrammi sono stati acquisiti

L'acquisizione di foto HDR prevede due passaggi. Prima di tutto, vengono acquisiti più fotogrammi e quindi i fotogrammi vengono elaborati nell'immagine HDR finale. Non si può avviare un'altra acquisizione mentre i fotogrammi HDR di origine sono ancora in fase di acquisizione, ma si può avviare un'acquisizione dopo che tutti i fotogrammi sono stati acquisiti, ma prima del completamento della post-elaborazione HDR. L'evento AllPhotosCaptured viene generato al termine delle acquisizioni HDR, per sapere che è possibile avviare un'altra acquisizione. Uno scenario tipico consiste nel disabilitare il pulsante di acquisizione dell'interfaccia utente all'avvio dell'acquisizione HDR e quindi riabilitarlo quando viene generato All Foto Captured.

private void AdvancedCapture_AllPhotosCaptured(AdvancedPhotoCapture sender, object args)
{
    // Update UI to enable capture button
}

Pulire l'oggetto AdvancedPhotoCapture

Al termine dell'acquisizione dell'app, prima di eliminare l'oggetto MediaCapture si deve chiudere l'oggetto AdvancedPhotoCapture chiamando FinishAsync e impostando la variabile membro su Null.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Acquisizione di foto in condizioni di scarsa illuminazione

A partire da Windows 10, versione 1607, AdvancedPhotoCapture può essere usato per acquisire foto usando un algoritmo predefinito che migliora la qualità delle foto acquisite in impostazioni di scarsa illuminazione. Quando si usa la funzionalità a bassa illuminazione della classe AdvancedPhotoCapture, il sistema valuterà la scena corrente e, se necessario, applicherà un algoritmo per compensare le condizioni di scarsa illuminazione. Se il sistema determina che l'algoritmo non è necessario, viene invece eseguita una normale acquisizione.

Prime di usare l'acquisizione di foto a bassa illuminazione, determinare se il dispositivo in cui è attualmente in esecuzione l'app supporta la tecnica recuperando il VideoDeviceController dell'oggetto dell'oggetto MediaCapture e quindi ottenendo la proprietà AdvancedPhotoControl. Controllare la raccolta SupportedModes del controller del dispositivo video per verificare se include AdvancedPhotoMode.LowLight. In caso affermativo, l'acquisizione a bassa illuminazione con AdvancedPhotoCapture è supportata.

bool _lowLightSupported;
_lowLightSupported = 
_mediaCapture.VideoDeviceController.AdvancedPhotoControl.SupportedModes.Contains(Windows.Media.Devices.AdvancedPhotoMode.LowLight);

Dichiarare quindi una variabile membro per archiviare l'oggetto AdvancedPhotoCapture.

private AdvancedPhotoCapture _advancedCapture;

Nell'app, dopo aver inizializzato l'oggetto MediaCapture, creare un oggetto AdvancedPhotoCaptureSettings e impostare la modalità a AdvancedPhotoMode.LowLight. Chiamare il metodo Configure dell'oggetto AdvancedPhotoControl passando nell'oggetto AdvancedPhotoCaptureSettings creato.

Chiamare MediaCapture dell'oggetto PrepareAdvancedPhotoCaptureAsync, passando in un ImageEncodingProperties che specifica il tipo di codifica da usare per l'acquisizione.

if (_lowLightSupported == false) return;

// Choose LowLight mode
var settings = new AdvancedPhotoCaptureSettings { Mode = AdvancedPhotoMode.LowLight };
_mediaCapture.VideoDeviceController.AdvancedPhotoControl.Configure(settings);

// Prepare for an advanced capture
_advancedCapture = 
    await _mediaCapture.PrepareAdvancedPhotoCaptureAsync(ImageEncodingProperties.CreateUncompressed(MediaPixelFormat.Nv12));

Per acquisire una foto, chiamare CaptureAsync.

AdvancedCapturedPhoto advancedCapturedPhoto = await _advancedCapture.CaptureAsync();
var photoOrientation = ConvertOrientationToPhotoOrientation(GetCameraOrientation());
var fileName = String.Format("SimplePhoto_{0}_LowLight.jpg", DateTime.Now.ToString("HHmmss"));
await SaveCapturedFrameAsync(advancedCapturedPhoto.Frame, fileName, photoOrientation);

Come nell'esempio HDR precedente, questo esempio usa una classe helper denominata Camera RotationHelper per determinare il valore di rotazione che deve essere codificato nell'immagine in modo che possa essere visualizzato correttamente da altre app e dispositivi. Questa classe è descritta ed elencata in modo completo nell'articolo Gestire l'orientamento del dispositivo con MediaCapture.

Il metodo SaveCapturedFrameAsync helper, che salva l'immagine su disco, viene illustrato più avanti in questo articolo.

Si possono acquisire più foto a bassa illuminazione senza riconfigurare l'oggetto AdvancedPhotoCapture, ma al termine dell'acquisizione si deve chiamare FinishAsync per pulire l'oggetto e le risorse associate.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Utilizzo di oggetti AdvancedCapturedPhoto

AdvancedPhotoCapture.CaptureAsync restituisce un oggetto AdvancedCapturedPhoto che rappresenta la foto acquisita. Questo oggetto espone la proprietà Frame che restituisce un oggetto CapturedFrame che rappresenta l'immagine. L'evento OptionalReferencePhotoCaptured fornisce anche un oggetto CapturedFrame negli argomenti dell'evento. Dopo aver creato un oggetto di questo tipo, è possibile eseguire alcune operazioni, tra cui la creazione di un SoftwareBitmap o il salvataggio dell'immagine in un file.

Ottenere un SoftwareBitmap da un CapturedFrame

È semplice ottenere un SoftwareBitmap da un oggetto CapturedFrame semplicemente accedendo alla proprietà SoftwareBitmap dell'oggetto. Tuttavia, la maggior parte dei formati di codifica non supporta SoftwareBitmap con AdvancedPhotoCapture, quindi è consigliabile controllare e assicurarsi che la proprietà non sia null prima di usarla.

SoftwareBitmap bitmap;
if (advancedCapturedPhoto.Frame.SoftwareBitmap != null)
{
    bitmap = advancedCapturedPhoto.Frame.SoftwareBitmap;
}

Nella versione corrente, l'unico formato di codifica che supporta SoftwareBitmap per AdvancedPhotoCapture è NV12 non compresso. Pertanto, se vuoi usare questa funzionalità, devi specificare la codifica quando si chiama PrepareAdvancedPhotoCaptureAsync.

_advancedCapture =
    await _mediaCapture.PrepareAdvancedPhotoCaptureAsync(ImageEncodingProperties.CreateUncompressed(MediaPixelFormat.Nv12));

Naturalmente, è sempre possibile salvare l'immagine in un file e quindi caricare il file in un SoftwareBitmap in un passaggio separato. Per altre informazioni sull'uso di SoftwareBitmap, vedere Creare, modificare e salvare immagini bitmap.

Salvare un oggetto CapturedFrame in un file

La classe CapturedFrame implementa l'interfaccia IInputStream, in modo che possa essere usata come input per BitmapDecoder e quindi un BitmapEncoder può essere usato per scrivere i dati dell'immagine su disco.

Nell'esempio seguente viene creata una nuova cartella nella raccolta immagini dell'utente e viene creato un file all'interno di questa cartella. Tenere presente che l'app dovrà includere la funzionalità Raccolta immagini nel file manifesto dell'app per accedere a questa directory. Viene quindi aperto un flusso di file nel file specificato. Viene quindi chiamato BitmapDecoder.CreateAsync per creare il decodificatore da CapturedFrame. Quindi CreateForTranscodingAsync crea un codificatore dal flusso di file e dal decodificatore.

I passaggi successivi codificano l'orientamento della foto nel file di immagine usando BitmapProperties del codificatore. Per altre informazioni sulla gestione dell'orientamento durante l'acquisizione di immagini, vedere Gestire l'orientamento del dispositivo con MediaCapture.

Infine, l'immagine viene scritta nel file con una chiamata a FlushAsync.

private static async Task<StorageFile> SaveCapturedFrameAsync(CapturedFrame frame, string fileName, PhotoOrientation photoOrientation)
{
    var folder = await KnownFolders.PicturesLibrary.CreateFolderAsync("MyApp", CreationCollisionOption.OpenIfExists);
    var file = await folder.CreateFileAsync(fileName, CreationCollisionOption.GenerateUniqueName);

    using (var inputStream = frame)
    {
        using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
        {
            var decoder = await BitmapDecoder.CreateAsync(inputStream);
            var encoder = await BitmapEncoder.CreateForTranscodingAsync(fileStream, decoder);
            var properties = new BitmapPropertySet {
                { "System.Photo.Orientation", new BitmapTypedValue(photoOrientation, PropertyType.UInt16) } };
            await encoder.BitmapProperties.SetPropertiesAsync(properties);
            await encoder.FlushAsync();
        }
    }
    return file;
}