Skip to content

Latest commit

 

History

History
341 lines (203 loc) · 48.3 KB

ch9.md

File metadata and controls

341 lines (203 loc) · 48.3 KB

carrot


Preface

이 문서는 중국어 원서인 “입문 Visual SLAM 이론에서 연습까지 14 강(视觉SLAM十四讲 从理论到实践)” 책의 원저자로부터 한글 번역 허가를 받고 구글 번역기를 이용하여 작성된 문서입니다. 본 문서는 아래의 Contribution을 특징으로 합니다.

  • 중국어 전공 서적을 구글 번역기를 이용해 한글로 초벌 번역했습니다.
  • 초벌 번역 후 매끄럽지 않은 문장은 문맥에 맞게 수정되었습니다.
  • 문서 내용 중 참고할만한 웹문서를 코멘트로 추가했습니다.
  • SLAM 연구에서 주로 사용되는 용어는 한글로 번역된 용어보다 주로 사용되는 영어로 된 용어 그대로 표시하였습니다.

그럼에도 불구하고 부정확하거나 매끄럽지 않은 부분이 있을수 있습니다. 그런 부분은 코멘트로 제안해주시면 반영하도록 노력하겠습니다. 또한 읽으시다가 잘 이해가 가지 않는 부분도 코멘트로 질문해주시면 답변해드리도록 하겠습니다.

번역 참가자:

  • 신동원 (광주과학기술원 박사과정)
  • 김선호 (VIRNECT 선임연구원)
  • 조원재 (일본국립농업기술혁신공학센터 연구원)
  • 장형기 (Imperial College London 석사)
  • 박준영 (광주과학기술원 석사과정)

2018년 10월 1일 신동원 드림


제 9장: 실습: Frontend 디자인


주요 목표

  1. Visual odometry frontend를 실제로 설계하십시오
  2. SLAM 소프트웨어 프레임워크의 구축 방법을 이해하십시오.
  3. Frontend 디자인에서 쉽게 발생하는 문제와 이를 수정하는 방법을 이해하십시오.

챕터 소개

우리는 처음 두 강의에서 배운 지식을 사용하여 Visual odometry 프로그램을 직접 작성할 것 입니다. 독자분들께선 카메라의 궤도와 랜드마크 지점 관리하고, 이를 통해 소프트웨어 프레임 워크 구성 방법을 경험할 것입니다. 이러한 과정에서 우리는 많은 문제를 겪을 것입니다 : 카메라가 너무 빨리 움직이거나, 이미지가 흐리거나, 특징 점 불일치등의 문제들은 알고리즘이 실패하게 할 것 입니다. 프로그램을 원활하게 운영하려면 위의 상황을 처리해야합니다. 이번 그러면 프로젝트 구현에 대한 많은 유용한 토론이 이루어집니다. 또, 이 강의에선 프로젝트를 실용화 하는데에 유용한 관점들을 소개합니다.


9.1 VO 프레임워크 구축

벽돌과 시멘트의 원리를 이해한다고 멋있는 궁전을 지을 수 있는 것은 아닙니다. 저자가 좋아하는 게임인 ‘마인크래프트’의 세계 속 플레이어들은 색과 성질이 다른 여러 종류의 정육면체들을 사용할 수 있습니다. 이 정육면체들을 사용하는 법은 굉장히 쉬운데, 단지 빈 공간에 정육면체들을 가져다 놓기만 하면 됩니다. 하지만 실제로 이 정육면체들로 무언가를 만들어 보려고 하면, 초심자들은 성냥갑처럼 생긴 집 정도밖에 만들지 못 하는데에 비해, 경험이 많고 창작적인 플레이어들은 집, 정원, 테라스, 파빌리온, 심지어 도시 등을 만들곤 합니다 (그림 9-1)®.

image 9-1 그림 9-1 시작은 작을 수 있으나, 점점 복잡한 구조를 구현함으로써 뛰어난 작품을 만듭니다.

SLAM 프로그램을 구현할때는, 우리는 우리가 이해하는 공학적 구현법과 알고리즘들이, 프로그램의 전체적인 작성법 만큼이나 중요하다고 생각하곤 합니다. 하지만, 알고리즘을 이해한다는 것은 마치 ‘마인크래프트’ 게임 속 각각의 정육면체들의 성질을 잘 이해한다는 것과 같으며, 다만 그 정육면체들을 이해하는 것만으론 멋진 건물을 지을 수 없는것처럼, 알고리즘을 이해하는 것 만으로는 멋진 프로그램을 작성할 수 있는 것은 아닙니다. 프로그램을 잘 짜려면 많은 시도와 경험이 필요하며, 저자는 이 과정이 복잡하고 어려울 수 있어도 독자들이 직접 도전해보며 부딪치는 것을 권장합니다. “마인크래프트” 세계에서도 멋진 건물을 짓기 위해서는 기둥, 벽, 지붕의 구조, 벽 무늬 및 기하학적 각도 계산법 등을 익혀야 하며, 이는 정육면체들의 성질을 배우는 과정과는 큰 차이가 있습니다.

SLAM을 구현하는 데에도 같은 방법으로 접근해야 합니다. 잘 짜여진 SLAM 프로그램은 여러가지 공학적 설계와 테크닉을 사용하며, 매 구현 단계마다 다음 단계에서 어떤 작업을 할지 설명합니다. 원칙적으로 사람마다 구현한 SLAM 프로그램이 조금씩 다를 수 있는데, 우리는 이때 ‘어떤 구현 방식이 반드시 가장 좋다’ 라고 말할 수 없는 때가 많습니다. 그러나, 우리는 보통 ‘지도 점(Mapping points) 어떻게 관리를 하는가’, ‘Mismatch를 어떻게 처리하는가’, ‘키 프레임을 어떻게 선택하는가’ 등의 공통적인 문제에 봉착합니다. 저자는 이번 섹션을 통해 독자들이 이러한 문제들을 해결하는 데에 직관적인 느낌을 갖길 바랍니다.

