HDR(High Dynamic Range) 및 저조도 사진 캡처

이 문서에서는 AdvancedPhotoCapture 클래스를 사용하여 HDR(High Dynamic Range) 사진을 캡처하는 방법을 보여 줍니다. 또한 이 API를 사용하면 최종 이미지 처리가 완료되기 전에 HDR 캡처에서 참조 프레임을 가져올 수 있습니다.

HDR 캡처와 관련된 다른 문서는 다음과 같습니다.

참고 항목

Windows 10, 버전 1709부터는 비디오 녹화와 AdvancedPhotoCapture 동시 사용이 지원됩니다. 이 기능은 이전 버전에서 지원되지 않습니다. 이러한 변화 덕분에 LowLagMediaRecordingAdvancedPhotoCapture를 동시에 준비할 수 있습니다. MediaCapture.PrepareAdvancedPhotoCaptureAsync 호출과 AdvancedPhotoCapture.FinishAsync 호출 사이에 비디오 녹화를 시작하거나 중지할 수 있습니다. 또한 비디오를 녹화하는 동안 AdvancedPhotoCapture.CaptureAsync를 호출할 수도 있습니다. 그러나 비디오 녹화 중에 HDR 사진 캡처와 같은 일부 AdvancedPhotoCapture 시나리오를 사용하면 HDR 캡처로 인해 일부 비디오 프레임이 변경되어 부정적인 사용자 환경을 유발할 수 있습니다. 이러한 이유로 AdvancedPhotoControl.SupportedModes에서 반환한 모드 목록은 비디오가 녹화되는 동안 다릅니다. 비디오 녹화를 시작하거나 중지한 직후에 이 값을 확인하여 현재 비디오 녹화 상태에서 원하는 모드가 지원되고 있는지 확인해야 합니다.

참고 항목

Windows 10, 버전 1709부터 AdvancedPhotoCapture가 HDR 모드로 설정되면 FlashControl.Enabled 속성의 설정이 무시되고 플래시가 절대 시작되지 않습니다. 다른 캡처 모드에서 FlashControl.Enabled의 경우 AdvancedPhotoCapture 설정을 덮어쓰고 일반 사진이 플래시로 캡처됩니다. Auto가 true로 설정되어 있으면 현재 장면의 조건에 대한 카메라 드라이버의 기본 동작에 따라 AdvancedPhotoCapture이 플래시를 사용하거나 사용하지 않습니다. 이전 릴리스에서 AdvancedPhotoCapture 플래시 설정은 항상 FlashControl.Enabled 설정을 재정의합니다.

참고 항목

이 문서는 기본 사진 및 비디오 캡처 구현 단계를 설명하는 MediaCapture를 사용한 기본적인 사진, 비디오 및 오디오 캡처에 설명된 개념 및 코드를 토대로 작성되었습니다. 좀 더 수준 높은 캡처 시나리오를 진행하기 전에 해당 문서의 기본 미디어 캡처 패턴을 좀 더 잘 이해하는 것이 좋습니다. 이 문서의 코드는 앱에 적절히 초기화된 MediaCapture의 인스턴스가 이미 있다고 가정합니다.

컨텍스트 또는 사용자 고유 앱의 시작점으로 사용되는 API를 확인하는 데 사용할 수 있는 AdvancedPhotoCapture 클래스의 사용을 보여 주는 유니버설 Windows 샘플이 있습니다. 자세한 내용은 카메라 고급 캡처 샘플을 참조하십시오.

고급 사진 캡처 네임스페이스

이 문서의 코드 예제에서는 기본 미디어 캡처에 필요한 네임스페이스 외에도 다음 네임스페이스의 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 사진 결과에만 관심이 있는 경우에는 이 작업을 수행할 필요가 없습니다.

Important

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 이벤트 처리기에서 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 개체를 반환합니다. 이 개체는 이미지를 나타내는 CapturedFrame 개체를 반환하는 Frame 속성을 노출합니다. OptionalReferencePhotoCaptured 이벤트는 이벤트 인수에 CapturedFrame 개체도 제공합니다. 이 형식의 개체를 가져와서 SoftwareBitmap을 만들거나 파일에 이미지를 저장하는 등 다양한 작업을 수행할 수 있습니다.

CapturedFrame에서 SoftwareBitmap 가져오기

개체의 SoftwareBitmap 속성에 액세스하기만 하면 CapturedFrame 개체에서 SoftwareBitmap을 가져오는 것은 간단합니다. 그러나 대부분의 인코딩 형식은 AdvancedPhotoCapture에서 SoftwareBitmap을 지원하지 않으므로 사용하기 전에 검사 속성이 null이 아닌지 확인해야 합니다.

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

현재 릴리스에서 AdvancedPhotoCaptureSoftwareBitmap을 지원하는 유일한 인코딩 형식은 압축되지 않은 NV12입니다. 따라서 이 기능을 사용하려면 PrepareAdvancedPhotoCaptureAsync를 호출할 때 해당 인코딩을 지정해야 합니다.

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

물론 항상 이미지를 파일에 저장한 다음 별도의 단계에서 SoftwareBitmap에 파일을 로드할 수 있습니다. SoftwareBitmap 작업에 대한 자세한 내용은 비트맵 이미지 만들기, 편집 및 저장을 참조하십시오.

파일에 CapturedFrame을 저장합니다.

CapturedFrame 클래스는 IInputStream 인터페이스를 구현하므로 BitmapDecoder에 대한 입력으로 사용할 수 있으며 BitmapEncoder를 사용하여 이미지 데이터를 디스크에 쓸 수 있습니다.

다음 예제에서는 사용자의 그림 라이브러리에 새 폴더가 만들어지고 이 폴더 내에 파일이 만들어집니다. 이 디렉터리에 액세스하려면 앱 매니페스트 파일에 그림 라이브러리 기능을 포함해야 합니다. 그런 다음 파일 스트림이 지정된 파일에 열립니다. 다음으로, BitmapDecoder.CreateAsync가 호출되어 CapturedFrame에서 디코더를 만듭니다. 그런 다음 CreateForTranscodingAsync는 파일 스트림 및 디코더에서 인코더를 만듭니다.

다음 단계에서는 인코더의 BitmapProperties를 사용하여 사진의 방향을 이미지 파일로 인코딩합니다. 이미지를 캡처할 때 방향을 처리하는 방법에 대한 자세한 내용은 Handle device orientation with 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;
}