처음← 이전 장다음 장 →

9장. 로봇의 눈: 카메라로 재고, 찾고, 따라가기

로봇이 카메라로 물체가 어디 있는지 재고, 그 숫자로 팔을 움직이는 방법을 본다. 수식은 가볍게 두고, 카메라가 무엇을 재고 어디서 틀리는지를 그림으로 잡는다. 강의 9강(76분, 존스홉킨스 대학 Gregory Hager의 초청 강의)에 해당한다.

9.1 로봇에게 왜 눈이 필요한가

지금까지 팔은 자기 관절 각도만 알면 되었다. 그런데 컵을 집으려면 컵이 어디 있는지부터 알아야 한다.

2.8절에서는 카메라가 "내 앞 2 cm에 컵이 있다"고 알려 준다고 그냥 가정했다. 이 장은 그 숫자가 어디서 나오는지 본다.

강의는 로봇이 눈으로 답해야 할 질문을 네 단계로 나눈다.

  1. 주변에 무엇이 어디 있나? 켜자마자 부딪히지 않고 다녀야 한다.
  2. 저것은 무엇이고, 나는 어디인가? 물건을 알아보고 내 자리를 안다.
  3. 물건에 어떻게 손을 대나? 안전하게 손을 뻗어 잡는다.
  4. 처음 보는 일은 어떻게 하나? 식탁 위의 처음 보는 그릇을 치우는 일 같은 것이다. 강의는 이 질문을 다루지 않는다.

질문마다 쓰는 기술이 다르다. 이 장의 순서도 이 표를 따른다.

질문 쓰는 기술 이 장
주변의 모양은? 스테레오: 카메라 두 대로 깊이 재기 9.5~9.7절
저것은 무엇이고 나는 어디인가? 특징점 찾기와 맞추기 9.8절
어떻게 손을 뻗나? 추적과 시각 서보 9.9~9.10절

강의는 카메라가 무엇을 재는지는 안다고 보고 바로 스테레오로 들어간다. 이 교재는 그 앞에 카메라의 기본(9.2~9.4절)을 채워 둔다.

알아 두기. 이 강의는 2008년 것이다. 그 뒤로 싼 깊이 카메라와 학습으로 찾는 특징점이 널리 쓰이게 되었다. 하지만 이 장의 뼈대인 투영, 삼각측량, 멀수록 커지는 오차는 지금 쓰는 장비에도 그대로 들어맞는다.

영상에서 보기: 02:52 로봇이 눈으로 답할 질문 · 03:53 처음 보는 물건 다루기 · 04:54 오늘의 세 주제

9.2 카메라가 주는 것은 숫자 격자다

사람은 사진을 보자마자 "책상 위의 컵"을 본다. 카메라가 주는 것은 그렇지 않다. 작은 칸(픽셀, pixel)마다 밝기 숫자가 하나씩 든 격자다.

사람에게는 컵, 카메라에게는 숫자
(가)의 파란 네모를 확대한 것이 (나)다. 사람 눈에는 모서리 선이지만, 숫자로는 "왼쪽이 크고 오른쪽이 작은 곳"일 뿐이다.
그림 9-1 · 컵 모서리를 확대하면 밝기 숫자만 있다

640 × 480 카메라라면 숫자가 307,200개다. 색 카메라는 칸마다 빨강·초록·파랑 세 숫자가 있어 그 세 배다. 이 숫자 더미에서 "컵"과 "컵까지의 거리"를 뽑아내는 일이 컴퓨터 비전(computer vision)이다.

픽셀의 자리는 (u, v)로 적는다. 왼쪽 위가 (0, 0)이고, u는 오른쪽으로, v는 아래로 커진다. 수학 시간의 y와 방향이 반대라 처음에 자주 헷갈린다.

무엇이 어려운가

숫자에서 물건을 뽑아내기 어려운 까닭은 크게 세 가지다. 이 장 곳곳에서 다시 만난다.

영상에서 보기: 32:53 같은 물건도 다르게 보인다

9.3 핀홀 카메라: 3차원 점이 영상의 점이 되기까지

카메라를 재는 기계로 쓰려면, 공간의 점이 어느 픽셀로 가는지 식으로 알아야 한다. 거의 모든 비전 계산이 쓰는 가장 단순한 모형이 핀홀 카메라(pinhole camera)다. 우리말로는 바늘구멍 사진기다.

어두운 상자 앞면에 바늘구멍을 하나 뚫는다. 물체의 각 점에서 나온 빛은 구멍을 지나 곧게 가서 뒷면에 닿는다. 그래서 뒷면에 거꾸로 선 상이 맺힌다.

렌즈 카메라도 계산할 때는 이 모형으로 본다. 렌즈는 구멍보다 빛을 많이 모을 뿐, 빛이 가는 길은 거의 같다.

멀어지면 상이 작아지고, f가 길면 상이 커진다
위는 옆에서 본 모습이고, 상자 뒷면의 파란 선이 센서다. 아래는 찍힌 영상을 바로 세운 것이다. 그림은 카메라 쪽을 크게 늘려 그렸다.
그림 9-2 · 직접 바꿔 보기: 높이 10 cm 컵과 바늘구멍 상자

그림에서 닮은 삼각형 두 개를 찾자. 구멍–컵 바닥–컵 꼭대기가 만든 큰 삼각형과, 구멍–센서 가운데–상의 끝이 만든 작은 삼각형이다. 두 삼각형은 모양이 같아서 변의 비가 같다.

수학 더하기: 닮은 삼각형. 세 각이 같은 두 삼각형은 크기만 다르고 모양이 같다. 그래서 대응하는 변끼리의 비가 같다. 그림의 두 삼각형은 구멍에서 마주 보는 각(맞꼭지각)이 같고, 둘 다 광축과 직각을 이룬다. 두 각이 같으니 나머지 한 각도 같다.

f를 픽셀 단위로 쓰면 h도 픽셀로 나온다(이유는 9.4절). 이 장은 계속 f = 500 픽셀, 영상 640 × 480인 카메라를 쓴다.

슬라이더로 해 볼 것

  1. Z를 50 cm에서 100 cm로: 상이 100 픽셀에서 50 픽셀로 절반이 된다. 거리와 크기는 반비례한다.
  2. f를 500에서 1000으로: 상이 두 배가 된다. 대신 시야각이 51°에서 27°로 좁아진다. 줌 렌즈가 하는 일이다.
  3. Z = 20 cm, f = 1000: 컵이 화면에 다 들어오지 않는다.

