はじめに

iOSで開発者がデプス(深度)データにアクセスできるようになったのはなったのは2017年のiOS 11から、つまり比較的最近のことです。登場して間もないからか、はたまた写真をボケさせる用途しかないと思われているからか、日本語での情報はまだほとんどありません。

しかし従来のカメラやGPSが、デジタルの世界と我々が生きる現実世界を繋ぐ重要な役割を担い、アプリ開発者に多くの創造性を与えてくれたのと同様に、モバイル端末で「奥行き」がわかるようになったというのはアプリ開発の次元がひとつ増えたようなものです。

そんなデプスについて、本書では基礎から応用まで順序立てて解説していきます。

対象読者

本書は「iOSアプリ開発の経験は十分にある」ことを前提に書かれています。したがってXcodeやSwiftやUIKitについては解説しませんが、「デプス」については基礎からじっくり解説します。

動作環境

本書は以下の環境に準拠しています。

iOS 13(ARKit 3) / Swift 5.1 / Xcode 11

サンプルコード

本書のサンプルプロジェクトはhttps://github.com/shu223/DepthBookにあります。なお、サンプルアプリを試すにはデュアルカメラまたはTrueDepthカメラを搭載したiOSデバイスが必要です。

目次

はじめに

対象読者
動作環境
サンプルコード

第1章 デプスの概要

1.1 デプスとは?
1.2 デプスの用途
1.3 Disparity(視差)とDepth(深度)
1.4 AVDepthData
  • depthDataMapプロパティ
  • depthDataTypeプロパティ
  • DisparityとDepthの相互変換

第2章 iOSにおけるデプス取得方法

2.1 デプス取得方法1: 撮影済み写真から取得
  • CGImageSourceオブジェクトを作成する
  • デプスデータを持つPHAssetだけを取得する
  • CGImageSourceからAuxiliaryデータを取得する
  • AuxiliaryデータからAVDepthDataを初期化する
  • デプスマップをCIImageとして取得する
2.2 デプス取得方法2: カメラからリアルタイムに取得
  • デプスが取れるタイプのAVCaptureDeviceを使用する
  • デプスが取れるフォーマットを指定する
  • セッションの出力にAVCaptureDepthDataOutputを追加する
  • AVCaptureDepthDataOutputDelegateを実装し、AVDepthDataを取得する
  • AVCaptureDataOutputSynchronizerで出力を同期させる
2.3 デプス取得方法3: ARKitから取得
  • ARFrameのcapturedDepthDataプロパティ
  • ARKitにおけるデプス取得の制約

第3章 デプス応用1: 背景合成

3.1 CIBlendWithMask
3.2 デプスデータをそのままマスクとして用いる
3.3 デプスマップを二値化する
3.4 マスクの平滑化

第4章 デプス応用2: 2D写真から3D点群を生成する

4.1 3D点群座標を求める計算式
4.2 Intrinsic Matrix
4.3 3D点群座標計算の実装
  • Intrinsic Matrixを取得する
  • Intrinsic Matrixをスケールする
  • Intrinsic Matrixを用いてX, Yを計算する

第5章 Portrait Effects Matte

5.1 AVPortraitEffectsMatte
  • mattingImageプロパティ
  • pixelFormatTypeプロパティ
  • AVPortraitEffectsMatteのイニシャライザ
5.2 Portrait Effects Matteの取得方法
  • CGImageSourceからAuxiliaryデータを取得する
  • AuxiliaryデータからAVPortraitEffectsMatteを初期化する
5.3 Portrait Effects Matteの取得条件/制約
  • デプスデータを持ち、人が写っている写真のみ
  • 静止画のみ
  • TrueDepthカメラ・デュアルカメラどちらでも取得可能

第6章 Semantic Segmentation Matte

6.1 Semantic Segmentation Matteの取得方法
6.2 AVSemanticSegmentationMatte
6.3 SSMをCIImage経由で取得する

第7章 People Occlusion (ARKit)

7.1 People Occlusionの実装方法
7.2 personSegmentationとpersonSegmentationWithDepthの違い
7.3 利用可能なコンフィギュレーション
7.4 segmentationBufferとestimatedDepthData
7.5 Metalカスタムレンダリング時のオクルージョン
  • ARMatteGenerator
  • オクルージョン処理のMetalシェーダ

第8章 デプス推定

8.1 FCRN-DepthPredictionモデル
8.2 デプス推定モデルを使用する
  • 基本実装(推論処理実行まで)
  • 推論結果(MLMultiArray)の処理

第9章 一般物体のセグメンテーション

