I’m currently working on a project that involves face tracking, and as a first prototype am using the built-in features in the ARKit library, Apple’s augmented reality API for iOS.

Using a device with a front-facing TrueDepth camera, an augmented reality session can be started to return quite a bit of information about faces it is tracking in an ARFaceAnchor object. One of these details is the face geometry itself, contained in the aptly named ARFaceGeometry object. For those who care about face shape landmarks, the 1220 individual vertices (points) can provide a wealth of information. However, there is little to no published information on which vertex corresponds to what point on the face.

While keeping in mind that this means these vertices could easily change in future versions of ARKit, I’ve taken the liberty of labeling points in case it is helpful for a future developer.

Update: A few people have asked how I generated this map. Details below.

Update: Since posting these pictures, a few people have asked how I generated this map and/or to share some code. I recommend starting with Yono Mittlefehldt’s AR Face Tracking Tutorial. That page goes through the basics of setting up your ARSession , ARFaceTrackingConfiguration , etc etc. It even goes so far as to attach SCNNode and child nodes that move with the face.

This is where Mittlefehldt stops short: he gives a few “magic numbers” corresponding to key points (eyes, nose, mouth, and hat) to attach emojis. To generate the maps above, I took this one short step further and generated a child node that puts the number itself on each node. If you’re reading through the tutorial, the difference is to add the following to your extension ViewController: ARSCNViewDelegate extension:

func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? { guard let device = cameraView.device else { return nil } let faceGeometry = ARSCNFaceGeometry(device: device) let node = SCNNode(geometry: faceGeometry) for x in [1076, 1070, 1163, 1168, 1094, 358, 1108, 1102, 20, 661, 888, 822, 1047, 462, 376, 39] { let text = SCNText(string: "\(x)", extrusionDepth: 1) let txtnode = SCNNode(geometry: text) txtnode.scale = SCNVector3(x: 0.0002, y: 0.0002, z: 0.0002) txtnode.name = "\(x)" node.addChildNode(txtnode) txtnode.geometry?.firstMaterial?.fillMode = .fill } node.geometry?.firstMaterial?.fillMode = .lines return node } func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { guard let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry else { return } for x in 0..<1220 { let child = node.childNode(withName: "\(x)", recursively: false) child?.position = SCNVector3(faceAnchor.geometry.vertices[x]) } faceGeometry.update(from: faceAnchor.geometry) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 func renderer ( _ renderer : SCNSceneRenderer , nodeFor anchor : ARAnchor ) -> SCNNode ? { guard let device = cameraView . device else { return nil } let faceGeometry = ARSCNFaceGeometry ( device : device ) let node = SCNNode ( geometry : faceGeometry ) for x in [ 1076 , 1070 , 1163 , 1168 , 1094 , 358 , 1108 , 1102 , 20 , 661 , 888 , 822 , 1047 , 462 , 376 , 39 ] { let text = SCNText ( string : " \ ( x ) " , extrusionDepth : 1 ) let txtnode = SCNNode ( geometry : text ) txtnode . scale = SCNVector3 ( x : 0.0002 , y : 0.0002 , z : 0.0002 ) txtnode . name = " \ ( x ) " node . addChildNode ( txtnode ) txtnode . geometry ? . firstMaterial ? . fillMode = . fill } node . geometry ? . firstMaterial ? . fillMode = . lines return node } func renderer ( _ renderer : SCNSceneRenderer , didUpdate node : SCNNode , for anchor : ARAnchor ) { guard let faceAnchor = anchor as ? ARFaceAnchor , let faceGeometry = node . geometry as ? ARSCNFaceGeometry else { return } for x in 0 .. < 1220 { let child = node . childNode ( withName : " \ ( x ) " , recursively : false ) child ? . position = SCNVector3 ( faceAnchor . geometry . vertices [ x ] ) } faceGeometry . update ( from : faceAnchor . geometry ) }

You can replace the list of vertex numbers with anything you’d like. The images above include all vertices, followed by every 4th vertex to clear out particularly “busy” areas.