Ship the three eye-state models beside the face pair
2d106det for the eye contours, OCEC for open or closed, SGC for sunglasses — all three pinned to a batch of one by the same script as the pair, and installed by every packager so the eyes-open filter works out of the box. The two classifiers are MIT, code and weights; the README records their provenance, SGC's undocumented training set, and the hashes as fetched and as shipped.
This commit is contained in:
@@ -324,11 +324,19 @@ fn unpack_bundled_models(app: &slint::android::AndroidApp) {
|
|||||||
// (`FaceDetector`, docs/faces.md §12.3) and a tablet has no other way to
|
// (`FaceDetector`, docs/faces.md §12.3) and a tablet has no other way to
|
||||||
// obtain the one it was not shipped with. Twenty megabytes of APK for
|
// obtain the one it was not shipped with. Twenty megabytes of APK for
|
||||||
// the choice; the embedder is the same for all three.
|
// the choice; the embedder is the same for all three.
|
||||||
const BUNDLED: [(&std::ffi::CStr, &str); 7] = [
|
//
|
||||||
|
// Then the three eye-state models (docs/faces.md §17): landmarks, open
|
||||||
|
// or closed, sunglasses. The app indexes without them; with them the
|
||||||
|
// eyes-open filter has something to read, and a tablet has no other way
|
||||||
|
// to get them either.
|
||||||
|
const BUNDLED: [(&std::ffi::CStr, &str); 10] = [
|
||||||
(c"models/scrfd_500m_640.onnx", "scrfd_500m_640.onnx"),
|
(c"models/scrfd_500m_640.onnx", "scrfd_500m_640.onnx"),
|
||||||
(c"models/scrfd_2.5g_640.onnx", "scrfd_2.5g_640.onnx"),
|
(c"models/scrfd_2.5g_640.onnx", "scrfd_2.5g_640.onnx"),
|
||||||
(c"models/scrfd_10g_640.onnx", "scrfd_10g_640.onnx"),
|
(c"models/scrfd_10g_640.onnx", "scrfd_10g_640.onnx"),
|
||||||
(c"models/arcface_mbf_b1.onnx", "arcface_mbf_b1.onnx"),
|
(c"models/arcface_mbf_b1.onnx", "arcface_mbf_b1.onnx"),
|
||||||
|
(c"models/2d106det_b1.onnx", "2d106det_b1.onnx"),
|
||||||
|
(c"models/ocec_s_b1.onnx", "ocec_s_b1.onnx"),
|
||||||
|
(c"models/sgc_l_48_b1.onnx", "sgc_l_48_b1.onnx"),
|
||||||
(c"models/yolo26s-sem-ade20k.onnx", "yolo26s-sem-ade20k.onnx"),
|
(c"models/yolo26s-sem-ade20k.onnx", "yolo26s-sem-ade20k.onnx"),
|
||||||
(
|
(
|
||||||
c"models/yolo26s-sem-ade20k.classes.json",
|
c"models/yolo26s-sem-ade20k.classes.json",
|
||||||
@@ -343,8 +351,8 @@ fn unpack_bundled_models(app: &slint::android::AndroidApp) {
|
|||||||
|
|
||||||
for (asset_path, name) in BUNDLED {
|
for (asset_path, name) in BUNDLED {
|
||||||
let dest = dir.join(name);
|
let dest = dir.join(name);
|
||||||
// Already unpacked. Not re-read on every launch: this is 61 MB of
|
// Already unpacked. Not re-read on every launch: this is 73 MB of
|
||||||
// copying across the seven entries, and the file does not change without
|
// copying across the ten entries, and the file does not change without
|
||||||
// the APK changing, at which point the install wiped it anyway. It
|
// the APK changing, at which point the install wiped it anyway. It
|
||||||
// matters more now than it did — a launch that skips every entry here
|
// matters more now than it did — a launch that skips every entry here
|
||||||
// costs nothing at all, which is what makes the second launch after an
|
// costs nothing at all, which is what makes the second launch after an
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ Measured on the first build, 2026-09-12:
|
|||||||
| DLL imports | 26 Windows system DLLs; **no** `libwinpthread-1.dll`, `libgcc_s`, `libstdc++` |
|
| DLL imports | 26 Windows system DLLs; **no** `libwinpthread-1.dll`, `libgcc_s`, `libstdc++` |
|
||||||
| `wine darkroom-desktop.exe --version` | `darkroom-desktop 0.12.0`, exit 0, 0.1 s |
|
| `wine darkroom-desktop.exe --version` | `darkroom-desktop 0.12.0`, exit 0, 0.1 s |
|
||||||
| `makensis` | 105 MB `DarkRoom-<version>-x86_64-setup.exe`, 64-bit stub |
|
| `makensis` | 105 MB `DarkRoom-<version>-x86_64-setup.exe`, 64-bit stub |
|
||||||
| `wine setup.exe /S` | Installs exe + 7 models to `AppData\Local\Programs\DarkRoom`, writes the `HKCU` uninstall key; the installed exe runs |
|
| `wine setup.exe /S` | Installs exe + 10 models to `AppData\Local\Programs\DarkRoom`, writes the `HKCU` uninstall key; the installed exe runs |
|
||||||
| `wine uninstall.exe /S` | Removes the directory and the key |
|
| `wine uninstall.exe /S` | Removes the directory and the key |
|
||||||
| Start Menu shortcut | **Not verifiable here.** `CreateShortcut` is `IShellLink` and does nothing under a headless Wine; the directory beside it is created. Check on Windows. |
|
| Start Menu shortcut | **Not verifiable here.** `CreateShortcut` is `IShellLink` and does nothing under a headless Wine; the directory beside it is created. Check on Windows. |
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -281,13 +281,14 @@ $LOCALAPPDATA\Programs\DarkRoom\
|
|||||||
darkroom.exe
|
darkroom.exe
|
||||||
models\
|
models\
|
||||||
scrfd_500m_640.onnx scrfd_2.5g_640.onnx scrfd_10g_640.onnx arcface_mbf_b1.onnx
|
scrfd_500m_640.onnx scrfd_2.5g_640.onnx scrfd_10g_640.onnx arcface_mbf_b1.onnx
|
||||||
|
2d106det_b1.onnx ocec_s_b1.onnx sgc_l_48_b1.onnx
|
||||||
yolo26s-sem-ade20k.onnx yolo26s-sem-ade20k.classes.json categories.txt
|
yolo26s-sem-ade20k.onnx yolo26s-sem-ade20k.classes.json categories.txt
|
||||||
LICENSE
|
LICENSE
|
||||||
uninstall.exe
|
uninstall.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
Plus a Start Menu shortcut, and nothing on the desktop unless the user ticks it. The models are
|
Plus a Start Menu shortcut, and nothing on the desktop unless the user ticks it. The models are
|
||||||
the same seven files the APK bundles and the PKGBUILD installs; `models\` beside the executable is
|
the same ten files the APK bundles and the PKGBUILD installs; `models\` beside the executable is
|
||||||
where §3.2's lookup finds them. **No `LICENSE` yet**: the repository has no licence file at its
|
where §3.2's lookup finds them. **No `LICENSE` yet**: the repository has no licence file at its
|
||||||
root (the Arch package points at the system's shared GPL text), so the installer has no licence
|
root (the Arch package points at the system's shared GPL text), so the installer has no licence
|
||||||
page until one is added — a one-file change, and the `.nsi` says where the page then goes. The face weights carry the research-only grant that
|
page until one is added — a one-file change, and the `.nsi` says where the page then goes. The face weights carry the research-only grant that
|
||||||
@@ -458,7 +459,7 @@ container was the cheaper way to get a pinned MinGW and a Wine that could be thr
|
|||||||
| It links | Yes, first attempt once the link flags were right. 115 MB, `PE32+ … (GUI)`. Two dead-code warnings, both `cfg`-shadowed constants, fixed. |
|
| It links | Yes, first attempt once the link flags were right. 115 MB, `PE32+ … (GUI)`. Two dead-code warnings, both `cfg`-shadowed constants, fixed. |
|
||||||
| It is a Windows executable | 26 imports, all Windows system DLLs. No MinGW runtime. `.rsrc` carries `PRODUCTVERSION 0,12,0,0`, `ProductName DarkRoom`, the icon. |
|
| It is a Windows executable | 26 imports, all Windows system DLLs. No MinGW runtime. `.rsrc` carries `PRODUCTVERSION 0,12,0,0`, `ProductName DarkRoom`, the icon. |
|
||||||
| It starts | `wine darkroom-desktop.exe --version` → `darkroom-desktop 0.12.0`, exit 0, 0.1 s. |
|
| It starts | `wine darkroom-desktop.exe --version` → `darkroom-desktop 0.12.0`, exit 0, 0.1 s. |
|
||||||
| The installer runs | `makensis` → 105 MB. `/S` installs the exe and seven models to `AppData\Local\Programs\DarkRoom`, writes the `HKCU` uninstall key; the installed exe runs; `uninstall.exe /S` removes directory and key. Shortcut unverifiable (§6). |
|
| The installer runs | `makensis` → 105 MB. `/S` installs the exe and ten models to `AppData\Local\Programs\DarkRoom`, writes the `HKCU` uninstall key; the installed exe runs; `uninstall.exe /S` removes directory and key. Shortcut unverifiable (§6). |
|
||||||
| It draws a window | Not attempted. |
|
| It draws a window | Not attempted. |
|
||||||
|
|
||||||
**What the first draft got wrong**, kept in place above with a note rather than rewritten, because
|
**What the first draft got wrong**, kept in place above with a note rather than rewritten, because
|
||||||
|
|||||||
Binary file not shown.
@@ -15,10 +15,19 @@ by every platform:
|
|||||||
scrfd_2.5g_640.onnx 3.3 MB "Balanced" — 14% more faces for 12% more time (faces.md §12.3)
|
scrfd_2.5g_640.onnx 3.3 MB "Balanced" — 14% more faces for 12% more time (faces.md §12.3)
|
||||||
scrfd_10g_640.onnx 17 MB "Thorough" — a further 12% for 3× the time
|
scrfd_10g_640.onnx 17 MB "Thorough" — a further 12% for 3× the time
|
||||||
arcface_mbf_b1.onnx 13 MB
|
arcface_mbf_b1.onnx 13 MB
|
||||||
|
2d106det_b1.onnx 4.8 MB 106 landmarks, for the eye boxes (faces.md §17)
|
||||||
|
ocec_s_b1.onnx 483 KB eyes open or closed, per eye
|
||||||
|
sgc_l_48_b1.onnx 6.1 MB sunglasses or not, per head
|
||||||
|
|
||||||
Which detector runs is a per-device setting (`FaceSettings::detector`); all three are installed so
|
Which detector runs is a per-device setting (`FaceSettings::detector`); all three are installed so
|
||||||
the choice exists on every platform. Each is its own `faces.model_id`, so changing it re-indexes.
|
the choice exists on every platform. Each is its own `faces.model_id`, so changing it re-indexes.
|
||||||
|
|
||||||
|
The last three are optional to the app: `library::face_models` reports them beside the pair when
|
||||||
|
all three are there, and a library without them indexes faces and simply has no eye readings —
|
||||||
|
every reader treats "never read" as unknown, never as closed. They are found in the *same*
|
||||||
|
directory as the pair, so a hand-placed pair does not pick up a package's eye models from a
|
||||||
|
directory it otherwise outranks.
|
||||||
|
|
||||||
**A clone without git-lfs gets a ~130-byte pointer where each model should be.** Both packagers check
|
**A clone without git-lfs gets a ~130-byte pointer where each model should be.** Both packagers check
|
||||||
for exactly that and refuse, rather than shipping the pointer and failing inside tract on the user's
|
for exactly that and refuse, rather than shipping the pointer and failing inside tract on the user's
|
||||||
machine. Fix it with `git lfs pull`.
|
machine. Fix it with `git lfs pull`.
|
||||||
@@ -31,7 +40,50 @@ cannot parse any of the graphs while they are dynamic:
|
|||||||
./tools/fix-face-model-shapes.sh det_2.5g.onnx models/face/scrfd_2.5g_640.onnx --input input.1=1,3,640,640
|
./tools/fix-face-model-shapes.sh det_2.5g.onnx models/face/scrfd_2.5g_640.onnx --input input.1=1,3,640,640
|
||||||
./tools/fix-face-model-shapes.sh det_10g.onnx models/face/scrfd_10g_640.onnx --input input.1=1,3,640,640
|
./tools/fix-face-model-shapes.sh det_10g.onnx models/face/scrfd_10g_640.onnx --input input.1=1,3,640,640
|
||||||
./tools/fix-face-model-shapes.sh w600k_mbf.onnx models/face/arcface_mbf_b1.onnx --dim None=1
|
./tools/fix-face-model-shapes.sh w600k_mbf.onnx models/face/arcface_mbf_b1.onnx --dim None=1
|
||||||
|
./tools/fix-face-model-shapes.sh 2d106det.onnx models/face/2d106det_b1.onnx --dim None=1
|
||||||
|
|
||||||
|
`2d106det_b1.onnx` is from the same `buffalo_l.zip` as `det_10g.onnx` and under the same grant: the
|
||||||
|
106-point landmark model whose lid contours the eye boxes are cut from (docs/faces.md §17.2).
|
||||||
|
sha256 as fetched `f001b856…a7109dbf`, as shipped `afc2984c…03368ef26`.
|
||||||
|
|
||||||
The weights carry a non-commercial research-only grant. They are here because this is a private
|
The weights carry a non-commercial research-only grant. They are here because this is a private
|
||||||
repository and self-installed builds; they come back out before anything is published, and the
|
repository and self-installed builds; they come back out before anything is published, and the
|
||||||
restriction binds whoever uses the app, not only the project. docs/faces.md §2.2a is the decision.
|
restriction binds whoever uses the app, not only the project. docs/faces.md §2.2a is the decision.
|
||||||
|
|
||||||
|
## The two classifiers are a different matter
|
||||||
|
|
||||||
|
`ocec_s_b1.onnx` and `sgc_l_48_b1.onnx` are **MIT, code and weights**, from Katsuya Hyodo's
|
||||||
|
ultra-lightweight classifier series — the same author as the whole-body detector the reference
|
||||||
|
pipeline uses. They are not under the InsightFace grant and do not come out when the project
|
||||||
|
publishes. Read 2026-09-19:
|
||||||
|
|
||||||
|
| | OCEC — open/closed eyes | SGC — sunglasses |
|
||||||
|
|---|---|---|
|
||||||
|
| Source | `github.com/PINTO0309/OCEC`, release `onnx`, `ocec_s.onnx` | `github.com/PINTO0309/SGC`, release `onnx`, `sgc_is_l_48x48.onnx` |
|
||||||
|
| Licence | MIT (repository `LICENSE`; no separate grant on the weights) | MIT, likewise |
|
||||||
|
| Training data | *Open and Closed Eyes* (Młodawski 2024, HF, **ODC-By 1.0** — attribution only), crops cut by DEIMv2-Wholebody34 (Apache 2.0) | **Not stated.** The README names no dataset and carries no acknowledgement; `data/` holds a class-ratio plot and nothing else. |
|
||||||
|
| Input | one eye, 40×24, RGB, `x/255` | one head, 48×48, RGB, `x/255` |
|
||||||
|
| Output | `prob_open`, a sigmoid | `prob_sunglasses`, a sigmoid |
|
||||||
|
| sha256 as fetched | `9a346a08…c604ba9b64` | `9c13d937…a4063c73` |
|
||||||
|
| sha256 as shipped | `c848d34c…4b29d04c7c` | `d49b6206…7e9538525c6` |
|
||||||
|
|
||||||
|
Both shipped with a dynamic batch dimension that tract loads but the project pins anyway, with the
|
||||||
|
same script as the pair:
|
||||||
|
|
||||||
|
./tools/fix-face-model-shapes.sh ocec_s.onnx models/face/ocec_s_b1.onnx --dim batch=1
|
||||||
|
./tools/fix-face-model-shapes.sh sgc_is_l_48x48.onnx models/face/sgc_l_48_b1.onnx --dim batch=1
|
||||||
|
|
||||||
|
The one thing D13's discipline turns up here is SGC's undocumented training set. The weights'
|
||||||
|
grant is MIT and that is what binds a redistributor; but "trained on what" is the question this
|
||||||
|
project reads first, and for SGC it has no answer. Recorded so it is a known gap rather than an
|
||||||
|
assumption, and so that whoever finds a sunglasses classifier with a stated dataset knows what to
|
||||||
|
replace.
|
||||||
|
|
||||||
|
Attribution, as ODC-By asks for the eye dataset:
|
||||||
|
|
||||||
|
> Michał Młodawski, *Open and Closed Eyes Dataset*, July 2024,
|
||||||
|
> https://huggingface.co/datasets/MichalMlodawski/closed-open-eyes
|
||||||
|
|
||||||
|
The variant choice was measured, not taken from the F1 column: on family snapshots the S variant
|
||||||
|
of OCEC read more open eyes as open than M or L did, which overfit their own domain
|
||||||
|
(docs/faces.md §17).
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
+7
-1
@@ -61,7 +61,13 @@ package() {
|
|||||||
# These live in LFS; a checkout without `git lfs pull` has ~130-byte
|
# These live in LFS; a checkout without `git lfs pull` has ~130-byte
|
||||||
# pointers here. Installing one produces a package whose face indexing
|
# pointers here. Installing one produces a package whose face indexing
|
||||||
# fails inside the graph loader on the user's machine, so refuse instead.
|
# fails inside the graph loader on the user's machine, so refuse instead.
|
||||||
for _m in scrfd_500m_640.onnx scrfd_2.5g_640.onnx scrfd_10g_640.onnx arcface_mbf_b1.onnx; do
|
#
|
||||||
|
# The last three are the eye-state models (docs/faces.md §17): landmarks,
|
||||||
|
# open/closed, sunglasses. Optional to the app, which indexes without
|
||||||
|
# them, but shipped beside the pair so the eyes-open filter works out of
|
||||||
|
# the box.
|
||||||
|
for _m in scrfd_500m_640.onnx scrfd_2.5g_640.onnx scrfd_10g_640.onnx arcface_mbf_b1.onnx \
|
||||||
|
2d106det_b1.onnx ocec_s_b1.onnx sgc_l_48_b1.onnx; do
|
||||||
_src="models/face/${_m}"
|
_src="models/face/${_m}"
|
||||||
if [[ "$(stat -c%s "${_src}")" -lt 100000 ]]; then
|
if [[ "$(stat -c%s "${_src}")" -lt 100000 ]]; then
|
||||||
echo "error: ${_m} is an LFS pointer, not a model — run: git lfs pull" >&2
|
echo "error: ${_m} is an LFS pointer, not a model — run: git lfs pull" >&2
|
||||||
|
|||||||
@@ -157,7 +157,8 @@ modules:
|
|||||||
# inside the graph loader on the user's machine. Refuse instead, with the
|
# inside the graph loader on the user's machine. Refuse instead, with the
|
||||||
# command that fixes it.
|
# command that fixes it.
|
||||||
- |
|
- |
|
||||||
for m in scrfd_500m_640.onnx scrfd_2.5g_640.onnx scrfd_10g_640.onnx arcface_mbf_b1.onnx; do
|
for m in scrfd_500m_640.onnx scrfd_2.5g_640.onnx scrfd_10g_640.onnx arcface_mbf_b1.onnx \
|
||||||
|
2d106det_b1.onnx ocec_s_b1.onnx sgc_l_48_b1.onnx; do
|
||||||
if [ "$(stat -c%s "models/face/$m")" -lt 100000 ]; then
|
if [ "$(stat -c%s "models/face/$m")" -lt 100000 ]; then
|
||||||
echo "error: $m is an LFS pointer, not a model — run: git lfs pull" >&2
|
echo "error: $m is an LFS pointer, not a model — run: git lfs pull" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ Section "DarkRoom" SecMain
|
|||||||
File "${STAGE}\darkroom.exe"
|
File "${STAGE}\darkroom.exe"
|
||||||
File "${STAGE}\LICENSE"
|
File "${STAGE}\LICENSE"
|
||||||
|
|
||||||
; The seven model files (four face, three scene), beside the executable,
|
; The ten model files (seven face, three scene), beside the executable,
|
||||||
; which is where `library::system_face_models_dirs` looks on Windows —
|
; which is where `library::system_face_models_dirs` looks on Windows —
|
||||||
; last, after the user's own directories, exactly as /usr/share is on Linux.
|
; last, after the user's own directories, exactly as /usr/share is on Linux.
|
||||||
SetOutPath "$INSTDIR\models"
|
SetOutPath "$INSTDIR\models"
|
||||||
|
|||||||
@@ -11,6 +11,14 @@
|
|||||||
# ... det_10g.onnx scrfd_10g_640.onnx --input input.1=1,3,640,640
|
# ... det_10g.onnx scrfd_10g_640.onnx --input input.1=1,3,640,640
|
||||||
# ... w600k_mbf.onnx arcface_mbf_b1.onnx --dim None=1
|
# ... w600k_mbf.onnx arcface_mbf_b1.onnx --dim None=1
|
||||||
#
|
#
|
||||||
|
# And the three eye-state models, verified 2026-09-19 (docs/faces.md §17).
|
||||||
|
# The landmark model's batch is the literal "None" like the embedder's; the
|
||||||
|
# two classifiers' is a *named* dim_param "batch":
|
||||||
|
#
|
||||||
|
# ... 2d106det.onnx 2d106det_b1.onnx --dim None=1
|
||||||
|
# ... ocec_s.onnx ocec_s_b1.onnx --dim batch=1
|
||||||
|
# ... sgc_is_l_48x48.onnx sgc_l_48_b1.onnx --dim batch=1
|
||||||
|
#
|
||||||
# ## Why this exists
|
# ## Why this exists
|
||||||
#
|
#
|
||||||
# The InsightFace exports declare dynamic input dimensions — SCRFD's H and W,
|
# The InsightFace exports declare dynamic input dimensions — SCRFD's H and W,
|
||||||
|
|||||||
Reference in New Issue
Block a user