その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

技術書でご飯は食べられるのか? #技術書典

昨日開催された技術書典5にて、新刊「実践ARKit」と既刊「Metal入門」という2冊の技術書を販売してきました。これまで商業出版も含め何度か技術書を書いてきて、「技術書に儲けを期待してはいけない」1と思い込んできましたが、昨日一日の売り上げは約46万円(詳細は後述)。もしかしたら技術書を書くこと自体でそれなりに稼げる時代が来つつあるのかもしれない・・・と考えを改めました。

f:id:shu223:20181010015119j:plain

(技術書典5。自分のブースからの光景)

自分の書籍の中身やイベントがめちゃくちゃ楽しかったという話は本記事ではいったん置いておいて、そんな「お金」の面について具体的な数字、気付きなどを書いておきたいと思います。

1日の売り上げ詳細

実践ARKitMetal入門、どちらも製本版+電子版のセットで2000円で販売しました。どっちが何冊売れたかはちゃんと数えてないのですが、だいたい3:1ぐらいでARKitのほうが多かった感じです。

で、現地で約36万円売れて、Web(BOOTH)でも同日に10万円程度を売り上げました2

印刷代は合計で11.5万円ぐらいで、BOOTHは手数料が3.6%、ダウンロードコード発行代が約1万円(コンカ)。粗利だけで33万円という感じです。

この金額の意味するところ

とはいえ技術書典での1日と同じ水準で売れ続けるわけはなく、これからは電子版を中心にポツポツ売れるだけなので、実際のところ「ご飯を食べるタネ」としてはまだまだ心もとないかもしれません。

しかし、この金額は僕にとっては非常に大きな意味を持ちます。

というのも、いままでにちゃんと「本屋に並ぶ技術書」も書かせていただきましたが(いわゆる商業出版)、印税率は8%で、共著だとそこから折半なので、たとえば4000円の本を買ってもらって1冊あたり160円。初版2000部のぶんはもらえるので32万円。これが何ヶ月かフルタイムで執筆してのトータルの収益3だったのです。

また別の話ですが、僕はGitHubでいろいろとOSSを公開していて、合計で約23,000スターを獲得しています。もちろんスター数が絶対的な価値を持つわけではありませんが、しかしソースコードを公開してエンジニアコミュニティに多少なりとも貢献をしているとはいえると思います。

そんなGitHubpaypal.meのお布施リンクを置いてみてはいますが、いまのところトータル収益はゼロ円です。

それとはまた別に、Qiitaでは21,000コントリビューション以上ありますが、もちろんここからの収益はありません。

それとカンファレンスでの登壇は、(略)

つまり何がいいたいかというと、いままで技術情報の発信というのは基本的に無報酬、お金とは違うところに意味を見出しつつ、稼ぎはそれ自体ではなく別のところで得るしかなかった、ということです。

それが、今回の技術書典5では、技術情報発信(ここでは書籍)自体でそれなりの稼ぎがあったので、これはエポックメイキングだなと。

というわけで個人的にすごく楽しくなってきたので、今後の動き方を考えるためにも、今回の技術書典5を振り返って(売り上げの観点から)「やってよかった」と思うことを書いておきたいと思います。

やってよかったこと

前日からBOOTHで販売開始

今回の実践ARKitは、技術書典の前日にはBOOTHで電子書籍版の販売を開始し、告知しました。

あと、当日会場での「開封の儀」をツイートしたりと、タイムラインからでも会場の温度感が伝わるようにしました。

東京の池袋で1日だけ開催された技術書典。非常に盛り上がったとはいえ、東京以外にお住まいの方々にとってはこのためだけに来るというのはハードルが高いと思います。

  • 遠方の多くの人は技術書典に来れない
  • しかし注目はしている=熱気は届いている

というわけで、タイムライン見てて、なんかツイートとか流れてきて「いいな〜買いたいな〜」と思ったそのときにネットで買えるなら買ってしまうのではないでしょうか。「電子版は後日用意します」は思っている以上に大きな機会損失かもしれません。

印刷代の割増を恐れずギリギリまで書く

どんな時間管理術や仕事術を弄したところで、結局一番捗るのは締切間際、というのが現実かと思います。締切間際のあの集中力と決断力はプライスレスです。

ギリギリまで粘ると印刷代の割増料金がかかるといっても1日で5%程度、元の印刷代を仮に40,000円とすると、たかだか2,000円です。たったの2,000円で締切間際の集中力ブーストがもう1日分買えるなら買うしかない、と判断しまして、入稿を10%割り増し(2日延長)の10/4(技術書典は10/8開催)に行いました。この2日の作業で本のクオリティは50%はアップしたと思います。

安売りしない

前回の「Metal入門」は、技術書典初参加で、まったく空気感もわからず、「自分のとこだけ売れなかったら寂しいなー」と日和って1,200円という価格設定にしました。しかしこのMetal入門いまでも日本語では唯一のMetalのまとまった情報源で、非常にニッチな本なので、いくら安かろうがいらない人はいらないし、欲しい人は高くても買うという類のものです。なんせ代わりがないので。数は出ないけど高くても売れる、ニッチというのは弱みでもあり強みでもあるのです。

今回の新刊のテーマであるARKitはもうちょいメジャーで、同フレームワークを扱った書籍は商業・同人共にいくつか出てますが、しかしネイティブでのARKit開発(Unityではなく)を扱っていて、著者にしっかりとしたiOSのバックグラウンドがあり、体系立てて構成も練られている、となるとやはり商業出版含め代わりはないかなと思っています。

