บทช่วยสอน iOS ARKit: การวาดในอากาศด้วยนิ้วเปล่า

เผยแพร่แล้ว: 2022-03-11

เมื่อเร็ว ๆ นี้ Apple ได้ประกาศไลบรารี Augmented Reality (AR) ใหม่ที่ชื่อว่า ARKit สำหรับหลาย ๆ คน ดูเหมือนว่าห้องสมุด AR ที่ดีอีกแห่งไม่ใช่ผู้ทำลายเทคโนโลยีที่ต้องสนใจ อย่างไรก็ตาม หากคุณดูความคืบหน้าของ AR ในช่วงสองสามปีที่ผ่านมา ไม่ควรสรุปอย่างรวดเร็วเกินไป

ภาพประกอบบทช่วยสอน ARKit: การโต้ตอบกับวัตถุเสมือนในแอพ iOS ARKit

ในโพสต์นี้ เราจะสร้างโครงการตัวอย่าง ARKit สนุกๆ โดยใช้ iOS ARKit ผู้ใช้จะวางนิ้วลงบนโต๊ะราวกับว่ากำลังถือปากกาอยู่ แตะที่ภาพขนาดย่อแล้วเริ่มวาด เมื่อเสร็จแล้ว ผู้ใช้จะสามารถเปลี่ยนภาพวาดของพวกเขาเป็นวัตถุ 3 มิติ ดังที่แสดงในแอนิเมชั่นด้านล่าง ซอร์สโค้ดแบบเต็มสำหรับตัวอย่าง iOS ARKit ของเรามีอยู่ที่ GitHub

การสาธิตตัวอย่าง iOS ARKit ของเรา แอพ Augmented Reality ที่กำลังใช้งาน

เหตุใดเราจึงควรสนใจ iOS ARKit ในตอนนี้

นักพัฒนาที่มีประสบการณ์ทุกคนคงทราบดีอยู่แล้วว่า AR เป็นแนวคิดแบบเก่า เราสามารถปักหมุดการพัฒนาอย่างจริงจังครั้งแรกของ AR ได้จนถึงเวลาที่นักพัฒนาเข้าถึงแต่ละเฟรมจากเว็บแคม แอพในสมัยนั้นมักใช้เพื่อเปลี่ยนใบหน้าของคุณ อย่างไรก็ตาม มนุษยชาติใช้เวลาไม่นานในการตระหนักว่าการเปลี่ยนใบหน้าเป็นกระต่ายไม่ใช่หนึ่งในความต้องการที่ใกล้จะถึงที่สุดของพวกเขา และในไม่ช้าโฆษณาก็หายไป!

ฉันเชื่อว่า AR นั้นขาดการก้าวกระโดดทางเทคโนโลยีที่สำคัญสองอย่างเสมอมาเพื่อให้มีประโยชน์: การใช้งานและการแช่ หากคุณติดตามโฆษณา AR อื่น ๆ คุณจะสังเกตเห็นสิ่งนี้ ตัวอย่างเช่น AR hype เริ่มต้นอีกครั้งเมื่อนักพัฒนาเข้าถึงเฟรมแต่ละเฟรมจากกล้องมือถือ นอกจากการกลับมาอย่างแข็งแกร่งของกระต่ายผู้ยิ่งใหญ่แล้ว เราได้เห็นแอพจำนวนมากที่ปล่อยวัตถุ 3 มิติลงบนรหัส QR ที่พิมพ์ออกมา แต่พวกเขาไม่เคยเอาออกเป็นแนวคิด พวกเขาไม่ได้เติมความเป็นจริง แต่เป็นรหัส QR ที่เพิ่มขึ้น

