ARKit-平面檢測(cè)與可視化

翻譯自:https://blog.markdaws.net

相關(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í)世界一樣的交互你需要知道用戶點(diǎn)擊了桌子上、正在看地板或者其他和平常生活類似的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)用戶移動(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)

sofafeatures
sofafeatures

下一張圖片是我照我的冰箱門的,你可以注意到這并沒(méi)有幾個(gè)特征點(diǎn)

doorfeatures
doorfeatures

這是非常重要的,因?yàn)闉榱俗孉RKit檢測(cè)到特征點(diǎn)你必須摘到有足夠多的特征點(diǎn)的東西去檢測(cè)。那些會(huì)造成糟糕的特征點(diǎn)提取的情況有:

  1. 糟糕的光線--弱光或者帶有高光反射的強(qiáng)光。杜絕有糟糕光線的環(huán)境
  2. 缺少紋理--如果你把相機(jī)對(duì)準(zhǔn)一個(gè)白墻,這里真的沒(méi)有什么特別的東西用來(lái)提取,ARKit就不能找到并且追蹤你。杜絕對(duì)準(zhǔn)一些純色或者發(fā)光的區(qū)域
  3. 快速移動(dòng)--這對(duì)于ARKit是情況各異的,通常如果你只是用圖片去檢測(cè)和估計(jì)3D姿態(tài),如果你移動(dòng)攝像頭太快你就會(huì)得到一些糟糕的圖片這會(huì)造成追蹤的失敗。不過(guò)ARKit用了叫做VIO的算法,所以ARKit除了用圖像信息以外還用到了設(shè)備運(yùn)動(dòng)傳感器的信息去估計(jì)用戶的姿態(tài)。這讓ARKit在追蹤上面很健壯

添加可視化的調(diào)試信息

在我們開始之前,給應(yīng)用添加一些調(diào)試信息是非常有用的,也就是渲染原始的世界的同時(shí)也把ARKit檢測(cè)到的特征點(diǎn)信息渲染出來(lái),這是很有用的可以讓你知道你是不是在一個(gè)可以追蹤的很好的地方。要做到這個(gè)你可以在我們的ARSCNView實(shí)例中打開調(diào)試選項(xiàng):

self.sceneView.debugOptions = ARSCNDebugOptionShowWorldOrigin | ARSCNDebugOptionShowFeaturePoints;

檢測(cè)平面幾何體

在ARKit中你可以通過(guò)在你的會(huì)話配置中設(shè)置planeDetection屬性指定你想要檢測(cè)水平面。這個(gè)值可以被設(shè)置為ARPlaneDetectionHorizontal或者ARPlaneDetectionNone

一旦你設(shè)置了這個(gè)屬性,你就開始得到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類型的,從這里我們可以得到這個(gè)平面的擴(kuò)展和中心信息

渲染平面

有了上面的信息,我們現(xiàn)在可以在虛擬的世界里畫一個(gè)SceneKit 3D平面。我們創(chuàng)建了一個(gè)Plane類繼承自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類,回到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類的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è)平面

kitchenplane
kitchenplane

這是地板的,你可以看到當(dāng)你在四周走動(dòng)的時(shí)候,ARKit在持續(xù)的鋪滿新屏幕,這在你開發(fā)應(yīng)用的時(shí)候是一個(gè)要注意的點(diǎn),用戶必須一上來(lái)就在四周走動(dòng)一圈然后再放置東西,所以在幾何體還不是足夠好到能用的時(shí)候給用戶好的視覺(jué)上的提示是一個(gè)很重要的部分

floorplane
floorplane

下面是和上面同一場(chǎng)景幾秒鐘之后的效果,ARKit已經(jīng)把所有的平面整合成了一個(gè)平面。提示,在ARSCNViewDelegate中你需要處理這樣的情況當(dāng)ARKit合并幾個(gè)平面時(shí)會(huì)刪除一些ARPlaneAnchor的實(shí)例

floorplane2
floorplane2

這是一個(gè)很有趣的場(chǎng)景,我在樓上距離地面大概有12到15英尺,在糟糕的光線條件下,ARKit依然可以提取出一個(gè)平面,難以置信!

aboveplane
aboveplane

這是一個(gè)梯子旁邊的比較窄的墻,注意平面在實(shí)際的表面邊緣超出部分是怎么延伸的

smallplane
smallplane

重要的結(jié)論

  1. 不要期望于一個(gè)平面可以完美的對(duì)齊表面,你可以從視頻里面看出來(lái),平面被檢測(cè)出來(lái),但是角度可能會(huì)不準(zhǔn)確,所以你如果開發(fā)一個(gè)應(yīng)用想要得到一個(gè)特別準(zhǔn)確的幾何體你可能會(huì)失望

  2. 邊緣不是特別好,平面經(jīng)常會(huì)大一點(diǎn)或者小一點(diǎn),不要試著做一個(gè)需要完美的對(duì)齊的應(yīng)用

  3. 追蹤很健壯并且很快,你可以看到我移動(dòng)的很快但是追蹤的依然很棒

  4. 特征點(diǎn)檢測(cè)的效果很好,在糟糕的光線和較遠(yuǎn)的距離下依然檢測(cè)到了一些平面

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在第一個(gè) hello world ARKit app 里我們給項(xiàng)目做了初始設(shè)置,并在真實(shí)世界里渲染了一個(gè) 3D 立...
    張嘉夫閱讀 14,077評(píng)論 15 49
  • ARKit ARKit框架通過(guò)集成iOS設(shè)備攝像頭和運(yùn)動(dòng)功能,在您的應(yīng)用程序或游戲中產(chǎn)生增強(qiáng)現(xiàn)實(shí)體驗(yàn)。 概述 增強(qiáng)...
    暗夜夜夜行路閱讀 5,996評(píng)論 0 17
  • 村上說(shuō)肉體才是我們的神殿,好好供奉她,才是實(shí)現(xiàn)我們美好生活的根本。當(dāng)身體疲憊不堪時(shí)候真的什么都不想做不想動(dòng)想都覺(jué)得...
    肉月閱讀 297評(píng)論 0 0
  • 我建立了一個(gè)關(guān)于各個(gè)行業(yè)的銷售交流群,大一起討論銷售經(jīng)驗(yàn),你要有興趣參加嗎?群里有很多銷售資料都是討論留下,很不錯(cuò)...
    25579笨小孩閱讀 2,280評(píng)論 0 1
  • 但他并不看好火爆全國(guó)的P2P互聯(lián)網(wǎng)金融,曾多次公開批評(píng),而且早于2014年就暫停了P2P網(wǎng)貸公司的審批。 ” 在黃...
    泡個(gè)小菜閱讀 810評(píng)論 1 0

友情鏈接更多精彩內(nèi)容