# 2026-08-16 코드 변경 — ROI 측광 / 자동 게인 / 우선순위 / 마스터-슬레이브 동기화

> `hik_camera_node.cpp`에 실제 기능을 추가했다. 아래는 무엇을, 왜, 어떻게 바꿨는지와 사용법.
> 관련 배경: `2026-08-15_test1_notes.md`, `camera_exposure_design_notes.md` 4절/5.4절.

---

## 1. 요청 사항 4가지와 대응

| 요청 | 결론 |
|---|---|
| 자동 게인 도입 | `gain_auto` 파라미터 신규 추가 (기존엔 `GainAuto`가 항상 강제 Off였음) |
| AE 측광 ROI를 화면 중간~맨 아래로 제한 | **가능. 단, 카메라 온보드 기능이 아니라 소프트웨어로 구현** (아래 2절 이유 참조) |
| 노출/게인 자동 조절 우선순위 지정 | `use_software_ae` 루프에서 명시적으로 구현 (노출 먼저, 게인은 최후 수단) |
| 가운데 카메라가 나머지 노출/게인을 결정 | `sync_role`(master/slave) + 토픽 2개로 구현 |

---

## 2. 왜 ROI 측광이 카메라 온보드로는 안 되는가

`hikSDK/include/MvCameraControl.h`, `CameraParams.h`를 다시 검색했다. AE 전용 측광 ROI(흔히 "AutoFunctionAOI"류)에 해당하는 노드/함수는 없고, 있는 건:

```
MV_CC_GetAOIoffsetX/Y, MV_CC_SetAOIoffsetX/Y   ← 캡처 자체를 크롭하는 AOI (측광 전용 아님)
GainAuto 관련 전용 C 함수는 아예 없음 (ExposureAuto와 동일하게 문자열 노드로만 설정 가능)
```

즉 이미지를 자르지 않고 "측광 계산에만" 특정 영역을 쓰는 카메라 자체 기능이 없다. 그래서 온보드 `ExposureAuto`/`GainAuto`를 끄고, 소프트웨어에서 직접 퍼센타일을 계산해 `ExposureTime`/`Gain`을 쓰는 방식(`use_software_ae`)으로 구현했다.

---

## 3. 새 파라미터

### 게인 (기존 `gain`은 유지, 아래 신규)

| 파라미터 | 기본값 | 의미 |
|---|---|---|
| `gain_auto` | `false` | true면 게인 자동 조절 (온보드 Continuous 또는 소프트웨어 루프) |
| `gain_auto_max_db` | `12.0` | 자동 게인 상한. 하드웨어 실측 상한(~16.9dB, `answer_1.md` 2절)보다 낮게 잡아 노이즈 제한 |

### 소프트웨어 AE/AG (ROI 측광 + 우선순위)

| 파라미터 | 기본값 | 의미 |
|---|---|---|
| `use_software_ae` | `false` | true면 온보드 auto 대신 아래 방식으로 직접 계산 |
| `ae_roi_top_ratio` | `0.0` | 측광에서 제외할 상단 비율. `0.5` = 하단 절반만 측광(하늘 제외) |
| `ae_target_percentile` / `ae_target_dn` | `70.0` / `130` | ROI 내 목표 퍼센타일이 이 DN이 되도록 조절 (설계노트 4.2절과 동일 사상) |
| `ae_saturation_percentile` / `ae_saturation_dn` | `98.0` / `245` | 이 퍼센타일이 이 DN을 넘지 않도록 하는 하드 제약 — 목표 미달이어도 이게 우선 |
| `ae_step_gain` | `0.5` | 프레임당 보정 비율(댐핑). 크면 빨리 수렴하되 진동 위험 |

**우선순위 로직** (`runSoftwareAeStep()`): 밝게 해야 할 때는 **노출을 `exposure_auto_max`까지 먼저** 늘리고, 그래도 모자라면 게인을 `gain_auto_max_db`까지 씀. 어둡게 해야 할 때는 반대로 **게인을 먼저** 줄이고, 그래도 남으면 노출을 줄임. 게인은 노이즈 비용이 있으므로 항상 마지막 수단.

### 마스터-슬레이브 동기화

| 파라미터 | 기본값 | 의미 |
|---|---|---|
| `sync_role` | `"independent"` | `independent`(기존 동작) / `master` / `slave` |
| `sync_master_camera_ns` | `""` | `sync_role=slave`일 때 구독할 마스터의 `camera_name` (예: `"cam2"`) |

