# L2CS-Net
L2CS-Net is a two-stage gaze estimator: a face detector locates faces, and a ResNet trunk with two angle-bin classification heads predicts pitch and yaw per face. LibreYOLO wraps it for inference only.
Tasks: Gaze. Install: pip install libreyolo.
Verified against LibreYOLO v1.5.0.

## Install

L2CS-Net needs no extra to construct, predict on, or export a model you
already have a checkpoint for.

```bash
pip install libreyolo
```

The one checkpoint LibreYOLO can fetch automatically, a Gaze360-trained
ResNet-50, downloads over `gdown` rather than a plain HTTP mirror, because it
lives on the author's Google Drive rather than the LibreYOLO org. That path
needs the `gaze` extra:

```bash
pip install "libreyolo[gaze]"
```

Without it, LibreYOLO prints manual download instructions instead of failing
silently. In 1.6.0 the upstream Google Drive download returns 404, so place a
local copy of the checkpoint at the path you load.

## Predict

**Python**

```python
from libreyolo import LibreYOLO, SAMPLE_IMAGE

# No face_detector given: falls back to OpenCV's bundled face
# detector (Haar on OpenCV 4, YuNet on OpenCV 5), so this runs with
# no extra download beyond the L2CS checkpoint itself.
model = LibreYOLO("LibreL2CSr50.pt")
result = model(SAMPLE_IMAGE)

print(result.gaze.pitch, result.gaze.yaw)
```

**CLI**

```bash
libreyolo predict model=LibreL2CSr50.pt source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpg save=True
```

**Face source**

```python
from libreyolo import LibreYOLO, SAMPLE_IMAGE

model = LibreYOLO("LibreL2CSr50.pt")

# Hand L2CS boxes from a detector you already ran.
result = model(SAMPLE_IMAGE, face_boxes=[[34, 12, 90, 80]])

# Or name a specific bundled face detector.
result = model(SAMPLE_IMAGE, face_detector="yunet")
```

L2CS-Net is a two-stage estimator: a face detector runs first, and the gaze
head reads pitch and yaw from each face crop it returns. Left alone, prediction
falls back to OpenCV's bundled detector, so a bare call works with no
additional download once the L2CS checkpoint itself is in hand. `face_boxes`
accepts boxes from a detector you already ran; `face_detector` accepts
`"auto"`, `"haar"`, `"yunet"`, a LibreYOLO detection model, or a plain
callable. `result.gaze` carries pitch and yaw in radians, aligned row by
row with `result.boxes`, the detected face boxes. See
[prediction](/docs/predict) for sources, streaming and result handling.

## Variants

Five backbone depths share one input resolution and take the same arguments.
Gaze360, the dataset behind the only published checkpoint, trained a
ResNet-50; the other four depths are supported architecturally but have no
published weights to load.

## Export

| Task | ONNX | TorchScript | ExecuTorch | TensorRT | OpenVINO | Paddle | MNN | RKNN | ncnn | TFLite | CoreML | Core AI |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Gaze | yes | yes | yes | yes | yes |  |  |  |  |  |  |  |

A "yes" means the export is supported. An empty cell means the exporter refuses that combination.

**Python**

```python
from libreyolo import LibreYOLO

model = LibreYOLO("LibreL2CSr50.pt")
model.export(format="onnx")
model.export(format="tensorrt", half=True)
```

**CLI**

```bash
libreyolo export model=LibreL2CSr50.pt format=onnx
```

**Use the exported file**

```python
import numpy as np
import onnxruntime as ort

# The exported graph is the ResNet trunk and the two angle-bin heads
# alone: it takes a preprocessed 448x448 face crop and returns raw
# (yaw_logits, pitch_logits), not decoded angles. The softmax,
# bin-expectation and degree conversion stay in Python; see
# libreyolo.models.l2cs.utils.bin_logits_to_angles.
session = ort.InferenceSession("LibreL2CSr50.onnx")
name = session.get_inputs()[0].name
yaw_logits, pitch_logits = session.run(
    None, {name: np.zeros((1, 3, 448, 448), dtype=np.float32)}
)
```

## Licensing

Check the license on the Hugging Face repository of the specific weights you download. That repository is authoritative and licenses are not always uniform across a family. This is a description of the licenses involved, not legal advice.

- Original work: L2CS-Net, Ahmed A. Abdelrahman et al.
- Upstream license: Gaze360 dataset terms: research and non-commercial use only, no redistribution
- Upstream source: https://github.com/Ahmednull/L2CS-Net
- LibreYOLO code: MIT
- Weights: Gaze360 dataset terms: research and non-commercial use only, no redistribution, distributed by their authors. LibreYOLO does not host or mirror them.
- Interpretation: The L2CS-Net source is MIT and may be used, modified and redistributed, including commercially. The one checkpoint LibreYOLO can fetch automatically, a ResNet-50 trained on Gaze360, is not covered by that grant: the Gaze360 dataset license restricts use of models trained on it to research and non-commercial purposes and forbids redistribution. LibreYOLO does not mirror or host that checkpoint. It downloads it, on request, directly from the author's own distribution and prints the Gaze360 terms once before the transfer starts. A model trained on data you hold the rights to, using this MIT architecture, carries none of those restrictions.

LibreYOLO does not host or mirror any L2CS checkpoint: nothing for this family
exists in the LibreYOLO Hugging Face org, unlike most other families on this
site. The one checkpoint the library can fetch automatically comes straight
from the author's own Google Drive distribution, gated behind the Gaze360
license notice printed before the transfer starts, and is not the "republished
at huggingface.co/LibreYOLO" copy the summary above implies.


## L2CS-Net architecture

- r18: [SVG](/diagrams/models/l2cs/r18-gaze.svg), [interactive diagram](/diagrams/models/l2cs/r18-gaze.html)
- r34: [SVG](/diagrams/models/l2cs/r34-gaze.svg), [interactive diagram](/diagrams/models/l2cs/r34-gaze.html)
- r50: [SVG](/diagrams/models/l2cs/r50-gaze.svg), [interactive diagram](/diagrams/models/l2cs/r50-gaze.html)
- r101: [SVG](/diagrams/models/l2cs/r101-gaze.svg), [interactive diagram](/diagrams/models/l2cs/r101-gaze.html)
- r152: [SVG](/diagrams/models/l2cs/r152-gaze.svg), [interactive diagram](/diagrams/models/l2cs/r152-gaze.html)
- basic-family: [SVG](/diagrams/models/l2cs/basic-family.svg), [interactive diagram](/diagrams/models/l2cs/basic-family.html)
- bottleneck-family: [SVG](/diagrams/models/l2cs/bottleneck-family.svg), [interactive diagram](/diagrams/models/l2cs/bottleneck-family.html)