そんなわけで、「製本版+電子版とのセットで2000円」と、技術書典としては少し高めの水準の価格設定にしました4。結果的にはちゃんと売れて、合計としても前回の2倍という売り上げになったので、これでよかったのかなと思います。

売れ残りを恐れず刷る

「完売の誘惑」というのはあって、やっぱり早々に売り切って「完売しました!」とツイートして、他のブースまわったりしたい、というのはあります。

また普通の小売業にとって在庫を抱えるというのはリスクを伴うことだと思います。単純にうちの場合、余った本の箱を家に送ろうものなら奥さんに間違いなく迷惑がられます。

しかし今回、技術書典会場に【BOOTH倉庫に無料発送できる窓口】という窓口が設置され、

もはや売れ残りを恐れる理由はなくなりました。在庫が多すぎると保管料が1000円かかるとのことですが、送る物品のサイズ・重量・管理コストを考えれば、気にするべくもないめちゃくちゃリーズナブルな価格だと思います。

というわけで僕は当初の予定より1.5倍ほど多めに刷りました。もはや売れ残りすら楽しみになって、早くBOOTHの在庫に反映されないかなーと心待ちにしています。

おわりに

「やってよかったこと」を書いてみましたが、当たり前すぎますかね。。

とにかく、「技術の発信」でも収入が得られる時代が到来しつつあることが嬉しいです。お客さんの課題に対して技術力をふるう「案件」も好きですが、その仕込みフェーズ自体でも稼げるとなると、より自由に生きるための選択肢が増えたような、そんなワクワク感があります。

技術書典6で書きたいネタも山程あります。引き続き勉強と発信を続けていきたい所存です。


  1. 本屋に自分の本が並ぶというのは親にも親戚にも誇らしかったし、エンジニアとしての箔はついたと思うし、仕事に繋がった面もあるし、海外でも物理的な本は自慢になったし、もろもろメリット・デメリット比較して書きたいと思ったから書いたわけで、実際書いてよかったと今も思います。

  2. 新刊は前日からBOOTHという販売サイトにて電子版の販売を開始しました。

  3. 書いた本はどちらも2, 3回増刷されましたが、なんやかんやの規約があってなかなか増刷分は入ってこなかったりします

  4. BOOTHでの通常販売はもうちょい高めになっています。

ARKitの解説書を個人出版しました #技術書典 #技術書典5

ARKitの解説書「実践ARKit」を本日よりBOOTHにて販売開始しました!ARKit 2.0 (iOS 12)・Xcode 10・Swift 4.2対応、全119ページ(製本版は92ページ)。サンプルコードはGitHubよりダウンロード可能です。

shu223.booth.pm

なお、明日開催される技術書典5では製本版+電子版セットで2000円で販売する予定です。だいぶお得です。サークル名は「堤and北」、配置は「う65」です。

techbookfest.org

書籍の紹介

本書は、作りながら学ぶネイティブARKitアプリケーション開発の実践入門書です。

はじめの一歩として3行で書ける最小実装のARから始めて、平面を検出する方法、その平面に仮想オブジェクトを設置する方法、そしてその仮想オブジェクトとインタラクションできるようにする方法・・・と、読み進めるにつれて「作りながら」引き出しが増えていき、最終的にはARKitを用いたメジャーや、空間に絵や文字を描くといった、ARKitならではのアプリケーションの実装ができるよう構成しています。

全92ページ。ARKit 2.0 (iOS 12), Xcode 10, Swift 4.2対応。サンプルコードはGitHubよりダウンロード可能です。

f:id:shu223:20181007153454p:plain

目次

第1章 入門編その1 - 最小実装で体験してみる

  • 1.1 手順1:プロジェクトの準備
  • 1.2 手順2:ViewControllerの実装
  • 1.3 基本クラスの解説

第2章 入門編その2 - 水平面を検出する

  • 2.1 水平面を検出するためのコンフィギュレーション
  • 2.2 平面検出に関するイベントをフックする - ARSessionDelegate
  • 2.3 平面検出に関するイベントをフックする - ARSCNViewDelegate
  • 2.4 検出した平面を可視化する

第3章 入門編その3 - 検出した水平面に仮想オブジェクトを置く

  • 3.1 3Dモデルを読み込む
  • 3.2 仮想オブジェクトとして検出した平面に置く

第4章 ARKit 開発に必須の機能

  • 4.1 トラッキング状態を監視する
  • 4.2 デバッグオプションを利用する
  • 4.3 トラッキング状態をリセットする / 検出済みアンカーを削除する

第5章 平面検出の基礎

  • 5.1 垂直平面の検出
  • 5.2 検出した平面のアラインメントを判別する
  • 5.3 平面ジオメトリの取得
  • ARPlaneGeometry と ARSCNPlaneGeometry

第6章 AR 空間におけるインタラクションを実現する

  • 6.1 ヒットテスト(当たり判定)を行う
  • 6.2 デバイスの移動に対するインタラクション

第7章 AR 体験の永続化と共有

  • 7.1 ARWorldMap
  • 7.2 ワールドマップを取得する
  • 7.3 ワールドマップを永続化・共有する
  • 7.4 ワールドマップからセッションを復元する
  • 7.5 ワールドマップ取得タイミング

第8章 フェイストラッキング

  • 8.1 フェイストラッキングを開始する - ARFaceTrackingConfiguration
  • 8.2 検出した顔のアンカーを取得する - ARFaceAnchor
  • 8.3 顔の動きを可視化する
  • ブレンドシェイプでアニ文字風3Dアバター

第9章 特徴点を利用する

  • 9.1 特徴点を可視化する
  • 9.2 フレームに含まれる特徴点群データ
  • 9.3 ワールドマップに含まれる特徴点群データ