동작 방식: 모든 카메라가 `<camera_name>/ae_exposure_time_us`, `<camera_name>/ae_gain_db` 토픽을 항상 발행한다(관찰용, role 무관). `master`는 이 값을 SDK가 매 프레임 주는 **실제 적용값**(`MV_FRAME_OUT_INFO_EX::fExposureTime`/`fGain` — 이미 `answer_1.md` 3절에서 "이미 있는데 안 쓰고 있다"고 지적했던 그 필드)으로 채워 발행하므로, 온보드 auto든 소프트웨어 AE든 수동 고정이든 어떤 모드든 그대로 방송된다. `slave`는 자기 auto 로직을 전부 끄고, 구독한 마스터 값을 그대로 `ExposureTime`/`Gain`에 적용한다.

**사용 예 (cam2가 마스터, cam1/cam3가 슬레이브):**
```yaml
# camera_params_cam2.yaml
sync_role: "master"

# camera_params_cam1.yaml, camera_params_cam3.yaml
sync_role: "slave"
sync_master_camera_ns: "cam2"
```

---

## 4. 알아두어야 할 제약

1. **`AutoGainLowerLimit`/`AutoGainUpperLimit` 노드명은 검증되지 않음.** `AutoExposureTimeLowerLimit`/`UpperLimit`과 같은 명명 규칙일 거라 가정하고 그대로 썼다 (`applyGainMode()`). 실패하면 `RCLCPP_WARN`으로 바로 로그에 뜨니, 뜨면 MVS Feature Tree에서 실제 노드명을 확인해서 고쳐야 한다. (`gain_auto`+`use_software_ae=false` 조합, 즉 온보드 auto gain을 쓸 때만 해당 — `use_software_ae=true`면 이 노드를 안 쓰므로 무관.)
2. **기본값은 전부 기존 동작을 그대로 유지한다.** `gain_auto:false`, `use_software_ae:false`, `sync_role:"independent"` — 기존 3대 yaml(`camera_params_cam1/2/3.yaml`)은 코드 수정 후에도 아무 변경 없이 그대로 동작한다. 새 기능을 쓰려면 yaml에 위 파라미터를 명시적으로 추가해야 한다 (이번에 yaml 파일 자체는 건드리지 않았다 — 실제 배포 설정은 테스트해보고 정할 문제라 판단).
3. **소프트웨어 AE 루프는 매 프레임 이미지에서 4픽셀 간격으로 서브샘플링해 밝기 퍼센타일을 계산**한다 (`runSoftwareAeStep()`). 1440×1080 기준 약 9.7만 샘플, 정렬 비용은 10Hz에서 무시할 만한 수준.
4. **동기화는 ROS 파라미터/토픽 레이어를 거치므로 완전한 프레임 단위 lockstep은 아니다.** 마스터가 결정한 값이 슬레이브에 반영되기까지 최소 한 토픽 왕복(수 ms~한 프레임 이내, 10Hz 트리거 기준) 지연이 있다. 카메라 3대가 완전히 다른 방향(예: 하나는 역광, 하나는 그늘)을 보고 있으면 슬레이브가 부적절하게 노출될 수 있음 — 실측으로 확인 필요 (설계노트 5.4절의 Master-Slave 단점과 동일한 근본 제약).

---

## 5. 별도로 코드 변경이 필요 없던 것 — 화이트밸런스

같은 대화에서 논의된 화이트밸런스 "끄기"는 코드 변경 없이 이미 가능하다: `balance_white_auto:false` + `balance_ratio_red/green/blue: 1024/1024/1024`(중립/1x 게인)로 설정하면 됨. cam2-cam3 겹침 매칭은 아직 실측/적용 전 — cam1-cam2와 같은 방식으로 겹침 장면에서 Continuous AWB 수렴값을 읽어(`captureLoop()`가 3초마다 로그로 이미 출력) 3대 평균을 내는 절차가 남아 있음 (코드 변경 사항 아님, 데이터 수집 후 yaml 값만 갱신하면 됨).

---

## 6. 빌드 검증에 대한 한계

이 변경은 개발 PC(macOS, ROS 2/colcon 미설치)에서 작성되어 **실제 `colcon build`로 컴파일 검증을 하지 못했다.** 코드를 전체 재검토해 문법·타입·초기화 순서 오류를 잡았지만(초기 구현에서 `soft_ae_exposure_us_`/`soft_ae_gain_db_` 초기화 순서 버그 1건 발견해 수정함), 로봇 PC에서 `colcon build --symlink-install` 후 다음을 반드시 확인할 것:
- 빌드 자체가 통과하는지 (특히 `AutoGainLowerLimit`/`AutoGainUpperLimit` 같은 문자열 노드는 컴파일 타임에 검증 안 됨 — 런타임 WARN 로그로만 확인 가능)
- `use_software_ae:true`로 켰을 때 `ros2 topic echo /cam2/ae_exposure_time_us` 등으로 실제 값이 그럴듯하게 움직이는지
- `sync_role` master/slave 조합에서 슬레이브가 마스터 값을 잘 따라가는지