이 장에서는 실용적인 SLAM의 프레임 워크를 짜는 과정에 대해 알아보려 합니다. 건물을 설계할 때에도 기둥 사이의 거리, 문면의 넓이 등 사소해 보이지만 중요한 작업이 많습니다. SLAM의 프레임 워크를 짜는 것 마찬가지로 중요한 작업이 많아 굉장히 복잡하고, 필수적으로 설명해야하는 핵심적인 부분만을 짚어도 많은 분량을 차지하게 되어 이 책을 지나치게 복잡하게 만들 것입니다. 그러나, 완성된 프레임 워크는 복잡하겠지만 우리는 우리가 프로그램을 짜는 과정에서 완성에 이르는 데에 ‘단순한 단계에서 복잡해지는 단계’의 과정은 상세히 공부해 볼 가치가 있다고 생각합니다. 우리는 간단한 데이터 구조에 대한 작업 부터 시작해서, 먼저 간단한 Visual odometry를 만든 후에 차차 추가적인 기능을 구현합니다. 우리는 이 섹션에서 이 간단한 단계에서 복잡한 단계로 넘어가는 이 과정을 소개하고 싶습니다.

본 강의의 코드는 slambook/project의 개발 과정이 진전됨에 따라 일부 축소 될 예정이라, 내용이 달라질 수 있습니다. 그래서 우리는 중간 코드도 목록에 저장하고, 그 번호로 이름 붙여서 독자가 수시로 보고 따라할 수 있도록 하겠습니다.

9.1.1 프로그램 프레임워크 결정

앞서 소개 된 강의의 내용에 따르면 VO는 단안, 쌍안, RGB-D의 세 가지 범주로 구분됩니다. 단안 VO 방식은 타 방식에 비해 복잡하기도 하고, RGB-D 방식이 가장 단순하며 초기화 문제도 없기 때문에, 우리는 RGB-D 방식으로 시작하기로 합니다. 실험 과정을 쉽게 하기 위해 실제 RGB-D 카메라 장비를 사용하는 대신, 공개된 데이터 세트를 사용합니다.

먼저, 리눅스 프로그램이 어떻게 구성되어 있는지 살펴보겠습니다. 소규모 라이브러리를 작성할 때 대개 소스 코드, 헤더 파일, 문서, 테스트 데이터, 구성 파일, 로그 등을 저장하는 폴더를 만들어 체계적으로 만듭니다. 참조해야할 라이브러리가 많으면 테스트를 위해 코드를 별도의 작은 모듈로 나눕니다. 독자는 OpenCV 또는 g2o와 같은 대형 및 중형 라이브러리가 어떻게 구성되는지 볼 수 있습니다. 예를 들어 OpenCV에는 core, imgproc, features2d와 같은 모듈이 있으며, 각 모듈은 각기 다른 작업을 담당합니다. g2o에는 core, solvers, types와 같은 여러 모듈이 있습니다. 그러나 SLAM 라이브러리라는 작은 라이브러리를 작성하면, 이 안에서 이 모든 모듈을 함께 사용할 수 있습니다.

이제 우리가 작성하려고하는 SLAM 라이브러리는 작은 라이브러리입니다. 이 책의 목적은 독자가 이 책에서 사용 된 다양한 알고리즘을 통합하고 자체 SLAM 프로그램을 작성하도록 돕는 것입니다. 우선, 프로젝트 디렉토리를 선택하고 그 아래에 폴더를 만들어 코드 파일을 구성합니다.

  1. bin은 실행 가능 바이너리 파일을 저장하는 데 사용됩니다.
  2. include / myslam은 주로 .h 파일 인 SLAM 모듈의 헤더 파일을 저장합니다. 헤더파일을 include 디렉토리에 저장하면, 자신의 헤더 파일을 포함하고 참조 할 때 "myslam / xxx.h"를 작성해야하기 때문에 다른 라이브러리와 혼동하는 오류를 피할 수 있습니다.
  3. src에는 소스 코드 파일, 주로 .cpp 파일을 저장합니다.
  4. test에는 .cpp 형식의 테스트 파일을 저장합니다.
  5. lib에는 컴파일 된 라이브러리 파일을 저장합니다.
  6. config에는 구성 파일을 저장합니다.
  7. cmake_modules에은 g2o와 같은 라이브러리로 작업 할 때 사용되는 써드 파티 라이브러리 용 cmake 파일을 저장합니다.

위 설명은 그림 9-2와 같은 디렉토리 구조입니다. 이 방법은 이전 강의에서 흩어져 있던 main.cpp보다 체계적입니다. 다음으로 이 디렉토리에 새 파일을 계속 추가하고 점진적으로 완전한 프로그램을 작성합니다.

image 9-2 그림 9-2 디렉토리 구조

9.1.2 기본 데이터 구조 결정

프로그램을 실행하려면 데이터 단위와 프로그램 처리 과정을 설계해야합니다. 이것은 집을 구성하는 기둥과 벽돌과 같습니다. 그렇다면 SLAM 프로그램에서 가장 기본적인 구조는 무엇입니까? 우리는 다음과 같은 기본 개념을 추상화합니다.

  1. 프레임 : 프레임은 카메라로 촬영 한 이미지의 단위입니다. 그것은 주로 이미지 (RGB-D의 경우 한 쌍의 이미지)를 포함합니다. 또한, 특징 점, 카메라 자세 및 내부 매개 변수와 같은 정보가 있습니다. 시각적 SLAM에서는 키 프레임에 대해 설명합니다. 카메라가 많은 양의 데이터를 수집하기 때문에 모든 데이터를 저장하는 것은 분명히 비현실적입니다. 카메라 데이터 수집에 아무런 변화 없이 컴퓨터로 모든 데이터를 받아들이면, 프로그램의 메모리 사용은 허용 될 수 없을 때까지 더 높아지고 높아질 것입니다. 이에 대해 우리는 보통 중요하다고 생각되는 프레임들을 저장하고, 카메라의 이동 경로를 이 키 프레임으로 설명할 수 있다고 생각하는 것입니다. 키 프레임을 선택하는 방법은 중요한 문제이나, 엔지니어링 경험을 토대로 이론적인 지침은 거의 없습니다. 이 책에서는 키 프레임 선택 방법을 사용 하겠지만 독자는 새로운 방법을 제안하는 것을 고려할 수도 있습니다.
  2. 랜드마크 지점 : 랜드마크 지점은 이미지의 특징 지점입니다. 이 지점을 이용해 카메라가 움직 인 후에도 3D 위치를 추정 할 수 있습니다. 일반적으로 랜드 마크 점은 맵에 배치되고 새 프레임은 맵에서 랜드 마크 점과 일치하여 카메라 포즈를 추정합니다.