จากนั้น Google ทำให้เราประหลาดใจด้วยนิยายวิทยาศาสตร์ชิ้นหนึ่งคือ Google Glass สองปีผ่านไป และเมื่อถึงเวลาที่ผลิตภัณฑ์อันน่าทึ่งนี้จะมีชีวิตขึ้นมา มันก็ตายไปแล้ว! นักวิจารณ์หลายคนวิเคราะห์สาเหตุของความล้มเหลวของ Google Glass โดยตำหนิทุกอย่างตั้งแต่แง่มุมทางสังคมไปจนถึงแนวทางที่น่าเบื่อของ Google ในการเปิดตัวผลิตภัณฑ์ อย่างไรก็ตาม เราใส่ใจในบทความนี้ด้วยเหตุผลหนึ่งประการ - การแช่ตัวในสิ่งแวดล้อม แม้ว่า Google Glass จะแก้ปัญหาการใช้งานได้ แต่ก็ยังไม่มีอะไรมากไปกว่าภาพ 2D ที่วางอยู่บนอากาศ

ยักษ์ใหญ่แห่งวงการเทคโนโลยีอย่าง Microsoft, Facebook และ Apple ได้เรียนรู้บทเรียนอันโหดร้ายนี้ด้วยใจ ในเดือนมิถุนายน 2017 Apple ได้ประกาศไลบรารี iOS ARKit ที่สวยงาม ทำให้การแช่เป็นสิ่งที่สำคัญสูงสุด การถือโทรศัพท์ยังคงเป็นอุปสรรคต่อประสบการณ์ผู้ใช้ครั้งใหญ่ แต่บทเรียนของ Google Glass สอนเราว่าฮาร์ดแวร์ไม่ใช่ปัญหา

ฉันเชื่อว่าเรากำลังมุ่งสู่จุดสูงสุดใหม่ของ AR ในเร็วๆ นี้ และด้วยจุดหมุนที่สำคัญใหม่นี้ ในที่สุด ก็สามารถหาตลาดที่บ้านได้ ซึ่งช่วยให้การพัฒนาแอป AR กลายเป็นกระแสหลักมากขึ้น นี่ก็หมายความว่าบริษัทพัฒนาแอพที่ใช้เทคโนโลยีความจริงเสริมทุกแห่งจะสามารถเข้าถึงระบบนิเวศและฐานผู้ใช้ของ Apple ได้

แต่ประวัติศาสตร์ที่เพียงพอ ให้เราลงมือสกปรกกับโค้ด และดูแอ็ปเปิ้ลความเป็นจริงยิ่งในการดำเนินการ!

คุณสมบัติการแช่ ARKit

ARKit มีคุณสมบัติหลักสองประการ อันดับแรกคือตำแหน่งของกล้องในพื้นที่ 3 มิติ และส่วนที่สองคือการตรวจจับระนาบแนวนอน เพื่อให้บรรลุผลก่อนหน้านี้ ARKit ถือว่าโทรศัพท์ของคุณเป็นกล้องที่เคลื่อนที่ในพื้นที่ 3 มิติจริง โดยการวางวัตถุเสมือน 3 มิติที่จุดใด ๆ จะถูกยึดกับจุดนั้นในพื้นที่ 3 มิติจริง และอย่างหลัง ARKit จะตรวจจับระนาบแนวนอนเช่นตารางเพื่อให้คุณสามารถวางวัตถุไว้บนนั้นได้

ARKit บรรลุเป้าหมายนี้ได้อย่างไร ทำได้โดยใช้เทคนิคที่เรียกว่า Visual Inertial Odometry (VIO) ไม่ต้องกังวล เช่นเดียวกับที่ผู้ประกอบการพบความสุขในจำนวนเสียงหัวเราะคิกคักที่คุณหัวเราะคิกคักเมื่อคุณหาที่มาที่อยู่เบื้องหลังชื่อการเริ่มต้นของพวกเขา นักวิจัยพบว่าพวกเขามีจำนวนรอยขีดข่วนบนหัวที่คุณพยายามถอดรหัสคำใด ๆ ที่เกิดขึ้นเมื่อ ตั้งชื่อสิ่งประดิษฐ์ของพวกเขา - ปล่อยให้พวกเขาสนุกและเดินหน้าต่อไป

