Apple Engine

Apple, iPhone, iOS, その周辺のことについて

ARKit + SceneKit を使用したポジショントラッキングなモバイル VR

ARKit Advent Calendar 2017 | 14日目

ARKit はカメラ画像と各種センサーを使用して端末の位置を特定しているため、 1眼のポジショントラッキングなモバイル VR なら、わりと簡単にができますよというお話。

とはいえ、ARKit は真っ白な部屋とか特徴点が拾えないとトラッキングが大幅にずれるのでできるという程度で。

Unity や Unreal の場合もカメラいじればなんとかなると思う。

 

やり方

起動時に背景に色を塗るか、キューブマップなどの画像で背景をつくるだけ。

 

手順 その1
Xcode から ARKit のテンプレートを開く

f:id:x67x6fx74x6f:20171211140754p:plain

 

手順 その2
ViewController.swift で ARSCNView のシーン設定後、シーンの背景を変更する

f:id:x67x6fx74x6f:20171211140813p:plain

 

黒く塗る場合

sceneView.scene.background.contents = UIColor.black

 

背景画像やキューブマップで背景をつけたい場合
(SceneKit のキューブマップ設定は過去の記事を参照)

sceneView.scene.background.contents = UIImage(named: "ファイル名.png")

 

以下の 動画 では AR からタップで、
Apple のサンプル Fox2 のキューブマップに変更され、 AR から VR へ遷移している。

 

なぜ、背景を変えると VR に変更できるのか?

ARKit はシーン上の背景画像に iSight or iSight Duo カメラ (端末裏のカメラ) を適応しているだけなので、 背景をカメラからの画像ではなくすことで VR することができる。

また、通常 ARKit は SCNCaptureDeviceOutputConsumerSource というものが ARSCNView のシーンの背景に割り当てられているが、 開発者側からはこれを操作することができない。

 

他の使い道

Animoji(アニ文字)などの FaceTraking の際、 背景を映したくない場合などにも使える。

 

背景をなくした FaceTraking のサンプル

f:id:x67x6fx74x6f:20171211141903g:plain

github.com

Apple の Shazam 買収は何をもたらすのか

ポッドキャスト検索のスタートアップ Pop Up Archive を買収したばかりだが、 Shazam の買収が行われるとのこと。

買収額は公表されていないが Tech Crunch によると4億ドルぐらいとのことだ。

Apple confirms Shazam acquisition; Snap and Spotify also expressed interest | TechCrunch

 

Shazam の1日あたりの利用は2000万を超えいるらしく、 2016年時点でストリーミングサービスに毎日100万クリックを誘導している。
同年、10億ダウンロードを達成、初の黒字化し今年上場すると噂されていた。

iOS 8 の際、Apple とパートナーシップを結んでおり、 すでに iOS のバーチャルアシスタントシステム Siri と統合されいる。

 

iTunes Music Store の売上の不振から、音楽の買い切りのを廃止して、 Apple Music のストリーミングでの提供のみにするとの噂が上がっており、 上手く働けばさらなる発展が期待ができそうではある。
(Apple はダウンロード販売終了を否定している)

例えば、Shazam が Apple Music でしか使用できなくなった場合、 ストリーミング音楽配信でトップの座を取る Spotify への流入はわずかだと思われるが減らすことができるだろう。

また The Wall Street Journal によると iTunes Store での音楽全体の売上の 10% を占めているらしく、 何らかのコストが削減できそうではある。

 

Android 8.1 の Google Pixel 2 や Pixel 2 XL ではロック画面に周囲で流れている曲を判別する "Now Playing" の機能が実装されているらしく、 これを鑑みると Android の Shazam アプリの配信をしなくなる可能性もある。
Android の Apple Music アプリへの統合という形になるかもしれない。

 

The Verge の見方では、Zappar 協力して Shazam のアプリでサービスとして提供している AR の機能なのではないかと記事が書かれた。 Google Lens のようなものを実現するのではないかと。

 

個人的には Apple が求めているのは Shazam が保有する特許も1つなのではないかと思っている。

(放送源や音響信号を識別、メディアコンテンツのデータベースを配置や検索、サンプルの識別の曖昧さ回避、 オーディオと対応するテキストの同期とその信頼値の決定、高騒音など音がよくない状況での認識など)

 

