ハイ ダイナミック レンジ (HDR) とローライトの写真のキャプチャ
この記事では、AdvancedPhotoCapture クラスを使って、ハイ ダイナミック レンジ (HDR) の写真をキャプチャする方法について説明します。 最終的な画像の処理が完了する前に、この API を使って、HDR キャプチャから参照フレームを取得することもできます。
HDR キャプチャに関連したその他の記事を以下に示します。
SceneAnalysisEffect でメディア キャプチャのプレビュー ストリームの内容をシステムで評価し、HDR 処理によるキャプチャ結果の向上が期待できるかどうかを判断できます。 詳しくは、「メディア キャプチャのシーン分析」をご覧ください。
HdrVideoControl で、Windows に組み込まれている HDR 処理アルゴリズムを使ってビデオをキャプチャします。 詳しくは、「ビデオ キャプチャのためのキャプチャ デバイス コントロール」をご覧ください。
VariablePhotoSequenceCapture を使うと、キャプチャ設定がそれぞれ異なる一連の写真をキャプチャすることができます。HDR またはその他の処理アルゴリズムを独自に実装することが可能です。 詳しくは、「可変の写真シーケンス」をご覧ください。
注意
Windows 10、バージョン 1709 以降では、ビデオ録画と AdvancedPhotoCapture の使用の同時実行がサポートされています。 これは、それより前のバージョンではサポートされていません。 この変更により、LowLagMediaRecordingとAdvancedPhotoCapture の準備を同時に実行できるようになりました。 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 のインスタンスが既に作成され、適切に初期化されていることを前提としています。
AdvancedPhotoCapture クラスの使い方を示す、ユニバーサル Windows のサンプルがあります。コンテキスト内で 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 に設定します。作成した AdvancedPhotoCaptureSettings オブジェクトを AdvancedPhotoControl オフジェクトの Configure メソッドに渡して呼び出します。
MediaCapture オブジェクトの PrepareAdvancedPhotoCaptureAsync を呼び出す際に 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 写真をキャプチャするには、AdvancedPhotoCapture オブジェクトの CaptureAsync メソッドを呼び出します。 このメソッドから返される 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 写真の最終的な結果だけが目的であれば、この処理は不要です。
重要
ハードウェア 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 写真のキャプチャには、2 つのステップがあります。 複数のフレームをキャプチャするステップと、その後、それらのフレームが最終的な 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 に設定します。 作成した AdvancedPhotoCaptureSettings オブジェクトを AdvancedPhotoControl オブジェクトの Configure メソッドに渡して呼び出します。
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;
}
現在のリリースで、AdvancedPhotoCapture での SoftwareBitmap の使用をサポートしている唯一のエンコード形式は、非圧縮形式の NV12 です。 したがってこの機能を使用する場合は、PrepareAdvancedPhotoCaptureAsync を呼び出す際にそのエンコーディングを指定する必要があります。
_advancedCapture =
await _mediaCapture.PrepareAdvancedPhotoCaptureAsync(ImageEncodingProperties.CreateUncompressed(MediaPixelFormat.Nv12));
もちろん、ファイルに画像を保存し、別個の手順で SoftwareBitmap にファイルを読み込むことも常に可能です。 SoftwareBitmap の操作について詳しくは、「ビットマップ画像の作成、編集、保存」をご覧ください。
CapturedFrame をファイルに保存する
CapturedFrame クラスは IInputStream インターフェイスを実装するので、BitmapDecoder への入力として使用できます。その後、BitmapEncoder を使えば画像データをディスクに書き込むことができます。
次の例では、ユーザーの画像ライブラリに新しいフォルダーが作成され、そのフォルダー内にファイルが作成されています。 このディレクトリにアクセスするためには、アプリが Pictures Library 機能をアプリ マニフェスト ファイルに含める必要があります。 すると、ファイル ストリームが指定のファイルに開かれます。 次に、CapturedFrame からデコーダーを作成するために BitmapDecoder.CreateAsync を呼び出します。 その後 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;
}