3차원으로: 카메라 좌표계 {C}

이제 카메라에 좌표계 {C}를 붙인다. 원점은 구멍이고, zC는 광축을 따라 앞으로, xC는 영상의 오른쪽, yC는 영상의 아래쪽이다. 강의도 이 방향을 쓴다.

yC가 아래인 것은 영상의 v가 아래로 커지는 것과 맞춘 것이다.

계산할 때는 센서를 구멍 앞 f 거리로 옮겨 생각한다. 상이 거꾸로 서지 않아 부호가 편하다. 이것을 영상면(image plane)이라 한다.

구멍에서 P로 가는 선이 영상면과 만나는 곳이 p다
O가 바늘구멍이다. 주황 점선 둘은 나란하다. 영상면에서 중심부터 p까지, 깊이 Z에서 광축부터 P까지 잰 어긋남이다.
그림 9-3 · 카메라 좌표계 {C}와 원근 투영

점 P를 {C}로 잰 숫자가 CP = (X, Y, Z)라 하자. 영상면 위의 점 p는 위와 같은 닮은 삼각형으로 구한다. 이것이 원근 투영(perspective projection)이다.

여기서 u, v는 영상 중심에서 잰 자리다. 핵심은 Z로 나누는 것이다. 멀리 있는 것은 작게, 가까운 것은 크게 찍힌다.

잠깐, 생각해 보기. {C}에서 (5, 2, 50) cm인 점과 (10, 4, 100) cm인 점은 영상의 어디에 찍힐까? f = 500 픽셀이다.

둘 다 중심에서 (50, 20) 픽셀이다. 둘째 점은 첫째 점과 같은 방향으로 두 배 멀리 있을 뿐이다.

구멍에서 뻗은 한 선 위의 점은 모두 같은 픽셀에 찍힌다. 그래서 사진 한 장으로는 그 선 위 어디인지, 곧 거리를 모른다. 컵의 실제 높이를 안다면 Z = f H / h로 거리를 짐작할 수는 있다.

영상에서 보기: 10:38 카메라 좌표계: x 오른쪽, y 아래, z 앞

9.4 픽셀로 적기, 보정, 그리고 팔 받침 {0}

투영식의 f, u, v는 무슨 단위로 쓸까? 영상은 픽셀로 나오고, 팔은 cm로 움직인다. 두 단위를 잇는 숫자를 구하는 일이 보정(calibration)이다.

내부 파라미터: 픽셀로 바꾸는 숫자 넷

f를 픽셀 단위로 쓰면 편하다. 렌즈가 4 mm이고 센서의 픽셀 한 칸이 0.008 mm라면, f = 4 ÷ 0.008 = 500 픽셀이다.

영상 중심의 픽셀 자리 (cx, cy)도 필요하다. 640 × 480이면 대략 (320, 240)이다.

가로와 세로 픽셀 크기가 조금 다를 수 있어서 fx, fy 둘로 둔다. 이 네 숫자가 내부 파라미터(intrinsic parameters)다. 강의가 말하는 "픽셀을 거리로 바꾸는 네 숫자"가 이것이다.

이 장에서는 fx = fy = f = 500으로 둔다.

외부 파라미터: 카메라가 어디에 있나

카메라가 어디에 어느 방향으로 놓였는지는 외부 파라미터(extrinsic parameters)다. 2장의 말로 하면 동차변환 하나, 0TC다.

체스판 무늬를 여러 방향에서 찍어 이 숫자들을 구하는 도구가 이미 잘 나와 있다. 강의는 MATLAB용 도구를 예로 든다.

알아 두기. 실제 렌즈는 화면 가장자리의 곧은 선을 휘게 찍는다(렌즈 왜곡). 보정 도구는 이 휨도 함께 구해서 영상을 펴 준다. 이 장은 왜곡이 없다고 본다.

거꾸로: 픽셀과 깊이에서 점으로

투영은 Z로 나누면서 깊이를 버렸다. 그러니 깊이 Z를 따로 알면 되돌릴 수 있다. 픽셀에서 중심을 빼 (u, v)를 만들고, 아래처럼 곱한다.

예제: 카메라가 본 컵을 팔 받침 기준으로

2.8절의 카메라 자리 (10, 5) cm를 다시 쓴다. 이번에는 책상 위 60 cm 높이에 달려 아래를 똑바로 내려다본다. 받침 {0}의 z₀는 위쪽이다.

카메라의 세 축을 받침 기준으로 읽는다. zC는 아래를 보니 −z₀, xC는 x₀와 같은 쪽으로 정한다. 그러면 오른손 법칙으로 yC는 −y₀이고, 회전 부분은 RX(180°)다.

컵 테두리의 가운데가 픽셀 (360, 270)에 찍혔다. 깊이는 9.5절의 스테레오로 재서 Z = 50 cm를 얻었다고 하자.

  1. 중심에서 재기. u = 360 − 320 = 40, v = 270 − 240 = 30.
  2. 되돌리기. X = 40 × 50 ÷ 500 = 4, Y = 30 × 50 ÷ 500 = 3이다. 그래서 CP = (4, 3, 50) cm다.
  3. 받침 기준으로. 2.8절처럼 돌리고 더한다. 동차변환 한 번이면 된다.

컵 테두리는 받침에서 (14, 2) cm, 높이 10 cm에 있다. 높이는 카메라 높이 60 cm에서 깊이 50 cm를 뺀 값과 맞는다. 받침에서 약 14.1 cm라 종이 팔이 닿는 2~18 cm 안이다.

잠깐, 생각해 보기. 보정이 잘못되어 0TC의 방향이 1° 틀렸다. 50 cm 앞의 컵은 받침 기준으로 얼마나 엉뚱한 곳에 찍힐까?

1°는 0.0175 라디안이다. 50 cm 떨어진 곳에서 50 × 0.0175 ≈ 0.9 cm 어긋난다.

보정의 작은 각도 오차가 거리만큼 커져서 손끝 오차가 된다. 9.10절에서 이 문제를 피하는 방법을 본다.

영상에서 보기: 07:33 왜 보정하나 · 08:05 픽셀과 거리를 잇는 숫자 넷 · 08:36 내부 보정과 외부 보정

