見出し画像

iOS/macOSのフォトグラメトリAPIは深度や重力の情報をどのように扱っているのか調べたメモ

今回はiOS 17で追加された撮影周りのObject Capture APIの話ではなく、2年前のWWDC21で発表された写真から3Dモデルを生成する方のObject Capture APIの話。

このフォトグラメトリAPI公開当時に、2D写真を撮影する方のサンプルコードも提供された。そのサンプルアプリを試し、コードを読んでみたのが下記記事なのだが、

このサンプルでは、写真撮影と同時にデプスデータと重力加速度も取得し、保存していたのだが、同時期に公開されていたフォトグラメトリ側のサンプルでは単に写真フォルダを渡すだけの実装となっており、

maybeSession = try PhotogrammetrySession(input: inputFolderUrl,
                                         configuration: configuration)

デプスと重力を使用する実装にはなっていなかった。(2023.8.20修正: デプスや重力を明示的に指定するPhotogrammetrySampleを使用するイニシャライザを使用していないだけで、実際には画像フォルダを指定するだけのイニシャライザの場合でもデプスetc.の追加情報を使用している。)

本記事では、このデプスと重力の情報をiOS/macOSのフォトグラメトリAPIに食わせるにはどのように実装するのか、またデプスや重力の情報は、フォトグラメトリの処理においてどのように用いられるのか(生成される3Dモデルの品質にどのように影響するのか)について、公式リファレンス・公式サンプル・WWDCセッションの情報をベースに調査する。


iOS 17のObject Capture for iOSサンプルではどう実装されているか

iOS 17で追加された撮影側のObject Capture APIを使う場合でも、3Dモデル生成(Reconstruction)フェーズはこの従来からあるフォトグラメトリAPIを使うことになる。

サンプルコードはこちら:

iOS 17の新しいObject Capture APIは要LiDARなので、この公式サンプルでは当然フォトグラメトリAPIにLiDARで取得したデプスデータを渡していると思われる。

というわけで PhotogrammetrySession の実装を見てみると、

photogrammetrySession = try PhotogrammetrySession(
    input: scanFolderManager.imagesFolder,
    configuration: configuration)

あれ…普通に画像フォルダを渡しているだけ…

おかしい、ではLiDAR由来のデプスデータは使ってないのだろうか?

アプリの Documents 配下を見てみると、確かに HEIC しか保存されてない。

Taking Pictures for 3D Object Capture」サンプルの方では HEIC 以外にデプスをTIFで、重力加速度をTXTで保存してたので、

どうやらiOS 17のスキャンサンプルではデプスは保存してないようだ

要LiDARなのは、Guided Captureの体験を成立させるために使われてるのだろう。

ObjectCaptureSessionにデプスや重力データも保存するオプションでもないだろうかとドキュメントを見渡してみたが、なさそうだった。

追記: ObjectCaptureSession はデプス含め様々なデータをHEICに保存している

(2023.8.20追記)

この記事を書いたあとに調べてわかったことだが、ObjectCaptureSessionで撮影した画像のHEICファイルにはデプスが保存されていた。

それだけでなく、デプス以外にも様々なデータを保存していた。

次のように、画像ファイルに入っているデータを確認した:

CGImageSourceCopyProperties(imageSource, nil) as? [String: AnyObject]

これをprintした出力が以下:

["{FileContents}": {
    ImageCount = 1;
    Images =     (
                {
            AuxiliaryData =             (
                                {
                    AuxiliaryDataType = kCGImageAuxiliaryDataTypeDepth;
                    Height = 192;
                    Width = 256;
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#DepthConfidenceMap";
                    Height = 192;
                    Width = 256;
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#CameraTrackingState";
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#CameraCalibrationData";
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#ObjectTransform";
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#ObjectBoundingBox";
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#RawFeaturePoints";
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#PointCloudData";
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#BundleVersion";
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#SegmentID";
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#Feedback";
                },
                                {
                    AuxiliaryDataType = "tag:apple.com,2023:ObjectCapture#WideToDepthCameraTransform";
                }
            );
            Height = 3024;
            ImageIndex = 0;
            NamedColorSpace = kCGColorSpaceSRGB;
            ThumbnailImages =             (
                                {
                    Height = 240;
                    Width = 320;
                }
            );
            Width = 4032;
        }
    );
}, "CanAnimate": 0, "FileSize": 3907659]

この AuxiliaryData に入っているデータがポイントで、kCGImageAuxiliaryDataTypeDepth のデプスデータだけでなく、

  • PointCloudData

  • RawFeaturePoints

  • ObjectBoundingBox

  • SegmentID

  • CameraCalibrationData

  • WideToDepthCameraTransform

