ARKit2.0で簡単に出来る画像認識

# はじめに
個人的にはARというと、マーカを置くとカメラ越しに何かしらのオブジェクトが見えるようになるんでしょ。といった残念な認識しかなかったのですが、昨今の製品やARKitのデモを見てみると、こんなことまで出来るのか！と驚きました。
今回は特にARKitの**画像認識**にフォーカスし、ARKitを使えば画像認識が簡単に出来るということを説明できればと思います。

# 画像認識のためのコーディング
### 1. プロジェクトの作成
XCodeから`Augmented Reality App`を作成して下さい。なお筆者の環境は

 * macOS 10.13.6
 * XCode 10.0
 * iOS 12.0(iPhoneXS)

です。

### 2. 画像の用意と設定
認識したい画像ファイルを用意し、`Assets.xcassets`の`AR Resources`に目的の画像を追加します。<img width="1063" alt="スクリーンショット 2018-11-28 17.43.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/274974/4c96cd9b-aff9-ea8e-2d04-660e6feaa9eb.png">
画像には名前と現実世界での大きさを設定する必要があります。[cm]や[m]の他、[Inches][Feet][Yards]が選択できますが、あまりに小さい画像は認識対象外のようで、1辺が1インチ以上無いと警告が出ます。(1インチ≒2.5[cm])


### 3. コーディング

```ViewController.swift
import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {
    @IBOutlet var sceneView: ARSCNView!
    //AR Resourcesに目的の画像が埋め込まれている
    let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: Bundle.main)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //ARSCNViewDelegateを受け取れるようにする
        sceneView.delegate = self
        let scene = SCNScene()
        sceneView.scene = scene
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        //ARImageTrackingConfigurationに目的の画像を設定
        let configuration = ARImageTrackingConfiguration()
        configuration.trackingImages = referenceImages!
        sceneView.session.run(configuration)
    }
    
    // ARSCNViewDelegate
    
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        let node = SCNNode()
        if let imageAnchor = anchor as? ARImageAnchor {
            // 目的の画像を青い面をかぶせる
            let plane = SCNPlane(width: imageAnchor.referenceImage.physicalSize.width, height: imageAnchor.referenceImage.physicalSize.height)
            plane.firstMaterial?.diffuse.contents = UIColor.blue
            let planeNode = SCNNode(geometry: plane)
            planeNode.eulerAngles.x = -.pi / 2
            node.addChildNode(planeNode)
        }
        return node
    }
}
```
コード量は非常に少ないと思います。

まず、`viewDidLoad`にて`ARSCNView`が発行する`ARSCNViewDelegate`のイベントをこの`ViewController`で受け取れるようにします。こうすることで画像認識の結果である`anchor`を`renderer`関数から受け取れるようになります。

次に、`viewWillAppear`で画像認識の設定を行います。`ARSCNView`のセッションを走らせる際に、`ARImageTrackingConfiguration`を指定し、目的の画像を与えます。今回の肝です。

最後に`renderer`にて画像認識結果に対する処理を記述します。今回は認識された画像に対して青い四角形でマスクするというシンプルなものにしています。

### 4.動かす
AR機能はmacでは動かないので、ARKit対応のiPhoneなどに書き込んで動作確認する必要があります。