それに音声解析に関しては Siri や iOS 内でも使用できそうだし、音声解析に使用している計算など 3DCG や AR にも利用できそうではある。

 

今回の買収に関しては結構面白い形になりそうな予感はしている。

 

 

ぴーえす

shazam という英単語を最初にこのアプリが出た時初めて知ったし、 LANDMARK DIGITAL SERVICES という会社が音声関連の特許をめっちゃ持っているということを知った。
それと、ボーイング社が AR で結構大事な特許を持っているのでびっくり。

iMac Pro は本当に高いのか

いや、まぁ、高いけど、Mac Retina 5K も結構なお値段になるので、 Apple 価格としては妥当なのではというとこと。
まぁ、高いけど。

 

価格に反映されるだろうと思われるスペックだけまとめてみた。

FaceTime カメラの解像度と拡張については省略

iMac Retina 5K CTO iMac Pro
CPU 第7世代 (Kaby Lake) Core i7 プロセッサ / 最大4.5GHz) Xeon W カスタムプロセッサ / 最大4.5GHz
CPU コア数 4 8
メモリ 32GB 2,400MHz DDR4 32GB 2,666MHz DDR4 ECC
GPU Radeon Pro 580 8GB VRAM Radeon Pro Vega 56 8GB HBM2
ストレージ 1TB SSD 1TB SSD
米国価格 $3,699 $4.999
日本価格(税別) 407,800 円 約 550,000 円

その差が、約 142,200 円

 

大体の価格差を考えてみる

ざっくりとした感じで調べてみた。
初期価格なので高めだからあくまで参考程度で。
(海外だと価格差はここまでないかも?)

 

CPU

バルクで買うと iMac 5K の i7-7700K が出始めころは4万中盤。   iMac Pro は Xeon E3-1285 v6 と言われていおり、 Xeon E3-1280 が 8 万近かったので約倍。

 

GPU

Radeon Pro 580 が 4.5 万ぐらい。
Vega 56 が 7万ぐらい。

 

メモリ

iMac 8GB 4枚構成らしいので大体4万円

どういう構成になるのかわからないが、2,666MHz DDR4 ECC 1枚 10万は超える。

 

まとめ

小売価格なので Apple 自体が仕入れている価格はもっと安いとは思われるが、 CPU、GPU、メモリの差分を考えると妥当なのではないかと思われる。

やっぱり、高いけど。

Business Chat (Developer Preview) について

f:id:x67x6fx74x6f:20171206182155p:plain

 

iOS2 Advent Calendar 2017 | 7日目

来年、サービスが開始されると言われているビジネスチャット。
個人的には WWDC 2017 のセッションで、CoreML、Metal 2、ARKit に匹敵するぐらいの機能とサービスではないかと考えており、あまり話題になっていないのでまとめてみる。

ざっくり、ビジネスチャットを説明すると iMessage アプリを使用した企業がお客さんと行うカスタマーサポートで、 Apple 製品で iMessage アプリ経由でのサポートを受けた人なら想像がつくと思う。
Apple 自体が使用しているものを企業や開発者に開放したものではないかと。

 

以下、Business Chat 開発者向けトップページ説明の意訳。

ビジネスチャットは、企業が顧客と直接メッセージでつながる強力かつ新しい方法を提供します。
ビジネスチャットを使用するとお客様は iPhone、iPad、および Apple Watch から、企業への質問に対する回答や問題の解決案を得たり、そこから決済を完了することができ、 お客様は Safari、マップ、Spotlight、および Siri からあなたのビジネスへのコミュニケーションを開始することができます。

 

英語が苦にならないなら、WWDC 2017 のセッションを観た方が分かりやすいし、下の文章を読まなくても済む。

developer.apple.com

 

使用用途

  • リアルやネットの店舗での購入のサポート、iMessage アプリ内での直接購入。
  • ゲームやサービスなどのカスタマーサポート

その他、対話ベースで何かを行うサービス関係で使用できると思われる。

 

Business Chat の流れ

  • 顧客はこのサービスを開始するため、企業へのアドレスを QR コードで取得する
  • 顧客から質問などメッセージを企業側に送る
  • 企業側からは Web サイトの管理画面から顧客に対して何らかのリアクションを送る

 