9.5 두 카메라로 깊이 재기: 스테레오

사진 한 장은 깊이를 버린다. 그런데 사람은 두 눈으로 거리를 느낀다. 같은 점을 조금 떨어진 두 자리에서 보면, 두 시선이 만나는 곳이 하나로 정해진다.

이렇게 두 시선으로 점의 자리를 구하는 것이 삼각측량(triangulation)이다. 카메라 둘로 하는 것을 스테레오(stereo) 비전이라 한다. 강의에 따르면 사람의 10%쯤은 두 눈의 스테레오를 쓰지 못하고 다른 단서로 메운다.

스테레오는 세 가지 일로 나뉜다.

  1. 보정: 두 카메라가 서로 어디에 어느 방향으로 있는지 안다(9.4절).
  2. 짝 찾기(correspondence): 왼쪽 영상의 이 점이 오른쪽 영상의 어느 점인지 찾는다. 가장 어렵다(9.7절).
  3. 복원(reconstruction): 짝마다 삼각측량으로 3차원 점을 구한다.

나란한 두 카메라

강의는 계산이 가장 쉬운 배치를 쓴다. 두 카메라가 같은 쪽을 나란히 보고, 옆으로 b만큼만 떨어져 있다. b를 기선(baseline)이라 한다.

이 배치에서는 한 점이 두 영상의 같은 줄(같은 v)에 찍힌다.

왼쪽 카메라를 {C}로 잡는다. 오른쪽 카메라의 원점은 xC 방향으로 b 떨어져 있다. 그러니 오른쪽 카메라에서 잰 점의 X는 X − b다.

두 카메라 모두 9.3절의 투영식에 넣는다.

두 x를 빼면 X가 지워진다. 이 차이를 시차(disparity) d라 한다.

깊이는 시차에 반비례한다. 가까울수록 두 영상에서 많이 어긋난다. 손가락을 코앞에 세우고 한쪽 눈씩 번갈아 감아 보자. 손가락은 크게 뛰고, 멀리 있는 창밖 건물은 거의 그대로다.

Z를 구하면 X = xL Z / f, Y = y Z / f로 나머지도 나온다. 9.4절의 되돌리기와 같다.

가까울수록 시차가 크고, 시차가 깊이를 알려 준다
위는 책상을 위에서 본 모습으로, 가로(카메라 사이)를 세로(거리)보다 약 7배 늘려 그렸다. 점 P는 두 카메라 한가운데 있다. 아래 두 띠는 각 영상에서 P가 찍힌 줄이고, 점선은 영상 중심이다.
그림 9-4 · 직접 바꿔 보기: 나란한 두 카메라를 위에서 본 모습

슬라이더로 해 볼 것

  1. b = 6 cm에서 Z를 50 → 100 → 300 cm로: d가 60 → 30 → 10 픽셀로 준다.
  2. Z = 300 cm에서 b를 6 → 24 cm로: d가 10 → 40 픽셀로 는다. 기선이 길면 먼 곳도 큰 시차로 잰다.
  3. b = 30 cm, Z = 20 cm: 점이 두 영상 밖으로 나간다. 기선이 길면 가까운 점을 두 카메라가 함께 보지 못한다.

알아 두기. 실제 두 카메라는 정확히 나란하지 않다. 안쪽으로 조금 돌아가 있으면 짝이 같은 줄이 아니라 비스듬한 선 위에 놓인다. 보정으로 두 카메라의 상대 자세를 알면, 영상을 다시 그려 나란한 카메라처럼 만들 수 있다(정렬, rectification). 그래서 거의 모든 스테레오 프로그램이 나란한 카메라를 가정한다.

강의는 한 걸음 더 나간다. 두 카메라의 상대 자세는 영상만 보고도 거꾸로 구할 수 있다. 그래서 카메라 한 대를 들고 걸어가며 찍어도, 앞뒤 두 장면으로 스테레오를 할 수 있다.

영상에서 보기: 05:59 스테레오란 · 09:07 나란한 두 카메라 · 11:10 깊이 = 기선 × 초점 거리 ÷ 시차 · 14:16 영상 정렬

9.6 멀수록 깊이가 흐려진다

시차는 픽셀로 잰다. 짝 찾기가 아무리 잘되어도 1픽셀쯤은 틀린다. 같은 1픽셀이 가까운 곳과 먼 곳에서 깊이를 얼마나 바꾸는지 보자.

f = 500 픽셀, b = 6 cm(사람 눈 사이와 비슷)인 카메라 한 쌍이다. 시차를 1픽셀 크게 또는 작게 잰 경우를 Z = f b / d에 넣었다.

실제 깊이 Z 시차 d 1픽셀 틀리면 깊이는
25 cm 120 픽셀 24.8 ~ 25.2 cm
50 cm 60 픽셀 49.2 ~ 50.8 cm
1 m 30 픽셀 0.97 ~ 1.03 m
2 m 15 픽셀 1.88 ~ 2.14 m
5 m 6 픽셀 4.3 ~ 6.0 m
10 m 3 픽셀 7.5 ~ 15 m

50 cm에서는 1 cm도 안 틀리는데, 10 m에서는 몇 m씩 틀린다. 거리가 두 배가 될 때마다 오차는 약 네 배가 된다.

수학 더하기: 민감도는 미분으로 본다. d가 조금(Δd) 바뀔 때 Z가 얼마나 바뀌는지는 미분 dZ/dd가 알려 준다. Z = f b / d를 d로 미분하면 −f b / d²이다. 여기에 d = f b / Z를 넣으면 크기가 Z² / (f b)가 된다. Δd가 작을 때만 맞는 어림값이다.

이 식에서 세 가지를 읽는다.

  1. Z²: 거리가 두 배면 오차는 네 배다.
  2. b: 기선을 두 배로 벌리면 오차가 절반이다.
  3. f: 초점 거리가 길거나 픽셀이 촘촘하면 오차가 준다. 대신 시야가 좁아진다(그림 9-2).

왜 그런가: 깊이 칸

픽셀 하나는 구멍에서 퍼져 나가는 가는 부채꼴 안을 본다. 두 카메라의 부채꼴이 겹친 마름모 한 칸 안의 점은 모두 같은 두 픽셀에 찍힌다. 카메라는 그 칸 안 어디인지 구별하지 못한다.

