RealSense 카메라 사용(2) - 카메라 연결과 이미지 로드



이전 글(http://sereto.blogspot.kr/2016/11/realsense-1.html)에서 Realsene Camera들을 소개했습니다. 그럼 이번편에서는 그 Hardware를 사용할 방법들에 대해서 알아보겠습니다. 사용하는 방법엔 두 가지가 있습니다. 하나는 Realsense SDK를 사용하는 것 그리고 libRealsense를 사용하는 것입니다. 

SDK에는 유용한 Tool들이 많이 구현되어있어 쉽게 이용가능합니다. 밑에 보는 그림과 같이 Hand tracking, gesture인식과 캘리브레이션, 얼굴인식, 표정인식, 3D scan등 realsense를 사용해서 할 수 있는 많은 것들을 구현해 놓았습니다. 그러나 윈도우에서 밖에 사용할 수 없고 무겁다는 단점이 있습니다. 
SDK's Tools
출처 - https://software.intel.com/en-us/intel-realsense-sdk

그에 반해 libRealsense는 카메라에 접근하고 카메라 셋팅하는 것이 전부이므로 훨씬 가볍고 윈도우뿐만아니라 리눅스나 맥에서도 사용가능합니다. 또한 C++말고도 python이나 java interface가 구현되어있어 활용가능합니다.

이후 글은 SDK를 활용하는 것 보다 C++에서 libRealsense로 카메라에 연결하고 카메라를 셋팅하는 방법을 다루겠습니다.

환경
librealsense는 예제 프로그램들이 잘 작성되어있어 그 것들을 잘 살펴보면 쉽게 사용방법을 익힐 수 있습니다. 그 중에서 cpp-tutorial-2-streams.cpp을 보면 color, depth, IR 이미지를 얻는 방법이 쉽게 구현되어있습니다.

헤더 파일
1
#include <librealsense/rs.hpp>
cs

Context 카메라 접근
1
2
3
4
5
6
7
8
rs::context ctx;
printf("There are %d connected RealSense devices.\n", ctx.get_device_count());
if(ctx.get_device_count() == 0return EXIT_FAILURE;
rs::device * dev = ctx.get_device(0);
printf("\nUsing device 0, an %s\n", dev->get_name());
printf("    Serial number: %s\n", dev->get_serial());
printf("    Firmware version: %s\n", dev->get_firmware_version());
cs
context는 지금 연결된 realsense device들을 관리합니다. 우선 하나의 realsense카메라만 사용하므로 0번 디바이스 하나에만 접근합니다. 

그 디바이스에서 사용할 카메라들을 밑의 코드와 같이 enable_stream을 이용해 설정할 수 있습니다. depth
1
2
3
4
5
6
7
// Configure all streams to run at VGA resolution at 60 frames per second
dev->enable_stream(rs::stream::depth, 640480, rs::format::z16, 60);
dev->enable_stream(rs::stream::color, 640480, rs::format::rgb8, 60);
dev->enable_stream(rs::stream::infrared, 640480, rs::format::y8, 60);
try { dev->enable_stream(rs::stream::infrared2, 640480, rs::format::y8, 60); }
catch(...) { printf("Device does not provide infrared2 stream.\n"); }
dev->start();
cs
depth 카메라인 경우 16비트짜리 unsigned int형을 사용하고 color카메라는 RGB각각 8비트를 사용하는 포멧입니다. IR카메라인 경우는 8비트 짜리 gray 포멧입니다. 또 R200인 경우에만 IR카메라가 두개이므로 try문에 들어가 있습니다.설정이 끝난 후에는 start함수로 작동시킵니다.

연결하고 설정방법등이 매우 직관적이고 간편합니다. 그리고 프레임마다 이미지를 얻는 것도 매우 편합니다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  while(1)
    {
        // Wait for new frame data
        dev->wait_for_frames()
        // depth data
        dev->get_frame_data(rs::stream::depth);
        //color image
        dev->get_frame_data(rs::stream::color);
        //infrared image
        dev->get_frame_data(rs::stream::infrared);
        // second infrared image
        if(dev->is_stream_enabled(rs::stream::infrared2))
        {
            dev->get_frame_data(rs::stream::infrared2);
        }
    }
cs

dev->get_frame_data()를 이용해 각 스트림을 얻어올 수 있습니다. 만약 opencv를 사용해 Mat형태로 메모리에 저장하고 사용하고 싶다면 밑의 코드와 같이 활용할 수 있습니다.
1
2
cv::Mat img(480640, CV_8UC3, (uchar3 *)dev->get_frame_data(rs::stream::color));
cv::Mat depth(480640, CV_16U, (uint16_t*)dev->get_frame_data(rs::stream::depth));
cs

그런데 color카메라와 depth카메라는 물리적으로 다른 곳에 설치되어있어 같은 픽셀 index가 같은 point를 가리키고 있지 않습니다. 그러므로 depth카메라를 color 카메라에 맞추던지, color를 depth카메라에 맞춘 후에 같은 index를 사용해야 하는데요. 그 작업을 카메라 사이의 extrinsic을 곱하는 등 복잡하게 할 필요없이 depth_aligned_to_color 나color_aligned_to_depth를 사용해서 같은 index로 같은 point정보를 얻어 올 수 있습니다.
그러나 카메라의 위치가 달라서 생긴 occlusion 부분의 정보는 얻을 수 없다는 단점이 있습니다. 
1
2
cv::Mat img(480640, CV_8UC3, (uchar3 *)dev->get_frame_data(rs::stream::color));
cv::Mat depth(480640, CV_16U, (uint16_t*)dev->get_frame_data(rs::stream::depth_aligned_to_color));
cs


RealSense 카메라 사용(1) - 카메라 외관과 스펙



인텔 Depth 카메라는 2가지 라인업으로 나오고 있습니다. 용도에 따라 다른데요.
가까운 물체 (F200,SR300)
멀리 있는 물체 (R200)
이렇게 두 가지 용도로 나눠 라인업을 구성했습니다.

여기서 살펴볼 제품은 가까운 물체를 위한 F200과 그 후속 제품인 SR300이 있습니다. 여기서는 SR300만 볼텐데요. 두 개의 외관은 100%똑같고 성능차이가 있습니다. 또 하나는 멀리있는 물체를 위한 R200을 살펴보겠습니다. 우선 SR300 외관을 보면 이렇게 생겼습니다.

SR300
출처 - https://software.intel.com/en-us/articles/introducing-the-intel-realsense-camera-sr300
SR300을 살펴보면 다른 Depth카메라와 같이 IR projector와 IR 카메라, Color카메라를 가지고 있어 depth 정보와 함께 color이미지도 같이 얻을 수 있습니다. 역시나 USB3.0에서만 작동합니다. 처음 홍보 때와 같이 윈도우 10의 얼굴인식용, 스카이프에 중점을 둔 것처럼, 거치할 수 있는 관절과 마이크를 내장하고 있는 것이 특징입니다. 

최적거리는 실내에서 20cm~120cm 입니다. depth는 640x480으로 초당 60프레임 얻을 수 있으며 color는 FHD는 30FPS, HD는 60FPS 성능으로 얻을 수 있습니다. 밑에서 자세한 스펙을 볼 수 있습니다. 

SR300 스펙 출처 - https://software.intel.com/en-us/articles/introducing-the-intel-realsense-camera-sr300

인텔 realsense의 또 다른 라인업인 R200을 살펴볼텐데요. 거치대와 마이크가 없고 카메라로만 구성되어 있어 굉장히 작고 간결한 모습을 볼 수 있습니다.


r200
R200 출처-http://reconstructme.net/qa_faqs/intel-realsense-r200-review/
R200 카메라 배치와 이미지들
출처-https://software.intel.com/en-us/articles/realsense-r200-camera

R200을 살펴보면 다른 카메라와 다르게 IR카메라가 2개 있고 IR projector와 Color카메라가 있습니다. 실제로 두 개의 IR카메라에서 얻어진 이미지를 Stereo vision으로 depth를 구합니다. 여기서 IR Projector는 굴곡이 없는 평면적인 물체를 인식하는데 도움을 주는 역할을 합니다. 

최적거리는 실내에서 50cm~350cm입니다. 그리고 밖에서 10M까지 사용할 수 있다고 합니다. 그러나 조건에 따라 가능하므로 사용하기 전에 intel document를 보시는 것을 추천합니다. 실제 물체 스캔이나 카메라 트래킹에 사용하시는 것이면 이 모델을 사용하시는 것이 좋습니다. 
R200 해상도와 FPS
출처-http://reconstructme.net/qa_faqs/intel-realsense-r200-review/
위의 표를 보면 굵게 처리되어있는 부분이 추천되는 해상도 입니다. 저같은 경우는 둘다 640x480을 사용하고 있습니다. 그리고 주의할 점으로 이 제품은 class 1에 해당하는 레이저를 사용하고 있습니다. 

이 하드웨어를 실제로 이용하려면 드라이버와 SDK를 설치해야합니다. 모두 인텔 공식 홈페이지에서 구할 수 있습니다. 그러나 저는 실제로 SDK를 사용하기보다 library형태로 open해서 배포하고 있는 librealsense를 사용합니다. 사용하기 더 쉽고 리눅스에서도 사용가능한 것이 장점이나 공식적인 intel제품이 아니며 SDK에서 미리 구현해놓은 얼굴인식, 트래킹 등을 사용할 수 없다는 것이 단점입니다. 

reference - https://software.intel.com/en-us/realsense/home

Camera Parameter & Coordinate System Transformation

이전 핀홀 카메라 모델에서 카메라 파라미터를 구하기 위해서 카메라 캘리브레이션이라는 것을 해야한다고 했습니다. 그 카메라 캘리브레이션의 의미와 방법에 대해 조금 더 깊게 살펴볼게요.

들어가기전에 좌표계에 대해서 알아보죠. 일단 필요한 세가지 좌표계를 생각해볼 수 있습니다. 첫번째로 우리가 보는 실세계의 3차원 좌표계를 World coordinate system이라고 합니다. 그렇다면 이 Coordinate system의 기준이 되는 Origin과 서로 직교하는 3차원 축 방향을 마련해야겠죠? 이 것은 정하기 나름입니다만 보통 입력으로 들어오는 첫 프레임의 카메라 위치를 기준으로 정합니다.

그리고 두번째 좌표계는 각 프레임마다 카메라의 중심이 Origin이 되고 각 축을 만드는 Camera coordinate system이 있겠죠. 그럼 위에서 말한 것과 같이 첫 프레임은 카메라좌표계와 실세계 좌표계가 동일합니다. 그러면 두 번째 프레임 부터, 점 $P_c$을 Global coordinate로 옮길 땐 첫 프레임을 찍을 때보다 카메라가 얼만큼 움직였는지 알아내 그 만큼을 역으로 계산하면 됩니다.

마지막으로는 픽셀 좌표계입니다. 즉, 실제 World coordinate에서 Camera coordinate를 거쳐 다시 2차원 pixel로 맵핑 된 후의 좌표계입니다. 향후 SfM이나 SLAM등에서는 2차원 Pixel에서 3차원 World coordinate를 알아내는 과정을 진행함으로써 3D points cloud 혹은 volume을 생성해 냅니다.

이 좌표계 변환에서 유용하게 쓰일 뿐만아니라 이것이 전부 일 수 있는 Camera extrinsic (Rotation, Translation), Camera intrinsic을 살펴 보겠습니다.
Camera Coordinate System (출처 : http://kr.mathworks.com/help/vision/ug/camera-calibration.html)
Extrinsic은 World 좌표계에서 Camera 좌표계로 변환시키는 과정입니다. 이 때 파라미터는 Rotation(회전)과 Translation(평행이동)으로 구성되어 있습니다. 즉, 3D to 3D Rigid Transformation입니다.
$$
P_c = \begin{bmatrix}R_{3 \times 3} & T_{3 \times 1} \\ 0 & 1 \end{bmatrix}P_w
$$
반면 Intrinsic은 3D Camera coordinate에서 2D Pixel coordinate으로 Projective transformation시킨 것 입니다. 이 Transformation은 Pinhole camera model에 의해서 다음과 같은 변환관계가 있습니다.
$$
P'=\begin{bmatrix} \alpha & - \alpha cot \theta & u_o & 0 \\ 0 & \frac{\beta}{sin\theta} & v_o & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix}P_c
$$
여기서 $f$= focal length, $u_0,v_0$= offset , $\alpha, \beta \rightarrow$ non-square pixel, $\theta$=skew angle입니다. skew angle은 요즘 카메라에서 거의 일어나지 않아없다고 봐도 무방합니다.

그러므로 World-coordinate에서 Pixel-coordinate까지 최종 Transform은
$$
P' = MP_w=K\begin{bmatrix} R & T \end{bmatrix}P_w = \begin{bmatrix} \alpha & -\alpha cot\theta & u_o & 0 \\ 0 & \frac{\beta}{ sin\theta } & v_0 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} \begin{bmatrix}R & T \\ 0 & 1 \end{bmatrix}P_w
$$
입니다.

핀홀 카메라 모델

주변 센서 중에 가장 많이 이용되는 것 중 하나가 카메라인 것 같아요. 많은 데이터를 얻을 수 있으면서도 값도 싸고 소형화도 가능해 어디든 잘 활용될 수 있어요. 그리고 사용하기 쉽게 패키징 되어있어 유저 입장에서는 별다른 노력 없이도 익숙하게 사용할 수 있죠. 

그런데 카메라의 개괄적인 모델만 알고 있어도 사진찍는데도 도움이 될 뿐더러 Computer Vision에서 만나는 여러문제를 이해할 수 있습니다. 가장 간단하고 기본적인 Pinhole camera model을 가지고 차근차근 살펴볼게요.

이후 포스팅에 사용되는 대부분의 그림은 Stanford의 Silvio교수가 강의한 CS231A 클래스의 그림을 사용하도록 하겠습니다.

우선 저희가 어떤 object를 필름 혹은 CCD에 담기 위해서는 어떻게 해야할까요? 

위와 같이 찍으려는 Object에서 반사된 ray를 필름에 모두 담으면 각 픽셀마다 object의 여러부분에서 반사된 ray들이 모두 합쳐져서 이미지를 얻을 수 없게 될 겁니다. 그렇다면 벽을 치고 그 벽에 작은 구멍(조리개, Aperture)만 뚫어서 픽셀당 하나의 ray만 닿을수 있게끔 해볼 수 있겠죠? 그림을 보면 다음과 같이 보일겁니다.
이 것이 가장 간단한 Pinhole camera model입니다. 이렇게 대략적으로 3D object의 한 점과 fixel간에 1:1매칭 관계를 만들 수 있고 그 결과로 필름에 object를 매핑할 수 있습니다. 그럼 Aperture의 크기를 변화시켜 볼까요? 크기를 키우면 들어오는 ray가 많아져 1:1매칭이 되질 않습니다. 그러므로 흐릿한 영상이 얻어지겠죠. 

위의 그림은 aperture를 줄여가면서 찍은 사진들 입니다. 확실히 구멍이 작았을때 더 선명한 사진을 얻을 수 있죠. 그럼 계속해서 줄이면 어떻게 될까요? 아마 빛이 충분히 들어오지 못해 어두운 사진을 얻게 될겁니다. 또한 빛의 회절현상으로 더 흐릿한 사진을 얻게 될 수 도 있을 것 같습니다.

그래서 충분한 빛을 모으기 위한 방법으로 렌즈가 사용됩니다. 볼록렌즈로 빛을 모아서 한점에 맞추면 초점이 잘맞아서 선명하게 보입니다. 그러나 그 외는 초점이 맞질 않아 흐릿하게 보여 사진을 찍을 때 out of focus가 일어납니다.
위 그림에서 보면 Object의 점 P보다 멀거나 가까울 땐 초점이 맞지 않아 그 부분에서 흐릿한 영상을 얻게 됩니다. 그런데 디지털 카메라나 스마트폰의 카메라는 밑의 사진 같이 극단적인 경우가 아니면 자동모드에서 모든 지점에 초점이 잘 맞는것을 볼 수 있습니다. 아마 Multi-AF 기술이거나 aperture를 작게 하고 iso를 높이거나 하드웨어 성능이 높아진 만큼 software로 후처리해 밝게 만드는 방법을 사용하지 않을까 합니다. 


그럼 실제 pinhole 카메라 모델에서 3D object가 2D이미지로 어떻게 매핑 되는지 확인해 볼까요?

위의 이미지만 잘 기억하고 있으면 됩니다. 여기서 $f$는 focal length $O$는 camera center입니다. 그래서 이렇게 3차원 점 $P$를 2차원 점 $P'$에 맵핑하는 것을 Projective transformation이라고도 합니다. $z$는 초점(O)부터 P까지의 거리, $f$는 focal length로써 초점부터 이미지 평면과의 거리로 나타낼 수 있습니다. $z:f=x:x'$와 같은 비례식을 이용한다면, 이 관계는
$$P=\begin{bmatrix} x\\ y\\ z \end{bmatrix} \rightarrow P'=\begin{bmatrix}x'\\y' \end{bmatrix}$$
$$x'=f\frac{x}{z},\,  y'=f\frac{y}{z}$$
그런데 이미지 픽셀은 왼쪽 아래꼭지점을 Origin으로 보고 좌표를 계산하기 때문에 결과로써 center값을 더해주어야하죠. 그럼 다시 매핑 관계를 표현하면
$$(x,\,y,\,z) \rightarrow (f\frac{x}{z}+c_x,\, f\frac{y}{z}+c_y)$$
또한 한가지 더 고려해야할 점은 보통 가로 세로 같은 수의 fixel이 있거나 가로 세로 같은 크기의 fixel들로 구성되어 있지 않습니다. 그래서 meter to fixel 표현에서 고려해야하죠.
$$(x,\,y,\,z) \rightarrow (fk\frac{x}{z}+c_x,\, fl\frac{y}{z}+c_y)$$
$k,l$ : pixel/m 이고 $f$: meter입니다. 그러므로 $fk$을 $\alpha$, $fl$을 $\beta$로 간단히 하면 최종적으로
$$(x,\,y,\,z) \rightarrow (\alpha \frac{x}{z}+c_x,\, \beta \frac{y}{z}+c_y)$$
로 나타낼 수 있습니다. 이 $\alpha, \beta$를 구하는것이 intrinsic calibration으로 미리 알고있는 가로세로길이의 체커보드로 구할 수 있는 파라미터입니다.

BundleFusion


BundleFusion논문을 소개하기 앞서, 이 논문은 Computer Vision에 관련되어있지만 지금까지와는 다른 3D vision에 관한 것입니다. 이 분야는 2D에서 주로하던 Recognition문제 보다는 Geometry문제들이 많이 포진되어있어요. 

그래서 같은 비전이지만 Machine Learning을 이용한 지능 시스템 보다는 카메라 모델을 이용한 3D reconstruction이나 Camera location tracking같은 문제들이 더 중점있게 다뤄지는게 다른점입니다.

특히 로봇분야에서 Simultaneous localization and mapping(SLAM)이란 단어로 연구되고 있는 분야이기도합니다. 요즘엔 VR, AR관련되어 더욱 중요해진 기술입니다. 


다시 말하지면, 오늘 소개할 논문은 BundleFusion이에요. 위의 동영상에도 보듯이, 3D view를 만드는데 상당히 빠르고 볼륨이 계속해서 수정되는 것을 볼 수 있죠. 그 만큼 시간이 지나도 Robust하고요. 또한 방 전체를 스캔할 수 있어서 향후 VR, AR사용에도 큰 활용도가 있을 것 같습니다.


전체적인 흐름도입니다. 우선 키넥트같은 쉽게 구할 수 있는 RGB-D센서를 사용해서 Frame당 Depth정보와 RGB정보를 얻고요. 동영상에서 보면 아이패드에 StructureIO를 붙여 사용하는 것을 볼 수 있습니다.

첫번째 단계로 센서로 받은 프레임 사이의 Correspondences를 구하고 Check하는 단계가 있습니다. 그 다음 그 Correspondences를 활용해서 카메라 Pose를 구합니다. 이 프레임웍에서 특이하게 포즈를 찾기위한 Optimization이 두번일어나는데 Local Optimization과 Global Optimization 두 단계로 나눠놓은 Hierarchical 단계로 이루어져 있습니다. 이렇게 하는 이유는 모든 프레임이 들어올 때 마다 Global Optimization으로 모든 프레임간의 회전 및 이동 관계를 계산하는 것보다 파라미터가 줄어들기 때문에 길고 큰 Scenes에서도 빠르게 작동합니다. 그리고 이 때 나온 포즈로 이전에 Correspondence를 다시 Check하는데 사용하고 목표인 볼륨을 만드는 데 사용됩니다. 볼륨을 만들 때 계산된 카메라 Pose에 따라서 물체의 볼륨을 줄이거나 늘리는 계산이 수행됩니다.

논문에서는 단계를 크게 두가지, Pose Alignment와 3D Reconstruction으로 나눠서 설명하고 또 세부적으로 Pose 예측단계에서는 Feature Correspondence Search, Hierarchical Optimization, Defining Energy Optimization, Optimization Strategy로 나눠서 설명하고,
3D Reconstruction부분은 Representation(TSDF), Volume Integration/De-integration, Reconstruction Update로 나눠서 설명합니다.

세세한 방법들은 https://arxiv.org/abs/1604.01093 여기서 확인하실 수 있습니다.

추가 - 얼마전 코드까지 공개해주셨네요.
https://github.com/niessner/BundleFusion

Reference - BundleFusion: Real-time Globally Consistent 3D Reconstruction using On-the-fly Surface Re-integration, Angela DaiMatthias NießnerMichael ZollhöferShahram IzadiChristian Theobalt