ORB SLAM2 코드 분석

happy_quokka·2023년 12월 7일
0

SLAM_Projcet

목록 보기
4/4

mono_kitii.cc

1. 이미지 불러오기

  • LoadImages()

2. SLAM system 생성 (MONOCULAR)

  • ORB_SLAM2::System SLAM() → <System.cc>

3. main loop (이미지 수만큼 반복)

  1. imread
  2. 현재 시간 t1 기록
  3. SLAM.TrackMonocular() → <System.cc>
    • 카메라 pose
  4. 현재 시간 t2 기록
  5. 다음 frame 기다림

4. SLAM.Shutdown() → <System.cc>

  • 모든 thread 종료

5. KeyFrameTrajectory.txt 파일 저장

  • SLAM.SaveKeyFrameTrajectoryTUM() → <System.cc>


System.cc

System 생성 System::System()

1. 생성시 멤버변수 초기화

  • mSensor : MONOCULAR
  • mbReset : false
  • mbActivateLocalizationMode : false
  • mbDeactivateLocalizationMode : false

2. ORB Vocabulary 불러오기

  • 기존의 Vocabulary/ORBvoc.txt 파일을 불러온다
  • mpVocabulary = new ORBVocabulary();

3. KeyFrame Database 생성

  • mpKeyFrameDatabase = new KeyFrameDatabase(*mpVocabulary);

4. Map 생성

  • mpMap = new Map();

5. tracking thread 초기화

  • mpTracker = new Tracking() → <Tracking.cc>

6. Local Mapping thread

  • mpLocalMapper = new LocalMapping() → <LocalMapping.cc>

7. Loop Closing thread

  • mpLoopCloser = new LoopClosing() → <LoopClosing.cc>

8. thread 간의 set pointers

  • mpTracker, mpLocalMapper, mpLoopCloser를 서로 연결해준다

TrackMonocular()

  • orb slam을 진행하는 함수

unique_lock

  • unique_lock<mutex> lock(mMutexMode);
  • {} 또는 함수가 끝날 때 자동으로 unlock 된다
  • 하나의 thread만 접근할 수 있도록 해준다
  • mutex : thread들의 동기화를 해줄 수 있게 하는 기능

1. check mode change

  • 처음에는 mbActivateLocalizationMode와 mbDeactivateLocalizationMode가 false이기 때문에 진행하지 않는다
  • localmapping이 진행중이라면 끝내고 진행

2. check reset

3. tracking 시작

  • GrabImageMonocular() → <Tracking.cc>
  • mTrackingState : NO_IMAGES_YET, NOT_INITIALIZED 중의 상태
  • mTrackedMapPoints : MapPoints
  • mTrackedKeyPointsUn : undistorted keypoint 벡터


Tracking.cc

Tracking 생성 Tracking::Tracking()

1. 생성시 멤버변수 초기화

  • mState(NO_IMAGES_YET)
  • mSensor(sensor)
  • mbOnlyTracking(false)
  • mbVO(false)
  • mpORBVocabulary(pVoc)
  • mpKeyFrameDB(pKFDB)
  • mpInitializer(static_cast<Initializer*>(NULL))
  • mpSystem(pSys)
  • mpViewer(NULL)
  • mpFrameDrawer(pFrameDrawer)
  • mpMapDrawer(pMapDrawer)
  • mpMap(pMap)
  • mnLastRelocFrameId(0)

2. 카메라 파라미터 설정

  • K matix 생성 : intrinsic matrix
  • DistCoef 생성 : [0,0,0,0][ 0,0,0,0 ], vector of distortion coefficients
  • mbf = 0.0
  • fps = 10.0
  • mMinFrames = 0, mMaxFrames = fps

3. ORB 파라미터 설정

  • nFeatures = 2000 : frame에서 추출하면 feature의 수
  • fScaleFactor = 1.2 : scale 피라미드 factor
  • nLevels = 8 : 피라미드의 level 수
  • fIniThFAST = 20 : grid로 나눈 후 한 cell에서 검출되어야하는 corner의 최소 개수 (초기 임계값)
  • fMinThFAST = 7 : corner가 검출되지 않는다면 사용할 더 낮은 thresdhold

