Фотозахват с расширенным динамическим диапазоном (HDR) и в условиях низкой освещенности

В этой статье показано, как использовать класс AdvancedPhotoCapture для фотозахвата с расширенным динамическим диапазоном (HDR). Кроме того, этот API дает возможность получить опорный кадр из захвата HDR, прежде чем завершить обработку окончательного изображения.

Другие статьи, связанные с захватом HDR:

Примечание

Начиная с Windows 10 версии 1709 поддерживается одновременная запись видео и использование AdvancedPhotoCapture. Такая функциональность не поддерживается в предыдущих версиях. Это изменение означает, что вы можете одновременно подготовить LowLagMediaRecording и AdvancedPhotoCapture. Можно запустить или остановить запись видео между вызовами методов MediaCapture.PrepareAdvancedPhotoCaptureAsync и AdvancedPhotoCapture.FinishAsync. Вы также можете вызвать AdvancedPhotoCapture.CaptureAsync во время записи видео. Тем не менее при некоторых сценариях использования AdvancedPhotoCapture, например, при записи HDR-фотографий во время ведения видеозаписи, некоторые видеокадры могут быть изменены из-за записи в режиме HDR, а может быть отрицательно воспринято пользователями. По этой причине во время записи видео AdvancedPhotoControl.SupportedModes возвращает другой список режимов. Это значение необходимо проверять сразу же после запуска или остановки видеозаписи, чтобы убедиться, что нужный режим поддерживается на текущем этапе видеозаписи.

Примечание

Начиная с Windows 10 версии 1709, при включении HDR-режима для AdvancedPhotoCapture значение свойства FlashControl.Enabled игнорируется и вспышка никогда не активируется. В других режимах съемки установленное свойство FlashControl.Enabled переопределяет параметры AdvancedPhotoCapture, поэтому обычные фотографии снимаются со вспышкой. Если в параметре Auto задано значение true, то AdvancedPhotoCapture может использовать или не использовать вспышку, в зависимости от стандартного поведения камеры в условиях текущей сцены фотосъемки. В предыдущих выпусках параметр вспышки AdvancedPhotoCapture всегда переопределял параметр FlashControl.Enabled.

Примечание

В этой статье используются понятия и код из статьи Основные принципы фото-, аудио- и видеозахвата с помощью MediaCapture, в которой описаны этапы реализации основных принципов фото- и видеозахвата. Мы рекомендуем ознакомиться с базовым шаблоном захвата мультимедиа в этой статье, прежде чем перейти к более сложным сценариям захвата. Код в этой статье подразумевает, что ваше приложение уже содержит экземпляр MediaCapture, инициализированный надлежащим образом.

Существует универсальной пример Windows, демонстрирующий функциональность класса AdvancedPhotoCapture, с помощью которого можно увидеть API, используемые в контексте. Кроме того, этот класс может служить начальной точкой создания собственного приложения. Дополнительные сведения см. в разделе Пример расширенной съемки на камеру.

Пространства имен расширенного фотозахвата

В примерах кода в этой статье помимо пространств имен, необходимых для основного захвата мультимедиа, используются API в следующих пространствах имен.

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

Фотозахват HDR

Определение возможности фотозахвата HDR на текущем устройстве

Описанный в этой статье способ захвата HDR выполняется с помощью объекта AdvancedPhotoCapture. Не все устройства поддерживают фотозахват HDR с помощью AdvancedPhotoCapture. Определите, поддерживает ли этот метод устройство, на котором в настоящее время запущено приложение. Для этого необходимо получить свойство VideoDeviceController объекта MediaCapture, а затем получить свойство AdvancedPhotoControl. Проверьте коллекцию SupportedModes контроллера видеоустройства , чтобы узнать, входит ли в нее режим AdvancedPhotoMode.Hdr. Если входит, то в AdvancedPhotoCapture поддерживается режим записи HDR.

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

Настройка и подготовка объекта AdvancedPhotoCapture

Поскольку необходим доступ к экземпляру AdvancedPhotoCapture из нескольких расположений в пределах вашего кода, нужно объявить переменную-член для удержания объекта.

private AdvancedPhotoCapture _advancedCapture;

В вашем приложении после инициализации объекта MediaCapture создайте объект AdvancedPhotoCaptureSettings и установите режим AdvancedPhotoMode.Hdr. Вызовите метод Configure объекта AdvancedPhotoControl, передав в него созданный объект AdvancedPhotoCaptureSettings.

