Core MLとNeural Engineで自転車を探す

iOSの機械学習フレームワークCore MLと最近のiPhoneなどに備わるAIプロセッサNeural Engineを用いて映画から自転車を探したところ、驚くような結果を得た。プログラミング・コードを簡潔に記述できるとともに、その処理速度が随分と速い。これが毎日使う小さなiPhoneで行えるのだから、唖然としてしまう。Neural EngineはFace IDだけでなく、汎用的なAI処理に活用できるわけだ。

これまでの自転車探索、正確にはオブジェクト検出はディープ・ラーニングのKeras-Yolo3を用いてきた。これをMacで実行すると恐ろしく時間がかかり、2時間の映画なら2〜3日はマシンを占有してしまう。 神谷典孝が示したようにGPUを活用すれば実用的な速度となり、2〜3時間で処理が完了する。ただし、GPUはタワー型の大型PCを必要とし、随分と高価であり、消費電力が馬鹿にならないほど大きい。

それでは以前と同じ2分13秒の映像の処理時間を比較してみよう。以下のグラフの最初の3項目はKeras-Yolo3のCPUでの実行、4番目はKeras-Yolo3のGPUでの実行、残る3つはCore MLのNeural Engineでの実行だ。GPUでの処理は改善の余地があるようだが、それにしても機械学習に最適化されたNeural Engineの優れた処理能力が際立っている。また、A12とA13の処理速度が僅差であることも注目される。

ちなみに、今回のCore MLによるプログラムで使用したモデルはYOLOv3.mlmodelで、Keras-Yolo3と同じdarknetのデータ・セットが元になっている。Appleはサンプル・コードとともに何種類もの特徴的なモデルを提供しており、利用し易いのが有難い。YOLOv3に関しても、精度より処理速度を重視したYOLOv3-Tinyがあり、それぞれデータ・タイプが異なるモデルも用意されている。

実際に利用するには、まず、GitHubのプロジェクト一式をダウンロードする。そして、Appleからダウンロードしたモデルと任意のムービーをプロジェトに登録して実行すれば良い(下図の(1)と(2))。名称が異なるモデルやムービを使用するには、ソースコードでのファイル名の指定を変更する(下図の(3)と(5))。これでiOSデバイスで実行ができる。極めて遅いがiOSシミュレータでも構わない。

プログラム・コードはユーザ・インターフェースやエラー・ハンドリング等を省略して簡略化している。それだけに、主要な処理コードは驚くほど短いことが分かるだろう。画像解析に基づいた処理は27〜33行で、ここでは解析結果をprint文でコンソールに出力している(上図の(4))。ファイル出力や図形描画といった更なる機能が必要であれば、このハンドラ部分に記述することになる。

//
//  ViewController.swift
//  MovieAnalyzer
//
//  Created by aka on 2020/03/13.
//  Copyright © 2020 aka. All rights reserved.
//

import UIKit
import Vision
import AVFoundation

class ViewController: UIViewController {
    private var requests = [VNRequest]()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupVision()
        processMovie()
    }

    func setupVision() {
        // 機械学習モデルを読み込む
        if let modelURL = Bundle.main.url(forResource: "YOLOv3", withExtension: "mlmodelc") {
            let mlModel = try! VNCoreMLModel(for: MLModel(contentsOf: modelURL))
            let mlRequest = VNCoreMLRequest(model: mlModel, completionHandler: { (request, error) in
                // 画像解析結果を処理する
                let results = request.results!
                print("Found \(results.count) objects")
                for item in results {
                    let object = item as! VNRecognizedObjectObservation
                    print("\(object.labels[0].identifier) \(object.confidence) \(object.boundingBox)")
                }
            })
            self.requests = [mlRequest]
        }
    }

    func processMovie() {
        // ムービーを読み込む
        let url = Bundle.main.url(forResource: "test", withExtension:"mov")
        let asset = AVURLAsset(url: url!, options: nil)
        let reader = try! AVAssetReader(asset: asset)
        let videoTrack = asset.tracks(withMediaType: .video).first!
        let outputSettings = [String(kCVPixelBufferPixelFormatTypeKey): NSNumber(value: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)]
        let trackReaderOutput = AVAssetReaderTrackOutput(track: videoTrack,
                                                        outputSettings: outputSettings)
        reader.add(trackReaderOutput)
        reader.startReading()

        // フレームごとに画像を読み込む
        while let sampleBuffer = trackReaderOutput.copyNextSampleBuffer() { // CMSampleBuffer
            if let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { // CVImageBuffer (CVPixelBuffer)
                let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: imageBuffer, options: [:])
                // 画像の処理を実行する
                try! imageRequestHandler.perform(self.requests)
            }
        }
    }
}

【追記】後半のプロジェクトやコードの説明を修正して、プログラムの変更方法を加筆した。(2020年4月9日)

One comment

Leave a Reply

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA