오늘은 컴퓨터 비전에서 최적화 및 마무리(?)를 담당하는 Bundle Adjustment(여기서는 줄여서 BA라고 한다.)에 대해서 알아보고자 한다. BA를 공부하기 앞서 시간이 지나 가물가물한 용어들에 대해 간단하게 알아보고 공부해보자. 또한 오늘부터는 개인적으로 공부하는 흐름에 중요하다고 생각하는 용어나 부분을 굵은 글씨로 표현하였다.
SLAM - 환경의 3D 구조를 파악하고, 이를 지도 형태로 표현하는 지도 작성(Mapping) 기능을 가지고 있으며 이를 통해
환경 내에서 자신의 위치와 방향을 계산할수 있게 해주는 기술.
3D 재구성 - 평평한 2D 데이터를 사용해 깊이와 형태를 가진 3D 환경을 복원하는 기술.
SfM - 다수의 2D 이미지로부터 카메라 위치와 3D 구조를 동시에 추정하는 기술.
BA는 3D 재구성 또는 SLAM에서 사용하는 최적화 기법이다. 이는 여러 카메라 뷰로 관찰된 3D 포인트와 카메라 파라미터를 조정하여 모든 관측치(이미지 좌표)가 가장 잘 맞도록 만드는 과정이다.
여러 개의 사진(또는 프레임)에서 얻은 데이터를 기반으로 3D 재구성을 수행할 때, 측정 오차나 부정확한 카메라 파라미터를 최소화해준다.
왜 BA가 필요할까? SLAM이나 SfM에서 얻은 초기 추정값은 보통 다음과 같은 이유로 정확하지 않을 수 있다.
BA는 이 모든 문제를 해결하기 위해 전체 시스템의 모든 요소를 동시에 최적화해준다.
그렇다면 본격적으로 BA란 무엇인지에 대해 알아가보자.
오늘 알아보기
최적화하는 대상
필요한 데이터
BA 의 수학적 모델
BA 과정
OpenCV 를 활용한 BA 구현하기
BA 는 어디서 쓸까?
BA 를 사용하면서 생길수 있는 문제들
또 BA를 공부하다가 보니 BA는 증강현실(AR)을 구현하는데에도 엄청 중요한 역할을 하는 기법이었는데 이와 관련해서도 추가로 알아보자
알아보기 +
증강현실에서 BA 가 중요한 이유
AR에서 BA 의 활용 사례
Python과 OpenCV로 간단한 AR 예제만들기
최적화 대상은 크게 두 가지이다.
1. 카메라 파라미터
2. 3D 포인트
BA는 다음과 같은 최적화 문제를 풀기 위한 알고리즘이다.
이 최적화 문제를 풀어야 하는 이유는 뭘까?
3D vision에서는 관찰된 2D 좌표 (
1. 초기화
카메라 파라미터 (
2. 잔차 계산하기
잔차란 관찰된 좌표와 예상된 좌표의 차이를 의미한다.
3. 비선형 최적화
Levenberg-Marquardt 알고리즘 같은 비선형 최적화 방법을 사용하여 모든 카메라와 3D 포인트를 동시에 최적화한다.
4. 수렴
잔차가 최소화되면 최적화 종료한다.
Python에서 직접 구현하기보다는, OpenCV나 Google의 Ceres Solver 같은 라이브러리를 사용하는 것이 효율적이라고 한다. 고로 OpenCV를 이용하여 BA를 간단하게 구현해 보자.
import cv2
import numpy as np
# 예제 데이터 (관측된 2D 포인트)
observed_points = np.array([
[100, 200], [150, 250], [200, 300] # 이미지에서 관측된 좌표
])
# 초기 카메라 파라미터
camera_matrix = np.array([
[800, 0, 320], # fx, 0, cx
[0, 800, 240], # 0, fy, cy
[0, 0, 1] # 0, 0, 1
])
# 초기 3D 포인트
points_3d = np.array([
[1, 2, 10], [2, 3, 15], [3, 4, 20] # 월드 좌표계의 초기 3D 포인트
])
# 초기 외부 파라미터 (카메라 위치와 방향)
rvec = np.array([0, 0, 0]) # 회전 벡터
tvec = np.array([0, 0, 0]) # 병진 벡터
# Bundle Adjustment 수행
_, rvec, tvec, inliers = cv2.solvePnPRansac(
points_3d, observed_points, camera_matrix, None
)
print("최적화된 회전 벡터:", rvec)
print("최적화된 병진 벡터:", tvec)
- BA는 고비용 계산을 하기 때문에 많은 카메라와 3D 포인트를 동시에 최적화하면 연산량이 급증할수 있다. 이때 효율적인 라이브러리(Ceres Solver, g2o)를 사용하거나 포인트를 샘플링해보자.
- BA는 초기값이 너무 부정확하면 수렴 문제가 발생하여 최적화가 실패할 수 있는데, 이러한 문제가 발생하면 적절한 초기 추정값을 재설정해주도록 하자.
증강현실에서는 실제 환경 위에 가상 객체를 정확히 배치해야 하기 때문에, 카메라의 위치와 방향, 그리고 3D 환경에 대한 정보가 정확해야 한다. BA 참 좋은것 같다.
AR 시스템은 다음을 필요로 한다.
- 카메라 포즈 추정카메라의 위치와 방향을 정확히 알아야 가상 객체를 올바르게 배치할 수 있다. 카메라 포즈 추정은 주로 SLAM 또는 Visual-Inertial Odometry(VIO)를 통해 이루어지고, 이 과정에서 BA가 카메라 위치를 정교하게 조정해준다.
- 3D 환경의 정확한 재구성AR 시스템은 현실 세계의 구조(평면, 깊이 등)를 정확히 이해해야 하는데, 이때 BA는 여러 관 찰로부터 3D 포인트의 위치를 정밀하게 계산할수 있다.
- 왜곡 보정
렌즈 왜곡이나 센서 노이즈로 인해 발생하는 오류를 최소화하여 가상 객체를 자연스럽게 배치할 수 있다.
그러면 BA는 AR에서 중요한 역할을 하고 있음을 알게 되었다. 실제로는 어떤 AR서비스에서 사용되고 있으며 어떤 문제를 해결하고 있을까?
- ARKit, ARCore 등 AR 프레임워크
Google의 ARCore와 Apple의 ARKit은 SLAM 기반의 AR 시스템을 제공한다. 이 시스템에서는 카메라가 움직일 때마다 주변 환경의 특징점을 추적하면서, BA를 통해 카메라와 3D 포인트를 정밀히 최적화하여 사용한다.
- 마커리스 AR
마커리스 증강현실에서는 특정 패턴 없이도 실제 환경의 특징점을 추적하여 AR 콘텐츠를 배치한다. 이러한 특징점 추 적과 정렬 과정에서 BA를 사용해 위치와 방향 오차를 최소화한다.
- 가상 객체와 실제 환경의 일치
우리가 실제로 AR 서비스를 사용해 보았을때 가상 객체를 현실의 표면에 배치하면 가끔 객체가 떠 있거나 어긋나보 인다. 카메라의 외부 파라미터가 부정확하면 이러한 현상이 일어나는데 BA는 이러한 문제가 발생했을때 최적화하여 더욱 정밀하게 가상 객체를 배치할수 있게 도와준다.
- 안정적인 카메라 추적
카메라가 움직일 때, 현실 세계와의 일치를 유지하면서 카메라 이동 시 발생하는 잔상(Drift)을 최소화해준다.
다음은 간단한 AR 구현에서 BA를 적용하는 방법의 개요(?)이다.
import cv2
import numpy as np
# 카메라 내부 파라미터 설정
camera_matrix = np.array([[800, 0, 320],
[0, 800, 240],
[0, 0, 1]])
dist_coeffs = np.zeros(5) # 렌즈 왜곡 계수 (필요 시 설정)
# 3D 월드 좌표 (예: 바닥에 배치할 가상 객체의 3D 점)
object_points = np.array([
[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0] # 정사각형 좌표
])
# 이미지에서 관측된 2D 점
image_points = np.array([
[320, 240], [400, 240], [400, 320], [320, 320]
])
# 카메라 포즈 계산
ret, rvec, tvec = cv2.solvePnP(object_points, image_points, camera_matrix, dist_coeffs)
# 가상 객체 그리기
def draw_virtual_object(frame, rvec, tvec, camera_matrix, dist_coeffs):
# 가상 객체의 3D 점
virtual_points = np.array([
[0, 0, -1], [1, 0, -1], [1, 1, -1], [0, 1, -1]
])
# 3D 점을 2D 이미지 좌표로 투영
projected_points, _ = cv2.projectPoints(virtual_points, rvec, tvec, camera_matrix, dist_coeffs)
# 객체를 이미지에 그리기
for i in range(len(projected_points)):
pt1 = tuple(projected_points[i][0])
pt2 = tuple(projected_points[(i+1) % len(projected_points)][0])
cv2.line(frame, pt1, pt2, (0, 255, 0), 2)
# 예제 프레임
frame = np.zeros((480, 640, 3), dtype=np.uint8)
# 가상 객체를 프레임에 추가
draw_virtual_object(frame, rvec, tvec, camera_matrix, dist_coeffs)
# 결과 출력
cv2.imshow("AR Example", frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
오늘 알아본 Bundle Adjustment는 증강현실 시스템에서 현실 세계와 가상의 환경을 정확히 결합하는데 중요한 역할을 한다. 우리는 지금까지 BA를 통해서 더욱 정밀하고 자연스러운 AR 경험을 제공받고 있었던것 같다. 혹시 VR에서도 쓰이지 않을까 궁금하여 찾아봤는데 다시 생각해보니 BA는 현실에서 특징점을 보완해주는지라(?) VR 내에서 맵을 구성하거나 하는데는 별로 중요하지는 않은것 같다. 아무튼 오늘까지 공부함으로 인해 3D Vision에서 반드시 이해하고자 했던 개념들은 다 공부하게 되었다. 다음은 나는 이를 어디에 써먹을수 있을지 찾아봐야겠다.