# 相机

LeRobot 提供了多种视频采集选项：

| 类             | 支持的相机                   |
| ----------------- | ----------------------------------- |
| `OpenCVCamera`    | 手机、内置笔记本电脑摄像头、USB 摄像头 |
| `ZMQCamera`       | 网络摄像头           |
| `RealSenseCamera` | Intel RealSense（带深度）        |
| `Reachy2Camera`   | Reachy 2 机器人摄像头              |

> [!TIP]
> 有关 `OpenCVCamera` 的兼容性详情，请参阅 [OpenCV 视频 I/O 概述](https://docs.opencv.org/4.x/d0/da7/videoio_overview.html)。

### 找到您的摄像头

每个摄像头都需要一个唯一的标识符才能被实例化，以便区分多个已连接的设备。

`OpenCVCamera` 和 `RealSenseCamera` 支持自动发现。运行以下命令以列出可用设备及其标识符。请注意，根据操作系统的不同，这些标识符可能会在重启计算机或重新插拔摄像头后发生变化。

```bash
lerobot-find-cameras opencv # or realsense for Intel Realsense cameras
```

如果连接了两个摄像头，输出将类似于以下内容：

```bash
--- Detected Cameras ---
Camera #0:
  Name: OpenCV Camera @ 0
  Type: OpenCV
  Id: 0
  Backend api: AVFOUNDATION
  Default stream profile:
    Format: 16.0
    Width: 1920
    Height: 1080
    Fps: 15.0
--------------------
(more cameras ...)
```

> [!WARNING]
> 在 `macOS` 上使用 Intel RealSense 相机时，可能会遇到此 [错误](https://github.com/realsenseai/librealsense/issues/12307)：`Error finding RealSense cameras: failed to set power state`。可以通过使用 `sudo` 权限运行相同命令来解决此问题。请注意，在 `macOS` 上使用 RealSense 相机并不稳定。

`ZMQCamera` 和 `Reachy2Camera` 不支持自动发现。必须通过提供其网络地址和端口或机器人 SDK 设置来手动配置它们。

## 相机放置与照明

相机图像是策略学习的依据，因此良好的相机设置比高分辨率更为重要：

- **均匀照亮场景。** 使用漫射且一致的光线，避免直射阳光、强烈反光和移动阴影。录制与部署之间的光照差异可能导致策略失效。
- **固定相机位置。** 请将其牢固安装，并在录制会话之间，或录制与部署之间保持不动。
- **明确任务。** 物体、夹爪和目标应在至少一个摄像头中可见。一个好的测试方法：仅通过查看摄像头图像，您能否自己完成该任务？
- **保持分辨率适中。** 640×480 分辨率、30 fps 帧率对于大多数任务已经足够，同时也能确保录制和训练的速度。

## 使用摄像头

### 帧访问模式

所有相机类都实现了三种用于捕获帧的访问模式：

| 方法                    | 行为                                                                                                                                                   | Blocks？        | 最适用于                                 |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------- |
| `read()`                  | 等待相机硬件返回一帧图像。根据相机和 SDK 的不同，可能会长时间阻塞。                                                | 是的            | 简单脚本，顺序捕获       |
| `async_read(timeout_ms)`  | 返回后台线程中最新的未消费帧。仅当缓冲区为空时阻塞，最长等待 `timeout_ms`。如果未收到帧，则引发 `TimeoutError`。 | 带超时 | 与相机帧率同步的控制循环 |
| `read_latest(max_age_ms)` | 查看缓冲区中最新的帧（可能已过期）。如果帧的年龄超过 `max_age_ms`，则引发 `TimeoutError`。                                        | No             | UI 可视化、日志记录、监控    |

### 使用示例

以下示例展示了如何使用相机 API 来配置不同类型的相机并捕获帧。

- 使用基于 OpenCV 的相机进行**阻塞和非阻塞帧捕获**
- 使用 Intel RealSense 相机进行**颜色和深度捕获**

> [!WARNING]
> 未能干净地断开相机连接可能会导致资源泄漏。请使用上下文管理器协议以确保自动清理：
>
> ```python
> with OpenCVCamera(config) as camera:
>     ...
> ```
>
> 您也可以手动调用 `connect()` 和 `disconnect()`，但请务必在后者中使用 `finally` 块。

```python
from lerobot.cameras.opencv import OpenCVCamera, OpenCVCameraConfig
from lerobot.cameras import ColorMode, Cv2Rotation

# Construct an `OpenCVCameraConfig` with your desired FPS, resolution, color mode, and rotation.
config = OpenCVCameraConfig(
    index_or_path=0,
    fps=15,
    width=1920,
    height=1080,
    color_mode=ColorMode.RGB,
    rotation=Cv2Rotation.NO_ROTATION
)

# Instantiate and connect an `OpenCVCamera`, performing a warm-up read (default).
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}")

```

```python
from lerobot.cameras.realsense import RealSenseCamera, RealSenseCameraConfig
from lerobot.cameras import ColorMode, Cv2Rotation

# Create a `RealSenseCameraConfig` specifying your camera’s serial number and enabling depth.
config = RealSenseCameraConfig(
    serial_number_or_name="233522074606",
    fps=15,
    width=640,
    height=480,
    color_mode=ColorMode.RGB,
    use_depth=True,
    # Optional fixed color controls. Omit them to leave the current sensor settings unchanged.
    exposure=120,
    gain=64,
    white_balance=4600,
    rotation=Cv2Rotation.NO_ROTATION
)

# Instantiate and connect a `RealSenseCamera` with warm-up read (default).
camera = RealSenseCamera(config)
camera.connect()

# Capture a color frame via `read()` and a depth map via `read_depth()`.
try:
    color_frame = camera.read()
    depth_map = camera.read_depth()
    print("Color frame shape:", color_frame.shape)
    print("Depth map shape:", depth_map.shape)
finally:
    camera.disconnect()
```

手动颜色控制会禁用相应的自动曝光或白平衡模式。其支持的取值范围因相机型号而异；如果传入无效值，将在连接时引发错误，错误信息中会包含传感器报告的取值范围。请求不支持的控制项同样会引发错误。省略的控制项将保持传感器现有的自动或手动设置不变。这些选项需要 `use_rgb=True`。

手动颜色控制需要专用的 RGB 模块。没有该模块的相机（例如 RealSense D405）不支持这些控制，并会在连接时引发错误。

### 处理深度

Intel RealSense 和 Reachy 2 摄像头可以同步捕获彩色和深度数据。调用 `read()` 会以 `(H, W, 3)` `uint8` 格式返回 **彩色** 帧。调用 `read_depth()` 会以 `(H, W, 1)` `uint16` 格式返回 **深度图**，其中每个像素值表示以**毫米**为单位的传感器距离。像素值为 `0` 通常表示“无可用测量值”（超出范围、被遮挡或置信度低）。

在录制过程中，控制循环通过 `read_latest()`（颜色）和 `read_latest_depth()`（深度）非阻塞地读取最新的缓冲帧，并将深度图作为同级特征添加（例如，在 `front` 旁边添加 `front_depth`）。

有关在录制数据集时深度流的存储和编码方式，请参阅视频编码指南中的[深度流](./video_encoding_parameters#depth-streams)部分。

## 使用手机摄像头

要在 macOS 上将 iPhone 用作摄像头，请启用连续互通相机功能：

- 请确保您的 Mac 运行的是 macOS 13 或更高版本，并且您的 iPhone 使用的是 iOS 16 或更高版本。
- 使用相同的 Apple ID 登录两台设备。
- 使用 USB 数据线连接您的设备，或开启 Wi-Fi 和蓝牙以建立无线连接。

有关更多详细信息，请访问 [Apple 支持](https://support.apple.com/en-gb/guide/mac-help/mchl77879b8a/mac)。

如果想在 OBS 中将手机用作摄像头，请按照以下步骤设置虚拟摄像头。

1. _（仅限 Linux）安装 `v4l2loopback-dkms` 和 `v4l-utils`_。这些包用于创建虚拟摄像头设备并验证其设置。使用以下命令安装：

```bash
sudo apt install v4l2loopback-dkms v4l-utils
```

2. _在手机上安装 [DroidCam 应用](https://droidcam.app)_。该应用支持 iOS 和 Android 系统。
3. _下载并安装 [OBS Studio](https://obsproject.com)_。
4. _下载并安装 [DroidCam OBS 插件](https://droidcam.app/obs)_。
5. _启动 OBS Studio_。

6. _将您的手机添加为源_。请按照[此处](https://droidcam.app/obs/usage)的说明操作。请务必将分辨率设置为`640x480`，以避免出现水印。
7. _调整分辨率设置_。在 OBS Studio 中，前往 `File > Settings > Video` 或 `OBS > Preferences... > Video`。手动输入将 `Base(Canvas) Resolution` 和 `Output(Scaled) Resolution` 更改为 `640x480`。
8. _启动虚拟摄像头_。在 OBS Studio 中，请按照[此处](https://obsproject.com/kb/virtual-camera-guide)的说明操作。
9. _验证虚拟摄像头的设置和分辨率_。
   - **Linux**：使用 `v4l2-ctl` 列出设备并检查分辨率：
     ```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
     ```
     您应该能看到 `VirtualCam` 已列出，分辨率为 `640x480`。
   - **macOS**：打开 Photo Booth 或 FaceTime，并选择“OBS Virtual Camera”作为输入。
   - **Windows**：原生相机应用不支持虚拟相机。请使用视频会议应用（Zoom、Teams），或直接运行 `lerobot-find-cameras opencv` 进行验证。

故障排除

> 虚拟相机分辨率不正确。

删除虚拟相机源并重新创建它。创建后无法更改分辨率。

> 在后台线程中读取 OpenCVCamera(X) 帧时出错：OpenCVCamera(X) 的帧宽度=640 或高度=480 与配置的宽度=1920 或高度=1080 不匹配。

此错误是由 OBS 虚拟摄像头在重新缩放后仍报告 `1920x1080` 分辨率引起的。目前唯一的解决方法是注释掉 `_postprocess_image()` 中的宽度和高度检查。

如果一切设置正确，您的手机将显示为标准 OpenCV 摄像头，并可与 `OpenCVCamera` 一起使用。