第10章 デプスを利用する

  • 10.1 ARFrameからデプスデータを取得する
  • 10.2 デプスを取得可能なコンフィギュレーション

第11章 ビデオフォーマット

  • 11.1 ARConfiguration.VideoFormat
  • 11.2 ビデオフォーマットを指定する
  • 11.3 使用可能なビデオフォーマット
  • 現行デバイスで使用可能なビデオフォーマット一覧
  • ビデオフォーマットはどう使い分けるのか?

第12章 アプリケーション実装例1: 現実空間の長さを測る

  • 12.1 ARKitにおける座標と現実のスケール
  • 12.2 現実空間における二点間の距離

第13章 アプリケーション実装例2: 空中に絵や文字を描く

  • 13.1 スクリーンの中心座標をワールド座標に変換する
  • 13.2 頂点座標の配列から、線としてのカスタムジオメトリを構成する
  • 13.3 その他の実装のポイント

第14章 アプリケーション実装例3: Core ML + Vision + ARKit

  • 14.1 CoreML・Vision・ARKit連携のポイント

第 15 章 Metal + ARKit

  • 15.1 その1-マテリアルをMetalで描画する
  • 15.2 その2-Metalによるカスタムレンダリング

対象読者

「実践入門書」を謳っている通り、実践を主眼に据えつつあくまでARKit入門者向けです。すでに現場でゴリゴリに触ってる人には物足りないかもしれません。また本書はネイティブiOSアプリ開発の話で、Unityを使ったARKitアプリの開発については書いてないです。

あと、全面的にリライトしたりARKit 2.0対応したりしてますが、「iOS 11 Progamming」で執筆を担当したARKitの章を土台としてますので、そちらを既にご購入の方には重複する部分が多くなってしまうかと思います。目次をよくご確認いただき、ご検討ください。

表紙の話

もともとあった表紙の構想は、ARKitにとって象徴的なシンボルである(と僕は思っている)「あの」飛行機の3Dモデル"ship.scn"を「ちょっと浮いた感じで真ん中あたりにプリントする」(+タイトル)というシンプルなものでした。

f:id:shu223:20181007153656p:plain

(ship.scn。XcodeのARKitテンプレートに同梱されている)

が、そういう特殊加工をするには部数が足りない(しかもかなり高い)ことがわかり断念。

で、次になんとなく思いついたのが、ship.scnを手書きスケッチ風にしてオライ◯ー的表紙として仕上げることでした。

何度かバージョンアップして、今に至ります。

f:id:shu223:20181007162610p:plain

(左から、Beta 1、Beta 2、RC1)

f:id:shu223:20181007154222j:plain

(入稿版)

「Metal入門」製本版も復活します

技術書典4では完売御礼だった「Metal入門」も第2刷として製本版を販売します。マイナーアップデートでSwift 4.2, iOS 12, Xcode 10対応しております。こちらも技術書典のみ製本版+電子版セットで2000円です。興味あればぜひともこの機会に。

f:id:shu223:20180422101932j:plain

(製本版はマットな表紙とほどほどな厚みが手に馴染む、最高な仕上がりとなっております)

電子版はBOOTHでも販売しております。

shu223.booth.pm

こちらの記事に評判をまとめています。

関連記事

shu223.hatenablog.com

shu223.hatenablog.com

デプス(深度)をiOSで扱う方法を網羅したサンプルコード集「iOS-Depth-Sampler」を公開しました

iOSにおけるデプス(深度)関連APIの実装方法を示すサンプル集「iOS-Depth-Sampler」をオープンソースで公開しました。

github.com

ソースコードGitHub に置いてあるので、ご自由にご活用ください。Swift 4.2です。

今のところ6つのサンプル(後述)が入っています。本記事のタイトル「網羅した」は少し大げさですが、撮影済みの写真からデプスを取得する方法から、リアルタイムに取得する方法、ARKitで取得する方法、フロント/リアカメラ、Disparity / Depth / Portrait Matteといったタイプと相互変換、デプスを利用した背景合成や3D空間へのマッピングといったサンプルも備えています。

f:id:shu223:20180918074109j:plain

