diff --git a/docs/source/_toctree.yml b/docs/source/_toctree.yml
index eb97117af..98417f134 100644
--- a/docs/source/_toctree.yml
+++ b/docs/source/_toctree.yml
@@ -7,8 +7,6 @@
- sections:
- local: il_robots
title: Imitation Learning for Robots
- - local: cameras
- title: Cameras
- local: bring_your_own_policies
title: Bring Your Own Policies
- local: integrate_hardware
@@ -108,6 +106,10 @@
- local: phone_teleop
title: Phone
title: "Teleoperators"
+- sections:
+ - local: cameras
+ title: Cameras
+ title: "Sensors"
- sections:
- local: torch_accelerators
title: PyTorch accelerators
diff --git a/docs/source/cameras.mdx b/docs/source/cameras.mdx
index 5c35be0ba..8af0f5ae5 100644
--- a/docs/source/cameras.mdx
+++ b/docs/source/cameras.mdx
@@ -1,12 +1,22 @@
# Cameras
-LeRobot offers multiple options for video capture, including phone cameras, built-in laptop cameras, external webcams, and Intel RealSense cameras. To efficiently record frames from most cameras, you can use either the `OpenCVCamera` or `RealSenseCamera` class. For additional compatibility details on the `OpenCVCamera` class, refer to the [Video I/O with OpenCV Overview](https://docs.opencv.org/4.x/d0/da7/videoio_overview.html).
+LeRobot offers multiple options for video capture:
-### Finding your camera
+| Class | Supported Cameras |
+| ----------------- | ----------------------------------- |
+| `OpenCVCamera` | Phone, built-in laptop, USB webcams |
+| `ZMQCamera` | Network-connected cameras |
+| `RealSenseCamera` | Intel RealSense (with depth) |
+| `Reachy2Camera` | Reachy 2 robot cameras |
-To instantiate a camera, you need a camera identifier. This identifier might change if you reboot your computer or re-plug your camera, a behavior mostly dependant on your operating system.
+> [!TIP]
+> For `OpenCVCamera` compatibility details, see the [Video I/O with OpenCV Overview](https://docs.opencv.org/4.x/d0/da7/videoio_overview.html).
-To find the camera indices of the cameras plugged into your system, run the following script:
+### Find your camera
+
+Every camera requires a unique identifier to be instantiated, allowing you to distinguish between multiple connected devices.
+
+`OpenCVCamera` and `RealSenseCamera` support auto-discovery. Run the command below to list available devices and their identifiers. Note that these identifiers may change after rebooting your computer or re-plugging the camera, depending on your operating system.
```bash
lerobot-find-cameras opencv # or realsense for Intel Realsense cameras
@@ -14,7 +24,7 @@ lerobot-find-cameras opencv # or realsense for Intel Realsense cameras
The output will look something like this if you have two cameras connected:
-```
+```bash
--- Detected Cameras ---
Camera #0:
Name: OpenCV Camera @ 0
@@ -33,13 +43,37 @@ Camera #0:
> [!WARNING]
> When using Intel RealSense cameras in `macOS`, you could get this [error](https://github.com/IntelRealSense/librealsense/issues/12307): `Error finding RealSense cameras: failed to set power state`, this can be solved by running the same command with `sudo` permissions. Note that using RealSense cameras in `macOS` is unstable.
-## Use Cameras
+`ZMQCamera` and `Reachy2Camera` do not support auto-discovery. They must be configured manually by providing their network address and port or robot SDK settings.
-Below are two examples, demonstrating how to work with the API.
+## Use cameras
-- **Asynchronous frame capture** using an OpenCV-based camera
+### Frame access modes
+
+All camera classes implement three access modes for capturing frames:
+
+| Method | Behavior | Blocks? | Best For |
+| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------- |
+| `read()` | Waits for the camera hardware to return a frame. May block for a long time depending on the camera and SDK. | Yes | Simple scripts, sequential capture |
+| `async_read(timeout_ms)` | Returns the latest unconsumed frame from background thread. Blocks only if buffer is empty, up to `timeout_ms`. Raises `TimeoutError` if no frame arrives. | With a timeout | Control loops synchronized to camera FPS |
+| `read_latest(max_age_ms)` | Peeks at the most recent frame in buffer (may be stale). Raises `TimeoutError` if frame is older than `max_age_ms`. | No | UI visualization, logging, monitoring |
+
+### Usage examples
+
+The following examples show how to use the camera API to configure and capture frames from different camera types.
+
+- **Blocking and non-blocking frame capture** using an OpenCV-based camera
- **Color and depth capture** using an Intel RealSense camera
+> [!WARNING]
+> Failing to cleanly disconnect cameras can cause resource leaks. Use the context manager protocol to ensure automatic cleanup:
+>
+> ```python
+> with OpenCVCamera(config) as camera:
+> ...
+> ```
+>
+> You can also call `connect()` and `disconnect()` manually, but always use a `finally` block for the latter.
+
@@ -60,16 +94,30 @@ config = OpenCVCameraConfig(
)
# Instantiate and connect an `OpenCVCamera`, performing a warm-up read (default).
-camera = OpenCVCamera(config)
-camera.connect()
+with OpenCVCamera(config) as camera:
+
+ # Read a frame synchronously — blocks until hardware delivers a new frame
+ frame = camera.read()
+ print(f"read() call returned frame with shape:", frame.shape)
+
+ # Read a frame asynchronously with a timeout — returns the latest unconsumed frame or waits up to timeout_ms for a new one
+ try:
+ for i in range(10):
+ frame = camera.async_read(timeout_ms=200)
+ print(f"async_read call returned frame {i} with shape:", frame.shape)
+ except TimeoutError as e:
+ print(f"No frame received within timeout: {e}")
+
+ # Instantly return a frame - returns the most recent frame captured by the camera
+ try:
+ initial_frame = camera.read_latest(max_age_ms=1000)
+ for i in range(10):
+ frame = camera.read_latest(max_age_ms=1000)
+ print(f"read_latest call returned frame {i} with shape:", frame.shape)
+ print(f"Was a new frame received by the camera? {not (initial_frame == frame).any()}")
+ except TimeoutError as e:
+ print(f"Frame too old: {e}")
-# Read frames asynchronously in a loop via `async_read(timeout_ms)`
-try:
- for i in range(10):
- frame = camera.async_read(timeout_ms=200)
- print(f"Async frame {i} shape:", frame.shape)
-finally:
- camera.disconnect()
```
@@ -111,10 +159,10 @@ finally:
-## Use your phone
+## Use your phone's camera
-
+
To use your iPhone as a camera on macOS, enable the Continuity Camera feature:
@@ -124,83 +172,49 @@ To use your iPhone as a camera on macOS, enable the Continuity Camera feature:
For more details, visit [Apple support](https://support.apple.com/en-gb/guide/mac-help/mchl77879b8a/mac).
-Your iPhone should be detected automatically when running the camera setup script in the next section.
-
-
+
-If you want to use your phone as a camera on Linux, follow these steps to set up a virtual camera
+If you want to use your phone as a camera using OBS, follow these steps to set up a virtual camera.
-1. _Install `v4l2loopback-dkms` and `v4l-utils`_. Those packages are required to create virtual camera devices (`v4l2loopback`) and verify their settings with the `v4l2-ctl` utility from `v4l-utils`. Install them using:
+1. _(Linux only) Install `v4l2loopback-dkms` and `v4l-utils`_. These packages create virtual camera devices and verify their settings. Install with:
-
-```python
+```bash
sudo apt install v4l2loopback-dkms v4l-utils
```
-
-2. _Install [DroidCam](https://droidcam.app) on your phone_. This app is available for both iOS and Android.
-3. _Install [OBS Studio](https://obsproject.com)_. This software will help you manage the camera feed. Install it using [Flatpak](https://flatpak.org):
+2. _Install the [DroidCam app](https://droidcam.app) on your phone_. This app is available for both iOS and Android.
+3. _Download and install [OBS Studio](https://obsproject.com)_.
+4. _Download and install the [DroidCam OBS plugin](https://droidcam.app/obs)_.
+5. _Start OBS Studio_.
-
-```python
-flatpak install flathub com.obsproject.Studio
-```
-
-
-4. _Install the DroidCam OBS plugin_. This plugin integrates DroidCam with OBS Studio. Install it with:
-
-
-```python
-flatpak install flathub com.obsproject.Studio.Plugin.DroidCam
-```
-
-
-5. _Start OBS Studio_. Launch with:
-
-
-```python
-flatpak run com.obsproject.Studio
-```
-
-
-6. _Add your phone as a source_. Follow the instructions [here](https://droidcam.app/obs/usage). Be sure to set the resolution to `640x480`.
-7. _Adjust resolution settings_. In OBS Studio, go to `File > Settings > Video`. Change the `Base(Canvas) Resolution` and the `Output(Scaled) Resolution` to `640x480` by manually typing it in.
+6. _Add your phone as a source_. Follow the instructions [here](https://droidcam.app/obs/usage). Be sure to set the resolution to `640x480` to avoid the watermarks.
+7. _Adjust resolution settings_. In OBS Studio, go to `File > Settings > Video` or `OBS > Preferences... > Video`. Change the `Base(Canvas) Resolution` and the `Output(Scaled) Resolution` to `640x480` by manually typing it.
8. _Start virtual camera_. In OBS Studio, follow the instructions [here](https://obsproject.com/kb/virtual-camera-guide).
-9. _Verify the virtual camera setup_. Use `v4l2-ctl` to list the devices:
+9. _Verify the virtual camera setup and resolution_.
+ - **Linux**: Use `v4l2-ctl` to list devices and check resolution:
+ ```bash
+ v4l2-ctl --list-devices # find VirtualCam and note its /dev/videoX path
+ v4l2-ctl -d /dev/videoX --get-fmt-video # replace with your VirtualCam path
+ ```
+ You should see `VirtualCam` listed and resolution `640x480`.
+ - **macOS**: Open Photo Booth or FaceTime and select "OBS Virtual Camera" as the input.
+ - **Windows**: The native Camera app doesn't support virtual cameras. Use a video conferencing app (Zoom, Teams) or run `lerobot-find-cameras opencv` directly to verify.
-
-```python
-v4l2-ctl --list-devices
-```
-
+
+Troubleshooting
-You should see an entry like:
+> The virtual camera resolution is incorrect.
-```
-VirtualCam (platform:v4l2loopback-000):
-/dev/video1
-```
+Delete the virtual camera source and recreate it. The resolution cannot be changed after creation.
-10. _Check the camera resolution_. Use `v4l2-ctl` to ensure that the virtual camera output resolution is `640x480`. Change `/dev/video1` to the port of your virtual camera from the output of `v4l2-ctl --list-devices`.
+> Error reading frame in background thread for OpenCVCamera(X): OpenCVCamera(X) frame width=640 or height=480 do not match configured width=1920 or height=1080.
-
-```python
-v4l2-ctl -d /dev/video1 --get-fmt-video
-```
-
+This error is caused by OBS Virtual Camera advertising a `1920x1080` resolution despite rescaling. The only fix for now is to comment out the width and height check in `_postprocess_image()`.
-You should see an entry like:
-
-```
->>> Format Video Capture:
->>> Width/Height : 640/480
->>> Pixel Format : 'YUYV' (YUYV 4:2:2)
-```
-
-Troubleshooting: If the resolution is not correct you will have to delete the Virtual Camera port and try again as it cannot be changed.
-
-If everything is set up correctly, you can proceed with the rest of the tutorial.
+
+
+If everything is set up correctly, your phone will appear as a standard OpenCV camera and can be used with `OpenCVCamera`.