VIO เป็นเทคนิคที่เฟรมของกล้องผสานเข้ากับเซ็นเซอร์ตรวจจับความเคลื่อนไหวเพื่อติดตามตำแหน่งของอุปกรณ์ในพื้นที่ 3 มิติ การติดตามการเคลื่อนไหวจากเฟรมของกล้องทำได้โดยการตรวจจับคุณสมบัติต่างๆ หรืออีกนัยหนึ่งคือ จุดขอบในภาพที่มีคอนทราสต์สูง เช่น ขอบระหว่างแจกันสีน้ำเงินกับโต๊ะสีขาว โดยการตรวจจับว่าจุดเหล่านี้เคลื่อนที่สัมพันธ์กันมากน้อยเพียงใดจากเฟรมหนึ่งไปยังอีกเฟรมหนึ่ง เราสามารถประเมินว่าอุปกรณ์นั้นอยู่ที่ใดในพื้นที่ 3 มิติ นั่นคือเหตุผลที่ ARKit ทำงานไม่ถูกต้องเมื่อวางหันหน้าเข้าหาผนังสีขาวที่ไม่มีลักษณะเฉพาะ หรือเมื่ออุปกรณ์เคลื่อนที่เร็วมากทำให้ภาพเบลอ

เริ่มต้นใช้งาน ARKit ใน iOS

ในขณะที่เขียนบทความนี้ ARKit เป็นส่วนหนึ่งของ iOS 11 ซึ่งยังอยู่ในช่วงเบต้า ดังนั้น ในการเริ่มต้น คุณต้องดาวน์โหลด iOS 11 Beta บน iPhone 6s ขึ้นไป และ Xcode Beta ใหม่ เราสามารถเริ่มโครงการ ARKit ใหม่ได้จาก New > Project > Augmented Reality App อย่างไรก็ตาม ฉันพบว่าสะดวกกว่าในการเริ่มกวดวิชาความเป็นจริงเสริมนี้ด้วยตัวอย่าง Apple ARKit อย่างเป็นทางการ ซึ่งมีบล็อกโค้ดที่จำเป็นสองสามข้อ และมีประโยชน์อย่างยิ่งสำหรับการตรวจจับเครื่องบิน ดังนั้น เรามาเริ่มด้วยโค้ดตัวอย่างนี้ อธิบายประเด็นหลักในนั้นก่อน แล้วจึงแก้ไขสำหรับโครงการของเรา

อันดับแรก เราควรกำหนดว่าจะใช้เครื่องยนต์ใด ARKit สามารถใช้ได้กับ Sprite SceneKit หรือ Metal ในตัวอย่าง Apple ARKit เราใช้ iOS SceneKit ซึ่งเป็นเอ็นจิ้น 3 มิติที่ Apple จัดหาให้ ต่อไป เราต้องตั้งค่ามุมมองที่จะแสดงวัตถุ 3 มิติของเรา ทำได้โดยการเพิ่มมุมมองประเภท ARSCNView

ARSCNView เป็นคลาสย่อยของมุมมองหลักของ SceneKit ที่ชื่อ SCNView แต่จะขยายมุมมองด้วยคุณสมบัติที่มีประโยชน์สองสามอย่าง มันแสดงฟีดวิดีโอสดจากกล้องของอุปกรณ์เป็นพื้นหลังของฉาก ในขณะที่มันจับคู่พื้นที่ของ SceneKit กับโลกแห่งความจริงโดยอัตโนมัติ สมมติว่าอุปกรณ์นั้นเป็นกล้องที่เคลื่อนไหวได้ในโลกนี้

ARSCNView ไม่ได้ทำการประมวลผล AR ด้วยตัวเอง แต่ต้องใช้วัตถุเซสชัน AR ที่จัดการกล้องของอุปกรณ์และการประมวลผลการเคลื่อนไหว ในการเริ่มต้น เราต้องกำหนดเซสชันใหม่:

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

