برنامج iOS ARKit التعليمي: الرسم في الهواء بأصابع عارية

نشرت: 2022-03-11

أعلنت Apple مؤخرًا عن مكتبتها الجديدة للواقع المعزز (AR) المسماة ARKit. بالنسبة للكثيرين ، بدا الأمر وكأنه مجرد مكتبة أخرى جيدة للواقع المعزز ، وليس كمشكلة تكنولوجية تهتم بها. ومع ذلك ، إذا ألقيت نظرة على تقدم الواقع المعزز في العامين الماضيين ، فلا ينبغي أن يكون المرء سريعًا جدًا في استخلاص مثل هذه الاستنتاجات.

رسم توضيحي لبرنامج ARKit: التفاعل مع الكائنات الافتراضية في تطبيق iOS ARKit

في هذا المنشور ، سننشئ مشروعًا ممتعًا من ARKit باستخدام iOS ARKit. سيضع المستخدم أصابعه على الطاولة كما لو كان يمسك بقلم ، ثم ينقر على الصورة المصغرة ويبدأ الرسم. بمجرد الانتهاء ، سيتمكن المستخدم من تحويل رسمه إلى كائن ثلاثي الأبعاد ، كما هو موضح في الرسم المتحرك أدناه. كود المصدر الكامل لمثال iOS ARKit متاح في GitHub.

عرض توضيحي لتطبيق الواقع المعزز لنظام iOS ARKit قيد الاستخدام

لماذا يجب أن نهتم بنظام iOS ARKit الآن؟

ربما يدرك كل مطور متمرس حقيقة أن الواقع المعزز هو مفهوم قديم. يمكننا تحديد أول تطوير جدي للواقع المعزز بالوقت الذي حصل فيه المطورون على إمكانية الوصول إلى الإطارات الفردية من كاميرات الويب. عادةً ما كانت تُستخدم التطبيقات في ذلك الوقت لتغيير وجهك. ومع ذلك ، لم تستغرق البشرية وقتًا طويلاً لتدرك أن تحويل الوجوه إلى أرانب لم يكن أحد أكثر احتياجاتها الوشيكة ، وسرعان ما تلاشى الضجيج!

أعتقد أن الواقع المعزز كان دائمًا يفتقد قفزتين تقنيتين رئيسيتين لجعله مفيدًا: سهولة الاستخدام والانغماس. إذا قمت بتتبع أنماط الواقع المعزز الأخرى ، فستلاحظ ذلك. على سبيل المثال ، انطلق الضجيج AR مرة أخرى عندما حصل المطورون على إطارات فردية من الكاميرات المحمولة. بجانب العودة القوية لمحولات الأرنب الرائعة ، رأينا موجة من التطبيقات التي تسقط كائنات ثلاثية الأبعاد على أكواد QR المطبوعة. لكنهم لم ينطلقوا أبدًا كمفهوم. لم تكن حقيقة مُعزَّزة ، بل كانت رموز QR مُعزَّزة.

ثم فاجأنا Google بقطعة من الخيال العلمي ، Google Glass. مرت سنتان ، وبحلول الوقت الذي كان من المتوقع أن يظهر فيه هذا المنتج المذهل ، كان قد مات بالفعل! قام العديد من النقاد بتحليل أسباب فشل Google Glass ، وإلقاء اللوم على أي شيء يتراوح من الجوانب الاجتماعية إلى نهج Google الباهت عند إطلاق المنتج. ومع ذلك ، فإننا نهتم في هذه المقالة لسبب واحد محدد - الانغماس في البيئة. بينما حلت Google Glass مشكلة قابلية الاستخدام ، لم تكن أكثر من صورة ثنائية الأبعاد مرسومة في الهواء.

لقد تعلم عمالقة التكنولوجيا مثل Microsoft و Facebook و Apple هذا الدرس القاسي عن ظهر قلب. في يونيو 2017 ، أعلنت شركة Apple عن مكتبة iOS ARKit الجميلة ، مما جعل الانغماس في مقدمة أولوياتها. لا يزال حمل الهاتف يمثل مانعًا كبيرًا لتجربة المستخدم ، ولكن درس Google Glass علمنا أن الأجهزة ليست هي المشكلة.