企業側ができるリアクション

  • テキストメッセージによる返信
  • 画像、動画、音声、ファイルなど iMessage が扱えるデータを表示する
  • 時間を指定するタイムピッカーからの項目選択(場所の提示も可)
  • 商品アイテムや提案などリストピッカーからの項目選択
  • Apple Pay による支払いを促す
  • iMessage 用アプリの起動リンク(インストールされていない場合は iMessage の App Store が起動)

 

試してみる

Business Chat Sandbox という Business Chat を試すサイトがあるのでアクセスしてみる。
多分、開発者のアカウントでなくてもアクセスできると思われる。

注意点としては iCloud の使用がオンになっているアカウントが必要なところ。

 

Business Chat Sandbox ページの起動

アクセスすると QR コードが表示されるので、顧客側と想定される iOS 11 の端末のカメラから QR コードを取得。
(QR コードから返信先が一発で表示されない場合があるので、その場合はもう一度トライ)

f:id:x67x6fx74x6f:20171206182359p:plain

 

iMessage が起動し Business Chat Sandbox への宛先が表示されるので、 何かメッセージを送ってみる。

f:id:x67x6fx74x6f:20171206182839p:plain

 

送信が完了すると Business Chat Sandbox で返信を行うことができる画面が表示される。

f:id:x67x6fx74x6f:20171206182900p:plain

 

 

Business Chat Sandbox ページから顧客と想定される端末にメッセージを送る

Business Chat Sandbox ページの初期状態だと「Text」になっているので、 入力部分に何かを書いて下にある「Send」を押すと顧客側の端末にメッセージが送信される。

f:id:x67x6fx74x6f:20171206183028p:plain

f:id:x67x6fx74x6f:20171206183205p:plain

 

また、右下 Message Contents (JSON) をクリックするとそこに送信する内容の JSON が表示され、 こちらが Apple 側のサーバーに送られ処理されている模様。
テキストの内容を変更するとリアルタイムに中身が変わる。

こちらをコピーして、「Raw JSON」の入力部分にペーストすると、返信するメッセージを定型化できる。

 

Business Chat からカスタムのアプリを呼び出す

以下から Business Chat の iMessage Extension のサンプルファイルをダウンロード

https://developer.apple.com/sample-code/wwdc/2017/iMessage-Business-Chat.zip

 

ターゲットのコードサイニングを変更し実機で実行。

エクステンションなので PackageDeliveryMessagesExtension の方も変更が必要なので注意。

README.md に書かれているように BCSandbox_payload.json で書かれている2箇所の <team-identifier> を自分の ID に変更して json 保存。

Business Chat Sandbox の「My App」にドラッグ&ドロップし「Send」を押すか、
内容を「Raw JSON」の入力部分にペーストし「Send」を押して送信。

顧客側の端末にはアプリの起動を促すメッセージが出る。

 

f:id:x67x6fx74x6f:20171206183110p:plain

起動を促すメッセージ

 

f:id:x67x6fx74x6f:20171206183358p:plain

タップ後の表示

 

f:id:x67x6fx74x6f:20171206183907p:plain

「CONFIRM」タップ後

 

 

「Text」「My App」「Raw JSON」以外の項目

Business Chat Sandbox では特に害はないので色々試してみると良いと思われる。

f:id:x67x6fx74x6f:20171206185131p:plain

 

f:id:x67x6fx74x6f:20171206185152p:plain

 

f:id:x67x6fx74x6f:20171206185212p:plain

 

f:id:x67x6fx74x6f:20171206185234p:plain

 

  

Business Chat を削除 / 通知の一時停止

iMessage アプリのメッセージ一覧で左にスワイプする。

f:id:x67x6fx74x6f:20171206190409p:plain

 

本番に向けての企業登録

こちらから可能。

https://register.apple.com/business/

 

まとめ

直接顧客とのやりとりから購入まで iMessage で行うことができ、 非常に強力だと思われる。
Apple Pay Cash などが対応されれば、商品やアプリ課金等などの返金対応などもできそうだし。

また、カスタマーサポートでの顧客との意思疎通の向上によって より良い体験をもたらすのではないかと期待している。

 

参照リンク

開発者ページトップ

Business Chat - Apple Developer

 

WWDC 2017 のセッション

https://developer.apple.com/videos/play/wwdc2017/240/

 

Business Chat の iMessage Extension のサンプルファイル

https://developer.apple.com/sample-code/wwdc/2017/iMessage-Business-Chat.zip

 

Business Chat の仕様

BusinessChat | Apple Developer Documentation

 

