File size: 6,077 Bytes
18224de 72834ef 18224de 72834ef 18224de 72834ef 18224de 72834ef 18224de | 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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | ---
license: mit
pipeline_tag: video-classification
library_name: blinklinmult
tags:
- blink-detection
- eye-state-recognition
- eye-tracking
- onnx
---
# BlinkLinMulT — pretrained blink and eye-state models
Models for **eye-state recognition** (per-frame closure) and **blink
detection** (temporal events), from the
[BlinkLinMulT](https://github.com/fodorad/BlinkLinMulT) project.
Three are the published 1.x models from
[*BlinkLinMulT: Transformer-Based Eye Blink Detection*](https://www.mdpi.com/2313-433X/9/10/196)
(J. Imaging, 2023), shipped as frozen ONNX graphs. The v2 frame-wise model ships
twice: as a PyTorch checkpoint (`blinkcnn`, trainable and fine-tunable) and as
the same weights frozen into a graph (`blinkcnn-onnx`, deployable).
## Models
| id | runtime | input | output | notes |
|---|---|---|---|---|
| `densenet121-union` | ONNX | `(B, T, 3, 64, 64)` | `(B, T, 1)` | 1.x frame-wise, DenseNet121 |
| `blinklint-union` | ONNX | `(B, T, 3, 64, 64)` | `(B, T, 1)` | 1.x sequence, DenseNet121 + LinT |
| `blinklinmult-union` | ONNX | crops + `(B, T, 160)` | `(B, T, 1)` and `(B, 1)` | 1.x two-stream, the paper's headline model |
| `blinkcnn` | PyTorch | `(B, T, 3, 64, 64)` | `(B, T, 1)` | v2 frame-wise, ConvNeXt-Femto |
| `blinkcnn-onnx` | ONNX | `(B, T, 3, 64, 64)` | `(B, T, 1)` | the same weights, frozen — **4.9x faster** |
Both axes are dynamic: `T=1` is a single frame, any larger `T` is video.
`blinkcnn` and `blinkcnn-onnx` are the *same weights*. The graph was exported
behind a numerical-parity gate — three window lengths, three input
distributions, worst deviation **2.6e-06**, and an end-to-end check through the
public API at **4.8e-07** — so it is a drop-in replacement, not an
approximation. Measured on CPU it runs **20.19 → 4.15 ms/frame** and loads in
560 ms rather than 2481. Prefer `blinkcnn-onnx` unless you are fine-tuning.
The ONNX models need no PyTorch at all: `pip install blinklinmult[onnx]` is a
~100 MB install against ~600 MB for the training stack.
## Usage
```python
from blinklinmult import BlinkDetector
model = BlinkDetector.from_pretrained("blinklint-union")
signal = model.score(crops) # (T,) closure probability per frame
blinks = model.detect(crops) # [(start, end), ...] frame indices
```
Weights download and cache on first use. Crops are `(T, 3, 64, 64)` float32 in
`[0, 1]`, channel-first — **`BlinkDetector` applies each model's own
normalisation**, so pass raw `[0, 1]` values and do not pre-normalise.
Install: `pip install blinklinmult[onnx]` for the ONNX models, plus `torch` for
`blinkcnn`.
## Normalisation — the one thing to get right
The two generations were trained on **different input conventions**, and applying
the wrong one costs accuracy *without raising an error*:
| generation | normalisation |
|---|---|
| 1.x (`*-union`) | ImageNet, mean `(0.485, 0.456, 0.406)` / std `(0.229, 0.224, 0.225)` |
| v2 (`blinkcnn`) | plain `/255` — no shift, no scale |
Each file's `.json` sidecar records its own constants. Use `BlinkDetector` and
this is handled for you.
## Events, and the operating points
Blink intervals come from **hysteresis extraction** over the per-frame signal: a
run must peak above a high threshold to count as a blink, but extends while it
stays above a low one. The high threshold decides *whether*, the low one decides
*where* — a single cut has to be low enough to catch a closure's shallow onset
and high enough to ignore noise, and no one value does both.
| model | threshold | low ratio | fitted? |
|---|---:|---:|---|
| `blinkcnn` | 0.53 | 0.25 | **yes**, on validation |
| the three `*-union` models | 0.50 | — | **no** — see below |
**The 1.x thresholds are placeholders, not measurements.** Those models were
released as weights alone, with no operating point, and refitting one would mean
re-running their validation splits under this codebase. So:
* frame-level `score()` output is **directly comparable** across generations;
* event-level counts from `detect()` are **not** — `blinkcnn`'s threshold was
tuned and theirs was not.
To close that gap, fit on your own validation data:
```python
from blinklinmult.fit import fit_operating_point
point = fit_operating_point(signals, annotations) # validation only
model.calibrate(point)
```
## Provenance
Each ONNX graph was exported from the original published PyTorch weights and
**verified against them before release** at window lengths 1, 15, 30 and 45 —
including lengths the export never traced, which is what proves the time axis is
genuinely dynamic rather than unrolled. Maximum observed deviation across all
three models and all lengths: **3.4e-05**, i.e. float32 rounding.
Every `.json` sidecar carries the source checkpoint's filename and SHA-256, the
opset, the normalisation, and the parity tolerances.
## Limitations
* **Eye crops in, not faces.** These models score a cropped eye region, not a
full frame. Crop geometry matters: v2 was trained on a square `2.0 ×` the eye's
corner-to-corner span. A different crop convention degrades accuracy.
* **`blinklinmult-union` needs the 160-d descriptor stream** (iris landmarks,
eyelid distances, EAR, head pose) alongside the crops. Producing it requires
the `[preprocess]` extra.
* **Frozen.** The 1.x models ship as graphs and cannot be fine-tuned. That is
deliberate: they are published artefacts.
* Trained on public blink corpora that skew toward frontal, indoor webcam
footage. Expect degradation on strong profile views, heavy occlusion, or
infrared imagery.
## Citation
```bibtex
@article{fodor2023blinklinmult,
title = {BlinkLinMulT: Transformer-Based Eye Blink Detection},
author = {Fodor, {\'A}d{\'a}m and Fenech, Kristian and L{\H{o}}rincz, Andr{\'a}s},
journal = {Journal of Imaging},
volume = {9},
number = {10},
pages = {196},
year = {2023},
doi = {10.3390/jimaging9100196}
}
```
## License
MIT, matching the source repository. The corpora these models were trained on
each carry their own licence and are **not** redistributed here.
|