C44680ff1f11c568794d792f78edc487
ARKit 小小实验室

前言

前阵子一直在预研ARKit相关的技术,和组内两位老铁从前置AR和后置AR这两个维度进行一些实验性的小实践,各自发散hhh。现在我总结下我这边所做的一些预研和实验成果。

AR场景导出3D文件

应用场景

前置摄像头捕获人脸,通过ARKit输出人脸3D模具进行打印,这个设备要求比较高,当前只支持具备3D结构光前置摄像头的iPhone X。

编程可能性

open class SCNScene : NSObject, NSSecureCoding {
    @available(iOS 10.0, *)
    open func write(to url: URL, options: [String : Any]? = nil, delegate: SCNSceneExportDelegate?, progressHandler: SceneKit.SCNSceneExportProgressHandler? = nil) -> Bool
}

通过ARKit实例化一个3D场景SCNScene,SCNScene通过上面那个API可以将整个3D场景本地持久化存储,导出为3D文件,当前支持导出格式为dae、scn。

原理

配置ARSession启动配置为ARFaceTrackingConfiguration,以支持人脸检测,一旦检测到人脸,就会将人脸节点faceNode(一种数据结构)添加3D场景中,人脸节点包含整个人脸拓扑图,也就是整个人脸的几何结构,包含五官。根据这个人脸拓扑图新建一个mask节点,添加到当前faceNode中。因为人脸已经添加到3D场景中,所以导出的3D则包含一个人脸的面具。关键代码如下

let configuration = ARFaceTrackingConfiguration()
session.run(configuration, options: [.resetTracking, .removeExistingAnchors])

// MARK: - ARSCNViewDelegate
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        // Hold onto the `faceNode` so that the session does not need to be restarted when switching masks.
        faceNode = node
        serialQueue.async {
            //根据人脸拓扑新建一个mask节点,并将该节点添加到faceNode上
        }
    }
    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let faceAnchor = anchor as? ARFaceAnchor else { return }

//mask节点随着人脸移动更新        maskNode?.update(withFaceAnchor: faceAnchor)
    }

//面具类
class Mask: SCNNode, VirtualFaceContent {

    init(geometry: ARSCNFaceGeometry) {
        let material = geometry.firstMaterial!

        material.diffuse.contents = UIColor.lightGray
        material.isDoubleSided = true;
        material.lightingModel = .physicallyBased

        super.init()
        self.geometry = geometry
    }
}
//创建面具
let device = sceneView.device!
let maskGeometry = ARSCNFaceGeometry(device: device, fillMesh: true)!
let glassesGeometry = ARSCNFaceGeometry(device: device)!
let mask = Mask(geometry: maskGeometry)

人脸面具3D文件预览效果

3D打印面具

先聊聊3D打印机原理,3D打印与激光成型技术一样,采用了分层加工、叠加成型来完成3D实体打印。每一层的打印过程分为两步,首先在需要成型的区域喷洒一层特殊胶水,胶水液滴本身很小,且不易扩散。然后是喷洒一层均匀的粉末,粉末遇到胶水会迅速固化黏结,而没有胶水的区域仍保持松散状态。这样在一层胶水一层粉末的交替下,实体模型将会被“打印”成型。
可是!!!
由上图所示,我们的人脸面具的3D文件是一个人脸薄片,并没有实质的质地,它并不是一个封闭的3D模型,所以在3D打印看来,它分层不了,这也导致直接使用当前的3D文件是没法打印的。所以得做一些几何拼合,达到模型封闭的效果。编码层面只需要对当前faceNode添加一些SCNBox结点就能实现,拼接效果如下:

通过3个长方体将整个人脸面具封闭起来,那在3D打印看来,这个模型是有质地、可分层的,自然可以打印出对应的模型,打印效果如下:

AR体素化

这个是当时为了解决面具薄片的一个预研的解决方案,通过体素化将整个人脸切片成一块一块网格,再用补丁的形式填充人脸网格,是人脸薄片转化为实地的人脸模具,最终实践发现这个方案实现效果不理想,很耗性能,但还是有有一定知识可以沉淀下来。
首先,我们先理解什么是体素。我们知道二维图片有像素这个单位,类比的,对于3D几何体就有体素这个单位,体素化能够对模型进行简化,得到均匀的网格,效果类似将物体转化为《我的世界》那样那种像素物体,效果如下:

体素化主要是利用苹果ModelIO这个框架,ModelIO框架可以帮助我们对3D文件进一步编辑处理,体素化相关实践代码如下,代码内已有注释:

- (void)voxelize:(id)sender
{
    //将3D场景MDLAsset资源文件,为了方便ModelIO进行进一步编辑
    SCNScene *tempScene = [SCNScene scene];
    [tempScene.rootNode addChildNode:_character];
    MDLAsset *asset = [MDLAsset assetWithSCNScene:tempScene];

    //根据DLAsset资源文件生成对应的体素网络,150影响网格切片维度,越大切得越细腻
    MDLVoxelArray *grid = [[MDLVoxelArray alloc] initWithAsset:asset divisions:150 patchRadius:0.f];

    //获取所有网格的顶点数据
    NSData *voxelData = [grid voxelIndices];
    if (voxelData && voxelData.bytes)
    {
        [_voxels removeFromParentNode];
        _voxels = [SCNNode node];
        [self.sceneView.scene.rootNode addChildNode:_voxels];

        //定义补丁
        SCNBox *particle = [SCNBox boxWithWidth:0.5f * SCALE_FACTOR height:0.5f * SCALE_FACTOR length:0.5f * SCALE_FACTOR chamferRadius:0.f];

        MDLVoxelIndex *voxels = (MDLVoxelIndex *)voxelData.bytes;
        size_t count = voxelData.length / sizeof(MDLVoxelIndex);
        for (int i = 0; i < count; ++i)
        {
            //根据顶点数据获取各个网格的网格位置,进而进行补丁
            vector_float3 position = [grid spatialLocationOfIndex:*voxels++];

            UIColor *color = [UIColor blueColor];

            SCNNode *voxelNode = [SCNNode nodeWithGeometry:[particle copy]];
            voxelNode.position = SCNVector3Make(position.x, position.y, position.z);
            SCNMaterial *material = [SCNMaterial material];
            material.diffuse.contents = color;
            voxelNode.geometry.firstMaterial = material;

            [_voxels addChildNode:voxelNode];
        }
    }
}

AR室内导航

灵感来源

top Created with Sketch.