相關(guān)工程地址:https://github.com/markdaws/arkit-by-example
在我們的第一個(gè)hello world ARKit應(yīng)用中我們?cè)O(shè)置了我們的工程并在真實(shí)的世界中渲染了一個(gè)虛擬的3D立方體宗苍,并且會(huì)在你四周移動(dòng)的時(shí)候持續(xù)追蹤
在這篇文章中熔任,我們會(huì)著眼于在真實(shí)場(chǎng)景中提取3D幾何體的信息并且將它進(jìn)行可視化。檢測(cè)幾何體對(duì)于增強(qiáng)現(xiàn)實(shí)的應(yīng)用是非常重要的拭抬,因?yàn)槿绻阆胱龅胶驮谡鎸?shí)世界一樣的交互你需要知道用戶(hù)點(diǎn)擊了桌子上梧税、正在看地板或者其他和平常生活類(lèi)似的3D交互
ARKit可以檢測(cè)平面沦疾。一旦我們檢測(cè)到了一個(gè)平面称近,我們可以把他進(jìn)行可視化,然后把這個(gè)平面的規(guī)格和角度展現(xiàn)出來(lái)
計(jì)算機(jī)視覺(jué)
在我們深入代碼之前哮塞,理解清楚在ARKit下面發(fā)生了什么是非常有用的刨秆,因?yàn)檫@個(gè)技術(shù)還不完美,有很多情況會(huì)降低或者影響到你的應(yīng)用的表現(xiàn)
AR的目的就是可以在真實(shí)世界的特別的點(diǎn)上插入虛擬的內(nèi)容忆畅,并且當(dāng)你在真實(shí)的世界移動(dòng)的時(shí)候可以持續(xù)的追蹤衡未。ARKit的基本處理流程會(huì)把iOS的攝像頭采集到的視頻流的每一幀進(jìn)行處理然后把特征點(diǎn)提取出來(lái)。特征點(diǎn)可以是很多東西家凯,但是你如果想去在圖片中檢測(cè)出有趣的特征點(diǎn)你可以在多個(gè)幀中進(jìn)行追蹤缓醋。一個(gè)特征點(diǎn)可能是一個(gè)物體的一個(gè)角或者一個(gè)布的紋理的一個(gè)邊等等。有很多方法去生成這些特征點(diǎn)绊诲,你可以在網(wǎng)上讀到更多的信息送粱,但對(duì)于我們的目標(biāo)指導(dǎo)我們可以從一個(gè)圖像中提取到很多獨(dú)特的進(jìn)行認(rèn)證的特征點(diǎn)就夠了
一旦你有了這些特征點(diǎn),你就可以在多幀之間追蹤這些特征點(diǎn)不論你走到哪里掂之,你可以獲取這些相關(guān)點(diǎn)然后估計(jì)3D姿態(tài)信息抗俄,比如說(shuō)當(dāng)前的攝像頭位置以及這些特征點(diǎn)的位置。當(dāng)用戶(hù)移動(dòng)的越多世舰,我們就會(huì)獲得更多的特征點(diǎn)动雹,這些3D姿態(tài)估計(jì)也會(huì)得到改善
在平面檢測(cè)中,一旦你有了一定數(shù)量的特征點(diǎn)冯乘,你就可以嘗試把平面對(duì)著那些點(diǎn)放到合適的位置洽胶,然后在尺寸晒夹、角度和位置上找到最佳的匹配裆馒。ARKit在持續(xù)分析這些特征點(diǎn)然后在代碼中向我們報(bào)告所有他找到的平面
下面是我手機(jī)在檢測(cè)我的沙發(fā)扶手的一個(gè)截屏,你可以看到這個(gè)布有很好的紋理丐怯,大量的獨(dú)特又有趣的特征點(diǎn)可以被追蹤到喷好,每一個(gè)十字星就是一個(gè)ARKit找到的獨(dú)特的特征點(diǎn)
下一張圖片是我照我的冰箱門(mén)的,你可以注意到這并沒(méi)有幾個(gè)特征點(diǎn)
這是非常重要的读跷,因?yàn)闉榱俗孉RKit檢測(cè)到特征點(diǎn)你必須摘到有足夠多的特征點(diǎn)的東西去檢測(cè)梗搅。那些會(huì)造成糟糕的特征點(diǎn)提取的情況有:
- 糟糕的光線(xiàn)--弱光或者帶有高光反射的強(qiáng)光。杜絕有糟糕光線(xiàn)的環(huán)境
- 缺少紋理--如果你把相機(jī)對(duì)準(zhǔn)一個(gè)白墻效览,這里真的沒(méi)有什么特別的東西用來(lái)提取无切,ARKit就不能找到并且追蹤你。杜絕對(duì)準(zhǔn)一些純色或者發(fā)光的區(qū)域
- 快速移動(dòng)--這對(duì)于ARKit是情況各異的丐枉,通常如果你只是用圖片去檢測(cè)和估計(jì)3D姿態(tài)哆键,如果你移動(dòng)攝像頭太快你就會(huì)得到一些糟糕的圖片這會(huì)造成追蹤的失敗。不過(guò)ARKit用了叫做VIO的算法瘦锹,所以ARKit除了用圖像信息以外還用到了設(shè)備運(yùn)動(dòng)傳感器的信息去估計(jì)用戶(hù)的姿態(tài)籍嘹。這讓ARKit在追蹤上面很健壯
添加可視化的調(diào)試信息
在我們開(kāi)始之前闪盔,給應(yīng)用添加一些調(diào)試信息是非常有用的,也就是渲染原始的世界的同時(shí)也把ARKit檢測(cè)到的特征點(diǎn)信息渲染出來(lái),這是很有用的可以讓你知道你是不是在一個(gè)可以追蹤的很好的地方辱士。要做到這個(gè)你可以在我們的ARSCNView實(shí)例中打開(kāi)調(diào)試選項(xiàng):
self.sceneView.debugOptions = ARSCNDebugOptionShowWorldOrigin | ARSCNDebugOptionShowFeaturePoints;
檢測(cè)平面幾何體
在ARKit中你可以通過(guò)在你的會(huì)話(huà)配置中設(shè)置planeDetection屬性指定你想要檢測(cè)水平面泪掀。這個(gè)值可以被設(shè)置為ARPlaneDetectionHorizontal或者ARPlaneDetectionNone
一旦你設(shè)置了這個(gè)屬性,你就開(kāi)始得到ARSCNViewDelegate的回調(diào)信息颂碘。這里有很多的方法异赫,我們要用到的第一個(gè)是
/**
Called when a new node has been mapped to the given anchor.
@param renderer The renderer that will render the scene.
@param node The node that maps to the anchor.
@param anchor The added anchor.
*/
- (void)renderer:(id <SCNSceneRenderer>)renderer
didAddNode:(SCNNode *)node
forAnchor:(ARAnchor *)anchor {
}
這個(gè)方法在ARKit每次認(rèn)為他檢測(cè)到了一個(gè)新的平面時(shí)進(jìn)行回調(diào)。我們會(huì)得到兩個(gè)信息头岔,node和anchor祝辣。這個(gè)SCNNode實(shí)例是一個(gè)ARKit創(chuàng)建的SceneKit node,他有一些屬性可以設(shè)置比如位置和角度切油,然后我們還得到了anchor的實(shí)例蝙斜,這個(gè)告訴我們這個(gè)被找到的anchor更多的信息,比如說(shuō)尺寸以及這個(gè)平面的中心
這個(gè)anchor是ARPlaneAnchorl類(lèi)型的澎胡,從這里我們可以得到這個(gè)平面的擴(kuò)展和中心信息
渲染平面
有了上面的信息孕荠,我們現(xiàn)在可以在虛擬的世界里畫(huà)一個(gè)SceneKit 3D平面。我們創(chuàng)建了一個(gè)Plane類(lèi)繼承自SCNNode去做這件事情攻谁。在構(gòu)建方法里面我們創(chuàng)建了這個(gè)平面以及設(shè)置他的尺寸:
// Create the 3D plane geometry with the dimensions reported
// by ARKit in the ARPlaneAnchor instance
self.planeGeometry = [SCNPlane planeWithWidth:anchor.extent.x height:anchor.extent.z];
SCNNode *planeNode = [SCNNode nodeWithGeometry:self.planeGeometry];
// Move the plane to the position reported by ARKit
planeNode.position = SCNVector3Make(anchor.center.x, 0, anchor.center.z);
// Planes in SceneKit are vertical by default so we need to rotate
// 90 degrees to match planes in ARKit
planeNode.transform = SCNMatrix4MakeRotation(-M_PI / 2.0, 1.0, 0.0, 0.0);
// We add the new node to ourself since we inherited from SCNNode
[self addChildNode:planeNode];
既然我們有了自己的Plane類(lèi)稚伍,回到ARSCNViewDelegate的回調(diào)方法,我們可以在ARKit報(bào)告了新的Anchor時(shí)創(chuàng)建我們的Plane:
- (void)renderer:(id <SCNSceneRenderer>)renderer
didAddNode:(SCNNode *)node
forAnchor:(ARAnchor *)anchor {
if (![anchor isKindOfClass:[ARPlaneAnchor class]]) {
return;
}
Plane *plane = [[Plane alloc] initWithAnchor: (ARPlaneAnchor *)anchor];
[node addChildNode:plane];
}
更新平面
如果你運(yùn)行了上面的程序戚宦,當(dāng)你向四周走動(dòng)時(shí)你可以看到新的平面在虛擬世界中渲染出來(lái)个曙,盡管這些平面不是在你走動(dòng)的時(shí)候合適的增長(zhǎng)的。ARKit在持續(xù)的分析這個(gè)場(chǎng)景受楼,當(dāng)它發(fā)現(xiàn)一個(gè)平面比他之前預(yù)計(jì)的大了或者笑了的時(shí)候垦搬,他會(huì)更新這個(gè)平面的擴(kuò)展值。所以我們需更新已經(jīng)渲染了的Plane
我們可以通過(guò)ARSCNViewDelegate中的一個(gè)方法得到這些更新的信息:
- (void)renderer:(id <SCNSceneRenderer>)renderer
didUpdateNode:(SCNNode *)node
forAnchor:(ARAnchor *)anchor {
// See if this is a plane we are currently rendering
Plane *plane = [self.planes objectForKey:anchor.identifier];
if (plane == nil) {
return;
}
[plane update:(ARPlaneAnchor *)anchor];
}
在我們的Plane類(lèi)的update方法中我們可以更新平面的寬度和高度:
- (void)update:(ARPlaneAnchor *)anchor {
self.planeGeometry.width = anchor.extent.x;
self.planeGeometry.height = anchor.extent.z;
// When the plane is first created it's center is 0,0,0 and
// the nodes transform contains the translation parameters.
// As the plane is updated the planes translation remains the
// same but it's center is updated so we need to update the 3D
// geometry position
self.position = SCNVector3Make(anchor.center.x, 0, anchor.center.z);
}
現(xiàn)在我們已經(jīng)讓平面渲染和更新了艳汽,我們可以進(jìn)到應(yīng)用里面看看了
提取信息的結(jié)果
下面是我檢測(cè)的視頻的一些截圖
這是廚房的猴贰,ARKit做得很好,準(zhǔn)確的找到了平面的擴(kuò)展信息和角度信息河狐,正好貼合這個(gè)平面
這是地板的米绕,你可以看到當(dāng)你在四周走動(dòng)的時(shí)候,ARKit在持續(xù)的鋪滿(mǎn)新屏幕馋艺,這在你開(kāi)發(fā)應(yīng)用的時(shí)候是一個(gè)要注意的點(diǎn)栅干,用戶(hù)必須一上來(lái)就在四周走動(dòng)一圈然后再放置東西,所以在幾何體還不是足夠好到能用的時(shí)候給用戶(hù)好的視覺(jué)上的提示是一個(gè)很重要的部分
下面是和上面同一場(chǎng)景幾秒鐘之后的效果捐祠,ARKit已經(jīng)把所有的平面整合成了一個(gè)平面碱鳞。提示,在ARSCNViewDelegate中你需要處理這樣的情況當(dāng)ARKit合并幾個(gè)平面時(shí)會(huì)刪除一些ARPlaneAnchor的實(shí)例
這是一個(gè)很有趣的場(chǎng)景雏赦,我在樓上距離地面大概有12到15英尺劫笙,在糟糕的光線(xiàn)條件下芙扎,ARKit依然可以提取出一個(gè)平面,難以置信填大!
這是一個(gè)梯子旁邊的比較窄的墻戒洼,注意平面在實(shí)際的表面邊緣超出部分是怎么延伸的
重要的結(jié)論
不要期望于一個(gè)平面可以完美的對(duì)齊表面,你可以從視頻里面看出來(lái)允华,平面被檢測(cè)出來(lái)圈浇,但是角度可能會(huì)不準(zhǔn)確,所以你如果開(kāi)發(fā)一個(gè)應(yīng)用想要得到一個(gè)特別準(zhǔn)確的幾何體你可能會(huì)失望
邊緣不是特別好靴寂,平面經(jīng)常會(huì)大一點(diǎn)或者小一點(diǎn)磷蜀,不要試著做一個(gè)需要完美的對(duì)齊的應(yīng)用
追蹤很健壯并且很快百炬,你可以看到我移動(dòng)的很快但是追蹤的依然很棒
特征點(diǎn)檢測(cè)的效果很好褐隆,在糟糕的光線(xiàn)和較遠(yuǎn)的距離下依然檢測(cè)到了一些平面