Вызовите метод PrepareAdvancedPhotoCaptureAsync объекта MediaCapture, передав объект ImageEncodingProperties с указанием типа кодирования, которое должно использоваться в захвате. Класс ImageEncodingProperties предоставляет статические методы для создания кодирования изображений, которые поддерживаются MediaCapture.

PrepareAdvancedPhotoCaptureAsync возвращает объект AdvancedPhotoCapture, который будет использоваться для инициализации фотозахвата. Вы можете использовать этот объект, чтобы зарегистрировать обработчики событий для OptionalReferencePhotoCaptured и AllPhotosCaptured, которые описываются далее в этой статье.

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;

Захват фотографий с технологией HDR

Чтобы захватить фотографию с расширенным динамическим диапазоном (HDR), вызовите метод CaptureAsync объекта AdvancedPhotoCapture. Этот метод возвращает объект AdvancedCapturedPhoto, предоставляющий созданную фотографию в свойстве 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());
}

Большинство приложений фотографии кодируют поворот сделанного фото в файле изображения, чтобы он правильно отображался в других приложениях и на других устройствах. В этом примере показано использование вспомогательного класса CameraRotationHelper для расчета правильной ориентации файла. Этот класс более подробно описан в статье Обработка ориентации устройства с помощью MediaCapture.

Вспомогательный метод SaveCapturedFrameAsync, который сохраняет изображение на диск, рассматривается далее в этой статье.

Получение дополнительного опорного кадра

При использовании технологии HDR захватывается несколько кадров. После захвата всех кадров из них составляется одно изображение. Вы можете получить доступ к кадру после его захвата, но до завершения обработки HDR, обработав событие OptionalReferencePhotoCaptured. Если вас интересует только конечный результат фотографии с расширенным динамическим диапазоном (HDR), этого делать не нужно.

Важно!

OptionalReferencePhotoCaptured не вызывается на устройствах, которые поддерживают HDR-оборудование, а потому не создают опорные кадры. В приложении должны предусматриваться случаи, при которых это событие не возникает.

Поскольку опорный кадр поступает из контекста вызова CaptureAsync, предусмотрен механизм для передачи контекстной информации обработчику OptionalReferencePhotoCaptured. Сначала нужно вызвать объект, который будет содержать контекстные данные. При этом имя и содержимое этого объекта зависит только от вас. В этом примере определяется объект, который содержит члены для отслеживания имени файла и ориентации камеры при захвате.

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

Создайте экземпляр объекта контекста, добавьте его члены, а затем передайте его в перегрузку CaptureAsync, где объект принимается в качестве параметра.

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

В обработчике событий OptionalReferencePhotoCaptured приведите свойство Context объекта OptionalReferencePhotoCapturedEventArgs к классу объектов контекста. В этом примере изменяется имя файла для разделения изображения опорного кадра и окончательного изображения с расширенным динамическим диапазоном (HDR), а затем вызывается вспомогательный метод SaveCapturedFrameAsync, чтобы сохранить изображение.

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

Получение уведомления после захвата всех кадров

Фотозахват с технологией HDR предполагает два шага. Сначала захватывается несколько кадров, а затем кадры обрабатываются и создается окончательное HDR-изображение. Инициировать другой захват во время захвата исходных кадров HDR нельзя. Однако это можно сделать после захвата всех кадров, но до завершающей обработки HDR. Событие AllPhotosCaptured возникает после завершения захвата HDR, уведомляя вас о том, что вы можете инициировать другой захват. Обычный сценарий — отключить кнопку захвата пользовательского интерфейса в начале захвата с HDR, а затем снова включить ее при вызове AllPhotosCaptured.

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

Удаление объекта AdvancedPhotoCapture

Когда приложение завершит захват, прежде чем ликвидировать объект MediaCapture, необходимо завершить работу объекта AdvancedPhotoCapture. Для этого вызовите FinishAsync и установите для переменной-члена значение Null.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Фотозахват при слабом освещении

Начиная c Windows 10 (версия 1607) класс AdvancedPhotoCapture можно использовать для съемки фото с применением встроенного алгоритма, который повышает качество фото, сделанных в условиях низкой освещенности. При использовании функции съемки при низком освещении, предоставляемой классом AdvancedPhotoCapture, система оценивает текущую сцену и при необходимости применяет алгоритм, компенсирующий условия низкой освещенности. Если система определяет, что этот алгоритм не нужен, выполняется обычный захват.