(2次元の写真をデプスを利用して3D空間へマッピング

今後iOSにデプス関連の機能が追加されるたびに、サンプルも追加していく予定です。

利用可能なデバイス

デュアルカメラもしくはTrueDepthカメラを持つデバイスを使用してください。 7 Plus, 8 Plus, X, XS, XRをお持ちの方はぜひビルドして遊んでみてください。リア(Dual Camera)とフロント(TrueDepth Camera)でそれぞれサポートしている機能が全然違うので、X or XSが理想です。

利用方法

git cloneしてXcode 10でビルドしてiOS 12端末 1 にインストール

以上です。Metalを使用しているのでシミュレータでは動作しない点、ご注意ください。

サンプル一覧

Real-time Depth

リアルタイムにカメラから深度情報を取得し、可視化するサンプルです。

f:id:shu223:20180913012136g:plain

Real-time Dpeth Mask

リアルタイムにカメラから取得した深度情報をマスクとして使用し、背景合成を行うサンプルです。

f:id:shu223:20180913012157g:plain

Depth from Camera Roll

カメラロールにある撮影済みの写真から深度情報を取り出し、可視化するサンプルです。標準のカメラアプリのポートレートモードで撮影した写真を利用できます。

f:id:shu223:20180913012537j:plain

Portrait Matte

Portrait Matte (Portrait Effect Matte)を使用し、背景を除去するサンプルです。ポートレートモードで「人間を撮影」した写真を利用できます。要iOS 12。

f:id:shu223:20180913012548g:plain

カメラはフロント・リアどちらもOKです。

shu223.hatenablog.com

ARKit Depth

ARKitで深度情報を取得し、可視化するサンプルです。

f:id:shu223:20180913012600g:plain

現状、ARKitではARFaceTrackingConfiguration使用時のみ(つまりフェイストラッキングARのときだけ)この方法で(※後で貼るスライド内で説明しています)深度情報を取得できます。

2D image in 3D space

写真(2D)を深度をz値として3D空間にマッピングするサンプルです。

f:id:shu223:20180915110312g:plain

AR occlusion

[WIP] An occlusion sample on ARKit using depth.

そもそもデプスって?

「デプスって何?」「何が嬉しいの?」「どういう仕組みで計測してるの?」といったところは拙スライドもご参照ください。

speakerdeck.com

デモやアニメーションを交えているので、動画の方がおすすめです。

www.youtube.com

付録: iOSで深度データにアクセスする方法のまとめ(書きかけ)

Qiitaにでも書こうとまとめてたのですが、途中になってるので、ここに一旦おいておきます。

PHAssetから深度マップ画像を生成

requestContentEditingInput(with: nil) { (contentEditingInput, info) in
    guard let url = contentEditingInput?.fullSizeImageURL else { fatalError() }
    let depthImage = CIImage(contentsOf: url, options: [CIImageOption.auxiliaryDisparity : true])!
    ...
}

深度データを持つPHAssetだけを取得する

let resultCollections = PHAssetCollection.fetchAssetCollections(
    with: .smartAlbum,
    subtype: .smartAlbumDepthEffect,
    options: nil)
var assets: [PHAsset] = []
resultCollections.enumerateObjects({ collection, index, stop in
    let result = PHAsset.fetchAssets(in: collection, options: nil)
    result.enumerateObjects({ asset, index, stop in
        assets.append(asset)
    })
})

PHAssetが持つ深度データからAVDepthData生成

アセットのURLを指定して、CGImageSourceを作成する。

guard let source = CGImageSourceCreateWithURL(url as CFURL, nil) else { fatalError() }

CGImageSourceCopyAuxiliaryDataInfoAtIndexを使用する

let info = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDisparity) as? [String : AnyObject]
let info = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDepth) as? [String : AnyObject]

ここで得られる辞書型データは次のような情報を持っている。

  • the depth data (CFDataRef) - (kCGImageAuxiliaryDataInfoData),
  • the depth data description (CFDictionary) - (kCGImageAuxiliaryDataInfoDataDescription)
  • metadata (CGImageMetadataRef) - (kCGImageAuxiliaryDataInfoMetadata)

この辞書型データをそのままAVDepthDataのイニシャライザに渡せる。

let depthData = try! AVDepthData(fromDictionaryRepresentation: info)

CGImageSourceCopyPropertiesから深度に関する情報を取得する

以下のように辿って深度に関する情報を得ることもできる。

guard let sourceProperties = CGImageSourceCopyProperties(source, nil) as? [String: AnyObject] else { fatalError() }
guard let fileContentsProperties = sourceProperties[String(kCGImagePropertyFileContentsDictionary)] as? [String : AnyObject] else { fatalError() }
guard let images = fileContentsProperties[String(kCGImagePropertyImages)] as? [AnyObject] else { return nil }
for imageProperties in images {
    guard let auxiliaryDataProperties = imageProperties[String(kCGImagePropertyAuxiliaryData)] as? [[String : AnyObject]] else { continue }
    ...
}

こういう情報が取れる。

["Width": 576, "AuxiliaryDataType": kCGImageAuxiliaryDataTypeDisparity, "Height": 768]

とはいえ深度マップのデータそのものは入っていない。

AVCaptureSynchronizedDataCollectionからデータを取り出す

AVCaptureDataOutputSynchronizerDelegateのdidOutputメソッドの第2引数からAVCaptureSynchronizedDataCollectionオブジェクトが得られる。

func dataOutputSynchronizer(_ synchronizer: AVCaptureDataOutputSynchronizer, didOutput synchronizedDataCollection: AVCaptureSynchronizedDataCollection) {

    if let syncedDepthData = synchronizedDataCollection.synchronizedData(for: depthDataOutput) as? AVCaptureSynchronizedDepthData, !syncedDepthData.depthDataWasDropped {
        let depthData = syncedDepthData.depthData
        ...
    }
    
    if let syncedVideoData = synchronizedDataCollection.synchronizedData(for: videoDataOutput) as? AVCaptureSynchronizedSampleBufferData, !syncedVideoData.sampleBufferWasDropped {
        let videoSampleBuffer = syncedVideoData.sampleBuffer
        ...
    }
}

iOS 12のPortrait Matte

let info = CGImageSourceCopyAuxiliaryDataInfoAtIndex(self, 0, kCGImageAuxiliaryDataTypePortraitEffectsMatte) as? [String : AnyObject]
let matte = try! AVPortraitEffectsMatte(fromDictionaryRepresentation: info)

  1. ほとんどのデプスAPIiOS 11から使えるのですが、インパクトのある「Portrait Matte」がiOS 12〜なので、本サンプルはiOS 12以上にしてしまいました。

iOSDCで「深度(デプス)」について話しました #iosdc

8/30〜9/2の4日間、900人規模で開催されたカンファレンスiOSDC 2018にて、「Depth in Depth」というタイトルで、iOSにおける深度データにまつわる諸々について30分の枠でお話させていただきました。

speakerdeck.com

