iOS ARKit - 面部追踪

2018-09-29  本文已影响0人  兵来将挡水来我喝

ARSCNView

  • ARSCNView 会自动将设备摄像头的实时视频输入渲染为场景背景。
  • ARSCNView 的属性 scene 的世界坐标系直接响应由会话配置建立的AR世界坐标系。
  • ARSCNView 会自动移动它的 SceneKit 相机来匹配设备的实际移动。

SCNNode

ARFaceTrackingConfiguration

if (!ARFaceTrackingConfiguration.isSupported) { return; }

ARFaceAnchor

ARFaceGeometry

面部表情追踪

创建项目

首先需要添加一个 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;
    }
}

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

参考资料

上一篇下一篇

猜你喜欢

热点阅读