# 비디오 인코딩 매개변수

비디오 저장이 활성화되면 LeRobot은 타임스텝마다 하나의 이미지 파일을 저장하는 대신 각 카메라 스트림을 **MP4** 파일로 저장합니다. 비디오 인코딩은 시간 축을 따라 압축을 수행하므로, PNG 파일 묶음에 비해 데이터셋 크기와 I/O를 줄이는 경우가 많습니다. 모든 플레이어와 로더가 이해하는 MP4 형식을 유지하면서요.

MP4로 프레임을 인코딩하는 것은 완전한 FFmpeg 파이프라인입니다. 인코더 선택, 픽셀 포맷, GOP/키프레임, 품질 대 속도 트레이드오프, 선택적 추가 인코더 플래그 등이 포함됩니다. 이러한 설정의 대부분은 PyAV를 통해 전달되는 `rgb_encoder`의 중첩된 `RGBEncoderConfig` (`lerobot.configs.video.RGBEncoderConfig`)를 통해 사용자가 조정할 수 있습니다.

이 매개변수는 `--dataset.rgb_encoder.<field>`를 사용하여 CLI에서 설정할 수 있습니다(예: `lerobot-record` 또는 `lerobot-rollout` 사용). 동일한 블록은 해당 실행의 모든 카메라 비디오 스트림에 적용됩니다.

> [!TIP]
> `rgb_encoder`가 효과를 내려면 비디오 저장이 켜져 있어야 합니다 — Python API에서는 `use_videos=True`, CLI에서는 `--dataset.video=true`(녹화 기본값)를 사용하세요. 비디오가 꺼져 있으면 입력은 이미지로 유지되고 `rgb_encoder`는 무시됩니다.

