Haute plage dynamique (HDR) et capture photo basse lumière

Cet article vous montre comment utiliser la classe AdvancedPhotoCapture pour capturer des photos de plage dynamique élevée (HDR). Cette API vous permet également d’obtenir un cadre de référence à partir de la capture HDR avant la fin du traitement de l’image finale.

Voici d’autres articles liés à la capture HDR :

Remarque

À compter de Windows 10, version 1709, l’enregistrement vidéo et l’utilisation simultanée d’AdvancedPhotoCapture sont pris en charge. Cela n’est pas pris en charge dans les versions précédentes. Cette modification signifie que vous pouvez avoir un LowLagMediaRecording préparé et AdvancedPhotoCapture en même temps. Vous pouvez démarrer ou arrêter l’enregistrement vidéo entre les appels à MediaCapture.PrepareAdvancedPhotoCaptureAsync et AdvancedPhotoCapture.FinishAsync. Vous pouvez également appeler AdvancedPhotoCapture.CaptureAsync pendant l’enregistrement de la vidéo. Toutefois, certains scénarios AdvancedPhotoCapture , comme la capture d’une photo HDR lors de l’enregistrement de la vidéo, entraînent la modification de certaines images vidéo par la capture HDR, ce qui entraîne une expérience utilisateur négative. Pour cette raison, la liste des modes retournés par AdvancedPhotoControl.SupportedModes sera différente pendant l’enregistrement de la vidéo. Vous devez vérifier cette valeur immédiatement après le démarrage ou l’arrêt de l’enregistrement vidéo pour vous assurer que le mode souhaité est pris en charge dans l’état actuel de l’enregistrement vidéo.

Remarque

À compter de Windows 10, version 1709, lorsque AdvancedPhotoCapture est défini sur le mode HDR, le paramètre de la propriété FlashControl.Enabled est ignoré et le flash n’est jamais déclenché. Pour les autres modes de capture, si FlashControl.Enabled, il remplace les paramètres AdvancedPhotoCapture et provoque la capture d’une photo normale avec flash. Si l’option Auto est définie sur true, AdvancedPhotoCapture peut ou ne pas utiliser le flash, selon le comportement par défaut du pilote de caméra pour les conditions de la scène actuelle. Dans les versions précédentes, le paramètre flash AdvancedPhotoCapture remplace toujours le paramètre FlashControl.Enabled .

Remarque

Cet article repose sur les concepts et le code décrits dans Capture photo, vidéo et audio de base à l’aide de MediaCapture, qui décrit comment implémenter la capture photo et vidéo de base. Nous vous recommandons de vous familiariser avec le modèle de capture simple de contenu multimédia de cet article avant d’adopter des scénarios de capture plus avancés. Le code de cet article suppose que votre application a déjà une instance de MediaCapture qui a été correctement initialisée.

Il existe un exemple Windows universel illustrant l’utilisation de la classe AdvancedPhotoCapture que vous pouvez utiliser pour voir l’API utilisée dans le contexte ou comme point de départ pour votre propre application. Pour plus d’informations, consultez l’exemple Caméra Advanced Capture.

Espaces de noms de capture de photos avancés

Les exemples de code de cet article utilisent des API dans les espaces de noms suivants en plus des espaces de noms requis pour la capture multimédia de base.

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

Capture de photos HDR

Déterminer si la capture de photos HDR est prise en charge sur l’appareil actuel

La technique de capture HDR décrite dans cet article est effectuée à l’aide de l’objet AdvancedPhotoCapture . Tous les appareils ne prennent pas en charge la capture HDR avec AdvancedPhotoCapture. Déterminez si l’appareil sur lequel votre application est en cours d’exécution prend en charge la technique en obtenant le VideoDeviceController de l’objet MediaCapture, puis en obtenant la propriété AdvancedPhotoControl. Vérifiez la collection SupportedModes du contrôleur d’appareil vidéo pour voir s’il inclut AdvancedPhotoMode.Hdr. Si c’est le cas, la capture HDR utilisant AdvancedPhotoCapture est prise en charge.

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

Configurer et préparer l’objet AdvancedPhotoCapture

Étant donné que vous devez accéder à l’instance AdvancedPhotoCapture à partir de plusieurs emplacements dans votre code, vous devez déclarer une variable membre pour contenir l’objet.

private AdvancedPhotoCapture _advancedCapture;

Dans votre application, après avoir initialisé l’objet MediaCapture, créez un objet AdvancedPhotoCaptureSettings et définissez le mode sur AdvancedPhotoMode.Hdr. Appelez la méthode Configure de l’objet AdvancedPhotoControl, en passant l’objet AdvancedPhotoCaptureSettings que vous avez créé.