บรรทัดสุดท้ายด้านบนเพิ่มตัวบ่งชี้ที่มองเห็นได้ซึ่งช่วยผู้ใช้ด้วยสายตาในการอธิบายสถานะของการตรวจจับเครื่องบิน Focus Square มาจากโค้ดตัวอย่าง ไม่ใช่ไลบรารี ARKit และเป็นหนึ่งในสาเหตุหลักที่เราเริ่มต้นด้วยโค้ดตัวอย่างนี้ คุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ในไฟล์ readme ที่รวมอยู่ในโค้ดตัวอย่าง รูปภาพต่อไปนี้แสดงช่องโฟกัสที่ฉายบนโต๊ะ:

โฟกัสสี่เหลี่ยมที่ฉายบนโต๊ะโดยใช้ Apple ARKit

ขั้นตอนต่อไปคือการเริ่มเซสชัน ARKit ควรรีสตาร์ทเซสชันทุกครั้งที่มุมมองปรากฏขึ้น เนื่องจากเราไม่สามารถใช้ข้อมูลเซสชันก่อนหน้าได้หากเราไม่ได้ติดตามผู้ใช้อีกต่อไป ดังนั้น เราจะเริ่มเซสชันใน viewDidAppear:

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

ในโค้ดด้านบนนี้ เราเริ่มต้นด้วยการตั้งค่าเซสชัน ARKit เพื่อตรวจจับระนาบแนวนอน ในการเขียนบทความนี้ Apple ไม่มีตัวเลือกอื่นนอกเหนือจากนี้ แต่เห็นได้ชัดว่ามันบ่งบอกถึงการตรวจจับวัตถุที่ซับซ้อนมากขึ้นในอนาคต จากนั้น เราเริ่มใช้งานเซสชันและทำให้แน่ใจว่าเรารีเซ็ตการติดตาม

สุดท้าย เราต้องอัปเดต Focus Square ทุกครั้งที่ตำแหน่งของกล้องเปลี่ยนแปลง เช่น การวางแนวหรือตำแหน่งของอุปกรณ์จริง สิ่งนี้สามารถทำได้ในฟังก์ชันผู้รับมอบสิทธิ์ผู้แสดงภาพของ SCNView ซึ่งถูกเรียกทุกครั้งที่มีการสร้างเฟรมใหม่ของเอ็นจิ้น 3 มิติ:

 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 ดังต่อไปนี้: อาร์เรย์ 2 มิติที่ยอดเยี่ยมที่มีตัวเลขทศนิยม 4x4 โดยการคูณตัวเลขเหล่านี้ด้วยวิธีใดวิธีหนึ่งด้วยจุดยอด 3 มิติ v1 ในพื้นที่ท้องถิ่น จะส่งผลให้เกิดจุดยอด 3 มิติใหม่ v2 ซึ่งแทน v1 ในพื้นที่โลก ดังนั้น ถ้า v1 = (1, 0, 0) อยู่ในพื้นที่ของมัน และเราต้องการวางไว้ที่ x = 100 ในอวกาศโลก v2 จะเท่ากับ (101, 0, 0) เทียบกับอวกาศ แน่นอน คณิตศาสตร์ที่อยู่เบื้องหลังสิ่งนี้จะซับซ้อนมากขึ้นเมื่อเราเพิ่มการหมุนเกี่ยวกับแกน แต่ข่าวดีก็คือเราสามารถทำได้โดยไม่เข้าใจ (ฉันขอแนะนำให้ตรวจสอบส่วนที่เกี่ยวข้องจากบทความที่ยอดเยี่ยมนี้สำหรับคำอธิบายเชิงลึกของแนวคิดนี้ ).

