출처: 누구나 하나쯤은 있는 블로그 http://anybody-has-a-blog.tistory.com/142
키넥트 카메라 캘리브레이션 하기 (Kinect Camera Calibration)
키넥트의 카메라는 컬러 영상을 획득하는 RGB 카메라와 Depth 영상을 만들어내는 IR 카메라로 구성되어 있다. Kinect가 IR 카메라로 깊이 (Depth) 영상을 얻어내는 방법이 Time of Flight 방식인 걸로 아는 분들이 많은데, 실제로는 패턴 (Structured light)을 대상물에 투영하고 스테레오 매칭 (Stereo matching)을 통해 깊이값을 계산하는 것으로 보인다. 일단 IR 영상을 보면 키넥트의 emitter 에서 수많은 점들을 환경에 투영하는 것을 볼 수 있는데, 깊이 값이 나오는 경우는 투영된 두 인접한 점 패턴들이 서로 구분이 가능한 거리만큼 대상물이 카메라와 멀리 떨어져 있어야 하기 때문이다. 만일 ToF 방식이라면 가까워도 깊이값이 나와야 할 것인데, 실제로는 그렇지 않다.
영상을 비교해보면 RGB 카메라의 화각 (FOV)이 IR 카메라의 화각보다 더 넓으며 컬러 영상이 깊이 영상과 동일한 장면을 찍고 있는 것이 아니라는 것을 알 수 있다. 아래 사진은 컬러 영상과 깊이 영상을 겹쳐놓은 것인데, 손가락의 위치나 팔 등이 전혀 일치하지 않는 것을 볼 수 있다.
깊이 영상과 컬러 영상 사이에는 1:1 픽셀 매칭이 되지 않는다.
이미 인터넷의 수 많은 동영상들에서 볼 수 있듯이, 키넥트를 제스쳐 인식 등의 상호작용 분야에 사용하려고만 한다면 depth 영상을 사용하는 데 별로 큰 문제는 없다. 그러나 컬러 영상과 연동해서 사용하려고 한다면 좀 문제가 된다. 예를 들어, 컬러 영상에서 특정 위치의 3D 정보를 얻어오고 싶은 경우, 동일한 위치에 있는 깊이 영상의 픽셀이 가진 값을 읽어서 사용하는 것은 옳지않은 방법이다. 왜냐하면 두 카메라의 다른 특성으로 인해 깊이 영상과 컬러 영상 간에 1:1로 픽셀이 대응하는 것이 아니기 때문이다. 따라서 컬러 영상에서 특정 픽셀이 갖는 깊이 값을 알아내기 위해서는 두 카메라 사이의 캘리브레이션이 반드시 필요하다.
카메라 캘리브레이션은 잘 알려져 있는 체스 보드 패턴을 이용해 쉽게 할 수 있다.
먼저 체스 보드 패턴을 프린트해서 출력하고 IR 카메라와 RGB 카메라로 패턴의 영상을 획득한다. 이 때 두 카메라가 동일한 장면을 찍도록 하는 것이 나중에 확인을 하는 데 수월하다. libfreenect 라이브러리는 IR 영상과 컬러 영상을 동시에 얻을 수는 없으므로, 패턴을 움직여서 고정시킨 후 각각 카메라에서 따로 영상을 저장해 주어야 한다. IR 카메라로 영상을 획득할 때는 패턴을 투영하는 Emitter 부분을 가려주어야 패턴 영상을 제대로 얻을 수 있으며, IR 카메라는 형광등 조명 아래서는 너무 어둡게 영상이 찍히므로 백열등을 하나 준비해서 켜주는 것이 좋다. 또한 IR 영상의 해상도는 640x488 라는 것에 주의하자.
체스보드 패턴을 이용해 찍은 IR 영상과 컬러 영상
IR Emitter 를 가리지 않으면 점 패턴으로 인해 제대로된 영상을 얻을 수 없다
영상 획득이 끝나면 각각의 카메라에 대한 캘리브레이션을 수행한다. 캘리브레이션은 OpenCV 함수들을 이용해 직접 프로그램을 만들어서 하거나, MATLAB Toolbox 나 GML Camera calibration 프로그램을 써서 할 수 있다. 캘리브레이션이 끝나면 각 카메라의 내부 파라미터 행렬 K_ir과 K_rgb 및 두 카메라의 distortion parameters 를 얻는다.
GML Camera Calibration Toolbox
각 카메라의 특성 외에도 두 카메라 사이의 관계를 알아내야 한다. 두 카메라 사이의 관계는 3x3 Rotation 행렬과 3x1 Translation 벡터로 표현되는데, 이 관계는 앞서 획득한 두 카메라의 영상들로부터 알아낼 수 있다. 두 카메라 사이가 대략 2.5cm 정도 떨어져 있고 거의 평행 하므로 Rotation 행렬은 거의 Identity 행렬에 가깝게 나오게 되며, Translation 벡터도 주로 X축 방향으로만 값을 가지게 된다. OpenCV를 사용하는 경우 cvStereoCalibrate 함수를 이용하면 구할 수 있다.
두 카메라의 특성 및 두 카메라 사이의 관계를 알았으니, 이제 컬러 영상의 깊이 값을 얻는 과정을 알아보자. 컬러 영상의 깊이 값을 얻는 과정은 의외로 단순하다.
우리는 키넥트가 제공하는 깊이 영상으로부터 깊이 영상의 한 픽셀이 3D 공간상에서 어느 위치에 있는지를 back-projection을 통해 알아낼 수 있다. 이 3D 공간 상의 점은 IR 카메라의 좌표계에 있는데, 이것을 RGB 카메라의 좌표계로 가져오기 위해서 앞서 구한 Rotation 행렬과 Translation 벡터를 사용한다. RGB 카메라의 좌표계로 변환된 점의 Z 값은 해당 점을 RGB 영상으로 다시 투영한 픽셀에 대응하는 깊이 값이 된다.
다시 말하면 아래와 같은 관계다.
For a pixel p_ir in IR image,
P_ir = inv(K) * p_ir
P_rgb = R * P_ir + t
p_rgb = K_rgb * P_rgb
p_rgb의 depth = P_rgb의 Z 값
P_ir : IR 카메라의 좌표계로 표현된 3D 공간의 점
R, t : IR 카메라의 좌표계에서 RGB 카메라의 좌표계로 변환하는 행렬 및 벡터
P_rgb : RGB 카메라의 좌표계로 표현된 3D 공간의 점
p_rgb : P_rgb가 RGB 영상으로 투영된 픽셀.
위에서 투영 전 후에 homogeneous coordinate로 바꾸는 과정은 생략했다. 두 개 이상의 3D 점들이 RGB 영상의 동일한 픽셀로 투영되는 경우도 있는데, 이럴 때는 가까운 것을 취하면 된다.
아래 그림은 위에 설명한 방식으로 계산한 컬러 영상의 깊이 영상이다. 주변에 존재하는 검은 부분은 RGB 카메라와 IR 카메라의 화각 차이로 RGB 카메라가 더 넓은 영역을 보기 때문에 생기게 된다 (그 부분은 깊이 값이 존재하지 않음).
계산된 컬러 영상의 깊이 영상
RGB 카메라에 대해 계산한 깊이 영상과 RGB 영상을 겹쳐서 보면 캘리브레이션 이전과는 다르게 픽셀과 깊이 값이 잘 맞는 것을 볼 수 있다. 깊이값을 컬러영상에 매핑하는 과정을 생각해보면, p_rgb가 갖는 컬러값은 곧 p_ir 이 가져야 하는 컬러값으로 볼 수 있으며, 따라서 깊이 영상에 컬러값을 매핑하는 것도 가능하다.
계산된 깊이 영상과 컬러 영상을 겹쳐놓은 것.
아래 그림은 다른 장면에 대해서 컬러 영상, 깊이 영상, 깊이 값을 컬러 영상으로 매핑한 것, 컬러 값을 깊이 영상에 매핑한 것, 그리고 두 가지를 오버레이한 것을 보여주는 그림이다. 앞서와 마찬가지로 깊이 값이나 컬러가 서로 다른 쪽으로 잘 매핑되는 것을 볼 수 있다.
컬러-깊이 영상간 매핑 결과 (클릭하면 커짐.)
구현 동영상
카메라 보정과 양쪽 영상간의 매핑을 통해 컬러 영상에 대한 깊이 값을 구할 수 있게 되었으니 이제 깊이 값을 컬러 영상과 연동하여 사용할 수 있게 되었다~.