🗂

(Swift)iPhoneのLiDARセンサーから無加工のデータを取得する

2024/07/04に公開

結論

お急ぎの方のため、先に結論から。

  1. センサーから得られる無加工の距離画像について実質的な解像度は横32・縦24程度です。APIからは10倍に引き伸ばした320 x 240のデータとして見えます。
  2. ピクセルには16 bit floatで深度が記録されます。そのままでは画像として扱えません。Metalシェーダーで白黒の濃淡画像としてレンダリングするなど、可視化するにはユーザー側で加工する必要があります。
  3. 通常の静止画あるいは動画を撮影しつつ同時に距離画像を取得できます。フレームレートは独立していて距離画像の取得頻度は実質10 fps程度です。
  4. センサー起動中はiPhone本体がかなり発熱します。それが原因か不明ですが、センサーがフリーズすることがあります。
  5. フリーズとはセンサーの更新が停止して同じデータを出力し続ける状態のことです。特にエラーは出ず、APIからは正常にセンサーが稼働しているように見えます。センサー起動後30秒ほどでフリーズする場合もあれば、数分起動し続けても問題なく動作する場合もあります。

上記はiOS 17.5.1がインストールされたiPhone 15 Pro実機で確認しました。

はじめに

2022年3月にリリースされたiOS 15.4以降、AVCaptureDevice APIを経由してLiDARセンサーに直接アクセスできるようになりました。以下のリンクからサンプルプロジェクトをダウンロードできます。

Capturing depth using the LiDAR camera - Apple Developer

上記のサンプル実装の中で、注目するのは以下3点です。

  1. CameraController.swift
  2. Views/Metal/MetalTextureViewDepth.swift
  3. Views/Metal/Shaders/shaders.metal

(コードの解説)CameraController.swift

まず注目するのは次の処理です。

// Find a match that outputs video data in the format the app's custom Metal views require.
guard let format = (device.formats.last { format in
    format.formatDescription.dimensions.width == preferredWidthResolution &&
    format.formatDescription.mediaSubType.rawValue == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange &&
    !format.isVideoBinned &&
    !format.supportedDepthDataFormats.isEmpty
}) else {
    throw ConfigurationError.requiredFormatUnavailable
}

上記はカメラモジュールから通常の動画としてデータを取得できるか問い合わせる処理です。その際にLiDARセンサーが深度情報を付与できるかsupportedDepthDataFormatsプロパティで確認しています。

従って、LiDARセンサーの情報だけを取得することはできません。LiDARセンサーから得られるデータだけが必要な場合、もったいないですが動画や静止画のデータは無駄になります。

なお、定数kCVPixelFormatType_420YpCbCr8BiPlanarFullRangeは画像のフォーマットを指定する定数です。この時点ではまだLiDARセンサーの詳細な設定は行われていません。

次はLiDARセンサーから取得するデータの形式として利用可能なものを問い合わせる処理です。

// Find a match that outputs depth data in the format the app's custom Metal views require.
guard let depthFormat = (format.supportedDepthDataFormats.last { depthFormat in
    depthFormat.formatDescription.mediaSubType.rawValue == kCVPixelFormatType_DepthFloat16
}) else {
    throw ConfigurationError.requiredFormatUnavailable
}

記事の冒頭で深度情報のピクセルの形式は16 bit floatと説明しましたが、これが根拠です。定数kCVPixelFormatType_DepthFloat16はピクセルに格納される深度情報の形式を表します。なお、バリエーションとして32 bit floatを表す定数もありますが、この定数は利用できません。

(コードの解説)Metalシェーダーを利用した可視化

LiDARから得られたデータを可視化する処理はユーザーが実装する必要があります。解像度が低いとはいえ、それなりの大きさのデータを扱うことになります。従って、実装するにはMetalシェーダーを利用したGPUプログラミングの知識が必要になります。

サンプルプロジェクトのshaders.metalがGPU上で実装される処理です。独自の拡張子が振られていますが、中身はC++で実装されています。

GPUプログラミングの経験がない方のために大雑把に説明しますと、GPUは画像を描画する際にRGBAの色情報の他に奥行きを表すデータを扱います。

デスクトップに複数のウィンドウが重なっている様子を想像してください。もし奥行きの情報がなければ、ウィンドウの重なりをどのように描画するべきかGPUは判断できません。

LiDARセンサーから得られるデータはRGBA情報がなく、奥行きの情報だけを保持していると考えてください。そのため、センサーから得られた生のデータをGPU上で加工できるのです。既存のGPUプログラミングが行かせるように、うまく設計されているわけです。

...と、ここまで自信たっぷりに書きましたが、私自身はGPUアーキテクチャやシェーダープログラミングについて素人です。説明が間違っている可能性もありますので、各自で裏をとってください。

おわりに

LiDARセンサーから取得したデータの利用目的として、建築関係や立体モデリングに関連する記事は多いのですが、距離画像として生のデータを扱う記事はほとんど見かけません。この記事がお役に立てたなら幸いです。

とはいえ、生のデータは粗すぎて使い道がないかもしれません。当初は暗視ゴーグル的なアプリが実装できるのではと期待していたのですが、そのような用途には全く利用できません。

もちろん深度情報は正確に得られます。例えば目の前に壁が迫ってきたことを警告する視覚障害ユーザー向けの支援アプリが実現できるかもしれません。しかし、それなら通常のカメラで得られる画像ベースの処理で実現できそうです。

ARKitなど、より高レイヤーのAPIではLiDARセンサーから得られるデータの精度が高いように感じます。これは機械学習を用いてデータを補正しているからです。

LiDARセンサーに直接アクセスできるようになったのは嬉しいのですが、なかなか使い所の難しい機能です。

(おまけ)サンプルプロジェクト実行時のログ

Selected video format: <AVCaptureDeviceFormat: 0x303a9f6d0 'vide'/'420f' 1920x1440, { 1- 60 fps}, photo dims:{1920x1440,4032x3024}, fov:72.044, supports vis (max strength:Low), max zoom:157.50 (upscales @1.91), secondary:2.00, AF System:2, ISO:55.0-5280.0, SS:0.000023-1.000000, supports HDR, supports wide color, supports depth, supports multicam, supports high photo quality, supports CS RoI>
Selected depth format: Optional('dpth'/'hdep'  320x 240, { 1- 30 fps}, photo dims:{}, fov:74.597)

余談になりますが、機種によってセンサーの精度は異なるのでしょうか?結論としては、わかりません。デバイスの世代に関係なく同じ機能を提供したいのであれば、実機を購入して動作検証するしかなさそうです。

ただし、実機でテストしたからといって、その結果を完全に信用はできません。というのも、古い世代のデバイスが陳腐化するのを避けるため、発売当初はソフトウェア的にセンサーの精度を低くしている可能性があるからです。動作検証する場合はiOSのバージョンも揃える方が良さそうです。

Discussion