C.W.K.
Stream
Lesson 02 of 05 · published

solvePnP: 카메라 되돌리기

~14 min · solvepnp, pnp, reprojection-error, pose

Level 0빈 종이
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"진짜 두개골에서 눈이 어디 있는지 알아. 사진에서 어디 떨어졌는지도 알아. solvePnP 가 물어: 무슨 카메라가 앞엣걸 뒤엣걸로 바꾸나?"

solvePnP 가 답하는 질문

Perspective-n-Point — solvePnP — 는 앞 레슨의 정확한 역이야. 정방향 투영은 pose 를 받아 픽셀을 냈어. solvePnP 는 픽셀을 받아 pose 를 내. 구체적으로: 레퍼런스 머리 위 위치를 아는 3D 점들이 있고, 그 같은 이목구비가 사진에 떨어진 2D 픽셀이 있어. 솔버는 그 3D 점들을 pinhole 모델로 돌렸을 때 정확히 그 픽셀들로 투영했을 단 하나의 회전과 이동을 찾아. 그 회전이 머리의 방향이야 — 눈대중이 아니라 계산된 투시 상자.

대응이 연료야

솔버는 대응(correspondences) — (2D 픽셀, 3D 점) 매칭 쌍 — 으로 돌아. 3D 반쪽은 canonical 머리에서 와 — 모든 landmark 의 위치를 아는 중립 레퍼런스 얼굴. 2D 반쪽은 검출기(다음 트랙)에서 와, 그 landmark 각각이 이 사진에 실제로 나타난 자리를 알려줘. 인덱스로 매칭돼: 사진의 landmark 33 이 모델의 점 33 과 짝지어. 그 인덱스 매칭이 검출기 출력과 레퍼런스 모델이 topology-lock 을 유지해야 하는 이유야 — 인덱스를 바꾸거나 드리프트시키면 모든 대응이 조용히 거짓말해.

reprojection error 최소화

보통 모든 픽셀을 완벽히 맞추는 pose 는 없어 — 진짜 얼굴은 중립 모델이 아니고, 검출은 노이지해. 그래서 solvePnP가장 가까운 pose 를 찾아: 총 reprojection error, 즉 3D 점이 투영돼 떨어지는 자리와 실제 관측된 자리 사이 픽셀 거리의 합을 최소화해. 그 남은 error 는 부산물만이 아냐 — 엔진의 정직 계기야. 작은 error 는 pose 가 얼굴을 잘 설명한다는 뜻; 큰 건 fit 이 무리했으니 덜 믿어야 한다는 뜻. 솔버가 최소화하는 그 숫자가 UI 가 나중에 confidence 로 보여주는 숫자야.

회전에서 상자로

솔버는 회전을 압축된 형태(회전 벡터)로 돌려주고, 그건 머리의 상자 방향 3x3 회전 행렬로 변환돼. 그 행렬을 사람이 읽는 yaw, pitch, roll 로 바꾸는 건 그 자체로 작은 지뢰밭이야 — 순진한 분해는 거의 정면인 얼굴에서 wrap 되고 거짓말해 — 그래서 Loomis 는 대신 머리의 forward, up 벡터에서 각도를 읽어. 그 조심스러움이 두 레슨 뒤 no-flip 레슨의 주제 전부야; 지금은 깔끔한 버전을 붙들어: 대응 in, pose out, error 는 영수증.

Code

solvePnP, 대응에서 pose 로, trust 계기와 함께·python
import cv2
import numpy as np

# 3D model points (canonical head) matched BY INDEX to the 2D landmarks.
object_points = CANONICAL_HEAD[LANDMARK_IDS]      # (N, 3), known on a reference head
image_points  = detected_landmarks[LANDMARK_IDS]  # (N, 2), where they landed in-photo

ok, rvec, tvec = cv2.solvePnP(
    object_points, image_points,
    camera_matrix, dist_coeffs,      # intrinsics; focal comes from the bounded sweep
    flags=cv2.SOLVEPNP_ITERATIVE,
)
R, _ = cv2.Rodrigues(rvec)           # rotation vector -> 3x3 matrix = the head's box

# The honesty meter: reproject the model and measure the leftover pixel gap.
reproj, _ = cv2.projectPoints(object_points, rvec, tvec, camera_matrix, dist_coeffs)
err = np.linalg.norm(reproj.squeeze() - image_points, axis=1).mean()
# small err -> the pose explains the face; large err -> trust this fit less.

External links

Exercise

평평한 카드의 알려진 네 모서리와 그것이 사진에 나타나는 네 픽셀을 상상해. 왜 일반적으로 그게 하나의 카메라 pose 를 못 박기에 충분한지 설명해봐 — 그다음 유일하지 않은 경우를 기술해: 카드가 거의 edge-on 으로 찍혔거나, 네 모서리가 이미지에서 거의 일직선이면? 왜 더 많이, 잘 퍼진 landmark 가 solvePnP 를 더 안정적으로 만드는지랑 연결해봐.
Hint
각 대응이 pose 를 제약하고, 잘 퍼진 게 충분하면 over-determine 해서 노이즈가 평균돼. degeneracy 는 점들이 이미지를 충분히 못 걸칠 때 나타나 — edge-on 카드나 거의 일직선인 모서리는 깊이를 거의 제약 못 해서, 많은 pose 가 거의 똑같이 reproject 돼. 얼굴 landmark 가 안정적인 건 딱 그것들이 진짜 3D 표면에 퍼져 있어서야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.