Compartir a través de


Alta gama dinámica (HDR) y captura de fotos de poca luz

En este artículo se muestra cómo usar la clase AdvancedPhotoCapture para capturar fotos de alto rango dinámico (HDR). Esta API también permite obtener un marco de referencia de la captura HDR antes de que se complete el procesamiento de la imagen final.

Otros artículos relacionados con la captura HDR incluyen:

Nota:

A partir de Windows 10, versión 1709, se admite la grabación de vídeo y el uso de AdvancedPhotoCapture simultáneamente. Esto no se admite en versiones anteriores. Este cambio significa que puede tener un LowLagMediaRecording preparado y AdvancedPhotoCapture al mismo tiempo. Puede iniciar o detener la grabación de vídeo entre llamadas a MediaCapture.PrepareAdvancedPhotoCaptureAsync y AdvancedPhotoCapture.FinishAsync. También puede llamar a AdvancedPhotoCapture.CaptureAsync mientras el vídeo está grabando. Sin embargo, algunos escenarios advancedPhotoCapture , como la captura de una foto HDR mientras graba vídeo provocarían que la captura HDR modificara algunos fotogramas de vídeo, lo que da lugar a una experiencia de usuario negativa. Por este motivo, la lista de modos devueltos por AdvancedPhotoControl.SupportedModes será diferente mientras el vídeo está grabando. Debe comprobar este valor inmediatamente después de iniciar o detener la grabación de vídeo para asegurarse de que el modo deseado se admite en el estado de grabación de vídeo actual.

Nota:

A partir de Windows 10, versión 1709, cuando AdvancedPhotoCapture está establecido en modo HDR, el valor de la propiedad FlashControl.Enabled se omite y el flash nunca se desencadena. Para otros modos de captura, si flashControl.Enabled, invalidará la configuración AdvancedPhotoCapture y hará que se capture una foto normal con flash. Si Auto se establece en true, AdvancedPhotoCapture puede usar o no flash, dependiendo del comportamiento predeterminado del controlador de cámara para las condiciones de la escena actual. En versiones anteriores, la configuración de flash AdvancedPhotoCapture siempre invalida la configuración flash FlashControl.Enabled .

Nota:

Este artículo se basa en los conceptos y el código analizados en Captura básica de fotos, audio y vídeo con MediaCapture, donde se describen los pasos para implementar la captura básica de fotos y vídeo. Se recomienda que te familiarices con el patrón de captura de multimedia básico de ese artículo antes de pasar a escenarios de captura más avanzados. El código que encontrarás en este artículo se ha agregado suponiendo que la aplicación ya tiene una instancia de MediaCapture inicializada correctamente.

Hay un ejemplo universal de Windows que muestra el uso de la clase AdvancedPhotoCapture que puedes usar para ver la API usada en contexto o como punto de partida para tu propia aplicación. Para obtener más información, consulte Ejemplo de captura avanzada de cámara.

Espacios de nombres avanzados de captura de fotos

En los ejemplos de código de este artículo se usan las API de los siguientes espacios de nombres, además de los espacios de nombres necesarios para la captura básica de medios.

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

Captura de fotos HDR

Determinar si se admite la captura de fotos HDR en el dispositivo actual

La técnica de captura HDR descrita en este artículo se realiza mediante el objeto AdvancedPhotoCapture. No todos los dispositivos admiten la captura HDR con AdvancedPhotoCapture. Determine si el dispositivo en el que se está ejecutando la aplicación admite actualmente la técnica obteniendo el objeto VideoDeviceController del objeto MediaCapture y, a continuación, obteniendo la propiedad AdvancedPhotoControl. Compruebe la colección SupportedModes del controlador de dispositivo de vídeo para ver si incluye AdvancedPhotoMode.Hdr. Si es así, se admite la captura HDR mediante AdvancedPhotoCapture.

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

Configurar y preparar el objeto AdvancedPhotoCapture

Dado que tendrá que acceder a la instancia de AdvancedPhotoCapture desde varios lugares dentro del código, debe declarar una variable miembro para contener el objeto.

private AdvancedPhotoCapture _advancedCapture;

En la aplicación, después de inicializar el objeto MediaCapture, cree un objeto AdvancedPhotoCaptureSettings y establezca el modo en AdvancedPhotoMode.Hdr. Llame al método Configure del objeto AdvancedPhotoControl y pase el objeto AdvancedPhotoCaptureSettings que creó.

