대상: ROS, OpenCV, SLAM, Vision 기반 로봇 개발 중 “카메라 좌표계가 뭐고, extrinsic이 왜 필요한지” 헷갈리는 개발자
환경: Ubuntu 20.04 + ROS Noetic + OpenCV / cv_bridge
1. 문제/주제 요약
카메라 데이터를 다루다 보면 다음과 같은 혼란을 자주 겪습니다 👇
- 이미지 좌표 $(u, v)$ 와 실제 3D 좌표 $(X, Y, Z)$ 는 어떻게 연결되는가?
camera_info의K,D,R,P는 뭘 의미하는가?- 카메라가 “세계를 보는 방향”을 어떻게 수학적으로 표현하나?
- SLAM이나 AprilTag 위치 추정에서 왜 extrinsic 보정이 필요한가?
이 글에서는 카메라 좌표 → 월드 좌표 변환의 개념을intrinsic, extrinsic 두 축으로 간단히 정리합니다.
2. 카메라 좌표계 기본 구조
카메라 관련 좌표계는 일반적으로 아래와 같이 3단계로 구분됩니다:
[World Coordinate] → [Camera Coordinate] → [Image Coordinate]
(Xw, Yw, Zw) (Xc, Yc, Zc) (u, v)
| 단계 | 의미 | 변환 |
|---|---|---|
| 월드 좌표 (World) | 로봇, 지도, 3D 공간의 기준 좌표계 | SLAM, TF 등에서 기준 |
| 카메라 좌표 (Camera) | 카메라 렌즈 중심을 원점으로 한 좌표계 | Extrinsic (R, t) |
| 이미지 좌표 (Image) | 2D 픽셀 좌표 (u, v) | Intrinsic (K) |
3. Extrinsic Parameters — “카메라의 위치와 자세”
Extrinsic은 카메라가 세상 안에서 어디 있고, 어디를 보는지를 나타냅니다.
수식적으로는 아래처럼 표현됩니다:
$$\begin{bmatrix} X_c \ Y_c \ Z_c \end{bmatrix}
= R \begin{bmatrix} X_w \ Y_w \ Z_w \end{bmatrix} + t$$
- $$R$$: 회전 행렬 (3×3)
- $$t$$: 이동 벡터 (3×1)
즉, 월드 좌표 → 카메라 좌표 변환입니다.
- $$R$$: 카메라가 바라보는 방향
- $$t$$: 카메라의 위치(월드 기준)
예시:
R = [0.999 0.002 0.045;
-0.002 1.000 0.001;
-0.045 -0.001 0.999]
t = [0.05, 0.10, 0.30] # 카메라가 로봇 바닥에서 30cm 위
💡 ROS에서는 이 변환이
tf로 표현됩니다.
예:base_link → camera_link변환이 extrinsic과 동일한 의미입니다.
4. Intrinsic Parameters — “카메라 내부 광학 특성”
Intrinsic은 **카메라 자체의 내부 설정(렌즈 특성)**을 나타냅니다.
이는 3D 점을 이미지 픽셀 좌표로 변환할 때 사용됩니다.
기본 수식:
$$s\begin{bmatrix} u \ v \ 1 \end{bmatrix}K
\begin{bmatrix} X_c \ Y_c \ Z_c \end{bmatrix}
$$
Intrinsic Matrix $K$:
$$
K =
\begin{bmatrix}
f_x & 0 & c_x \
0 & f_y & c_y \
0 & 0 & 1
\end{bmatrix}
$$
| 기호 | 의미 |
|---|---|
| $$f_x, f_y$$ | 초점 거리 (픽셀 단위) |
| $$c_x, c_y$$ | 이미지 중심 좌표 (principal point) |
| $$s$$ | 스케일 (Zc에 비례) |
예시 (640×480 카메라):
K: [525.0, 0.0, 319.5,
0.0, 525.0, 239.5,
0.0, 0.0, 1.0]
💡 ROS에서는
/camera/camera_info토픽에 포함되어 있으며,image_proc,depth_image_proc,aruco_detect등에서 사용됩니다.
5. Intrinsic + Extrinsic 결합: 3D → 2D 투영
전체 변환을 합치면 다음과 같습니다:
$$s
\begin{bmatrix} u \ v \ 1 \end{bmatrix}
K [R|t]
\begin{bmatrix} X_w \ Y_w \ Z_w \ 1 \end{bmatrix}
$$
즉,
- [R|t] : 월드 → 카메라 좌표 (extrinsic)
- K : 카메라 내부 투영 (intrinsic)
이 조합을 통해 월드상의 3D 점이 이미지 상의 $(u,v)$ 로 투영됩니다.
예시 Python 코드 (OpenCV):
import cv2
import numpy as np
K = np.array([[525, 0, 319.5],
[0, 525, 239.5],
[0, 0, 1]])
R = np.eye(3)
t = np.array([[0], [0], [0.3]]) # 카메라가 30cm 위
point_3d = np.array([[1.0, 0.0, 2.0]]) # (Xw, Yw, Zw)
proj, _ = cv2.projectPoints(point_3d, cv2.Rodrigues(R)[0], t, K, None)
print(proj)
출력 예시:
[[[582.4, 312.7]]]
→ 실제 3D 점이 이미지 좌표 (582, 312)에 투영됨.
6. ROS 관점에서의 Intrinsic / Extrinsic
| 개념 | ROS 표현 | 관련 토픽 / 노드 |
|---|---|---|
| Intrinsic | /camera_info | image_proc, cv_bridge |
| Extrinsic | TF 변환 | base_link → camera_link, map → camera |
| 보정(calibration) | camera_calibration 패키지 | rosrun camera_calibration cameracalibrator.py |
TF 트리 예시:
map
└── odom
└── base_link
└── camera_link
⚙️ SLAM이나 AprilTag 위치 추정 시,
“카메라가 로봇에서 몇 cm 옆으로 붙어 있는가”가 바로 extrinsic입니다.
TF를 정확히 맞추지 않으면 좌표 계산이 엉뚱하게 됩니다.
7. 시각적 요약
World (Xw, Yw, Zw)
│ ↑
[R|t]│ │ Extrinsic
▼ │
Camera (Xc, Yc, Zc)
│ ↑
│ │ Intrinsic
▼ │
Image (u, v)
8. 자주 하는 실수 & 팁
| 증상 | 원인 | 해결 방법 |
|---|---|---|
| Depth → 3D 변환 값 이상 | Intrinsic K 행렬 잘못됨 | /camera_info 실제 값 사용 |
| 카메라와 로봇 좌표 불일치 | TF(base_link→camera_link) 오차 | static_transform_publisher 수정 |
| 이미지 왜곡 발생 | 렌즈 왜곡 계수 D 미적용 | image_proc로 rectify 필요 |
| 3D→2D 매핑 뒤집힘 | 좌표계 축 방향 혼동 | OpenCV (x→right, y→down, z→forward) 주의 |
| ArUco 위치가 반대로 나옴 | Extrinsic 행렬의 회전(R) 방향 오류 | cv2.Rodrigues 변환 확인 |
9. 정리
| 구분 | 의미 | ROS 연계 | 예시 |
|---|---|---|---|
| Intrinsic | 카메라 내부 특성 (초점, 중심, 왜곡) | /camera_info | K, D |
| Extrinsic | 카메라의 위치·방향 (로봇 대비) | tf | base_link → camera_link |
| 전체 변환 | 월드 → 카메라 → 이미지 | SLAM, Visual Odometry | `K[R |
📌 요약
- Intrinsic = 카메라 “내부 렌즈 정보”
- Extrinsic = 카메라 “세상 속 위치와 방향”
- 전체 투영:
$$
s [u, v, 1]^T = K [R|t] [X, Y, Z, 1]^T
$$ - ROS에서는
camera_info+tf로 자동 반영됨 - SLAM, Visual Odometry, AprilTag 인식 등은 모두 이 변환을 기반으로 동작