タイトルの"in Depth"というのはWWDCのセッションや海外技術書等ではアドバンスト的な技術解説でよく用いられるタイトルですが、本セッションの難度としてはどちらかというと「デプス入門」の方が適切です。深度とは何か、どう嬉しいのか、どういうしくみで計測されてるのか、といった基礎から解説しておりますので深度についてまったく知らない方にも参考になるかと思います。1

またトーク内ではiOSAPI面に関してはサラッと流し、深度の取り扱いについて本質的・汎用的な部分の解説や、デモに時間を割くようにつとめました。iOSAPIの細かい話についてはトークよりもブログ記事の方が向いてると思うので、また別記事にでも書いていきます。

(2018.9.27追記)動画

iOSDC公式チャンネルに動画が公開されていました。

www.youtube.com

デモとかアニメーションを多用しているのでスライドよりも動画の方がおすすめです。

デモ

トーク内では全4回のデモを入れました。最初の人物切り抜きのデモに関しては@niwatako氏が動画をツイートしてくれていたので、貼らせていただきます。

客席に座っていた人に壇上に出てもらい、その場で撮影を行い、深度による切り抜きを行う、というデモです。壇上に上がってもらったのは仲のいいPEAKS永野さんですが、仕込みではなく、本当に彼を撮影し、切り抜きを試したのはこのときが初です。2

その他セッション中のツイートはこちらにまとめられています:

togetter.com

サンプルコード

ひとつのアプリケーションとして整理して公開する予定です。が、後述する理由もありなかなか時間がとれていません。なんとかiOS 12が出るまでには。。

自分にプレッシャーをかける意味でティザーリポジトリだけつくっておきました。

github.com

ここにpushする予定なので気になるみなさまはwatchしておいてください。公開後も、iOSにおける深度データ取り扱いに関するサンプルはここに追加していく予定です。

(2018.9.18追記)サンプル公開しました!

shu223.hatenablog.com

iOSDCの思い出

子供が生まれました!

なんと登壇日の朝、娘が生まれました

f:id:shu223:20180901090822j:plain

登壇の前夜、臨月の妻から破水の連絡がきて、病院に直行しました。助産師さんによると時間がかかりそうとのことだったのでもしかすると発表は辞退するかも3、というタイミングだったのですが、徹夜のお産ののち(僕は手を握っていただけ)朝9時に生まれ、もろもろ見届けたのち無事午後イチの登壇にも間に合ったという。さっそく親孝行な子です👶

今週は仕事を基本的には休ませてもらって(緊急のものはリモートで対応)、妻とてんやわんやしながら初の子育てに励んでいます。

ベストトーク賞3位!

なんと、「Depth in Depth」で、ベストトーク賞3位をいただきました。

f:id:shu223:20180906090232j:plain

1位tarunonさんの「MicroViewControllerで無限にスケールするiOS開発」、2位Kuniwakさんの 「iOS アプリの開発速度を70%高速化したデバッグノウハウ」という、超評判になっていたトークに続いての順位なので、まったくもって信じられない気持ちでした。

僕は勉強会やカンファレンスには多く登壇していますが、トーク力に関してはまったく自信がなく4、こういうプレゼンでの賞をもらうのは初めてなのでめちゃくちゃ嬉しかったです。

全体の感想

日本のiOSエンジニアが集まるお祭りとして最高なiOSDCですが、今年は前述のような状況もあってセッションもあまり聞きに行けず、懇親会にも出られず、さらにその後の打ち上げでもゆっくりできず、という感じでした。が、それでも少しでも参加して「お祭り」の空気を吸えたし、発表ドリブンで深度まわりにも勉強できたし、出産と重なったけど無事登壇できたし、賞までいただけて、今年もiOSDCは楽しかったなと。来年もたぶん生活は落ち着いてはないですが、どうにか参加したい所存です。

素晴らしいカンファレンスを開催してくださった運営チームのみなさま、スポンサーのみなさま、参加者のみなさま、スピーカーのみなさま、発表練習やスライド作成のもくもく会につきあってくれたみなさま、どうもありがとうございました!


  1. CfPの段階では実際に"In Depth"な内容まで踏み込むつもりでいて、Metalを用いて2次元の写真を3次元にマッピングするところまで掘り下げる予定でスライド作成を進めていました。が、30分という時間の中でそこまで掘り下げると全体的に中途半端になりそうなので泣く泣くカットしました。

  2. iOSDCでは動画を撮っているので、壇上に出てこれそうで、動画に残っても大丈夫そうな人に出てもらいました。

  3. 大会委員長の長谷川さんにはその旨を連絡していました。

  4. なので、「トーク下手がしゃべってもおもしろくなる」ように内容を何度も推敲して登壇に臨むようにしてます。

ARKit 2.0の画像トラッキングとARKit 1.5の画像検出の違い

ARKit 2.0では、「画像トラッキング」という新機能が加わりました。既知の2次元画像を検出・トラッキングできるので、ポスター等の現実世界に存在する2D画像を基盤にしてAR体験を開始することができるようになる、というものです。これを聞いて「あれ、その機能、既になかったっけ」と思った方もいたのではないでしょうか。

その既視感はある意味では正しく、ARKit 1.5で既に「画像検出」という機能が追加されています。

では、これらはどう違うのでしょうか?ARKit 1.5の画像検出機能を強化したものが2.0の画像トラッキングで、リプレースされたのでしょうか。それとも別々の機能として共存しているのでしょうか。

結論としては後者で、それぞれ強みを持つ別々の機能として共存しています。

コンフィギュレーションの違い