Прежде чем использовать фотозахват в условиях низкой освещенности, определите, поддерживает ли этот метод устройство, на котором в настоящее время запущено приложение. Для этого необходимо получить свойство VideoDeviceController объекта MediaCapture, а затем получить свойство AdvancedPhotoControl. Просмотрите коллекцию контроллера видеоустройств SupportedModes, чтобы узнать, включает ли она AdvancedPhotoMode.LowLight. Если да, то захват в условиях низкого освещения с использованием AdvancedPhotoCapture поддерживается.

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

Затем объявите переменную-член для хранения объекта AdvancedPhotoCapture.

private AdvancedPhotoCapture _advancedCapture;

После того как вы инициализировали объект MediaCapture, создайте в своем приложении объект AdvancedPhotoCaptureSettings и установите режим AdvancedPhotoMode.LowLight. Вызовите метод Configure объекта AdvancedPhotoControl, передав созданный объект AdvancedPhotoCaptureSettings.

Вызовите метод PrepareAdvancedPhotoCaptureAsync объекта MediaCapture, передав объект ImageEncodingProperties с указанием типа кодирования, которое должно использоваться в захвате.

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

Чтобы сделать фото, вызовите метод 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);

Как и в примере HDR выше, здесь используется вспомогательный класс CameraRotationHelper, чтобы определить значение поворота, которое необходимо добавить в код изображения, чтобы оно правильно отображалось в других приложениях и на других устройствах. Этот класс более подробно описан в статье Обработка ориентации устройства с помощью MediaCapture.

Вспомогательный метод SaveCapturedFrameAsync, который сохраняет изображение на диск, рассматривается далее в этой статье.

Можно сделать несколько фото в условиях низкого освещения, не изменяя настройки объекта AdvancedPhotoCapture, однако по завершении фотозахвата необходимо вызвать метод FinishAsync, чтобы удалить объект и связанные ресурсы.

await _advancedCapture.FinishAsync();
_advancedCapture = null;

Работа с объектами AdvancedCapturedPhoto

AdvancedPhotoCapture.CaptureAsync возвращает объект AdvancedCapturedPhoto, представляющий сделанное фото. Этот объект предоставляет свойство Frame, которое возвращает объект CapturedFrame, представляющий изображение. Событие OptionalReferencePhotoCaptured также предоставляет объект CapturedFrame в аргументах события. Получив объект этого типа, с ним можно выполнить несколько действий, включая создание SoftwareBitmap или сохранение изображения в файл.

Получение объекта SoftwareBitmap из CapturedFrame

Чтобы получить SoftwareBitmap из объекта CapturedFrame, нужно всего лишь получить доступ к свойству SoftwareBitmap объекта. Однако большинство форматов кодирования не поддерживают свойство SoftwareBitmap с AdvancedPhotoCapture, поэтому прежде необходимо проверить и убедиться, что это свойство не равно null.

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

Единственный формат кодирования, поддерживающий свойство SoftwareBitmap для AdvancedPhotoCapture, в этом выпуске — это несжатый NV12. Поэтому если требуется использовать эту функцию, необходимо задать эту кодировку при вызове метода PrepareAdvancedPhotoCaptureAsync.

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

Конечно, всегда можно сохранить изображение в файл, а затем загрузить файл в SoftwareBitmap отдельным действием. Дополнительные сведения о работе с SoftwareBitmap см. в разделе Создание, редактирование и сохранение растровых изображений.

Сохранение CapturedFrame в файл

Класс CapturedFrame реализует интерфейс IInputStream, чтобы его можно было использовать в качестве ввода в BitmapDecoder, после чего BitmapEncoder можно использовать для записи данных изображения на диск.

В следующем примере создается новая папка в библиотеке изображений пользователя и в этой папке создается файл. Обратите внимание, что для получения доступа к этому каталогу файл манифеста вашего приложения должен включать функцию Библиотека изображений. Затем поток файлов открывается в заданном файле. После этого вызывается метод BitmapDecoder.CreateAsync для создания декодера из CapturedFrame. Затем CreateForTranscodingAsync создает кодировщик из потока файлов и декодера.

Дальнейшие шаги включают кодирование ориентации фото и включение этого кода в файл изображения с помощью BitmapProperties кодировщика. Дополнительные сведения об ориентации при создании изображений см. в разделе Обработка ориентации устройства с использованием MediaCapture.

Наконец, изображение записывается в файл вызовом метода 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;
}