Add camera exposure design notes and Q&A grounded in actual hardware specs
Design notes for multi-camera + LiDAR SLAM exposure tuning, plus a question/answer pair that verifies the notes' assumptions (gain ceiling, lens sweet spot, blur budget, 12-bit raw capture) against the actual MV-CS016-10UC camera and VM0420MP5 lens datasheets and the driver source. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,773 @@
|
||||
# 멀티카메라 + LiDAR SLAM 노출 설계 노트
|
||||
|
||||
> 대상 시스템: 하드웨어 트리거 동기 카메라 3대 + LiDAR, 후처리(offline) SLAM
|
||||
> 목적: 실내/실외 혼재 환경에서 포화(하얗게 뜸) 없이 SLAM 프론트엔드가 쓸 만한 이미지를 확보
|
||||
|
||||
---
|
||||
|
||||
## 0. 요약 (먼저 읽을 것)
|
||||
|
||||
| 항목 | 현재 | 권장 |
|
||||
|---|---|---|
|
||||
| 조리개 | f/8 ~ f/11 | **f/4 (또는 f/2.8)** — 실장착 렌즈 `VM0420MP5` 최대개방 F2.0 기준 1~2 stop 닫은 값, 회절 한계(f/5.1) 이내 |
|
||||
| 노출 하한 | 100 µs | **15 µs** (`MV-CS016-10UC` 데이터시트 Standard exposure mode 하한 — 개체별 실측 권장) |
|
||||
| 노출 상한 | 5000 µs | **블러 계산으로 결정 (우리 렌즈 4mm 기준 실측하면 0.6~1.3 ms, "보통 2~3 ms"는 광각 렌즈 가정 — 3.1절 참조)** |
|
||||
| 게인 상한 | 16 dB | **16.9 dB가 하드웨어 실측 상한(더 못 엶)**, 실사용 목표는 12 dB |
|
||||
| 측광 | 전체 평균 밝기 | **ROI + 퍼센타일**, 이후 gradient 기반 |
|
||||
| 타임스탬프 | 트리거 시각 | **트리거 + 지연 + 노출시간/2** |
|
||||
| 저장 | 8-bit | **12-bit raw + 프레임별 메타데이터** |
|
||||
|
||||
핵심 진단 세 가지:
|
||||
|
||||
1. **f/8~f/11은 회절 한계를 2~3배 넘어섰습니다.** 빛도 잃고 해상력도 잃는 이중 손해입니다.
|
||||
2. **조리개를 "가장 밝을 때" 기준으로 잡으면 실내를 물리적으로 커버할 수 없습니다.** 뒤의 EV 계산으로 확인 가능합니다.
|
||||
3. **게인 실측 상한은 24 dB가 아니라 16.9 dB입니다** (카메라 데이터시트 및 `camera_params_cam1/2/3.yaml` 실측 주석으로 확인, `MV-CS016-10UC` 스펙상 0~17 dB). 아래 1.4절/3.2절/7.2절의 "24 dB" 시나리오는 이 카메라에서 재현 불가능하며, 16.9 dB 기준으로 재계산했습니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. Stop(스톱) 계산법
|
||||
|
||||
### 1.1 정의
|
||||
|
||||
**1 stop = 센서에 도달하는 빛의 양이 정확히 2배 또는 절반.**
|
||||
|
||||
모든 노출 파라미터를 stop이라는 하나의 단위로 환산하면 서로 직접 더하고 뺄 수 있습니다.
|
||||
|
||||
```
|
||||
stop = log2(빛의 양 비율)
|
||||
```
|
||||
|
||||
### 1.2 파라미터별 환산식
|
||||
|
||||
**노출 시간 (exposure time)** — 빛의 양에 정비례
|
||||
|
||||
```
|
||||
Δstop = log2(t2 / t1)
|
||||
```
|
||||
- 100 µs → 5000 µs : `log2(5000/100) = log2(50) = 5.64 stop`
|
||||
|
||||
**게인 (gain, dB)** — 머신비전 카메라는 `dB = 20·log10(선형배수)` 규약을 씁니다
|
||||
|
||||
```
|
||||
선형배수 = 10^(dB / 20)
|
||||
Δstop = dB / 6.02
|
||||
```
|
||||
- 16 dB : `16 / 6.02 = 2.66 stop` (선형 6.3배)
|
||||
- 24 dB : `24 / 6.02 = 3.99 stop` (선형 15.8배)
|
||||
- 외우기: **6 dB ≈ 1 stop**
|
||||
|
||||
**조리개 (f-number, N)** — 빛의 양이 `1/N²`에 비례
|
||||
|
||||
```
|
||||
Δstop = 2 · log2(N_old / N_new) ← 양수면 밝아짐
|
||||
```
|
||||
- f/11 → f/4 : `2·log2(11/4) = 2·1.459 = 2.92 stop 밝아짐`
|
||||
- f/8 → f/4 : `2·log2(8/4) = 2.00 stop 밝아짐`
|
||||
- f/8 → f/11 : `2·log2(8/11) = -0.92 stop` (약 1 stop 어두워짐)
|
||||
|
||||
표준 조리개 계열은 √2배씩 증가하며 각 단계가 정확히 1 stop입니다:
|
||||
|
||||
```
|
||||
f/1.4 f/2 f/2.8 f/4 f/5.6 f/8 f/11 f/16
|
||||
```
|
||||
|
||||
**조도 (lux)** — 그대로 비율의 log2
|
||||
|
||||
- 직사광 100,000 lux vs 실내 300 lux : `log2(100000/300) = 8.4 stop`
|
||||
|
||||
### 1.3 현재 시스템의 stop 예산
|
||||
|
||||
```
|
||||
노출: 100 → 5000 µs = 5.64 stop
|
||||
게인: 0 → 16 dB = 2.66 stop
|
||||
------------------------------------
|
||||
합계 (조리개 고정 시) = 8.30 stop (약 315배)
|
||||
```
|
||||
|
||||
실외 직사광 ↔ 일반 실내 차이가 8~9 stop이므로, **여유가 사실상 0**입니다. 실내에서 노출 상한과 게인 상한을 동시에 최대로 써야 겨우 적정 노출이 되고, 이는 모션 블러와 노이즈가 동시에 최악인 지점입니다.
|
||||
|
||||
### 1.4 EV(Exposure Value)로 커버 범위 확인하기
|
||||
|
||||
Stop보다 실전적인 도구입니다. 장면 밝기를 `EV100`이라는 절대 숫자로 표현합니다.
|
||||
|
||||
**적정 노출 조건식:**
|
||||
|
||||
```
|
||||
log2(N² / t) = EV_scene + g
|
||||
|
||||
N = f-number
|
||||
t = 노출시간 [초]
|
||||
g = 게인 [stop] = dB / 6.02
|
||||
```
|
||||
|
||||
이걸 뒤집으면, **주어진 세팅이 커버할 수 있는 장면 밝기**가 나옵니다:
|
||||
|
||||
```
|
||||
EV_scene = log2(N² / t) - g
|
||||
```
|
||||
|
||||
**참고 EV100 값:**
|
||||
|
||||
| 장면 | EV100 |
|
||||
|---|---|
|
||||
| 눈밭/수면 반사 직사광 | 16 |
|
||||
| 맑은 날 직사광 | 15 |
|
||||
| 흐린 날 실외 | 12~13 |
|
||||
| 건물 그늘 / 해질녘 | 10~11 |
|
||||
| 밝은 실내(사무실, 창가) | 7~8 |
|
||||
| 어두운 실내(창고, 복도) | 5~6 |
|
||||
| 지하주차장 | 3~5 |
|
||||
|
||||
**현재 세팅(f/8, 100~5000 µs, 0~16 dB) 커버 범위:**
|
||||
|
||||
```
|
||||
가장 밝은 쪽: t=100µs, g=0 → EV = log2(64/0.0001) - 0 = 19.29
|
||||
가장 어두운쪽: t=5000µs, g=16dB → EV = log2(64/0.005) - 2.66 = 10.98
|
||||
|
||||
→ 커버 범위: EV 10.98 ~ 19.29
|
||||
```
|
||||
|
||||
**해석:** 위쪽 EV 19.3은 지구상에 거의 없는 밝기입니다(직사광이 15). 즉 **3~4 stop을 아무 쓸모없는 밝은 쪽에 낭비**하고 있고, 아래쪽은 EV 11에서 끊겨서 **실내(EV 5~8)에 4~6 stop 모자랍니다.**
|
||||
|
||||
**f/4로 열었을 때:**
|
||||
|
||||
```
|
||||
밝은 쪽: log2(16/0.0001) = 17.29
|
||||
어두운쪽: log2(16/0.005) - 2.66 = 8.98
|
||||
→ EV 8.98 ~ 17.29
|
||||
```
|
||||
|
||||
**f/4 + 게인 상한 16.9 dB (실측 하드웨어 상한 — 24 dB는 이 카메라에서 불가능):**
|
||||
|
||||
```
|
||||
g = 16.9 / 6.02 = 2.81 stop
|
||||
어두운쪽: log2(16/0.005) - 2.81 = 8.84
|
||||
→ EV 8.84 ~ 17.29 ← 밝은 실내(EV 7~8) 상단에 겨우 걸침. 직사광은 커버하지만
|
||||
어두운 실내(EV 5~6)·지하주차장(EV 3~5)은 이 조합만으론 못 미침
|
||||
```
|
||||
|
||||
**f/2.8 + 게인 상한 16.9 dB + 노출 상한 3 ms(블러 고려):**
|
||||
|
||||
```
|
||||
g = 16.9 / 6.02 = 2.81 stop
|
||||
밝은 쪽: log2(7.84/0.0001) = 16.26
|
||||
어두운쪽: log2(7.84/0.003) - 2.81 = 8.55
|
||||
→ EV 8.55 ~ 16.26
|
||||
```
|
||||
|
||||
**정정:** 원래 초안은 게인 상한을 24 dB(3.99 stop)로 가정해 "f/4+24dB → EV 7.65부터 커버", "f/2.8+24dB+3ms → EV 7.71까지 커버"라고 결론 내렸었습니다. 하지만 이 카메라의 실측 게인 상한은 16.9 dB(2.81 stop)로 확인되어(0번 요약 및 3.2절 참조), 위 두 시나리오 모두 **다크사이드 EV가 약 1.2 stop씩 나빠집니다.** 즉 조리개를 열고 게인을 최대로 써도 "밝은 실내"(EV 7~8) 하단조차 안정적으로 커버하지 못하고, "어두운 실내"(EV 5~6)는 이 조합으로는 원천적으로 불가능합니다. 어두운 쪽 stop을 더 벌려면 게인이 아니라 **더 밝은 렌즈(더 낮은 F No.) 또는 더 긴 노출 상한(3.1절의 실제 블러 한계 재계산 필요 — 아래 참조)** 쪽에서 찾아야 합니다.
|
||||
|
||||
이 계산을 스프레드시트나 짧은 파이썬 스크립트로 만들어 두면, 렌즈를 바꾸거나 상한을 조정할 때마다 즉시 검증할 수 있습니다.
|
||||
|
||||
```python
|
||||
import math
|
||||
|
||||
def coverage(N, t_min_us, t_max_us, gain_max_db):
|
||||
g = gain_max_db / 6.02
|
||||
ev_bright = math.log2(N**2 / (t_min_us * 1e-6))
|
||||
ev_dark = math.log2(N**2 / (t_max_us * 1e-6)) - g
|
||||
return ev_dark, ev_bright
|
||||
|
||||
print(coverage(8.0, 100, 5000, 16)) # 현재
|
||||
print(coverage(4.0, 100, 3000, 16.9)) # 권장 — 게인은 16.9 dB가 이 카메라의 실측 하드웨어 상한(24 dB 불가)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 렌즈 스위트 스팟 (Sweet Spot)
|
||||
|
||||
### 2.1 왜 존재하는가
|
||||
|
||||
렌즈의 선명도(MTF)를 조리개에 대해 그리면 **가운데가 가장 좋은 U자 뒤집힌 형태**가 됩니다. 양쪽 끝이 나빠지는 이유가 서로 다릅니다.
|
||||
|
||||
**조리개를 열었을 때(f/1.4~f/2) 나빠지는 이유 — 수차(aberration)**
|
||||
- 구면수차, 코마, 비점수차 → 렌즈 주변부 광선이 한 점에 모이지 않음
|
||||
- 비네팅(주변부 광량 저하) 심함 → **photometric 일관성에 직접 악영향**
|
||||
- 상면만곡(field curvature) → 화면 가장자리 초점 어긋남
|
||||
- 피사계심도 얕음 → 근거리/원거리 동시에 못 잡음
|
||||
|
||||
> **실장착 렌즈 기준:** ZLKC `VM0420MP5`(4mm, C-mount, 수동 아이리스)는 최대개방이 **F2.0**이 한계입니다(`resource/VM0420MP5 en.pdf`). f/1.4 영역은 이 렌즈로는 애초에 시험 불가 — 수차가 가장 심한 극단은 f/2.0이고, 그보다 열 수 없다는 점에서 오히려 안전한 쪽입니다.
|
||||
|
||||
**조리개를 조였을 때(f/8~f/16) 나빠지는 이유 — 회절(diffraction)**
|
||||
- 이건 렌즈 품질과 무관한 **물리 법칙**입니다. 좋은 렌즈를 사도 해결 안 됩니다.
|
||||
- 조리개 구멍이 작아질수록 빛이 회절해 점광원이 Airy disk로 퍼집니다.
|
||||
|
||||
```
|
||||
Airy disk 지름 = 2.44 · λ · N (λ ≈ 0.55 µm, 가시광 중심)
|
||||
```
|
||||
|
||||
| 조리개 | Airy disk 지름 |
|
||||
|---|---|
|
||||
| f/2.8 | 3.8 µm |
|
||||
| f/4 | 5.4 µm |
|
||||
| f/5.6 | 7.5 µm |
|
||||
| **f/8** | **10.7 µm** |
|
||||
| **f/11** | **14.7 µm** |
|
||||
| f/16 | 21.5 µm |
|
||||
|
||||
### 2.2 우리 시스템에 적용
|
||||
|
||||
픽셀 피치 3.45 µm은 이제 가정이 아니라 확인된 값입니다 — 실장착 카메라 `MV-CS016-10UC`가 정확히 Sony IMX273(3.45 µm)을 씁니다(`resource/MVCS01610UMUC ... datasheet.pdf`). 아래 계산이 바로 우리 카메라 값입니다:
|
||||
|
||||
- **f/8** → Airy 10.7 µm = **픽셀 3.1개에 걸쳐 번짐**
|
||||
- **f/11** → Airy 14.7 µm = **픽셀 4.3개에 걸쳐 번짐**
|
||||
|
||||
즉 현재 세팅은 **하드웨어적으로 이미지를 3~4픽셀 블러 처리한 것과 같습니다.** 코너 검출기(FAST/ORB/Harris)는 로컬 gradient 피크에 의존하므로, 이 정도 번짐이면 검출되는 특징점 수와 위치 반복성(repeatability)이 눈에 띄게 나빠집니다.
|
||||
|
||||
**회절 한계 조리개 (Airy 지름 = 픽셀 2개 기준):**
|
||||
|
||||
```
|
||||
N_limit = 2·p / (2.44 · λ)
|
||||
|
||||
p = 3.45 µm → N_limit = 6.9 / 1.342 ≈ f/5.1 ← 우리 카메라(MV-CS016-10UC, IMX273)가 정확히 이 값
|
||||
p = 2.74 µm → N_limit ≈ f/4.1
|
||||
p = 5.86 µm → N_limit ≈ f/8.7
|
||||
```
|
||||
|
||||
**→ 우리 시스템의 조리개 상한선은 f/5.1입니다.** f/4 권장안은 이 한계 안쪽이라 문제 없고, f/5.6은 살짝 벗어나므로 f/4 쪽이 더 안전합니다.
|
||||
|
||||
### 2.3 대부분 렌즈의 스위트 스팟
|
||||
|
||||
**최대개방에서 2~3 stop 조인 지점**, 실무적으로 **f/4 ~ f/5.6**이 대부분입니다.
|
||||
|
||||
우리 렌즈(`VM0420MP5`, 최대개방 F2.0)에 그대로 대입하면 2~3 stop 조인 지점이 정확히 **f/4 ~ f/5.6**이라, 이 일반론이 실제 렌즈 스펙과 잘 맞아떨어집니다. 다만 2.2절에서 구한 회절 한계(f/5.1)를 넘지 않으려면 **f/4 쪽으로 붙이는 게 안전**합니다 — f/5.6은 회절 한계를 살짝 넘습니다.
|
||||
|
||||
### 2.4 그래도 실측으로 확인할 것
|
||||
|
||||
이론은 이론이고, 저가 광각 렌즈는 개방에서 정말 형편없을 수 있습니다. 다음 절차로 30분 만에 확인 가능합니다:
|
||||
|
||||
1. 삼각대 고정, 벽면에 슬랜티드 엣지(45도 기울인 흑백 경계) 또는 지멘스 스타 차트 부착
|
||||
2. 조리개를 f/2.8, f/4, f/5.6, f/8, f/11로 바꾸며 촬영 (노출로 밝기 보상)
|
||||
3. 각 이미지에서 측정:
|
||||
- **ORB/FAST 특징점 개수** ← SLAM에는 이게 MTF보다 직접적인 지표
|
||||
- 슬랜티드 엣지 MTF50 (`sfrmat`, `MTF Mapper` 등)
|
||||
- 중앙 / 코너 각각 따로
|
||||
4. 특징점 개수가 최대가 되는 조리개를 선택
|
||||
|
||||
---
|
||||
|
||||
## 3. 조리개 결정 절차 — "가장 어두운 곳 기준" 구체화
|
||||
|
||||
질문: *어두운 곳 기준으로 조리개를 맞출 때, 그때의 노출값과 게인값은 무엇으로 두는가?*
|
||||
|
||||
**답: 물리적 한계값(5000 µs, 16 dB)이 아니라, "품질 허용 한계값"으로 둡니다.** 그래야 여유(headroom)가 남습니다.
|
||||
|
||||
### 3.1 Step 1 — 노출 상한 `t_blur_max` 를 계산으로 결정
|
||||
|
||||
```
|
||||
블러(px) ≈ f_px · ω · t_exp + f_px · v⊥ · t_exp / Z
|
||||
|
||||
f_px : 초점거리 [픽셀]
|
||||
ω : 각속도 [rad/s] ← 핸드헬드에서 지배적
|
||||
v⊥ : 시선 수직 속도 [m/s]
|
||||
Z : 피사체 거리 [m]
|
||||
```
|
||||
|
||||
핸드헬드 보행 기준 실측 권장값: IMU 자이로 로그에서 **각속도 95 퍼센타일**을 뽑아 쓰세요. 몸을 돌리거나 코너를 돌 때 1~2 rad/s가 흔히 나옵니다.
|
||||
|
||||
**우리 렌즈+센서 조합의 실제 f_px:**
|
||||
|
||||
```
|
||||
f_px = f_lens[mm] / pixel_pitch[mm] = 4 / 0.00345 ≈ 1159 px
|
||||
```
|
||||
|
||||
(`VM0420MP5` 4mm + IMX273 3.45 µm 기준. 실측 캘리브레이션 `JUL8_calib.md`의 cam1 fx=1200.0 / cam2 fx=1196.8과도 거의 일치 — 이 식이 신뢰할 만함을 뒷받침합니다.)
|
||||
|
||||
이 값은 아래 원래 예시가 가정했던 f_px=500(표준 화각)이나 f_px=300(광각)보다 훨씬 큽니다 — 즉 **이 렌즈는 예시들보다 화각이 좁아서(수평 약 64°), 같은 각속도에서도 블러 허용 노출시간이 훨씬 짧습니다.**
|
||||
|
||||
실제 f_px=1159 기준 예시 (ω = 1.5 rad/s, 블러 목표 1.0 px):
|
||||
|
||||
```
|
||||
t_blur_max = 1.0 / (1159 · 1.5) = 0.58 ms
|
||||
```
|
||||
|
||||
실제 f_px=1159 기준 예시 (ω = 1.0 rad/s, 블러 목표 1.5 px, 조금 더 관대한 목표):
|
||||
|
||||
```
|
||||
t_blur_max = 1.5 / (1159 · 1.0) = 1.29 ms
|
||||
```
|
||||
|
||||
참고용 원래 예시 (다른 화각의 렌즈였다면):
|
||||
|
||||
```
|
||||
f_px = 500, ω = 1.5 rad/s, 블러 목표 1.0 px → t_blur_max = 1.33 ms
|
||||
f_px = 300(광각), ω = 1.0 rad/s, 블러 목표 1.5 px → t_blur_max = 5.0 ms
|
||||
```
|
||||
|
||||
→ **초점거리가 짧을수록(광각) 노출을 길게 쓸 수 있습니다.** 우리 렌즈는 광각이 아니라서(4mm/1160px), 요약표의 "보통 2~3 ms"는 과도한 값입니다 — 실제로는 **0.6~1.3 ms** 수준을 목표로 잡고, 최종 값은 반드시 실제 IMU 각속도 95퍼센타일로 재계산하세요. 이 값이 짧아지면 1.4절의 다크사이드 EV 계산도 함께 나빠진다는 점에 유의합니다(노출 상한이 줄면 어두운 장면을 커버할 능력도 줄어듦).
|
||||
|
||||
### 3.2 Step 2 — 실사용 게인 상한 `g_work` 를 실측으로 결정
|
||||
|
||||
게인을 0 / 6 / 12 / 16.9 dB로 바꿔가며 동일 장면 촬영(이 카메라는 16.9 dB가 물리적 상한이라 18/24 dB는 설정 자체가 안 됨) → ORB 특징점 개수와 매칭 인라이어 비율을 측정 → 급격히 꺾이는 지점 직전을 `g_work`로 잡습니다. 보통 **12 dB 근처**입니다.
|
||||
|
||||
`g_max`(비상 상한)는 통상 `g_work + 12 dB` 정도로 열어두라고 하지만, **이 카메라(MV-CS016-10UC)는 게인 자체가 0~16.9 dB까지만 되므로 그 공식을 그대로 쓸 수 없습니다.** 예: `g_work = 12 dB`인 경우, `g_max`는 24 dB가 아니라 **하드웨어 물리적 한계인 16.9 dB**이고, 비상용으로 실제 확보되는 여유는 `16.9 - 12 = 4.9 dB(0.81 stop)`뿐입니다. `g_work`를 더 낮게 잡을수록(예: 8 dB) 이 여유는 늘어나지만 대신 실사용 노출이 그만큼 길어져야 합니다.
|
||||
|
||||
### 3.3 Step 3 — 가장 어두운 목표 환경에서 조리개 조절
|
||||
|
||||
1. 실제로 데이터를 취득해야 하는 **가장 어두운 장소**로 이동 (지하주차장? 실내 복도? 야간?)
|
||||
2. 카메라를 **수동 모드**로 두고 `t = t_blur_max`, `gain = g_work` 로 고정
|
||||
3. **조리개 링을 돌려가며** 히스토그램 중앙값(median)이 8-bit 기준 **DN 100~120**이 되는 지점을 찾음
|
||||
4. 그 위치에서 조리개를 **기계적으로 고정** (고정 나사 + 나사고정제/락타이트 또는 매니큐어). 포커스 링도 동일하게 고정.
|
||||
5. 카메라 3대 모두 동일 f-number가 되도록 맞춤 (렌즈 개체 편차가 있으므로 눈금이 아니라 **실측 밝기**로 맞추세요)
|
||||
|
||||
### 3.4 Step 4 — 밝은 쪽 검증
|
||||
|
||||
1. 맑은 날 직사광 실외로 이동
|
||||
2. `t = t_min`, `gain = 0 dB`
|
||||
3. 관심 영역(하늘 제외)의 **98 퍼센타일 < DN 250** 인지 확인
|
||||
4. 포화되면 → **ND 필터** 추가 (ND2 = 1 stop, ND4 = 2 stop, ND8 = 3 stop)
|
||||
- 얇은 평행 평판 ND는 내부 파라미터에 유의미한 영향 없음
|
||||
- 단, 실제 사용 상태(필터 장착 상태)로 캘리브레이션 재검증 권장
|
||||
5. 포화 안 되면 → ND 불필요. 위 EV 계산상 **f/4 + 100 µs 이면 EV 17.3까지 커버**하므로 ND 없이 가능성이 높습니다.
|
||||
|
||||
### 3.5 Step 5 — 조리개 고정 후 캘리브레이션
|
||||
|
||||
조리개와 포커스를 확정한 **바로 그 상태**로:
|
||||
- 각 카메라 내부 캘리브레이션 (intrinsic + distortion)
|
||||
- 카메라 간 / 카메라-LiDAR 외부 캘리브레이션
|
||||
- **비네팅 맵**과 **광응답함수(CRF)** 도 이때 함께 측정해두면 후처리에서 크게 유리 (7절 참조)
|
||||
|
||||
이후 조리개/포커스 링에 **표식(마킹)** 을 남기고, 세션마다 사진을 찍어 변화 여부를 기록하세요.
|
||||
|
||||
---
|
||||
|
||||
## 4. 측광 지표 (Metering Metric) — 논문 요약
|
||||
|
||||
### 4.1 현재 방식의 문제: 평균 밝기 (Mean Intensity)
|
||||
|
||||
```
|
||||
목표: mean(I) = 128 이 되도록 노출 조절
|
||||
```
|
||||
|
||||
**문제:** 프레임의 20%를 차지하는 하늘(DN 255)이 평균을 끌어올림 → 컨트롤러가 노출을 줄임 → 정작 특징점이 있는 지면/건물이 DN 30~50으로 뭉개짐. **관측하신 현상이 정확히 이것입니다.**
|
||||
|
||||
더 근본적으로, 평균 밝기는 **"이미지에 정보가 얼마나 있는가"와 아무 상관이 없습니다.** 평균 128인 완전 균일한 회색 이미지는 특징점이 0개입니다.
|
||||
|
||||
### 4.2 즉시 적용 가능한 개선: ROI + 퍼센타일 측광
|
||||
|
||||
논문 구현 없이 **오늘 바꿀 수 있는 것**이고, 개선 폭이 가장 큽니다.
|
||||
|
||||
```
|
||||
1) ROI 마스크: 프레임 상단 1/3 제외 (하늘) 또는 LiDAR 깊이로 원거리 영역 제외
|
||||
2) 제어 목표: ROI 내부의 p70 퍼센타일 → DN 130
|
||||
3) 하드 제약: ROI 내부의 p98 퍼센타일 < DN 245 (포화 방지)
|
||||
4) 하늘이 포화되는 것은 허용 — SLAM에 하늘 특징점은 어차피 불필요
|
||||
```
|
||||
|
||||
퍼센타일은 소수의 극단적으로 밝은 화소에 흔들리지 않으므로, 평균 대비 훨씬 안정적입니다.
|
||||
|
||||
### 4.3 Shim et al. — Gradient 기반 노출 제어
|
||||
|
||||
- *"Auto-adjusting camera exposure for outdoor robotics using gradient information"*, IROS 2014
|
||||
- *"Gradient-based Camera Exposure Control for Outdoor Mobile Platforms"*, arXiv:1708.07338 / IEEE TCSVT
|
||||
|
||||
**핵심 아이디어:** 대부분의 영상처리 알고리즘(코너 검출, 디스크립터, 스테레오 매칭)이 **로컬 gradient**에 의존하므로, 밝기가 아니라 **gradient 총량이 최대가 되는 노출**을 찾자.
|
||||
|
||||
**동작 방식:**
|
||||
1. 현재 이미지 `I`에 여러 감마 값 `γ`를 적용해 "다른 노출로 찍었다면 어땠을지"를 **합성**
|
||||
`I_γ = 255 · (I/255)^γ` — `γ < 1`이면 밝아진 이미지, `γ > 1`이면 어두운 이미지
|
||||
2. 각 합성 이미지에서 Sobel gradient 크기 맵을 계산
|
||||
3. 특별한 매핑 함수를 적용해 gradient를 합산:
|
||||
- 아주 작은 gradient(노이즈)는 임계값 `δ` 이하로 **버림**
|
||||
- 큰 gradient는 log 압축해서 **몇 개의 강한 엣지가 점수를 독점하지 못하게 함**
|
||||
- 결과적으로 **"적당한 크기의 gradient가 많이 있는 상태"** 가 최고점
|
||||
4. 점수가 최대인 `γ*`를 찾음
|
||||
5. `γ* < 1` 이면 노출을 올리고, `γ* > 1` 이면 내림
|
||||
|
||||
실무 제어 법칙(비례 제어, 상수는 튜닝):
|
||||
```
|
||||
Δstop = -k · log2(γ*) k ∈ [0.5, 1.0]
|
||||
t_next = t_curr · 2^(Δstop)
|
||||
```
|
||||
|
||||
**멀티카메라 확장 (우리 케이스에 직접 해당):** 논문은 이 개념을 다중 카메라 시스템으로 확장해, **인접 카메라 간 밝기 일관성**과 **각 카메라의 적정 노출**을 동시에 만족하는 제어 알고리즘을 제시합니다. 각 카메라가 독립 AE로 돌면 크로스 카메라 매칭과 파노라마 스티칭이 깨지는데, 이를 비용함수에 일관성 항으로 넣어 해결합니다.
|
||||
|
||||
**장점:** 광응답함수 캘리브레이션이 필요 없음 (감마 근사만 사용) → 구현 난이도 낮음
|
||||
**단점:** 여러 γ를 sweep 해야 해서 계산량이 있음 (후처리 시스템이면 문제 없음)
|
||||
|
||||
### 4.4 Zhang, Forster, Scaramuzza — Active Exposure Control
|
||||
|
||||
- *"Active Exposure Control for Robust Visual Odometry in HDR Environments"*, ICRA 2017
|
||||
- 코드: `github.com/uzh-rpg/active_camera_exposure_control` (GPLv3)
|
||||
|
||||
**Shim 대비 차이:** 감마로 근사하는 대신 **카메라의 실제 광응답함수(CRF)를 오프라인 캘리브레이션**해 두고, 이를 이용해 "노출 시간이 t일 때의 이미지"를 **해석적으로 정확히 예측**합니다.
|
||||
|
||||
그 위에서 gradient 지표를 **노출 시간에 대해 미분**해서 gradient ascent로 최적점을 찾습니다. Sweep이 아니라 미분이므로 훨씬 빠릅니다.
|
||||
|
||||
논문은 SVO(Semi-direct Visual Odometry)를 가변 노출에 대응하도록 수정해 붙였고, 카메라 내장 AE와 고정 노출 양쪽 모두보다 좋은 결과를 보였습니다.
|
||||
|
||||
**위 저장소에는 4가지 방법이 모두 구현되어 있어 바로 비교 가능합니다:**
|
||||
- `kMeanIntensity` — 평균 밝기 (baseline, 현재 방식)
|
||||
- `kPercentile` — 퍼센타일 기반 (4.2절 방식)
|
||||
- `kGradient` — Zhang et al. gradient 미분 방식
|
||||
- `kShim` — Shim et al. sweep 방식
|
||||
|
||||
**→ 실무 착수점으로 이 저장소를 그대로 쓰는 것을 추천합니다.**
|
||||
|
||||
### 4.5 Kim, Cho, Kim — Bayesian Optimization
|
||||
|
||||
- *"Exposure control using Bayesian optimization based on entropy weighted image gradient"*, ICRA 2018
|
||||
|
||||
Gradient에 **엔트로피 가중치**를 곱한 지표를 쓰고, 국소 gradient ascent 대신 **베이지안 최적화**로 전역 탐색합니다. 목적함수가 다봉(multi-modal)일 때 국소 최적에 빠지지 않는 것이 장점이고, 적은 샘플로 수렴합니다.
|
||||
|
||||
### 4.6 최신 흐름 — Region-weighted / Motion-aware
|
||||
|
||||
- *"Region-weighted gradient and motion-aware camera exposure control for robust visual odometry"* (2025)
|
||||
|
||||
Gradient 지표에 **영역별 가중치**(가까운 구조물 > 먼 배경)와 **모션 블러 페널티**를 추가합니다. IMU 각속도를 읽어서 "지금 빠르게 회전 중이니 노출을 길게 못 쓴다"를 제어에 반영하는 방식으로, 3.1절의 블러 계산을 실시간으로 하는 것에 해당합니다. VINS-Mono와 ORB-SLAM3 양쪽에서 정확도 향상을 보고합니다.
|
||||
|
||||
또한 *BorealHDR* 라는 다중 노출 스테레오 데이터셋(8.4 km, 50 궤적, LiDAR 기반 3D 맵 및 포즈 GT 포함)이 공개되어 있어, **AE 알고리즘을 재현 가능하게 오프라인 벤치마크**할 수 있습니다. 후처리 파이프라인이시니 이런 에뮬레이터 방식이 잘 맞습니다.
|
||||
|
||||
### 4.7 적용 우선순위
|
||||
|
||||
```
|
||||
1단계 (즉시) : ROI + 퍼센타일 측광으로 교체 ← 개선폭 최대, 비용 최소
|
||||
2단계 (1~2주) : uzh-rpg 저장소 kShim / kGradient 적용
|
||||
3단계 : 3대 카메라 밝기 일관성 항 추가 (Shim 멀티카메라)
|
||||
4단계 : IMU 각속도 연동 노출 상한 동적 조절
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 노출 중심(Exposure-Midpoint) 타임스탬프
|
||||
|
||||
### 5.1 문제 정의
|
||||
|
||||
하드웨어 트리거는 **노출의 시작**을 결정합니다. 하지만 이미지가 실제로 표현하는 시각은 **노출 구간의 중심**입니다.
|
||||
|
||||
```
|
||||
트리거 엣지 ────┬──────────────────────────────────> 시간
|
||||
│
|
||||
├─ t_latency ─┤
|
||||
├──── t_exp ────┤
|
||||
│ ↑ │
|
||||
노출시작 유효시각 노출종료
|
||||
(t_exp/2)
|
||||
```
|
||||
|
||||
카메라 3대가 각자 AE로 돌면 `t_exp`가 서로 다릅니다. **같은 트리거 엣지를 받아도 유효 시각이 서로 어긋납니다.**
|
||||
|
||||
```
|
||||
카메라 A: t_exp = 3000 µs → 유효시각 = t_trig + Δ + 1500 µs
|
||||
카메라 B: t_exp = 200 µs → 유효시각 = t_trig + Δ + 100 µs
|
||||
차이 = 1400 µs = 1.4 ms
|
||||
```
|
||||
|
||||
### 5.2 오차 크기 — 핸드헬드 보행 기준
|
||||
|
||||
**병진 오차:**
|
||||
```
|
||||
보행 1.4 m/s × 1.4 ms = 2.0 mm ← 무시 가능
|
||||
```
|
||||
|
||||
**회전 오차 (이쪽이 지배적):**
|
||||
```
|
||||
각속도 1.5 rad/s × 1.4 ms = 2.1 mrad = 0.12°
|
||||
픽셀 환산: f_px 500 × 2.1 mrad = 1.05 px
|
||||
```
|
||||
|
||||
**→ 1 px 수준의 재투영 오차**입니다. 일반적인 RANSAC 인라이어 임계값(1~2 px)과 같은 수준이라 무시하기 어렵습니다. 노출 차이가 최대(100 vs 5000 µs)일 때는 2.45 ms → **1.8 px**까지 커집니다.
|
||||
|
||||
특히 **핸드헬드로 몸을 돌릴 때 각속도가 순간적으로 2~3 rad/s**까지 올라가고, 하필 그 순간이 실내→실외 전환처럼 노출이 크게 다른 상황과 겹치기 쉽습니다.
|
||||
|
||||
### 5.3 해결 절차
|
||||
|
||||
**[A] 트리거→노출시작 지연 `t_latency` 를 1회 실측**
|
||||
|
||||
대부분의 머신비전 카메라는 GPIO에 **Strobe / ExposureActive** 출력이 있습니다. 이 신호는 실제 센서 적분 구간 동안만 HIGH입니다.
|
||||
|
||||
```
|
||||
1. 오실로스코프 CH1 = 트리거 입력, CH2 = ExposureActive 출력
|
||||
2. 트리거 rising edge → ExposureActive rising edge 까지의 시간 = t_latency
|
||||
3. ExposureActive의 HIGH 폭 = 실제 t_exp (설정값과 다를 수 있음 — 반드시 확인)
|
||||
4. 카메라 3대 각각 측정 (개체차 있음)
|
||||
5. 노출 시간을 100 µs / 1000 µs / 5000 µs로 바꿔가며 t_latency가 일정한지 확인
|
||||
```
|
||||
|
||||
**[B] 프레임별 실제 노출 시간을 로깅**
|
||||
|
||||
GenICam Chunk Data를 활성화합니다:
|
||||
```
|
||||
ChunkModeActive = True
|
||||
ChunkSelector = ExposureTime → ChunkEnable = True
|
||||
ChunkSelector = Gain → ChunkEnable = True
|
||||
ChunkSelector = Timestamp → ChunkEnable = True
|
||||
ChunkSelector = FrameID → ChunkEnable = True
|
||||
```
|
||||
이러면 **각 이미지에 실제 적용된 노출/게인이 이미지와 함께 원자적으로** 붙어옵니다. AE는 명령과 실제 적용 사이에 1~2프레임 지연이 있으므로, **설정값이 아니라 chunk 값을 써야 합니다.**
|
||||
|
||||
**[C] 후처리에서 타임스탬프 보정**
|
||||
|
||||
```python
|
||||
t_effective = t_trigger + t_latency + (t_exp_chunk / 2.0)
|
||||
```
|
||||
|
||||
ROS 환경이라면 bag을 다시 쓰거나(`header.stamp` 재작성), 별도 CSV 사이드카를 만들어 SLAM 입력 시점에 적용합니다.
|
||||
|
||||
**[D] 검증**
|
||||
|
||||
보정 전/후로 다음을 비교:
|
||||
- 회전 구간에서의 재투영 오차 RMS
|
||||
- 카메라 간 특징점 매칭 인라이어 비율
|
||||
- LiDAR 포인트를 이미지에 투영했을 때의 엣지 정합도 (회전 구간에서 특히)
|
||||
|
||||
보정이 맞다면 **회전이 빠른 구간에서 개선폭이 크게 나타나야** 합니다. 정지 구간에서는 차이가 없어야 정상입니다.
|
||||
|
||||
### 5.4 대안 / 보완 전략
|
||||
|
||||
**대안 1 — 노출 시간 동기화 (Master-Slave)**
|
||||
3대의 `t_exp`를 항상 동일하게 강제하고, 밝기 차이는 **게인으로만** 흡수합니다. 유효 시각이 구조적으로 일치하므로 보정이 불필요합니다.
|
||||
- 장점: 가장 확실, 후처리 단순
|
||||
- 단점: 태양을 정면으로 보는 카메라와 그늘을 보는 카메라의 노출 요구가 3~4 stop 차이날 때, 게인만으로는 2.7 stop(16 dB)밖에 못 메움
|
||||
|
||||
**대안 2 — 노출 비율 제한 (절충안, 추천)**
|
||||
3대의 `t_exp` 비율을 예를 들어 **최대 2:1 이내**로 제한합니다. 그러면 최대 유효시각 차이가 `t_max/4`로 묶입니다.
|
||||
```
|
||||
t_max = 3000 µs → 최대 어긋남 750 µs → 회전 1.5 rad/s에서 0.56 px
|
||||
```
|
||||
여기에 [C]의 보정까지 하면 잔차는 무시할 수준이 됩니다.
|
||||
|
||||
**대안 3 — TriggerDelay 사전 보정**
|
||||
카메라의 `TriggerDelay` 레지스터로 노출 시작을 미리 당겨서 중심을 맞추는 방법입니다. 다만 다음 프레임의 `t_exp`를 미리 알아야 하므로, AE가 천천히 변할 때만 근사적으로 동작합니다. **후처리 시스템이면 [C]가 더 정확하고 간단합니다.**
|
||||
|
||||
### 5.5 LiDAR 쪽 체크리스트
|
||||
|
||||
- LiDAR 포인트가 **포인트 단위 타임스탬프**를 갖는지 확인 (스캔 단위만 있으면 deskew 불가)
|
||||
- IMU로 **모션 디스큐(motion compensation)** 적용 여부 확인
|
||||
- 카메라와 LiDAR가 **동일 시간 기준**(PTP / PPS+NMEA / 공통 트리거 카운터)을 쓰는지 확인
|
||||
- LiDAR 스캔 구간의 어느 시점을 "스캔 시각"으로 잡을지 규약을 문서화 (스캔 시작? 중심?) — 카메라 중심시각 규약과 일관되게
|
||||
|
||||
---
|
||||
|
||||
## 6. 12-bit RAW 저장이란
|
||||
|
||||
### 6.1 무엇이 다른가
|
||||
|
||||
**센서 ADC는 원래 10~12 bit입니다.** 8-bit 출력을 선택하면 카메라가 내부에서 감마/LUT를 적용하고 8-bit로 잘라서 내보냅니다. 이때 버려진 정보는 **영구히 복구 불가**입니다.
|
||||
|
||||
| | 8-bit | 12-bit |
|
||||
|---|---|---|
|
||||
| 밝기 계단 수 | 256 | 4096 |
|
||||
| 카메라 내부 처리 | 감마/LUT 적용됨 | 선형 그대로 |
|
||||
| 하늘 근처 계조 | 뭉개짐 | 살아있음 |
|
||||
| 그림자 디테일 | 소실 | 후처리로 끌어올림 가능 |
|
||||
|
||||
**"RAW"의 의미:** 카메라가 감마, 화이트밸런스, 샤프닝, 디모자이킹(컬러의 경우)을 **적용하지 않은 상태**. 이 처리들을 후처리 PC에서 우리가 원하는 대로 수행합니다.
|
||||
|
||||
### 6.2 후처리 시스템에서의 실질적 가치
|
||||
|
||||
핵심은 **"12→8 bit 변환 규칙을 언제든 다시 정할 수 있다"** 는 것입니다.
|
||||
|
||||
```
|
||||
[취득] 12-bit 선형 raw → 디스크 저장 (한 번만 찍음)
|
||||
│
|
||||
[후처리] ├─→ 감마 2.2 → 8-bit → SLAM 시도 1
|
||||
├─→ CLAHE → 8-bit → SLAM 시도 2
|
||||
└─→ 로컬 톤매핑 → 8-bit → SLAM 시도 3
|
||||
```
|
||||
|
||||
**8-bit로 저장했다면, 결과가 나쁠 때 현장에 다시 나가야 합니다.** 12-bit raw면 책상에서 재처리합니다. 이게 후처리 파이프라인의 가장 큰 이점입니다.
|
||||
|
||||
### 6.3 PixelFormat 선택
|
||||
|
||||
| 포맷 | 바이트/픽셀 | 비고 |
|
||||
|---|---|---|
|
||||
| `Mono8` | 1.0 | 현재 추정 |
|
||||
| `Mono12Packed` / `Mono12p` | **1.5** | 12-bit를 압축 패킹 — 권장 |
|
||||
| `Mono16` | 2.0 | 12-bit 데이터를 16-bit 컨테이너에 담음, 낭비 |
|
||||
| `BayerRG12Packed` | 1.5 | 컬러 센서. 디모자이킹은 후처리에서 |
|
||||
| `RGB8` | 3.0 | 카메라 내부 디모자이킹 — **피하세요** |
|
||||
|
||||
**컬러 카메라라면 반드시 Bayer raw로 저장하세요.** 카메라 내부 디모자이킹은 대역폭 3배에 품질도 나쁩니다.
|
||||
|
||||
### 6.4 대역폭/저장 용량 계산
|
||||
|
||||
```
|
||||
초당 데이터량 = 카메라수 × 해상도 × 바이트/픽셀 × FPS
|
||||
```
|
||||
|
||||
예시 (5 MP, 20 FPS, 3대):
|
||||
```
|
||||
Mono8 : 3 × 5.0e6 × 1.0 × 20 = 300 MB/s → 1시간 1.08 TB
|
||||
Mono12p : 3 × 5.0e6 × 1.5 × 20 = 450 MB/s → 1시간 1.62 TB
|
||||
```
|
||||
|
||||
**요구사항:**
|
||||
- **NVMe SSD 필수** (SATA SSD 550 MB/s는 여유가 없음, HDD는 불가)
|
||||
- 인터페이스 대역폭 확인: GigE 125 MB/s / 10GigE 1.25 GB/s / USB3 400 MB/s / CoaXPress
|
||||
- **무손실 압축** 고려: 12-bit 선형 데이터는 `zstd -1` 이나 `lz4` 로 실시간 압축이 가능하고, 보통 1.5~2배 줄어듭니다. 손실 압축(JPEG)은 특징점 품질에 영향을 주므로 피하세요.
|
||||
|
||||
### 6.5 확인해야 할 함정
|
||||
|
||||
- **프레임레이트 저하:** 일부 센서는 12-bit에서 리드아웃이 느려져 최대 FPS가 떨어집니다. 데이터시트 확인 필수.
|
||||
- **대역폭 초과 시 프레임 드롭:** 드롭이 생기면 하드웨어 동기가 무의미해집니다. **FrameID 연속성을 반드시 로깅/검증**하세요 (7절).
|
||||
- **Gamma / LUT 비활성화 확인:** `Gamma = 1.0`, `LUTEnable = False`. 이게 켜져 있으면 raw가 raw가 아닙니다.
|
||||
- **BlackLevel 값 기록:** 선형성 복원에 필요합니다.
|
||||
|
||||
### 6.6 후처리 파이프라인 권장 순서
|
||||
|
||||
```
|
||||
12-bit 선형 raw
|
||||
↓ BlackLevel 차감
|
||||
↓ (컬러면) 디모자이킹
|
||||
↓ 비네팅 보정 (플랫필드 맵)
|
||||
↓ 노출/게인으로 정규화 ← photometric 일관성 확보
|
||||
↓ 톤매핑: 감마 또는 CLAHE
|
||||
8-bit mono → SLAM 프론트엔드
|
||||
```
|
||||
|
||||
**CLAHE 사용 시 주의:** `clipLimit`, `tileGridSize`를 **전 프레임에 걸쳐 고정**하세요. 프레임마다 다르면 밝기 일관성이 깨져서 direct 방식이나 루프클로저에 악영향입니다.
|
||||
|
||||
---
|
||||
|
||||
## 7. 메타데이터 로깅 대상 정리
|
||||
|
||||
### 7.1 프레임 단위 (매 이미지마다)
|
||||
|
||||
| 항목 | 출처 | 왜 필요한가 |
|
||||
|---|---|---|
|
||||
| `frame_id` (시퀀스 번호) | Chunk `FrameID` | **드롭 프레임 검출** — 가장 중요 |
|
||||
| `trigger_count` | 트리거 보드 | 트리거와 프레임의 1:1 대응 검증 |
|
||||
| `device_timestamp` | Chunk `Timestamp` | 카메라 내부 클럭 |
|
||||
| `host_timestamp` | 수신 PC | 클럭 드리프트 추정용 |
|
||||
| **`exposure_time_us`** | **Chunk `ExposureTime`** | **타임스탬프 보정 + 광도 정규화** |
|
||||
| **`gain_db`** | **Chunk `Gain`** | **광도 정규화** |
|
||||
| `black_level` | Chunk / 주기적 | 선형성 복원 |
|
||||
| `device_temperature` | `DeviceTemperature` | 암전류 변화, 캘리브레이션 열드리프트 |
|
||||
| `pixel_format` | 설정 | 후처리 디코딩 |
|
||||
| `wb_gains` (컬러) | Chunk | 컬러 일관성 |
|
||||
| `ae_state` | 컨트롤러 | AE 수렴/포화 여부 디버깅 |
|
||||
| `metering_score` | 컨트롤러 | 측광 지표 값 (튜닝 분석용) |
|
||||
|
||||
### 7.2 세션 단위 (취득 1회마다, YAML 매니페스트)
|
||||
|
||||
```yaml
|
||||
session:
|
||||
id: 2026-08-13_park_001
|
||||
datetime_start: 2026-08-13T14:20:00+09:00
|
||||
environment: outdoor_sunny # indoor / outdoor / mixed
|
||||
weather: clear
|
||||
platform: handheld
|
||||
operator: ...
|
||||
|
||||
cameras:
|
||||
- name: cam0
|
||||
model: ...
|
||||
serial: ...
|
||||
firmware: ...
|
||||
lens_model: ...
|
||||
lens_serial: ...
|
||||
aperture_fnumber: 4.0 # ★ 반드시 기록
|
||||
focus_setting: hyperfocal_marked
|
||||
nd_filter: none # none / ND4 / ND8
|
||||
pixel_format: Mono12p
|
||||
resolution: [2448, 2048]
|
||||
roi: [0, 0, 2448, 2048]
|
||||
binning: 1
|
||||
gamma_enabled: false
|
||||
lut_enabled: false
|
||||
exposure_limits_us: [30, 3000]
|
||||
gain_limits_db: [0, 17] # MV-CS016-10UC 실측 하드웨어 상한 (24 불가)
|
||||
ae_algorithm: shim_gradient_v2
|
||||
ae_roi_mask: masks/cam0_sky_exclude.png
|
||||
|
||||
sync:
|
||||
trigger_source: external_board
|
||||
trigger_freq_hz: 20
|
||||
trigger_latency_us: # ★ 오실로스코프 실측값
|
||||
cam0: 12.4
|
||||
cam1: 12.1
|
||||
cam2: 12.8
|
||||
time_base: PTP # PTP / PPS+NMEA / trigger_counter
|
||||
|
||||
lidar:
|
||||
model: ...
|
||||
scan_rate_hz: 10
|
||||
per_point_timestamp: true
|
||||
sync_mode: PPS+NMEA
|
||||
deskew_applied: false # 후처리에서 수행
|
||||
|
||||
imu:
|
||||
model: ...
|
||||
rate_hz: 200
|
||||
|
||||
calibration:
|
||||
intrinsics_file: calib/intrinsics_2026-08-10.yaml
|
||||
intrinsics_sha256: ...
|
||||
extrinsics_file: calib/extrinsics_2026-08-10.yaml
|
||||
extrinsics_sha256: ...
|
||||
calibrated_aperture: 4.0 # ★ 세션 조리개와 일치하는지 검증
|
||||
photometric_crf: calib/crf_cam0.txt
|
||||
vignetting_map: calib/vignette_cam0.png
|
||||
|
||||
storage:
|
||||
raw_path: /data/2026-08-13_park_001/
|
||||
compression: zstd-1
|
||||
```
|
||||
|
||||
### 7.3 필수 검증 스크립트 (후처리 첫 단계)
|
||||
|
||||
취득 직후 자동으로 돌려야 하는 검사들:
|
||||
|
||||
```
|
||||
[ ] FrameID 연속성 — 카메라 3대 모두 결번 없는가
|
||||
[ ] 트리거 카운트 == 프레임 카운트
|
||||
[ ] 3대 프레임 개수 일치
|
||||
[ ] 노출 시간 히스토그램 — 상한/하한에 붙어있는 프레임 비율
|
||||
→ 상한에 계속 붙어있으면 조리개가 너무 조여진 것
|
||||
[ ] 포화 화소 비율 (DN > 4000 in 12-bit)
|
||||
→ ROI 내에서 1% 넘으면 경고
|
||||
[ ] 언더 화소 비율 (DN < 100 in 12-bit)
|
||||
[ ] 카메라 간 노출 시간 비율 — 2:1 초과 프레임 수
|
||||
[ ] device_timestamp vs host_timestamp 드리프트 추이
|
||||
[ ] 온도 변화 폭 — 세션 중 10°C 이상 변하면 캘리브레이션 재검토
|
||||
```
|
||||
|
||||
이 검사를 통과 못 하면 **현장을 떠나기 전에** 재취득하는 게 원칙입니다.
|
||||
|
||||
---
|
||||
|
||||
## 8. Claude Code 작업 지시용 태스크 목록
|
||||
|
||||
개발 PC에서 착수할 때 이 순서를 권장합니다.
|
||||
|
||||
### Phase 1 — 진단 도구 (반나절)
|
||||
1. `ev_coverage.py` — 1.4절 EV 커버리지 계산기. 조리개/노출/게인 조합 입력 → 커버 EV 범위 출력, 목표 환경 EV 리스트와 비교해 부족분 표시
|
||||
2. `blur_budget.py` — 3.1절 블러 계산기. IMU 로그(rosbag/csv) 입력 → 각속도 퍼센타일 통계 → 목표 블러(px) 대비 `t_blur_max` 산출
|
||||
3. `bag_qc.py` — 7.3절 검증 스크립트 전체
|
||||
|
||||
### Phase 2 — 광학 특성 실측 (1일, 현장 작업 포함)
|
||||
4. `aperture_sweep_eval.py` — 조리개별 촬영 이미지 폴더 입력 → ORB 특징점 수, 매칭 반복성, MTF50을 조리개별로 플롯
|
||||
5. `gain_noise_eval.py` — 게인별 SNR 및 특징점 품질 곡선 → `g_work` 결정
|
||||
6. 오실로스코프 측정으로 `trigger_latency` 3대 실측 (수작업, 결과를 YAML에 기록)
|
||||
|
||||
### Phase 3 — 타임스탬프 보정 (1~2일)
|
||||
7. Chunk Data 활성화 (`ExposureTime`, `Gain`, `Timestamp`, `FrameID`) 및 로깅 경로 구현
|
||||
8. `retimestamp.py` — `t_trig + t_latency + t_exp/2` 로 타임스탬프 재작성. rosbag 재작성 또는 사이드카 CSV 생성
|
||||
9. 보정 전/후 재투영 오차 비교 리포트 생성 (회전 구간 별도 집계)
|
||||
|
||||
### Phase 4 — 노출 제어 개선 (1~2주)
|
||||
10. ROI 마스크 생성 도구 (수동 폴리곤 또는 LiDAR 깊이 기반 자동)
|
||||
11. 퍼센타일 측광 컨트롤러 구현 및 교체 (4.2절)
|
||||
12. `uzh-rpg/active_camera_exposure_control` 통합, `kShim`/`kGradient` 비교 평가
|
||||
13. 3대 밝기 일관성 항 추가 (Shim 멀티카메라 방식)
|
||||
14. 노출 비율 2:1 제한 로직 추가 (5.4절 대안 2)
|
||||
|
||||
### Phase 5 — 후처리 파이프라인 (1주)
|
||||
15. `Mono12p` 전환, 대역폭/드롭 검증
|
||||
16. 광응답함수(CRF) + 비네팅 맵 캘리브레이션 도구
|
||||
17. 12→8 bit 톤매핑 파이프라인 (감마 / CLAHE / 로컬 톤매핑 선택 가능하게, 파라미터는 세션 전체 고정)
|
||||
18. 노출·게인 기반 광도 정규화 적용
|
||||
|
||||
---
|
||||
|
||||
## 9. 참고문헌
|
||||
|
||||
- I. Shim, J.-Y. Lee, I. S. Kweon, "Auto-adjusting camera exposure for outdoor robotics using gradient information," IROS 2014
|
||||
- I. Shim, T.-H. Oh, J.-Y. Lee, J. Choi, D.-G. Choi, I. S. Kweon, "Gradient-based Camera Exposure Control for Outdoor Mobile Platforms," arXiv:1708.07338 / IEEE TCSVT
|
||||
- Z. Zhang, C. Forster, D. Scaramuzza, "Active Exposure Control for Robust Visual Odometry in HDR Environments," ICRA 2017
|
||||
- 코드: https://github.com/uzh-rpg/active_camera_exposure_control
|
||||
- J. Kim, Y. Cho, A. Kim, "Exposure control using Bayesian optimization based on entropy weighted image gradient," ICRA 2018
|
||||
- "Region-weighted gradient and motion-aware camera exposure control for robust visual odometry," 2025
|
||||
- BorealHDR — 다중 노출 스테레오 데이터셋 (AE 알고리즘 오프라인 재현 벤치마크용)
|
||||
- J. Engel, V. Usenko, D. Cremers, "A Photometrically Calibrated Benchmark For Monocular Visual Odometry" (TUM mono dataset — 광응답함수/비네팅 캘리브레이션 방법론)
|
||||
Reference in New Issue
Block a user