Business Chat Sandbox (要 iCloud アカウント)

https://icloud.developer.apple.com/businesschat/

 

Business Chat の企業登録

https://register.apple.com/business/

 

SceneKit の SCNNode のレンダリングオーダーでどこでもドア的表現をする

そう言ってみれば説明していなかったなとということで、SCNNode の Rendering Order について書いておこうと思う。

マテリアルの Writes depth や Reads depth は深度情報の重なりの順序を無視し前面や背面にジオメトリ表示するが、
こちらはノードのレンダーされる順番が変更される。
初期値は 0 で、数値が大きいものほど後にレンダリングされる。

Rendering Order は Photoshop や Illustrator のレイヤー、PowerPoint や Keynote の重ね順の様なもので、
例えば、AR の表現でよくあるどこでもドアの様な外からは見えないが、扉の様なものをくぐると別世界に行ける的なことができる。

  f:id:x67x6fx74x6f:20171128172559g:plain

 

 

どこでもドア試してみる

今回は、内側の壁とそれを隠す外側の壁、中にキャラを置いている。

 

f:id:x67x6fx74x6f:20171128172654p:plain

 

外側の壁が邪魔なので、外側の壁を選択し Material Inspector( Command + Option + 5)で Transparency を 0.001 に変更して限りなく透明にする。 (完全に透明にするとレンダリングの影響を受けなくなるため)

 

f:id:x67x6fx74x6f:20171128172822p:plain

 

すけすけになってしまうため、中で表示するノードを選択し Node Inspector( Command + Option + 3)の Rendering Order を 1 にする。

 

f:id:x67x6fx74x6f:20171128172857p:plain

 

ノードの選択解除と Scene Editor のグリッド表示をオフ(Editor > Display > Hide Grid)にするとわかりやすい。
(Scene Editor のバグで Show Grid が変わらず Hide Grid と表示されない場合あり)

 

f:id:x67x6fx74x6f:20171128172922p:plain

 

内側の壁とキャラクタが、外側のジオメトリを含んだノードのレンダリング後に シーンへレンダリングされるためこの様な形となる。

 

ARKit で平面認識をする必要がなければ、Scene Editor で作成したものをそのまま使うことができるし、 ドア的なジオメトリを置いてアニメーションさせても良いだろう。

 

注意点

ジオメトリを透明にしているだけで、完全にものがないわけではない。
外側の壁である透明な場所にものを置きたいなどの場合は、素直に Metal を使ってマルチパスレンダリングからジオメトリを消すべし。

 

サンプルコード

github.com

ARKit の Face Tracking で顔にマスクをつける for iPhone X - Depth image (深度画像) 取得編

一応、深度画像を取得してみようと思う。

 

深度画像取得の流れ

ARKit の ARFrame から取得できるので、ARSessionDelegate から呼び出し何か View に表示する。

ARFrame の capturedDepthData が持つ depthDataMap が cvImageBuffer を返すので、 今回は CIImage > UIImage > UIImageView へ変換している。

 

コードを書いてみる

前回のプロジェクトの Storyboard かコードで UIImageView を作成して(今回は depthImageView と命名) ViewController クラスの中に以下のコードを書いていく。

/// Depth Image の取得
func session(_ session: ARSession, didUpdate frame: ARFrame) {
    // 15 fps で返り、それ以外は nil を返すため判別する
    guard let depth = frame.capturedDepthData?.depthDataMap else { return }
    
    // CIImage で取得し傾きを変更する
    let ciImage = CIImage.init(cvImageBuffer: depth)
    depthImageView.image = UIImage.init(ciImage:
        ciImage.oriented(CGImagePropertyOrientation(rawValue: 6)!)
    )
}

 

まとめ

使い道は置いといて、かなり簡単に取得できる。

 

今回のサンプルコード

github.com

 

ARKit の Face Tracking で顔にマスクをつける for iPhone X - 実践編

前回は概要の説明だったが、今回は Xcode を使用しアプリを作成していく。

Apple 公式のサンプル「Creating Face-Based AR Experiences」のコードをスリムにし機能を絞ったものなので、 英語やコードを読むことが苦にならないのならそちらを読んだ方が良い。

ちなみに、カメラを使用しているため実機でしかアプリが動かないので注意。

 