**언제** 프레임이 기록되는지 인코딩되는지(스트리밍 vs. 에피소드 후)에 대한 세부 사항, 큐, 그리고 기타 최상위 `--dataset.*` 스위치는 [Streaming Video Encoding](./streaming_video_encoding)을 참고하세요. 인코딩 파라미터 비교 및 실험은 [video-benchmark Space](https://huggingface.co/spaces/lerobot/video-benchmark)를 참고하세요.

---

## 예시

```bash
lerobot-record \
    --robot.type=so100_follower \
    --robot.port=/dev/tty.usbmodem58760431541 \
    --robot.cameras="{laptop: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --robot.id=black \
    --teleop.type=so100_leader \
    --teleop.port=/dev/tty.usbmodem58760431551 \
    --teleop.id=blue \
    --dataset.repo_id=<my_username>/<my_dataset_name> \
    --dataset.num_episodes=2 \
    --dataset.single_task="Grab the cube" \
    --dataset.streaming_encoding=true \
    --dataset.encoder_threads=2 \
    --dataset.rgb_encoder.vcodec=h264 \
    --dataset.rgb_encoder.preset=fast \
    --dataset.rgb_encoder.extra_options='{"tune": "film", "profile:v": "high", "bf": 2}' \
    --display_data=true
```

---

## 파라미터 조정

> [!WARNING]
> 기본값은 일반적인 로봇공학 데이터셋에 대해 **압축률**, **시각적 품질**, **디코딩/시크 속도**를 균형 있게 맞추도록 조정되어 있습니다. 이를 변경하면 녹화(CPU 부하, 프레임 드롭)와 학습(디코딩 처리량, 이미지 품질) 모두에 영향을 미칠 수 있습니다.
>
> 이 매개변수들은 특정 이유가 있을 때만 재정의하고, 새로운 설정에 의존하기 전에 파이프라인에 미치는 영향을 측정하세요.

아래의 모든 플래그는 CLI에서 `--dataset.rgb_encoder.` 접두사가 붙습니다.

| 매개변수       | 유형             | 기본값       | 설명                                                                                                                                                                                                             |
| --------------- | ---------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vcodec`        | `str`            | `"libsvtav1"` | 비디오 코덱 이름. `"auto"`는 고정된 우선순위 목록에서 첫 번째 사용 가능한 하드웨어 인코더를 선택하며, 사용할 수 없으면 `libsvtav1`로 대체됩니다.                                                                                        |
| `pix_fmt`       | `str`            | `"yuv420p"`   | 출력 픽셀 형식. FFmpeg 빌드에서 선택한 코덱이 지원해야 합니다.                                                                                                                                        |
| `g`             | `int`            | `2`           | GOP 크기 — `g`프레임마다 하나의 keyframe을 생성합니다. FFmpeg 옵션 `g`으로 출력됩니다.                                                                                                                                                   |
| `crf`           | `int` 또는 `float` | `30`          | 코덱별로 매핑된 추상적 품질 값(자세한 내용은 [매핑](https://github.com/huggingface/lerobot/blob/main/src/lerobot/configs/video.py#L197) 참조). 매핑이 단조일 경우, 값이 낮을수록 품질이 높고 출력 크기가 커집니다. |
| `preset`        | `int` 또는 `str`   | `12` \*       | 인코더 속도 프리셋; 코덱에 따라 의미가 다릅니다. \* 설정되지 않았고 `vcodec=libsvtav1`인 경우, LeRobot은 기본적으로 `12`를 사용합니다.                                                                                                |
| `fast_decode`   | `int`            | `0`           | `libsvtav1`: `0–2`, `svtav1-params`를 통해 전달됩니다. `h264` / `hevc` (소프트웨어): `>0`인 경우 `tune=fastdecode`를 설정합니다. 기타 코덱: 일반적으로 사용되지 않습니다.                                                                    |
| `video_backend` | `str`            | `"pyav"`      | 현재 비디오 인코딩에는 `"pyav"`만 구현되어 있습니다.                                                                                                                                                              |
| `extra_options` | `dict`           | `{}`          | Extra FFmpeg or codec specific options merged after the structured fields above. Cannot override keys already set by those fields.                                                                                      |

---

## 깊이 스트림

Depth map(Intel RealSense, Reachy 2)은 RGB 스트림과 함께 **독립적인 비디오 스트림**으로 저장됩니다. 원시 depth(`uint16` 밀리미터 또는 `float32` 미터)는 8-bit 코덱을 통과할 수 없으므로, LeRobot은 각 map을 12-bit 코드(`[0, 4095]`)로 **양자화**합니다. 기본적으로 depth 센서의 `1/depth` 오차 프로파일에 맞추기 위해 로그 스케일로 양자화한 후, 이를 고 비트 깊이 픽셀 포맷(`gray12le`)에 패킹하여 12-bit 코덱으로 인코딩합니다.

  
    
      원시 깊이
      
        uint16 mm
        
        float32 m
      
    
    
      →
    
    
      
        기록 시간
      
      
        Clip
        
          to [depth_min,
          
          depth_max]
        
      
      
        →
      
      
        양자화
        
          12비트 코드 0–4095
          
          log (default) 또는 linear
        
      
      
        →
      
      
        패키지
        
          gray12le로
          
          plane
        
      
      
        →
      
      
        인코딩
        
          HEVC
          
          메인 12
        
      
    
    
      →
    
    
      MP4
      
        저장된
        
        스트림
      
    
    
      →
    
    
      
        로드 시간
      
      
        Dequantize
        
          mm / m
        
      
    
  

`--dataset.depth_encoder.<field>`를 통해 설정되는 4개의 양자화 옵션을 추가한 `DepthEncoderConfig`를 사용하여 병렬 **`depth_encoder`** 블록으로 깊이 파이프라인을 구성합니다. 이 블록은 `RGBEncoderConfig`의 모든 필드(`vcodec`, `pix_fmt`, `crf`, …)를 공유합니다.

```bash
lerobot-record \
    ... \
    --dataset.depth_encoder.vcodec=hevc \
    --dataset.depth_encoder.depth_min=0.05 \
    --dataset.depth_encoder.depth_max=5.0 \
    --dataset.depth_encoder.use_log=true
```

| 매개변수       | 유형    | 기본값                         | 설명                                                                                                                            |
| --------------- | ------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `vcodec`        | `str`   | `"hevc"`                        | HEVC Main 12(12비트 지원 코덱, MP4 호환).                                                                                 |
| `extra_options` | `dict`  | `{"x265-params": "lossless=1"}` | **Depth 기본값은 lossless**(정확한 round-trip)이며, `crf`는 무시됩니다. 더 작은 lossy 스트림을 얻으려면 `extra_options={}`를 전달하고 `crf`를 설정하세요. |
| `pix_fmt`       | `str`   | `"gray12le"`                    | 양자화 코드를 전달하는 데 사용되는 단일 채널 12비트 픽셀 형식입니다.                                                                  |
| `depth_min`     | `float` | `0.01`                          | 미터 단위의 깊이를 양자화 `0`에 매핑합니다. 해당 값은 디코딩 시 클리핑됩니다.                                                             |
| `depth_max`     | `float` | `10.0`                          | 미터 단위의 깊이를 양자화 `4095`에 매핑합니다. 해당 값 이상은 디코딩 시 클리핑됩니다.                                                          |
| `shift`         | `float` | `3.5`                           | 0 근처에서 수치적 안정성을 위해 대수적 양자화에 사용되는 사전 로그 오프셋(미터). `depth_min + shift > 0`을 만족해야 합니다.      |
| `use_log`       | `bool`  | `True`                          | `true`인 경우, 로그 공간에서 양자화합니다(일반적인 깊이 센서에 권장됨). 균일/선형 양자화를 사용하려면 `false`로 설정하세요.              |

> [!TIP]
> `depth_min`, `depth_max`, `shift`는 입력深度的 단위와 관계없이 항상 **미터**로 해석됩니다. 입력은 자동 감지됩니다: 정수 배열(예: RealSense에서 바로 가져온 `uint16` 밀리미터)은 밀리미터로, 부동소수점 배열은 미터로 처리됩니다. `depth_min` / `depth_max`를 센서의 실제 작동 범위를 포괄하도록 선택하세요. 해당 범위를 벗어난 값은 포화 상태로 처리되어 경계 부분의 디테일이 손실될 수 있습니다.

Depth 기능은 `meta/info.json`에서 `"is_depth_map": true`로 표시되며, 해당 quantizer 설정(`video.depth_min`, `video.depth_max`, `video.shift`, `video.use_log`)이 영구적으로 저장됩니다. 이를 통해 로드 시 depth를 **물리적 단위로 역양자화**할 수 있습니다.

### 로드 시 출력 단위

`depth_encoder`는 **기록 시점**의 문제입니다. 학습 중과 같은 _로드_ 시점에 depth map이 역양자화되는 단위는 읽기 시점 플래그 `--dataset.depth_output_unit`로 별도로 설정됩니다:

```bash
lerobot-train \
    --dataset.repo_id=<my_username>/<my_dataset_name> \
    --dataset.depth_output_unit=m \
    --policy.type=act
```

| 매개변수           | 유형  | 기본값 | 설명                                                                                  |
| ------------------- | ----- | ------- | -------------------------------------------------------------------------------------------- |
| `depth_output_unit` | `str` | `"mm"`  | 물리적 단위 깊이 맵은 로드 시 `"mm"`(밀리미터) 또는 `"m"`(미터)로 역양자화됩니다. |

> [!TIP]
> 이것은 순수하게 디코딩 시의 표시 선택 사항이며, 저장된 비디오나 메타데이터를 **변경하지** 않으므로 동일한 데이터셋을 `mm` 또는 `m`으로 다시 인코딩 없이 읽을 수 있습니다. 깊이 카메라가 없는 데이터셋에는 영향을 미치지 않습니다.

---

## 데이터셋 메타데이터의 영속성

비디오 스트림의 첫 에피소드가 인코딩된 후, 인코더 구성은 각 비디오 기능 아래에 파일 자체에서 탐지된 값과 함께 **데이터셋 메타데이터**(`meta/info.json`)에 저장됩니다. 비디오 기능 `observation.images.<camera>`의 경우, `info.json`의 레이아웃은 다음과 같습니다:

```json
{
  "features": {
    "observation.images.laptop": {
      "dtype": "video",
      "shape": [480, 640, 3],
      "info": {
        "video.height": 480,
        "video.width": 640,
        "video.codec": "h264",
        "video.pix_fmt": "yuv420p",
        "video.fps": 30,
        "video.channels": 3,
        "is_depth_map": false,
        "video.g": 2,
        "video.crf": 30,
        "video.preset": "fast",
        "video.fast_decode": 0,
        "video.video_backend": "pyav",
        "video.extra_options": { "tune": "film", "profile:v": "high", "bf": 2 }
      }
    }
  }
}
```

`info` 블록에는 두 가지 출처가 기여합니다:

| 소스              | 출처                                   | 필드                                                                                                                  |
| ------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **스트림에서 파생된**  | PyAV를 사용하여 인코딩된 MP4에서 다시 읽습니다.             | `video.height`, `video.width`, `video.codec`, `video.pix_fmt`, `video.fps`, `video.channels`, `is_depth_map`, `audio.*` |
| **인코더 기반** | `RGBEncoderConfig` / `DepthEncoderConfig`에서 가져왔습니다. | `video.g`, `video.crf`, `video.preset`, `video.fast_decode`, `video.video_backend`, `video.extra_options`               |

> [!IMPORTANT]
> 이 블록은 **첫 번째** 에피소드에서 **한 번만** 채워집니다. 데이터셋의 모든 에피소드가 동일한 `rgb_encoder`로 인코딩되었다고 가정합니다. 녹화 중간에 인코더 설정을 변경하는 것은 지원되지 않으며, `info.json`에는 첫 번째 에피소드에 사용된 매개변수만 반영됩니다.

---

## 데이터셋 병합

`merge_datasets`로 데이터셋을 집계할 때, 비디오 파일은 재인코딩 없이 그대로 연결되고, `info.json`의 인코더 필드는 키별로 병합됩니다:

| 병합 규칙         | 필드                                                                             | 동작                                                                                                                                                 |
| ------------------ | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **일치해야 함**     | `video.codec`, `video.pix_fmt`, `video.height`, `video.width`, `video.fps`         | Stream에서 파생된 필드는 소스 간에 일치해야 하며, 그렇지 않으면 FFmpeg의 concat demuxer가 실패합니다.                                                                 |
| **느슨하게 병합됨** | `video.g`, `video.crf`, `video.preset`, `video.fast_decode`, `video.extra_options` | Encoder-tuning 필드. 모든 소스가 일치하면 값이 유지되고, 일치하지 않으면 `null`(`video.extra_options`의 경우 `{}`)로 설정되고 경고가 기록됩니다. |