FAST 알고리즘 : grid를 나누고 각 grid 안에서 FAST를 사용하여 corner point 검출

4. ORB extractor 생성

  • mpORBextractorLeft = new ORBextractor() → <ORBextractor.cc>
  • mpIniORBextractor = new ORBextractor() → <ORBextractor.cc>

frame feature 계산 및 tracking 시작 GrabImageMonocular()

1. gray 이미지로 변환

2. mCurrentFrame 지정

  • frame 생성 Frame() → <Frame.cc>
  • mState가 NOT_INITIALIZED 또는 NO_IMAGES_YET 인 경우
    • mpIniORBextractor
  • 그 외의 경우
    • mpORBextractorLeft
  • Frame이 생성되면 keypoint, descriptor가 계산된 상태
  • keypoint는 undistored 상태
  • 이미지가 grid로 나뉘고 그 해당되는 grid에 keypoint들이 분류되어 있는 상태

3. tracking 수행

  • Track() → <Tracking.cc>의 Tracking::Track()

4. camera pose 반환

  • cv::Mat mTcw 반환

tracking 수행 Tracking::Track()

1. mState 확인

  • 맨 처음에는 NO_IMAGES_YET 상태
  • NOT_INITIALIZED로 변경 후 mLastProcessedState에 저장

2. if) NOT_INITIALIZED 상태이면

  1. MonocularInitialization() 수행 → <Tracking.cc>의 Tracking::MonocularInitialization()
  2. frameDrawer update
  3. mState가 OK가 아니면 return

3. else) tracking 진행

  • 쭈우우욱

initialization Tracking::MonocularInitialization()

1. if) 맨 처음 mpInitializer가 없을 때

  • mCurrentFrame의 keypoint 수가 100개 보다 많을 때
  • reference frame 설정
  1. mInitialFrame, mLastFrame를 mCurrentFrame로 설정
  2. mvbPrevMatched에 mCurrentFrame의 keypoint 복사
  3. mpInitializer 생성
    • 즉, 현재 이미지인 mCurrentFrame를 reference 이미지로 지정

2. else) mpInitializer가 있을 때

2-1. 초기화

  • if) mCurrentFrame의 keypoint가 100개 이하이면
  • mpInitializer 삭제 후 return

2-2. correspondences 찾기

  • 현재 frame과 reference frame 간의 correspondence 찾기
  • orbmatcher 수행
  • matcher.SearchForInitialization() → <ORBmatcher.cc>의 ORBmatcher::SearchForInitialization()


ORBextractor.cc

ORBextractor 생성 ORBextractor::ORBextractor()

1. 생성시 멤버변수 초기화

  • nFeatures = 2000
  • scaleFactor = 1.2
  • nLevels = 8
  • fIniThFAST = 20
  • fMinThFAST = 7

2. 이미지 pyramid 준비

  • mvScaleFactor=[1.0,1.2,1.44,....]mvScaleFactor = [1.0, 1.2, 1.44,....] : 계속 scaleFactor를 곱하여 원소 nLevels개의 vector
  • mvLevelSigma2=[1.0,mvScaleFactor[1]2,mvScaleFactor[2]2,....]mvLevelSigma2 = [1.0, mvScaleFactor[1]^2, mvScaleFactor[2]^2, ....] : mvScaleFactor의 제곱 vector (원소 nLevels개)
  • mvInvScaleFactor : mvScaleFactor의 역수
  • mvInvLevelSigma2 : mvLevelSigma2의 역수
  • mvImagePyramid
  • mnFeaturesPerLevel : 각 레벨에서 원하는 특징점 수를 설정 (레벨이 증가할수록 특징점 수 감소)
  • sumFeatures : 모든 level에서의 특징점 수의 합
  • nfeatures - sumFeatures : 마지막 레벨에서의 특징점 수 결정

3. descriptor를 위한 pattern 불러오기

  • 미리 정해져있는 bit_pattern_31_에서 point pair 위치를 가져와서 pattern에 복사
  • npoints(512개) 즉, 128 * 4(x1,y1,x2,y2) 총 128쌍을 가져온다