まず実装面でみると、ARKit 1.5で搭載された画像検出は、検出対象とするリファレンス画像をARKit 1.0から存在するコンフィギュレーションであるARWorldTrackingConfiguration(平面検出を行うのもこれ)の検出対象の一種として指定することで実装します。

let configuration = ARWorldTrackingConfiguration()
configuration.detectionImages = [referenceImage]

一方ARKit 2.0で追加された画像トラッキングは、新たなコンフィギュレーションARImageTrackingConfigurationを使用します。

let configuration = ARImageTrackingConfiguration()
configuration.trackingImages = [referenceImage]

どちらの実装もリファレンス画像のセットをコンフィギュレーションのプロパティに渡すところは共通で、コンフィギュレーションだけが違っています。

f:id:shu223:20180825223341p:plain

実は、ここに両者の違いが大きく表れています。

ARWorldTrackingConfigurationは、現実世界を検出するコンフィギュレーションです。現実世界の面(Surface)を検出し、そこにある画像やオブジェクトも検出します。

一方でARImageTrackingConfigurationは、既知の2D画像だけをトラッキングします。その周囲の面は検出しません。

パフォーマンスの違い

上のコンフィギュレーションの違いにより、パフォーマンスに大きな差が出てきます。ARWorldTrackingConfigurationはカメラに映る世界の面を全て見ているのでパフォーマンスコストが高く、対象画像は静止している必要があります。

一方でARImageTrackingConfigurationは対象画像しか見ないので60fpsでその画像の位置と向きを推定します。そして対象画像が移動しても追従します。

また、同じ理由から、一度に復数の対象画像を検出する場合も、ARImageTrackingConfigurationの方がよりより多くの画像をより確実に処理できます。

実際に同じデバイスで両コンフィギュレーションによる画像検出/トラッキングを試してみると(NDA期間中につきスクリーンキャプチャ動画は載せられませんが)、次のような結果になりました。

  • ARWorldTrackingConfiguration
    • trackingStatenormalになるまでが遅い
    • 検出した画像の位置・向きの更新が遅い
  • ARImageTrackingConfiguration
    • 一瞬でtrackingStatenormalになる
    • 検出した画像の位置・向きを毎フレーム正確に追従する

それぞれのユースケース

画像への追従性、パフォーマンス面だけで見ると画像トラッキングの方が圧倒的に利点がありそうですが、こちらはワールド自体は見ていないので、たとえばその対象画像がカメラの視野から外れた場合に、その画像に仮想コンテンツを固定し続けることができません。一方で(ARWorldTrackingConfiguration)の画像検出では、対象画像が見えなくなった後でもワールド空間におけるそのコンテンツの位置を追跡し続けることができます。

画像検出は、美術館の絵画や映画のポスターのような静的画像にARコンテンツを連携させるようなケースに適しているとされ、また画像トラッキングは、卓上でのカードゲームやボードゲームのように移動する物体上の画像に対してARコンテンツを連携させるようなケースに適しているとされています。1

iOS 12のPortrait Matteがすごい/ #iOSDC 2018で登壇します

iOS 12の新機能"Portrait Matte"(ポートレート・マット)の概要と、実装方法を紹介します。1

f:id:shu223:20180822200133p:plain:w393

深度マップとセグメンテーション

昨今のiPhoneではデュアルカメラ(iPhone 7Plus, 8 Plus, Xの背面に搭載)、あるいはTrueDepthカメラ(iPhone Xの前面に搭載)から深度マップを作成し、奥行きを知ることができるようになっています。

深度マップは、

  • AR表現における回り込み(オクルージョン)
  • モノや人物の背景を差し替える 2

といった用途に用いられます。

どちらの例も要は人物やモノの「領域」を検出して分割する(セグメンテーション)ところがキーで、深度マップはそのセグメンテーションにおけるマスクとして有用なわけです。

f:id:shu223:20180822190535p:plain:w600

(撮影した画像(左)と深度マップ(右))

iOS 12の新機能 "Portrait Matte"

深度マップ関連APIiOS 11から追加されたわけですが、iOS 12では新たに"Portrait Matte"なる新機能が追加されました。"Portrait Effect Matte"とも呼ばれます。

f:id:shu223:20180822200059p:plain:w600

(従来の深度マップ(左)とPortrait Matte(右))

聞き慣れない用語ですが、たぶんApple独自用語です。WWDC18の"Creating Photo and Video Effects Using Depth"から、「Portrait Matteとはなにか」という説明をしている部分を引用してみます。

so what is a portrait matte? A portrait matte is a segmentation from foreground to background and what this means precisely is that you have a mask which is 1.

0 in the background and you get soft and continuous values in between.

つまり、背景と前景の分離に用いるセグメンテーションに特化したフォーマットで、

とスッパリ分かれており、輪郭部分の髪の毛のような詳細もその間の連続値で表現される、というもののようです。

f:id:shu223:20180822200133p:plain:w393

True Depthな前面カメラからだけではなく、背面カメラからも取得できるようです。

It is available for both the front and the rear facing camera.

ただし、静止画のみ(動画では取得不可)かつ人間が写っている場合だけ取得可能です。

It is available to you with portrait still images and at the moment only when there are people in the scene.

Portrait Matteの取得方法

Portrait Matteの取得方法は従来の深度データ(AVDepthData)の取得方法と非常に似ています。

CGImageSourceを作成したら、CGImageSourceCopyAuxiliaryDataInfoAtIndexkCGImageAuxiliaryDataTypePortraitEffectsMatteを指定してAuxiliaryデータを取得すれば、

guard let info = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypePortraitEffectsMatte) as? [String : AnyObject] else { return }