أعتقد أننا نتجه نحو ذروة ضجيج جديد للواقع المعزز قريبًا جدًا ، ومع هذا المحور المهم الجديد ، يمكن أن تجد السوق المحلية في النهاية ، مما يسمح بتطوير المزيد من تطبيقات AR لتصبح سائدة. هذا يعني أيضًا أن كل شركة لتطوير تطبيقات الواقع المعزز ستكون قادرة على الاستفادة من نظام Apple البيئي وقاعدة المستخدمين.

لكن يكفي التاريخ ، دعونا نلوث أيدينا بالكود ونرى الواقع المعزز لشركة Apple أثناء العمل!

ميزات الغمر ARKit

يوفر ARKit ميزتين رئيسيتين ؛ الأول هو موقع الكاميرا في مساحة ثلاثية الأبعاد والثاني هو اكتشاف المستوى الأفقي. لتحقيق الأول ، تفترض ARKit أن هاتفك عبارة عن كاميرا تتحرك في مساحة ثلاثية الأبعاد حقيقية بحيث يتم إرساء بعض الأشياء الافتراضية ثلاثية الأبعاد في أي نقطة إلى تلك النقطة في مساحة ثلاثية الأبعاد حقيقية. وللأخير ، يكتشف ARKit المستويات الأفقية مثل الجداول بحيث يمكنك وضع الأشياء عليها.

إذن كيف تحقق ARKit ذلك؟ يتم ذلك من خلال تقنية تسمى قياس المسافات البصري بالقصور الذاتي (VIO). لا تقلق ، تمامًا مثل رواد الأعمال الذين يجدون سعادتهم في عدد الضحكات التي تضحكهم عندما تكتشف المصدر وراء اسم الشركة الناشئة ، يجد الباحثون مصدرهم في عدد خدوش الرأس التي تحاول فك شفرة أي مصطلح يأتون به عندما تسمية اختراعاتهم - لذلك دعونا نسمح لهم بالمرح والمضي قدمًا.

VIO هي تقنية يتم من خلالها دمج إطارات الكاميرا مع مستشعرات الحركة لتتبع موقع الجهاز في مساحة ثلاثية الأبعاد. يتم تتبع الحركة من إطارات الكاميرا عن طريق اكتشاف الميزات ، أو بعبارة أخرى ، نقاط الحافة في الصورة ذات التباين العالي - مثل الحافة بين إناء أزرق وطاولة بيضاء. من خلال اكتشاف مقدار تحرك هذه النقاط بالنسبة إلى بعضها البعض من إطار إلى آخر ، يمكن للمرء تقدير مكان الجهاز في مساحة ثلاثية الأبعاد. هذا هو السبب في أن ARKit لن يعمل بشكل صحيح عند وضعه في مواجهة جدار أبيض عديم الملامح أو عندما يتحرك الجهاز بسرعة كبيرة مما ينتج عنه صور غير واضحة.

الشروع في العمل مع ARKit في iOS

حتى وقت كتابة هذا المقال ، كان ARKit جزءًا من iOS 11 ، والذي لا يزال في مرحلة تجريبية. لذلك ، للبدء ، تحتاج إلى تنزيل iOS 11 Beta على iPhone 6s أو أعلى ، و Xcode Beta الجديد. يمكننا بدء مشروع ARKit جديد من جديد> مشروع> تطبيق الواقع المعزز . ومع ذلك ، وجدت أنه من الأنسب بدء هذا البرنامج التعليمي الواقعي المعزز باستخدام نموذج Apple ARKit الرسمي ، والذي يوفر عددًا قليلاً من كتل التعليمات البرمجية الأساسية وهو مفيد بشكل خاص لاكتشاف الطائرة. لذا ، دعونا نبدأ بهذا الكود كمثال ، ونوضح النقاط الرئيسية فيه أولاً ، ثم نقوم بتعديله لمشروعنا.

أولاً ، يجب أن نحدد المحرك الذي سنستخدمه. يمكن استخدام ARKit مع Sprite SceneKit أو Metal. في مثال Apple ARKit ، نستخدم iOS SceneKit ، محرك ثلاثي الأبعاد توفره Apple. بعد ذلك ، نحتاج إلى إعداد عرض من شأنه أن يعرض كائناتنا ثلاثية الأبعاد. يتم ذلك عن طريق إضافة طريقة عرض من نوع ARSCNView .