프레임 정보와 랜드마크의 위치 외 에도, 우리는 프로그램을 더 매끄럽게 작성할 수 있도록 약간의 도구가 더 필요합니다. 예를 들면

  1. 구성 (configuration) 파일 : 프로그램을 작성하면서 독자는 특징점, 특징점의 매칭 비율 등 과 같은 다양한 종류의 파라미터들을 보게 될 것 입니다. 이 파라미터들을 직접 프로그램에 작성할 수 도 있지만, 이것은 좋은 방법이 아닙니다. 이 파라미터들은 자주 수정해야 할 수도 있고, 매 수정마다 프로그램을 재 컴파일 해야할 것입니다. 파라미터의 수가 많아질수록 프로그램을 수정하는 것은 더욱 어려워 질 것 입니다. 그래서 보다 좋은 방식은 외부에서 하나의 구성 파일을 정의하고, 프로그램 실행 시 이 구성 파일에서 파라미터의 값을 읽어내는 것입니다. 이러한 방법으로, 파라미터 값을 바꿔야 할 때마다 프로그램을 수정하지 않아도 됩니다.
  2. 좌표 변환: 월드 좌표계부터 카메라 좌표계, 카메라 좌표계에서 그 다음 프레임의 카메라 좌표계, 카메라 좌표계에서 화소 좌표계 등 많은 좌표 변환이 필요합니다. 하나의 좌표 변환 함수로 이 기능들을 모아두면 훨씬 편해집니다.

C ++의 클래스로 표현되는 프레임 및 랜드마크의 개념을 정의하는 방법은 아래와 같습니다. 우리는 클래스가 별도의 헤더 파일과 소스 파일을 갖도록 작성함으로써 동일한 파일에 많은 클래스를 작성하는 것을 피합니다. 그런 다음 함수 선언을 헤더 파일에 넣고, 구현된 함수는 소스 파일에 넣습니다 (함수가 짧지 않으면 헤더 파일에 쓸 수 있음). 우리는 Google의 코드 작성 방식처럼 초보자도 읽기 쉬운 프로그램을 작성하는것을 지향합니다. 우리의 프로그램은 소프트웨어 공학 측면 보다는 알고리즘에 편향되어 있기 때문에, 복잡한 클래스 상속 관계나, 인터페이스, 템플릿 등에 대한 정보는 다루지 않고, 그 대신 알고리즘의 정확한 구현과 확장에 집중합니다. 또, 우리는 C ++ 소프트웨어 설계에서 피하는 것이 좋지만 데이터 멤버를 public으로 설정합니다. 프로그램 작성자가 원하는 경우 private 또는 protected 접근지정자로 변경하고 설정을 추가하고 인터페이스를 가져올 수 있습니다. 복잡한 과정이 많은 단계에서는, 우리는 이 복잡한 과정을 ‘특징 점 추출하기 & 매칭하기’와 같이 각각의 함수로 구현되는 작은 단계들로 나누어, 작성자가 알고리즘의 흐름을 바꾸고 싶을 때 프로그램 전체 흐름을 바꾸지 않고 로컬 프로세스만 바꾸게 합니다.

이제 VO를 시작하겠습니다. 이 버전을 버전 0.1로 설정하여 시작 단계임을 나타냅니다. 프레임은 프레임, 카메라는 카메라 모델, MapPoint는 특징 지점 / 랜드마크 지점, 지도는 특징 지점을 관리하며, Config는 구성 매개 변수를 제공합니다. 이들의 관계는 그림 9-3과 같습니다. 우리는 데이터 멤버와 일반적인 메소드 만 작성하고 나중에 더 많은 컨텐츠가 사용되면 나중에 추가합니다.

image 9-3 그림 9-3. 기본 클래스의 관계에 대한 개략도.

Camera 클래스가 가장 쉽습니다. 먼저 구현하십시오.

9.1.3 Camera 클래스

Camera 클래스는 카메라의 내부 및 외부 매개 변수를 저장하고 카메라 좌표계, 픽셀 좌표계 및 월드 좌표계 간의 좌표 변환을 수행합니다. 물론 월드 좌표계에서는 카메라의 (가변) 외부 매개 변수가 필요합니다. 외부 매개 변수는 매개 변수로 전달됩니다.

camera.h Code

설명은 다음과 같습니다 (위에서 아래로).

  1. 이 간단한 예제에서는 헤더 파일의 중복 참조를 방지하는 ifndef 매크로 정의를 사용합니다. 이 매크로가 없으면 두 위치에서 헤더 파일을 참조 할 때 클래스의 중복 정의가 나타납니다. 따라서 이러한 매크로는 각 프로그램 헤더 파일에 정의됩니다.
  2. 클래스 이름 공간 정의를 namespace myslam (우리가 직접 작성한 SLAM 프로그램이기 때문에 myslam이라고 부름)로 둘러 쌉니다. 이름 공간을 사용함으로써 다른 라이브러리에서 같은 이름의 함수를 우연히 정의하는것을 방지할 수 있습니다. 매크로 정의와 이름 공간은 각 파일에 한 번 씩 작성되므로 여기에서 소개하고 추후에는 스킵할 것입니다.
  3. common_include.h 파일에 일반적으로 사용되는 헤더 파일을 넣습니다. 이로 인해 매번 긴 include 목록을 쓰는 것을 피할 수 있습니다.
  4. 우리는 스마트 포인터를 Camera의 포인터 유형으로 정의함으로써, 매개 변수를 전달할 때 Camera::Ptr 유형을 사용할 수 있습니다.
  5. Sophus::SE3를 사용하여 카메라의 포즈를 표현합니다. Sophus 라이브러리는 Lie 대수학에서 소개되었습니다. 소스 파일에서 Camera 메서드의 구현을 지정합니다.