9.1 iOSにおける他のセグメンテーション手段との違い
9.2 DeeplabV3を利用したリアルタイムセグメンテーションの実装
  • 推論結果(MLMultiArray)の処理

参考文献

第1章 デプスの概要

1.1 デプスとは?

デプスとは、「奥行き」を示す情報です。1チャンネルの画像データで表され、このデータはデプスマップとも呼ばれます。

左:カラー画像 / 右:デプスマップ

図1.1: 左:カラー画像 / 右:デプスマップ

各ピクセル値が奥行きを表し、0.0(近い)〜1.0(遠い)、あるいは0(近い)〜255(遠い)といった値をとります。従来のカラー画像も値だけみると同じようなものに見えますが、各ピクセル値はR,G,B,Aあるいはグレーといった「色」の度合いを表すので、意味するところはまったく違います。

1チャンネル画像なので、上の画像のようにグレースケールで描画されることが多いですが、ヒートマップのように赤〜青で色付けされることもあります。

デプスマップを赤〜青で可視化

図1.2: デプスマップを赤〜青で可視化

従来のカラー画像(RGB)に加えてデプスデータを持つ画像を「RGB-D画像」と呼ぶこともあります。

1.2 デプスの用途

iOSで初めてデプスが取得可能になったデバイスはiPhone 7 Plusでしたが、当時はまだデプスデータにアクセス可能なAPIは公開されておらず*1、用途としては写真のDEPTH EFFECT(一眼レフのように背景がきれいにボケる)だけでした。

このため、「デプスは写真をボケさせるもの」「写真アプリを作っているわけではない自分たちには関係がない」と考えてしまう開発者も実は多いかもしれません。

しかしそれは「位置情報は地図アプリだけで使うもの」「カメラはカメラアプリだけで使うもの」と言っているようなものです。位置情報はSNSアプリでもレストラン検索アプリでも使いますし、カメラも何らかの写真投稿機能がある、あるいはユーザーアイコンを使用するすべてのアプリに関係があります。

デプスも同様に広い応用範囲が考えられます。たとえばデプスを利用すると、背景を消し、別の背景と入れ替えることができる*2ので、

左から:元画像、背景を消した画像、別の背景と合成した画像×2

図1.3: 左から:元画像、背景を消した画像、別の背景と合成した画像×2

これを用いて以下のような機能が考えられます。

  • オークションアプリやファッション系アプリ、フリマアプリで、商品の背景を白く飛ばす
  • オンライミーティングや動画コミュニケーションアプリで、背景を入れ替える(散らかっている部屋やプライベートな空間でも使用できるようになる)

またデプスは奥行きの情報なので、2次元の写真を、

写真とデプスマップ

図1.4: 写真とデプスマップ

次のように3次元空間にマッピングすることが可能になります。*3

2D写真から3D点群を生成して様々な角度から見る

図1.5: 2D写真から3D点群を生成して様々な角度から見る

この考え方の応用として、ひとつの物体を複数の角度からデプスデータ付きで撮影すれば、それらを統合して3Dモデルを生成することも可能となります。既にiPhoneのカメラだけで3Dモデルを生成することができるアプリケーションもいくつも登場しています。

以上のように、デプスの応用範囲は広く、さまざまなアプリケーションで役立つ可能性があります。

1.3 Disparity(視差)とDepth(深度)

iOSで取得できるデプスデータにはDisparity(視差)とDepth(深度)の2種類があります。

iPhone ◯ Plus系、X系のiOSデバイスは背面に2つのレンズを持つデュアルカメラを搭載し、その2つのレンズの視差を利用して世界の奥行きを推定することができます。

背面のデュアルカメラ

図1.6: 背面のデュアルカメラ

またiPhone XやXS、XRといったiOSデバイスの前面に搭載されているTrueDepthカメラは、視差方式と違い赤外線を用いて直接的に深度を計測します。

TrueDepthカメラ

図1.7: TrueDepthカメラ

本書ではこれら「視差/Disparity」と「深度/Depth」を総称して「デプス」と表現することにします。

視差は対象物体が近いほど大きく、遠いほど小さくなります。一方で深度の場合、「深度が大きい」は「奥にある」ということを示しているわけですから、対象物体が近いほど小さく、遠いほど大きくなります。つまり、「視差」(Disparity)と奥行きを示す「深度」(Depth)は逆数の関係にあります*4。DisparityとDepthは相互変換できます。

この関係から、デプスマップを1チャンネルのグレースケール画像として可視化すると、Disparityは近くにあるものほど白くなり、Depthは遠くにあるものほど白くなる、といった真逆の結果になります。

Disparity(左)とDepth(右)

図1.8: Disparity(左)とDepth(右)

この違いはデプスマップをマスクとして使用する場合等に重要になってきます*5