ARSCNView عبارة عن فئة فرعية من طريقة العرض الرئيسية لـ SceneKit تسمى SCNView ، ولكنها توسع العرض مع اثنين من الميزات المفيدة. فهي تعرض بث الفيديو المباشر من كاميرا الجهاز كخلفية للمشهد ، بينما تقوم تلقائيًا بمطابقة مساحة SceneKit مع العالم الحقيقي ، على افتراض أن الجهاز عبارة عن كاميرا متحركة في هذا العالم.

لا يقوم ARSCNView من تلقاء نفسه ، ولكنه يتطلب كائن جلسة AR يدير كاميرا الجهاز ومعالجة الحركة. لذلك ، للبدء ، نحتاج إلى تعيين جلسة جديدة:

 self.session = ARSession() sceneView.session = session sceneView.delegate = self setupFocusSquare()

يضيف السطر الأخير أعلاه مؤشرًا مرئيًا يساعد المستخدم بصريًا في وصف حالة اكتشاف المستوى. يتم توفير Focus Square بواسطة نموذج الكود ، وليس مكتبة ARKit ، وهو أحد الأسباب الرئيسية التي بدأنا بها مع نموذج التعليمات البرمجية هذا. يمكنك العثور على المزيد حول هذا الموضوع في الملف التمهيدي المضمن في نموذج التعليمات البرمجية. تُظهر الصورة التالية مربع تركيز مُسقط على جدول:

مربع التركيز مُسقط على طاولة باستخدام Apple ARKit

الخطوة التالية هي بدء جلسة ARKit. من المنطقي إعادة تشغيل الجلسة في كل مرة يظهر فيها العرض لأنه لا يمكننا الاستفادة من معلومات الجلسة السابقة إذا لم نعد نتتبع المستخدم. لذلك ، سنبدأ الجلسة في العرض

 override func viewDidAppear(_ animated: Bool) { let configuration = ARWorldTrackingSessionConfiguration() configuration.planeDetection = .horizontal session.run(configuration, options: [.resetTracking, .removeExistingAnchors]) }

في الكود أعلاه ، نبدأ بإعداد تكوين جلسة ARKit لاكتشاف المستويات الأفقية. حتى كتابة هذا المقال ، لا تقدم Apple خيارات أخرى غير ذلك. لكن من الواضح أنه يلمح إلى اكتشاف أشياء أكثر تعقيدًا في المستقبل. بعد ذلك ، نبدأ تشغيل الجلسة ونتأكد من إعادة ضبط التتبع.

أخيرًا ، نحتاج إلى تحديث Focus Square عندما يتغير موضع الكاميرا ، أي الاتجاه الفعلي للجهاز أو موضعه. يمكن القيام بذلك في وظيفة تفويض العارض الخاصة بـ SCNView ، والتي يتم استدعاؤها في كل مرة يتم فيها عرض إطار جديد للمحرك ثلاثي الأبعاد:

 func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) { updateFocusSquare() }

عند هذه النقطة ، إذا قمت بتشغيل التطبيق ، يجب أن ترى مربع التركيز فوق تدفق الكاميرا بحثًا عن مستوى أفقي. في القسم التالي ، سنشرح كيفية اكتشاف الطائرات ، وكيف يمكننا وضع مربع التركيز وفقًا لذلك.

كشف الطائرات في ARKit

يمكن لـ 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 node . node هي عقدة SceneKit طبيعية موضوعة في الموضع والاتجاه الدقيقين للمستوى. ليس لها هندسة ، لذا فهي غير مرئية. نستخدمها لإضافة عقدة مستوية خاصة بنا ، وهي أيضًا غير مرئية ، ولكنها تحتوي على معلومات حول اتجاه المستوى وموضعه في anchor .