camera.cpp Code Link

독자는 이 방법이 다섯 번째 강의의 내용과 일치하는지 확인할 수 있습니다. 픽셀 좌표계, 카메라 좌표계 및 세계 좌표계 간의 좌표 변환을 완료합니다.

9.1.3 Frame 클래스

Frame 클래스를 살펴 보겠습니다. Frame 클래스는 여러 곳에서 사용되는 기본 데이터 단위이지만, 초기 설계 단계에서는 추후에 무엇이 추가될 지 알 수 없습니다. 그렇기에 프레임 클래스는 기본적인 데이터 저장과 인터페이스만을 제공합니다. 나중에 새 콘텐츠가있는 경우 계속 추가하십시오.

frame.h Code

프레임에서는 ID, 타임 스탬프, 포즈, 카메라 및 이미지의 수를 정의합니다.이 파라미터들은 프레임에 포함 된 가장 중요한 정보입니다. 이 정보에서부터 Frame 만들기, 주어진 점에 해당하는 깊이 찾기, 카메라의 광학 중심 찾기, 점이 시야에 있는지 여부 등을 결정할 수 있습니다. 이것의 구현법은 간단함으로, 이 함수의 자세한 구현은 frame.cpp를 참조하십시오

9.1.5 MapPoint 클래스

MapPoint는 랜드 마크 포인트를 나타냅니다. 월드 좌표를 추정하고 현재 프레임에서 추출한 특징점을 지도의 랜드 마크 점과 일치시켜 카메라 모션을 추정하므로 해당 설명자를 저장해야합니다. 또한, 포인트가 관측 된 횟수와 일치되는 횟수를 통해 MapPoint가 얼마나 좋은지 또는 나쁜지를 나타내는 지표로 기록합니다.

mappoint.h Code

마찬가지로 독자는 src/map.cpp를 탐색하여 구현을 볼 수 있습니다. 지금까지는 이러한 데이터 멤버의 초기화 만 고려했습니다.

9.1.6 지도 클래스

Map 클래스는 모든 랜드 마크를 관리하며 새로운 키 프레임을 추가하고 불량한 키 프레임을 삭제하는 등의 작업을 담당합니다. VO 매칭 과정은 맵을 다루기 만하면됩니다. 물론 Map도 많은 작업을 수행하지만 이 단계에서는 주요 데이터 구조 만 정의합니다.

map.h Code

Map 클래스는 실제로 무작위 액세스가 필요하며 언제든지 삽입 및 삭제해야하는 키 프레임과 랜드 마크를 저장하므로 저장을 위해 해시(unordered_map)를 사용합니다.

9.1.7 Config 클래스

Config 클래스는 매개 변수 파일을 읽고 프로그램의 어디서든지 매개 변수 값을 제공합니다. 이를 위해 우리는 싱글 톤 모드 (Singleton)로 Config를 작성합니다. 이 작성 법은 하나의 전역 객체를 가지고 있습니다. 매개 변수 파일을 설정할때에는, 객체를 생성하고 매개 변수 파일을 읽는데, 우리는 어디에서든지 이 매개 변수 값에 접근할 수 있고, 프로그램의 끝에서 이 매개 변수 값을 삭제할 수 있습니다.

config.h Code

설명은 다음과 같습니다.

  1. 우선 생성자를 private로 선언하여, 이 클래스의 객체가 다른 곳에 빌드되는 것을 방지하고, setParameterFile에서만 생성할 수 있게 합니다. 실제 생성 된 객체는 Config의 스마트 포인터입니다 : static shared_ptrconfig. 스마트 포인터를 사용하는 이유는 자동으로 파괴되는 점을 이용해서, 다른 함수를 작성해서 파괴를 해야하는 수고를 덜 수 있기 때문입니다.
  2. 파일 읽기를 위해 OpenCV에서 제공하는 FileStorage 클래스를 사용합니다. YAML 파일을 읽고 이 필드에 액세스 할 수 있습니다. 인수의 실제 값은 정수, 부동 소수점 또는 문자열 일 수 있으므로 템플릿 함수 get을 통해 모든 유형의 매개 변수 값을 가져옵니다. 아래는 Config의 구현입니다. 이 소스 파일에서 싱글 톤 패턴에 대한 전역 포인터를 정의했다는 점에 유의하십시오.

config.cpp Code ** 코드 그림 삽입 **

구현시 매개 변수 파일의 존재 여부만 판단하면됩니다. 이 Config 클래스를 정의한 후에는 어디서나 매개 변수 파일의 매개 변수를 가져올 수 있습니다. 예를 들어, 카메라의 초점 거리 f x를 정의하려면 다음 단계를 수행하십시오.

  1. "Camera.fx : 500"를 매개 변수 파일에 추가하십시오.
  2. 코드에서 다음과 같이 구현 하면 :

config.cpp_2

f x의 값을 구할 수 있습니다. 물론, 매개 변수 파일의 구현은 확실히 이것보다 더 복잡합니다. 우리는 주로 프로그램을 개발할 때, 편의성의 이유로 이 구현 방법을 사용합니다. 물론 독자는 매개 변수 구성을 더 간단한 방법으로 구현할 수도 있습니다. 지금까지 SLAM 프로그램의 기본 데이터 구조를 정의하고 몇 가지 기본 클래스를 작성했습니다. 이것은 벽돌과 시멘트를 만드는 것과 같습니다. cmake를 사용하여 이 버전 0.1을 컴파일 할 수 있습니다. 그러나 아직 실질적인 기능은 없습니다. 다음으로 앞에서 언급 한 VO 알고리즘을 프로젝트에 추가하고 각 알고리즘의 성능을 조정하는 몇 가지 테스트를 수행해봅니다. 저자는 의도적으로 특정 디자인 문제를 노출하므로, 책에 표시되는 구현 방법이 반드시 가장 좋은 방법은 아닙니다.

9.2. 기본 VO: 특징 점 추출 및 매칭