顔にマスクをつけるアプリ作成するわけだが全 Blend Shape をジオメトリに適応するのは面倒なので、 用意されている ARSCNFaceGeometry を使用する。

f:id:x67x6fx74x6f:20171117173401p:plain
ARSCNFaceGeometry で生成されるジオメトリ

こちらは、顔のジオメトリ情報やテクスチャの UV、 Blend Shape が設定されているので、 アンカーとなるノードに設定し、このジオメトリを設定する。

 

Face Tracking アプリの振る舞い

振る舞いに関しては ARKit の平面認識とあまり変わらない。

  • Face Tracking が使用できるかどうか調べる
  • 指で画面操作をしない状態が続くため、画面をロックしないようにする。
  • シーンのジオメトリを削除し、Face Tracking 用の顔のジオメトリをシーンに配置する「リセットの設定」をつくる
  • 画面表示の際 Face Tracking の設定を行い、ARKit のセッション起動
  • 問題なく動作した場合 ARSCNView のデリゲートが顔を検知するので「リセットの設定」からアンカー(ARFaceAnchor)に顔のジオメトリを設置
  • フレーム毎に ARSCNView のデリゲートが呼ばれるため、アンカーにある情報をジオメトリに渡す。
  • バックグラウンドなど画面表示が消える場合はセッションを止めたり、表示が戻った場合に「リセットの設定」を動かしたりして表示の管理をする。

 

あとは、セッションのエラー情報表示や、おかしくなった場合にリセットなど元に戻す処理など必要であれば設定する。
今回はエラーの処理を行わない。

 

以降、Xcode を使用しアプリを作成していく。
(読むのが面倒な場合はサンプルコードへ)

 

Xcode プロジェクト作成と初期設定

Xcode を起動し新規プロジェクト(Command + Shift + N)から、iOS の Single View App を選択し、 適当なプロジェクト名と Laungage を Swift に設定して作成。

f:id:x67x6fx74x6f:20171117173653p:plain

f:id:x67x6fx74x6f:20171117173709p:plain

 

info.plist

アプリでカメラを使用するため ProjectNavigator (Command + 1) から info.plist を選択し、 「Privacy - Camera Usage Description」を追加してカメラ使用に出るアラートの文言を決める。
これを設定しないと起動時に落ちる。

 

Main.storyboard

Main.storyboard を開き、左の Document Outline にある View Controller の下にある View を選択。
Identity Inspector (Command + Option + 3) の一番上にある Class の UIView を ARSCNView に変更。

f:id:x67x6fx74x6f:20171117173749p:plain

 

ViewController.swift の編集

ViewController.swift を開き、以下を設定。

import ARKit

 

ViewController に ARKit 用のデリゲート ARSCNViewDelegate, ARSessionDelegate を追加し、 情報を取得する ARSCNView と faceNode、virtualFaceNode の SCNNode を追加

class ViewController: UIViewController, ARSCNViewDelegate, ARSessionDelegate {
    
    @IBOutlet var sceneView: ARSCNView!
    
    private var faceNode = SCNNode()
    private var virtualFaceNode = SCNNode()

    override func viewDidLoad() {
        ・
        ・
        ・

 

Storyboard の ARSCNView と ViewController.swift をつなぐ

Main.storyboard を開き、Assistant Editor (Command + Shift + Enter) で表示し Storyboard の ARSCNView を右クリック+ドラッグで右側のにある ViewController.swift で先ほどの IBOutlet の sceneView と繋ぐ

f:id:x67x6fx74x6f:20171117173817p:plain

 

これで初期設定は完了。

 

ARKit のカメラを起動する

通常の ARKit 同様に各種設定を行い run を行うだけ。

 

viewDidLoad

viewDidLoad() に以下のものに変更

override func viewDidLoad() {
    super.viewDidLoad()

    // Face Tracking が使えなければ、これ以下の命令を実行を実行しない
    guard ARFaceTrackingConfiguration.isSupported else { return }

    // Face Tracking アプリの場合、画面を触らない状況が続くため画面ロックを止める
    UIApplication.shared.isIdleTimerDisabled = true
    
    // ARSCNView と ARSession のデリゲート、周囲の光の設定
    sceneView.delegate = self
    sceneView.session.delegate = self
    sceneView.automaticallyUpdatesLighting = true
    
    // トラッキングの初期化を実行
    resetTracking()
}

コメント通り、使用できるか調べ、画面ロックを止め、デリゲート等設定して、トラッキング初期化を実行する。

isSupported でそのまま return を返し処理を抜けているので、対応端末以外は画面が黒くなる。 本来はここで対象端末ではない旨を説明するべし。

 

トラッキングの初期化関数をつくる

didReceiveMemoryWarning() {...} の下あたりに以下の関数を作成

// Face Tracking の設定を行い
// オプションにトラッキングのリセットとアンカーを全て削除してセッション開始
func resetTracking() {
    let configuration = ARFaceTrackingConfiguration()
    configuration.isLightEstimationEnabled = true
    
    sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
}

ARFaceTrackingConfiguration で Face Tracking の設定を行い、現実世界の光の取得を許可して、 オプションにトラッキングのリセットとアンカーを全て削除してセッション開始。

 

画面表示 / 非表示の際の設定

先ほどの関数の上に以下のコードを入れる。

// この ViewController が表示された場合にトラッキングの初期化する
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    resetTracking()
}

// この ViewController が非表示になった場合にセッションを止める
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    sceneView.session.pause()
}

