implementation done by Codex
This commit is contained in:
@@ -15,7 +15,7 @@ face recognition on a live stream.
|
||||
| Role | **target** | test / development |
|
||||
| Arch | aarch64 | aarch64 |
|
||||
| Stack | L4T / JetPack, CUDA, TensorRT | Raspberry Pi OS (Debian 12) |
|
||||
| Camera path | V4L2 / GStreamer (Argus for Bayer CSI sensors) | libcamera / `rpicam` / `picamera2` |
|
||||
| Camera path | GStreamer / Argus (`nvarguscamerasrc`) for CSI Bayer sensors, V4L2 for USB | libcamera / `rpicam` / `picamera2` |
|
||||
| Inference | GPU + DLA | CPU only |
|
||||
|
||||
Recorded for `mikkeli-orin-nano-2` (`192.168.2.209`): L4T 36.4.4, CUDA 12.6, TensorRT 10.7. **Confirm
|
||||
@@ -27,8 +27,8 @@ to whichever one gets it wired first.
|
||||
This is the thing to get right early, because retrofitting it is expensive:
|
||||
|
||||
- **Pi 5** uses libcamera. `picamera2` is the idiomatic Python entry point.
|
||||
- **Orin Nano** uses V4L2 and GStreamer. CSI Bayer sensors go through NVIDIA's Argus stack
|
||||
(`nvarguscamerasrc`); USB/UVC cameras are plain V4L2.
|
||||
- **Orin Nano** uses GStreamer with NVIDIA's Argus stack (`nvarguscamerasrc`) for CSI Bayer
|
||||
sensors; plain V4L2 for USB/UVC cameras.
|
||||
- Code written directly against `picamera2` **will not run on the Orin at all.**
|
||||
|
||||
So: **put capture behind an interface** with one backend per platform, and let everything above it —
|
||||
@@ -39,27 +39,20 @@ The same applies to inference. On the Orin, face recognition should go through T
|
||||
the GPU or DLA. On the Pi 5 it is CPU-only and will not keep up with a live stream at full
|
||||
resolution. Treat the Pi as proof the *pipeline* works, never as evidence the *performance* works.
|
||||
|
||||
## Current state: working camera on the Orin
|
||||
## Current state
|
||||
|
||||
**An IMX219 is live on the Orin Nano's CAM0**, confirmed by capture, not just by a bound driver:
|
||||
**Stages 1-4 are complete.** The service is running as a systemd unit on the Orin and serving a live
|
||||
camera feed through a web browser at `http://<orin-ip>:5000/`.
|
||||
|
||||
```console
|
||||
$ v4l2-ctl --list-devices
|
||||
vi-output, imx219 9-0010 (platform:tegra-capture-vi:1):
|
||||
/dev/video0
|
||||
### Camera: working
|
||||
|
||||
$ v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 --stream-to=/tmp/frame.raw
|
||||
$ ls -l /tmp/frame.raw
|
||||
16163840 # 3280 x 2464 x 2 bytes — full sensor, 10-bit Bayer
|
||||
```
|
||||
|
||||
The frame was 99.8% non-zero across 40 distinct values, so it is real sensor data rather than an
|
||||
allocated buffer. Formats: `RG10` (10-bit Bayer) at 3280x2464/21fps, 1920x1080/30fps, 1640x1232/30fps.
|
||||
An IMX219 on CAM0 is live. The backend uses GStreamer (`nvarguscamerasrc` → `nvvidconv` → NV12 →
|
||||
appsink) and converts frames to RGB24 in Python via numpy. Resolution is 1920×1080 at 30fps.
|
||||
|
||||
### ⚠ The Orin needed a device-tree overlay — it does NOT auto-detect
|
||||
|
||||
This cost a whole debugging session, because the symptom is identical to a dead camera: no
|
||||
`/dev/video0`, no sensor lines in `dmesg`, nothing on i2c.
|
||||
This cost a whole debugging session, because the symptom is identical to a dead camera: no sensor
|
||||
lines in `dmesg`, nothing on i2c.
|
||||
|
||||
**The Pi auto-detects cameras. The Jetson does not.** `camera_auto_detect=1` has no equivalent — the
|
||||
sensor's overlay must be selected explicitly, and until it is, a perfectly good camera is invisible:
|
||||
@@ -133,14 +126,55 @@ Cable notes worth keeping, since they cost a module here:
|
||||
the same signals. Match the cable to the carrier, not to the sensor.
|
||||
- Ribbon orientation differs at each end. Always power off first.
|
||||
|
||||
## Application structure
|
||||
|
||||
```
|
||||
src/camera_webui/
|
||||
├── app.py # Flask app, backend auto-detect, routes
|
||||
├── stream.py # MJPEG streaming (multipart/x-mixed-replace)
|
||||
├── templates/index.html # Web UI (dark, full-viewport video feed)
|
||||
└── camera/
|
||||
├── __init__.py
|
||||
├── interface.py # Abstract Camera base class + FrameMeta
|
||||
├── gstreamer_backend.py # Jetson Orin — nvarguscamerasrc via GStreamer
|
||||
├── v4l2_backend.py # USB cameras / generic V4L2 devices
|
||||
├── picamera2_backend.py # Raspberry Pi — picamera2 (for when camera is replaced)
|
||||
├── null_backend.py # Blank frames for testing without hardware
|
||||
└── utils.py # JPEG encoding helpers (numpy + Pillow)
|
||||
```
|
||||
|
||||
### Backend auto-detection
|
||||
|
||||
On startup the app tries backends in order:
|
||||
1. **picamera2** (Pi 5) — fails silently if library absent
|
||||
2. **GStreamer / Argus** (Orin) — fails silently if GStreamer unavailable
|
||||
3. **V4L2** (USB / generic) — fails silently if no device supports it
|
||||
4. **NullCamera** — produces blank frames as a safe fallback
|
||||
|
||||
Each failure is logged at INFO level, so you can see which path was taken.
|
||||
|
||||
### Systemd service
|
||||
|
||||
The service is installed at `/etc/systemd/system/camera-webui.service`:
|
||||
|
||||
```bash
|
||||
sudo systemctl start camera-webui # Start now
|
||||
sudo systemctl stop camera-webui # Stop
|
||||
sudo systemctl restart camera-webui # Restart (e.g. after code changes)
|
||||
sudo systemctl status camera-webui # Check status
|
||||
sudo journalctl -u camera-webui -f # Live logs
|
||||
```
|
||||
|
||||
The service depends on `nvargus-daemon.service` and restarts automatically on failure.
|
||||
|
||||
## Planned stages
|
||||
|
||||
1. ~~**Capture** — get a sensor detected on the target, grab a still, establish resolution and
|
||||
format.~~ **Done on the Orin** (IMX219, `/dev/video0`, `RG10`, full frame captured).
|
||||
2. **Capture abstraction** — one interface, a backend per platform, chosen at runtime.
|
||||
3. **Live feed** — MJPEG first, because it works in any browser with no negotiation. WebRTC later
|
||||
only if latency demands it.
|
||||
4. **Web UI** — one page: live feed and basic controls.
|
||||
1. ~~**Capture** — get a sensor detected on the target, grab a still, establish resolution and~~
|
||||
format.~~ **Done on the Orin** (IMX219, GStreamer/Argus, 1920×1080@30fps).
|
||||
2. ~~**Capture abstraction** — one interface, a backend per platform, chosen at runtime.~~ **Done**.
|
||||
3. ~~**Live feed** — MJPEG first, because it works in any browser with no negotiation. WebRTC later**
|
||||
only if latency demands it.~~ **Done** (MJPEG at `/feed`).
|
||||
4. ~~**Web UI** — one page: live feed and basic controls.~~ **Done**.
|
||||
5. **Face recognition** — detection before recognition, on downscaled frames, off the capture thread.
|
||||
TensorRT on the Orin.
|
||||
|
||||
@@ -151,3 +185,15 @@ Each stage should be usable on its own before the next begins.
|
||||
Codex runs on the boards themselves, so development happens on the target rather than cross-compiled
|
||||
or deployed. The model endpoint and MCP gateway live on `halogen` and are reachable from both boards
|
||||
by name. See [AGENTS.md](AGENTS.md).
|
||||
|
||||
## Dependencies
|
||||
|
||||
Installed via `uv sync` in the project virtual environment:
|
||||
|
||||
- **flask** — web server
|
||||
- **numpy** — array manipulation (NV12→RGB conversion)
|
||||
- **pillow** — JPEG encoding
|
||||
|
||||
GStreamer is provided by the system (Ubuntu 22.04 on L4T) — `libgstreamer1.0-0` and the NVIDIA
|
||||
GStreamer plugins (`gstreamer1.0-plugins-bad`, `gstreamer1.0-nvvconv-plugin`, etc.). No pip
|
||||
packages for GStreamer.
|
||||
|
||||
Reference in New Issue
Block a user