우선적으로 우리는 VO를 구현할 겁니다. 먼저 특징 점을 이용한 방법을 구현해봅시다. 이 방법은 입력 이미지를 기반으로 카메라 움직임과 특징점 위치를 계산합니다. 이전에 논의한 것은 두 프레임 간의 포즈 추정이지만 두 프레임의 추정만으로는 충분하지 않습니다. 특징 점을 로컬 지도에 사영하고, 그 로컬 지도를 이용해서 현재 프레임과 지도 사이의 위치 관계를 계산합니다. 그러나 이 방법으로는 프로그램이 굉장히 복잡해질 수 있으므로, 우선적으로 두 프레임 사이의 모션 추정에서 시작합니다.

9.2.1 하나 또는 두개 프레임의 Visual odometry

앞 두 챕터에서 설명 되었듯이, 특징 점의 위치가 최적화 되어 있지 않은 상태에서 두 프레임 간의 모션 추정만 관심이 있을 경우에는, 추정된 포즈를 연결하여 이동 경로를 추측할 수 있습니다. 이 방법은 두 프레임 간의(Pairwise) 구조적이지 않은 (Structureless) VO 쌍으로 여겨지며, 가장 구현하기 쉬운 방법이지만 그리 좋은 방법으로 여겨지지는 않습니다. 왜 좋은 방법이 아닐까요? 함께 알아가봅시다. 이 프로젝트는 이제 버전 0.2라는 것을 기억하십시오.

그림 9-4에는 두 프레임 간의 VO 작업에 대한 개략적인 다이어그램이 나와 있습니다. 이 VO에서는 참조 프레임 (Ref)과 현재 프레임 (Curr)의 두 가지 개념을 지니고 있습니다. 참조 프레임을 기준 좌표계로 취하고, 현재 프레임과의 피쳐를 매칭시켜 모션 관계를 추정합니다. 월드 프레임의 좌표계에 대한 참조 프레임의 좌표계의 변환 행렬을 Trw로하고, 월드 프레임의 좌표계에 대한 현재 프레임의 좌표계와의 사이에 라고 Tcw가정하면, 추정 될 동작과 두 프레임의 변환 행렬은 왼쪽 곱셈 관계를 형성합니다 (left multiplication relationship).

Equation

t - 1에서 t까지의 이동 경로를 구한다면, t - 1을 참조 프레임으로 취하여 t 시점의 움직임을 찾습니다. 이것은 특징 점 매칭, 옵티컬 플로우 또는 direct method로 수행 할 수 있습니다. 하지만 여기서 우리는 모션에만 신경을 쓰고 구조는 신경 쓰지 않습니다. 즉, 특징 점을 통해 모션이 성공적으로 획득되는 한,이 프레임의 특징점은 더 이상 필요하지 않습니다. 물론 이 방법은 결함이 있지만 많은 수의 특징 점을 무시하면 많은 계산을 줄일 수 있습니다. t에서 t + 1까지의 이동 경로를 구한다면, 우리는 t 시간을 참조 프레임으로 취하고 t와 t + 1 사이의 움직임 관계를 고려합니다. 왕복 운동을 하면 모션 트랙을 얻을 수 있습니다.

img 9_4 그림 9-4 2 개의 프레임으로 구성된 VO 다이어그램.

이 VO의 작동 방식은 간단하지만 이를 구현하는 방법은 여러 가지가 있습니다. 이를 달성하기 위해 전통적인 매칭 특징점 인 PnP 방법을 예로 사용합니다. 저자는 독자들이 이전의 강의 지식을 결합하여 optical flow / direct 방법의 VO 또는 프로그램 실행을 위한 ICP를 실현할 수 있기를 바랍니다. 특징 점을 매칭하는 방법에서 가장 중요한 것은 참조 프레임과 현재 프레임 간의 피쳐 매칭 관계입니다. 이 흐름은 다음과 같이 요약 할 수 있습니다.

  1. 새 프레임의 특징 점과 디스크립터를 추출하십시오.
  2. 시스템이 초기화되지 않은 경우, 현재 프레임을 참조 프레임으로 사용하고 깊이 맵에 따라 특징 점의 3D 위치를 계산 한 다음 1 단계로 돌아갑니다.
  3. 참조 프레임과 현재 프레임 간의 모션을 추정합니다.
  4. 위의 추정이 성공했는지 확인합니다.
  5. 성공하면 현재 프레임을 새 참조 프레임으로 되돌리고 1 단계로 돌아갑니다.
  6. 실패 할 경우 연속적인 손실 된 프레임 수를 기록하십시오. 연속 손실이 특정 프레임 수를 초과하면 VO 상태가 손실되고 알고리즘이 종료됩니다. 그렇지 않은 경우 1 단계로 돌아가십시오.

VisualOdometry 클래스는 위 알고리즘의 구현을 제공합니다.

slambook/project/0.2/include/myslam/visual_odometry.h

visual_odometry.h Code ** 코드 그림 삽입**

이 VisualOdometry 클래스에 대해 설명 할 몇 가지 사항이 있습니다.

  1. VO 자체에는 몇 가지 상태가 있습니다. 첫 번째 프레임을 설정하고 매끄러운 추적 또는 손실을 설정하면 Finite State Machine (FSM)이라고 생각할 수 있습니다. 물론 더 많은 상태가 있을 수 있습니다. 예를 들어 모노 VO에는 적어도 하나의 초기화 상태가 있습니다. 우리의 구현에서 가장 간단한 세 가지 상태, 즉 초기화, 정상 및 손실을 고려하십시오.
  2. 클래스에서 중간 변수를 정의하여 복잡한 매개 변수 전달의 필요성을 제거합니다. 그것들은 모두 클래스 내부에서 정의되었으므로 각 함수가 클래스에 액세스 할 수 있습니다.
  3. 특징 점 추출 및 일치의 매개 변수를 매개 변수 파일에서 읽습니다. 예 :

visual_odometry.h_2

  1. addFrame 함수는 외부 적으로 호출되는 인터페이스입니다. VO를 사용할 때 이미지 데이터를 Frame 클래스에로드 한 후 addFrame을 호출하여 포즈를 추정합니다. 이 함수는 VO가있는 상태에 따라 다른 작업을 구현합니다.

visual_odometry.h_3