checkIfObjectShouldMoveOntoPlane ตรวจสอบว่าเราได้วาดวัตถุแล้วและตรวจสอบว่าแกน 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 เป็นฟังก์ชันที่มีประโยชน์ซึ่งจะส่งคืนเฉพาะตำแหน่งเท่านั้น

ตอนนี้ เรามีข้อมูลทั้งหมดที่เราต้องการเพื่อวางวัตถุ 3 มิติลงบนพื้นผิวที่ตรวจพบโดยกำหนดจุด 2 มิติบนหน้าจอ มาเริ่มวาดกันเลย

การวาดภาพ

อันดับแรก ให้เราอธิบายวิธีการวาดรูปร่างที่ตามนิ้วของมนุษย์ในการมองเห็นด้วยคอมพิวเตอร์ การวาดรูปร่างทำได้โดยการตรวจจับตำแหน่งใหม่สำหรับนิ้วที่กำลังเคลื่อนที่ วางจุดยอดที่ตำแหน่งนั้น และเชื่อมต่อจุดยอดแต่ละจุดกับจุดยอดก่อนหน้า จุดยอดสามารถเชื่อมต่อด้วยเส้นธรรมดาหรือผ่านเส้นโค้ง Bezier หากเราต้องการเอาต์พุตที่ราบรื่น

เพื่อความง่าย เราจะใช้วิธีการวาดแบบไร้เดียงสา สำหรับตำแหน่งใหม่ของนิ้ว เราจะวางกล่องขนาดเล็กมากที่มีมุมโค้งมนและความสูงเกือบเป็นศูนย์ในแผนที่ตรวจพบ จะปรากฏราวกับว่าเป็นจุด เมื่อผู้ใช้วาดภาพเสร็จแล้วและเลือกปุ่ม 3 มิติ เราจะเปลี่ยนความสูงของวัตถุที่ตกหล่นตามการเคลื่อนไหวของนิ้วของผู้ใช้

รหัสต่อไปนี้แสดงคลาส 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 เดียวกัน เราจะวางจุดที่ตำแหน่งนั้นหากเปิดใช้งานการวาดหรือเพิ่มภาพวาดเป็นโครงสร้าง 3 มิติหากเปิดใช้งานโหมด 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 เป็นคลาสที่จัดการจุดที่วาด ในโหมด 3 มิติ เราประเมินความแตกต่างจากตำแหน่งสุดท้ายและเพิ่ม/ลดความสูงของจุดทั้งหมดด้วยค่านั้น

จนถึงขณะนี้ เรากำลังวาดบนพื้นผิวที่ตรวจพบโดยสมมติว่าปากกาเสมือนอยู่ตรงกลางหน้าจอ ตอนนี้เพื่อความสนุกแล้ว นั่นคือการตรวจจับนิ้วของผู้ใช้และใช้งานแทนศูนย์กลางหน้าจอ

การตรวจจับปลายนิ้วของผู้ใช้

หนึ่งในไลบรารีสุดเจ๋งที่ Apple เปิดตัวใน iOS 11 คือ Vision Framework มีเทคนิคการมองเห็นด้วยคอมพิวเตอร์บางอย่างในวิธีที่สะดวกและมีประสิทธิภาพ โดยเฉพาะอย่างยิ่ง เราจะใช้เทคนิคการติดตามวัตถุสำหรับบทช่วยสอนความเป็นจริงยิ่งของเรา การติดตามวัตถุทำงานดังนี้: ขั้นแรก เราจัดเตรียมรูปภาพและพิกัดของสี่เหลี่ยมจัตุรัสภายในขอบเขตรูปภาพสำหรับวัตถุที่เราต้องการติดตาม หลังจากนั้นเราเรียกใช้ฟังก์ชันบางอย่างเพื่อเริ่มต้นการติดตาม สุดท้าย เราป้อนรูปภาพใหม่ที่ตำแหน่งของวัตถุนั้นเปลี่ยนไปและผลการวิเคราะห์ของการดำเนินการครั้งก่อน ระบุว่าจะส่งคืนตำแหน่งใหม่ของวัตถุให้เรา