非表示になった場合はセッションを止め、表示の際は先ほど作成した resetTracking() を実行しトラッキングを再開する。

 

ARKit の初期設定は完了。
ビルドして iPhone X で実行するとカメラのプレビューが表示される。

 

Face Tracking

Face Tracking の設定を行う。

シリアルキューの設定

後々使うシリアルキューの変数を ViewController クラスで設定する。
private var virtualFaceNode = SCNNode() の下あたりに以下の宣言を書く。

// シリアルキューの設定
private let serialQueue = DispatchQueue(label: "com.test.FaceTracking.serialSceneKitQueue")

 

viewDidLoad で virtualFaceNode に ARSCNFaceGeometry を設定する

viewDidLoad の resetTracking() 上に以下のコードを設定する

// virtualFaceNode に ARSCNFaceGeometry を設定する
let device = sceneView.device!
let maskGeometry = ARSCNFaceGeometry(device: device)!

maskGeometry.firstMaterial?.diffuse.contents = UIColor.lightGray
maskGeometry.firstMaterial?.lightingModel = .physicallyBased

virtualFaceNode.geometry = maskGeometry

 

後ほど設定するデリゲートで参照する virtualFaceNode にマスクのジオメトリである ARSCNFaceGeometry を設定して、 マスクの diffuse をライトグレイ、マテリアルに環境光を適応するためライトモデルを Physically Based にする。

ちなみに、ビルド対象のアクティブスキーマを実機か、Generic OS Device にしないと「let device = sceneView.device!」の箇所でコンパイルエラーになる。
コードを書いている際にビックリマークが気になるならシミュレータ以外を選択すべし。

 

起点となるノードの初期設定

func resetTracking() {...} の下にトラッキング開始時に起点のノードを初期化する関数を書く。

// Face Tracking の起点となるノードの初期設定
private func setupFaceNodeContent() {
    // faceNode 以下のチルドノードを消す
    for child in faceNode.childNodes {
        child.removeFromParentNode()
    }
    
    // マスクのジオメトリの入った virtualFaceNode をノードに追加する
    faceNode.addChildNode(virtualFaceNode)
}

 

ARSCNView にマスクを描画する

ARSCNView のデリゲートからアンカーを取得。マスクのノード設置して画面に描画する。
使うデリゲートは2つ。

  • トラッキング開始時(マスクが追加された時)
  • トラッキング情報が更新された時

 

トラッキング開始時

先ほど書いた func setupFaceNodeContent() {...} の下に、以下のコードを書く。

// ARNodeTracking 開始
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    faceNode = node
    serialQueue.async {
        self.setupFaceNodeContent()
    }
}

トラッキング開始時に渡されるトラッキングの起点となる node が取得でき、 その node を faceNode へ参照渡しすることで、 faceNode の更新が ARSCNView 内に反映される。

コードでは、ノードを受け取り時 serialQueue.async 内で先ほどの setupFaceNodeContent() を呼び、 参照渡しされている faceNode での処理が ARSCNView で適応されている。

この時点で iPhone X にビルドすると、頭の動きはトラッキングされるが表情を反映されない。

 

トラッキング情報更新時

以下のコードを先ほどのコードの下へ書き表情の更新を行う。