같은 픽셀 차이가 먼 곳에서는 훨씬 긴 깊이를 덮는다
파란 선은 왼쪽 카메라, 주황 선은 오른쪽 카메라의 픽셀 경계다. 차이를 잘 보이려고 픽셀을 일부러 아주 크게 그렸다. 진한 칸과 옅은 칸이 가운데 줄의 깊이 칸이다.
그림 9-5 · 픽셀 경계선이 만드는 깊이 칸

칸의 폭은 거리에 비례해 넓어지는데, 칸의 길이는 거리의 제곱만큼 길어진다. 그래서 먼 칸일수록 가늘고 긴 바늘 모양이 된다.

잠깐, 생각해 보기. 사람 두 눈 사이는 6 cm 남짓이다. 사람의 스테레오는 어느 거리에서 가장 잘 될까?

강의에 따르면 코앞 약 45 cm(18인치)다. 팔 길이를 넘어가면 스테레오는 거의 쓰이지 않는다.

손으로 무언가를 다루는 거리에서 깊이가 가장 정확한 셈이다. 먼 거리는 크기, 가림, 움직임 같은 다른 단서로 짐작한다.

알아 두기. 로봇 머리에 단 카메라 한 쌍도 같다. 팔이 닿는 1 m 안에서는 cm 수준으로 재지만, 10 m 밖에서는 m 수준으로 틀린다. 그래서 멀리 다니는 로봇은 레이저 거리계 같은 다른 센서를 함께 쓰기도 한다.

영상에서 보기: 11:40 깊이는 시차에 반비례한다 · 12:42 10 m에서는 시차 한 칸이 수십 cm · 13:13 사람 눈은 팔 길이까지

9.7 짝 찾기와 시차 지도

삼각측량 식은 쉽다. 어려운 것은 왼쪽 영상의 한 점이 오른쪽 영상의 어디인지 찾는 일이다. 방법은 둘이다.

창 맞추기: SAD

닮은 정도는 숫자로 잰다. 가장 흔한 것이 SAD(sum of absolute differences)다.

두 창의 같은 자리 숫자끼리 빼서 절댓값을 모두 더한다. 작을수록 닮았다.

IL, IR은 두 영상의 밝기 숫자다. 정렬된 카메라에서는 짝이 같은 줄에 있다.

그래서 오른쪽 영상의 같은 줄에서만 창을 d = 0, 1, 2, …칸 밀어 가며 찾는다. 가장 작은 SAD를 준 d가 그 픽셀의 시차다.

창을 밀어 가며 차이의 합이 가장 작은 곳을 찾는다
파란 창은 왼쪽 영상 열 8~12, 주황 창은 오른쪽 영상에서 d칸 왼쪽으로 민 창이다. "차이" 줄은 칸마다 뺀 값의 절댓값이고, 아래 막대가 그 합(SAD)이다.
그림 9-6 · 직접 밀어 보기: 같은 줄 따라 창 맞추기

d = 4에서 SAD가 10으로 뚝 떨어진다. 다른 d에서는 250이 넘는다. 오른쪽 창이 왼쪽 창의 무늬와 겹쳐졌기 때문이다.

SAD는 빼기와 더하기뿐이라 빠르고, 칩으로 만들기도 쉽다. 강의는 창의 평균을 먼저 빼고 비교하는 변형도 소개한다. 오른쪽 영상이 통째로 조금 어두워도 짝이 맞는다.

얼마나 많이 계산하나

640 × 480 영상, 시차 후보 100개, 창 11 × 11이면 한 장에 307,200 × 100 × 121 ≈ 37억 번 빼기를 한다. 강의 기준으로 10년쯤 전까지는 스테레오를 돌리는 것만도 큰일이었다. 계산 순서를 바꾸는 요령과 빠른 칩 덕분에, 강의 당시에 이미 1초에 30장을 처리했다.

언제 틀리고, 틀린 것을 어떻게 아나

잠깐, 생각해 보기. 흰 종이나 무늬 없는 벽을 스테레오로 보면 어떻게 될까?

어느 창이나 숫자가 비슷해서 SAD가 다 작다. 그중 가장 작은 것은 우연히 정해지니, 시차가 아무 숫자나 나온다.

물체 가장자리도 문제다. 한쪽 카메라에만 보이는 곳(가림, occlusion)은 짝이 아예 없다.

틀린 답을 걸러 내는 간단한 방법이 좌우 확인(left-right check)이다. 왼쪽에서 오른쪽으로 찾은 짝에서, 거꾸로 오른쪽에서 왼쪽으로 다시 찾는다. 두 답이 같아야 한다.

무늬 없는 곳의 답은 거의 무작위라서, 100개 후보 중 두 번 같은 수가 나올 가능성이 낮다. 다르면 그 픽셀을 버린다. 강의는 비전에서 "거의 공짜로 얻는 좋은 방법"이라고 평한다.

시차 지도

이렇게 걸러 낸 결과를 픽셀마다 칠한 그림이 시차 지도(disparity map)다. 실제 프로그램은 보통 흑백으로 그리고 가까운 곳을 밝게 칠한다. 이 책의 그림은 진한 색일수록 가깝다.

시차 지도는 가까운 곳일수록 진하고, 무늬 없는 곳은 비어 있다
흰 벽은 무늬가 없어 비었다. 컵과 상자 바로 왼쪽의 빈 줄은 오른쪽 카메라에서 가려 짝이 없는 곳이다. (다)는 바닥 평면과 시차가 같은 칸을 지운 것이라, 바닥에 닿은 물건의 맨 아랫줄도 함께 지워졌다.
그림 9-7 · 시차 지도와 바닥 평면 빼기 (생각해 만든 예)

강의의 예에서 좌우 확인을 거치면 쓸 수 있는 픽셀은 절반쯤이다. 그래서 스테레오는 정밀한 3차원 모형보다는, 1초에 30번 나오는 거친 3차원 모양에 알맞다.

쓰임: 바닥을 빼면 장애물이 남는다

바퀴 로봇은 바닥 위를 다닌다. 평평한 바닥은 시차 지도에서도 평면이다. 그림 9-7 (나)처럼 아래로 갈수록 고르게 진해진다.

그 평면을 찾아 빼면 남는 것이 모두 장애물이다. 바닥 위로 솟은 상자도, 바닥보다 꺼진 계단 아래도 남는다.