といった情報まで保存していることがわかる。(※なお、kCGImageAuxiliaryDataTypeXxxxといったタイプが定義されていないこれらのデータは CGImageSourceCopyAuxiliaryDataInfoAtIndex で取得することはできなかった)

WWDC23のセッションでも

This year, Mac reconstruction also utilizes the LiDAR data we save in our images.
(今年は、画像に保存されているLiDARデータもMacの再構築に利用されます。)

と言っているので、PhotogrammetrySesssionではLiDARで取得した情報も使っているということで間違いなさそう。


なおHEICファイルに入っているAuxiliary Dataの確認にはiOS-Depth-Samplerを使用した。

WWDC21 「Create 3D models with Object Capture」での言及

"Create 3D models with Object Capture" のトランスクリプトでdepthやgravityについて言及している箇所を抽出してみる。

If you capture on iPhone or iPad, we can use stereo depth data from supported devices to allow the recovery of the actual object size, as well as the gravity vector so your model is automatically created right-side up. (iPhoneやiPadでキャプチャーする場合、サポートされているデバイスからステレオデプスデータを使用することで、実際のオブジェクトのサイズと重力ベクトルを復元することができます。)

If there is embedded depth data in HEIC images, it will automatically be used to recover the actual scale of the object.
(HEIC画像に深度データが埋め込まれている場合、それは自動的にオブジェクトの実際のスケールを回復するために使用されます。)

A PhotogrammetrySample includes the image plus other optional data such as a depth map, gravity vector, or custom segmentation mask. (PhotogrammetrySampleには、画像に加えて、深度マップ、重力ベクトル、カスタム・セグメンテーション・マスクなどのオプション・データが含まれます。)

This app allows you to capture images in addition to depth data and gravity information to recover true scale and orientation of your object.
(このアプリは、深度データと重力情報に加えて画像をキャプチャし、対象物の真のスケールと向きを復元することができます。)

To help you get started capturing high-quality photos with depth and gravity on iOS, we provide the CaptureSample App.
(iOSで奥行きと重厚感のある高品質な写真を撮影するために、CaptureSample Appをご用意しています。)

It demonstrates how to use the iPhone and iPads with dual camera to capture depth data and embed it right into the output HEIC files. (デュアルカメラ搭載のiPhoneやiPadを使って深度データをキャプチャし、出力されたHEICファイルに埋め込む方法を紹介している。)

The app also shows you how to save gravity data.
(このアプリは、重力データを保存する方法も教えてくれる。)
You can view your gallery to quickly verify that you have all good-quality photos with depth and gravity and delete bad shots.
(ギャラリーを表示して、奥行きと重厚感のある良質な写真ばかりであることをすぐに確認し、悪い写真を削除することができます。)

ここからの学びは2点:

  • フォトグラメトリAPIにデプスや重力を渡す際には PhotogrammetrySample を用いる

  • デプスデータは HEIC ファイルに埋め込まれている?

    • TIF として出力されていると思ったが、あらためてコードを見てみる

    • iOS 17のサンプルで出力した HEIC にデプスマップが含まれてないか確認してみる

公式ドキュメントでの言及

PhotogrammetrySession にデプスや重力を渡す際には PhotogrammetrySample を用いることがわかった。

その公式ドキュメントを見てみる。

PhotogrammetrySample のAPIリファレンス

An object that represents one image and its corresponding metadata.
(1つの画像とそれに対応するメタデータを表すオブジェクト。)

This object holds a single input image for constructing 3D objects from a series of photographs, along with the image’s metadata, such as EXIF data or information about a depth buffer, an object mask, or gravity vector.
(このオブジェクトは、一連の写真から3Dオブジェクトを構築するための単一の入力画像と、EXIFデータや深度バッファ、オブジェクトマスク、重力ベクトルに関する情報などの画像のメタデータを保持します。)

Use a unique id for each PhotogrammetrySession so RealityKit can distinguish different PhotogrammetrySample instances in status updates, error messages, and other contexts.
(RealityKitがステータス更新やエラーメッセージ、その他のコンテキストで異なるPhotogrammetrySampleインスタンスを区別できるように、各PhotogrammetrySessionには一意のIDを使用します。)

次のようなプロパティ群を持ち、使い方もわかりやすい。

metadata プロパティ

EXIF情報は何に使うのだろう、と metadata プロパティのドキュメントを見てみると、意外にも解説があった(※こういうニッチな class/struct にはたいてい何の解説もない)

