高动态范围 (HDR) 和低亮度照片捕获

本文介绍了如何使用 AdvancedPhotoCapture 类捕获高动态范围 (HDR) 照片。 此 API 还允许你在完成最终图像的处理之前从 HDR 捕获获取参考帧。

相关 HDR 捕获的其他文章包括:

注意

从 Windows 10 版本 1709 开始,支持录制视频并同时使用 AdvancedPhotoCapture。 以前的版本不支持这一点。 此更改意味着可以同时拥有准备好的 LowLagMediaRecordingAdvancedPhotoCapture。 可以在调用 MediaCapture.PrepareAdvancedPhotoCaptureAsyncAdvancedPhotoCapture.FinishAsync 之间开始或停止录制视频。 还可以在录制视频时调用 AdvancedPhotoCapture.CaptureAsync。 但是,某些 AdvancedPhotoCapture 方案(如在录制视频时捕获 HDR 照片)会导致某些视频帧被 HDR 捕获更改,从而导致用户体验不佳。 因此,由 AdvancedPhotoControl.SupportedModes 返回的模式列表在录制视频时将会有所不同。 在开始或停止视频录制后,应该立即检查此值,以确保在当前的视频录制状态下支持所需的模式。

注意

从 Windows 10 版本 1709 开始,当 AdvancedPhotoCapture 设置为 HDR 模式时,忽略 FlashControl.Enabled 属性的设置并从不触发闪光灯。 对于其他捕获模式,如果是 FlashControl.Enabled,它将替代 AdvancedPhotoCapture 设置,并使用闪关灯捕获正常照片。 如果自动设置为 true,AdvancedPhotoCapture 可能会使用或不使用闪光灯,具体取决于当前场景条件的摄像头驱动程序默认行为。 以前版本中,AdvancedPhotoCapture 闪光灯设置始终替代 FlashControl.Enabled 设置。

注意

本文以使用 MediaCapture 捕获基本的照片、视频和音频中讨论的概念和代码为基础,该文章介绍了实现基本照片和视频捕获的步骤。 我们建议你先熟悉该文中的基本媒体捕获模式,然后再转到更高级的捕获方案。 本文中的代码假设你的应用已有一个正确完成初始化的 MediaCapture 的实例。

有一个通用 Windows 示例展示了 AdvancedPhotoCapture 类的用法,你可以使用该类查看用在上下文中或用作自己应用起始点的 API。 有关详细信息,请参阅相机高级捕获示例

高级照片捕获命名空间

本文中的代码示例除了在基本媒体捕获所需的命名空间中之外,还在以下命名空间中使用 API。

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

HDR 照片捕获

确定 HDR 照片捕获在当前设备上是否受支持

本文中介绍的 HDR 捕获技术使用 AdvancedPhotoCapture 对象进行执行。 并非所有设备都支持使用 AdvancedPhotoCapture 的 HDR 捕获。 确定该技术在当前运行你的应用的设备上是否受支持,方法是获取 MediaCapture 对象的 VideoDeviceController,然后获取 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。调用 AdvancedPhotoControl 对象的 Configure 方法,传入创建的 AdvancedPhotoCaptureSettings 对象。

调用 MediaCapture 对象的 PrepareAdvancedPhotoCaptureAsync,传入用于指定该捕获应使用的编码类型的 ImageEncodingProperties 对象。 ImageEncodingProperties 类提供用于创建受 MediaCapture 支持的图像编码的静态方法。

PrepareAdvancedPhotoCaptureAsync 返回将用于启动照片捕获的 AdvancedPhotoCapture 对象。 可以使用该对象来注册本文稍后将介绍的 OptionalReferencePhotoCapturedAllPhotosCaptured 的处理程序。

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 照片

通过调用 AdvancedPhotoCapture 对象的 CaptureAsync 方法,捕获 HDR 照片。 此方法将返回在其 Frame 属性中提供已捕获照片的 AdvancedCapturedPhoto 对象。

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 照片结果感兴趣,你不需要执行此操作。

重要

在支持硬件 HDR 的设备上不会引发 OptionalReferencePhotoCaptured,因此不会生成参考帧。 你的应用应对不会引发此事件的这种情况进行处理。

由于参考帧在调用 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 事件处理程序中,将 OptionalReferencePhotoCapturedEventArgs 对象的 Context 属性转换为上下文对象类。 此示例修改文件名以区分参考帧图像和最终的 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 后期处理之前,你可以启动一个捕获。 当完成 HDR 捕获时将引发 AllPhotosCaptured 事件,通知你可以启动另一个捕获。 典型方案是在 HDR 捕获开始时禁用 UI 的捕获按钮,然后在引发 AllPhotosCaptured 时重新启用它。

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

清理 AdvancedPhotoCapture 对象

当你的应用完成捕获时,在释放 MediaCapture 对象之前,你应通过调用 FinishAsync 并将你的成员变量设置为 Null 来关闭 AdvancedPhotoCapture 对象。

await _advancedCapture.FinishAsync();
_advancedCapture = null;

低亮度照片捕获

从 Windows 10 版本 1607 开始,可以使用 AdvancedPhotoCapture 捕获利用内置算法来增强照片(在低亮度设置下捕获)质量的照片。 当你使用 AdvancedPhotoCapture 类的低亮度功能时,系统将对当前场景进行评估,并根据需要应用一种算法进行低光补偿。 如果系统确定不需要应用该算法,则执行常规捕获。

在使用低亮度照片捕获功能之前,应确定该技术在当前运行你的应用的设备上是否受支持,方法是获取 MediaCapture 对象的 VideoDeviceController,然后获取 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。 调用 AdvancedPhotoControl 对象的 Configure 方法,传入创建的 AdvancedPhotoCaptureSettings 对象。

调用 MediaCapture 对象的 PrepareAdvancedPhotoCaptureAsync,传入用于指定该捕获应使用的编码类型的 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 或将该图像保存到文件。

从 CapturedFrame 获取 SoftwareBitmap

CapturedFrame 对象获取 SoftwareBitmap 轻而易举,只需访问该对象的 SoftwareBitmap 属性即可。 不过,大多数编码格式都不会因 AdvancedPhotoCapture 而支持 SoftwareBitmap,因此在使用之前,应检查并确保该属性不为 null。

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

在当前版本中,因 AdvancedPhotoCapture 而支持 SoftwareBitmap 的唯一编码格式是未压缩的 NV12。 因此,如果想要使用此功能,必须在调用 PrepareAdvancedPhotoCaptureAsync 时指定该编码格式。

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

当然,可以始终将图像保存到某个文件,然后在单独的步骤中将该文件加载到 SoftwareBitmap 中。 有关使用 SoftwareBitmap 的详细信息,请参阅创建、编辑和保存位图图像

将 CapturedFrame 保存到文件

CapturedFrame 类可实现 IInputStream 接口,因此可以将它用作对 BitmapDecoder 的输入,然后可以使用 BitmapEncoder 将图像数据写入磁盘。

在以下示例中,在用户的图片库中创建一个新文件夹,并且在此文件夹中创建一个文件。 请注意,你的应用需要包含应用部件清单文件中的图片库功能才能访问此目录。 然后针对指定文件打开文件流。 接下来,调用 BitmapDecoder.CreateAsyncCapturedFrame 创建解码器。 然后,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;
}