바닥에 깔린 신문지는 색이 달라도 바닥과 같은 평면이라 사라진다. 밟고 지나가도 된다는 뜻이다.

알아 두기. 무늬가 없으면 무늬를 뿌리면 된다. 작은 프로젝터로 빛 무늬를 비추면 흰 벽도 짝 찾기 좋은 곳이 된다(구조광, structured light). 요즘 흔한 깊이 카메라 가운데 많은 수가 눈에 안 보이는 적외선 무늬를 뿌려 이 일을 한다.

영상에서 보기: 16:19 특징 기반과 영역 기반 · 18:56 같은 줄 따라 찾기 · 22:31 좌우 확인 · 27:10 바닥을 빼서 장애물 찾기

9.8 특징점: 저것은 무엇이고 나는 어디인가

스테레오로 부딪히지 않고 다닐 수는 있다. 하지만 "프린터에 가서 출력물을 가져와"는 못 한다. 저것이 무엇이고, 나는 어디인지 알아야 한다.

물건 알아보기는 닭과 달걀 문제다. 영상의 어디에 있는지(분할), 무엇인지(인식), 3차원 자세가 어떤지(자세 추정) 셋 가운데 하나만 알아도 나머지는 쉽다. 셋을 한꺼번에 모르니 어렵다.

통째로 기억하지 말고 조각으로 기억한다

처음에는 두 가지 길이 있었다.

길을 연 생각은 물건을 작은 조각 수천 개로 기억하는 것이다. 얼굴이라면 뺨보다 눈, 코, 입처럼 정보가 많은 곳을 기억한다.

이 조각을 특징점(feature)이라 한다. 반쯤 가려져도 남은 조각으로 알아볼 수 있다.

좋은 특징점은 세 가지를 갖춘다.

  1. 다시 찾을 수 있다. 보는 방향, 거리, 조명이 바뀌어도 같은 자리에서 다시 나온다.
  2. 구별된다. 다른 곳의 특징점과 헷갈리지 않는다.
  3. 많다. 열 개가 아니라 수천 개다.
꼭짓점은 자리가 하나로 정해지고, 두 보기의 특징점을 숫자로 맞춘다
(나)의 원 하나가 특징점 하나이고, 원의 크기와 안의 짧은 선은 그 점이 스스로 정한 크기와 방향이다. 파란 선은 맞는 짝, 주황 점선은 틀린 짝이다. 왼쪽 아래 꼭짓점은 보기 2에서 짝을 못 찾았다.
그림 9-8 · 특징점 고르기와 짝 맞추기

(가)처럼 작은 창을 놓고 조금씩 밀어 본다. 평평한 곳은 어디로 밀어도 창 속 숫자가 같고, 모서리선 위에서는 선을 따라 밀면 같다. 꼭짓점(코너, corner)은 어느 쪽으로 밀어도 달라지니 자리가 딱 한 곳으로 정해진다.

SIFT: 크기와 회전에 흔들리지 않는 특징점

SIFT(scale-invariant feature transform)는 David Lowe가 만든 특징점이다. 강의 당시 사실상 업계 표준이었다. 하는 일은 네 단계다.

  1. 크기까지 찾기: 영상을 점점 흐리게 만들어 가며, 자리와 크기 양쪽에서 두드러지는 곳을 찾는다. 그래서 특징점마다 자기 크기가 있다.
  2. 자리 다듬기: 자리를 픽셀보다 정밀하게 고친다. 모서리선 위의 점처럼 자리가 흐린 것은 버린다.
  3. 방향 정하기: 주변 밝기가 가장 크게 변하는 방향을 그 점의 방향으로 삼는다.
  4. 기술자 만들기: 정한 크기와 방향에 맞춰 주변을 4 × 4칸으로 나눈다. 칸마다 밝기가 변하는 방향 8가지를 센다. 4 × 4 × 8 = 128개 숫자가 그 점의 지문, 기술자(descriptor)다.

보통 영상 한 장에서 안정된 특징점이 2,000개쯤 나온다. 강의의 집 사진은 233 × 189 픽셀의 작은 영상인데도 특징점이 832개였고, 걸러서 536개가 남았다. 작은 물건 하나를 알아보는 데는 3~6개만 맞으면 된다.

잠깐, 생각해 보기. 그림 9-8 (나)의 보기 2는 돌아가고 작아졌다. 그런데도 SIFT 기술자는 왜 거의 같은 숫자가 나올까?

특징점마다 크기와 방향을 스스로 정하고, 그 크기와 방향에 맞춘 창에서 숫자를 재기 때문이다. 자를 물건에 붙여서 재는 셈이다. 2장에서 좌표계를 물체에 붙이면, 물체가 움직여도 그 좌표계로 잰 숫자는 그대로인 것과 같은 생각이다.

짝 맞추기: 가장 가까운 기술자

두 영상의 특징점을 맞출 때는 기술자 128개 숫자를 점 하나의 좌표로 보고 거리를 잰다. 가장 가까운 것이 짝이다. 강의에 따르면 맞는 짝과 틀린 짝의 거리는 보통 두 배쯤 차이가 나서 꽤 믿을 만하다.

수학 더하기: 숫자가 많은 점 사이의 거리. 평면의 두 점 사이 거리는 √((x₁ − x₂)² + (y₁ − y₂)²)다. 숫자가 128개여도 똑같이 칸마다 빼서 제곱하고, 모두 더해 제곱근을 씌운다. 예를 들어 (3, 8, 1, 0)과 (2, 7, 2, 0) 사이는 √(1 + 1 + 1 + 0) = √3 ≈ 1.73이다.

Lowe는 한 가지를 더 확인한다. 가장 가까운 것과 둘째로 가까운 것의 거리 비가 0.8보다 작을 때만 짝으로 받는다. 둘이 비슷하게 가까우면 헷갈리는 점이니 버린다.

그래도 틀린 짝이 섞인다. 그래서 투표(voting)를 한다.

맞는 짝들은 모두 같은 물건 자세(어디에, 얼마나 돌고, 얼마나 크게)를 가리킨다. 같은 자세에 표가 몰린 짝만 남기고 나머지를 버린다. 강의의 방법은 이것을 허프 변환(Hough transform)으로 한다.

물건 대신 세상을 알아보기

세상 전체를 커다란 물건 하나로 볼 수도 있다. 다니며 본 특징점을 지도에 적어 두고, 나중에 다시 보면 내가 어디인지 안다.