BRIEF로 descriptor 계산 방법

  • keypoint 주변의 랜덤 픽셀 쌍 선택
  • 첫번째 픽셀 : 키 포인트를 중심으로 하는 가우시안 분포에서 선택
  • 두번째 픽셀 : 첫번째 픽셀을 중심으로 하는 2배 큰 분산을 가지는 분포에서 선택
  • if 첫번째 픽셀 > 두번째 픽셀 -> '1' else '0'
  • BRIEF는 128번 반복하여 keypoint마다 128bit vector 생성
  • ORB의 경우 BRIEF를 keypoint 방향에 따라 조종하여 rotation invariant
  • https://dhpark1212.tistory.com/entry/ORB-Oriented-FAST-and-Rotated-BRIEF 참고

bitpattern_31

  • 낮은 상관도와 높은 분산을 가진 sampling pair를 원한다
  • 낮은 상관도 : 새로운 pair가 새로운 정보를 제공
  • 높은 분산 : feature 분별이 뚜렷
  • sampling 쌍을 학습하여 좋은 256개의 pair를 남긴다
  • 그래서 bitpattern_31는 256개의 pair (256*4)개로 표현된다

4. orient 계산을 위한 준비

  • umax : circular patch의 row 끝 지점들을 의미(길이를 의미)
  • HALF_PATCH_SIZE는 15인데 그 결과 예를 들면 umax = [15, 15, 15, 15, 14, 14, 14, 13, .... , 1, 0] 이런식으로 된다

기존 FAST는 방향을 계산하지 않는다. rotation invarient를 위해 intensity centroid방법을 사용해서 방향을 계산한다


ORB 추출 ORBextractor::operator()

  • keypoint

1. 피라미드 계산

  • ComputePyramid(image) → <ORBextractor.cc>의 ORBextractor::ComputePyramid()

2. keypoint 구하기

  • ComputeKeyPointsOctTree(allKeypoints); → <ORBextractor.cc>의 ORBextractor::ComputeKeyPointsOctTree()
  • 그 결과 allKeypoints : pyramid level별로 keypoint들이 담겨있다

3. descriptors 초기화 및 생성

  • nkeypoints : keypoints의 개수
  • nkeypoints에 맞게 descriptors, keypoints 초기화

4. descriptors 구하기

  • 아래의 과정 nlevels(8)번 반복
  1. GaussianBlur
    • image pyramid에서 resize된 이미지(mvImagePyramid[level])에 블러 처리
  2. computeDescriptors() → <ORBextractor.cc>의 computeDescriptors()
    • 결과 : desc

5. 최종 keypoint 추가

  1. keypoint를 scale에 맞게 조정
  2. keypoints vector에 계산한 keypoint 추가

pyramid 구하기 ORBextractor::ComputePyramid()

  • nlevels(8)만큼 아래의 과정 반복
  • 결과 mvImagePyramid에 resize된 이미지가 담긴다

1. 이미지 사이즈 계산

  • 원하는 이미지 사이즈에 맞게 col, row 조정
  • 반복을 진행할 수록 이미지 사이즈가 줄어든다 (1.2배(mvInvScaleFactor)씩 줄어든다)
  • 원하는 이미지 피라미드 크기에 EDGE_THRESHOLD를 더해서 조금 더 크게 이미지 사이즈 만들기

2. 이미지 resize 수행

  • 지정한 이미지 사이즈로 이미지 resize() 수행
  • level 0일때는 수행하지 않는다

3. 이미지 가장자리 추가

  • opencv의 copyMakeBorder 함수 사용
  • 위, 아래, 왼쪽, 오른쪽 가장자리를 EDGE_THRESHOLD 정도 비우고 안에 이미지 복사
  • borderType은 BORDER_REFLECT_101으로 가장자리를 반대로 이어붙인다
    • 예시 : gfedcb|abcdefg|fedcba

keypoint 추출(FAST) ORBextractor::ComputeKeyPointsOctTree()

  • nlevels(8)만큼 1~3번 과정 반복

1. cell 설정

  • W = 30 : cell의 개수
  • nCols : col 개수
  • nRows : row 개수
  • wCell : cell 하나의 width
  • hCell : cell 하나의 height