1.4 AVDepthData

iOSにおけるデプスの取得方法の解説に入る前に、もっとも重要なクラスをひとつ紹介しておきます。

AVDepthDataは、デプスデータを表すクラスです。以降の節でさまざまなフレームワークでのデプス取得方法を示しますが、いずれの方法を使用するにせよ、(ほとんどの場合は)最終的にデプスデータをこの型で得ることになります。iOS 11以降で利用可能です。

depthDataMapプロパティ

AVDepthDataは多くのプロパティやメソッドを持ちますが、最も重要なのはdepthDataMapプロパティです。

var depthDataMap: CVPixelBuffer { get }

このプロパティはデプスマップのピクセルデータをCVPixelBuffer型で保持します。CVPixelBufferはiOS 4の頃から存在し、多くの画像を扱うフレームワークがこの型をサポートしています。ですのでデプスデータをAVDepthDataとして取得してしまえば、あとはそのデプスマップをCore ImageでもMetalでも、従来の画像処理方法で好きなように処理可能です。

depthDataTypeプロパティ

得られたデプスデータがDisparityなのかDepthなのかといった「デプスデータの種別」はdepthDataTypeプロパティより確認できます。

var depthDataType: OSType { get }

型がOSTypeとなっていますが、次のいずれかのタイプが得られます。DisparityかDepthか、またビット深度は16か32か、の4種類です。

  • kCVPixelFormatType_DisparityFloat16
  • kCVPixelFormatType_DisparityFloat32
  • kCVPixelFormatType_DepthFloat16
  • kCVPixelFormatType_DepthFloat32

DisparityとDepthの相互変換

AVDepthDataでは、converting(toDepthDataType:)メソッドを使用してDisparityとDepthの相互変換が可能です。引数には変換したいデプスデータタイプを指定します。

func converting(toDepthDataType depthDataType: OSType) -> Self

たとえば次のように実装することで、DisparityをDepthへ、DepthをDisparityへビット深度を維持しつつ変換できます。

extension AVDepthData {

    // Depthに変換するメソッド
    func convertToDepth() -> AVDepthData {
        let targetType: OSType
        switch depthDataType {
        case kCVPixelFormatType_DisparityFloat16:
            targetType = kCVPixelFormatType_DepthFloat16
        case kCVPixelFormatType_DisparityFloat32:
            targetType = kCVPixelFormatType_DepthFloat32
        default:
            // もともとDepth
            return self
        }
        // DisparityをDepthへ
        return converting(toDepthDataType: targetType)
    }

    // Disparityに変換するメソッド
    func convertToDisparity() -> AVDepthData {
        let targetType: OSType
        switch depthDataType {
        case kCVPixelFormatType_DepthFloat16:
            targetType = kCVPixelFormatType_DisparityFloat16
        case kCVPixelFormatType_DepthFloat32:
            targetType = kCVPixelFormatType_DisparityFloat32
        default:
            // もともとDisparity
            return self
        }
        // DepthをDisparityへ
        return converting(toDepthDataType: targetType)
    }
}

[*1] iPhone 7 Plus発売時、iOSバージョンは10でした。

[*2] デプスを利用した背景合成については第3章「デプス応用1: 背景合成」で解説しています。

[*3] RGB-D画像から3D点群を生成する方法については第4章「デプス応用2: 2D写真から3D点群を生成する」で解説しています。

[*4] この視差と深度の関係は、WWDC 2017のセッション「Capturing Depth in iPhone Photography」[1]でわかりやすく解説されています。

[*5] 第3章「デプス応用1: 背景合成」

第2章 iOSにおけるデプス取得方法

AVFoundation、Core Image、ImageI/O、Photos、ARKit等々、多くのフレームワークをまたがってデプス関連の機能が用意されています。これらをひとつひとつ個別に解説していくと関連性や使い所がわかりにくくなってしまうため、本書では次の3種類に大別してデプスをAVDepthDataオブジェクトとして取得するまでの実装方法や関連APIを解説していきます。

デプス取得方法 利用するフレームワーク
撮影済み写真から取得 Photos, Image I/O
カメラからリアルタイムに取得 AVFoundation, Core Media
ARKitから取得 ARKit

〜 第2章はここまで 〜

第3章 デプス応用1: 背景合成

前章ではデプスデータを取得する方法について解説しました。本章では応用編として、デプスデータを利用する実装について解説します。まずはデプスデータを用いて背景合成を行ってみましょう。

サンプルコード: MaskWithDepth

背景合成とは写真の人物やモノ等の背景を何か別のものに差し替える画像処理のことで、たとえばクロマキー合成もその一手法です。本節ではマスク画像を基準に、オリジナル画像と背景画像をブレンドする手法で背景合成を行います。