카메라 한 대로 다니며 특징점의 3차원 자리까지 구하면 지도도 만든다. 이것을 위치 추정과 지도 만들기(SLAM)라 한다.

강의가 가장 좋아한다는 예는 타이태닉호다. 바닷속 로봇은 자기 위치를 재기 어렵다. 3 km 넘게 지나가며 찍은 사진들을 특징점으로 이어 붙여, 로봇이 지나간 길과 배 전체 모자이크를 함께 구했다.

영상에서 보기: 35:28 물건을 조각으로 기억하기 · 38:35 SIFT의 단계 · 41:11 짝 맞추기와 투표 · 46:24 타이태닉 모자이크

9.9 추적: 예측 → 찾기 → 맞추기 → 고치기

물건을 집으려면 물건과 손이 움직이는 동안에도 어디 있는지 계속 알아야 한다. 영상이 들어올 때마다 목표의 자리를 이어서 알아내는 일이 추적(tracking)이다.

처음(시각 0)에 무엇을 따라갈지 정한다. 그 뒤 시각 t마다 목표의 설정(configuration)을 구한다. 설정을 몇 개의 숫자로 할지는 고르기 나름이다.

강의의 생각은 순기구학과 닮았다. 설정 p를 알면, 처음 본보기 영상이 지금 어떻게 보일지 계산할 수 있다. 관절 각도를 알면 손끝 자리를 계산하는 것과 같다.

추적은 영상마다 네 걸음을 되풀이하는 고리다
파란 점선 네모는 짐작한 자리, 주황 네모는 고친 자리다. ③의 "세운 조각"은 짐작한 설정으로 되돌려 바로 세운 영상 조각이다.
그림 9-9 · 추적 고리 (강의의 얼굴 추적을 단순하게 그림)
  1. 예측: 지난 설정과 움직임으로 이번 설정을 짐작한다.
  2. 찾기: 짐작한 자리 근처만 잘라 낸다. 그 조각을 짐작한 설정으로 되돌려(돌리고 키워) 본보기와 같은 모양으로 세운다.
  3. 맞추기: 세운 조각과 본보기를 픽셀마다 빼서 어긋남 e를 잰다.
  4. 고치기: 어긋남을 설정의 작은 변화 Δp로 바꿔 더한다. 다음 영상이 오면 다시 ①로 간다.

잘 따라가고 있다면 ②에서 세운 조각은 늘 본보기와 똑같아 보인다. 그래서 강의는 추적을 영상 안정화(stabilization)라고 부른다. 얼굴이 움직여도 화면 가운데 작은 창 속 얼굴은 가만히 있다.

어긋남을 설정으로: 영상 자코비안

④에서 어긋남을 설정 변화로 바꿀 때 자코비안을 쓴다. 6~7장의 자코비안은 관절이 조금 돌 때 손끝이 얼마나 움직이는지였다. 여기서는 설정이 조금 바뀔 때 픽셀 밝기가 얼마나 바뀌는지다.

픽셀이 10,000개, 설정이 6개면 M은 10,000 × 6 행렬이다. 행이 열보다 훨씬 많은 길쭉한 행렬이라 역행렬이 없다. 그래서 유사역행렬로 가장 그럴듯한 Δp를 구한다.

수학 더하기: 유사역행렬. 식이 미지수보다 많으면 모든 식을 정확히 맞추는 답이 보통 없다. 그때는 어긋남의 제곱 합이 가장 작은 답을 고른다(최소제곱). 그 답이 (MᵀM)⁻¹Mᵀ를 곱한 것이다. MᵀM은 6 × 6이라 역행렬을 구할 수 있다.

손끝 오차에 자코비안의 역을 곱해 관절을 움직이는 것과 같은 꼴이다. 강의도 "로봇을 자코비안으로 제어하는 것과 다를 바 없다"고 말한다. 강의는 M의 열을 영상으로 보여 주는데, 얼굴의 경우 코가 얼굴이 어느 쪽을 보는지 알려 준다.

강의의 얼굴 추적 시연

강의 예제다. Hager가 자기 얼굴을 따라가는 영상을 보여 주며 모형을 하나씩 더한다.

수술 로봇의 도구 두 개를 숫자 18개로 따라가는 예도 나온다.

영상에서 보기: 51:35 추적이란 무엇인가 · 54:10 추적 = 안정화 · 56:14 영상 자코비안 · 59:50 얼굴 추적 시연

9.10 시각 서보: 영상에서 고리 닫기

이제 마지막 조각이다. 카메라 두 대와 팔이 있다.

손가락 끝을 휴대폰의 한 점에 대려면 무엇을 계산해야 할까? 강의는 세 가지 답을 차례로 본다.

방법 하는 일 약한 곳
① 보고 나서 가기 스테레오로 목표의 3차원 자리를 재고, 0TC로 받침 기준으로 바꿔 팔을 보낸다 가는 동안 눈을 감는다. 팔의 기구학이 틀리면 엉뚱한 곳에 간다
② 3차원에서 고리 닫기 손끝과 목표를 둘 다 계속 재고, 3차원 자리의 차이를 0으로 만든다 팔의 오차는 고리가 잡는다. 보정이 틀리면 복원한 자리 자체가 틀린다
③ 영상에서 고리 닫기 두 영상에서 손끝과 목표가 찍힌 자리의 차이를 0으로 만든다 보정이 틀려도 끝자리가 맞는다

③을 시각 서보(visual servoing)라 한다. 정확히는 영상 기반 시각 서보다.

왜 보정이 필요 없나

손끝과 목표가 정말 닿아 있으면, 어느 카메라로 찍든 두 점은 같은 픽셀에 찍힌다. 거꾸로, 자리가 다른 두 카메라에서 모두 같은 픽셀에 찍히면 실제로 닿아 있다. 이 판단 어디에도 보정 숫자가 없다.

그래서 오차를 영상 속 두 점의 자리 차이 e로 정의한다. 관절이 돌 때 e가 얼마나 바뀌는지가 또 하나의 자코비안 Je다. 오차를 줄이는 쪽으로 관절을 돌린다.

둘을 합치면 ė = −k e다. 오차가 매 순간 자기 크기에 비례해 줄어든다. Je가 정사각이 아니면 9.9절의 유사역행렬을 쓴다.

