iOSで開発者がデプス(深度)データにアクセスできるようになったのはなったのは2017年のiOS 11から、つまり比較的最近のことです。登場して間もないからか、はたまた写真をボケさせる用途しかないと思われているからか、日本語での情報はまだほとんどありません。
しかし従来のカメラやGPSが、デジタルの世界と我々が生きる現実世界を繋ぐ重要な役割を担い、アプリ開発者に多くの創造性を与えてくれたのと同様に、モバイル端末で「奥行き」がわかるようになったというのはアプリ開発の次元がひとつ増えたようなものです。
そんなデプスについて、本書では基礎から応用まで順序立てて解説していきます。
本書は「iOSアプリ開発の経験は十分にある」ことを前提に書かれています。したがってXcodeやSwiftやUIKitについては解説しませんが、「デプス」については基礎からじっくり解説します。
本書のサンプルプロジェクトはhttps://github.com/shu223/DepthBook
にあります。なお、サンプルアプリを試すにはデュアルカメラまたはTrueDepthカメラを搭載したiOSデバイスが必要です。
はじめに
第1章 デプスの概要
第2章 iOSにおけるデプス取得方法
第3章 デプス応用1: 背景合成
第4章 デプス応用2: 2D写真から3D点群を生成する
第5章 Portrait Effects Matte
第6章 Semantic Segmentation Matte
第7章 People Occlusion (ARKit)
第8章 デプス推定
第9章 一般物体のセグメンテーション
参考文献
デプスとは、「奥行き」を示す情報です。1チャンネルの画像データで表され、このデータはデプスマップとも呼ばれます。
各ピクセル値が奥行きを表し、0.0
(近い)〜1.0
(遠い)、あるいは0
(近い)〜255
(遠い)といった値をとります。従来のカラー画像も値だけみると同じようなものに見えますが、各ピクセル値はR,G,B,Aあるいはグレーといった「色」の度合いを表すので、意味するところはまったく違います。
1チャンネル画像なので、上の画像のようにグレースケールで描画されることが多いですが、ヒートマップのように赤〜青で色付けされることもあります。
従来のカラー画像(RGB)に加えてデプスデータを持つ画像を「RGB-D画像」と呼ぶこともあります。
iOSで初めてデプスが取得可能になったデバイスはiPhone 7 Plusでしたが、当時はまだデプスデータにアクセス可能なAPIは公開されておらず*1、用途としては写真のDEPTH EFFECT(一眼レフのように背景がきれいにボケる)だけでした。
このため、「デプスは写真をボケさせるもの」「写真アプリを作っているわけではない自分たちには関係がない」と考えてしまう開発者も実は多いかもしれません。
しかしそれは「位置情報は地図アプリだけで使うもの」「カメラはカメラアプリだけで使うもの」と言っているようなものです。位置情報はSNSアプリでもレストラン検索アプリでも使いますし、カメラも何らかの写真投稿機能がある、あるいはユーザーアイコンを使用するすべてのアプリに関係があります。
デプスも同様に広い応用範囲が考えられます。たとえばデプスを利用すると、背景を消し、別の背景と入れ替えることができる*2ので、
これを用いて以下のような機能が考えられます。
またデプスは奥行きの情報なので、2次元の写真を、
次のように3次元空間にマッピングすることが可能になります。*3
この考え方の応用として、ひとつの物体を複数の角度からデプスデータ付きで撮影すれば、それらを統合して3Dモデルを生成することも可能となります。既にiPhoneのカメラだけで3Dモデルを生成することができるアプリケーションもいくつも登場しています。
以上のように、デプスの応用範囲は広く、さまざまなアプリケーションで役立つ可能性があります。
iOSで取得できるデプスデータにはDisparity(視差)とDepth(深度)の2種類があります。
iPhone ◯ Plus系、X系のiOSデバイスは背面に2つのレンズを持つデュアルカメラを搭載し、その2つのレンズの視差を利用して世界の奥行きを推定することができます。
またiPhone XやXS、XRといったiOSデバイスの前面に搭載されているTrueDepthカメラは、視差方式と違い赤外線を用いて直接的に深度を計測します。
本書ではこれら「視差/Disparity」と「深度/Depth」を総称して「デプス」と表現することにします。
視差は対象物体が近いほど大きく、遠いほど小さくなります。一方で深度の場合、「深度が大きい」は「奥にある」ということを示しているわけですから、対象物体が近いほど小さく、遠いほど大きくなります。つまり、「視差」(Disparity)と奥行きを示す「深度」(Depth)は逆数の関係にあります*4。DisparityとDepthは相互変換できます。
この関係から、デプスマップを1チャンネルのグレースケール画像として可視化すると、Disparityは近くにあるものほど白くなり、Depthは遠くにあるものほど白くなる、といった真逆の結果になります。
この違いはデプスマップをマスクとして使用する場合等に重要になってきます*5。
iOSにおけるデプスの取得方法の解説に入る前に、もっとも重要なクラスをひとつ紹介しておきます。
AVDepthData
は、デプスデータを表すクラスです。以降の節でさまざまなフレームワークでのデプス取得方法を示しますが、いずれの方法を使用するにせよ、(ほとんどの場合は)最終的にデプスデータをこの型で得ることになります。iOS 11以降で利用可能です。
AVDepthData
は多くのプロパティやメソッドを持ちますが、最も重要なのはdepthDataMap
プロパティです。
var depthDataMap: CVPixelBuffer { get }
このプロパティはデプスマップのピクセルデータをCVPixelBuffer
型で保持します。CVPixelBuffer
はiOS 4の頃から存在し、多くの画像を扱うフレームワークがこの型をサポートしています。ですのでデプスデータをAVDepthData
として取得してしまえば、あとはそのデプスマップをCore
ImageでもMetalでも、従来の画像処理方法で好きなように処理可能です。
得られたデプスデータがDisparityなのかDepthなのかといった「デプスデータの種別」はdepthDataType
プロパティより確認できます。
var depthDataType: OSType { get }
型がOSType
となっていますが、次のいずれかのタイプが得られます。DisparityかDepthか、またビット深度は16か32か、の4種類です。
kCVPixelFormatType_DisparityFloat16
kCVPixelFormatType_DisparityFloat32
kCVPixelFormatType_DepthFloat16
kCVPixelFormatType_DepthFloat32
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: 背景合成」
AVFoundation、Core Image、ImageI/O、Photos、ARKit等々、多くのフレームワークをまたがってデプス関連の機能が用意されています。これらをひとつひとつ個別に解説していくと関連性や使い所がわかりにくくなってしまうため、本書では次の3種類に大別してデプスをAVDepthData
オブジェクトとして取得するまでの実装方法や関連APIを解説していきます。
デプス取得方法 | 利用するフレームワーク |
---|---|
撮影済み写真から取得 | Photos, Image I/O |
カメラからリアルタイムに取得 | AVFoundation, Core Media |
ARKitから取得 | ARKit |
〜 第2章はここまで 〜
前章ではデプスデータを取得する方法について解説しました。本章では応用編として、デプスデータを利用する実装について解説します。まずはデプスデータを用いて背景合成を行ってみましょう。
サンプルコード: MaskWithDepth
背景合成とは写真の人物やモノ等の背景を何か別のものに差し替える画像処理のことで、たとえばクロマキー合成もその一手法です。本節ではマスク画像を基準に、オリジナル画像と背景画像をブレンドする手法で背景合成を行います。
〜 第3章はここまで 〜
デプスマップの応用例として見た目にインパクトがあるのが、2Dの写真(つまり通常の写真)を3D点群データに変換するというものです。
本章ではその計算方法と、実装について解説します。
サンプルコード: PointCloudWithDepth
〜 第4章はここまで 〜
iOS 12から取得可能になった「Portrait Effects Matte」(以下PEM)は、前景と背景を分離するマスクとして用いる1チャンネル画像です。これをマスクとして用いると、次のように非常に高精細な背景分離が行えます。
生成にあたってデプスデータも用いられてはいますが、すべてのデプスの階調を保持することは目的としておらず、前景としての「人」と、それ以外の背景とをきれいに分離する用途に特化しています。
既存のデプスマップはカラー画像と比較して解像度が低く、髪の毛等の細かいディテールが表現できませんでしたが、Portrait Effects Matteの生成にあたってはApple独自のニューラルネットワークが使用され、高い解像度で髪の毛等の細部まで再現します。
本章ではこのPEMに関連するクラスと、取得までの実装方法について解説します。また取得にあたっての条件・制約についても解説します。
サンプルコード: MaskWithPortraitMatte
〜 第5章はここまで 〜
iOS 13では新たに「Semantic Segmentation Matte」(以下SSM)[4]呼ばれるMatteを取得できるようになりました。前章で紹介したPortrait Effect Matte(以下PEM)は、「人間の全身」のセグメンテーションに特化したマスクデータでしたが、SSMは、人の髪・肌・歯といった「人間の特定部位」をセグメンテーションするためのマスクデータです。
これらを使って、たとえば髪の色を変える、肌の色を変える、歯を白くする、といったことが可能となります。
〜 第6章はここまで 〜
ARKit 3(iOS 13)から、人物のオクルージョンが可能になりました。オクルージョンとは、手前にある物体が背後にある物体を隠して見えないようにする状態のことです。
たとえば下図の左の画像のようにカメラに映っている状態で、ARKitでワールドトラッキングを行い、検出したテーブルの平面に仮想オブジェクトを設置するとします。すると、従来は下図の右の画像のように描画されていました。
手前の人物より奥にあるテーブルの平面に設置されているはずの物体が、手前の人物の上に描画されているので、違和感があります。
これを、前後関係を考慮して次のように描画するのが「オクルージョン」です。
本機能はiOS 13以上、A12以降のデバイスで利用可能です。人体の全体、または一部だけでも、複数人が映っていても動作します[5]。またAppleによると屋内環境で最も理想的に動作するとのことです[6]。
〜 第7章はここまで 〜
これまでデプス(深度)は、デュアルカメラやTrueDepthカメラ等、ハイエンドなiOSデバイスに搭載されるカメラで撮影した場合にのみ取得できるものでした。
しかしWWDC19のタイミングで、Appleが公式に配布するCore MLモデルに「FCRN-DepthPrediction」と呼ばれるデプス推定モデルが追加され、デプス撮影機能をもたないカメラで撮影した静止画や動画からも(機械学習で推定した)デプスデータを取得できるようになりました。
〜 第8章はここまで 〜