Captura de foto de pouca luz e HDR (High Dynamic Range)

Este artigo mostra como usar a classe AdvancedPhotoCapture para capturar fotos HDR (High Dynamic Range). Essa API também permite que você obtenha um quadro de referência da captura HDR antes que o processamento da imagem final seja concluído.

Outros artigos relacionados à captura HDR incluem:

Observação

A partir do Windows 10, versão 1709, há suporte para a gravação de vídeo e o uso de AdvancedPhotoCapture simultaneamente. Isso não é possível em versões anteriores. Essa alteração significa que você pode ter uma LowLagMediaRecording preparada e uma AdvancedPhotoCapture ao mesmo tempo. Você pode iniciar ou parar a gravação de vídeo entre chamadas para MediaCapture.PrepareAdvancedPhotoCaptureAsync e AdvancedPhotoCapture.FinishAsync. Você também pode chamar AdvancedPhotoCapture.CaptureAsync enquanto o vídeo é gravado. No entanto, alguns cenários de AdvancedPhotoCapture, como capturar uma foto HDR, enquanto a gravação de vídeo resulta na alteração de alguns quadros de vídeo pela captura de HDR, resultando em uma experiência de usuário negativa. Por esse motivo, a lista de modos retornada pelo AdvancedPhotoControl.SupportedModes será diferente enquanto o vídeo é gravado. Você deve verificar esse valor imediatamente depois de iniciar ou parar a gravação de vídeo para garantir que o modo desejado seja suportado no estado de gravação de vídeo atual.

Observação

A partir do Windows 10, versão 1709, quando a AdvancedPhotoCapture é definida como o modo HDR, a configuração da propriedade FlashControl.Enabled é ignorada e o flash nunca é acionado. Para outros modos de captura, se FlashControl.Enabled, isso substitui as configurações de AdvancedPhotoCapture e resulta na captura de uma foto normal com flash. Se Auto estiver configurado como true, a AdvancedPhotoCapture pode ou não usar o flash, dependendo do comportamento padrão do driver da câmera para as condições na cena atual. Em versões anteriores, a configuração de flash de AdvancedPhotoCapture sempre substitui a configuração de FlashControl.Enabled.

Observação

Este artigo se baseia em conceitos e códigos discutidos em Captura básica de fotos, áudio e vídeo com o MediaCapture, que descreve as etapas para implementar uma captura básica de fotos e vídeos. Recomendamos que você se familiarize com o padrão de captura de mídia básica neste artigo antes de passar para cenários de captura mais avançados. O código neste artigo presume que seu aplicativo já tenha uma instância de MediaCapture inicializada corretamente.

Há um exemplo Universal do Windows que demonstra o uso da classe AdvancedPhotoCapture que você pode usar para ver a API usada no contexto ou como ponto de partida para seu próprio aplicativo. Para obter mais informações, consulte o exemplo de captura avançada com câmera.

Namespaces de captura de foto avançada

Os exemplos de código neste artigo usam APIs nos namespaces a seguir, além dos namespaces necessários para captura de mídia básica.

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

Captura de foto HDR

Determinar se a captura de fotos HDR é aceita no dispositivo atual

A técnica de captura HDR descrita neste artigo é executada usando o objeto AdvancedPhotoCapture. Nem todos os dispositivos oferecem suporte à AdvancedPhotoCapture. Determine se o dispositivo em que seu aplicativo está sendo executado oferece suporte à técnica obtendo o VideoDeviceController do objeto MediaCapture e depois obtendo a propriedade AdvancedPhotoControl. Verifique a coleção SupportedModes do controlador de dispositivo de vídeo para verificar se inclui AdvancedPhotoMode.Hdr. Caso inclua, a capture HDR usando AdvancedPhotoCapture é suportada.

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

Configurar e preparar o objeto AdvancedPhotoCapture

Como você precisará acessar a instância de AdvancedPhotoCapture em vários locais em seu código, será necessário declarar uma variável de membro para armazenar o objeto.

private AdvancedPhotoCapture _advancedCapture;

No aplicativo, depois de inicializar o objeto MediaCapture, crie um objeto AdvancedPhotoCaptureSettings e defina o modo como AdvancedPhotoMode.Hdr. Faça uma chamada com o objeto AdvancedPhotoControl usando o método Configure, passando o objeto AdvancedPhotoCaptureSettings criado.