2. FAST (keypoint 추출)

  • 모든 cell을 반복하며 FAST 알고리즘 진행
  • vToDistributeKeys : 모든 cell 즉, 전체 이미지에서 추출된 모든 keypoint가 들어있다
  1. iniThFAST(20)을 threshold로 사용하여 keypoint 추출
    • 만약 keypoint가 검출되지 않는다면, minThFAST(7)로 threshold를 낮춰서 FAST 수행
  2. 추출한 keypoint는 cell 안에 있는 값이므로 이미지에 맞게 (border 제외한 내부의 이미지) x, y값 조정 후 vToDistributeKeys 벡터에 넣는다
    • 이때의 x, y값은 border를 제외한 이미지 부분에 위치해있다
    • 더 정확히 말하면 border=19, 여기서 사용되는 minBorder=16로 위, 아래, 양옆에 적용된다

3. keypoints 후 작업

  • vToDistributeKeys값을 가지고 DistributeOctTree() 수행 → <ORBextractor.cc>의 ORBextractor::DistributeOctTree()
  • DistributeOctTree() 수행 결과 keypoint가 추출
  • 수행 결과 전체 이미지에 맞게 keypoint의 x, y값 조정

4. orientation 계산

  • nlevels(8)만큼 반복
  • computeOrientation() → <ORBextractor.cc>의 computeOrientation()

keypoint 분배 ORBextractor::DistributeOctTree()

  • return vResultKeys
  • 각 node마다 가장 좋은 keypoint 선택하여 return

1. 변수 생성

  • nIni : 초기 node의 개수 (가로길이 / 세로길이)
  • hX : node의 가로 길이 (가로길이 / nIni)
  • lNodes : ExtractorNode 리스트
  • vpIniNodes : nIni 크기의 초기 ExtractorNode 벡터

ExtractorNode 클래스

  • ExtractorNode 클래스는 옥타곤 트리 구조의 노드를 나타내며, 해당 노드에 속하는 키포인트들을 저장하고 분할하는 등의 역할을 수행
  • vKeys : 해당 node에 속하는 keypoint 저장 벡터
  • UL, UR, BL, BR: 노드의 네 꼭짓점인 상단 왼쪽(UL), 상단 오른쪽(UR), 하단 왼쪽(BL), 하단 오른쪽(BR) 점의 좌표
  • lit: 해당 노드가 속한 리스트(ExtractOctTree 클래스에서 사용되는 lNodes 리스트)에서의 반복자(iterator)
  • bNoMore: 노드가 더 이상 분할될 수 없는지 여부를 나타내는 플래그, 처음은 false로 지정된다
  • DivideNode() : 노드를 분할하여 새로운 4개의 하위 노드들을 생성하는 역할, 이를 통해 keypoint를 분할하여 관리할 수 있다

2. node 설정 (nIni번 반복)

  • ExtractorNode ni는 UL(왼쪽 위), UR(오른쪽 위), BL(왼쪽 아래), BR(오른쪽 아래)로 구성되어 있는데 이 범위를 정해준다
  • vKeys를 vToDistributeKeys 크기로 지정
  • lNodes에 ni 추가
  • vpIniNodes[i]를 최근에 추가된 node로 설정

3. node에 keypoint 할당 (vToDistributeKeys.size()번 반복)

  • vToDistributeKeys의 keypoint의 x좌표를 통해 어떤 node에 해당하는지 분류
  • 해당 node의 vKeys에 keypoint를 추가한다

4. node 정리 (lNodes만큼 반복)

  • node의 vKeys의 개수가 1개이면 bNoMore=true
  • vKeys가 없다면 node 지우기

5. node를 하위 node로 분할 (bFinish가 true가 될때까지 반복)

  1. 하위 node들로 분할하기(INodes.end()일때까지 반복)
    • if) node가 하나의 keypoint만 가지고 있다면 다음 node로
    • else)
      • DivideNode() 수행하여 4개의 하위 노드로 분할 → <ORBextractor.cc>의 ExtractorNode::DivideNode()
      • 하위 노드들 중에 keypoint가 1개보다 많다면 vSizeAndPointerToNode에 keypoint 수와 해당 node 추가
      • 자식 node를 다 추가한 후에는 현재 node 제거
  2. if) node의 개수가 feature의 수보다 많거나 모든 node가 하나의 point를 가지고 있으면 bFinish=true로 반복문 탈출
  3. else) node의 개수가 많으면 while(!bFinish) 반복
    • vSizeAndPointerToNode에 있는 node들을 분할
    • 원소의 개수가 많은 node부터 다시 DivideNode() 수행 (1, 2와 같은 방법으로 수행)