보정이 틀리면 Je가 조금 틀린다. 그러면 가는 길이 조금 비뚤어지고 느려질 뿐이다. 고리가 안정하기만 하면 오차가 0이 되는 끝자리는 그대로다.

잠깐, 생각해 보기. 9.4절에서 0TC가 1° 틀리면 50 cm 앞에서 약 0.9 cm 어긋났다. ③에서도 이 0.9 cm가 남을까?

남지 않는다. ③의 오차는 영상 속 손끝과 목표의 차이라서 0TC를 거치지 않는다. 보정 오차는 Je를 조금 틀리게 할 뿐이고, 고리는 오차가 0이 될 때까지 계속 줄인다.

Hager의 이야기가 재미있다.

카메라 두 대를 "한 자쯤 떨어졌겠지" 하고 책상에 놓고, 보정 없이 짐작한 숫자로 처음 돌렸다. 그런데 0.5 mm 안으로 맞았다. 플로피 디스크를 드라이브에 넣는 시연도 나온다.

사람도 이렇게 한다. 시야를 비트는 안경을 쓰고 한동안 지나면 두 손가락을 다시 잘 맞댄다. 팔의 기구학을 계산하고 눈을 감은 채 가는 것이 아니라, 보면서 고리를 닫는다.

남은 문제

강의는 "조각은 다 있는데 왜 아직 로봇이 야구를 하지 않나"라는 질문으로 끝난다. 남은 숙제는 이렇다.

계산에 드는 돈은 빠르게 줄었다. Hager의 첫 추적 프로그램은 2만 달러짜리 컴퓨터에서 1초에 10번 돌았다. 1헤르츠에 2천 달러인 셈이다.

십여 년 뒤 같은 프로그램은 1헤르츠에 20센트로 돌았다.

이것으로 9강이 끝난다. 다음 10강에서는 팔이 출발점에서 도착점까지 부드럽게 가는 길(궤적)을 만든다.

영상에서 보기: 1:03:58 카메라와 팔 사이에 무엇을 넣나 · 1:06:37 영상에서 고리 닫기 · 1:09:11 보정 없이 0.5 mm · 1:10:43 남은 문제들

9.11 연습문제

따로 말이 없으면 카메라는 f = 500 픽셀, 영상 640 × 480, 중심 (320, 240)이다. 답은 문제 8개 아래에 있다.

  1. 카메라 기준으로 CP = (6, −3, 60) cm인 점은 영상의 어느 픽셀(열, 행)에 찍히나?
  2. 높이 10 cm인 컵이 영상에서 40픽셀 높이로 찍혔다. 컵까지 거리는? 같은 컵이 80픽셀로 찍히면?
  3. 기선 b = 10 cm인 나란한 두 카메라다. 한 점이 왼쪽 영상 열 412, 오른쪽 영상 열 362에 찍혔고, 두 영상 모두 행 290이다. 왼쪽 카메라 기준 (X, Y, Z)를 구하라.
  4. 문제 3의 카메라로 시차를 1픽셀 틀렸다.
    1. Z = 100 cm와 300 cm에서 깊이 오차를 9.6절의 어림식으로 구하라.
    2. 300 cm에서 오차를 2 cm로 줄이려면 기선이 얼마여야 하나?
  5. 9.4절 예제의 카메라(0TC)가 책상 위 지우개의 윗면을 픽셀 (300, 200)에서 봤다. 깊이는 Z = 55 cm다. 받침 기준 위치 0P는?
  6. 왼쪽 영상 열 5~7의 창이 (20, 80, 140)이다. 오른쪽 영상 같은 줄의 열 2~7은 차례로 50, 22, 78, 143, 60, 30이다.
    1. d = 0, 1, 2, 3에서 SAD를 구하고 시차를 정하라. (d칸 민 창은 열 5−d ~ 7−d다.)
    2. f b = 3000 픽셀·cm라면 깊이는? 시차를 1픽셀 틀리면 깊이가 어디까지 바뀌나?
  7. 장난감 기술자(숫자 4개)로 짝을 맞춘다. 오른쪽 영상의 후보는 b₁ = (2, 7, 2, 0), b₂ = (8, 1, 0, 3), b₃ = (6, 6, 0, 4)다. 왼쪽 특징점 a = (3, 8, 1, 0)과 c = (5, 5, 4, 3)의 짝을 각각 찾아라(거리 비 0.8 미만만 받는다).
  8. 개념 문제.
    1. 추적의 설정 숫자는 몇 개인가? 영상 속 둥근 공, 위에서 본 책상 위 휴대폰(밀기와 돌리기), 공간 속 단단한 물체.
    2. 손끝과 목표를 영상에서 맞추는 시각 서보(9.10절 ③)가 카메라 보정 없이도 정확하게 닿는 까닭을 두 문장으로 설명하라.

답

답 1. u = 500 × 6 ÷ 60 = 50, v = 500 × (−3) ÷ 60 = −25다. 중심을 더하면 (370, 215)다. Y가 음수라 중심보다 위에 찍힌다.

답 2. h = f H / Z를 Z로 풀면 Z = f H / h = 500 × 10 ÷ 40 = 125 cm다. 80픽셀이면 62.5 cm다. 상이 두 배로 커졌으니 거리는 절반이다.

답 3. 시차 d = 412 − 362 = 50픽셀이다. Z = f b / d = 500 × 10 ÷ 50 = 100 cm다. 왼쪽 영상에서 u = 412 − 320 = 92, v = 290 − 240 = 50이다.

되돌리면 X = 92 × 100 ÷ 500 = 18.4, Y = 50 × 100 ÷ 500 = 10이다. 답은 (18.4, 10, 100) cm다.

답 4.

  1. f b = 5000이므로 ΔZ ≈ 100² ÷ 5000 = 2 cm(100 cm에서), 300² ÷ 5000 = 18 cm(300 cm에서)다. 거리가 세 배면 오차는 아홉 배다. 정확히 계산하면 300 cm에서 283.0 ~ 319.1 cm다.
  2. ΔZ = Z² / (f b)를 b로 풀면 b = Z² / (f ΔZ) = 300² ÷ (500 × 2) = 90 cm다. 카메라 두 대를 90 cm나 벌려야 한다.

답 5. u = 300 − 320 = −20, v = 200 − 240 = −40이다. 되돌리면 X = −20 × 55 ÷ 500 = −2.2, Y = −40 × 55 ÷ 500 = −4.4다. CP = (−2.2, −4.4, 55)를 9.4절처럼 바꾼다.

