Swiftでアイマスの画像認識やってみる

# はじめに
Xcodeの`CreateML`が簡単に画像認識をアプリケーションを組み込めるようなので試しにやってみました。

https://developer.apple.com/jp/machine-learning/create-ml/
<img width="758" alt="スクリーンショット 2019-12-02 17.06.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166626/726c1e83-70f2-9064-6ad2-7d4032e90ea1.png">
上のようにやってくれるらしい。

# 作成したもの

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">作った <a href="https://t.co/FuWqPuZsdH">pic.twitter.com/FuWqPuZsdH</a></p>&mdash; 清水 幸佑 (@thimi0412) <a href="https://twitter.com/thimi0412/status/1201424930601988096?ref_src=twsrc%5Etfw">December 2, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


## 環境
- Xcode Version 11.2.1

# 画像データの準備

## 何を分類するのか
りんごとかオレンジとかの分類だとなんかつまらないので、今回は[アイドルマスターシンデレラガールズ カードギャラリー](https://imas.gamedbs.jp/cg/)さんから画像をいただいて、`島村卯月`、`渋谷凛`、`本田未央`を分類してみようと思います。


学習用のデータとテスト用のデータのフォーマットはこんな感じ。
各ディレクトリの名前がラベルになるので`train_data`と`test_data`の名前を間違えないように気をつけましょう。

```
train_data
├── mio_honda
├── rin_shibuya
└── uzuki_shimamura

test_data
├── mio_honda
├── rin_shibuya
└── uzuki_shimamura
```

こんな感じで入ってます。
<img width="605" alt="スクリーンショット 2019-12-02 17.24.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166626/a853c59e-2221-a7e8-eaaa-0626294cbd63.png">



#モデルの作成

## プロジェクトの作成
`Xcode > Open Developer Tool > Create ML`から起動
<img width="643" alt="スクリーンショット 2019-12-02 17.08.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166626/bc52133a-42f6-fa2e-d22e-e5472e4973ee.png">

プロジェクトのフォルダを決定しテンプレートを選択します。今回は画像認識なので`Image Classifier`を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166626/a3466fc0-1594-8958-33c1-ad0d8592e625.png)

## 学習開始
学習用データを`Training Data`に、テスト用データを`Testing Data`にデータを追加しましょう。
追加したら`Train`ボタンを押して完了です。
<img width="1394" alt="スクリーンショット 2019-12-02 17.26.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166626/5586efcb-a43c-5369-361f-9571a084795c.png">

学習が完了したら`Output`にある`.mlmodel`ファイルをドラッグして別の場所に保存しておきましょう。<img width="1393" alt="スクリーンショット 2019-12-02 17.31.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166626/e3709dd8-f713-ef3c-f529-23ae751ba8f6.png">

## アプリケーションに組み込む’
Xcodeから`Create new Xcode project`で`Single View App`でプロジェクトを作成します。私はswiftには疎いのでとりあえず書いたコードを乗っけておきます。
`ViewController.swift`とカメラを使用するので`info.plist`をいじりました。

先ほど作成した`.mlmodel`をプロジェクトに追加してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166626/6c16d768-bde7-9a4c-72c1-f29bcb213a56.png)


`info.plist`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166626/0c90424a-98b7-9688-80a2-2565d11705a2.png)


```swift:ViewController.swift
import UIKit
import AVKit
import CoreML
import Vision

class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {

    @IBOutlet weak var cameraDisplay: UIImageView!
    @IBOutlet weak var resultLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        setUpCamere()
    }

    func setUpCamere() {
        guard let device = AVCaptureDevice.default(for: .video) else { return }
        guard let input = try? AVCaptureDeviceInput(device: device) else { return }
        
        let session = AVCaptureSession()
        session.sessionPreset = .hd4K3840x2160
        
        let previewLayler = AVCaptureVideoPreviewLayer(session: session)
        previewLayler.frame = view.frame
        cameraDisplay.layer.addSublayer(previewLayler)
        
        let output = AVCaptureVideoDataOutput()
        output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "CameraOutput"))
        session.addInput(input)
        session.addOutput(output)
        session.startRunning()
    }
    

    
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        guard let sampleBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
        scanImage(buffer: sampleBuffer)
    }
    
    func scanImage(buffer: CVPixelBuffer) {
        // .mlmodelを読み込ませる
        guard let model = try? VNCoreMLModel(for: IdolClassifier_1().model) else { return }
        let request = VNCoreMLRequest(model: model) { request, _ in
            guard let results = request.results as? [VNClassificationObservation] else { return }
            guard let mostConfidentResult = results.first else { return }
            
            DispatchQueue.main.async {
                if mostConfidentResult.confidence >= 0.9 {
                    let confidenceText = "\n \(Int(mostConfidentResult.confidence * 100))% confidence"
                    switch mostConfidentResult.identifier {
                    case "uzuki_shimamura":
                        self.resultLabel.text = "uzuki_shimamura \(confidenceText)"
                    case "rin_shibuya":
                        self.resultLabel.text = "rin_shibuya\(confidenceText)"
                    case "mio_honda":
                        self.resultLabel.text = "mio_honda\(confidenceText)"
                    
                    default:
                        return
                    }
                } else {
                    self.resultLabel.text = "I don't know"
                }
            }
        }
        let requestHandler = VNImageRequestHandler(cvPixelBuffer: buffer, options: [:])
        do {
            try requestHandler.perform([request])
        } catch {
            print(error)
        }
        
    }
}

```