iOS ARKit - 面部追踪

ARSCNView

  • ARSCNView 类提供了创建增强现实体验的最简单方法,该体验将虚拟3D内容与现实世界的设备相机视图相结合。当我们运行 ARSCNView 提供的 ARSession 对象时:
  • ARSCNView 会自动将设备摄像头的实时视频输入渲染为场景背景。
  • ARSCNView 的属性 scene 的世界坐标系直接响应由会话配置建立的AR世界坐标系。
  • ARSCNView 会自动移动它的 SceneKit 相机来匹配设备的实际移动。
  • 因为 ARKit 会自动将 SceneKit 空间与现实世界匹配,所以放置虚拟对象的时候如果想调整它在真实世界的位置,那只需要适当地设置该对象(SCNNode对象)的 position 属性就可以了。

SCNNode

  • SCNNode 是 scene 的结构元素,表示3D坐标空间中的位置和变换,可以附加几何,灯光,相机或其他可显示内容。
  • SCNNode 只能用来确定 scene 的空间的坐标系和虚拟内容的逻辑结构,如果想要呈现2D或者3D的虚拟内容,还需要添加 SCNGeometry 对象。
  • 我们可以用代码的方式创建 SCNNode 来进行建模,也可以从3D模型文件中加载一个 SCNNode,或者组合这两种方法。

ARFaceTrackingConfiguration

  • 由于面部跟踪仅适用于具有有前置TrueDepth相机的iOS设备。所以在向用户提供需要面部跟踪的任何功能之前,需要先使用 ARFaceTrackingConfiguration 的 isSupported 属性来判断当前设备上是否可以使用面部跟踪。
if (!ARFaceTrackingConfiguration.isSupported) { return; }
  • ARSession 使用 ARFaceTrackingConfiguration 来运行时,就会使用设备的前置摄像头来检测用户的面部,并将表示面部的 ARFaceAnchor 对象添加到锚点列表中。此外,当我们启用 lightEstimationEnabled 设置时,ARFaceTrackingConfiguration 会把检测到的面部作为光探针,来估算出当前环境光的照射方向和亮度信息。

ARFaceAnchor

  • ARFaceAnchor 包含有相关面部的位置和方位,还有面部拓扑结构以及描述面部表情特征的信息。

  • 从父类 ARAnchor 继承的 transform 属性是一个4*4的矩阵,它在世界坐标中描述了脸部的当前位置、方位、比例。transform 矩阵会创建一个面部坐标系,用于定位相对于面部的其他元素。该坐标系是一个右手坐标系,正 X 方向指向面部自身的左边,正 Y 方向指向面部自身的上方,正 Z 方向从面部指向外面。面部坐标空间的单位为米,原点位于鼻子后方几厘米处,如下图所示。


    image.png

ARFaceGeometry

  • ARFaceAnchor 的 geometry 属性封装了人脸具体的拓扑结构信息,包括顶点坐标缓存、纹理坐标缓存、以及三角形索引缓存。经过测试,人脸拓扑结构中包含1220个顶点坐标、1220个纹理坐标、2304个三角形。

  • 当面部的形状或者表情改变时,ARSession 只提供在面部拓扑网格之间变化的顶点缓冲,表明顶点位置的变化,因为 ARKit 会根据用户面部的形状和表情来使面部拓扑的网格适应变化。

面部表情追踪

  • blendShapes 属性提供了当前人脸面部表情的一个高阶模型,表示了一系列的面部特征相对于无表情时的偏移系数。它是一个 NSDictionary,其 key 有多种具体的面部表情参数可选,比如 ARBlendShapeLocationEyeBlinkLeft 代表左眼的眨眼系数,而 ARBlendShapeLocationJawOpen 表示下巴开口系数。每个key对应的value是一个取值范围为0.0 - 1.0的浮点数,0.0表示中立情况的取值(面无表情时),1.0表示最大程度(比如下巴开口到最大值)。ARKit里提供了52种非常具体的面部表情形变参数,我们可以自行选择采用较多的或者只是采用某几个参数来达成我们的目标,比如,用“张嘴”、“眨左眼”、“眨右眼”来驱动一个卡通人物。

