共用方式為


高動態範圍 (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 設定並導致使用閃光燈擷取普通相片。 如果 Auto 設定為 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 相片。 此方法會傳回 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 事件處理常式中,將 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

只要存取物件的 SoftwareBitmap 屬性,即可從 CapturedFrame 物件取得 SoftwareBitmap。 但是,大多數編碼格式不支援 SoftwareBitmapAdvancedPhotoCapture,因此您應該在使用之前檢查並確保該屬性不為 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.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;
}