それをそのままAVPortraitEffectsMatteのイニシャライザに渡せます。

let matte = AVPortraitEffectsMatte(fromDictionaryRepresentation: info)

AVPortraitEffectsMatteCVPixelBuffer型のmattingImageプロパティを持っており、そこからセグメンテーション用のマスクとしてCore ImageなりMetalなりで用いることができます。

var mattingImage: CVPixelBuffer { get }

iOSDCでiOSにおける深度データの諸々について話します

"Depth in Depth"というタイトルで、iOSDC 2018で登壇します。

Depth in Depth by 堤 修一 | プロポーザル | iOSDC Japan 2018 - fortee.jp

概要:

原始のiPhoneからカメラは搭載されていましたが、深度センサが搭載されたのは比較的最近のことです。カメラやGPSが、デジタルの世界と我々が生きる現実世界を繋ぐ重要な役割を担い、アプリ開発者に多くの創造性を与えてくれたのと同様に、「奥行き」がわかるようになったというのはアプリ開発の次元がひとつ増えたようなものです。本トークではiOSでの深度の扱いについて、取得方法だけではなく、細かいパラメータの意味やMetalでの処理まで詳解します。

まだ発表内容を遂行しているところですが、発表のゴールとしては次のようなものを考えています。

  • iOSで取得できる深度データにも種類があること、またそれぞれどういうものなのかがわかる
  • 深度データ取得の(ハードウェア的な)しくみがわかる
  • iOSにおける深度データを取得する実装方法がわかる
  • 取得した深度データを使い、背景入れ替えや3Dマッピングといった応用実装ができる

ニッチな内容ですが、iOSの深度の取り扱いについてのまとまった話を日本語で聞けるレアな内容だとも思いますので、興味のある方はぜひ聞きに来てください!(上のリンクから⭐ボタンを押しておくとタイムテーブルに反映されます。たぶん)


  1. 本記事はNDAに配慮し、Xcode 10やiOS 12のスクショは使わず、公開情報のみで構成しています。

  2. グリーンバックのように彩度をキーとして背景合成を行う技術はクロマキー合成と呼ばれる

オランダの小さな島で開催されたカンファレンス「Swift Island」に参加した話 #swiftisland2018

今月の初旬、「Swift Island 2018」というSwiftおよびiOSをテーマとするカンファレンスに参加してきました。テッセル島というオランダにある小さな島が会場で、

f:id:shu223:20180723144557j:plain:w500

  • カンファレンス会場にバンガローがあり、基本的に全員がそこに泊まる
    • 部屋は別々
    • チケット代に宿代・食事代も含まれている
  • よくある「スピーカーの話を聴く」聴講型ではなく、「全員が手を動かす」ワークショップ型

というフォーマットで、今までに参加したどのカンファレンスとも違った良さがありました。

ワークショップ主体の少人数制・合宿型カンファレンス

冒頭にもちらっと書きましたが、カンファレンス名に"Island"とあり、一見「島での開催」が本カンファレンス最大の特徴っぽい感じがしますが、実際に参加してみての感想としては、

  • チケット販売枚数は最大50(ソールドアウトだったので、つまり参加者は50人)
  • すべてのセッションが参加者10人ちょっとの少人数ワークショップ形式で、全員が手を動かす

という点が本カンファレンス最大の個性かつ特長であると感じました。日本のSwift/iOSカンファレンスであるtry! SwiftやiOSDCは800人とかの規模であることを考えると、50人というのは非常にコンパクトです。トータル3日も一緒にいれば、全員の顔ぐらいはなんとなく覚えられそうなレベル。

そして、午前1コマ、午後2コマの1日3コマ × 2日間のワークショップがあり、同じ1コマの時間帯に4つのワークショップが平行開催されていて、1つあたりのワークショップの参加者は12〜13人ほど。全員が手を動かします。

そして斬新だったのは同じワークショップは繰り返し開催される点で、このおかげで人数的にあぶれたり、参加したいワークショップがかぶったりしてても別のコマで参加できるので、基本的には出たいワークショップにはすべて参加することができました。

こういうワークショップベースのカンファレンスは今まで経験したことなかったのですが、やはり聴いてなんとなくわかって気になるのと、自分で実際に一度書いてみるのとでは全然「自分のものにした感」が違う、と感じました。

「(都市から多少隔離された)島での合宿型」という形式も、50人というグループの結束が高まる/非日常感によりいつもと違う分野で手を動かすぞという気持ちを盛り上げてくれる、といった相乗効果があったと思います。

どのレイヤで手を動かすのか

参加者全員が手を動かすといっても、その方法、レベル感は講師によって様々で、例を挙げると、

  • Swiftの新機能

    • iOSのプロジェクトと、テストが用意されている
    • そのままだとテストがコケる
    • そのテストが通るように、iOS側のコードを書いていく
    • その際、Swiftの新機能Mirrorとかを使う
  • 機械学習

    • Turi Createを使ったモデルをトレーニングするコードがJupyter Notebook形式で用意されている
    • それを順次実行して学習を回してみる(ここで各ステップについてちょっと解説が入る)
    • できたCoreMLモデルをiOSに組み込む
  • Siri Shortcut

    • 前で講師が解説 → さぁみんなもやってみよう
    • を手頃なチャンクごとに繰り返す
    • 書籍みたいなテキストが用意されていて、ついていけなければ復習もできる
  • Network, Natural Language

    • フレームワーク群を使うiOSアプリケーションのスケルトンプロジェクトみたいなものが用意されている
    • 作業順とヒントが示されていて、各自で実装していく(例: Network FrameworkでUDPによるソケット通信をやるためのクラスとメソッドの定義だけ用意されていて、その中身を実装していく)