فكيف يتم حفظ الموقف والتوجيه في ARPlaneAnchor ؟ يتم ترميز كل من الموضع والتوجيه والمقياس في مصفوفة 4x4. إذا أتيحت لي الفرصة لاختيار مفهوم رياضيات واحد لتتعلمه ، فستكون بلا شك مصفوفات. على أي حال ، يمكننا التحايل على هذا من خلال وصف مصفوفة 4x4 هذه على النحو التالي: مصفوفة رائعة ثنائية الأبعاد تحتوي على أرقام فاصلة عائمة 4x4. بضرب هذه الأرقام بطريقة معينة في رأس ثلاثي الأبعاد ، v1 ، في مساحتها المحلية ، ينتج عنه رأس ثلاثي الأبعاد جديد ، v2 ، يمثل v1 في الفضاء العالمي. لذلك ، إذا كانت v1 = (1 ، 0 ، 0) في مساحتها المحلية ، وأردنا وضعها عند x = 100 في الفضاء العالمي ، فإن v2 ستكون مساوية لـ (101 ، 0 ، 0) بالنسبة إلى مساحة العالم. بالطبع ، تصبح الرياضيات الكامنة وراء ذلك أكثر تعقيدًا عندما نضيف تناوبًا حول المحاور ، لكن الخبر السار هو أنه يمكننا الاستغناء عن فهمها (أوصي بشدة بمراجعة القسم ذي الصلة من هذه المقالة الممتازة للحصول على شرح متعمق لهذا المفهوم ).

checkIfObjectShouldMoveOntoPlane يتحقق مما إذا كان لدينا بالفعل كائنات مرسومة ويتحقق مما إذا كان المحور الصادي لجميع هذه الكائنات يتطابق مع المستويات المكتشفة حديثًا.

الآن ، عد إلى 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 عمليات البحث الاختبارية عن مستويات العالم الحقيقي المقابلة لنقطة ثنائية الأبعاد في عرض الشاشة عن طريق عرض هذه النقطة ثنائية الأبعاد على أقرب مستوى أسفل المستوى. result.worldTransform عبارة عن مصفوفة 4x4 تحتوي على جميع معلومات التحويل الخاصة بالمستوى المكتشف ، بينما تعد result.worldTransform.translation دالة مفيدة ترجع الموضع فقط.

الآن ، لدينا جميع المعلومات التي نحتاجها لإسقاط كائن ثلاثي الأبعاد على الأسطح المكتشفة مع إعطاء نقطة ثنائية الأبعاد على الشاشة. لذا ، لنبدأ الرسم.

رسم

دعونا أولاً نشرح نهج رسم الأشكال الذي يتبع إصبع الإنسان في رؤية الكمبيوتر. يتم رسم الأشكال عن طريق اكتشاف كل موقع جديد للإصبع المتحرك ، وإسقاط قمة في ذلك الموقع ، وربط كل قمة بالرأس السابقة. يمكن توصيل الرؤوس بخط بسيط ، أو من خلال منحنى بيزير إذا احتجنا إلى إخراج سلس.

من أجل التبسيط ، سوف نتبع نهجًا ساذجًا للرسم. لكل موقع جديد للإصبع ، سنقوم بإسقاط صندوق صغير جدًا بزوايا مستديرة وارتفاعه صفر تقريبًا على الخطة المكتشفة. ستظهر كما لو كانت نقطة. بمجرد انتهاء المستخدم من الرسم وتحديد الزر ثلاثي الأبعاد ، سنقوم بتغيير ارتفاع جميع الكائنات التي تم إسقاطها بناءً على حركة إصبع المستخدم.

يُظهر الكود التالي فئة 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 = 0 ، بحيث يظهر فوق المستوى.

بعد ذلك ، في وظيفة رد نداء العارض في SceneKit ، سنرسم بعض المؤشرات التي تعمل كنقطة طرف القلم ، باستخدام نفس فئة PointNode . سنقوم بإسقاط نقطة في هذا الموقع إذا تم تمكين الرسم ، أو نرفع الرسم إلى هيكل ثلاثي الأبعاد إذا تم تمكين الوضع ثلاثي الأبعاد:

 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 هي فئة تدير النقاط المرسومة. في الوضع ثلاثي الأبعاد ، نقوم بتقدير الاختلاف عن الموضع الأخير ونزيد / نخفض ارتفاع جميع النقاط بهذه القيمة.

حتى الآن ، نحن نرسم على السطح المكتشف على افتراض أن القلم الافتراضي في وسط الشاشة. الآن بالنسبة للجزء الممتع - اكتشاف إصبع المستخدم واستخدامه بدلاً من مركز الشاشة.