创建项目

首先需要添加一个 ARSCNView,在这个项目里面 ARSCNView 是懒加载的,代码如下:

- (ARSCNView *)sceneView {
    if (!_sceneView) {
        _sceneView = [[ARSCNView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
        _sceneView.delegate = self;
        _sceneView.automaticallyUpdatesLighting = YES;
    }
    return _sceneView;
}


然后根据 SCNGeometry 对象来设置 ARSCNView 的 scene 的节点。这里有3种节点,分别是网格状的人脸、面具人脸、机器人头部。机器人头部节点使用的是创建好的3D模型来进行初始化,所以不用传入代码创建的 SCNGeometry 对象。此外,我们可以根据需要来设置 scene 的背景内容,默认是以现实世界为背景。

/** 虚拟面部节点的类型 */
typedef NS_ENUM(NSInteger, XHBVirtualFaceType) {
    XHBVirtualFaceTypeMesh,     /**< 面部拓扑的网格 */
    XHBVirtualFaceTypeMask,     /**< 面具 */
    XHBVirtualFaceTypeRobot     /**< 机器人 */
};

/** 设置面部节点 */
- (void)p_setupFaceNode {
    // fillMesh 设置为NO,会空出眼睛和嘴巴的区域
    ARSCNFaceGeometry *faceGeometry = [ARSCNFaceGeometry faceGeometryWithDevice:self.sceneView.device fillMesh:NO];
    
    switch (self.virtualFaceType) {
        case XHBVirtualFaceTypeMesh:
            self.meshFaceNode = [[XHBMeshFaceNode alloc] initWithFaceGeometry:faceGeometry];
            self.virtualFaceNode = (SCNNode *)self.meshFaceNode;
            // 更改场景的背景内容
            self.sceneView.scene.background.contents = [UIColor blackColor];
//            self.sceneView.scene.background.contents = [UIImage imageNamed:@"head"];
            break;
        case XHBVirtualFaceTypeMask:
            self.maskFaceNode = [[XHBMaskFaceNode alloc] initWithFaceGeometry:faceGeometry];
            self.virtualFaceNode = (SCNNode *)self.maskFaceNode;
            break;
        case XHBVirtualFaceTypeRobot:
            self.robotFaceNode = [[XHBRobotFaceNode alloc] init];
            self.virtualFaceNode = (SCNNode *)self.robotFaceNode;
            self.sceneView.scene.background.contents = [UIColor lightGrayColor];
            break;
    }
}


节点初始化的代码如下,这里只列举面部网格节点的代码:

@interface XHBMeshFaceNode : SCNNode

- (instancetype)initWithFaceGeometry:(ARSCNFaceGeometry *)faceGeometry;
- (void)updateWithFaceAnchor:(ARFaceAnchor *)faceAnchor;

@end

@implementation XHBMeshFaceNode

#pragma mark -
#pragma mark -------------------- Life Cycle --------------------
- (instancetype)initWithFaceGeometry:(ARSCNFaceGeometry *)faceGeometry {
    self = [super init];
    if (self) {
        SCNMaterial *material = faceGeometry.firstMaterial;
        // 更改材料的填充模型为线条
        material.fillMode = SCNFillModeLines;
        // 漫反射的内容颜色
        material.diffuse.contents = [UIColor whiteColor];
        // 基于物理的阴影,包含了真实世界灯光和材料之间相互作用的光照模型
        material.lightingModelName = SCNLightingModelPhysicallyBased;
        
        self.geometry = faceGeometry;
    }
    return self;
}

#pragma mark -
#pragma mark -------------------- Public Method --------------------
- (void)updateWithFaceAnchor:(ARFaceAnchor *)faceAnchor {
    [(ARSCNFaceGeometry *)self.geometry updateFromFaceGeometry:faceAnchor.geometry];
}

@end


接着使用 ARSCNView 的 ARSession 属性配置 ARFaceTrackingConfiguration 运行起来,代码如下:

/** 设置追踪会话 */
- (void)p_setupTrackingSession {
    // 是否可以使用面部追踪
    if (!ARFaceTrackingConfiguration.isSupported) { return; }
    
    // 禁用系统的“空闲计时器”,防止屏幕进入屏幕变暗的睡眠状态
    [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
    
    ARFaceTrackingConfiguration *configuration = [[ARFaceTrackingConfiguration alloc] init];
    // YES,为 trackingSession 中捕获的 ARFrame 对象的 lightEstimate 属性提供场景照明信息
    [configuration setLightEstimationEnabled:YES];
    [self.trackingSession runWithConfiguration:configuration
                                       options:ARSessionRunOptionResetTracking | ARSessionRunOptionRemoveExistingAnchors];
}

- (ARSession *)trackingSession {
    return self.sceneView.session;
}


当 ARSCNView 首次捕捉到脸部时,会添加一个和 ARAnchor 对应的 SCNNode,然后后就会调用 ARSCNView 的代理方法,我们可以在这里让之前创建的自定义 SCNNode 成为系统创建的 SCNNode 的子节点,子节点会继承父节点的坐标系。代码如下:

- (void)p_setupFaceNodeContent {
    if (!self.faceNode) { return; }
    
    if (self.virtualFaceNode) {
        [self.faceNode addChildNode:self.virtualFaceNode];
    }
}

- (void)renderer:(id<SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
    self.faceNode = node;
    [self p_setupFaceNodeContent];
}


之后 ARSCNView 会以一定的频率来更新 ARAnchor,然后调用代理方法。我们可以在代理方法里面把新的 ARAnchor 传给自定义节点,让节点更新它的顶点数据。代码如下:

/** 节点实现的方法  */
- (void)updateWithFaceAnchor:(ARFaceAnchor *)faceAnchor {
    [(ARSCNFaceGeometry *)self.geometry updateFromFaceGeometry:faceAnchor.geometry];
}

- (void)renderer:(id<SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
    if (!anchor) { return; }
    
    switch (self.virtualFaceType) {
        case XHBVirtualFaceTypeMesh:
            [self.meshFaceNode updateWithFaceAnchor:(ARFaceAnchor *)anchor];
            break;
        case XHBVirtualFaceTypeMask:
            [self.maskFaceNode updateWithFaceAnchor:(ARFaceAnchor *)anchor];
            break;
        case XHBVirtualFaceTypeRobot:
            [self.robotFaceNode updateWithFaceAnchor:(ARFaceAnchor *)anchor];
            break;
    }
}

到这里,就可以让虚拟世界的面部跟着我们的现实世界的面部做动作了。完整的项目在这里

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 引言ARKit 为开发 iPhone 和 iPad 增强现实(AR)app 提供了一个前沿平台。本文为你介绍 AR...
    蚂蚁安然阅读 8,923评论 0 14
  • (一) AR(增强现实技术)介绍 增强现实(Augmented Reality,简称 AR),是一种实时地计算摄影...
    NewSongs阅读 2,258评论 5 53
  • ARKit ARKit框架通过集成iOS设备摄像头和运动功能,在您的应用程序或游戏中产生增强现实体验。 概述 增强...
    暗夜夜夜行路阅读 5,726评论 0 17
  • 以撒40娶妻利百加,婚后近20年没有孩子。以撒开始为此求告耶和华,耶和华应允他的祈祷,利百加怀孕,很...
    玛哈念跳舞阅读 871评论 0 6
  • 宝贝成长的过程中妈妈总是希望小儿好养。 下边是昨晚上爱心树首席教官分享的一堂课。由于昨晚上宝贝很淘气,没有顾得好好...
    赵小建阅读 832评论 2 1