Llame al objeto PrepareAdvancedPhotoCaptureAsync del objeto MediaCaptureAsync y pase un objeto ImageEncodingProperties que especifique el tipo de codificación que debe usar la captura. La clase ImageEncodingProperties proporciona métodos estáticos para crear las codificaciones de imagen compatibles con MediaCapture.

PrepareAdvancedPhotoCaptureAsync devuelve el objeto AdvancedPhotoCapture que usarás para iniciar la captura de fotos. Puede usar este objeto para registrar controladores para optionalReferencePhotoCaptured y AllPhotosCaptured, que se describen más adelante en este artículo.

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;

Capturar una foto HDR

Capture una foto HDR llamando al método CaptureAsync del objeto AdvancedPhotoCapture. Este método devuelve un objeto AdvancedCapturedPhoto que proporciona la foto capturada en su propiedad 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 mayoría de las aplicaciones de fotografía querrán codificar la rotación de una foto capturada en el archivo de imagen para que otras aplicaciones y dispositivos puedan mostrarla correctamente. En este ejemplo se muestra el uso de la clase auxiliar CameraRotationHelper para calcular la orientación adecuada para el archivo. Esta clase se describe y se muestra completa en el artículo Control de la orientación del dispositivo con MediaCapture.

El método auxiliar SaveCapturedFrameAsync , que guarda la imagen en el disco, se describe más adelante en este artículo.

Obtención del marco de referencia opcional

El proceso HDR captura varios fotogramas y, a continuación, los compone en una sola imagen después de que se hayan capturado todos los fotogramas. Puedes obtener acceso a un fotograma después de capturarlo, pero antes de que se complete todo el proceso HDR controlando el evento OptionalReferencePhotoCaptured. No necesitas hacer esto si solo estás interesado en el resultado final de la foto HDR.

Importante

OptionalReferencePhotoCaptured no se genera en dispositivos que admiten HDR de hardware y, por tanto, no generan fotogramas de referencia. La aplicación debe controlar el caso en el que no se genera este evento.

Dado que el marco de referencia llega fuera del contexto de la llamada a CaptureAsync, se proporciona un mecanismo para pasar información de contexto al controlador OptionalReferencePhotoCaptured . En primer lugar, debe llamar a un objeto que contendrá la información de contexto. El nombre y el contenido de este objeto le corresponde. En este ejemplo se define un objeto que tiene miembros para realizar un seguimiento del nombre de archivo y la orientación de la cámara de la captura.

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

Cree una nueva instancia del objeto de contexto, rellene sus miembros y, a continuación, pásela a la sobrecarga de CaptureAsync que acepta un objeto como parámetro.

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

En el controlador de eventos OptionalReferencePhotoCaptured, convierta la propiedad Context del objeto OptionalReferencePhotoCapturedEventArgs en la clase de objeto de contexto. En este ejemplo se modifica el nombre de archivo para distinguir la imagen de fotograma de referencia de la imagen HDR final y, a continuación, se llama al método auxiliar SaveCapturedFrameAsync para guardar la imagen.

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

Recibir una notificación cuando se han capturado todos los fotogramas

La captura de fotos HDR tiene dos pasos. En primer lugar, se capturan varios fotogramas y, a continuación, los fotogramas se procesan en la imagen HDR final. No puedes iniciar otra captura mientras se capturan los fotogramas HDR de origen, pero puedes iniciar una captura después de que se hayan capturado todos los fotogramas, pero antes de que se complete el posproceso HDR. El evento AllPhotosCaptured se genera cuando se completan las capturas HDR, lo que te permite saber que puedes iniciar otra captura. Un escenario típico es deshabilitar el botón de captura de la interfaz de usuario cuando comienza la captura HDR y, a continuación, volver a habilitarlo cuando se genera AllPhotosCaptured .

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

Limpieza del objeto AdvancedPhotoCapture

Cuando la aplicación haya terminado de capturar, antes de eliminar el objeto MediaCapture, debe apagar el objeto AdvancedPhotoCapture llamando a FinishAsync y estableciendo la variable miembro en null.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Captura de fotos de poca luz

A partir de Windows 10, versión 1607, AdvancedPhotoCapture se puede usar para capturar fotos mediante un algoritmo integrado que mejora la calidad de las fotos capturadas en configuraciones de poca luz. Cuando se usa la característica de poca luz de la clase AdvancedPhotoCapture , el sistema evaluará la escena actual y, si es necesario, aplicará un algoritmo para compensar las condiciones de poca luz. Si el sistema determina que el algoritmo no es necesario, se realiza una captura normal en su lugar.