الكشف عن طرف إصبع المستخدم

إحدى المكتبات الرائعة التي قدمتها Apple في iOS 11 هي Vision Framework. يوفر بعض تقنيات رؤية الكمبيوتر بطريقة سهلة وفعالة. على وجه الخصوص ، سنستخدم تقنية تتبع الكائن في برنامجنا التعليمي للواقع المعزز. يعمل تتبع الكائن على النحو التالي: أولاً ، نوفر له صورة وإحداثيات مربع داخل حدود الصورة للكائن الذي نريد تتبعه. بعد ذلك نقوم باستدعاء بعض الوظائف لتهيئة التتبع. أخيرًا ، نقوم بتغذية صورة جديدة تغير فيها موضع ذلك الكائن ونتيجة التحليل للعملية السابقة. بالنظر إلى ذلك ، سيعود لنا الموقع الجديد للكائن.

سنستخدم خدعة صغيرة. سنطلب من المستخدم أن يضع يده على الطاولة كما لو كان يحمل قلمًا ، وللتأكد من أن الصورة المصغرة الخاصة به تواجه الكاميرا ، وبعد ذلك يجب عليه النقر على الصورة المصغرة الخاصة به على الشاشة. هناك نقطتان يجب توضيحهما هنا. أولاً ، يجب أن تحتوي الصورة المصغرة على ميزات فريدة كافية لتتبعها عبر التباين بين الصورة المصغرة البيضاء والقشرة والجدول. هذا يعني أن صبغة البشرة الداكنة ستؤدي إلى تتبع أكثر موثوقية. ثانيًا ، نظرًا لأن المستخدم يضع يديه على الطاولة ، وبما أننا نكتشف بالفعل الجدول كمستوى ، فإن إسقاط موقع الصورة المصغرة من العرض ثنائي الأبعاد إلى البيئة ثلاثية الأبعاد سيؤدي إلى تحديد الموقع الدقيق للإصبع تقريبًا على جدول.

تُظهر الصورة التالية نقاط الميزة التي يمكن أن تكتشفها مكتبة Vision:

تم اكتشاف نقاط ميزات iOS ARKit بواسطة مكتبة 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 إلى مساحة إحداثيات الصورة. توفر لنا displayTransform مصفوفة displayTransform التي تحول من مساحة إحداثيات الصورة إلى مساحة إحداثيات منفذ العرض ، ولكن ليس العكس. إذن كيف يمكننا عمل المعكوس؟ باستخدام معكوس المصفوفة. لقد حاولت حقًا تقليل استخدام الرياضيات في هذا المنشور ، لكن في بعض الأحيان لا مفر منه في العالم ثلاثي الأبعاد.

بعد ذلك ، في العارض ، سنقوم بالتغذية في صورة جديدة لتتبع الموقع الجديد للإصبع:

 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 بدلاً من مركز الشاشة عند الرسم ، وقد انتهينا.

ARKit والمستقبل

في هذا المنشور ، أوضحنا كيف يمكن للواقع المعزز أن يكون غامرًا من خلال التفاعل مع أصابع المستخدم وجداول الحياة الواقعية. مع المزيد من التطورات في رؤية الكمبيوتر ، وإضافة المزيد من الأجهزة الصديقة للواقع المعزز إلى الأدوات (مثل كاميرات العمق) ، يمكننا الوصول إلى البنية ثلاثية الأبعاد للمزيد والمزيد من الكائنات من حولنا.

على الرغم من أنه لم يتم إصداره للجماهير بعد ، إلا أنه من الجدير بالذكر كيف أن Microsoft جادة جدًا في الفوز بسباق AR من خلال جهاز Hololens ، الذي يجمع بين الأجهزة المصممة للواقع المعزز وتقنيات التعرف على البيئة ثلاثية الأبعاد المتقدمة. يمكنك الانتظار لمعرفة من سيفوز في هذا السباق ، أو يمكنك أن تكون جزءًا منه من خلال تطوير تطبيقات واقع معزز غامرة حقيقية الآن! لكن من فضلك ، قدم خدمة للبشرية ولا تغير الأشياء الحية إلى أرانب.