![lena.gif](https://qiita-image-store.s3.amazonaws.com/0/274974/76130072-89f3-ee92-e91f-0a4b03b77021.gif)

成功です :tada:
数ある女性の画像の中から、目的の画像を見事に見つけているようです。

# 少し解説
単純なパターンマッチングならこの程度のコード量でできてしまいますが、少し注意すべき点があります。

### 1. 画像の選定
最初は机にあったお菓子の箱を認識させようと思ったのですが、XCodeに読み込ませたあたりで警告 :warning: が表示されました

<img width="669" alt="スクリーンショット 2018-11-16 17.44.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/274974/95240eef-306a-0cf7-0002-7e66aa2218c0.png">

1
> **AR reference image "caloriemate"**: The histogram of the image is narrow or not well distributed. Recognition works better on image with wider, flatter histograms.


2
> **AR reference image "caloriemate"**: The resolution of the image is low. Both width and height should be at least 480 pixels.

2は単に画像を480pixel以上にしてくれと言っているようですが、問題は1です。
画像処理に知見のある人であればピンとくるのでしょうが、histogramって言われても一体どうすれば。。という感じです。検索すると、stack overflowで以下の投稿が見つかりました。

[AR Reference image - The histogram of the image is narrow](https://stackoverflow.com/questions/50886821/ar-reference-image-the-histogram-of-the-image-is-narrow)

![aj580.jpg](https://i.stack.imgur.com/aj580.jpg)

> The image on the right has multiple problems, but shares the "uniform color regions" and "narrow histogram" issues you have. For a human, that image is pretty easy to recognize, because it uses our ability to identify solid shapes on empty backgrounds — that ability apparently isn't a strength of the computer vision algorithm in play here, though. Instead, you're looking for lots of detail — 

正に知りたかった情報でした。要は一様な(シンプルな)画像はコンピュータビジョンでの識別には適さないので、特徴の多いカラフルな画像を使ってくれとのことのようです。
お菓子の画像は黄色を基調としているので、特徴が少なかったのが良くなかったようです。

そのためか、最初の女性の画像と比べるとお菓子の画像検出は、カメラを動かすと比較的ロストしやすくなっているようでした。(左:女性の画像、右:お菓子の画像)

![lena2.gif](https://qiita-image-store.s3.amazonaws.com/0/274974/9cc48470-fdd5-e9df-1962-01296a54f107.gif) ![caloriemate.gif](https://qiita-image-store.s3.amazonaws.com/0/274974/971c1016-c6fd-2fd8-bd44-1a1f10163f10.gif)

画像の条件をまとめておきましょう。

 * 実寸が縦横1インチ(約2.5cm)以上である
 * 解像度が縦横480ピクセル以上である
 * 特徴のある画像である(これはXCodeに置いて調べないとわからなそう)

### 2. `ARWorldTrackingConfiguration`との違い
先ほど利用した`ARImageTrackingConfiguration`はARKit2.0で追加された機能ですが、ARKit1.5から既に`ARWorldTrackingConfiguration`の`detectionImages`に設定した画像を認識可能だったようです。
こちらの記事([ARKit 2.0の画像トラッキングとARKit 1.5の画像検出の違い](https://qiita.com/shu223/items/f01b4dc00ceb915b0e61))に詳しく書かれており、以下のように記述されています。
> `ARWorldTrackingConfiguration`は、現実世界を検出するコンフィギュレーションです。現実世界の面（Surface）を検出し、そこにある画像やオブジェクトも検出します。
> 一方で`ARImageTrackingConfiguration`は、既知の2D画像だけをトラッキングします。その周囲の面は検出しません。

以下のように解釈しました。
[ARKitのための3D数学](https://qiita.com/k-boy/items/775633fe3fd6da9c5fb6)より画像をお借りして説明すると。
<img width="540" src="https://camo.qiitausercontent.com/10b32b38323e9fe3069e57b841065a650eeae668/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3131313336362f65663765366166632d316636352d653334632d336635642d3464383937333930373338322e706e67">

**World**と言うだけあって`ARWorldTrackingConfiguration`では<font color="Blue">ワールド座標系</font>との同定も行い、画像や世界(を構成する平面など)やカメラ(=iPhone)の位置も計算しているので、<font color="Blue">ワールド座標系</font>での画像とカメラの位置と姿勢が取得できる。

一方で、`ARImageTrackingConfiguration`では~~現実世界との同定は行わず、おそらく<font color="Green">カメラ座標系</font>での画像の位置と姿勢を計算しているので、<font color="Blue">ワールド座標系</font>での画像の位置と姿勢は取得できない。(カメラを一点に固定すれば別か)~~
[k-boyさんのご指摘](https://qiita.com/naninunenoy/items/288a7b9900bcdff40ebc#comment-aec197306089d6424c00)によると、カメラの位置調整に差異あれど出力されているのは<font color="Blue">ワールド座標系</font>とのことでした。

その分、計算量の違いからか`ARWorldTrackingConfiguration`と比べて`ARImageTrackingConfiguration`の方が画像の追跡がスムーズと言う利点があるようです。
(左: `ARWorldTrackingConfiguration`、右:`ARImageTrackingConfiguration`)

![ARworld.gif](https://qiita-image-store.s3.amazonaws.com/0/274974/e80b5a6b-8195-32dd-5d30-1bde520bfb30.gif)![ARimage.gif](https://qiita-image-store.s3.amazonaws.com/0/274974/e30f4aa0-b0fb-93f8-c3a4-c64baea7b638.gif)

右の`ARImageTrackingConfiguration`ではcamera position(`sceneView.pointOfView?.worldPosition`)が常に(0,0,0)になっていますね。

* コード全文: [ARKitImageTrackingExample/ViewController.swift](https://github.com/naninunenoy/ARKitImageTrackingExample/blob/master/ARKitImageTrackingExample/ViewController.swift)
  * 距離を測ってみたかったので、ボタンを2度押した際の画像の位置の直線距離を計算する仕様になっています

ARKitには一口に画像検出と言っても色々な機能や設定があるので、ARKitで何がどこまでできるかをしっかりと見極め、自分の作りたいアプリの仕様と照らし合わせて開発する必要がありそうですね。

# おわりに
ARKitは現在2.0がリリースされており、

 * 平面認識(床/壁)
 * 画像認識
 * 顔認識(目線/舌)
 * 3D物体認識

などの機能が提供されています。カメラが高性能になりiPhoneの価格が上がる一方で、今までのスマホでは出来なかった画像処理が可能になっていると言えそうです。このまま進めば人体のボーン検出や文字認識までやってくれるのではないかと勝手に期待しています。