《 iPhone X ARKit Face Tracking 》
歡迎大家前往騰訊雲社區,獲取更多騰訊海量技術實踐幹貨哦~
本文來自於騰訊Bugly公眾號(weixinBugly), 作者:jennysluo,未經作者同意,請勿轉載,原文地址:http://mp.weixin.qq.com/s/YJ82vQYHAMmtueDgHgKcNA
iPhone X前置深度攝像頭帶來了Animoji和face ID,同時也將3D Face Tracking的接口開放給了開發者。有幸去Cupertino蘋果總部參加了iPhone X的封閉開發,本文主要分享一下iPhone X上使用ARKit進行人臉追蹤及3D建模的相關內容。
iPhone X前置深度攝像頭識別人臉視頻
新增接口
ARFaceTrackingConfiguration
ARFaceTrackingConfiguration
利用iPhone X前置深度攝像頭識別用戶的人臉。由於不同的AR體驗對iOS設備有不同的硬件要求,所有ARKit配置要求iOS設備至少使用A9及以上處理器,而face tracking更是僅在帶有前置深度攝像頭的iPhone X上才會有。因此在進行AR配置之前,首先我們需要確認用戶設備是否支持我們將要創建的AR體驗
ARFaceTrackingConfiguration.isSupported
對於不支持該ARKit配置的設備,提供其它的備選方案或是降級策略也是一種不錯的解決方案。然而如果你的app確定ARKit是其核心功能,在info.plist
UIRequiredDeviceCapabilities
裏可以確保你的app只在支持ARKit的設備上可用。當我們配置使用
ARFaceTrackingConfiguration
,session
會自動添加ARFaceAnchor
對象到其anchor list
中。每一個face anchor
提供了包含臉部位置,方向,拓撲結構,以及表情特征等信息。另外,當我們開啟isLightEstimationEnabled
設置,ARKit會將檢測到的人臉作為燈光探測器以估算出的當前環境光的照射方向及亮度等信息(詳見ARDirectionalLightEstimate對象),這樣我們可以根據真實的環境光方向及強度去對3D模型進行照射以達到更為逼真的AR效果。ARFrame
當我們設置為基於人臉的AR(ARFaceTrackingConfiguration),session
刷新的frame裏除了包含彩色攝像頭采集的顏色信息以外(capturedImage),還包含了由深度攝像頭采集的深度信息(capturedDepthData)。其結構和iPhone7P後置雙攝采集的深度信息一樣為AVDepthData
。當設置其它AR模式時該屬性為nil。在iPhone X上實測效果比7P後置的深度信息更為準確,已經可以很好的區分人像和背景區域。
需註意的是,深度攝像頭采樣頻率和顏色攝像頭並不一致,因此ARFrame的capturedDepthData
屬性也可能是nil。實測下來在幀率60的情況下,每4幀裏有1幀包含深度信息。
ARFaceAnchor
前面說過,當我們配置使用ARFaceTrackingConfiguration
,session
會自動添加ARFaceAnchor
對象到其anchor list
中。每一個face anchor
提供了包含臉部位置,方向,拓撲結構,以及表情特征等信息。比較遺憾的是,當前版本只支持單人臉識別,未來如果ARKit提供多人臉識別後開發者應該也能較快的進行版本升級。
-
人臉位置和方向
父類ARAnchor的transform屬性以一個4*4矩陣描述了當前人臉在世界坐標系的位置及方向。我們可以使用該矩陣來放置虛擬3D模型以實現貼合到臉部的效果(如果使用SceneKit,會有更便捷的方式來完成虛擬模型的佩戴過程,後面會詳述)。該變換矩陣創建了一個“人臉坐標系”以將其它模型放置到人臉的相對位置,其原點在人頭中心(鼻子後方幾厘米處),且為右手坐標系—x軸正方向為觀察者的右方(也就是檢測到的人臉的左方),y軸正方向延人頭向上,z軸正方向從人臉向外(指向觀察者)
-
人臉拓撲結構 ARFaceGeometry
ARFaceAnchor的geometry屬性封裝了人臉具體的拓撲結構信息,包括頂點坐標、紋理坐標、以及三角形索引(實測下來單個人臉包含1220個3D頂點以及2304個三角面片信息,精準度已經相當高了)。
有了這些數據,我們可以實現各種貼合人臉的3D面皮—比如虛擬妝容或者紋身等。我們也可以用其創建人臉的幾何形狀以完成對虛擬3D模型的遮擋。
如果我們使用SceneKit + Metal做渲染,可以十分方便的通過ARSCNFaceGeometry完成人臉建模,後面會詳細說明。
-
面部表情追蹤
blendShapes屬性提供了當前人臉面部表情的一個高階模型,表示了一系列的面部特征相對於無表情時的偏移系數。聽起來也許有些抽象,具體來說,可以看到blendShapes是一個NSDictionary,其key有多種具體的面部表情參數可選,比如ARBlendShapeLocationMouthSmileLeft代表左嘴角微笑程度,而ARBlendShapeLocationMouthSmileRight表示右嘴角的微笑程度。每個key對應的value是一個取值範圍為0.0 - 1.0的浮點數,0.0表示中立情況的取值(面無表情時),1.0表示最大程度(比如左嘴角微笑到最大值)。ARKit裏提供了51種非常具體的面部表情形變參數,我們可以自行選擇采用較多的或者只是采用某幾個參數來達成我們的目標,比如,用“張嘴”、“眨左眼”、“眨右眼”來驅動一個卡通人物。
創建人臉AR體驗
以上介紹了一下使用ARKit Face Tracking所需要了解的新增接口,下面來詳細說明如何搭建一個app以完成人臉AR的真實體驗。
創建一個ARKit應用可以選擇3種渲染框架,分別是SceneKit,SpriteKit和Metal。對於做一個自拍類的app,SceneKit無疑是一種很好的選擇。其接口方便易用,底層使用Metal2渲染,且提供了多種材質以及光照模型,通常情況下無需自定義shader即可完成3D貼臉以及3D掛件的渲染。首先我們需要添加一個ARSCNView,設置好scene以及delegate,在viewWillAppear裏添加下面兩行代碼
ARFaceTrackingConfiguration *configuration = [ARFaceTrackingConfiguration new];
[self.sceneView.session runWithConfiguration:configuration];
這樣就創建好了一個ARKit Face Tracking的場景,此時前置攝像頭已經開啟並實時檢測/追蹤人臉信息。當檢測到人臉之後,我們可以通過delegate更新人臉anchor的函數來同步更新我們自定義的3D面皮或者3D模型。
- (void)renderer:(id <SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor; - (void)renderer:(id <SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor;
比如我們要放置一張京劇臉譜貼合到用戶臉上,我們可以生成一個臉譜的SCNNode
- (SCNNode *)textureMaskNode { if (!_textureMaskNode) { _textureMaskNode = [self makeFaceGeometry:^(SCNMaterial *material) { material.fillMode = SCNFillModeFill; material.diffuse.contents = [UIImage imageNamed:@"maskImage.png"]; } fillMesh:NO]; _textureMaskNode.name = @"textureMask"; } return _textureMaskNode; } - (SCNNode*)makeFaceGeometry:(void (^)(SCNMaterial*))materialSetup fillMesh:(BOOL)fillMesh { #if TARGET_OS_SIMULATOR return [SCNNode new]; #else id<MTLDevice> device = self.sceneView.device; ARSCNFaceGeometry *geometry = [ARSCNFaceGeometry faceGeometryWithDevice:device fillMesh:fillMesh]; SCNMaterial *material = geometry.firstMaterial; if(material && materialSetup) materialSetup(material); return [SCNNode nodeWithGeometry:geometry]; #endif }
註意這個fillMesh參數,如果設置為NO,生成的“蒙皮”眼睛和嘴巴區域是鏤空的,反之亦然。模型建好以後,我們需要在face anchor刷新的時候同步更新3D蒙皮的幾何信息使其與人臉達到貼合的狀態。
- (void)renderer:(id<SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor { ARFaceAnchor *faceAnchor = (ARFaceAnchor *)anchor; if (!faceAnchor || ![faceAnchor isKindOfClass:[ARFaceAnchor class]]) { return; } if (_needRenderNode) { [node addChildNode:self.textureMaskNode]; _needRenderNode = NO; } ARSCNFaceGeometry *faceGeometry = (ARSCNFaceGeometry *)self.textureMaskNode.geometry; if( faceGeometry && [faceGeometry isKindOfClass:[ARSCNFaceGeometry class]] ) { [faceGeometry updateFromFaceGeometry:faceAnchor.geometry]; } }
這裏我們是直接將蒙皮node添加到face node作為其childNode,因而不需要對其位置信息做額外處理就能跟隨人臉移動。如果是直接加到場景的rootNode上面,還需要同步更新其位置、方向等屬性。打上方向光之後,蒙皮顯得十分貼合立體。
SCNLight *directional = [SCNLight light]; directional.type = SCNLightTypeDirectional; directional.color = [UIColor colorWithWhite:1 alpha:1.0]; directional.castsShadow = YES; _directionalLightNode = [SCNNode node]; _directionalLightNode.light = directional;
demo裏我們做了一個戲劇變臉效果,當用戶遮擋人臉後將其臉譜換掉。實現的原理是當用戶人臉檢測不到時記一個標誌,再次檢測到用戶人臉時將其3D蒙皮的貼圖換掉。比較坑的是,ARKit 檢測不到人臉時也並未將其node移除,因此delegate也沒有回調
- (void)renderer:(id <SCNSceneRenderer>)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor;
那麽如何知道face tracking失敗呢?可以通過每一幀刷新的時候遍歷查找到ARAnchor,檢測其isTrackFace狀態。
- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame { for (ARAnchor *anchor in frame.anchors) { if ([anchor isKindOfClass:[ARFaceAnchor class]]) { ARFaceAnchor *faceAnchor = (ARFaceAnchor *)anchor; self.isTrackFace = faceAnchor.isTracked; } } }
同樣的,我們可以在人臉node上添加其他3D模型(比如3D眼鏡)的node使其跟隨人臉移動,可以達到非常逼真的效果,SceneKit支持多種格式的模型加載,比如obj、dae等。如果使用的是dae且不是放在bundle裏面,需要提前用scntool壓縮,模型加載及動畫播放所遇到的坑此處不贅述。需要註意的是,當我們給用戶戴上3D眼鏡或帽子的時候,我們當然是希望模型的後面部分能正確的被用戶的臉給擋住以免露出馬腳。因此我們需要渲染一個用來遮擋的node並實時更新其幾何信息,使用戶在頭歪向一邊的時候3D眼鏡的鏡架能被人臉正確遮擋。
- (SCNNode *)occlusionMaskNode { if (!_occlusionMaskNode) { _occlusionMaskNode = [self makeFaceGeometry:^(SCNMaterial *material) { material.colorBufferWriteMask = SCNColorMaskNone; material.lightingModelName = SCNLightingModelConstant; material.writesToDepthBuffer = true; } fillMesh:YES]; _occlusionMaskNode.renderingOrder = -1; _occlusionMaskNode.name = @"occlusionMask"; } return _occlusionMaskNode; }
同樣的我們需要在face anchor刷新的時候通過updateFromFaceGeometry:更新其幾何信息。需要註意的是,由於ARKit只對人臉區域進行建模,在3D模型設計的時候還需去掉一些不必要的部件:比如眼鏡的模型就不需要添加鏡腳,因為耳朵部分並沒有東西可以去做遮擋。
3D模型設計的時候還需去掉一些不必要的部件效果視頻
如果要做類似上面視頻中的鏡片反射效果,使用SceneKit也十分方便,只需要將鏡片的反射貼圖(SCNMaterial的reflective屬性)映射到cube map即可,支持以下4種設置方案
- A horizontal strip image where
6 * image.height == image.width
- A vertical strip image where
image.height == 6 * image.width
- A spherical projection image (latitude/longitude) where
2 * image.height == image.width
- A NSArray of 6 images. This array must contain images of the exact same dimensions, in the following order, in a left-handed coordinate system: +X, -X, +Y, -Y, +Z, -Z (or Right, Left, Top, Bottom, Front, Back).
除了人臉的空間位置信息和幾何信息,ARKit還提供了十分精細的面部表情形變參數,用來做類似張嘴觸發是完全沒問題的,我們還可以用其實現一些有趣的效果。比如,根據臉部微笑的程度去替換3D蒙皮的diffuse貼圖,使用戶笑的時候會出現誇張的效果。
- (UIImage *)meshImageWithBlendShapes:(NSDictionary *)blendShapes { if (self.diffuseArray.count == 0) return nil; NSUInteger _count = self.diffuseArray.count; NSNumber *smileLeft = blendShapes[ARBlendShapeLocationMouthSmileLeft]; NSNumber *smileRight = blendShapes[ARBlendShapeLocationMouthSmileRight]; CGFloat smileBlend = (smileLeft.floatValue + smileRight.floatValue) / 2; smileBlend = smileBlend - 0.1; if (smileBlend < 0.0) smileBlend = 0.0; NSUInteger index = (NSUInteger)(smileBlend * _count / 0.5); if (index > _count - 1) { index = _count - 1; } return self.diffuseArray[index]; }
將幾個臉部表情系數的組合映射到一個具體的分值,可以實現face dance那樣有趣的表情模仿。還可以將其映射到3D虛擬人物的形變上以實現animoji的效果,此處開發者們可自行腦洞大開:)
拍照 & 錄制
可能是由於SceneKit原本是設計用來做遊戲渲染的框架,只提供了一個截屏的接口snapshot,拍照尚可調用,而錄制並不是特別方便。如果你計劃通過SCNRenderer 的函數
+ (instancetype)rendererWithContext:(nullable EAGLContext *)context options:(nullable NSDictionary *)options;
將其放在OpenGL context裏渲染,可以避開視頻錄制的坑,但也許會遇到更新人臉geometry等其他問題。如果采用默認的Metal方案,設置一個定時器,將snapshot獲取到的UIImage轉成pixel buffer再進行視頻編碼,很難做到每秒30幀的同步輸出。如果你的app在錄制的時候UI非常幹凈,可以采用系統錄屏框架replaykit來進行屏幕錄制;如果你想完全掌控每一幀的輸出以方便在錄制過程中加上水印,可以用SCNRenderer的render函數
- (void)renderAtTime:(CFTimeInterval)time viewport:(CGRect)viewport commandBuffer:(id <MTLCommandBuffer>)commandBuffer passDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor
將場景渲染到一個id對象中,通過紋理綁定的方式將其轉換為CVPixelBufferRef以完成視頻編碼。某位朋友提醒,可以通過method swizzling的方式直接獲取CAMetalLayer的nextDrawable,甚至可以避免上訴方案錄制時產生的額外GPU開銷,有興趣的朋友可以嘗試一下。
寫在末尾
這次能有機會參加Apple的封閉開發且是如此有趣的模塊,在沒有網絡的情況下摸索著做出demo,接觸到了最前沿的AR相關技術,對我來說是一份非常寶貴的經歷。心懷感恩,踏步前行。
demo效果視頻
相關閱讀
網頁加速特技之 AMP
表格行與列邊框樣式處理的原理分析及實戰應用
EB級別雲存儲是如何漲成的?
此文已由作者授權騰訊雲技術社區發布,轉載請註明原文出處
原文鏈接:https://cloud.tencent.com/community/article/388360?utm_source=bky
《 iPhone X ARKit Face Tracking 》