LoginSignup
0
0

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

Posted at

先日、長年使用していた 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)。

参考リンク

  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 の特定バージョンまでは選択肢にあったという情報もインターネットにはありましたが、真相は不明です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0