図3.1:

〜 第3章はここまで 〜

第4章 デプス応用2: 2D写真から3D点群を生成する

デプスマップの応用例として見た目にインパクトがあるのが、2Dの写真(つまり通常の写真)を3D点群データに変換するというものです。

入力画像(左)と3D空間への描画結果(右)

図4.1: 入力画像(左)と3D空間への描画結果(右)

本章ではその計算方法と、実装について解説します。

サンプルコード: PointCloudWithDepth

〜 第4章はここまで 〜

第5章 Portrait Effects Matte

iOS 12から取得可能になった「Portrait Effects Matte」(以下PEM)は、前景と背景を分離するマスクとして用いる1チャンネル画像です。これをマスクとして用いると、次のように非常に高精細な背景分離が行えます。

カラー画像(左)、Portrait Effects Matte(中)、背景分離結果(右)

図5.1: カラー画像(左)、Portrait Effects Matte(中)、背景分離結果(右)

生成にあたってデプスデータも用いられてはいますが、すべてのデプスの階調を保持することは目的としておらず、前景としての「人」と、それ以外の背景とをきれいに分離する用途に特化しています。

従来のデプスマップ(左)とPortrait Effects Matte(右)

図5.2: 従来のデプスマップ(左)とPortrait Effects Matte(右)

既存のデプスマップはカラー画像と比較して解像度が低く、髪の毛等の細かいディテールが表現できませんでしたが、Portrait Effects Matteの生成にあたってはApple独自のニューラルネットワークが使用され、高い解像度で髪の毛等の細部まで再現します。

図5.3:

本章ではこのPEMに関連するクラスと、取得までの実装方法について解説します。また取得にあたっての条件・制約についても解説します。

サンプルコード: MaskWithPortraitMatte

〜 第5章はここまで 〜

第6章 Semantic Segmentation Matte

iOS 13では新たに「Semantic Segmentation Matte」(以下SSM)[4]呼ばれるMatteを取得できるようになりました。前章で紹介したPortrait Effect Matte(以下PEM)は、「人間の全身」のセグメンテーションに特化したマスクデータでしたが、SSMは、人の髪・肌・歯といった「人間の特定部位」をセグメンテーションするためのマスクデータです。

左から、Hair / Skin / Teeth

図6.1: 左から、Hair / Skin / Teeth

これらを使って、たとえば髪の色を変える、肌の色を変える、歯を白くする、といったことが可能となります。

SSMを使用して肌の色を変える

図6.2: SSMを使用して肌の色を変える

〜 第6章はここまで 〜

第7章 People Occlusion (ARKit)

ARKit 3(iOS 13)から、人物のオクルージョンが可能になりました。オクルージョンとは、手前にある物体が背後にある物体を隠して見えないようにする状態のことです。

たとえば下図の左の画像のようにカメラに映っている状態で、ARKitでワールドトラッキングを行い、検出したテーブルの平面に仮想オブジェクトを設置するとします。すると、従来は下図の右の画像のように描画されていました。

図7.1:

手前の人物より奥にあるテーブルの平面に設置されているはずの物体が、手前の人物の上に描画されているので、違和感があります。

これを、前後関係を考慮して次のように描画するのが「オクルージョン」です。

図7.2:

本機能はiOS 13以上、A12以降のデバイスで利用可能です。人体の全体、または一部だけでも、複数人が映っていても動作します[5]。またAppleによると屋内環境で最も理想的に動作するとのことです[6]。

〜 第7章はここまで 〜

第8章 デプス推定

これまでデプス(深度)は、デュアルカメラやTrueDepthカメラ等、ハイエンドなiOSデバイスに搭載されるカメラで撮影した場合にのみ取得できるものでした。

しかしWWDC19のタイミングで、Appleが公式に配布するCore MLモデルに「FCRN-DepthPrediction」と呼ばれるデプス推定モデルが追加され、デプス撮影機能をもたないカメラで撮影した静止画や動画からも(機械学習で推定した)デプスデータを取得できるようになりました。

実際のデプスと本モデルで推定したデプスの比較(FCRNのREADMEより)

図8.1: 実際のデプスと本モデルで推定したデプスの比較(FCRNのREADMEより)

〜 第8章はここまで 〜

第9章 一般物体のセグメンテーション

WWDC19のタイミングで、Appleが公式に配布するCore MLモデルに「DeeplabV3」と呼ばれるセグメンテーション用モデルが追加されました。

本モデルはiOS 12以上で利用可能です。

試し読みはここまでです。
この続きは、製品版でお楽しみください。