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