Appelez prepareAdvancedPhotoCaptureAsync de l’objet MediaCapture, en passant un objet ImageEncodingProperties spécifiant le type d’encodage que la capture doit utiliser. La classe ImageEncodingProperties fournit des méthodes statiques pour créer les encodages d’image pris en charge par MediaCapture.

PrepareAdvancedPhotoCaptureAsync retourne l’objet AdvancedPhotoCapture que vous utiliserez pour lancer la capture de photos. Vous pouvez utiliser cet objet pour inscrire des gestionnaires pour optionalReferencePhotoCaptured et AllPhotosCaptured, qui sont abordés plus loin dans cet article.

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;

Capturer une photo HDR

Capturez une photo HDR en appelant la méthode CaptureAsync de l’objet AdvancedPhotoCapture. Cette méthode retourne un objet AdvancedCapturedPhoto qui fournit la photo capturée dans sa propriété 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 plupart des applications de photographie souhaitent encoder la rotation d’une photo capturée dans le fichier image afin qu’elle puisse être affichée correctement par d’autres applications et appareils. Cet exemple montre l’utilisation de la classe d’assistance CameraRotationHelper pour calculer l’orientation appropriée pour le fichier. Cette classe est décrite et répertoriée dans l’article Gérer l’orientation de l’appareil avec MediaCapture.

La méthode d’assistance SaveCapturedFrameAsync , qui enregistre l’image sur le disque, est décrite plus loin dans cet article.

Obtenir une trame de référence facultative

Le processus HDR capture plusieurs images, puis les composite en une seule image une fois que tous les images ont été capturées. Vous pouvez accéder à une image après sa capture, mais avant la fin du processus HDR entier en gérant l’événement OptionalReferencePhotoCaptured . Vous n’avez pas besoin de le faire si vous n’êtes intéressé que par le résultat final de la photo HDR.

Important

OptionalReferencePhotoCaptured n’est pas déclenché sur les appareils qui prennent en charge le matériel HDR et ne génèrent donc pas de trames de référence. Votre application doit gérer le cas où cet événement n’est pas déclenché.

Étant donné que le cadre de référence arrive hors contexte de l’appel à CaptureAsync, un mécanisme est fourni pour transmettre des informations de contexte au gestionnaire OptionalReferencePhotoCaptured . Tout d’abord, vous devez appeler un objet qui contiendra vos informations de contexte. Le nom et le contenu de cet objet sont à vous. Cet exemple définit un objet qui a des membres pour suivre le nom de fichier et l’orientation de la caméra de la capture.

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

Créez une instance de votre objet de contexte, remplissez ses membres, puis transmettez-le à la surcharge de CaptureAsync qui accepte un objet en tant que paramètre.

// 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);

Dans le gestionnaire d’événements OptionalReferencePhotoCaptured, castez la propriété Context de l’objet OptionalReferencePhotoCapturedEventArgs vers votre classe d’objet de contexte. Cet exemple modifie le nom de fichier pour distinguer l’image de cadre de référence de l’image HDR finale, puis appelle la méthode d’assistance SaveCapturedFrameAsync pour enregistrer l’image.

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);
    }
}

Recevoir une notification lorsque toutes les images ont été capturées

La capture de photos HDR comporte deux étapes. Tout d’abord, plusieurs images sont capturées, puis les images sont traitées dans l’image HDR finale. Vous ne pouvez pas lancer une autre capture pendant que les images HDR sources sont toujours capturées, mais vous pouvez lancer une capture une fois que toutes les images ont été capturées, mais avant la fin du post-traitement HDR. L’événement AllPhotosCaptured est déclenché lorsque les captures HDR sont terminées, vous informant que vous pouvez lancer une autre capture. Un scénario classique consiste à désactiver le bouton de capture de votre interface utilisateur lorsque la capture HDR commence, puis à la réactiver lorsque AllPhotosCaptured est déclenché.

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

Nettoyer l’objet AdvancedPhotoCapture

Une fois votre application capturée, avant de supprimer l’objet MediaCapture, vous devez arrêter l’objet AdvancedPhotoCapture en appelant FinishAsync et en définissant votre variable membre sur Null.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Capture de photo basse lumière

À compter de Windows 10, version 1607, AdvancedPhotoCapture peut être utilisé pour capturer des photos à l’aide d’un algorithme intégré qui améliore la qualité des photos capturées dans des paramètres à faible éclairage. Lorsque vous utilisez la fonctionnalité basse lumière de la classe AdvancedPhotoCapture , le système évalue la scène actuelle et, si nécessaire, applique un algorithme pour compenser les conditions de faible luminosité. Si le système détermine que l’algorithme n’est pas nécessaire, une capture régulière est effectuée à la place.

Avant d’utiliser la capture de photos basse lumière, déterminez si l’appareil sur lequel votre application est en cours d’exécution prend en charge la technique en obtenant la propriété VideoDeviceController de l’objet MediaCapture, puis en obtenant la propriété AdvancedPhotoControl. Vérifiez la collection SupportedModes du contrôleur d’appareil vidéo pour voir s’il inclut AdvancedPhotoMode.LowLight. Si c’est le cas, la capture basse lumière à l’aide d’AdvancedPhotoCapture est prise en charge.

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

Ensuite, déclarez une variable membre pour stocker l’objet AdvancedPhotoCapture .

private AdvancedPhotoCapture _advancedCapture;

Dans votre application, après avoir initialisé l’objet MediaCapture, créez un objet AdvancedPhotoCaptureSettings et définissez le mode sur AdvancedPhotoMode.LowLight. Appelez la méthode Configure de l’objet AdvancedPhotoControl, en passant l’objet AdvancedPhotoCaptureSettings que vous avez créé.

Appelez prepareAdvancedPhotoCaptureAsync de l’objet MediaCapture, en passant un objet ImageEncodingProperties spécifiant le type d’encodage que la capture doit utiliser.

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));

Pour capturer une photo, appelez 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);

Comme l’exemple HDR ci-dessus, cet exemple utilise une classe d’assistance appelée CameraRotationHelper pour déterminer la valeur de rotation qui doit être encodée dans l’image afin qu’elle puisse être affichée correctement par d’autres applications et appareils. Cette classe est décrite et répertoriée dans l’article Gérer l’orientation de l’appareil avec MediaCapture.

La méthode d’assistance SaveCapturedFrameAsync , qui enregistre l’image sur le disque, est décrite plus loin dans cet article.

Vous pouvez capturer plusieurs photos à faible lumière sans reconfigurer l’objet AdvancedPhotoCapture, mais lorsque vous avez terminé la capture, vous devez appeler FinishAsync pour nettoyer l’objet et les ressources associées.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Utilisation d’objets AdvancedCapturedPhoto

AdvancedPhotoCapture.CaptureAsync renvoie un objet AdvancedCapturedPhoto représentant la photo capturée. Cet objet expose la propriété Frame qui retourne un objet CapturedFrame représentant l’image. L’événement OptionalReferencePhotoCaptured fournit également un objet CapturedFrame dans ses arguments d’événement. Une fois que vous avez obtenu un objet de ce type, il existe plusieurs choses que vous pouvez faire avec elle, notamment la création d’un SoftwareBitmap ou l’enregistrement de l’image dans un fichier.

Obtenir un SoftwareBitmap à partir d’un CapturedFrame

Il est trivial d’obtenir un SoftwareBitmap à partir d’un objet CapturedFrame en accédant simplement à la propriété SoftwareBitmap de l’objet. Toutefois, la plupart des formats d’encodage ne prennent pas en charge SoftwareBitmap avec AdvancedPhotoCapture. Vous devez donc vérifier et vérifier que la propriété n’est pas null avant de l’utiliser.

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

Dans la version actuelle, le seul format d’encodage prenant en charge SoftwareBitmap pour AdvancedPhotoCapture n’est pas compressé NV12. Par conséquent, si vous souhaitez utiliser cette fonctionnalité, vous devez spécifier cet encodage lorsque vous appelez PrepareAdvancedPhotoCaptureAsync.

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

Bien sûr, vous pouvez toujours enregistrer l’image dans un fichier, puis charger le fichier dans un SoftwareBitmap dans une étape distincte. Pour plus d’informations sur l’utilisation de SoftwareBitmap, consultez Créer, modifier et enregistrer des images bitmap.

Enregistrer un CapturedFrame dans un fichier

La classe CapturedFrame implémente l’interface IInputStream, afin qu’elle puisse être utilisée comme entrée dans un BitmapDecoder, puis une bitmapEncoder peut être utilisée pour écrire les données d’image sur le disque.

Dans l’exemple suivant, un nouveau dossier dans la bibliothèque d’images de l’utilisateur est créé et un fichier est créé dans ce dossier. Notez que votre application doit inclure la fonctionnalité Bibliothèque d’images dans le fichier manifeste de votre application afin d’accéder à ce répertoire. Un flux de fichiers est ensuite ouvert au fichier spécifié. Ensuite, bitmapDecoder.CreateAsync est appelé pour créer le décodeur à partir de CapturedFrame. CreateForTranscodingAsync crée ensuite un encodeur à partir du flux de fichiers et du décodeur.

Les étapes suivantes encoder l’orientation de la photo dans le fichier image à l’aide de BitmapProperties de l’encodeur. Pour plus d’informations sur la gestion de l’orientation lors de la capture d’images, consultez Gérer l’orientation de l’appareil avec MediaCapture.

Enfin, l’image est écrite dans le fichier avec un appel à 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;
}