6. 각각의 node에서 best point 선택

  • vResultKeys에 각각 node 중에 가장 response가 높은 keypoint 선택

node 분할 ExtractorNode::DivideNode()

  1. UL, UR, DL, DR로 분할
  2. 해당 위치에 해당하는 keypoint도 분할
  3. 나누어진 node의 keypoint 수가 1개이면 bNoMore = true

keypoint마다의 angle 계산computeOrientation()

  • keypoint별로 angle 계산 IC_Angle() → <ORBextractor.cc>의 IC_Angle()

angle 계산 IC_Angle()

  • m_01 : y축 방향의 gradient 계산
  • m_10 : x축 방향(가로)의 gradient 계산
  • gradient를 계산할 때 거리 x 밝기값 으로 계산
  • center의 좌표가 (100,100)이라고 할 때, center[-3]은 (100, 97)을 의미
  • step은 다음 row로 넘어가는 것 의미
  • 결과는 m_01과 m_10의 즉, x와 y 방향의 각도가 나온다

descriptors 구하기 computeDescriptors()

  • descriptors 크기 : (keypoints.size(), 32)
  • keypoint 마다 computeOrbDescriptor() 수행 → <ORBextractor.cc>의 computeOrbDescriptor()
  • 이 결과 keypoint마다의 descriptor가 계산된다

실제 ORB descriptors 계산 computeOrbDescriptor()

  • 기존의 정해진 pattern을 활용하여 32bit의 descriptor 계산
  • 두 점의 좌표를 비교해가며 계산
  • |= 는 OR 연산 수행
  • 결과로 32크기의 descriptor 계산


Frame.cc

frame 생성 Frame::Frame()

  • mpORBvocabulary(voc)
  • mpORBextractorLeft(extractor)
  • mpORBextractorRight(static_cast<ORBextractor*>(NULL))
  • mTimeStamp(timeStamp)
  • mK(K.clone())
  • mDistCoef(distCoef.clone())
  • mbf(bf)
  • mThDepth(thDepth)

1. 기본 설정

  • mnId : frame ID
  • image pyramid 관련 값 가져오기

2. extract ORB

  • ExtractORB() → <Frame.cc>
  • ExtractORB(0,imGray);
  • 결과 : keypoint, descriptor 계산

3. 왜곡 보정 및 calibration 수행

  • mvKeys : Vector of keypoints
  • UndistortKeyPoints() → <Frame.cc>의 Frame::UndistortKeyPoints()

4. 이미지 bound 설정(처음 한번만 수행)

  • ComputeImageBounds() → <Frame.cc>의 Frame::ComputeImageBounds()
  • 그 결과 이미지의 최소, 최대 x,y 좌표가 구해진다
  • grid의 width와 height 계산
  • mfGridElementWidthInv : grid의 witdh의 inv
  • mfGridElementHeightInv : grid의 height의 inv
  • mbInitialComputations=false로 변경

5. keypoint를 grid로 분류

  • mb : Stereo baseline
  • AssignFeaturesToGrid() → <Frame.cc>의 Frame::AssignFeaturesToGrid()

extractORB Frame::ExtractORB()

  • ORB 추출
  • mono이기 때문에 flag가 0이고 mvKeys,mDescriptors를 계산한다
  • (*mpORBextractorLeft)(im,cv::Mat(),mvKeys,mDescriptors); → <ORBextractor.cc>의 operator()

calibration & 왜곡 보정 Frame::UndistortKeyPoints()

  1. if) 왜곡이 없으면 mvKeys 그대로
  2. keypoints의 x, y 좌표를 mat matrix 변수로 설정
  3. Undistort 수행
    • opencv의 undistortPoints() 이용
    • 미리 설정되어있는 K, DistCoef를 활용하여 x, y좌표 왜곡 보정
    • 즉, 왜곡과 calibration 수행
  4. 왜곡 보정된 x,y 좌표를 mvKeysUn에 저장

