iOS 11でも開発者向けにも多くの面白い機能が追加された。本稿では、そのうちの「ARKit」「Core ML」について、実際にアプリを作りながら紹介する。
2017年9月19日(米国時間)、iOS 11の正式版がリリースされました。iOS 11では、ユーザー向けだけではなく開発者向けにも多くの面白い機能が追加されています。本稿では、そのうちの「ARKit」「Core ML」について、実際にアプリを作りながら紹介します。
ARKitとはAR(Augmented Reality)アプリの開発をサポートしてくれるフレームワークです。
ARとは現実の映像にオブジェクトや情報を重ねる技術で、ゲームや医療、教育など多くの分野で利用されています。
ARKitでは、それらを実現するための、現実の映像へのオブジェクトの重ね合わせ、水平な面の検出、映像内の距離計算などの機能を提供しています。
今回は下記の要件を満たすアプリを作りながらARKitの概要を紹介します。
ARKitのサンプルコードはこちらからダウンロードできます。
まずは、ARKitのサンプルアプリを動かしてみます。
Xcodeを立ち上げて「Create a New Xcode project」からプロジェクトを作成します。プロジェクトの種類は「Augmented Reality App」を選択します。
アプリ名・チーム名などの情報を入力します。「Content Technology」は「SceneKit」を選びます。
SceneKitはAppleが提供する3Dゲーム開発などに使われるフレームワークです。今回は3Dのオブジェクトを配置するので「SceneKit」を選択します。
プロジェクトを作ったらアプリを起動します。起動すると、下記のように空中に飛行機が浮いているような画面になります。
なお、ARKitはシミュレーターでは動かせないので、実機での起動が必要です。
ARKitでは端末の傾きや移動に応じてオブジェクトも動きます。下記は端末の角度を変えたときのスクリーンショットです。端末を斜めにすることで、飛行機の角度と位置が変化したことが分かると思います。
続けて、先ほど要件を決めたアプリを作ってみます。
まずは、サンプルアプリから不要な処理を取り除きます。「ViewController.swift」を下記のように修正してください。
import UIKit import SceneKit import ARKit class ViewController: UIViewController, ARSCNViewDelegate { @IBOutlet var sceneView: ARSCNView! override func viewDidLoad() { super.viewDidLoad() sceneView.delegate = self sceneView.debugOptions = ARSCNDebugOptions.showFeaturePoints sceneView.scene = SCNScene() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) let configuration = ARWorldTrackingConfiguration() sceneView.session.run(configuration) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) sceneView.session.pause() } }
これで、カメラが立ち上がるだけのアプリになりました。
続けて、床を検知する機能を追加します。「ViewController」を下記のように修正してください。
class ViewController: UIViewController, ARSCNViewDelegate { // 省略 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) let configuration = ARWorldTrackingConfiguration() configuration.planeDetection = .horizontal // 今回追加 sceneView.session.run(configuration) } // 省略 func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { // 水平な面を検知したときに呼ばれる } }
ここでは、「planeDetection」の設定と「renderer」メソッドの定義を行いました。「planeDetection」は検知対象を設定するプロパティーです、今のところ「.horizontal」(水平方向)のみ対応しています。
水平な面を検知した際には、先ほど定義した「renderer」メソッドが呼ばれます。このメソッドはARSCNViewのdelegateメソッドになります。
最後に床の上にオブジェクトを配置する処理を追加します。「ViewController」の「renderer」メソッドを下記のように修正してください。
class ViewController: UIViewController, ARSCNViewDelegate { // 省略 func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { guard let planeAnchor = anchor as? ARPlaneAnchor else { return } let box = SCNBox(width: 0.1, height: 0.1, length: 0.05, chamferRadius: 0.01) let planeNode = SCNNode(geometry: box) planeNode.position = SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z) planeNode.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0) node.addChildNode(planeNode) let material = SCNMaterial() material.diffuse.contents = UIColor(red: 0.85, green: 0.95, blue: 0.85, alpha:1.00) let sideMaterial = SCNMaterial() sideMaterial.diffuse.contents = UIColor(red: 0.55, green: 0.57, blue: 0.55, alpha: 1.00) planeNode.geometry?.materials = [material, sideMaterial] } }
アプリを起動してカメラを床に向けて待つと、オブジェクトが配置されます。
最後にオブジェクトをタップすると回転する処理を実装します。
まずは「UITapGestureRecognizer」を使ってタップイベントを取得できるようにします。「ViewController」を下記のように修正してください。
class ViewController: UIViewController, ARSCNViewDelegate { @IBOutlet var sceneView: ARSCNView! override func viewDidLoad() { // 省略 sceneView.addGestureRecognizer(UITapGestureRecognizer( target: self, action: #selector(self.tapView(sender:)))) } // 省略 @objc func tapView(sender: UITapGestureRecognizer) { } }
タップされたときにオブジェクトが回転する処理を追加します。先ほど定義したtapViewを下記のように修正してください。
class ViewController: UIViewController, ARSCNViewDelegate { // 省略 @objc func tapView(sender: UITapGestureRecognizer) { let tapPoint = sender.location(in: sceneView) let results = sceneView.hitTest(tapPoint, types: .existingPlaneUsingExtent) results.forEach { guard let anchor = $0.anchor else { return } let node = sceneView.node(for: anchor) let action = SCNAction.rotateBy(x: 10, y: 0, z: 0, duration: 2.0) node?.childNodes.first?.runAction(action) } } }
これでタップ時にオブジェクトが回転するようになりました。
タップした場所にオブジェクトがあるかどうかは「hitTest」メソッドで判定しています。このメソッドは、タップした場所と重なる水平な面を返してくれます。この機能を使ってタップした場所にあるオブジェクトを回転させる処理を実装しました。
続いて、Core MLについて見ていきます。
Core MLは機械学習に関するフレームワークで、学習済みのモデルを使った推論を行うための機能を備えています。今までも学習済みモデルを使った推論は可能でしたが、今回追加されたCore MLによって、それが簡単に行えるようになりました。
Core MLは単体で使うことも可能ですが、他フレームワークとの併用もサポートされています。具体的には画像解析には「Vision」、ゲームに利用する場合は「GameplayKit」が使えます。それぞれのフレームワークにはCore MLを使うためのAPIが用意されており、それらを活用することでCore MLをより簡単に扱えます。
今回は画像を使ったアプリを作るため、Visionを利用します。
CoreMLのサンプルコードはこちらからダウンロードできます。
それでは、Core MLを使ったアプリを作っていきます。
今回はCore MLを使って、写真に写っているものを解析するアプリ、具体的には下記のような要件のアプリを作ります。
まずはプロジェクトを作成します。今回は「Single View App」を作成します。
続けて画像選択処理を実装します。作成したプロジェクトの「ViewController.swift」を下記のように修正してください。
import UIKit class ViewController: UIViewController { let imageView = UIImageView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor(red: 0.98, green: 0.98, blue: 0.98, alpha: 1) imageView.backgroundColor = UIColor.gray imageView.frame = CGRect(x: (view.frame.width - 240) / 2, y: 20, width: 240, height: 240) view.addSubview(imageView) let selectBtn = UIButton(frame: CGRect(x: 0, y: view.frame.height - 44, width: view.frame.width, height: 44)) selectBtn.addTarget(self, action: #selector(self.tapSelectBtn), for: .touchUpInside) selectBtn.setTitle("画像選択", for: .normal) selectBtn.setTitleColor(.white, for: .normal) selectBtn.backgroundColor = .darkGray view.addSubview(selectBtn) } @objc func tapSelectBtn() { let c = UIImagePickerController() c.delegate = self present(c, animated: true) } } extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { if let image = info[UIImagePickerControllerOriginalImage] as? UIImage { imageView.image = image picker.dismiss(animated: true, completion: nil) } } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true, completion: nil) } }
これで選択された画像と画像選択ボタンのみのアプリができました。
画像選択ボタンを押すと、下記のように画像選択画面が表示されます。
続けて、選択した画像に何が写っているかを推測する処理を追加します。今回はAppleの用意している学習済みモデルを利用して推測します。
「Download Core ML Model」から「MobileNet.mlmodel」をダウンロードしてください。ダウンロードが完了したら、ドラッグ&ドロップでプロジェクトに追加します。
もし「Target Membership」にチェックが入っていない場合は、チェックを入れるようにしてください。
最後に、推論処理を実装します。「ViewController」を下記のように修正してください。
import UIKit import CoreML // 今回追加 import Vision // 今回追加 class ViewController: UIViewController { let imageView = UIImageView() let resultLabel = UILabel() // 今回追加 override func viewDidLoad() { // 省略 resultLabel.frame = CGRect(x: (view.frame.width - 240) / 2, y: imageView.frame.maxY, width: 240, height: 200) resultLabel.numberOfLines = 0 view.addSubview(resultLabel) } // 省略 } extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { if let image = info[UIImagePickerControllerOriginalImage] as? UIImage, let model = try? VNCoreMLModel(for: MobileNet().model) { imageView.image = image picker.dismiss(animated: true, completion: nil) let request = VNCoreMLRequest(model: model) { request, error in guard let results = request.results as? [VNClassificationObservation] else { return } let resultStrs = results.map { "\($0.identifier): \(NSString(format: "%.2f", $0.confidence))" }.prefix(5) self.resultLabel.text = "● 解析結果\n" + resultStrs.joined(separator: "\n") } request.imageCropAndScaleOption = .centerCrop let handler = VNImageRequestHandler(cgImage: image.cgImage!) try! handler.perform([request]) } } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true, completion: nil) } }
起動して画像を選択すると、画像に写っているものを推論してくれました。何枚か試したところ、高い精度で認識してくれました。
なお、Core MLのサンプルで使った画像は「フリー素材ぱくたそ」のものを利用しています。
本稿では、iOS 11 SDKの新機能を紹介しましたが、いかがでしたでしょうか。本稿が皆さまの開発の一助になりましたら幸いです。
Copyright © ITmedia, Inc. All Rights Reserved.