// ARNodeTracking 更新。ARSCNFaceGeometry の内容を変更する
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let faceAnchor = anchor as? ARFaceAnchor else { return }

    let geometry = virtualFaceNode.geometry as! ARSCNFaceGeometry
    geometry.update(from: faceAnchor.geometry)
}

デリゲートの更新情報から、表情の Blend Shape を含むアンカーを取得。 virtualFaceNode のジオメトリとして設定した ARSCNFaceGeometry のメソッド update(from: ARSCNFaceGeometry) に取得したアンカーの状態を渡している。

iPhone X にビルドするとマスクへ表情が適応される。

 

ARKit でのそのほかの処理

デリゲートでエラーと中断処理を追加する。 今回はエラーや中断の中身は特に書いていないので、必要であれば何か処理を入れる必要がある。

// エラーと中断処理
func session(_ session: ARSession, didFailWithError error: Error) {
    guard error is ARError else { return }
}

func sessionWasInterrupted(_ session: ARSession) {
}

func sessionInterruptionEnded(_ session: ARSession) {
    DispatchQueue.main.async {
        // 中断復帰後トラッキングを再開させる
        self.resetTracking()
    }
}

 

おまけ1:ホームインジケーターを隠す

以下のコードを書くと、アプリ起動時にホームインジケーターが自動で消える。

override func prefersHomeIndicatorAutoHidden() -> Bool {
    return true
}

 

おまけ2:顔にテクスチャを貼る

ライトグレイのマスクではスケキヨみたいなので、画面タップから UIImagePickerController から画像を選択し、マスクへテクスチャとして反映させます。
以下、設定項目とコード内容

  • info.plist に「Privacy - Photo Library Usage Description」を設定する
  • タッチイベントを設定する
  • UIImagePickerController の設定とデリゲートからマスクにテクスチャを貼る

 

info.plist

フォトライブラリを使用するため info.plist を選択し、 「Privacy - Photo Library Usage Description」を追加してカメラ使用に出るアラートの文言を決める。
これを設定しないと起動時に落ちる。

 

viewDidLoad の修正

viewDidLoad のどこかに以下のコードを書く。

// タップジェスチャ設定を呼び出す
self.addTapGesture()

 

ViewController のエクステンションを書く

ViewController エクステンションなので、 以下のコードを、ViewController の {} の外に書く。

いつも通りの UITapGestureRecognizer 設定と UIImagePickerController の設定。 画像を取得したら virtualFaceNode.geometry?.firstMaterial? に渡している。

extension ViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {

    // タップジェスチャ設定
    func addTapGesture(){
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        sceneView.addGestureRecognizer(tapGesture)
    }
    
    // タップジェスチャ動作時の関数
    @objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
        
        if (UIImagePickerController.isSourceTypeAvailable(.photoLibrary) != false) {
            let picker = UIImagePickerController()
            picker.delegate = self
            picker.sourceType = .photoLibrary
            self.present(picker, animated:true, completion:nil)
        }else{
            print("fail")
        }
    }
    
    // フォトライブラリで画像選択時の処理
    @objc func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        
        // オリジナルサイズの画像を選択
        let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage
        
        // マスクにテクスチャを反映させる
        virtualFaceNode.geometry?.firstMaterial?.diffuse.contents = pickedImage

        // UIImagePickerController を閉じる
        dismiss(animated: true, completion: nil)
    }
    
    // フォトライブラリでキャンセルタップ時の処理
    @objc func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        // UIImagePickerController を閉じる
        dismiss(animated: true, completion: nil)
    }
}

面倒なので、UIImagePickerController を使用しているが画面が隠れるため、 viewDidAppear と viewWillDisappear の処理が走る。
Apple のサンプルでは Popover を使用しているのでこちらを使用するのが良いと思われる。

また、UIImagePickerController 使用の際、Xcode コンソールに 「PlugInKit Code=13 "query cancelled"」というエラーが出るがARKit で画面が覆われると起こるらしい。

Stack Overflow では Bug なのでは? と言われているが詳細不明。

 

まとめ

記事自体は長くなってしまったが、プログラム的には 200 行いかないくらいでかけるので、 ARSCNFaceGeometry を使用するなら、お手軽なのではというところ。

ジオメトリとそのモーフデータをつくるのが面倒だが、ARFaceAnchor から Blend Shape を使用するものもそこまで難しないところがよい。

 

今回のサンプルコード

github.com