이미지 테두리 지정 Frame::ComputeImageBounds()

  1. if) 왜곡이 있으면 이미지의 4 꼭지점의 왜곡을 보정
    • opencv의 undistortPoints() 이용
  2. else) 그냥 이미지의 4 꼭지점 이용

keypoint를 grid로 분류 Frame::AssignFeaturesToGrid()

  • grid의 위치에 따라 keypoint를 할당
  • PosInGrid()로 keypoint가 해당 grid에 포함되는지 확인 → <Frame.cc>의 Frame::PosInGrid()

grid에 keypoint가 존재하는지 확인 Frame::PosInGrid()

  • undistored keypoint가 해당 범위안에 존재하는지 확인


ORBmatcher.cc

nmatches 계산 ORBmatcher::SearchForInitialization()

  • F1 : reference frame
  • F2 : 현재 frame

1. 두 frame의 descriptor간의 거리 계산

  • reference의 keypoint 수 만큼 반복
  1. F2 특정 영역의 feature의 index 계산
    • GetFeaturesInArea()
    • 계산 결과 : vIndices2
  2. d1 : F1의 descriptor
  3. vIndices2 만큼 반복
    • d2 : F2의 descriptor
    • dist : 두 descriptor 사이 거리 계산
      • DescriptorDistance(d1, d2) → <ORBmatcher.cc>의 ORBmatcher::DescriptorDistance()
    • 가장 짧은 2개의 dist를 bestDist, bestDist2에 저장
  4. 가장 짧은 distance일때의 F1, F2 인덱스를 서로 저장
  5. vMatchedDistance에 bestDist 저장 후 nmatches 증가
  6. F1과 F2의 angle 차이를 계산하여 그 구간의 rotHist에 인덱스 저장
    • rotHist : 회전 불변성 강화 , 회전 히스토그램은 특징점의 회전에 대한 일치 여부를 판단하고, 일치하는 특징점들을 선택하는 데 활용

2. rotHist에서 가장 큰 3개의 index 구하기

  • mbCheckOrientation라면
  • ComputeThreeMaxima() → <ORBmatcher.cc>의 ORBmatcher::ComputeThreeMaxima()

3. 매칭 결과 정확도 향상 + 잘못된 매칭 제거

  • rotHist 배열에서 큰 3개의 값에 해당하는 경우가 아니면 해당 인덱스의 매칭을 제거하고 nmatches 값 감소

4. 이전 match 업데이트

  • F2에서 매칭된 좌표들을 가져와서 mvbPrevMatched 업데이트
  • 이후의 특징점 매칭 과정에서 이전에 매칭된 특징점들을 참조할수있다

히스토그램에서 가장 큰 3개의 값 구하기 ORBmatcher::ComputeThreeMaxima()

  • 히스토그램에서 가장 큰 세 개의 값과 해당 값들의 인덱스를 계산하는 역할


LocalMapping.cc

LocalMapping 생성LocalMapping::LocalMapping()

생성시 멤버변수 초기화

  • mbMonocular(bMonocular)
  • mbResetRequested(false)
  • mbFinishRequested(false)
  • mbFinished(true)
  • mpMap(pMap)
  • mbAbortBA(false)
  • mbStopped(false)
  • mbStopRequested(false)
  • mbNotStop(false)
  • mbAcceptKeyFrames(true)


LoopClosing.cc

LoopClosing 생성 LoopClosing::LoopClosing()

생성시 멤버변수 초기화

  • mbResetRequested(false)
  • mbFinishRequested(false)
  • mbFinished(true)
  • mpMap(pMap)
  • mpKeyFrameDB(pDB)
  • mpORBVocabulary(pVoc)
  • mpMatchedKF(NULL)
  • mLastLoopKFid(0)
  • mbRunningGBA(false)
  • mbFinishedGBA(true)
  • mbStopGBA(false)
  • mpThreadGBA(NULL)
  • mbFixScale(bFixScale)
  • mnFullBAIdx(0)
  • mnCovisibilityConsistencyTh = 3

0개의 댓글