iOS13とVision.frameworkで犬と猫に癒される

Ubiregi Advent Calendar 2019 17日目です。

今回はiOS13のVision.frameworkで新たに使えるようになった`VNRecognizeAnimalsRequest`を使って動物（🐶と🐱）を識別するアプリのサンプルを紹介します。

## Vision.frameworkとは

![e3663268-5db4-42c9-a7f0-2114920a9f1f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108964/3724285f-b324-af04-180f-b0817afecc37.png)
参照：https://developer.apple.com/documentation/coreml

- iOS11から追加された画像や映像から人や文字など様々なものを識別するAPIを提供するApple標準フレームワーク
- 機械学習フレームワークの`Core ML`を抽象化したもの
- iOS13から新たに🐶と🐱の識別子が追加された🎉🎉🎉

## やってみた

![DogAndCatSample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108964/01378298-e42b-61bd-eb0a-ae5714fdaaae.gif)

私の家には可愛い<img width="50" alt="スクリーンショット 2019-02-01 18.02.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108964/83330096-f275-8d05-4e50-3e45dc76e2a2.png">がいて被写体としてちょうどいいんですが残念ながら🐶を検出できないため、
今回は止む無くネットから拾ってきた画像を使いカメラロールを経由して動物画像を`Vision`に解析してもらい`CollectionViewController`に表示するプログラムを作りました。

遷移先の画面で識別結果を表示しています。`confidence`は信頼を返す値です。
二匹以上いる場合や、🐶と🐱が混じっている場合もそれぞれ認識されていますし、何気なく突っ込んだサングラス🐶や🐱もちゃんと識別されていることに驚きました。
カメラロールには、シュミレータに入っているデフォルトの風景画像以外に、猫に近い豹や虎などを含めてみましたが精度はとても高く結局画像が荒いものは精度は落ちました。


![Simulator Screen Shot - iPhone 11 Pro Max - 2019-12-17 at 02.11.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108964/92abc06d-9666-78e9-7032-7ba56a8c9067.png)


ライオンキングはネコでした。癒されているので問題ありません。

![Simulator Screen Shot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108964/93c4d314-7b72-8643-0312-183a306e4bbf.png)
液体になっているネコもネコでした。癒し。

![Simulator Screen Shot - iPhone 11 Pro Max - 2019-12-17 at 04.08.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108964/21d1b5b6-4116-028b-7ea9-7cf0726bc416.png)
！！！！ブサ可愛い。。

## どうやっているのか
```Swift
    private func searchAnimals(with datas: [Data], completion: @escaping () -> (Void)) {
        let group = DispatchGroup()
        let lock = NSLock()
        var searchIndex = 0
        
        let animalRecognitionRequest = VNRecognizeAnimalsRequest { [weak self] (request, error) in
            DispatchQueue.global(qos: .userInitiated).async {
                defer { lock.unlock() }
                guard let results = request.results as? [VNRecognizedObjectObservation], results.count > 0 else {
                    return
                }
                var detectionString = ""
                var animalCount = 0
                
                for result in results {
                    let animals = result.labels
                    for animal in animals {
                        animalCount = animalCount + 1
                        let animalType = animal.identifier == VNAnimalIdentifier.cat.rawValue ? "😸" : "🐶"
                        let string = "#\(animalCount) \(animal.identifier) \(animalType) confidence is \(animal.confidence)\n"
                        detectionString = detectionString + string
                    }
                }
                guard let self = self else { return }
                self.thumbnails.append(ThumbnailEntity(data: datas[searchIndex], detection: detectionString))
                self.output.insertItem(at: self.thumbnails.count - 1)
            }
        }
        
        let queue = DispatchQueue.global(qos: .userInitiated)
        for (i, data) in datas.enumerated() {
            group.enter()
            queue.async(group: group) {
                defer { group.leave() }
                lock.lock()
                guard let image = UIImage(data: data), let cgImage = image.cgImage else { return }
                searchIndex = i
                let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
                try? requestHandler.perform([animalRecognitionRequest])
            }
        }

        group.notify(queue: .main) {
            completion()
        }
    }
```

### 説明
`VNImageRequestHandler`に対して`CGImage`をセットして`VNRecognizeAnimalsRequest`配列を渡して実行します。
すると`VNRecognizedObjectObservation`配列として結果が返ってきます。
この時に、一枚の画像の中にたくさんの🐶や🐱がいる場合もあるので配列で受け取ります。とても尊い配列です。
あとは🐶か🐱を判定するために、`VNAnimalIdentifier.doc`や`VNAnimalIdentifier.cat`を使えば判定が可能です。
`rawValue`は`Dog`と`Cat`文字列です。
このサンプルコードでは、今後Appleのアップデートで`VNAnimalIdentifier`がたくさん増えると🐱以外は全て🐶になります。逆にしたら良かった。（そうじゃない）
今回画像データをループでまわして見つかり次第`CollectionViewController`に`insert`しているので排他制御を行うためのコードが入っています。普段、UIの更新はメインスレッドでやれば、くらいしかちゃんと気にしていなかったので勉強になりました。


## サンプルコード
https://github.com/isaragi/DogAndCat

今回作ったサンプルはこちらです。ツッコミ所はいろいろあって例えば今回は画像が何百枚もあることを考えていません。
`VIPER`というUIアーキテクチャを採用して作ってみたのでその感想あたりもいろいろ書きたいんですが枠が残っていれば書こうかと思います。


## 最後に

Vision系のML技術はAppleのVision.framework以外にもたくさんあります。
例えば、
・Cloud Vision APIとCloud AutoML Vision
https://cloud.google.com/solutions/image-search-app-with-cloud-vision?hl=ja
・FirebaseのML Kitも画像のラベル付け機能をベータ版リリースしています。
https://firebase.google.com/docs/ml-kit/label-images

これらはGoogleの提供する機能ですが、iPhoneとandroidを比較すると画像に対する機能はiPhoneより一歩進んでいる印象です。
これらを使えばiOSでもandroidでも共通したAPIを利用できる利点がありますが、Appleのframeworkを使えば、サードパーティに頼らずサーバーレスでアプリ内で完結して使えるのも一つの強みだと思います。