Chame o PrepareAdvancedPhotoCaptureAsync do objeto MediaCapture passando um objeto ImageEncodingProperties especificando o tipo de codificação que a captura deve usar. A classe ImageEncodingProperties fornece métodos estáticos para criar as codificações de imagem que são suportadas pelo MediaCapture.

PrepareAdvancedPhotoCaptureAsync retorna o objeto AdvancedPhotoCapture que você usará para iniciar a captura de foto. Use esse objeto para registrar manipuladores para OptionalReferencePhotoCaptured e AllPhotosCaptured que são discutidos posteriormente neste artigo.

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 uma foto HDR

Capture uma foto HDR, chame o método CaptureAsync do objeto AdvancedPhotoCapture. Esse método retorna um objeto AdvancedCapturedPhoto que fornece a foto capturada em sua propriedade 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());
}

A maioria dos aplicativos de fotografia vai querer codificar a rotação da foto capturada no arquivo de imagem para que ela possa ser exibida corretamente por outros aplicativos e dispositivos. Este exemplo mostra o uso da classe auxiliar CameraRotationHelper para calcular a orientação adequada para o arquivo. Essa classe é descrita e listada na íntegra no artigo Tratar a orientação do dispositivo com MediaCapture.

O método auxiliar SaveCapturedFrameAsync, que salva a imagem em disco, será abordado posteriormente neste artigo.

Obter o quadro de referência opcional

O processo HDR captura vários quadros e, em seguida, compõe esses quadros em uma única imagem depois de todos os quadros serem capturados. Você pode acessar um quadro após sua captura, mas antes da conclusão do processo de HDR manipulando o evento OptionalReferencePhotoCaptured. Você não precisará fazer isso se só estiver interessado no resultado final de fotos HDR.

Importante

OptionalReferencePhotoCaptured não é gerado nos dispositivos com suporte para HDR de hardware e, portanto, não gera quadros de referência. Seu aplicativo deve manipular o caso em que esse evento não for gerado.

Como o quadro de referência é decorrente do contexto da chamada para CaptureAsync, um mecanismo é fornecido para passar informações de contexto ao manipulador OptionalReferencePhotoCaptured. Primeiro você deve chamar um objeto que contenha as informações de contexto. O nome e o conteúdo desse objeto dependem de você. Este exemplo define um objeto que tem membros para controlar o nome de arquivo e a orientação de câmera da captura.

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

Crie uma nova instância do seu objeto de contexto, popule seus membros e passe-os para a sobrecarga de CaptureAsync que aceita um 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);

No manipulador de eventos OptionalReferencePhotoCaptured, converta a propriedade Context do objeto OptionalReferencePhotoCapturedEventArgs na sua classe de objeto de contexto. Este exemplo modifica o nome do arquivo para distinguir a imagem do quadro de referência da imagem HDR final e depois chama o método auxiliar SaveCapturedFrameAsync para salvar a imagem.

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

Receber uma notificação quando todos os quadros foram capturados

A captura de fotos HDR tem duas etapas. Primeiro, vários quadros são capturados e, em seguida, os quadros são processados na imagem HDR final. Você não pode iniciar outra captura quando os quadros HDR de origem ainda estiverem sendo capturados, mas pode iniciar uma captura depois que todos os quadros forem capturados, mas antes da conclusão do pós-processamento de HDR. O evento AllPhotosCaptured é gerado quando as capturas HDR são concluídas, permitindo que você saiba que pode iniciar outra captura. Um cenário típico é desabilitar o botão de captura da sua interface do usuário quando a captura HDR começar e habilitá-la novamente quando AllPhotosCaptured for gerado.

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

Limpar o objeto AdvancedPhotoCapture

Quando seu aplicativo terminar de capturar, antes do descarte do objeto MediaCapture, você deve desligar o objeto AdvancedPhotoCapture chamando FinishAsync e definindo sua variável de membro como nula.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Captura de fotos com pouca luz

A partir do Windows 10, versão 1607, o AdvancedPhotoCapture pode ser usado para capturar fotos usando um algoritmo interno que melhora a qualidade das fotos capturadas nas configurações de pouca luz. Quando você usar o recurso de pouca luz da classe AdvancedPhotoCapture, o sistema avaliará a cena atual e, se necessário, aplicará um algoritmo para compensar as condições de pouca luz. Se o sistema determinar que o algoritmo não é necessário, uma captura normal será realizada.