다양한 이유로, 우리가 설계 한 위의 VO 알고리즘이 모든 단계에서 실패 할 수 있음을 언급 할 필요가 있습니다. 따라서 견고한 VO를 설계하려면 위에서 언급 한 모든 가능한 오류를 고려해야합니다 (분명히 명시 적으로 고려해야 함). 자연스럽게 프로그램이 복잡해집니다. checkEstimatedPose에서 inliers의 수와 모션의 크기를 기반으로 간단한 테스트를합니다. 내부 점이 너무 작지 않고 모션이 너무 클 수 없다고 생각합니다. 물론 독자는 문제를 발견하고 효과를 시험해 볼 수있는 다른 방법에 대해서도 생각할 수 있습니다.

우리는 나머지 VisualOdometry 클래스를 건너 뛰고 독자는 GitHub에서 모든 소스 코드를 찾을 수 있습니다. 마지막으로 VO 테스트 프로그램을 추가하여 데이터 세트를 테스트하고 사용하여 예상되는 모션 효과를 관찰합니다.

run_vo.cpp Code

이 프로그램을 실행하기 위해 할 일이 몇 가지 있습니다.

  1. OpenCV 3 모듈을 사용하여 예상 포즈를 표시하므로 OpenCV 3을 설치하고 viz 모듈도 컴파일하고 설치해야합니다.
  2. TUM 데이터 세트 중 하나를 준비하십시오. 간단히하기 위해 저자는 fr1_xyz를 권장합니다. associate.py를 사용하여 연결 파일 인 associate.txt를 생성하십시오. TUM 데이터 세트 형식은 8.3 절에 설명되어 있습니다.
  3. config / default.yaml에서 데이터 세트의 경로를 채우고 작성자의 작성 방법을 참조하십시오. 그런 다음

run_vo.cpp_2

프로그램을 실행하면 그림 9-5와 같이 라이브 데모가 표시됩니다.

데모에서 현재 프레임의 이미지와 예상 위치를 볼 수 있습니다. 세계 좌표계의 좌표축 (큰 좌표축)과 현재 프레임의 좌표축 (작은 좌표축)을 그립니다. 색상과 축간의 대응은 blue-Z, red-X, green-Y입니다. 이론과 실제 구현 결과에 차이가 있지만 사용자의 예측과 어느정도 일치하는 카메라의 움직임을 직관적으로 느낄 수 있습니다. 이 프로그램은 또한 VO 단일 계산을위한 시간을 출력합니다.이 계산은 매번 작성자의 컴퓨터에서 약 30ms의 속도로 실행될 수 있습니다. 특징 지점의 수를 줄이면 작업 속도가 빨라질 수 있습니다. 독자는 작동 매개 변수와 데이터 세트를 수정하여 다양한 상황에서 어떻게 작동하는지 확인할 수 있습니다.

img 9_5 그림 9-5 VO 데모 버전 0.2.

9.2.2. 토론

이 섹션에서는 두 프레임 사이에 간단한 Visual odometry를 구현했지만 그 효과는 속도와 정확성 측면에서 이상적이지 않습니다. 이 단순한 아이디어로 좋은 결과를 얻지 못하는 것 같습니다. 이러한 이유가 무엇인지 생각해 봅시다.

  1. 포즈 추정에서 RANSAC에서 얻은 PnP 솔루션을 사용했습니다. RANSAC은 PnP를 계산하기 위해 몇 개의 랜덤 포인트만을 사용하기 때문에 inlier를 결정할 수 있지만 이 방법은 잡음에 취약합니다. 좀 더 나은 방법으로는 3D-2D 지점에서 노이즈가 발생하는 경우 RANSAC 솔루션을 초기 값으로 사용하고, 비선형 최적화를 사용하여 최적 값을 찾습니다. 다음 섹션에서는 이 접근법이 현재 접근법보다 낫다는 것을 보여줄 것입니다.
  2. 현재 VO가 구조화되지 않았으므로, 특징 점의 3D 위치는 동작을 추정하기 위해 모두 참값으로 처리됩니다. 그러나 사실, RGB-D의 깊이 맵에는 특정 오류가 있습니다. 특히 깊이가 너무 가깝거나 너무 멀면 오류가 발생합니다. 또한 특징점은 종종 오브젝트의 모서리에 위치하기 때문에 해당 위치의 깊이 측정이 정확하지 않을 수 있습니다. 따라서 현재의 방법은 정확하지 않을 수 있습니다. 우리는 특징 점의 정보를 정확하게 추출하고 최적화해야합니다.
  3. 기준 프레임 / 현재 프레임만을 고려한 포즈 추정법은, 추정을 기준 프레임에 너무 의존하게 만듭니다. 기준 프레임의 정보가 정확하지 않을 때에, 예를 들어 오브젝트가 가려지거나 조명의 변화가 갑작스럽게 큰 경우 위치 추정이 쉽게 손실됩니다. 또한, 기준 프레임의 정확한 포즈 추정이 가능하지 않을 때, 추후에 상당한 에러가 생길 것 입니다. 그리고 두 프레임의 데이터 만 사용하면 모아온 모든 정보를 의미있게 사용하는 것이 아닙니다. 보다 자연스러운 방법은 현재 프레임을 기준 프레임과 비교하는 대신 현재 프레임을 맵과 비교하는 것입니다. 따라서 현재 프레임을 지도에 매치하는 방법과 지도 지점을 최적화하는 방법에 대해 신경을 써야합니다.
  4. 각 단계의 실행 시간이 출력되기 때문에 계산량을 일반적으로 이해할 수 있습니다 (표 9-1).

Table_1 (역자: 왼쪽에서부터 ‘특징점 추출’, ‘디스크립터 계산’, ‘특징 점 매칭’, ‘PnP 계산’, ‘기타’, ‘통합’)

특징 포인트의 추출 및 매칭은 계산 시간의 대부분을 차지하는 반면, 겉으로 보기에는 복잡한 PnP 최적화는 계산에 비해 거의 무시할 수 있음을 알 수 있습니다. 따라서, 특징 추출 방법 및 일치 알고리즘의 속도를 향상시키는 방법은 특징 지점 방법의 중요한 주제가 될 것입니다. Direct / Optical Flow을 사용하는 예측 가능한 방법은 무거운 피쳐 계산을 효과적으로 피하는 것입니다. 직접 및 광학 흐름 방법은이 책 이전에 논의되었으며 독자는 독자적으로 시도 할 수 있습니다.