Antes de usar la captura de fotos de poca luz, determine si el dispositivo en el que se ejecuta la aplicación admite actualmente la técnica obteniendo el VideoDeviceController del objeto MediaCapture y, a continuación, obteniendo la propiedad AdvancedPhotoControl. Compruebe la colección SupportedModes del controlador de dispositivo de vídeo para ver si incluye AdvancedPhotoMode.LowLight. Si es así, se admite la captura de poca luz mediante AdvancedPhotoCapture .

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

A continuación, declare una variable miembro para almacenar el objeto AdvancedPhotoCapture .

private AdvancedPhotoCapture _advancedCapture;

En la aplicación, después de inicializar el objeto MediaCapture, cree un objeto AdvancedPhotoCaptureSettings y establezca el modo en AdvancedPhotoMode.LowLight. Llame al método Configure del objeto AdvancedPhotoControl y pase el objeto AdvancedPhotoCaptureSettings que creó.

Llame al objeto PrepareAdvancedPhotoCaptureAsync del objeto MediaCaptureAsync y pase un objeto ImageEncodingProperties que especifique el tipo de codificación que debe usar la captura.

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

Para capturar una foto, llame a 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);

Al igual que en el ejemplo HDR anterior, en este ejemplo se usa una clase auxiliar denominada CameraRotationHelper para determinar el valor de rotación que se debe codificar en la imagen para que otras aplicaciones y dispositivos puedan mostrarlos correctamente. Esta clase se describe y se muestra completa en el artículo Control de la orientación del dispositivo con MediaCapture.

El método auxiliar SaveCapturedFrameAsync , que guarda la imagen en el disco, se describe más adelante en este artículo.

Puede capturar varias fotos de poca luz sin volver a configurar el objeto AdvancedPhotoCapture, pero cuando haya terminado de capturar, debe llamar a FinishAsync para limpiar el objeto y los recursos asociados.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Trabajar con objetos AdvancedCapturedPhoto

AdvancedPhotoCapture.CaptureAsync devuelve un objeto AdvancedCapturedPhoto que representa la foto capturada. Este objeto expone la propiedad Frame que devuelve un objeto CapturedFrame que representa la imagen. El evento OptionalReferencePhotoCaptured también proporciona un objeto CapturedFrame en sus argumentos de evento. Después de obtener un objeto de este tipo, hay una serie de cosas que puede hacer con él, incluida la creación de un SoftwareBitmap o el guardado de la imagen en un archivo.

Obtener un objeto SoftwareBitmap de un objeto CapturedFrame

Es trivial obtener un Objeto SoftwareBitmap de un objeto CapturedFrame simplemente accediendo a la propiedad SoftwareBitmap del objeto. Sin embargo, la mayoría de los formatos de codificación no admiten SoftwareBitmap con AdvancedPhotoCapture, por lo que debe comprobar y asegurarse de que la propiedad no es nula antes de usarla.

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

En la versión actual, el único formato de codificación que admite SoftwareBitmap para AdvancedPhotoCapture es NV12 sin comprimir. Por lo tanto, si desea usar esta característica, debe especificar esa codificación al llamar a PrepareAdvancedPhotoCaptureAsync.

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

Por supuesto, siempre puede guardar la imagen en un archivo y, a continuación, cargar el archivo en un SoftwareBitmap en un paso independiente. Para obtener más información sobre cómo trabajar con SoftwareBitmap, vea Crear, editar y guardar imágenes de mapa de bits.

Guardar un objeto CapturedFrame en un archivo

La clase CapturedFrame implementa la interfaz IInputStream, por lo que se puede usar como entrada en un bitmapDecoder y, a continuación, se puede usar un bitmapEncoder para escribir los datos de imagen en el disco.

En el ejemplo siguiente, se crea una nueva carpeta de la biblioteca de imágenes del usuario y se crea un archivo dentro de esta carpeta. Tenga en cuenta que la aplicación tendrá que incluir la funcionalidad Biblioteca de imágenes en el archivo de manifiesto de la aplicación para acceder a este directorio. A continuación, se abre una secuencia de archivos en el archivo especificado. A continuación, se llama a BitmapDecoder.CreateAsync para crear el descodificador a partir de CapturedFrame. A continuación , CreateForTranscodingAsync crea un codificador a partir de la secuencia de archivos y el descodificador.

Los pasos siguientes codifican la orientación de la foto en el archivo de imagen mediante bitmapProperties del codificador. Para obtener más información sobre cómo controlar la orientación al capturar imágenes, consulta Controlar la orientación del dispositivo con MediaCapture.

Por último, la imagen se escribe en el archivo con una llamada 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;
}