最近、Appleは新しい拡張現実(AR)ライブラリという名前のライブラリを発表しました ARKit 。多くの人にとって、それは単なる別の優れたARライブラリのように見え、気にするテクノロジーの混乱ではありませんでした。ただし、過去2年間のARの進捗状況を見ると、そのような結論を出すのに早すぎるべきではありません。
この投稿では、iOSARKitを使用して楽しいARKitサンプルプロジェクトを作成します。ユーザーは、ペンを持っているかのようにテーブルに指を置き、サムネイルをタップして描画を開始します。完了すると、下のアニメーションに示すように、ユーザーは自分の図面を3Dオブジェクトに変換できるようになります。 iOSARKitの例の完全なソースコードが利用可能です GitHubで 。
経験豊富な開発者なら誰でも、ARが古い概念であるという事実をおそらく知っているでしょう。 ARの最初の本格的な開発は、開発者がWebカメラから個々のフレームにアクセスできるようになるまでに特定できます。当時のアプリは通常、顔を変えるために使用されていました。しかし、人類は、顔をウサギに変えることが彼らの最も差し迫ったニーズの1つではないことに気付くのにそれほど時間はかかりませんでした。そして、すぐに誇大広告は消えました!
ARは、使いやすさと没入感という2つの重要なテクノロジーの飛躍を常に見逃していると思います。他のARの誇大宣伝を追跡した場合、これに気付くでしょう。たとえば、開発者がモバイルカメラから個々のフレームにアクセスできるようになると、ARの誇大宣伝が再び始まりました。素晴らしいバニートランスフォーマーの強力な復活に加えて、印刷されたQRコードに3Dオブジェクトをドロップするアプリの波が見られました。しかし、彼らは決して概念として離陸したことはありません。それらは拡張現実ではなく、拡張されたQRコードでした。
それからグーグルは空想科学小説、グーグルグラスで私たちを驚かせた。 2年が経過し、この素晴らしい製品が実現することが期待される頃には、すでに死んでいました!多くの批評家がGoogleGlassの失敗の理由を分析し、社会的側面から製品発売時のGoogleの鈍いアプローチに至るまであらゆることに責任を負わせました。ただし、この記事では、環境への没入という1つの特定の理由で注意を払っています。 Google Glassは使いやすさの問題を解決しましたが、それでも空中にプロットされた2D画像にすぎませんでした。
Microsoft、Facebook、Appleなどの技術者は、この厳しい教訓を心から学びました。 2017年6月、Appleは美しいiOS ARKitライブラリを発表し、イマージョンを最優先事項としました。スマートフォンを持っていることは依然として大きなユーザーエクスペリエンスの妨げになりますが、Google Glassのレッスンでは、ハードウェアは問題ではないことがわかりました。
間もなく新しいARの誇大宣伝のピークに向かっていると思います。この新しい重要なピボットにより、最終的には国内市場を見つけることができ、より多くのARアプリ開発が主流になる可能性があります。これはまた、そこにあるすべての拡張現実アプリ開発会社がAppleのエコシステムとユーザーベースを利用できることを意味します。
しかし、十分な歴史があります。コードで手を汚して、Appleの拡張現実が実際に動作するのを見てみましょう。
ARKitは2つの主要な機能を提供します。 1つは3D空間でのカメラの位置で、もう1つは水平面の検出です。前者を実現するために、ARKitは、お使いの携帯電話が実際の3D空間を移動するカメラであり、任意のポイントに3D仮想オブジェクトをドロップすると実際の3D空間のそのポイントに固定されると想定しています。後者の場合、ARKitはテーブルのような水平面を検出するため、オブジェクトを配置できます。
スタートアップのために資金を調達する方法
では、ARKitはどのようにしてこれを達成するのでしょうか?これは、Visual Inertial Odometry(VIO)と呼ばれる手法によって行われます。心配しないでください。起業家がスタートアップ名の背後にあるソースを見つけたときにあなたが笑う笑いの数に喜びを感じるように、研究者はあなたが思いついた用語を解読しようとしている頭の傷の数に彼らを見つけます彼らの発明に名前を付ける-それでは、彼らが楽しんで、先に進むことを許可しましょう。
VIOは、カメラフレームをモーションセンサーと融合させて、3D空間でのデバイスの位置を追跡する手法です。カメラフレームからの動きの追跡は、特徴、つまり、青い花瓶と白いテーブルの間のエッジのように、コントラストの高い画像内のエッジポイントを検出することによって行われます。これらのポイントが1つのフレームから別のフレームに相互にどれだけ移動したかを検出することにより、デバイスが3D空間のどこにあるかを推定できます。そのため、特徴のない白い壁に面して配置した場合、またはデバイスの動きが非常に速く、画像がぼやける場合、ARKitは正しく機能しません。
この記事の執筆時点では、ARKitはiOS 11の一部であり、まだベータ版です。したがって、開始するには、iPhone6s以降にiOS11ベータ版をダウンロードし、新しいXcodeベータ版をダウンロードする必要があります。から新しいARKitプロジェクトを開始できます 新規>プロジェクト>拡張現実アプリ 。ただし、この拡張現実チュートリアルを公式から始める方が便利だと思いました。 AppleARKitサンプル 、これはいくつかの重要なコードブロックを提供し、平面検出に特に役立ちます。それでは、このサンプルコードから始めて、最初にその要点を説明してから、プロジェクト用に変更してみましょう。
まず、使用するエンジンを決定する必要があります。 ARKitは、SpriteSceneKitまたはMetalで使用できます。 Apple ARKitの例では、Appleが提供する3DエンジンであるiOSSceneKitを使用しています。次に、3Dオブジェクトをレンダリングするビューを設定する必要があります。これは、タイプARSCNView
のビューを追加することによって行われます。
ARSCNView
はSCNView
という名前のSceneKitメインビューのサブクラスですが、いくつかの便利な機能でビューを拡張します。デバイスのカメラからのライブビデオフィードをシーンの背景としてレンダリングし、デバイスがこの世界で移動するカメラであると想定して、SceneKitスペースを現実の世界に自動的に一致させます。
ARSCNView
単独ではAR処理を行いませんが、デバイスのカメラとモーション処理を管理するARセッションオブジェクトが必要です。したがって、開始するには、新しいセッションを割り当てる必要があります。
self.session = ARSession() sceneView.session = session sceneView.delegate = self setupFocusSquare()
上記の最後の行は、ユーザーが平面検出のステータスを視覚的に説明するのに役立つ視覚的なインジケーターを追加します。フォーカススクエアは、ARKitライブラリではなくサンプルコードによって提供されます。これが、このサンプルコードから始めた主な理由の1つです。詳細については、サンプルコードに含まれているreadmeファイルを参照してください。次の画像は、テーブルに投影されたフォーカススクエアを示しています。
次のステップは、ARKitセッションを開始することです。ユーザーを追跡しなくなった場合、以前のセッション情報を使用できなくなるため、ビューが表示されるたびにセッションを再開することは理にかなっています。したがって、viewDidAppearでセッションを開始します。
override func viewDidAppear(_ animated: Bool) { let configuration = ARWorldTrackingSessionConfiguration() configuration.planeDetection = .horizontal session.run(configuration, options: [.resetTracking, .removeExistingAnchors]) }
上記のコードでは、水平面を検出するようにARKitセッション構成を設定することから始めます。この記事を書いている時点で、Appleはこれ以外のオプションを提供していません。しかし、明らかに、それは将来、より複雑なオブジェクトを検出することを示唆しています。次に、セッションの実行を開始し、トラッキングをリセットしていることを確認します。
最後に、カメラの位置、つまり実際のデバイスの向きや位置が変わるたびに、フォーカススクエアを更新する必要があります。これは、SCNViewのレンダラーデリゲート関数で実行できます。この関数は、3Dエンジンの新しいフレームがレンダリングされるたびに呼び出されます。
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { updateFocusSquare() }
この時点で、アプリを実行すると、水平面を検索しているカメラストリーム上にフォーカスの正方形が表示されます。次のセクションでは、平面がどのように検出されるか、およびそれに応じてフォーカススクエアを配置する方法について説明します。
ARKitは、新しい飛行機を検出したり、既存の飛行機を更新したり、削除したりできます。飛行機を便利な方法で処理するために、飛行機の位置情報とフォーカススクエアへの参照を保持するダミーのSceneKitノードを作成します。平面はX方向とZ方向に定義されます。ここで、Yはサーフェスの法線です。つまり、平面に印刷されているように見せたい場合は、描画ノードの位置を常に平面の同じY値内に維持する必要があります。 。
平面の検出は、ARKitが提供するコールバック関数を介して行われます。たとえば、次のコールバック関数は、新しいプレーンが検出されるたびに呼び出されます。
var planes = [ARPlaneAnchor: Plane]() func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { if let planeAnchor = anchor as? ARPlaneAnchor { serialQueue.async { self.addPlane(node: node, anchor: planeAnchor) self.virtualObjectManager.checkIfObjectShouldMoveOntoPlane(anchor: planeAnchor, planeAnchorNode: node) } } } func addPlane(node: SCNNode, anchor: ARPlaneAnchor) { let plane = Plane(anchor) planes[anchor] = plane node.addChildNode(plane) } ... class Plane: SCNNode { var anchor: ARPlaneAnchor var focusSquare: FocusSquare? init(_ anchor: ARPlaneAnchor) { self.anchor = anchor super.init() } ... }
コールバック関数は、anchor
という2つのパラメーターを提供します。およびnode
。 node
は、平面の正確な位置と方向に配置された通常のSceneKitノードです。ジオメトリがないため、見えません。これを使用して独自の平面ノードを追加します。これも非表示ですが、平面の方向と位置に関する情報をanchor
に保持します。
では、位置と向きはARPlaneAnchor
にどのように保存されますか?位置、方向、およびスケールはすべて4x4マトリックスにエンコードされます。私があなたが学ぶために1つの数学の概念を選ぶ機会があれば、それは間違いなく行列になるでしょう。とにかく、この4x4行列を次のように記述することで、これを回避できます。4x4浮動小数点数を含む鮮やかな2次元配列。これらの数値にローカル空間の3D頂点v1を特定の方法で乗算すると、ワールド空間のv1を表す新しい3D頂点v2が生成されます。したがって、ローカル空間でv1 =(1、0、0)であり、ワールド空間でx = 100に配置する場合、v2はワールド空間に関して(101、0、0)に等しくなります。もちろん、軸を中心に回転を追加すると、この背後にある計算はより複雑になりますが、良いニュースは、それを理解しなくても実行できることです(から関連するセクションを確認することを強くお勧めします この優れた記事 この概念の詳細な説明については)。
checkIfObjectShouldMoveOntoPlane
すでにオブジェクトが描画されているかどうかを確認し、これらすべてのオブジェクトのy軸が新しく検出された平面のy軸と一致するかどうかを確認します。
ここで、前のセクションで説明したupdateFocusSquare()
に戻ります。フォーカスを画面の中央に正方形に保ちたいが、最も近い検出された平面に投影したい。以下のコードはこれを示しています。
func updateFocusSquare() { let worldPos = worldPositionFromScreenPosition(screenCenter, self.sceneView) self.focusSquare?.simdPosition = worldPos } func worldPositionFromScreenPosition(_ position: CGPoint, in sceneView: ARSCNView) -> float3? { let planeHitTestResults = sceneView.hitTest(position, types: .existingPlaneUsingExtent) if let result = planeHitTestResults.first { return result.worldTransform.translation } return nil }
sceneView.hitTest
この2Dポイントを最も近い平面の下に投影することにより、画面ビューの2Dポイントに対応する実世界の平面を検索します。 result.worldTransform
は、検出された平面のすべての変換情報を保持する4x4行列であり、result.worldTransform.translation
位置のみを返す便利な関数です。
これで、画面上の2Dポイントを指定して、検出されたサーフェスに3Dオブジェクトをドロップするために必要なすべての情報が得られました。それでは、描き始めましょう。
まず、コンピュータビジョンで人間の指に沿って図形を描くアプローチについて説明しましょう。図形の描画は、移動する指のすべての新しい位置を検出し、その位置に頂点をドロップし、各頂点を前の頂点に接続することによって行われます。頂点は、単純な線で接続することも、スムーズな出力が必要な場合はベジェ曲線で接続することもできます。
簡単にするために、描画には少し単純なアプローチに従います。指の新しい位置ごとに、角が丸く、検出されたプランの高さがほぼゼロの非常に小さなボックスをドロップします。ドットのように見えます。ユーザーが描画を終了して3Dボタンを選択すると、ユーザーの指の動きに基づいて、ドロップされたすべてのオブジェクトの高さが変更されます。
次のコードはPointNode
を示していますポイントを表すクラス:
let POINT_SIZE = CGFloat(0.003) let POINT_HEIGHT = CGFloat(0.00001) class PointNode: SCNNode { static var boxGeo: SCNBox? override init() { super.init() if PointNode.boxGeo == nil { PointNode.boxGeo = SCNBox(width: POINT_SIZE, height: POINT_HEIGHT, length: POINT_SIZE, chamferRadius: 0.001) // Setup the material of the point let material = PointNode.boxGeo!.firstMaterial material?.lightingModel = SCNMaterial.LightingModel.blinn material?.diffuse.contents = UIImage(named: 'wood-diffuse.jpg') material?.normal.contents = UIImage(named: 'wood-normal.png') material?.specular.contents = UIImage(named: 'wood-specular.jpg') } let object = SCNNode(geometry: PointNode.boxGeo!) object.transform = SCNMatrix4MakeTranslation(0.0, Float(POINT_HEIGHT) / 2.0, 0.0) self.addChildNode(object) } . . . }
上記のコードで、y軸に沿ってジオメトリを高さの半分だけ平行移動していることがわかります。これは、オブジェクトの下部が常に次の位置にあることを確認するためです。 y = 0 、平面の上に表示されるようにします。
次に、SceneKitのレンダラーコールバック関数で、同じPointNode
を使用して、ペンの先端のように機能するインジケーターを描画します。クラス。描画が有効になっている場合はその場所にポイントをドロップし、3Dモードが有効になっている場合は描画を3D構造に上げます。
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { updateFocusSquare() // Setup a dot that represents the virtual pen's tippoint if (self.virtualPenTip == nil) { self.virtualPenTip = PointNode(color: UIColor.red) self.sceneView.scene.rootNode.addChildNode(self.virtualPenTip!) } // Draw if let screenCenterInWorld = worldPositionFromScreenPosition(self.screenCenter, self.sceneView) { // Update virtual pen position self.virtualPenTip?.isHidden = false self.virtualPenTip?.simdPosition = screenCenterInWorld // Draw new point if (self.inDrawMode && !self.virtualObjectManager.pointNodeExistAt(pos: screenCenterInWorld)){ let newPoint = PointNode() self.sceneView.scene.rootNode.addChildNode(newPoint) self.virtualObjectManager.loadVirtualObject(newPoint, to: screenCenterInWorld) } // Convert drawing to 3D if (self.in3DMode ) { if self.trackImageInitialOrigin != nil { DispatchQueue.main.async { let newH = 0.4 * (self.trackImageInitialOrigin!.y - screenCenterInWorld.y) / self.sceneView.frame.height self.virtualObjectManager.setNewHeight(newHeight: newH) } } else { self.trackImageInitialOrigin = screenCenterInWorld } } }
virtualObjectManager
描画されたポイントを管理するクラスです。 3Dモードでは、最後の位置との差を推定し、その値ですべてのポイントの高さを増減します。
これまでは、仮想ペンが画面の中央にあると仮定して、検出された表面に描画しています。次に、ユーザーの指を検出して、画面の中央の代わりにそれを使用するという楽しい部分について説明します。
AppleがiOS11で導入したクールなライブラリの1つは、VisionFrameworkです。それは非常に便利で効率的な方法でいくつかのコンピュータビジョン技術を提供します。特に、拡張現実チュートリアルではオブジェクトトラッキング手法を使用します。オブジェクトの追跡は次のように機能します。まず、追跡するオブジェクトの画像と画像境界内の正方形の座標を提供します。その後、いくつかの関数を呼び出して追跡を初期化します。最後に、そのオブジェクトの位置が変更された新しい画像と、前の操作の分析結果をフィードします。それが与えられると、オブジェクトの新しい場所が返されます。
小さなトリックを使用します。ペンを持っているかのようにテーブルに手を置き、サムネイルがカメラの方を向いていることを確認してから、画面上のサムネイルをタップするようにユーザーに依頼します。ここで詳しく説明する必要がある2つのポイントがあります。まず、サムネイルには、白いサムネイル、スキン、およびテーブルの間のコントラストを介してトレースできる十分な固有の機能が必要です。これは、肌の色素が濃いほど、追跡の信頼性が高くなることを意味します。次に、ユーザーがテーブルに手を置いており、テーブルを平面としてすでに検出しているため、サムネイルの位置を2Dビューから3D環境に投影すると、指の位置がほぼ正確になります。テーブル。
次の画像は、Visionライブラリで検出できる特徴点を示しています。
次のように、タップジェスチャでサムネイル追跡を初期化します。
// MARK: Object tracking fileprivate var lastObservation: VNDetectedObjectObservation? var trackImageBoundingBox: CGRect? let trackImageSize = CGFloat(20) @objc private func tapAction(recognizer: UITapGestureRecognizer) { lastObservation = nil let tapLocation = recognizer.location(in: view) // Set up the rect in the image in view coordinate space that we will track let trackImageBoundingBoxOrigin = CGPoint(x: tapLocation.x - trackImageSize / 2, y: tapLocation.y - trackImageSize / 2) trackImageBoundingBox = CGRect(origin: trackImageBoundingBoxOrigin, size: CGSize(width: trackImageSize, height: trackImageSize)) let t = CGAffineTransform(scaleX: 1.0 / self.view.frame.size.width, y: 1.0 / self.view.frame.size.height) let normalizedTrackImageBoundingBox = trackImageBoundingBox!.applying(t) // Transfrom the rect from view space to image space guard let fromViewToCameraImageTransform = self.sceneView.session.currentFrame?.displayTransform(withViewportSize: self.sceneView.frame.size, orientation: UIInterfaceOrientation.portrait).inverted() else { return } var trackImageBoundingBoxInImage = normalizedTrackImageBoundingBox.applying(fromViewToCameraImageTransform) trackImageBoundingBoxInImage.origin.y = 1 - trackImageBoundingBoxInImage.origin.y // Image space uses bottom left as origin while view space uses top left lastObservation = VNDetectedObjectObservation(boundingBox: trackImageBoundingBoxInImage) }
上記の最も難しい部分は、タップ位置をUIView座標空間から画像座標空間に変換する方法です。 ARKitはdisplayTransform
を提供します画像座標空間からビューポート座標空間に変換する行列ですが、その逆はありません。では、どうすればその逆を行うことができますか?逆行列を使用する。この投稿では、数学の使用を最小限に抑えようとしましたが、3Dの世界では避けられない場合があります。
次に、レンダラーで、指の新しい位置を追跡するために新しい画像をフィードします。
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { // Track the thumbnail guard let pixelBuffer = self.sceneView.session.currentFrame?.capturedImage, let observation = self.lastObservation else { return } let request = VNTrackObjectRequest(detectedObjectObservation: observation) { [unowned self] request, error in self.handle(request, error: error) } request.trackingLevel = .accurate do { try self.handler.perform([request], on: pixelBuffer) } catch { print(error) } . . . }
オブジェクトの追跡が完了すると、サムネイルの場所を更新するコールバック関数が呼び出されます。これは通常、タップレコグナイザーで記述されたコードの逆です。
fileprivate func handle(_ request: VNRequest, error: Error?) { DispatchQueue.main.async { guard let newObservation = request.results?.first as? VNDetectedObjectObservation else { return } self.lastObservation = newObservation var trackImageBoundingBoxInImage = newObservation.boundingBox // Transfrom the rect from image space to view space trackImageBoundingBoxInImage.origin.y = 1 - trackImageBoundingBoxInImage.origin.y guard let fromCameraImageToViewTransform = self.sceneView.session.currentFrame?.displayTransform(withViewportSize: self.sceneView.frame.size, orientation: UIInterfaceOrientation.portrait) else { return } let normalizedTrackImageBoundingBox = trackImageBoundingBoxInImage.applying(fromCameraImageToViewTransform) let t = CGAffineTransform(scaleX: self.view.frame.size.width, y: self.view.frame.size.height) let unnormalizedTrackImageBoundingBox = normalizedTrackImageBoundingBox.applying(t) self.trackImageBoundingBox = unnormalizedTrackImageBoundingBox // Get the projection if the location of the tracked image from image space to the nearest detected plane if let trackImageOrigin = self.trackImageBoundingBox?.origin { self.lastFingerWorldPos = self.virtualObjectManager.worldPositionFromScreenPosition(CGPoint(x: trackImageOrigin.x - 20.0, y: trackImageOrigin.y + 40.0), in: self.sceneView) } } }
最後に、self.lastFingerWorldPos
を使用します描画時に画面中央の代わりに、これで完了です。
この投稿では、ARがユーザーの指や実際のテーブルとの対話を通じてどのように没入できるかを示しました。コンピュータビジョンがさらに進歩し、ARに適したハードウェアをガジェット(深度カメラなど)に追加することで、周囲のオブジェクトの3D構造にアクセスできるようになります。
まだ大衆にリリースされていませんが、MicrosoftがARレースに勝つために非常に真剣に取り組んでいることは言及する価値があります Hololensデバイス 、ARに合わせたハードウェアと高度な3D環境認識技術を組み合わせたものです。誰がこのレースに勝つかを待つことも、真の没入型を開発して参加することもできます 拡張現実アプリを今すぐ !しかし、どうか、人類に恩恵を与え、生きている物体をウサギに変えないでください。
ARKitを使用すると、開発者は、カメラビューによって提示されたシーンを分析し、部屋の水平面を見つけることで、iPhoneおよびiPadで没入型拡張現実アプリを構築できます。
Apple Visionライブラリを使用すると、開発者はビデオストリーム内のオブジェクトを追跡できます。開発者は、追跡するオブジェクトの初期画像フレーム内の長方形の座標を提供し、次にビデオフレームをフィードすると、ライブラリはそのオブジェクトの新しい位置を返します。
Apple ARKitの使用を開始するには、iPhone6s以降にiOS11をダウンロードし、[新規]> [プロジェクト]> [拡張現実アプリ]から新しいARKitプロジェクトを作成します。 Appleが提供するARサンプルコードは、https://developer.apple.com/arkit/で確認することもできます。
拡張現実は、デジタルビデオやその他のCGI要素を現実世界のビデオにオーバーレイするプロセスです。カメラでキャプチャされたビデオに高品質のCGIオーバーレイを作成するには、多数のセンサーとソフトウェアソリューションを使用する必要があります。
拡張現実には、さまざまな業界で多くの潜在的なアプリケーションがあります。 ARを使用して、仮想設定でトレーニングを提供できます。仮想設定では、トレーニング目的で再現するのは困難です。宇宙飛行、医療産業、特定のエンジニアリングプロジェクト。
ARはCGIを実際のビデオにオーバーレイするという概念に基づいていますが、VRは、実際の要素を含まない完全に合成された3D環境を生成します。その結果、VRはより没入型になる傾向がありますが、ARはユーザーにもう少し柔軟性と自由を提供します。