지우개 윗면은 받침에서 (7.8, 9.4) cm, 높이 5 cm에 있다.

답 6. 창을 하나씩 빼서 더한다.

d 오른쪽 창 SAD
0 (143, 60, 30) 123 + 20 + 110 = 253
1 (78, 143, 60) 58 + 63 + 80 = 201
2 (22, 78, 143) 2 + 2 + 3 = 7
3 (50, 22, 78) 30 + 58 + 62 = 150

가장 작은 d = 2가 시차다. 깊이는 Z = 3000 ÷ 2 = 1500 cm(15 m)다.

시차를 1픽셀 틀려 1로 재면 30 m, 3으로 재면 10 m다. 시차가 작으면 1픽셀 차이로 깊이가 10 m에서 30 m까지 흔들린다. 이런 먼 점의 깊이는 믿기 어렵다.

답 7. 칸마다 빼서 제곱해 더하고 제곱근을 씌운다.

답 8.

  1. 둥근 공은 방향이 없어 자리 2개다. 책상 위 휴대폰은 자리 2개와 회전 1개로 3개다. 공간 속 단단한 물체는 이동 3 + 회전 3으로 6개다.
  2. 오차가 영상 속 두 점의 자리 차이라서, 오차를 계산하는 데 보정 숫자가 들어가지 않는다. 오차가 0이면 두 카메라 모두에서 두 점이 겹친 것이고 실제로 닿은 것이므로, 보정은 가는 길만 바꿀 뿐 끝자리는 바꾸지 못한다.

파이썬으로 확인

파이썬(numpy)이 있으면 아래를 붙여 넣어 돌려 본다. 주석의 값이 그대로 나온다.

import numpy as np

np.set_printoptions(precision=2, suppress=True)
f, cx, cy = 500.0, 320.0, 240.0   # 초점 거리, 영상 중심 (픽셀)

def project(P):
    """카메라 기준 점 (X, Y, Z) -> 픽셀 (열, 행)"""
    X, Y, Z = P
    return np.array([f * X / Z + cx, f * Y / Z + cy])

def back_project(px, Z):
    """픽셀 (열, 행)과 깊이 Z -> 카메라 기준 점"""
    u, v = px[0] - cx, px[1] - cy
    return np.array([u * Z / f, v * Z / f, Z])

def triangulate(col_L, col_R, row, b):
    """나란한 두 카메라: 시차, 왼쪽 카메라 기준 점"""
    d = col_L - col_R
    return d, back_project([col_L, row], f * b / d)

def depth_range(Z, b, err=1):
    """시차를 err 픽셀 틀렸을 때의 깊이 범위"""
    d = f * b / Z
    return [round(f * b / (d + s * err), 1) for s in (1, -1)]

def sad(win_L, win_R):
    return int(np.abs(np.subtract(win_L, win_R)).sum())

# 9.3 한 선 위의 두 점은 같은 픽셀에
print(project([5, 2, 50]), project([10, 4, 100]))
# [370. 260.] [370. 260.]

# 9.4 카메라 -> 팔 받침
T_0C = np.array([[1, 0, 0, 10], [0, -1, 0, 5],
                 [0, 0, -1, 60], [0, 0, 0, 1]], float)
P_C = back_project([360, 270], 50)
print(P_C, T_0C @ np.append(P_C, 1))
# [ 4.  3. 50.] [14.  2. 10.  1.]
print(round(50 * np.radians(1), 2))   # 0.87 (cm, 1° 오차)

# 9.6 표: 시차와 1픽셀 오차 (b = 6 cm)
for Z in (50, 1000):
    print(Z, f * 6 / Z, depth_range(Z, 6))
# 50 60.0 [49.2, 50.8]
# 1000 3.0 [750.0, 1500.0]

# 9.7 계산량
print(640 * 480 * 100 * 11 * 11)      # 3717120000

# 문제 1, 2, 3
print(project([6, -3, 60]))           # [370. 215.]
print(f * 10 / 40, f * 10 / 80)       # 125.0 62.5
d, P = triangulate(412, 362, 290, 10)
print(d, P)                           # 50 [ 18.4  10.  100. ]

# 문제 4
for Z in (100, 300):
    print(Z, Z**2 / (f * 10), depth_range(Z, 10))
# 100 2.0 [98.0, 102.0]
# 300 18.0 [283.0, 319.1]
print(300**2 / (f * 2))               # 90.0

# 문제 5
P_C = back_project([300, 200], 55)
print(P_C, T_0C @ np.append(P_C, 1))
# [-2.2 -4.4 55. ] [7.8 9.4 5.  1. ]

# 문제 6 (오른쪽 줄의 열 번호: 값)
row_R = {2: 50, 3: 22, 4: 78, 5: 143, 6: 60, 7: 30}
win_L = [20, 80, 140]
print([sad(win_L, [row_R[c - d] for c in (5, 6, 7)])
       for d in range(4)])            # [253, 201, 7, 150]
print(3000 / 2, 3000 / 1, 3000 / 3)   # 1500.0 3000.0 1000.0

# 문제 7: 가장 가까운 후보와 거리 비
B = np.array([[2, 7, 2, 0], [8, 1, 0, 3], [6, 6, 0, 4]])
for q in ([3, 8, 1, 0], [5, 5, 4, 3]):
    dist = np.linalg.norm(B - q, axis=1)
    i = np.argsort(dist)
    print(dist, i[0] + 1, round(dist[i[0]] / dist[i[1]], 2))
# [1.73 9.17 5.48] 1 0.32
# [5.1  6.4  4.36] 3 0.85

더 보기

이 강의에는 숙제가 없다. 스탠퍼드 강의 페이지의 유인물 가운데 "Robots and Vision"이 Hager의 강의 슬라이드다. 9.5~9.7절의 스테레오 식, SIFT의 단계, 추적 고리, 시각 서보의 세 방법이 그대로 들어 있다.

다 읽었으면 9강 영상을 한 번 본다. 강의 영상이 슬라이드를 많이 보여 주니, 유인물을 옆에 펴 놓고 보면 좋다.

← 8장. 속도는 J로, 힘은 Jᵀ로: 특이점, 손끝 속도 제어, 정적 힘10장. 부드러운 길: 궤적 만들기 →