เราจะใช้เคล็ดลับเล็ก ๆ เราจะขอให้ผู้ใช้วางมือบนโต๊ะราวกับว่ากำลังถือปากกาอยู่ และเพื่อให้แน่ใจว่าภาพขนาดย่อหันเข้าหากล้อง หลังจากนั้นควรแตะภาพขนาดย่อบนหน้าจอ มีสองประเด็นที่ต้องอธิบายอย่างละเอียดที่นี่ ประการแรก ภาพขนาดย่อควรมีคุณลักษณะเฉพาะเพียงพอที่จะลากเส้นผ่านความเปรียบต่างระหว่างภาพขนาดย่อสีขาว ผิว และตาราง ซึ่งหมายความว่าเม็ดสีผิวคล้ำจะส่งผลให้การติดตามมีความน่าเชื่อถือมากขึ้น ประการที่สอง เนื่องจากผู้ใช้วางมือบนโต๊ะ และเนื่องจากเราตรวจพบตารางเป็นระนาบแล้ว การฉายตำแหน่งของภาพขนาดย่อจากมุมมอง 2D ไปยังสภาพแวดล้อม 3D จะส่งผลให้ตำแหน่งที่แน่นอนของนิ้วบน โต๊ะ.

รูปภาพต่อไปนี้แสดงจุดคุณลักษณะที่ห้องสมุด 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 เป็นพื้นที่พิกัดของรูปภาพ ARKit ให้เมทริกซ์ displayTransform แก่เราที่แปลงจากพื้นที่พิกัดภาพเป็นพื้นที่พิกัดวิวพอร์ต แต่ไม่ใช่วิธีอื่น แล้วเราจะทำอินเวอร์สได้อย่างไร? โดยใช้อินเวอร์สของเมทริกซ์ ฉันพยายามลดการใช้คณิตศาสตร์ให้น้อยที่สุดในโพสต์นี้ แต่บางครั้งก็หลีกเลี่ยงไม่ได้ในโลก 3 มิติ

ถัดไป ในโปรแกรมแสดงภาพ เราจะป้อนรูปภาพใหม่เพื่อติดตามตำแหน่งใหม่ของนิ้ว:

 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 แทน screen center เมื่อวาด เสร็จแล้ว

ARKit และอนาคต

ในโพสต์นี้ เราได้แสดงให้เห็นว่า AR สามารถดื่มด่ำผ่านการโต้ตอบกับนิ้วของผู้ใช้และตารางในชีวิตจริงได้อย่างไร ด้วยความก้าวหน้าทางคอมพิวเตอร์วิทัศน์ที่มากขึ้น และด้วยการเพิ่มฮาร์ดแวร์ที่เป็นมิตรกับ AR ให้กับแกดเจ็ต (เช่น กล้องความลึก) เราจึงสามารถเข้าถึงโครงสร้าง 3 มิติของวัตถุรอบตัวเรามากขึ้นเรื่อยๆ

แม้ว่าจะยังไม่เผยแพร่สู่สายตาคนทั่วไป แต่ก็ควรค่าแก่การพูดถึงว่า Microsoft จริงจังแค่ไหนที่จะชนะการแข่งขัน AR ผ่านอุปกรณ์ Hololens ซึ่งรวมฮาร์ดแวร์ที่ปรับแต่ง AR เข้ากับเทคนิคการจดจำสภาพแวดล้อม 3 มิติขั้นสูง คุณสามารถรอดูว่าใครจะเป็นผู้ชนะการแข่งขันนี้ หรือคุณสามารถเป็นส่วนหนึ่งของมันโดยการพัฒนาแอปเสมือนจริงที่เสมือนจริงทันที! แต่ได้โปรดช่วยมนุษยชาติและอย่าเปลี่ยนสิ่งของที่มีชีวิตเป็นกระต่าย