・・・と千差万別でした。どの講師の方のサンプル/教材も「どうやったらこのテーマをうまく学んでもらえるか」を考えて推敲してあるなぁと感心しました。

f:id:shu223:20180704161814j:plain:w500

(Siri Shortcutのワークショップ)

f:id:shu223:20180707041636j:plain:w500

機械学習のワークショップ)

参加者同士のコミュニケーション

全員が基本的にはワークショップ開始前日に到着するのですが、その「前夜」にもう既に参加者同士の輪が出来始めていて、自分はそこに飛び込めず、「やばい、このままずっとぼっちで過ごすのか・・・」と一時不安にもなりましたが、朝・昼・晩と食事がついていて、大きなティピーテント内にいくつか置かれたテーブルで基本的には相席になるので、そこで絶対に話す機会はあるし、

f:id:shu223:20180704124146j:plain:w600

ワークショップでも隣同士で話したり、Pub Quizといったチームで挑むイベントがあったりで、コミュニケーションのきっかけに困ることはありませんでした。

f:id:shu223:20180704220302j:plain:w600

ちなみに他のヨーロッパの国々から来てる人はいたものの、飛行機で10時間以上かかるところからわざわざ来たという人はいなくて、ヨーロッパ外からの参加は僕だけだったかもしれません。そのあたりで珍しがってもらえたというのもコミュニケーションの一助になった気がします。

テッセル島

会場のテッセル島は首都アムステルダムからものすごく遠いというわけではないのですが、電車と船と予約制バスを乗り継いで行くので、なんだかんだで半日かかります。

人より羊が多いらしく、とにかく何もありません。

f:id:shu223:20180703154846j:plain:w600

島での滞在中は自転車をレンタルして、朝とか夜とかにいろいろ周りました。灯台を目指してみたり、対岸の海の方に行ったり。

f:id:shu223:20180706094346j:plain:w600

f:id:shu223:20180723122525j:plain:w600

好みがあるとは思いますが、僕はこういうところをのんびりサイクリングするのは大好きだし、車もほとんど走ってないので会話もじっくりできて、最高でした。

アムステルダム

今回テッセルには4泊したのですが、オランダははじめてだったので、その前後でアムステルダムに合計6泊しました。

マリファナが合法、ビザが取りやすい、ぐらいの前知識しかなくて、僕はそれらに興味がなかったので、実はとくにワクワクすることもなく現地に赴いたのですが、行ってみれば移住したいと思ってしまうほどに気に入りました。

  • アムステルダムは「運河が張り巡らされている港町」
    • もともと海が近くて川が流れてる街が好きな自分にはどストライク。
    • 運河をつなぐ橋の袂には大抵カフェがあってみんな屋外でビール飲んでる。

f:id:shu223:20180630125307j:plain:w600

  • オランダ全体が自転車社会

    • 自転車専用のレーンが整備されててあらゆる場所に自転車を止められる
    • アムステルダムも街が大きくなくて自転車で事足りる
    • (ペーパードライバーな自分は車社会のアメリカでは生きづらかった…)
  • 気候も良い

    • 暑すぎない、それでいてちゃんと暑い夏 1
    • 毎日快晴(※これはたまたまそうだったのかもしれない)

まぁ、しばらく滞在しているといくつか好きではない面も見えてきましたが。。いずれ1ヶ月とかアパート借りて執筆やリモートワーク、みたいな感じでもうちょっと長めに過ごせたらいいなと思います。

お金の話

  • チケット代+自転車レンタル+テッセル延泊+テッセル移動・・・合計€1000ぐらい 2
  • 東京 <-> アムステルダム往復航空券・・・$1340 3
  • アムステルダム6泊(3泊 + 1泊 + 2泊。全部Airbnb・・・合計$950ぐらい
    • 後で気付いたが、1泊とか2泊ならアムステルダムの場合はホテルの方がお得。チェックインも楽だし。
  • モバイルWi-Fiルータ、SIM・・・合計20,000円ぐらい
    • 空港でSIM買えたのでWiFiルータいらなかった。というかアムステルダムでもテッセルでもほとんど繋がらなくて、使えなかった。。
    • SIMは快適でした
  • 食費・・・数えてないが、それなりにかかってるはず

ユーロとドルと円が混じってますがざっくり35万〜40万円といった感じでしょうか。目を背けたくなる金額ですが、しかし、海外の知らない土地に行って、新しい人達と出会って・・・というのは自分にとって重要な人生の醍醐味で、ここをケチって何のために稼いでるのか、というところだし、新しい技術を学ぶきっかけというのは(腰が重い自分にとっては特に)いつでも価値あることなので、100%行ってよかったと思えます。4

まとめ

Swift Island 2018について書きました。要点をまとめると、

  • 少人数・ワークショップ主体のカンファレンス形式がとてもよかった
  • アムステルダムいいところだった
  • お金はかかるけど、たまにはこうして海外カンファレンスに参加するのはいいものだ

といったところです。またチャンスがあれば参加したいと思います。

f:id:shu223:20180707040820j:plain

こちらもどうぞ:これまでの海外カンファレンス参加記事

note.mu

d.hatena.ne.jp

d.hatena.ne.jp


  1. サンフランシスコみたいな「涼しい夏」は夏を過ごした感じがしない。。

  2. 海外のカンファレンスは高いのです。というか日本のが安い

  3. 夏の繁忙期なのでちょっと高い

  4. ちなみにオランダ滞在中にリモートワークも少ししたのですが、旅費の半分ぐらいは相殺できていると思います。