先日、長年使用していた Web カメラが壊れてしまったため、Windows Hello 対応の Web カメラ Elecom UCAM-CF20FBBK を購入しました。実売価格 5000 円程度でスマートなログイン体験ができるようになり、大満足です。
ところで、Windows Hello では顔認証のために IR(赤外線)画像が利用されています。1 通常のカメラでは環境光の影響を受け、顔認証に必要な情報を取得できないケースがあるためです。
低照度の場合
横から光を当てられている場合
せっかく手元に IR カメラがあるならば、それが見ている世界を見たいのがエンジニア心というもの。しかし、Zoom 等でカメラの一覧を確認しても IR カメラは選択肢に出てきません。Windows Hello は利用でき、デバイスマネージャにも見えているのになぜでしょうか。
どうやら 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
を開いてビルドします。
見えました! 映像が奇妙な色調になっているのは、サンプルが赤外線の値を疑似カラーグラデーションに変換しているためです。
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;
}
成功です! IR カメラの映像を WPF アプリケーションでも表示できました。なお、ソースの全文と成果物はこちらに格納しています(MIT License)。
参考リンク
- Windows Hello 顔認証 | Microsoft Learn
- How do i access the Microsoft IR camera? : r/Surface
- Access and record using your IR laptop camera using Matlab - YouTube
- Access Integrated IR Camera - Microsoft Community
- webcam - How to access Windows Hello IR Camera - Super User
- Infrared Camera in Media Foundation – Fooling Around
- Windows.Media.Capture.Frames 名前空間 - Windows UWP applications | Microsoft Learn
- InfraredMediaFrame クラス (Windows.Media.Capture.Frames) - Windows UWP applications | Microsoft Learn
- MediaFrameReader を使ったメディア フレームの処理 - UWP applications | Microsoft Learn
- Camera frames sample : microsoft/Windows-universal-samples
- .NET 6からWindows Runtime APIを呼ぶのが劇的に楽な件 - はつねの日記
- デスクトップ アプリで Windows ランタイム API を呼び出す - Windows apps | Microsoft Learn
- Display SoftwareBitmap on image control without saving to file in UWP C# - Stack Overflow
- ビットマップ画像の作成、編集、保存 - UWP applications | Microsoft Learn
- wpf - Convert memory stream to BitmapImage? - Stack Overflow
- Known trimming incompatibilities - .NET | Microsoft Learn
- Support IL trimming · Issue #2478 · microsoft/WindowsAppSDK
-
より正確には NIR = Near Infrared (近赤外線) 画像です。サーモグラフィに使用されるものは LWIR = Long Wavelength Infrared (長波長赤外線)であり、波長が異なります。 ↩
-
https://learn.microsoft.com/ja-jp/windows-hardware/design/device-experiences/windows-hello-face-authentication ↩
-
Windows 10 の特定バージョンまでは選択肢にあったという情報もインターネットにはありましたが、真相は不明です。 ↩