개선 사항: PnP 결과 최적화

다음으로, 이전 내용에 따라 VO를 향상시키는 몇 가지 방법을 시도해 보겠습니다. 이 섹션에서는 RANSAC PnP와 반복 최적화를 사용하여 카메라 포즈를 평가하여 이전 섹션의 성능이 향상되는지 확인해 보겠습니다.

비선형 최적화 문제에 대한 해답은 이미 강의 6과 7에서 소개되었습니다. 이 절의 목적은 구조보다는 포즈를 추정하기위한 것이므로 재 투영 오차를 최소화하여 최적화 문제를 구성하기 위해 카메라 포즈 ξ를 최적화 변수로 사용합니다. 이전과 마찬가지로 Google은 g2o에서 최적화 가장자리를 사용자 정의합니다. 하나의 위치 추정만 최적화하므로 단면적입니다.

g2o_types.h Code

3D 점과 카메라 모델을 멤버 변수에 넣으면 재 투영 오차와 자코비안 행렬을 쉽게 계산할 수 있습니다.

g2o_types.cpp Code

그런 다음 이전 PoseEstimationPnP 함수에서 RANSAC PnP 결과의 초기 값으로 수정 한 다음 g2o를 호출하여 양식을 최적화합니다.

slambook/project/0.3/src/visual_odometry.cpp

visual_odometry.cpp Code

저자는 독자가 이 프로그램을 직접 실행하고 이전 결과를 비교해보는 것을 추천합니다. 추정 된 움직임이 훨씬 안정적이라는 것을 알게 될 것입니다. 동시에 새로운 최적화가 여전히 구조화되지 않았기 때문에 규모가 작고 컴퓨팅 시간에 대한 영향은 기본적으로 무시할 수 있습니다. 전체적인 시각적 거리계 계산 시간은 여전히 약 30ms입니다.

토론

반복적인 최적화 방법을 도입 한 후에는 순수 RANSAC PnP에 비해 평가 결과의 품질이 크게 향상됨을 발견했습니다. 우리는 여전히 프레임 사이의 정보만을 사용하지만 결과적으로 얻어진 움직임은 보다 정확하고 매끄 럽습니다. 이 개선에서 우리는 최적화의 중요성을 알 수 있습니다. 그러나 버전 0.3의 VO는 여전히 프레임 간의 일치 제한 사항의 영향을받습니다. 비디오 시퀀스의 프레임이 손실되면 후속 프레임이 이전 프레임과 일치하지 않습니다. 다음으로 VO에 지도 기능을 구현해봅니다

9.4 개선 사항: 로컬 맵 (Local Maps)

이 섹션에서는 VO가 일치하는 특징점을 지도에 넣고 현재 프레임을지도 지점과 일치시켜 포즈를 계산합니다. 이 방법과 이전 방법의 차이점은 그림 9-6에 나와 있습니다.

image 9-6 그림 9-6 ‘두 프레임의 VO’와 ‘지도 기능을 사용한 VO’의 작동 원리의 차이

두 프레임을 사용한 VO 방식은 기준 프레임과 현재 프레임 사이의 특징 일치 및 동작 관계를 계산하고, 계산 후 이전 프레임을 새로운 기준 프레임으로 설정합니다. 지도를 사용하는 VO에서는 각 프레임은 새로운 특징 지점을 추가하거나 이전 특징 지점의 위치 추정치를 업데이트하는 것과 같은 정보를 지도에 제공합니다. 맵의 피쳐 포인트 위치는 종종 월드 좌표에 있습니다. 따라서 현재 프레임이 업데이트되면 맵과 피쳐 매칭 및 모션 관계, 즉 를 직접 계산해야 합니다.

이 방식의 이점은 우리가 끊임없이 업데이트된 지도를 유지할 수 있다는 것입니다. 지도가 정확하면 중간의 특정 프레임에 오류가 있어도 프레임의 정확한 위치를 찾는 것이 쉬워집니다. 우리는 아직 SLAM의 구조에 대해 상세히 논의하지 않았기 때문에 여기에 있는지도는 임시 개념 일 뿐이며, 한 곳으로 캐싱되는 특징 지점의 집합을 나타냅니다.

지도는 지역 (Local) 지도와 전역 (Global) 지도로 나눌 수 있으며, 용도가 다르기 때문에 별도로 논의됩니다. 이름에서 알 수 있듯이 지역 지도는 인근의 피쳐 포인트 정보를 나타냅니다. 즉, 카메라의 현재 위치에서 가까운 특징 점만을 추출하고, 시야에서 벗어나거나 멀리 위치한 특징점들은 버립니다. 이 특징 점들은 현재 프레임을 일치시켜 카메라 위치를 찾는 데 사용되므로, 우리는 이 과정이 빨리 수행되는 것을 원합니다. 반면에, 전역 지도는 SLAM이 실행 된 이후 모든 특징 포인트를 기록합니다. 전역 지도는 전체 환경을 표현하는데 사용되지만, 분명 위치 추정에 전역 지도를 사용하면 컴퓨터에 너무 큰 부담을 주기 때문에, 주로 루프백 감지 및 맵 표시에만 사용합니다.

VO에서는 위치를 추정하기 위해 직접 사용할 수있는 지역 지도가 더 많이 사용됩니다 (지도를 사용하기로 결정한 경우). 이제 지역 지도를 유지하는 것에 대해 이야기 해 봅시다. 카메라가 움직이면서 맵에 새로운 특징 점을 추가합니다. 우리는 여전히 독자들에게 지도를 사용할지 여부가 정밀도와 효율성 간의 모순에 대한 이해에 달려 있음을 상기시켜줍니다. 효율성을 높이기 위해 2 개 또는 2 개의 비정형 VO를 사용할 수 있으며, 지역 지도를 구축 할 수도 있고 맵 최적화를 고려하여 더 나은 정밀도를 얻을 수도 있습니다.

