Skip to content

Latest commit

 

History

History
168 lines (130 loc) · 11.4 KB

File metadata and controls

168 lines (130 loc) · 11.4 KB

Windows Hello 用赤外線カメラに見えている世界を見てみたい

先日、長年使用していた Web カメラが壊れてしまったため、Windows Hello 対応の Web カメラ Elecom UCAM-CF20FBBK を購入しました。実売価格 5000 円程度でスマートなログイン体験ができるようになり、大満足です。

ところで、Windows Hello では顔認証のために IR(赤外線)画像が利用されています。1 通常のカメラでは環境光の影響を受け、顔認証に必要な情報を取得できないケースがあるためです。

低照度の場合

通常画像1 IR画像1

横から光を当てられている場合

通常画像2 IR画像2 2

せっかく手元に IR カメラがあるならば、それが見ている世界を見たいのがエンジニア心というもの。しかし、Zoom 等でカメラの一覧を確認しても IR カメラは選択肢に出てきません。Windows Hello は利用でき、デバイスマネージャにも見えているのになぜでしょうか。

device-manager.png

どうやら IR カメラは通常のカメラと並列には扱われないようです。3 というのも、IR カメラの映像は通常カメラのそれと比べて特殊な見え方をします。意図せず IR カメラを選択したライトユーザが「カメラが壊れた!」と思わないためにも、選択肢から隠すのは妥当なように思えます。では、IR カメラの映像を確認する方法は本当にないのでしょうか?

UWP で Windows Hello 用 IR カメラの映像を表示する

実は、WinRT API に含まれる Windows.Media.Capture.Frames.InfraredMediaFrame で IR カメラの映像を取得できます。UWP の公式サンプル集 には Windows.Media.Capture.Frames のサンプルもありますので、これが利用できます。GitHub からクローン後、Visual Studio で Samples\CameraFrames\cs\CameraFrames.sln を開いてビルドします。

process-media-frames-with-mediaframereader.png

見えました! 映像が奇妙な色調になっているのは、サンプルが赤外線の値を疑似カラーグラデーションに変換しているためです。

WPF で Windows Hello 用 IR カメラの映像を表示する

WPF でも TargetFramework を指定することで WinRT API を使用できますので、IR カメラの映像は表示できるはずです。試したところ、MediaCapture.CapturePhotoToStreamAsync() ではエラーとなり、IR カメラの映像をキャプチャできませんでした:

メディアの種類に指定されたデータが無効か、矛盾するか、またはこのオブジェクトではサポートされていません。
Passthrough mode, sink media type does not match source media type

そのため、素直に上記サンプルと同様 MediaFrameReader を使ったメディア フレームの処理を行います:

private async void Start(object sender, RoutedEventArgs e)
{
    // デバイスで現在利用可能な MediaFrameSourceGroup のリストを取得する
    var frameSourceGroups = await MediaFrameSourceGroup.FindAllAsync();

    // IR データを生成する FrameSourceGroups を選択
    var selectedGroup = frameSourceGroups.FirstOrDefault(group => group.SourceInfos.Any(info => info.SourceKind == MediaFrameSourceKind.Infrared));
    if (selectedGroup == null)
    {
        Debug.WriteLine("IR カメラが見つかりませんでした");
        return;
    }

    var mediaCapture = new MediaCapture();
    try
    {
        // MediaCapture に選択したソースを設定して初期化
        await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings()
        {
            SourceGroup = selectedGroup,
            SharingMode = MediaCaptureSharingMode.ExclusiveControl,
            MemoryPreference = MediaCaptureMemoryPreference.Cpu,
            StreamingCaptureMode = StreamingCaptureMode.Video
        });
    }
    catch (Exception ex)
    {
        Debug.WriteLine("MediaCapture の初期化に失敗しました: " + ex.Message);
        return;
    }

    // 選択したソースから MediaFrameReader を作成
    var mediaFrameReader = await mediaCapture.CreateFrameReaderAsync(mediaCapture.FrameSources[selectedGroup.SourceInfos[0].Id]);

    // MediaFrameReader の FrameArrived イベントハンドラに処理を登録
    mediaFrameReader.FrameArrived += MediaFrameReader_FrameArrived;

    // MediaFrameReader の開始
    await mediaFrameReader.StartAsync();
}


/// <summary>
/// MediaFrameReader にフレームが到着した時の処理
/// </summary>
private void MediaFrameReader_FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    // sender から最新フレームへの参照を取得
    using var latestFrameReference = sender.TryAcquireLatestFrame();

    // 最新フレームのビットマップ
    var softwareBitmap = latestFrameReference.VideoMediaFrame.SoftwareBitmap;

    // WPF の Image コントロールで表示できるよう、BGRA8 のアルファ乗算済みに変換する
    if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
        softwareBitmap.BitmapAlphaMode != BitmapAlphaMode.Premultiplied)
    {
        softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
    }

    // UI スレッドで画像を更新する
    CameraImage.Dispatcher.BeginInvoke(async () =>
    {
        // 同時実行させない
        if (_running) return;
        _running = true;

        // WPF の Image コントロールで表示できるよう、SoftwareBitmap から BitmapImage に変換する
        CameraImage.Source = await ConvertSoftwareBitmap2BitmapImage(softwareBitmap);

        _running = false;
    });
}

/// <summary>
/// インメモリで SoftwareBitmap から BitmapImage に変換する
/// </summary>
/// <param name="src">変換元</param>
/// <returns>変換結果</returns>
private static async Task<BitmapImage> ConvertSoftwareBitmap2BitmapImage(SoftwareBitmap src)
{
    // インメモリストリームに SoftwareBitmap をセット
    using var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
    var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
    encoder.SetSoftwareBitmap(src);
    await encoder.FlushAsync();

    // インメモリストリームから BitmapImage を作成
    var result = new BitmapImage();
    result.BeginInit();
    result.StreamSource = stream.AsStream();
    result.CacheOption = BitmapCacheOption.OnLoad;
    result.EndInit();
    result.Freeze();

    return result;
}

InfraredMediaFrameSample.png

成功です! IR カメラの映像を WPF アプリケーションでも表示できました。なお、ソースの全文と成果物はこちらに格納しています(MIT License)。

参考リンク

Footnotes

  1. より正確には NIR = Near Infrared (近赤外線) 画像です。サーモグラフィに使用されるものは LWIR = Long Wavelength Infrared (長波長赤外線)であり、波長が異なります。

  2. https://learn.microsoft.com/ja-jp/windows-hardware/design/device-experiences/windows-hello-face-authentication

  3. Windows 10 の特定バージョンまでは選択肢にあったという情報もインターネットにはありましたが、真相は不明です。