You can provide EXIF metadata captured by your digital camera to assist in the object-creation process. During object creation, RealityKit can use data from the EXIF keys listed below.
(デジタルカメラで撮影したEXIFメタデータを提供することで、オブジェクト作成プロセスを支援することができます。オブジェクト作成時に、RealityKitは以下のEXIFキーのデータを使用できます。)

RealityKitが利用するEXIFデータのキーの一覧:

  • TIFFMake

  • TIFFModel

  • TIFFOrientation

  • ExifBodySerialNumber

  • ExifLensMake

  • ExifLensModel

  • ExifLensSerialNumber

  • ExifFocalLength

  • ExifFocalLengthIn35mmFilm

  • GPSAltitude

  • GPSAltitudeRef

  • GPSLatitude

  • GPSLatitudeRef

  • GPSLongitude

  • GPSLongitudeRef

objectMaskプロパティ

object maskとは、たとえば Portrait Matte みたいなもののこと。

このプロパティにもがっつり解説があった。

When a photograph of an object includes surrounding objects, such as plants, buildings, or people in an outdoor space, you can create an object mask to exclude the portions of the image that don’t contain the object. Masking extraneous image data reduces the number of landmarks RealityKit attempts to match, speeds up the object-creation process, and produces a more accurate 3D model.
オブジェクトの写真に、植物や建物、屋外の人など、周囲のオブジェクトが含まれている場合、オブジェクトマスクを作成して、画像のオブジェクトが含まれていない部分を除外することができます。余計な画像データをマスクすることで、RealityKitがマッチングを試みるランドマークの数が減り、オブジェクトの作成プロセスがスピードアップし、より正確な3Dモデルが作成されます。

Provide the object mask in kCVPixelFormatType_OneComponent8 format and with the same height and width as image. RealityKit ignores any pixel in image when the corresponding pixel in objectMask has a value of 0.0 (black) unless isObjectMaskingEnabled is set to False in the session’s configuration.
オブジェクトマスクは kCVPixelFormatType_OneComponent8 形式で、画像と同じ高さと幅で指定してください。RealityKitは、セッションの設定でisObjectMaskingEnabledがFalseに設定されていない限り、objectMaskの対応するピクセルの値が0.0(黒)である場合、imageのピクセルを無視します。

RealityKitの内部処理にどう影響するかしっかり書いてあった。

depthDataMap

Some cameras, including iPhone cameras, capture depth data in addition to image data. Providing this data can help PhotogrammetrySession determine the real-world scale of the photographed object and result in a correctly sized 3D object for placement in an AR scene. This property is read-only.
iPhoneのカメラを含むいくつかのカメラは、画像データに加えて深度データをキャプチャします。このデータを提供することで、PhotogrammetrySessionは撮影されたオブジェクトの実世界の縮尺を決定し、ARシーンに配置するための正しいサイズの3Dオブジェクトを生成することができます。このプロパティは読み取り専用です。

Depth data can be in either kCVPixelFormatType_DisparityFloat32 or kCVPixelFormatType_DepthFloat32 format.
Depth データは、kCVPixelFormatType_DisparityFloat32 または kCVPixelFormatType_DepthFloat32 フォーマットで指定できます。

写真からのReconstructionプロセス自体に使われるというよりは、サイズ決定に関わるのか。

gravity

Some cameras, including iPhone cameras, capture a gravity vector for each image. This vector indicates the orientation of the camera at the moment you took the picture. RealityKit uses this gravity vector to improve landmark matching with the other images in the session.
iPhoneのカメラを含むいくつかのカメラは、画像ごとに重力ベクトルをキャプチャします。このベクトルは、撮影した瞬間のカメラの向きを示しています。RealityKitはこの重力ベクトルを使って、セッション内の他の画像とのランドマークマッチングを改善します。

こちらは想像通り、写真のアラインメントに使用されるようだ。

ここから先は

1,130字

文章やサンプルコードは多少荒削りかもしれませんが、ブログや書籍にはまだ書いていないことを日々大量に載せています。たったの400円で、すぐに購読解除してもその月は過去記事もさかのぼって読めるので、少しでも気になる内容がある方にはオトクかと思います。

技術的なメモやサンプルコード、思いついたアイデア、考えたこと、お金の話等々、頭をよぎった諸々を気軽に垂れ流しています。

最後まで読んでいただきありがとうございます!もし参考になる部分があれば、スキを押していただけると励みになります。 Twitterもフォローしていただけたら嬉しいです。 https://twitter.com/shu223/