# Camera

LeRobot cung cấp nhiều tùy chọn cho việc thu video:

| Lớp             | Camera được hỗ trợ                   |
| ----------------- | ----------------------------------- |
| `OpenCVCamera`    | Điện thoại, laptop tích hợp sẵn, webcam USB |
| `ZMQCamera`       | Camera kết nối mạng           |
| `RealSenseCamera` | Intel RealSense (có chiều sâu)        |
| `Reachy2Camera`   | Camera của robot Reachy 2              |

> [!TIP]
> Để biết chi tiết về tính tương thích với `OpenCVCamera`, hãy xem [Tổng quan về I/O video với OpenCV](https://docs.opencv.org/4.x/d0/da7/videoio_overview.html).

### Tìm camera của bạn

Mỗi camera cần một định danh duy nhất để được khởi tạo, cho phép bạn phân biệt giữa nhiều thiết bị đã kết nối.

`OpenCVCamera` và `RealSenseCamera` hỗ trợ tự động phát hiện thiết bị. Chạy lệnh bên dưới để liệt kê các thiết bị khả dụng cùng định danh của chúng. Lưu ý rằng các định danh này có thể thay đổi sau khi khởi động lại máy tính hoặc cắm lại camera, tùy thuộc vào hệ điều hành của bạn.

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

Đầu ra sẽ trông giống như sau nếu bạn kết nối hai camera:

```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]
> Khi sử dụng camera Intel RealSense trên `macOS`, bạn có thể gặp [lỗi](https://github.com/realsenseai/librealsense/issues/12307) này: `Error finding RealSense cameras: failed to set power state`. Bạn có thể khắc phục bằng cách chạy cùng lệnh đó với quyền `sudo`. Lưu ý rằng việc sử dụng camera RealSense trên `macOS` không ổn định.

`ZMQCamera` và `Reachy2Camera` không hỗ trợ tự động phát hiện. Bạn phải cấu hình thủ công bằng cách cung cấp địa chỉ mạng và cổng hoặc cài đặt SDK của robot.

## Vị trí đặt camera và ánh sáng

Hình ảnh từ camera là thứ mà chính sách của bạn học từ đó, vì vậy một bố trí camera tốt quan trọng hơn độ phân giải cao:

- **Chiếu sáng đều cho toàn cảnh.** Sử dụng ánh sáng khuếch tán, đồng nhất và tránh ánh nắng trực tiếp, phản xạ mạnh và bóng đổ di chuyển. Sự khác biệt về ánh sáng giữa lúc ghi hình và triển khai có thể khiến chính sách thất bại.
- **Cố định các camera tại chỗ.** Gắn chúng thật chắc chắn và không di chuyển giữa các phiên ghi hình, hoặc giữa lúc ghi hình và triển khai.
- **Định nghĩa bài toán.** Đối tượng, gắp và đích đến phải hiển thị được trên ít nhất một camera. Một bài kiểm tra tốt: bạn có thể tự thực hiện bài toán chỉ bằng cách nhìn vào hình ảnh từ camera không?
- **Giữ độ phân giải ở mức vừa phải.** 640×480 ở 30 fps là đủ cho hầu hết các tác vụ và giúp quá trình ghi hình cũng như huấn luyện diễn ra nhanh hơn.

## Sử dụng camera

### Chế độ truy cập khung

Tất cả các lớp camera đều triển khai ba chế độ truy cập để thu thập khung hình:

| Phương pháp                    | Hành vi                                                                                                                                                   | Các khối?        | Phù hợp nhất cho                                 |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------- |
| `read()`                  | Chờ phần cứng camera trả về một khung hình. Có thể bị chặn trong thời gian dài tùy thuộc vào camera và SDK.                                                | Có            | Các script đơn giản, bắt đầu tuần tự       |
| `async_read(timeout_ms)`  | Trả về khung hình chưa được tiêu thụ gần nhất từ luồng nền. Chỉ bị chặn nếu bộ đệm trống, tối đa `timeout_ms`. Ném `TimeoutError` nếu không có khung hình nào đến. | Với một thời gian chờ | Vòng lặp điều khiển đồng bộ với FPS của camera |
| `read_latest(max_age_ms)` | Xem khung gần nhất trong bộ đệm (có thể đã cũ). Gây ra `TimeoutError` nếu khung cũ hơn `max_age_ms`.                                        | Không             | Trực quan hóa giao diện người dùng, ghi log, giám sát    |

### Ví dụ sử dụng

Các ví dụ sau cho thấy cách sử dụng API camera để cấu hình và chụp khung hình từ các loại camera khác nhau.

- **Chụp khung hình đồng bộ và không đồng bộ** bằng camera dựa trên OpenCV
- **Chụp màu và độ sâu** bằng camera Intel RealSense

> [!WARNING]
> Việc ngắt kết nối camera không sạch sẽ có thể gây rò rỉ tài nguyên. Hãy sử dụng giao thức trình quản lý ngữ cảnh để đảm bảo dọn dẹp tự động:
>
> ```python
> with OpenCVCamera(config) as camera:
>     ...
> ```
>
> Bạn cũng có thể gọi `connect()` và `disconnect()` thủ công, nhưng luôn luôn sử dụng khối `finally` cho lệnh sau.

```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()
```

Các điều khiển màu thủ công sẽ vô hiệu hóa chế độ phơi sáng hoặc cân bằng trắng tự động tương ứng. Phạm vi hỗ trợ của chúng thay đổi theo model camera; một giá trị không hợp lệ sẽ gây ra lỗi tại thời điểm kết nối, bao gồm phạm vi được báo cáo bởi cảm biến. Yêu cầu một điều khiển không được hỗ trợ cũng sẽ gây ra lỗi. Các điều khiển bị bỏ qua sẽ giữ nguyên cài đặt tự động hoặc thủ công hiện có của cảm biến. Các tùy chọn này yêu cầu `use_rgb=True`.

Kiểm soát màu thủ công yêu cầu một module RGB chuyên dụng. Các camera không có module này, chẳng hạn như RealSense D405, không hỗ trợ tính năng này và sẽ gây ra lỗi khi kết nối.

### Làm việc với độ sâu

Camera Intel RealSense và Reachy 2 có thể ghi nhận cả màu sắc và độ sâu đồng bộ. Gọi `read()` sẽ trả về khung **màu sắc** dưới dạng `(H, W, 3)` `uint8`. Gọi `read_depth()` sẽ trả về **bản đồ độ sâu** dưới dạng `(H, W, 1)` `uint16`, trong đó giá trị của mỗi pixel là khoảng cách từ cảm biến được biểu diễn bằng **milimét**. Giá trị pixel là `0` thường có nghĩa là “không có phép đo khả dụng” (ngoài phạm vi, bị che khuất, hoặc độ tin cậy thấp).

Trong quá trình ghi hình, vòng lặp điều khiển đọc các khung hình mới nhất trong bộ đệm một cách không chặn thông qua `read_latest()` (màu) và `read_latest_depth()` (độ sâu), thêm bản đồ độ sâu như một đặc trưng anh em (ví dụ `front_depth` bên cạnh `front`).

Để biết cách lưu trữ và mã hóa các luồng độ sâu khi ghi lại một bộ dữ liệu, hãy xem phần [Depth streams](./video_encoding_parameters#depth-streams) trong hướng dẫn mã hóa video.

## Sử dụng camera trên điện thoại của bạn

Để sử dụng iPhone của bạn như một camera trên macOS, hãy bật tính năng Continuity Camera:

- Đảm bảo Mac của bạn đang chạy macOS 13 trở lên và iPhone của bạn đang chạy iOS 16 trở lên.
- Đăng nhập vào cả hai thiết bị bằng cùng một Apple ID.
- Kết nối thiết bị của bạn bằng cáp USB hoặc bật Wi-Fi và Bluetooth để kết nối không dây.

Để biết thêm chi tiết, hãy truy cập [Apple support](https://support.apple.com/en-gb/guide/mac-help/mchl77879b8a/mac).

Nếu bạn muốn sử dụng điện thoại của mình như một camera trong OBS, hãy làm theo các bước sau để thiết lập camera ảo.

1. _(Chỉ trên Linux) Cài đặt `v4l2loopback-dkms` và `v4l-utils`_. Các gói này tạo ra các thiết bị camera ảo và kiểm tra cài đặt của chúng. Cài đặt bằng cách:

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

2. _Cài đặt [ứng dụng DroidCam](https://droidcam.app) trên điện thoại của bạn_. Ứng dụng này có sẵn trên cả iOS và Android.
3. _Tải xuống và cài đặt [OBS Studio](https://obsproject.com)_.
4. _Tải xuống và cài đặt [plugin DroidCam OBS](https://droidcam.app/obs)_.
5. _Mở OBS Studio_.

6. _Thêm điện thoại của bạn làm nguồn_. Làm theo các hướng dẫn [tại đây](https://droidcam.app/obs/usage). Hãy chắc chắn đặt độ phân giải thành `640x480` để tránh các watermark.
7. _Điều chỉnh cài đặt độ phân giải_. Trong OBS Studio, chuyển đến `File > Settings > Video` hoặc `OBS > Preferences... > Video`. Thay đổi `Base(Canvas) Resolution` và `Output(Scaled) Resolution` thành `640x480` bằng cách nhập thủ công.
8. _Bắt đầu camera ảo_. Trong OBS Studio, làm theo các hướng dẫn [tại đây](https://obsproject.com/kb/virtual-camera-guide).
9. _Kiểm tra cấu hình và độ phân giải của camera ảo_.
   - **Linux**: Sử dụng `v4l2-ctl` để liệt kê các thiết bị và kiểm tra độ phân giải:
     ```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
     ```
     Bạn nên thấy `VirtualCam` được liệt kê và độ phân giải `640x480`.
   - **macOS**: Mở Photo Booth hoặc FaceTime và chọn "OBS Virtual Camera" làm đầu vào.
   - **Windows**: Ứng dụng Camera gốc không hỗ trợ camera ảo. Hãy sử dụng ứng dụng hội nghị video (Zoom, Teams) hoặc chạy trực tiếp `lerobot-find-cameras opencv` để kiểm tra.

Xử lý sự cố

> Độ phân giải của camera ảo không chính xác.

Xóa nguồn camera ảo và tạo lại nó. Độ phân giải không thể thay đổi sau khi tạo.

> Lỗi đọc khung hình trong luồng nền cho OpenCVCamera(X): chiều rộng=640 hoặc chiều cao=480 của khung hình OpenCVCamera(X) không khớp với chiều rộng=1920 hoặc chiều cao=1080 đã cấu hình.

Lỗi này do OBS Virtual Camera thông báo độ phân giải `1920x1080` bất chấp việc đã thay đổi kích thước. Cách khắc phục duy nhất hiện tại là chú thích (comment out) phần kiểm tra chiều rộng và chiều cao trong `_postprocess_image()`.

Nếu mọi thứ được thiết lập đúng, điện thoại của bạn sẽ xuất hiện như một camera OpenCV tiêu chuẩn và có thể được sử dụng với `OpenCVCamera`.

