Наскоро Apple обяви новата си библиотека с добавена реалност (AR) на име ARKit . За мнозина това изглеждаше като поредната добра AR библиотека, а не технологичен прекъсвач, за който да се интересуват. Ако обаче погледнете напредъка на AR през последните няколко години, не бива да се прави твърде бързо такива заключения.
В тази публикация ще създадем забавен пример за проект на ARKit, използвайки iOS ARKit. Потребителят ще постави пръстите си върху маса, сякаш държи писалка, докосва миниизображението и започва да рисува. След като приключи, потребителят ще може да трансформира своя чертеж в 3D обект, както е показано в анимацията по-долу. Наличен е пълният изходен код за нашия пример за ARKit на iOS в GitHub .
Всеки опитен разработчик вероятно е наясно с факта, че AR е стара концепция. Можем да определим първата сериозна разработка на AR до момента, в който разработчиците са получили достъп до отделни кадри от уеб камери. Приложенията по това време обикновено са били използвани за преобразяване на лицето ви. Въпреки това, човечеството не отне много време, за да осъзнае, че превръщането на лица в зайчета не е една от най-близките им нужди и скоро шумът изчезна!
Вярвам, че AR винаги е пропускал два ключови технологични скока, за да го направи полезен: използваемост и потапяне. Ако сте проследили други AR хипове, ще забележите това. Например, хип-хопът на AR отново излетя, когато разработчиците получиха достъп до отделни кадри от мобилни камери. Освен силното завръщане на страхотните трансформатори на зайчета, видяхме вълна от приложения, които пускат 3D обекти върху отпечатани QR кодове. Но те никога не са излитали като концепция. Те не бяха разширена реалност, а по-скоро разширени QR кодове.
Тогава Google ни изненада с част от научната фантастика, Google Glass. Минаха две години и когато се очакваше този невероятен продукт да оживее, той вече беше мъртъв! Много критици анализираха причините за провала на Google Glass, обвинявайки всичко, вариращо от социални аспекти до скучния подход на Google при пускането на продукта. Ние обаче се грижим в тази статия по една конкретна причина - потапяне в околната среда. Докато Google Glass решава проблема с използваемостта, това все още не е нищо повече от 2D изображение, нанесено във въздуха.
Технически титани като Microsoft, Facebook и Apple научиха този суров урок наизуст. През юни 2017 г. Apple обяви своята красива iOS ARKit библиотека, като потапянето е нейният основен приоритет. Държането на телефон все още е голям блокиращ потребителски опит, но урокът на Google Glass ни научи, че проблемът не е в хардуера.
Вярвам, че много скоро се насочваме към нов пик на AR и с този нов значителен въртящ момент, той в крайна сметка може да намери своя вътрешен пазар, което позволява повече разработки на AR приложения да станат основни. Това също означава, че всяка компания за разработка на приложения за добавена реалност ще може да се възползва от екосистемата и потребителската база на Apple.
Но достатъчно история, нека си изцапаме ръцете с код и да видим разширената реалност на Apple в действие!
ARKit предоставя две основни функции; първото е местоположението на камерата в 3D пространство, а второто е разпознаването на хоризонтална равнина. За да постигне първото, ARKit приема, че телефонът ви е камера, движеща се в реалното 3D пространство, така че пускането на някакъв 3D виртуален обект във всяка точка ще бъде закотвено към тази точка в реалното 3D пространство. А към последното ARKit открива хоризонтални равнини като маси, за да можете да поставяте обекти върху него.
И така, как ARKit постига това? Това се прави чрез техника, наречена визуална инерционна одометрия (VIO). Не се притеснявайте, точно както предприемачите намират удоволствието си от броя кикотене, което кикотете, когато разберете източника зад тяхното начално име, изследователите намират своето в броя на драскотините по главата, които правите, опитвайки се да дешифрират всеки термин, който измислят назовавайки техните изобретения - така че нека им позволим да се забавляват и да продължим напред.
VIO е техника, при която кадрите на камерата се сливат със сензори за движение, за да се проследи местоположението на устройството в 3D пространство. Проследяването на движението от кадрите на камерата се извършва чрез откриване на функции или, с други думи, крайни точки в изображението с висок контраст - като ръба между синя ваза и бяла маса. Чрез откриване на това колко тези точки се преместват една спрямо друга от един кадър в друг, може да се прецени къде е устройството в 3D пространство. Ето защо ARKit няма да работи правилно, когато е поставен с лице към бяла стена без характеристики или когато устройството се движи много бързо, което води до замъглени изображения.
Към момента на писане на тази статия ARKit е част от iOS 11, който все още е в бета версия. Така че, за да започнете, трябва да изтеглите iOS 11 Beta на iPhone 6s или по-нова версия и новия Xcode Beta. Можем да започнем нов проект на ARKit от Ново> Проект> Приложение за разширена реалност . Намерих обаче за по-удобно да стартирам този урок за добавена реалност с официалния Проба на Apple ARKit , който предоставя няколко основни кодови блока и е особено полезен за откриване на равнина. И така, нека започнем с този примерен код, да обясним първо основните моменти в него и след това да го модифицираме за нашия проект.
Първо, трябва да определим кой двигател ще използваме. ARKit може да се използва със Sprite SceneKit или Metal. В примера за Apple ARKit използваме iOS SceneKit, 3D механизъм, предоставен от Apple. След това трябва да настроим изглед, който да изобразява нашите 3D обекти. Това се прави чрез добавяне на изглед от тип ARSCNView
.
ARSCNView
е подклас на основния изглед на SceneKit, наречен SCNView
, но разширява изгледа с няколко полезни функции. Той изобразява видео на живо от камерата на устройството като фон на сцената, докато автоматично съпоставя пространството на SceneKit с реалния свят, като се предположи, че устройството е движеща се камера в този свят.
ARSCNView
не извършва AR обработка самостоятелно, но изисква AR сесия обект, който управлява камерата на устройството и обработка на движение. И така, за да започнем, трябва да назначим нова сесия:
self.session = ARSession() sceneView.session = session sceneView.delegate = self setupFocusSquare()
Последният ред по-горе добавя визуален индикатор, който помага на потребителя визуално да описва състоянието на откриване на самолет. Focus Square се осигурява от примерния код, а не от библиотеката ARKit и това е една от основните причини да започнем с този примерен код. Можете да намерите повече за него във файла readme, включен в примерния код. Следващото изображение показва фокусиран квадрат, проектиран върху маса:
Следващата стъпка е да започнете сесията ARKit. Има смисъл да рестартирате сесията всеки път, когато се покаже изгледът, защото не можем да използваме информацията от предишната сесия, ако вече не проследяваме потребителя. И така, ще започнем сесията в viewDidAppear:
override func viewDidAppear(_ animated: Bool) { let configuration = ARWorldTrackingSessionConfiguration() configuration.planeDetection = .horizontal session.run(configuration, options: [.resetTracking, .removeExistingAnchors]) }
В горния код започваме, като задаваме конфигурация на сесия ARKit за откриване на хоризонтални равнини. По време на писането на тази статия Apple не предоставя опции, различни от тази. Но очевидно намеква за откриване на по-сложни обекти в бъдеще. След това започваме да изпълняваме сесията и се уверете, че сме нулирали проследяването.
И накрая, трябва да актуализираме Focus Square, когато се промени позицията на камерата, т.е. действителната ориентация или позиция на устройството. Това може да се направи във функцията за делегиране на визуализатор на 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
и node
. node
е нормален възел SceneKit, поставен в точното положение и ориентация на равнината. Той няма геометрия, така че е невидим. Използваме го, за да добавим свой собствен равнинен възел, който също е невидим, но съдържа информация за ориентацията на равнината и позицията в anchor
И така, как се записват позицията и ориентацията в ARPlaneAnchor
Позицията, ориентацията и мащабът са кодирани в матрица 4x4. Ако имам възможност да избера една математическа концепция, която да научите, това несъмнено ще бъде матрици. Както и да е, можем да заобиколим това, като опишем тази матрица 4х4, както следва: Брилянтен двуизмерен масив, съдържащ 4х4 числа с плаваща запетая. Чрез умножаването на тези числа по определен начин от 3D връх, v1, в неговото локално пространство, се получава нов 3D връх, 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
е матрица 4х4, която съдържа цялата информация за преобразуване на откритата равнина, докато result.worldTransform.translation
е удобна функция, която връща само позицията.
Сега разполагаме с цялата информация, от която се нуждаем, за да пуснем 3D обект върху открити повърхности с дадена 2D точка на екрана. И така, нека започнем да рисуваме.
Нека първо да обясним подхода за рисуване на фигури, който следва човешкия пръст в компютърното зрение. Рисуването на фигури се извършва чрез откриване на всяко ново място за движещия се пръст, пускане на връх на това място и свързване на всеки връх с предишния. Върховете могат да бъдат свързани чрез обикновена линия или чрез крива на Безие, ако се нуждаем от плавен изход.
За простота ще следваме малко наивен подход за рисуване. За всяко ново местоположение на пръста ще пуснем много малка кутия със заоблени ъгли и почти нулева височина върху открития план. Ще изглежда сякаш е точка. След като потребителят завърши рисуването и избере 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 представи в iOS 11, е Vision Framework. Той предоставя някои техники за компютърно зрение по доста удобен и ефективен начин. По-специално, ще използваме техниката за проследяване на обекти за нашия урок за разширена реалност. Проследяването на обекти работи по следния начин: Първо, ние му предоставяме изображение и координати на квадрат в границите на изображението за обекта, който искаме да проследяваме. След това извикваме някаква функция за инициализиране на проследяването. Накрая подаваме ново изображение, в което позицията на този обект се променя и резултатът от анализа на предишната операция. Като се има предвид това, ще ни върне новото местоположение на обекта.
Ще използваме малък трик. Ще помолим потребителя да остави ръка на масата, сякаш държи писалка, и да се увери, че миниатюрата им е обърната към камерата, след което трябва да почука върху миниатюрата си на екрана. Има две точки, които трябва да бъдат доразработени тук. Първо, миниизображението трябва да има достатъчно уникални функции, за да се проследи чрез контраста между бялото миниизображение, кожата и таблицата. Това означава, че по-тъмният пигмент на кожата ще доведе до по-надеждно проследяване. Второ, тъй като потребителят опира ръцете си на масата и тъй като вече откриваме таблицата като равнина, проектирането на местоположението на миниатюрата от 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, изтеглете iOS 11 на iPhone 6s или по-нова версия и създайте нов проект ARKit от New> Project> Augmented Reality App. Можете също да проверите примерния код на AR, предоставен от Apple тук: https://developer.apple.com/arkit/
Разширената реалност е процесът на наслагване на цифрово видео и други елементи на CGI върху видео от реалния свят. Създаването на качествено CGI наслагване на видео, заснето от камера, изисква използването на множество сензори и софтуерни решения.
Разширената реалност има многобройни потенциални приложения в различни индустрии. AR може да се използва за осигуряване на обучение във виртуални настройки, които иначе би било трудно да се пресъздадат за целите на обучението, напр. космически полети, медицинска индустрия, определени инженерни проекти.
Докато AR се основава на концепцията за наслагване на CGI върху видео от реалния свят, VR генерира напълно синтетична 3D среда, без елементи от реалния живот. В резултат на това VR е по-завладяваща, докато AR предлага малко повече гъвкавост и свобода на потребителя.