Antes de usar a captura com pouca luz, determine se o dispositivo em que seu aplicativo está sendo executado oferece suporte à técnica obtendo o VideoDeviceController do objeto MediaCapture e depois obtendo a propriedade AdvancedPhotoControl. Verifique a coleção de SupportedModes do controlador do dispositivo de vídeo para ver se ele inclui AdvancedPhotoMode.LowLight. Se incluir, há suporte para a captura com pouca luz usando AdvancedPhotoCapture.

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

Em seguida, declare uma variável membro para armazenar o objeto AdvancedPhotoCapture.

private AdvancedPhotoCapture _advancedCapture;

Em seu aplicativo, após a inicialização do objeto MediaCapture, crie um objeto AdvancedPhotoCaptureSettings e defina o modo como AdvancedPhotoMode.LowLight. Chame o método Configure do objeto AdvancedPhotoControl passando o objeto AdvancedPhotoCaptureSettings que você criou.

Chame o PrepareAdvancedPhotoCaptureAsync do objeto MediaCapture passando um objeto ImageEncodingProperties especificando o tipo de codificação que a captura deve usar.

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 uma foto, chame 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);

Como o exemplo de HDR acima, este exemplo usa uma classe auxiliar chamada CameraRotationHelper para determinar o valor de rotação que deve ser codificado na imagem para que ela possa ser exibida corretamente por outros aplicativos e dispositivos. Essa classe é descrita e listada na íntegra no artigo Tratar a orientação do dispositivo com MediaCapture.

O método auxiliar SaveCapturedFrameAsync, que salva a imagem em disco, será abordado posteriormente neste artigo.

Você pode capturar várias fotos com pouca luz sem precisar reconfigurar o objeto AdvancedPhotoCapture, mas quando terminar a captura, deverá chamar FinishAsync para limpar o objeto e os recursos associados.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Trabalhando com objetos AdvancedCapturedPhoto

AdvancedPhotoCapture.CaptureAsync retorna um objeto AdvancedCapturedPhoto que representa a foto capturada. Esse objeto expõe a propriedade Frame que retorna um objeto CapturedFrame que representa a imagem. O evento OptionalReferencePhotoCaptured também fornece um objeto CapturedFrame em seus argumentos de evento. Depois de obter um objeto desse tipo, há uma série de coisas que você poderá fazer com ele, inclusive criar um SoftwareBitmap ou salvar a imagem em um arquivo.

Obter um SoftwareBitmap de um CapturedFrame

É muito simples obter um SoftwareBitmap de um objeto CapturedFrame simplesmente acessando a propriedade SoftwareBitmap do objeto. No entanto, a maioria dos formatos de codificação não dá suporte para SoftwareBitmap com AdvancedPhotoCapture. Portanto, você deve verificar se a propriedade não é nula antes de usá-la.

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

Na versão atual, o único formato de codificação que dá suporte a SoftwareBitmap para AdvancedPhotoCapture é o NV12 descompactado. Portanto, se você quiser usar esse recurso, especifique essa codificação quando chamar PrepareAdvancedPhotoCaptureAsync.

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

É claro que você pode sempre salvar a imagem em um arquivo e, em seguida, carregar o arquivo em um SoftwareBitmap em uma etapa separada. Para obter mais informações sobre como trabalhar com SoftwareBitmap, consulte Criar, editar e salvar imagens de bitmap.

Salvar um CapturedFrame em um arquivo

A classe CapturedFrame implementa a interface IInputStream, para que possa ser usada como entrada para um BitmapDecoder e depois um BitmapEncoder pode ser usado para gravar os dados da imagem em disco.

No exemplo a seguir, uma nova pasta na biblioteca de imagens do usuário é criada e um arquivo é criado dentro dessa pasta. Observe que seu aplicativo precisará incluir a funcionalidade Biblioteca de Imagens no arquivo de manifesto do aplicativo para acessar esse diretório. Depois, um fluxo do arquivo é aberto no arquivo especificado. Em seguida, o BitmapDecoder.CreateAsync é chamado para criar o decodificador do CapturedFrame. Em seguida, CreateForTranscodingAsync cria um codificador a partir do fluxo do arquivo e o decodificador.

As próximas etapas codificam a orientação da foto no arquivo de imagem usando os valores de BitmapProperties do codificador. Para obter mais informações sobre como lidar com a orientação ao capturar imagens, consulte Tratar a orientação do dispositivo com MediaCapture.

Por fim, a imagem é gravada em um arquivo com uma chamada para 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;
}