|
| 1 | +# Lenovo IR Camera — Debug Guide |
| 2 | + |
| 3 | +## Symptom |
| 4 | + |
| 5 | +``` |
| 6 | +Response: ENROLL_FAIL Found 0 faces in ir. Expecting exactly 1. |
| 7 | +``` |
| 8 | + |
| 9 | +## Root Cause |
| 10 | + |
| 11 | +The YuNet face detector assigns **significantly lower confidence scores** to faces |
| 12 | +captured by IR cameras (grayscale image, low contrast, often overexposed). |
| 13 | + |
| 14 | +The default threshold of `0.9` was tuned for standard RGB webcams. On IR cameras |
| 15 | +it is nearly impossible to reach. Lowering it to `0.5–0.6` resolves the issue |
| 16 | +in most cases. |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## Quick Fix (no recompilation required) |
| 21 | + |
| 22 | +Edit `/etc/linuxcampam/config.ini`: |
| 23 | + |
| 24 | +```ini |
| 25 | +[Auth] |
| 26 | +detection_threshold = 0.5 |
| 27 | + |
| 28 | +[Capture] |
| 29 | +enroll_averaging = on |
| 30 | +enroll_average_frames = 7 |
| 31 | +``` |
| 32 | + |
| 33 | +Then restart the daemon: |
| 34 | +```bash |
| 35 | +sudo systemctl restart linuxcampam |
| 36 | +``` |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +## Diagnosing with logs |
| 41 | + |
| 42 | +During enrollment, watch the logs in real time: |
| 43 | + |
| 44 | +```bash |
| 45 | +journalctl -u linuxcampam -f |
| 46 | +``` |
| 47 | + |
| 48 | +What to look for: |
| 49 | + |
| 50 | +| Log message | Meaning | |
| 51 | +|-------------|---------| |
| 52 | +| `0 faces found above threshold (0.9)` | Threshold too high → lower it | |
| 53 | +| `Best score: 0.65 \| Threshold: 0.9` | Face found but below threshold → lower to 0.55 | |
| 54 | +| `Brightness: 12` | Frame nearly black → IR emitter not activating | |
| 55 | +| `Brightness: 240` | Frame overexposed → camera exposure issue | |
| 56 | + |
| 57 | +--- |
| 58 | + |
| 59 | +## Inspecting the failed frame |
| 60 | + |
| 61 | +On every failed enrollment the captured frame is automatically saved to: |
| 62 | + |
| 63 | +``` |
| 64 | +/var/log/linuxcampam/failed_enroll_ir_<username>.jpg |
| 65 | +``` |
| 66 | + |
| 67 | +Open it to check what the camera actually captured: |
| 68 | +```bash |
| 69 | +xdg-open /var/log/linuxcampam/failed_enroll_ir_<username>.jpg |
| 70 | +``` |
| 71 | + |
| 72 | +- **Nearly black frame** → IR emitter did not activate (see section below) |
| 73 | +- **Grainy / blurry frame** → increase `enroll_average_frames` |
| 74 | +- **Face visible but not detected** → lower `detection_threshold` further |
| 75 | +- **No face in frame** → positioning issue during enrollment |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +## IR emitter not activating |
| 80 | + |
| 81 | +If the saved frame is nearly black: |
| 82 | + |
| 83 | +1. Verify `linux-enable-ir-emitter` is installed: |
| 84 | + ```bash |
| 85 | + ls /usr/local/bin/linux-enable-ir-emitter |
| 86 | + ``` |
| 87 | + |
| 88 | +2. Test it manually: |
| 89 | + ```bash |
| 90 | + sudo linux-enable-ir-emitter run |
| 91 | + ``` |
| 92 | + |
| 93 | +3. If not installed: |
| 94 | + ```bash |
| 95 | + sudo apt install linux-enable-ir-emitter |
| 96 | + # or from source: https://github.com/EmixamPP/linux-enable-ir-emitter |
| 97 | + ``` |
| 98 | + |
| 99 | +4. Configure for your specific hardware model: |
| 100 | + ```bash |
| 101 | + sudo linux-enable-ir-emitter configure |
| 102 | + ``` |
| 103 | + Follow the interactive procedure — move your head in front of the camera |
| 104 | + while it tries different configurations. When the emitter blinks and you see |
| 105 | + `The infrared emitter has been successfully enabled!` you are done. |
| 106 | + |
| 107 | +--- |
| 108 | + |
| 109 | +## Finding the correct IR camera device |
| 110 | + |
| 111 | +```bash |
| 112 | +# List all webcams |
| 113 | +v4l2-ctl --list-devices |
| 114 | + |
| 115 | +# Identify the IR camera by checking shape and brightness |
| 116 | +python3 -c " |
| 117 | +import cv2 |
| 118 | +for i in range(4): |
| 119 | + cap = cv2.VideoCapture(i) |
| 120 | + if not cap.isOpened(): |
| 121 | + print(f'video{i}: could not open') |
| 122 | + continue |
| 123 | + ret, frame = cap.read() |
| 124 | + cap.release() |
| 125 | + if ret: |
| 126 | + print(f'video{i}: shape={frame.shape} brightness={frame.mean():.0f}') |
| 127 | +" |
| 128 | +``` |
| 129 | + |
| 130 | +The IR camera has **1 channel** (shape like `(360, 640)`) instead of 3 (RGB). |
| 131 | +On Lenovo laptops it is typically `/dev/video2`. |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +## Recommended settings for Lenovo ThinkPad / IdeaPad |
| 136 | + |
| 137 | +```ini |
| 138 | +[Auth] |
| 139 | +detection_threshold = 0.5 |
| 140 | +timeout_ms = 5000 |
| 141 | + |
| 142 | +[Capture] |
| 143 | +enroll_hdr = off ; IR cameras do not support HDR |
| 144 | +enroll_averaging = on |
| 145 | +enroll_average_frames = 7 |
| 146 | + |
| 147 | +[Hardware] |
| 148 | +camera_path_ir = /dev/video2 ; verify with v4l2-ctl --list-devices |
| 149 | +``` |
| 150 | + |
| 151 | +--- |
| 152 | + |
| 153 | +## Code changes (this fix) |
| 154 | + |
| 155 | +| File | Change | |
| 156 | +|------|--------| |
| 157 | +| `include/constants.hpp` | `IR_TRIGGER_DELAY_MS`: 200 → 1500 ms | |
| 158 | +| `include/constants.hpp` | `CAMERA_WARMUP_FRAMES`: 10 → 15 | |
| 159 | +| `include/constants.hpp` | `CAMERA_WARMUP_DELAY_MS`: 100 → 200 ms | |
| 160 | +| `src/service/config.hpp` | `DEFAULT_DETECTION_THRESHOLD`: 0.9 → 0.6 | |
| 161 | +| `src/service/auth_engine.cpp` | Log YuNet score + brightness in `generateEmbedding` | |
| 162 | +| `src/service/auth_engine.cpp` | Always save failed enrollment frame with diagnostic info | |
| 163 | +| `config/config.ini` | Updated default `detection_threshold` + averaging enabled | |
0 commit comments