지역 지도의 문제 중 하나는 규모를 유지하는 것입니다. 실시간 성능을 보장하기 위해 지도 크기가 너무 크지 않도록 해야합니다 (그렇지 않으면 일치하는 데 많은 시간이 소요됩니다). 또한 단일 프레임의 특성을 지도와 일치시키는 일부 가속화 방법이 있지만 기술적 복잡성으로 인해 루틴에서 제공되지 않습니다.

이제 맵 포인트 클래스를 구현해 보겠습니다. 이전에는 사용되지 않은 MapPoint 클래스, 주로 해당 생성자 및 빌드 함수를 약간 수정했습니다.

slambook/project/0.4/include/myslam/mappoint.h

mappoint.h_2 Code

주요 변경 사항은 VisualOdometry 클래스에 있습니다. 워크 플로우의 변경으로 인해 각 반복에서 맵 추가 및 삭제, 각 맵 포인트가 관측 된 횟수 계산 등과 같은 몇 가지 주요 기능을 수정했습니다. 이러한 것들은 사소한 일이므로 독자들이 GitHub에 제공된 소스 코드를 자세히 살펴볼 것을 권장합니다. 다음 항목에 집중하십시오.

  1. 첫 번째 프레임의 특징점을 추출한 후 첫 번째 프레임의 모든 특징점을지도에 넣습니다.

change_vo

  1. 후속 프레임에서는 OptimizeMap 함수를 사용하여 맵을 최적화합니다. 여기에는 보기에 없는 지점 삭제, 일치 항목 수가 줄어들 때 새로운 지점 추가 등이 포함됩니다.

change_vo_2

저자는 고의적으로 몇 구간을 비워두었고, 관심있는 독자들이 직접 이 칸을 채워보았으면 합니다. 예를 들어, triangulation을 사용하여 피쳐 포인트의 세계 좌표를 업데이트하거나 맵 크기를보다 효과적으로 관리하기위한 전략을 고려할 수 있습니다. 이러한 문제는 모두 열려 있습니다.

  1. 기능 일치 코드. 매칭하기 전에 맵에서 후보 포인트 (시야에 나타나는 포인트)를 취하여 현재 프레임의 피쳐 설명자와 일치시킵니다.

change_vo_3

기존지도와 더불어 "키 프레임"이라는 개념을 도입했습니다. 키 프레임은 많은 비주얼 SLAM에서 사용되지만 개념은 주로 백엔드를위한 것이므로 나중에 해당 섹션에서 키 프레임의 자세한 처리에 대해 설명합니다. 실제로는 리소스 집약적인 각 이미지에 대한 세부적인 최적화 및 루프백 검색을 원하지 않습니다. 적어도 카메라가 제 위치에 놓여지면 전체 모델 (지도 또는 궤적)이 커지기를 원하지 않습니다. 따라서 백엔드 최적화의 주요 목표는 키 프레임입니다.

키 프레임은 카메라가 움직이는 동안의 특수 프레임입니다. "특수"의 의미는 스스로 지정할 수 있습니다. 카메라가 특정 간격을 지나갈 때마다 새로운 키 프레임이 찍히고 저장됩니다. 이 키 프레임의 포즈는 신중하게 최적화되며 맵의 일부 맵 포인트를 제외하고 두 키 프레임 사이에있는 키 프레임은 당연한 것으로 간주됩니다. 이 섹션의 구현은 일부 키 프레임을 추출하고 백엔드 최적화를위한 데이터를 준비합니다. 이제 독자는 이 프로젝트를 컴파일하고 어떻게 작동하는지 볼 수 있습니다. 이 섹션의 루틴은 지역 지도의 포인트를 이미지 평면에 투영하고 표시합니다. 포즈가 올바르게 추정되면 공간에서 고정 된 것처럼 보입니다. 반대로, 만약 당신이 부 자연스럽게 움직이는 것을 느끼면, 카메라 자세 추정이 충분히 정확하지 않거나, 또는 특징점의 위치가 충분히 정확하지 않을 수 있습니다. 우리는 버전 0.4에서 지도의 최적화를 제공하지 않았으며, 독자는 독자적으로 시도해 보는 것이 좋습니다. 사용 된 원리는 주로 최소 제곱 및 삼각 측량이며 처음 두 강의에서 소개되었으며 너무 어렵지 않을 것입니다.

image 9-7 그림 9-7 0.4 버전의 VO 스크린 샷. 두 시점에 도로 표시 투영 지점이 표시되어 있습니다.

9.5 요약

이 섹션을 통해 독자는 앞의 두 강의에서 소개 된 알고리즘을 경험적으로 이해할 수 있도록 간단한 VO를 처음부터 구현할 수 있었습니다. 이 기능이 없으면 예를 들어 "특징점의 VO로 얼마나 많은 ORB 특징점을 실시간으로 처리 할 수 있습니까?"라는 질문을 보는 것이 어렵습니다. VO는 시간 경과에 따라 카메라의 움직임과 특징점의 위치를 추정 할 수 있지만 이 로컬 접근법에는 심각한 단점이 있습니다.

  1. 정확한 위치 추정을 잃기 쉽다. 일단 잃어 버리면 "카메라가 돌아올 때까지 기다리십시오" (기준 프레임을 저장하고 새 프레임과 비교) 또는 전체 VO를 재설정하여 새 이미지 데이터를 추적합니다.
  2. 드리프트를 누적합니다. 주된 이유는 각 추정치의 오차가 다음 추정치에 누적되어 부정확한 장기 궤적을 야기한다는 것입니다. 더 큰 것의 지역 지도는 이 현상을 완화 할 수 있지만 항상 존재합니다.

단기간의 이용에만 관심이 있거나 VO의 정확성이 애플리케이션 요구 사항을 충족시키는 경우 전체 SLAM 대신 Visual odometry 만 필요한 경우가 있습니다. 예를 들어 무인기 제어 또는 AR 게임 응용 프로그램에서 사용자는 전 세계적으로 일관된 맵을 필요로하지 않으므로 가벼운 VO가 더 나은 선택 일 수 있습니다. 그러나 이 책의 목적은 전체 SLAM을 소개하는 것이므로 더 자세히 살펴보고 백엔드 및 루프백 검색이 어떻게 작동하는지 확인해야합니다.