README.md: title = 620 dense face landmarks on the GNM Head mesh
Browse files
README.md
CHANGED
|
@@ -1,134 +1,134 @@
|
|
| 1 |
-
---
|
| 2 |
-
license: cc-by-nc-4.0
|
| 3 |
-
library_name: pytorch
|
| 4 |
-
pipeline_tag: keypoint-detection
|
| 5 |
-
tags:
|
| 6 |
-
- face-landmarks
|
| 7 |
-
- gnm
|
| 8 |
-
- 3d-face
|
| 9 |
-
- dense-landmarks
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
# Facepoints: 620 face
|
| 13 |
-
|
| 14 |
-

|
| 15 |
-
|
| 16 |
-
Finds **620 points on a face in a photo**. Every point is a fixed location on Google's open
|
| 17 |
-
[GNM Head](https://github.com/google/GNM) mesh, so the output fits that model to a person directly —
|
| 18 |
-
for building or animating a 3D avatar. The same forward pass also gives the standard 68-point (iBUG)
|
| 19 |
-
and 98-point (WFLW) landmark sets and an error estimate for every point. To our knowledge (2026-10-23)
|
| 20 |
-
it is the first publicly released detector trained on points defined on the GNM mesh.
|
| 21 |
-
|
| 22 |
-
| | |
|
| 23 |
-
|---|---|
|
| 24 |
-
| Input | one face, cropped from a photo or video frame; a face detector supplies the box |
|
| 25 |
-
| Output | 620 points, each (x, y) plus its own error estimate, in pixels; the 68- and 98-point sets |
|
| 26 |
-
| Points | 416 landmark and 150 skin points, 36 eyelid-edge, 16 front teeth (7 upper, 9 lower), 2 iris centres (`points_gnm.json` maps each to the GNM Head mesh, v3.0) |
|
| 27 |
-
| Model | HRNet-W48, 256-px input, 102.7 M parameters (+1.2 M for the 68/98 heads) |
|
| 28 |
-
| Weights | `model/model.safetensors` + `model/config.json`; SHA-256 of the weights `b37075caa3eaf0d00f23f32925b16ec3a8e2e45c42aa9c00956f94f2574e6526` |
|
| 29 |
-
|
| 30 |
-
For face tracking and avatar research. Not for identifying or judging people, and not for commercial
|
| 31 |
-
use (the weights are CC BY-NC 4.0; see *Licence*).
|
| 32 |
-
|
| 33 |
-
## Use
|
| 34 |
-
|
| 35 |
-
```bash
|
| 36 |
-
git clone https://huggingface.co/myned-ai/facepoints && cd facepoints # git-lfs for the weights
|
| 37 |
-
pip install -e . # torch, timm, safetensors, opencv-python-headless, numpy
|
| 38 |
-
```
|
| 39 |
-
|
| 40 |
-
```python
|
| 41 |
-
import cv2
|
| 42 |
-
from facepoints import Detector, square_box, gnm_positions
|
| 43 |
-
|
| 44 |
-
det = Detector("./model") # the folder with model.safetensors + config.json
|
| 45 |
-
img = cv2.imread("face.jpg") # BGR
|
| 46 |
-
x, y, w, h = my_face_detector(img) # any face box, e.g. OpenCV's YuNet
|
| 47 |
-
out = det.detect(img, square_box(x, y, w, h)) # the box, then two passes on the model's own extent
|
| 48 |
-
|
| 49 |
-
out["points"], out["std"] # (620, 2) image px, and one predicted std per point (px)
|
| 50 |
-
out["300w"], out["wflw"] # (68, 2) and (98, 2)
|
| 51 |
-
gnm_positions(mesh_xyz) # (620, 3): the same points on any posed GNM Head mesh (weights x vertices)
|
| 52 |
-
```
|
| 53 |
-
|
| 54 |
-
Fitting the GNM Head to a face = solving for the mesh whose projected `gnm_positions` match
|
| 55 |
-
`out["points"]`, weighted by `1 / std`. One face at a time; the output moves with the box, which is
|
| 56 |
-
why `detect` re-frames on its own points. A box with no face in it yields points with a very large
|
| 57 |
-
`std` — threshold it. A complete example with OpenCV's face finder:
|
| 58 |
-
`python run.py photo.jpg out.jpg` (the model from `./model`, or `--model DIR`); tests: `pytest tests`.
|
| 59 |
-
|
| 60 |
-
## Results
|
| 61 |
-
|
| 62 |
-
**Against registered 3D meshes** of 9 studio people none of the models trained on, 14 cameras, their
|
| 63 |
-
widest mouth shapes. The score is how well the points follow the face: median distance to the paired
|
| 64 |
-
truth vertex, in mm, after removing each point's constant offset at rest (protocol in the report).
|
| 65 |
-
|
| 66 |
-

|
| 67 |
-
|
| 68 |
-
| | lips | cheeks | chin | rest of face |
|
| 69 |
-
|---|---|---|---|---|
|
| 70 |
-
| **ours** | **1.78** | **2.00** | 2.12 | 0.81 |
|
| 71 |
-
| Pixel3DMM | 1.81 | 2.19 | **1.67** | **0.78** |
|
| 72 |
-
| 3DDFA-V3 | 2.40 | 2.61 | 2.84 | 1.41 |
|
| 73 |
-
|
| 74 |
-
Level with Pixel3DMM except on the chin, ahead of 3DDFA-V3 on every region.
|
| 75 |
-
|
| 76 |
-

|
| 77 |
-
|
| 78 |
-
**On the 2D benchmarks** (the 68/98-point outputs; full test sets; NME = mean point error as % of the
|
| 79 |
-
eye-corner distance):
|
| 80 |
-
|
| 81 |
-

|
| 82 |
-
|
| 83 |
-
| | 300W | 300VW | WFLW |
|
| 84 |
-
|---|---|---|---|
|
| 85 |
-
| **ours, 68/98-point heads** | 3.33 | 3.52 | 4.66 |
|
| 86 |
-
| **ours, 620 points through a linear map** | 3.31 | 3.56 | 4.59 |
|
| 87 |
-
| STAR (official checkpoints) | 3.06 | 3.45 | 4.47 |
|
| 88 |
-
| SPIGA | 3.02 | 3.37 | 4.06 |
|
| 89 |
-
| RwSA (ICCV 2025) | 2.96 | 3.38 | 4.23 |
|
| 90 |
-
| FAN | 3.69 | 4.11 | 7.27 |
|
| 91 |
-
| MediaPipe (478 points, mapped) | 3.87 | 4.00 | 7.72 |
|
| 92 |
-
|
| 93 |
-
Behind the three models built for these benchmarks by 0.2–0.6, level with STAR on video, ahead of FAN
|
| 94 |
-
and MediaPipe everywhere. Profile faces are the weak spot.
|
| 95 |
-
|
| 96 |
-

|
| 97 |
-
|
| 98 |
-
**The error estimate** (`std`) comes from a Student-t likelihood (ν = 10). Error rises with it, but
|
| 99 |
-
the intervals are too narrow: multiply `std` by 1.34 to calibrate on average. Measured on studio
|
| 100 |
-
faces only.
|
| 101 |
-
|
| 102 |
-
## Limitations
|
| 103 |
-
|
| 104 |
-
- Hidden points (teeth behind closed lips, the far side of a turned face) are estimated, not seen;
|
| 105 |
-
sunglasses give guessed eyelids and irises; closed eyes come out about 1 mm open.
|
| 106 |
-
- The 620 points have no hand-labelled truth on wild photos; their wild accuracy is known only through
|
| 107 |
-
the 68/98 outputs.
|
| 108 |
-
- On 24 held-out studio people, error correlated with darker skin (Spearman −0.44, p = 0.03),
|
| 109 |
-
confounded with exposure; Ava-256 is biased to lighter-skinned people under 35.
|
| 110 |
-
|
| 111 |
-
## Training
|
| 112 |
-
|
| 113 |
-
The 620 points: Ava-256 (Meta), 230 people, 16 cameras; labels are projections of each person's
|
| 114 |
-
registered mesh, whose vertices were mapped onto the GNM template. The 68/98 heads: the 300W and WFLW
|
| 115 |
-
training photos; on those photos the 620 points carry pseudo-labels. HRNet-W48, three stages, one
|
| 116 |
-
run. To replicate the model, follow [train/README.md](train/README.md): data preparation, the three
|
| 117 |
-
commands and the export. The scripts and the shipped sample lists, split and pseudo-label map:
|
| 118 |
-
[prep/](prep/); every key: [train/config.py](train/config.py).
|
| 119 |
-
Protocol, data and analysis in full: technical report (in preparation).
|
| 120 |
-
|
| 121 |
-
## Licence
|
| 122 |
-
|
| 123 |
-
Copyright 2026 Myned AI (opensource@myned.ai).
|
| 124 |
-
|
| 125 |
-
| | licence | source |
|
| 126 |
-
|---|---|---|
|
| 127 |
-
| code | Apache-2.0 | `LICENSE` |
|
| 128 |
-
| `model/model.safetensors`, `points_gnm.json`, `prep/*.npz` | CC BY-NC 4.0 | `LICENSE-WEIGHTS` |
|
| 129 |
-
| Ava-256 · Multiface (images) | CC BY-NC 4.0 | LICENSE files |
|
| 130 |
-
| 300W · WFLW (WIDER FACE images) · 300VW | research only · CC BY-NC-ND · none stated | dataset pages |
|
| 131 |
-
| ImageNet start (timm `hrnet_w48.ms_in1k`) | weights MIT; ImageNet terms non-commercial | model card |
|
| 132 |
-
| GNM Head | Apache-2.0, attribution required | LICENSE file |
|
| 133 |
-
|
| 134 |
-
Figures show Multiface people (consented) and WFLW test faces as the benchmark publishes them.
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-nc-4.0
|
| 3 |
+
library_name: pytorch
|
| 4 |
+
pipeline_tag: keypoint-detection
|
| 5 |
+
tags:
|
| 6 |
+
- face-landmarks
|
| 7 |
+
- gnm
|
| 8 |
+
- 3d-face
|
| 9 |
+
- dense-landmarks
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# Facepoints: 620 dense face landmarks on the GNM Head mesh
|
| 13 |
+
|
| 14 |
+

|
| 15 |
+
|
| 16 |
+
Finds **620 points on a face in a photo**. Every point is a fixed location on Google's open
|
| 17 |
+
[GNM Head](https://github.com/google/GNM) mesh, so the output fits that model to a person directly —
|
| 18 |
+
for building or animating a 3D avatar. The same forward pass also gives the standard 68-point (iBUG)
|
| 19 |
+
and 98-point (WFLW) landmark sets and an error estimate for every point. To our knowledge (2026-10-23)
|
| 20 |
+
it is the first publicly released detector trained on points defined on the GNM mesh.
|
| 21 |
+
|
| 22 |
+
| | |
|
| 23 |
+
|---|---|
|
| 24 |
+
| Input | one face, cropped from a photo or video frame; a face detector supplies the box |
|
| 25 |
+
| Output | 620 points, each (x, y) plus its own error estimate, in pixels; the 68- and 98-point sets |
|
| 26 |
+
| Points | 416 landmark and 150 skin points, 36 eyelid-edge, 16 front teeth (7 upper, 9 lower), 2 iris centres (`points_gnm.json` maps each to the GNM Head mesh, v3.0) |
|
| 27 |
+
| Model | HRNet-W48, 256-px input, 102.7 M parameters (+1.2 M for the 68/98 heads) |
|
| 28 |
+
| Weights | `model/model.safetensors` + `model/config.json`; SHA-256 of the weights `b37075caa3eaf0d00f23f32925b16ec3a8e2e45c42aa9c00956f94f2574e6526` |
|
| 29 |
+
|
| 30 |
+
For face tracking and avatar research. Not for identifying or judging people, and not for commercial
|
| 31 |
+
use (the weights are CC BY-NC 4.0; see *Licence*).
|
| 32 |
+
|
| 33 |
+
## Use
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
git clone https://huggingface.co/myned-ai/facepoints && cd facepoints # git-lfs for the weights
|
| 37 |
+
pip install -e . # torch, timm, safetensors, opencv-python-headless, numpy
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
```python
|
| 41 |
+
import cv2
|
| 42 |
+
from facepoints import Detector, square_box, gnm_positions
|
| 43 |
+
|
| 44 |
+
det = Detector("./model") # the folder with model.safetensors + config.json
|
| 45 |
+
img = cv2.imread("face.jpg") # BGR
|
| 46 |
+
x, y, w, h = my_face_detector(img) # any face box, e.g. OpenCV's YuNet
|
| 47 |
+
out = det.detect(img, square_box(x, y, w, h)) # the box, then two passes on the model's own extent
|
| 48 |
+
|
| 49 |
+
out["points"], out["std"] # (620, 2) image px, and one predicted std per point (px)
|
| 50 |
+
out["300w"], out["wflw"] # (68, 2) and (98, 2)
|
| 51 |
+
gnm_positions(mesh_xyz) # (620, 3): the same points on any posed GNM Head mesh (weights x vertices)
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
Fitting the GNM Head to a face = solving for the mesh whose projected `gnm_positions` match
|
| 55 |
+
`out["points"]`, weighted by `1 / std`. One face at a time; the output moves with the box, which is
|
| 56 |
+
why `detect` re-frames on its own points. A box with no face in it yields points with a very large
|
| 57 |
+
`std` — threshold it. A complete example with OpenCV's face finder:
|
| 58 |
+
`python run.py photo.jpg out.jpg` (the model from `./model`, or `--model DIR`); tests: `pytest tests`.
|
| 59 |
+
|
| 60 |
+
## Results
|
| 61 |
+
|
| 62 |
+
**Against registered 3D meshes** of 9 studio people none of the models trained on, 14 cameras, their
|
| 63 |
+
widest mouth shapes. The score is how well the points follow the face: median distance to the paired
|
| 64 |
+
truth vertex, in mm, after removing each point's constant offset at rest (protocol in the report).
|
| 65 |
+
|
| 66 |
+

|
| 67 |
+
|
| 68 |
+
| | lips | cheeks | chin | rest of face |
|
| 69 |
+
|---|---|---|---|---|
|
| 70 |
+
| **ours** | **1.78** | **2.00** | 2.12 | 0.81 |
|
| 71 |
+
| Pixel3DMM | 1.81 | 2.19 | **1.67** | **0.78** |
|
| 72 |
+
| 3DDFA-V3 | 2.40 | 2.61 | 2.84 | 1.41 |
|
| 73 |
+
|
| 74 |
+
Level with Pixel3DMM except on the chin, ahead of 3DDFA-V3 on every region.
|
| 75 |
+
|
| 76 |
+

|
| 77 |
+
|
| 78 |
+
**On the 2D benchmarks** (the 68/98-point outputs; full test sets; NME = mean point error as % of the
|
| 79 |
+
eye-corner distance):
|
| 80 |
+
|
| 81 |
+

|
| 82 |
+
|
| 83 |
+
| | 300W | 300VW | WFLW |
|
| 84 |
+
|---|---|---|---|
|
| 85 |
+
| **ours, 68/98-point heads** | 3.33 | 3.52 | 4.66 |
|
| 86 |
+
| **ours, 620 points through a linear map** | 3.31 | 3.56 | 4.59 |
|
| 87 |
+
| STAR (official checkpoints) | 3.06 | 3.45 | 4.47 |
|
| 88 |
+
| SPIGA | 3.02 | 3.37 | 4.06 |
|
| 89 |
+
| RwSA (ICCV 2025) | 2.96 | 3.38 | 4.23 |
|
| 90 |
+
| FAN | 3.69 | 4.11 | 7.27 |
|
| 91 |
+
| MediaPipe (478 points, mapped) | 3.87 | 4.00 | 7.72 |
|
| 92 |
+
|
| 93 |
+
Behind the three models built for these benchmarks by 0.2–0.6, level with STAR on video, ahead of FAN
|
| 94 |
+
and MediaPipe everywhere. Profile faces are the weak spot.
|
| 95 |
+
|
| 96 |
+

|
| 97 |
+
|
| 98 |
+
**The error estimate** (`std`) comes from a Student-t likelihood (ν = 10). Error rises with it, but
|
| 99 |
+
the intervals are too narrow: multiply `std` by 1.34 to calibrate on average. Measured on studio
|
| 100 |
+
faces only.
|
| 101 |
+
|
| 102 |
+
## Limitations
|
| 103 |
+
|
| 104 |
+
- Hidden points (teeth behind closed lips, the far side of a turned face) are estimated, not seen;
|
| 105 |
+
sunglasses give guessed eyelids and irises; closed eyes come out about 1 mm open.
|
| 106 |
+
- The 620 points have no hand-labelled truth on wild photos; their wild accuracy is known only through
|
| 107 |
+
the 68/98 outputs.
|
| 108 |
+
- On 24 held-out studio people, error correlated with darker skin (Spearman −0.44, p = 0.03),
|
| 109 |
+
confounded with exposure; Ava-256 is biased to lighter-skinned people under 35.
|
| 110 |
+
|
| 111 |
+
## Training
|
| 112 |
+
|
| 113 |
+
The 620 points: Ava-256 (Meta), 230 people, 16 cameras; labels are projections of each person's
|
| 114 |
+
registered mesh, whose vertices were mapped onto the GNM template. The 68/98 heads: the 300W and WFLW
|
| 115 |
+
training photos; on those photos the 620 points carry pseudo-labels. HRNet-W48, three stages, one
|
| 116 |
+
run. To replicate the model, follow [train/README.md](train/README.md): data preparation, the three
|
| 117 |
+
commands and the export. The scripts and the shipped sample lists, split and pseudo-label map:
|
| 118 |
+
[prep/](prep/); every key: [train/config.py](train/config.py).
|
| 119 |
+
Protocol, data and analysis in full: technical report (in preparation).
|
| 120 |
+
|
| 121 |
+
## Licence
|
| 122 |
+
|
| 123 |
+
Copyright 2026 Myned AI (opensource@myned.ai).
|
| 124 |
+
|
| 125 |
+
| | licence | source |
|
| 126 |
+
|---|---|---|
|
| 127 |
+
| code | Apache-2.0 | `LICENSE` |
|
| 128 |
+
| `model/model.safetensors`, `points_gnm.json`, `prep/*.npz` | CC BY-NC 4.0 | `LICENSE-WEIGHTS` |
|
| 129 |
+
| Ava-256 · Multiface (images) | CC BY-NC 4.0 | LICENSE files |
|
| 130 |
+
| 300W · WFLW (WIDER FACE images) · 300VW | research only · CC BY-NC-ND · none stated | dataset pages |
|
| 131 |
+
| ImageNet start (timm `hrnet_w48.ms_in1k`) | weights MIT; ImageNet terms non-commercial | model card |
|
| 132 |
+
| GNM Head | Apache-2.0, attribution required | LICENSE file |
|
| 133 |
+
|
| 134 |
+
Figures show Multiface people (consented) and WFLW test faces as the benchmark publishes them.
|