Add slam_in_autonomous_driving repo

This commit is contained in:
Hang Cui
2025-11-03 09:52:30 -08:00
parent 0e874755e4
commit df731f29fa
987 changed files with 752983 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
- Delimiters: [pb]
Language: TextProto
BasedOnStyle: google
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 8
UseTab: Never
...

View File

@@ -0,0 +1,57 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# latex
*.aux
*.blg
*.log
*.toc
*.gz
*.bbl
# cmake
**build**
bin
.idea
data/ch6/frame*.txt
data/ch6/*.png
data/ch7/**.pcd
**DS_Store
data/ch9/**.pcd
data/ch3/state.txt
data/ch3/gins.txt
data/ch3/gnss_output.txt
data/ch4/gins_preintg.txt

View File

@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.5)
project(slam_in_auto_driving)
set(CMAKE_BUILD_TYPE "Release")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "-w")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -g -ggdb ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS_DEBUG "-g ${CMAKE_CXX_FLAGS}")
add_definitions("-DCATKIN_ENABLE_TESTING=0")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
option(BUILD_WITH_UBUNTU1804 OFF)
include(cmake/packages.cmake)
include_directories(${PROJECT_SOURCE_DIR}/src)
include_directories(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/build/devel/include)
add_subdirectory(src)

View File

@@ -0,0 +1,22 @@
FROM osrf/ros:noetic-desktop-full
ADD docker/sources.list /etc/apt
RUN apt-get update \
&& apt-get install -y ros-noetic-pcl-ros ros-noetic-velodyne-msgs libopencv-dev libgoogle-glog-dev libeigen3-dev libsuitesparse-dev libpcl-dev libyaml-cpp-dev libbtbb-dev libgmock-dev unzip python3-tk\
&& mkdir /sad
COPY ./thirdparty/ /sad/
WORKDIR /sad/
RUN ls -la \
&& rm -rf ./Pangolin \
&& unzip ./Pangolin.zip \
&& mkdir ./Pangolin/build \
&& cmake ./Pangolin -B ./Pangolin/build \
&& make -j4 -C ./Pangolin/build install \
# && cmake ./g2o -B ./g2o/build \
# && make -j4 -C ./g2o/build install \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /sad

View File

@@ -0,0 +1,27 @@
# Look for csparse; note the difference in the directory specifications!
find_path(CSPARSE_INCLUDE_DIR NAMES cs.h
PATHS
/usr/include/suitesparse
/usr/include
/opt/local/include
/usr/local/include
/sw/include
/usr/include/ufsparse
/opt/local/include/ufsparse
/usr/local/include/ufsparse
/sw/include/ufsparse
PATH_SUFFIXES
suitesparse
)
find_library(CSPARSE_LIBRARY NAMES cxsparse libcxsparse
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(CSPARSE DEFAULT_MSG
CSPARSE_INCLUDE_DIR CSPARSE_LIBRARY)

View File

@@ -0,0 +1,94 @@
# Cholmod lib usually requires linking to a blas and lapack library.
# It is up to the user of this module to find a BLAS and link to it.
if (CHOLMOD_INCLUDE_DIR AND CHOLMOD_LIBRARIES)
set(CHOLMOD_FIND_QUIETLY TRUE)
endif (CHOLMOD_INCLUDE_DIR AND CHOLMOD_LIBRARIES)
find_path(CHOLMOD_INCLUDE_DIR
NAMES
cholmod.h
PATHS
$ENV{CHOLMODDIR}
${INCLUDE_INSTALL_DIR}
PATH_SUFFIXES
suitesparse
ufsparse
)
find_library(CHOLMOD_LIBRARY
NAMES cholmod libcholmod
PATHS $ENV{CHOLMODDIR} ${LIB_INSTALL_DIR})
set(CHOLMOD_LIBRARIES ${CHOLMOD_LIBRARY})
if(CHOLMOD_LIBRARIES)
get_filename_component(CHOLMOD_LIBDIR ${CHOLMOD_LIBRARIES} PATH)
find_library(AMD_LIBRARY
NAMES amd libamd
PATHS ${CHOLMOD_LIBDIR} $ENV{CHOLMODDIR} ${LIB_INSTALL_DIR})
if (AMD_LIBRARY)
set(CHOLMOD_LIBRARIES ${CHOLMOD_LIBRARIES} ${AMD_LIBRARY})
else ()
set(CHOLMOD_LIBRARIES FALSE)
endif ()
endif(CHOLMOD_LIBRARIES)
if(CHOLMOD_LIBRARIES)
find_library(COLAMD_LIBRARY NAMES colamd libcolamd PATHS ${CHOLMOD_LIBDIR} $ENV{CHOLMODDIR} ${LIB_INSTALL_DIR})
if (COLAMD_LIBRARY)
set(CHOLMOD_LIBRARIES ${CHOLMOD_LIBRARIES} ${COLAMD_LIBRARY})
else ()
set(CHOLMOD_LIBRARIES FALSE)
endif ()
endif(CHOLMOD_LIBRARIES)
if(CHOLMOD_LIBRARIES)
find_library(CAMD_LIBRARY NAMES camd libcamd PATHS ${CHOLMOD_LIBDIR} $ENV{CHOLMODDIR} ${LIB_INSTALL_DIR})
if (CAMD_LIBRARY)
set(CHOLMOD_LIBRARIES ${CHOLMOD_LIBRARIES} ${CAMD_LIBRARY})
else ()
set(CHOLMOD_LIBRARIES FALSE)
endif ()
endif(CHOLMOD_LIBRARIES)
if(CHOLMOD_LIBRARIES)
find_library(CCOLAMD_LIBRARY NAMES ccolamd libccolamd PATHS ${CHOLMOD_LIBDIR} $ENV{CHOLMODDIR} ${LIB_INSTALL_DIR})
if (CCOLAMD_LIBRARY)
set(CHOLMOD_LIBRARIES ${CHOLMOD_LIBRARIES} ${CCOLAMD_LIBRARY})
else ()
set(CHOLMOD_LIBRARIES FALSE)
endif ()
endif(CHOLMOD_LIBRARIES)
if(CHOLMOD_LIBRARIES)
find_library(CHOLMOD_METIS_LIBRARY NAMES metis libmetis PATHS ${CHOLMOD_LIBDIR} $ENV{CHOLMODDIR} ${LIB_INSTALL_DIR})
if (CHOLMOD_METIS_LIBRARY)
set(CHOLMOD_LIBRARIES ${CHOLMOD_LIBRARIES} ${CHOLMOD_METIS_LIBRARY})
endif ()
endif(CHOLMOD_LIBRARIES)
if(CHOLMOD_LIBRARIES)
find_library(CHOLMOD_SUITESPARSECONFIG_LIBRARY NAMES suitesparseconfig
PATHS ${CHOLMOD_LIBDIR} $ENV{CHOLMODDIR} ${LIB_INSTALL_DIR})
if (CHOLMOD_SUITESPARSECONFIG_LIBRARY)
set(CHOLMOD_LIBRARIES ${CHOLMOD_LIBRARIES} ${CHOLMOD_SUITESPARSECONFIG_LIBRARY})
endif ()
endif(CHOLMOD_LIBRARIES)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(CHOLMOD DEFAULT_MSG
CHOLMOD_INCLUDE_DIR CHOLMOD_LIBRARIES)
mark_as_advanced(CHOLMOD_LIBRARIES)

View File

@@ -0,0 +1,209 @@
# Ceres Solver - A fast non-linear least squares minimizer
# Copyright 2015 Google Inc. All rights reserved.
# http://ceres-solver.org/
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Google Inc. nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Author: alexs.mac@gmail.com (Alex Stewart)
#
# FindGlog.cmake - Find Google glog logging library.
#
# This module defines the following variables:
#
# GLOG_FOUND: TRUE iff glog is found.
# GLOG_INCLUDE_DIRS: Include directories for glog.
# GLOG_LIBRARIES: Libraries required to link glog.
#
# The following variables control the behaviour of this module:
#
# GLOG_INCLUDE_DIR_HINTS: List of additional directories in which to
# search for glog includes, e.g: /timbuktu/include.
# GLOG_LIBRARY_DIR_HINTS: List of additional directories in which to
# search for glog libraries, e.g: /timbuktu/lib.
#
# The following variables are also defined by this module, but in line with
# CMake recommended FindPackage() module style should NOT be referenced directly
# by callers (use the plural variables detailed above instead). These variables
# do however affect the behaviour of the module via FIND_[PATH/LIBRARY]() which
# are NOT re-called (i.e. search for library is not repeated) if these variables
# are set with valid values _in the CMake cache_. This means that if these
# variables are set directly in the cache, either by the user in the CMake GUI,
# or by the user passing -DVAR=VALUE directives to CMake when called (which
# explicitly defines a cache variable), then they will be used verbatim,
# bypassing the HINTS variables and other hard-coded search locations.
#
# GLOG_INCLUDE_DIR: Include directory for glog, not including the
# include directory of any dependencies.
# GLOG_LIBRARY: glog library, not including the libraries of any
# dependencies.
# Reset CALLERS_CMAKE_FIND_LIBRARY_PREFIXES to its value when
# FindGlog was invoked.
macro(GLOG_RESET_FIND_LIBRARY_PREFIX)
if (MSVC)
set(CMAKE_FIND_LIBRARY_PREFIXES "${CALLERS_CMAKE_FIND_LIBRARY_PREFIXES}")
endif (MSVC)
endmacro(GLOG_RESET_FIND_LIBRARY_PREFIX)
# Called if we failed to find glog or any of it's required dependencies,
# unsets all public (designed to be used externally) variables and reports
# error message at priority depending upon [REQUIRED/QUIET/<NONE>] argument.
macro(GLOG_REPORT_NOT_FOUND REASON_MSG)
unset(GLOG_FOUND)
unset(GLOG_INCLUDE_DIRS)
unset(GLOG_LIBRARIES)
# Make results of search visible in the CMake GUI if glog has not
# been found so that user does not have to toggle to advanced view.
mark_as_advanced(CLEAR GLOG_INCLUDE_DIR
GLOG_LIBRARY)
glog_reset_find_library_prefix()
# Note <package>_FIND_[REQUIRED/QUIETLY] variables defined by FindPackage()
# use the camelcase library name, not uppercase.
if (Glog_FIND_QUIETLY)
message(STATUS "Failed to find glog - " ${REASON_MSG} ${ARGN})
elseif (Glog_FIND_REQUIRED)
message(FATAL_ERROR "Failed to find glog - " ${REASON_MSG} ${ARGN})
else ()
# Neither QUIETLY nor REQUIRED, use no priority which emits a message
# but continues configuration and allows generation.
message("-- Failed to find glog - " ${REASON_MSG} ${ARGN})
endif ()
endmacro(GLOG_REPORT_NOT_FOUND)
# Handle possible presence of lib prefix for libraries on MSVC, see
# also GLOG_RESET_FIND_LIBRARY_PREFIX().
if (MSVC)
# Preserve the caller's original values for CMAKE_FIND_LIBRARY_PREFIXES
# s/t we can set it back before returning.
set(CALLERS_CMAKE_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}")
# The empty string in this list is important, it represents the case when
# the libraries have no prefix (shared libraries / DLLs).
set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "" "${CMAKE_FIND_LIBRARY_PREFIXES}")
endif (MSVC)
# Search user-installed locations first, so that we prefer user installs
# to system installs where both exist.
list(APPEND GLOG_CHECK_INCLUDE_DIRS
/usr/local/include
/usr/local/homebrew/include # Mac OS X
/opt/local/var/macports/software # Mac OS X.
/opt/local/include
/usr/include)
# Windows (for C:/Program Files prefix).
list(APPEND GLOG_CHECK_PATH_SUFFIXES
glog/include
glog/Include
Glog/include
Glog/Include)
list(APPEND GLOG_CHECK_LIBRARY_DIRS
/usr/local/lib
/usr/local/homebrew/lib # Mac OS X.
/opt/local/lib
/usr/lib)
# Windows (for C:/Program Files prefix).
list(APPEND GLOG_CHECK_LIBRARY_SUFFIXES
glog/lib
glog/Lib
Glog/lib
Glog/Lib)
# Search supplied hint directories first if supplied.
find_path(GLOG_INCLUDE_DIR
NAMES glog/logging.h
PATHS ${GLOG_INCLUDE_DIR_HINTS}
${GLOG_CHECK_INCLUDE_DIRS}
PATH_SUFFIXES ${GLOG_CHECK_PATH_SUFFIXES})
if (NOT GLOG_INCLUDE_DIR OR
NOT EXISTS ${GLOG_INCLUDE_DIR})
glog_report_not_found(
"Could not find glog include directory, set GLOG_INCLUDE_DIR "
"to directory containing glog/logging.h")
endif (NOT GLOG_INCLUDE_DIR OR
NOT EXISTS ${GLOG_INCLUDE_DIR})
find_library(GLOG_LIBRARY NAMES glog
PATHS ${GLOG_LIBRARY_DIR_HINTS}
${GLOG_CHECK_LIBRARY_DIRS}
PATH_SUFFIXES ${GLOG_CHECK_LIBRARY_SUFFIXES})
if (NOT GLOG_LIBRARY OR
NOT EXISTS ${GLOG_LIBRARY})
glog_report_not_found(
"Could not find glog library, set GLOG_LIBRARY "
"to full path to libglog.")
endif (NOT GLOG_LIBRARY OR
NOT EXISTS ${GLOG_LIBRARY})
# Mark internally as found, then verify. GLOG_REPORT_NOT_FOUND() unsets
# if called.
set(GLOG_FOUND TRUE)
# Glog does not seem to provide any record of the version in its
# source tree, thus cannot extract version.
# Catch case when caller has set GLOG_INCLUDE_DIR in the cache / GUI and
# thus FIND_[PATH/LIBRARY] are not called, but specified locations are
# invalid, otherwise we would report the library as found.
if (GLOG_INCLUDE_DIR AND
NOT EXISTS ${GLOG_INCLUDE_DIR}/glog/logging.h)
glog_report_not_found(
"Caller defined GLOG_INCLUDE_DIR:"
" ${GLOG_INCLUDE_DIR} does not contain glog/logging.h header.")
endif (GLOG_INCLUDE_DIR AND
NOT EXISTS ${GLOG_INCLUDE_DIR}/glog/logging.h)
# TODO: This regex for glog library is pretty primitive, we use lowercase
# for comparison to handle Windows using CamelCase library names, could
# this check be better?
string(TOLOWER "${GLOG_LIBRARY}" LOWERCASE_GLOG_LIBRARY)
if (GLOG_LIBRARY AND
NOT "${LOWERCASE_GLOG_LIBRARY}" MATCHES ".*glog[^/]*")
glog_report_not_found(
"Caller defined GLOG_LIBRARY: "
"${GLOG_LIBRARY} does not match glog.")
endif (GLOG_LIBRARY AND
NOT "${LOWERCASE_GLOG_LIBRARY}" MATCHES ".*glog[^/]*")
# Set standard CMake FindPackage variables if found.
if (GLOG_FOUND)
set(GLOG_INCLUDE_DIRS ${GLOG_INCLUDE_DIR})
set(GLOG_LIBRARIES ${GLOG_LIBRARY})
endif (GLOG_FOUND)
glog_reset_find_library_prefix()
# Handle REQUIRED / QUIET optional arguments.
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Glog DEFAULT_MSG
GLOG_INCLUDE_DIRS GLOG_LIBRARIES)
# Only mark internal variables as advanced if we found glog, otherwise
# leave them visible in the standard GUI for the user to set manually.
if (GLOG_FOUND)
mark_as_advanced(FORCE GLOG_INCLUDE_DIR
GLOG_LIBRARY)
endif (GLOG_FOUND)

View File

@@ -0,0 +1,137 @@
# - Try to find Pango
# Once done, this will define
#
# PANGO_FOUND - system has Pango
# PANGO_INCLUDE_DIRS - the Pango include directories
# PANGO_LIBRARIES - link these to use Pango
# PANGO_DEFINITIONS - compiler flags
# PANGOCAIRO_FOUND - system has Pangocairo
# PANGOFT2_FOUND - system has Pangoft2
FIND_PACKAGE(PkgConfig)
# pango main library
PKG_CHECK_MODULES(PC_PANGO QUIET pango)
SET(PANGO_DEFINITIONS ${PC_PANGO_CFLAGS_OTHER})
SET(PANGO_INCLUDE_HINTS
${PC_PANGO_INCLUDEDIR}
${PC_PANGO_INCLUDE_DIRS}
$ENV{PANGO_HOME}/include
$ENV{PANGO_ROOT}/include
/usr/local/include
/usr/include
/pango/include
)
FIND_PATH(PANGO_INCLUDE_DIR
NAMES pango/pango.h
HINTS ${PANGO_INCLUDE_HINTS}
PATH_SUFFIXES pango pango-1.0 pango1.0
)
SET(PANGO_LIBRARY_HINTS
${PC_PANGO_LIBDIR}
${PC_PANGO_LIBRARY_DIRS}
$ENV{PANGO_HOME}/lib
$ENV{PANGO_ROOT}/lib
/usr/local/lib
/usr/lib
/lib
/pango/lib
)
FIND_LIBRARY(PANGO_LIBRARY
NAMES pango PANGO PANGO-1.0 pango-1.0
HINTS ${PANGO_LIBRARY_HINTS}
PATH_SUFFIXES pango pango-1.0 pango1.0
)
SET(PANGO_INCLUDE_DIRS ${PANGO_INCLUDE_DIR})
SET(PANGO_LIBRARIES ${PANGO_LIBRARY})
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Pango DEFAULT_MSG PANGO_INCLUDE_DIRS PANGO_LIBRARIES)
MARK_AS_ADVANCED(PANGO_INCLUDE_DIR PANGO_LIBRARY)
IF(PANGO_FOUND)
# dependencies
FIND_PACKAGE(GLIB2 REQUIRED)
IF(GLIB2_FOUND)
SET(PANGO_INCLUDE_DIRS ${PANGO_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS})
SET(PANGO_LIBRARIES ${PANGO_LIBRARIES} ${GLIB2_LIBRARIES})
ENDIF()
FIND_PACKAGE(GObject REQUIRED)
IF(GLIB2_FOUND)
SET(PANGO_INCLUDE_DIRS ${PANGO_INCLUDE_DIRS} ${GOBJECT_INCLUDE_DIRS})
SET(PANGO_LIBRARIES ${PANGO_LIBRARIES} ${GOBJECT_LIBRARIES})
ENDIF()
# pangocairo
PKG_CHECK_MODULES(PC_PANGOCAIRO QUIET pangocairo)
FIND_PATH(PANGOCAIRO_INCLUDE_DIRS
NAMES pango/pangocairo.h
HINTS ${PC_PANGOCAIRO_INCLUDEDIR}
${PC_PANGOCAIRO_INCLUDE_DIRS}
$ENV{PANGOCAIRO_HOME}/include
$ENV{PANGOCAIRO_ROOT}/include
/pangocairo/include
${PANGO_INCLUDE_HINTS}
PATH_SUFFIXES pango pango-1.0 pangocairo libpangocairo-1.0 pangocairo1.0
)
IF(PANGOCAIRO_INCLUDE_DIRS)
SET(PANGO_INCLUDE_DIRS ${PANGO_INCLUDE_DIRS} ${PANGOCAIRO_INCLUDE_DIRS})
ENDIF()
FIND_LIBRARY(PANGOCAIRO_LIBRARIES
NAMES pangocairo PANGOcairo PANGOcairo-1.0 pangocairo-1.0 libpangocairo-1.0
HINTS ${PC_PANGOCAIRO_LIBDIR}
${PC_PANGOCAIRO_LIBRARY_DIRS}
$ENV{PANGOCAIRO_HOME}/lib
$ENV{PANGOCAIRO_ROOT}/lib
${PANGO_LIBRARY_HINTS}
PATH_SUFFIXES pango pango-1.0 pangocairo libpangocairo-1.0 pangocairo1.0
)
IF(PANGOCAIRO_LIBRARIES)
SET(PANGO_LIBRARIES ${PANGO_LIBRARIES} ${PANGOCAIRO_LIBRARIES})
ENDIF()
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Pangocairo DEFAULT_MSG PANGOCAIRO_INCLUDE_DIRS PANGOCAIRO_LIBRARIES)
# pangoft2
PKG_CHECK_MODULES(PC_PANGOFT2 QUIET pangoft2)
FIND_PATH(PANGOFT2_INCLUDE_DIRS
NAMES pango/pangoft2.h
HINTS ${PC_PANGOFT2_INCLUDEDIR}
${PC_PANGOFT2_INCLUDE_DIRS}
$ENV{PANGOFT2_HOME}/include
$ENV{PANGOFT2_ROOT}/include
/pangoft2/include
${PANGO_INCLUDE_HINTS}
PATH_SUFFIXES pango pangoft2 libpangoft2-1.0 pangoft21.0
)
IF(PANGOFT2_INCLUDE_DIRS)
SET(PANGOFT2_INCLUDE_DIRS ${PANGO_INCLUDE_DIRS} ${PANGOFT2_INCLUDE_DIRS})
FIND_PACKAGE(Freetype REQUIRED)
IF(FREETYPE_FOUND)
SET(PANGOFT2_INCLUDE_DIRS ${PANGOFT2_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIRS})
ENDIF()
FIND_PACKAGE(Fontconfig REQUIRED)
IF(FONTCONFIG_FOUND)
SET(PANGOFT2_INCLUDE_DIRS ${PANGOFT2_INCLUDE_DIRS} ${FONTCONFIG_INCLUDE_DIRS})
ENDIF()
ENDIF()
FIND_LIBRARY(PANGOFT2_LIBRARIES
NAMES pangoft2 pangoft2-1.0 libpangoft2-1.0
HINTS ${PC_PANGOFT2_LIBDIR}
${PC_PANGOFT2_LIBRARY_DIRS}
$ENV{PANGOFT2_HOME}/lib
$ENV{PANGOFT2_ROOT}/lib
${PANGO_LIBRARY_HINTS}
PATH_SUFFIXES pango pangoft2 libpangoft2-1.0 pangoft21.0
)
IF(PANGOFT2_LIBRARIES)
SET(PANGOFT2_LIBRARIES ${PANGO_LIBRARIES} ${PANGOFT2_LIBRARIES})
ENDIF()
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Pangoft2 DEFAULT_MSG PANGOFT2_INCLUDE_DIRS PANGOFT2_LIBRARIES)
MARK_AS_ADVANCED(PANGOCAIRO_INCLUDE_DIRS PANGOCAIRO_LIBRARIES PANGOFT2_INCLUDE_DIRS PANGOFT2_LIBRARIES)
ENDIF()

View File

@@ -0,0 +1,132 @@
# 引入该目录下的.cmake文件
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
# livox ros driver
add_subdirectory(thirdparty/livox_ros_driver)
include_directories(${CMAKE_BINARY_DIR}/devel/include) # 引用ros生成的msg header
# eigen 3
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIRS})
# sophus
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/sophus)
# glog
find_package(Glog REQUIRED)
include_directories(${Glog_INCLUDE_DIRS})
# csparse
find_package(CSparse REQUIRED)
include_directories(${CSPARSE_INCLUDE_DIR})
# cholmod
find_package(Cholmod REQUIRED)
include_directories(${CHOLMOD_INCLUDE_DIRS})
# pcl
find_package(PCL REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
# opencv
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
# g2o 使用thirdparty中的
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/g2o/)
set(g2o_libs
${PROJECT_SOURCE_DIR}/thirdparty/g2o/lib/libg2o_stuff.so
${PROJECT_SOURCE_DIR}/thirdparty/g2o/lib/libg2o_core.so
# ${PROJECT_SOURCE_DIR}/thirdparty/g2o/lib/libg2o_solver_cholmod.so
${PROJECT_SOURCE_DIR}/thirdparty/g2o/lib/libg2o_solver_dense.so
${PROJECT_SOURCE_DIR}/thirdparty/g2o/lib/libg2o_solver_csparse.so
${PROJECT_SOURCE_DIR}/thirdparty/g2o/lib/libg2o_csparse_extension.so
${PROJECT_SOURCE_DIR}/thirdparty/g2o/lib/libg2o_types_sba.so
${CSPARSE_LIBRARY}
${CHOLMOD_LIBRARY}
)
# ros
# 为了2D scan, pointcloud2
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
sensor_msgs
pcl_ros
pcl_conversions
)
include_directories(${catkin_INCLUDE_DIRS})
find_package(Pangolin REQUIRED)
include_directories(${Pangolin_INCLUDE_DIRS})
# yaml-cpp
find_package(yaml-cpp REQUIRED)
include_directories(${yaml-cpp_INCLUDE_DIRS})
# 其他thirdparty下的内容
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/velodyne/include)
if(BUILD_WITH_UBUNTU1804)
function(extract_file filename extract_dir)
message(STATUS "Extract ${filename} to ${extract_dir} ...")
set(temp_dir ${extract_dir})
if(EXISTS ${temp_dir})
file(REMOVE_RECURSE ${temp_dir})
endif()
file(MAKE_DIRECTORY ${temp_dir})
execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xvzf ${filename}
WORKING_DIRECTORY ${temp_dir})
endfunction()
set(TBB_ROOT_DIR ${PROJECT_SOURCE_DIR}/thirdparty/tbb/oneTBB-2019_U8/oneTBB-2019_U8)
set(TBB_BUILD_DIR "tbb_build_dir=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}")
set(TBB_BUILD_PREFIX "tbb_build_prefix=tbb")
extract_file(${PROJECT_SOURCE_DIR}/thirdparty/tbb/2019_U8.tar.gz ${PROJECT_SOURCE_DIR}/thirdparty/tbb/oneTBB-2019_U8)
include(${TBB_ROOT_DIR}/cmake/TBBBuild.cmake)
#message(STATUS "======TBB_BUILD_DIR = ${TBB_BUILD_DIR}")
#message(STATUS "======TBB_BUILD_PREFIX = ${TBB_BUILD_PREFIX}")
tbb_build(TBB_ROOT ${TBB_ROOT_DIR}
compiler=gcc-9
stdver=c++17
${TBB_BUILD_DIR}
${TBB_BUILD_PREFIX}
CONFIG_DIR
TBB_DIR)
find_package(TBB REQUIRED)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty/tbb/oneTBB-2019_U8/oneTBB-2019_U8/include)
link_directories(${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/tbb_release)
set(third_party_libs
${catkin_LIBRARIES}
${g2o_libs}
${OpenCV_LIBS}
${PCL_LIBRARIES}
${Pangolin_LIBRARIES}
glog gflags
${yaml-cpp_LIBRARIES}
yaml-cpp
TBB::tbb
)
else()
set(third_party_libs
${catkin_LIBRARIES}
${g2o_libs}
${OpenCV_LIBS}
${PCL_LIBRARIES}
${Pangolin_LIBRARIES}
glog gflags
${yaml-cpp_LIBRARIES}
yaml-cpp
tbb
)
endif ()

View File

@@ -0,0 +1,15 @@
preprocess:
lidar_type: 1 # 1 for Livox serials LiDAR, 2 for Velodyne LiDAR, 3 for ouster LiDAR,
scan_line: 6
time_scale: 1e-3 # 兼容不同数据集的时间单位仅对Velodyne LiDAR(lidar_type=2)生效
mapping:
extrinsic_T: [ 0.04165, 0.02326, -0.0284 ]
extrinsic_R: [ 1, 0, 0,
0, 1, 0,
0, 0, 1 ]
point_filter_num: 3
max_iteration: 10
imu_has_gravity: true
with_ui: false

View File

@@ -0,0 +1,37 @@
YAML: 1.0
bag_path: /home/xiang/Data/nclt/20130110.bag
lio_yaml: ./config/velodyne_nclt.yaml
map_data: ./data/ch9/map_data/
origin:
- 276726.5626579862
- 4685961.4821172
- 272.809
rtk_has_rot: false
rtk_outlier_th: 1.0
rtk_pos_noise: 2.0
rtk_height_noise_ratio: 10.0
rtk_ang_noise: 5.0
lidar_continuous_num: 5
rtk_ext:
t: [0, 0.24, -0.283]
loop_closing:
min_id_interval: 100
min_distance: 30
skip_id: 5
ndt_score_th: 4.5
common:
dataset: nclt
lid_topic: points_raw
imu_topic: imu_raw
time_sync_en: false
preprocess:
lidar_type: 2
scan_line: 32
time_scale: 1e-3
mapping:
extrinsic_est_en: true
extrinsic_T: [0, 0, 0.28]
extrinsic_R: [1, 0, 0, 0, 1, 0, 0, 0, 1]
point_filter_num: 3
max_iteration: 10
imu_has_gravity: true

View File

@@ -0,0 +1,48 @@
common:
dataset: "nclt"
lid_topic: "points_raw"
imu_topic: "imu_raw"
time_sync_en: false # ONLY turn on when external time synchronization is really not possible
preprocess:
lidar_type: 2 # 1 for Livox serials LiDAR, 2 for Velodyne LiDAR, 3 for ouster LiDAR,
scan_line: 32
blind: 4
time_scale: 1e-3 # 兼容不同数据集的时间单位仅对Velodyne LiDAR(lidar_type=2)生效
mapping:
acc_cov: 0.1
gyr_cov: 0.1
b_acc_cov: 0.0001
b_gyr_cov: 0.0001
fov_degree: 180
det_range: 100.0
extrinsic_est_en: true # true: enable the online estimation of IMU-LiDAR extrinsic
extrinsic_T: [ 0, 0, 0.28 ]
extrinsic_R: [ 1, 0, 0,
0, 1, 0,
0, 0, 1 ]
publish:
path_publish_en: false
scan_publish_en: true # false: close all the point cloud output
scan_effect_pub_en: true # true: publish the pointscloud of effect point
dense_publish_en: false # false: low down the points number in a global-frame point clouds scan.
scan_bodyframe_pub_en: true # true: output the point cloud scans in IMU-body-frame
path_save_en: true # 保存轨迹,用于精度计算和比较
pcd_save:
pcd_save_en: false
interval: -1 # how many LiDAR frames saved in each pcd file;
# -1 : all frames will be saved in ONE pcd file, may lead to memory crash when having too many frames.
IMU:
NoiseGyro: 1e-2 # 3 # 2.44e-4 #1e-3 # rad/s^0.5
NoiseAcc: 1e-1 #2 # 1.47e-3 #1e-2 # m/s^1.5
GyroWalk: 1e-6 # rad/s^1.5
AccWalk: 1e-4 # m/s^2.5
Frequency: 200.0
point_filter_num: 7
max_iteration: 4

View File

@@ -0,0 +1,15 @@
preprocess:
lidar_type: 2 # 1 for Livox serials LiDAR, 2 for Velodyne LiDAR, 3 for ouster LiDAR,
scan_line: 32
time_scale: 1e-3 # 兼容不同数据集的时间单位仅对Velodyne LiDAR(lidar_type=2)生效
mapping:
extrinsic_T: [ 0, 0, 0.28 ]
extrinsic_R: [ 1, 0, 0,
0, 1, 0,
0, 0, 1 ]
point_filter_num: 10
max_iteration: 3
imu_has_gravity: true
with_ui: false

View File

@@ -0,0 +1,15 @@
preprocess:
lidar_type: 2 # 1 for Livox serials LiDAR, 2 for Velodyne LiDAR, 3 for ouster LiDAR,
scan_line: 32
time_scale: 1e3 # 兼容不同数据集的时间单位
mapping:
extrinsic_T: [ 0, 0, -0.28 ]
extrinsic_R: [ 2.67949e-08,-1, 0,
1,2.67949e-08, 0,
0, 0, 1, ]
point_filter_num: 10
max_iteration: 3
imu_has_gravity: false
with_ui: false

View File

@@ -0,0 +1,15 @@
preprocess:
lidar_type: 2 # 1 for Livox serials LiDAR, 2 for Velodyne LiDAR, 3 for ouster LiDAR,
scan_line: 32
time_scale: 1e-3 # 兼容不同数据集的时间单位
mapping:
extrinsic_T: [ -0.5, 1.4, 1.5 ]
extrinsic_R: [ 1, 0, 0,
0, 1, 0,
0, 0, 1 ]
point_filter_num: 10
max_iteration: 3
imu_has_gravity: false
with_ui: false

View File

@@ -0,0 +1,23 @@
common:
dataset: "wxb"
lid_topic: "points_raw"
imu_topic: "imu_raw"
time_sync_en: false # ONLY turn on when external time synchronization is really not possible
preprocess:
lidar_type: 2 # 1 for Livox serials LiDAR, 2 for Velodyne LiDAR, 3 for ouster LiDAR,
scan_line: 32
time_scale: 1e-3 # 兼容不同数据集的时间单位仅对Velodyne LiDAR(lidar_type=2)生效
mapping:
extrinsic_T: [ 0, 0, 0 ]
extrinsic_R: [ 1, 0, 0,
0, 1, 0,
0, 0, 1 ]
point_filter_num: 10
max_iteration: 3
imu_has_gravity: false
with_ui: false

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,892 @@
0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426297.41856861 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426297.51855731 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426297.61857772 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426297.71855021 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426297.81854868 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426297.91858721 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.01854825 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.11854506 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.21854734 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.3185637 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.41855741 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.5185554 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.61854768 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.71856046 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.81854892 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426298.91856313 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.01855373 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.11854792 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.2185626 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.31856155 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.41854763 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.51855254 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.61856437 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.71856618 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.81854439 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426299.91855526 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.01856732 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.11854553 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.21855378 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.31856799 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.41856647 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.51855326 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.61855817 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.71860671 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.81854749 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426300.91854763 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.01855397 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.11855412 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.21857858 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.31854963 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.41855764 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.51856136 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.61854291 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.71858835 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.81855226 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426301.91856337 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.01854563 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.11859298 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.21855092 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.31856084 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.41858602 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.51856112 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.61855888 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.71855569 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.81854916 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426302.91855097 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.0185535 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.11855578 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.21856213 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.31855488 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.4185605 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.5185492 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.61857009 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.71855688 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.81856275 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426303.91855979 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.01856065 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.11855245 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.21855068 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.31855774 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.41855001 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.51854563 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.6185689 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.71856403 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.81855226 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426304.9185648 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.0185473 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.1185503 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.21870112 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.31854892 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.41855574 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.51854491 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.61856389 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.71855688 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.81855607 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426305.91855741 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.01855731 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.11854911 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.21855927 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.31854749 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.41855431 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.51854849 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.61854887 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.71854234 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.81855059 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426306.91854811 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.01855469 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.1185503 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.2185452 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.31855083 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.4185555 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.51856112 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.61855888 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.71859765 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.81854773 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426307.91854692 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.01856184 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.1185503 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.21856332 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.31856537 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.41854572 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.51855135 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.61856461 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.71854663 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.8185513 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426308.91855311 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.01854658 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.11856651 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.2185688 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.31855154 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.4185586 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.51855874 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.61854839 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.71855593 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.8185513 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426309.91856074 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.01854396 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.1185596 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.21855903 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.31856441 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.41855264 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.51855731 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.6185627 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.7185545 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.81854963 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426310.91855669 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.01855111 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.11854815 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.21856189 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.31856799 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.41855311 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.51856184 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.61855412 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.71858001 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.81855226 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426311.91855145 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.01855111 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.11855459 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.21855497 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.31856227 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.41855383 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.51855183 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.61856174 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.71856618 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.81856489 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426312.9186008 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.0185523 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.11852789 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.21855664 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.31855798 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.41855335 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.51858568 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.61859655 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.71855688 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.81855106 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426313.91855955 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.01867414 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.11855602 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.21856809 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.3185513 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.41854835 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.51856136 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.61855149 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.71855998 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.81854749 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426314.91853499 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.01855278 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.11855245 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.21855855 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.31855297 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.41854858 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.51855493 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.61858511 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.71869206 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.81854486 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426315.91855383 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.01854706 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.11855102 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.21854925 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.31855631 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.41855574 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.51859736 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.61855125 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.71863365 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.81855559 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426316.91856599 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.01854849 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.11854529 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.21855021 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.31858039 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.41855001 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.51855421 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.6185658 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.71854806 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.81855083 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426317.9185555 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.01855159 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.11855149 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.21855712 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.31854916 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.41855192 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.51855803 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.61856222 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.71857047 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.81855702 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426318.91856647 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.0185535 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.1185472 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.21855569 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.31855822 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.41863823 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.51856351 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.61855936 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.71856427 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.81856489 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426319.91855478 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.01854777 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.11854219 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.21856475 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.31855464 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.41855645 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.51854563 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.61855006 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.71856689 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.81854367 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426320.91856384 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.0185554 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.11863136 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.21854877 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.31859922 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.41922092 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.5185535 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.61860013 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.71926475 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.81854272 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426321.91855621 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.01856494 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.11855006 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.21855021 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.3185575 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.41855502 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.51860952 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.61855173 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.71856165 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.81863499 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426322.91854787 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.01855469 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.1185441 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.21854854 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.31855488 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.4185586 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.51855087 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.61855316 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.71854949 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.81855893 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426323.91855478 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.01858521 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.11855078 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.21857166 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.31855011 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.41855717 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.51847386 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.61855507 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.7185638 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.81854463 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426324.91859412 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.01855707 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.11854839 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.21857023 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.3185544 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.41854787 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.51854849 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.61857271 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.71855855 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.8185451 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426325.91875482 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.01855922 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.11854029 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.21861172 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.31856465 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.41854787 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.51856041 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.61858654 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.71855497 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.81854773 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426326.91855955 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.01856565 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.11855364 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.21855402 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.31854892 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.41854572 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.51855445 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.6185565 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.71856833 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.81855917 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426327.91855145 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.01854944 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.11854935 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.21856976 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.31856537 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.41854644 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.51854682 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.61855078 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.71855736 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.81855345 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426328.91856217 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426329.01855421 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426329.11855483 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426329.21857023 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426329.31868935 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426329.4185462 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426329.51854992 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426329.61866689 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426329.7185576 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426329.91854811 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.0185585 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.11855936 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.21856022 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.31855893 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.41855597 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.51855278 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.61855412 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.71855235 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.81854749 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426330.91856527 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.01855302 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.11854553 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.21855116 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.31871295 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.41855621 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.51854992 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.61855245 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.71856165 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.81855559 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426331.91854382 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.01855469 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.11855102 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.2185514 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.31859636 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.41855454 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.51855278 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.61855412 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.71856761 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.81855798 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426332.91856003 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.01856875 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.11855459 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.21855736 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.31854916 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.41855478 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.51855898 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.61854982 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.7185607 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.81854486 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426333.9185493 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.01855302 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.11854935 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.21855044 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.31856132 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.4185586 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.51855421 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.61855412 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.71855187 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.81854796 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426334.91856647 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.01855421 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.11855316 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.21854901 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.31855679 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.41856337 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.51856327 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.61854315 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.71856713 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.81855369 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426335.91855884 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.01856184 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.11854196 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.21855831 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.31859112 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.41866875 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.51856303 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.61857271 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.71863818 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.81855559 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426336.91855717 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.01855588 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.11854601 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.21855831 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.31854987 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.41855335 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.51855421 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.61855221 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.71857429 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.81855559 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426337.91854763 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.01855659 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.11854839 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.21855617 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.31856012 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.41862464 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.51855373 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.61855292 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.71856403 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.81854773 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426338.91854811 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.01855636 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.11855245 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.2185545 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.3185637 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.41855621 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.51855278 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.61856174 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.71855617 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.81854796 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426339.91856337 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.01856136 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.11854672 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.21855426 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.31856465 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.41855741 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.51854682 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.61855483 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.71856809 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.81854534 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426340.91858029 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.01856446 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.11854887 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.21855783 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.31856227 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.41856003 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.51855302 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.61855412 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.71856141 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.81855035 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426341.91855645 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.01855135 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.11855459 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.21856141 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.31855178 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.41854787 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.51855516 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.6185596 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.71855569 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.81854463 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426342.91855574 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.01856184 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.11855197 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.2185545 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.31855702 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.41855431 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.51856279 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.61856031 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.71856809 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.81867027 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426343.91855931 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.01856017 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.11855698 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.21855903 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.31859398 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.41854811 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.5185461 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.61856222 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.71904683 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.81854558 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426344.91859341 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.01856136 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.11854839 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.21856165 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.31856585 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.41856647 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.5186615 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.6185596 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.7185533 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.81855512 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426345.91855288 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.01855803 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.11855412 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.21856713 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.31855226 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.41855812 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.51854992 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.61856675 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.71855378 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.81854725 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426346.91854572 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.0185585 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.11855221 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.21855521 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.31855178 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.41855931 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.51862788 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.61855626 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.71856236 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.81855416 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426347.91855049 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.01855969 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.11855006 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.21856976 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.31856322 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.41854835 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.51855588 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.61855364 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.7185514 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.8185482 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426348.91855049 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.01854992 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.11856389 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.21855593 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.31856394 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.41855836 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.51857424 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.61859012 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.71860647 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.81854415 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426349.91855311 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.01854944 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.11855316 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.21856332 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.31854987 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.41855645 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.5185554 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.61856365 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.71854591 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.81854749 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426350.91867995 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.01859403 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.11854672 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.21855187 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.31855702 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.41854501 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.51855469 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.61883068 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.71855617 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.81854558 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426351.91856098 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.01855826 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.11855507 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.21865439 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.31855345 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.41855288 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.51855636 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.61857724 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.7185545 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.81854463 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426352.91856647 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.01856351 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.11854982 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.21854591 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.31859183 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.41856599 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.51855278 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.61856079 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.71856833 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.8185575 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426353.9185586 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.01855946 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.11855197 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.21858573 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.31854939 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.41856408 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.51855445 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.61855125 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.71855426 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.81855488 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426354.91857147 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.01857066 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.11855054 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.21856165 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.31855631 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.41854978 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.51855493 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.61856461 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.71855092 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.81855249 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426355.91859174 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.01856661 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.1185503 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.21854973 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.31855059 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.41855693 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.51855421 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.61855483 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.71856308 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.81855488 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426356.91855145 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.01855421 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.11855197 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.21857595 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.31854749 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.41855717 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.51855063 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.61857462 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.7185638 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.81855106 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426357.91855979 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.01855612 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.11855698 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.21855402 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.31859875 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.41855836 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.51854897 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.6185534 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.7185638 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.81854939 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426358.91854906 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.01856112 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.11854982 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.21855545 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.31855488 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.41855478 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.51855779 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.6185534 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.71856713 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.81855369 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426359.91855836 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.01855111 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.1185472 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.21856236 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.31854868 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.41855788 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.51855206 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.61854887 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.71856451 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.81855345 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426360.91854882 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.01856041 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.11855936 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.21855712 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.31856132 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.41856337 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.51855111 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.61855912 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.71858287 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.81854224 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426361.91858959 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.01855898 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.11854267 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.21856284 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.31856132 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.41854906 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.51856685 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.61854553 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.71856356 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.81854534 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426362.91855121 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.01860404 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.11862469 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.2185514 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.3185544 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.41860628 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.51854849 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.61855245 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.71855164 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.81882358 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426363.91855311 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.01855397 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.11854672 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.2185955 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.31856561 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.41854811 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.51855516 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.61855626 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.71855545 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.81861258 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426364.91855145 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.01855612 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.11857772 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.2185607 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.3186028 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.4186058 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.51855135 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.61855507 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.71856785 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.81855226 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426365.91856098 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.01854801 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.11855578 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.21855998 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.31857276 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.41854739 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.51854968 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.61856246 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.71855164 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.81859517 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426366.91856432 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.01854873 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.11856437 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.21855974 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.31856012 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.41855526 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.51855111 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.6185894 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.71854472 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.8185432 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426367.91856122 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.0185349 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.11853671 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.2185483 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.31854463 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.41854525 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.51864409 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.61855292 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.71856165 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.8185389 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426368.91857791 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.01856041 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.11853957 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.21854734 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.31854296 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.41854811 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.51855636 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.61855125 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.71855092 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.81854701 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426369.91867042 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.01858425 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.11854553 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.21857929 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.31854725 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.41854906 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.5185535 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.61854744 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.71854782 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.81854486 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426370.91855645 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.01855302 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.11853552 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.21856117 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.31856132 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.41853929 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.51856351 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.61855984 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.71855426 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.81854296 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426371.91860223 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.01856399 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.11853671 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.21855378 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.31854582 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.41855121 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.51853585 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.61859322 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.71854854 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.81853557 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426372.91855764 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.01853395 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.11854625 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.21855354 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.31854558 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.41854453 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.51854086 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.61861444 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.71853971 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.8185482 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426373.91854811 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.01854467 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.11854696 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.21853638 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.31853914 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.41856647 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.51854062 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.61854362 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.71855307 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.81853628 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426374.91855359 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.01860213 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.11854076 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.21855998 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.31856036 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.41853666 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.51855016 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.61856508 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.71855593 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.81854296 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426375.91855049 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.01854134 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.11853242 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.21854353 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.31854987 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.41856289 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.51854229 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.6185894 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.71854305 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.8185358 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426376.91854644 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.01854277 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.118541 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.21854424 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.31857657 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.41853714 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.51853752 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.61854482 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.71855617 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.81853938 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426377.91855025 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.01854229 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.11853838 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.21854472 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.31854057 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.41863561 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.51853681 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.61853743 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.71854901 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.81854486 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426378.91854215 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.0185523 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.11853576 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.2185421 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.31853247 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.41854405 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.51854873 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.6185329 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.71854115 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.81854248 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426379.91853881 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.01853871 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.11854458 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.2185421 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.31853676 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.41854191 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.51854277 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.61854577 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.71854997 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.8185358 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426380.91853642 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.01854539 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.11854386 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.21854186 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.31853032 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.41855454 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.51854014 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.61854076 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.71855807 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.81853437 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426381.91854286 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.0185504 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.11854362 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.21854687 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.31854057 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.4186058 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.51854825 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.6185441 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.71853685 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.81854057 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426382.91854453 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.01854277 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.11854076 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.21854591 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.31854296 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.41853595 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.51853752 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.61855292 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.71854401 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.81853437 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426383.91853595 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.01853657 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.11853576 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.21854115 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.31855249 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.41854906 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.51854014 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.61856055 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.71854377 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.81855416 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426384.91855121 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.01853657 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.11854577 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.21854925 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.31853652 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.41854119 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.51855206 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.61854172 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.71854877 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.81853604 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426385.91853547 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426386.018538 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426386.11854219 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426386.21853709 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426386.31855249 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426386.41854024 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
1624426386.51855063 455342.536 4413646.58 37.7136002 -0.972287965 0 0 0.233786468 0 0 0 0 0 0 0 0 0

View File

@@ -0,0 +1 @@
-0.0699018 -0.101214 1.02861e-07 0.999033 0.0323435 -0.0136968 -0.0264372

View File

@@ -0,0 +1 @@
-0.0699018 -0.101214 1.02861e-07 0.999033 0.0323435 -0.0136968 -0.0264372

View File

@@ -0,0 +1,205 @@
imu init done, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
=== frame 0
I1118 17:14:25.421314 209159 lio_iekf.cc:137] pred: p: 04.90454e-05 -0.000504891 08.02587e-05, v: 0.000440792 -0.00511598 0.000799364, q: 0-0.00120653 -7.59703e-05 -9.70547e-06 00000.999999, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:07.768869 209159 lio_iekf.cc:155] post: p: -0.0153191 00.0503512 -0.0341755, v: 0.000479927 00.00466279 -0.00575974, q: -0.00121657 00.00206976 00.00160879 0000.999996, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:07.775338 209159 lio_iekf.cc:136] === frame 1
I1118 17:15:07.775344 209159 lio_iekf.cc:137] pred: p: -0.0150588 00.0506864 -0.0347105, v: 0.00477512 0.00214923 -0.0051144, q: 0.000245399 00.00217255 00.00190123 0000.999996, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.890256 209159 lio_iekf.cc:155] post: p: -0.030806 0.0386739 00.021142, v: -0.0501599 -0.0260004 00.0399524, q: -0.000261551 000.00225525 000.00119186 00000.999997, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.893633 209159 lio_iekf.cc:136] === frame 2
I1118 17:15:11.893646 209159 lio_iekf.cc:137] pred: p: -0.035667 0.0359853 0.0252403, v: -0.0455922 -0.0274532 000.040713, q: 0.00225679 0.00228433 0.00164527 000.999993, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.894618 209159 lio_iekf.cc:155] post: p: -0.0277568 00.0270789 00.0295162, v: -0.0105089 -0.0578568 00.0589583, q: 00.00219828 00.00260048 0.000873066 0000.999994, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.899643 209159 lio_iekf.cc:136] === frame 3
I1118 17:15:11.899652 209159 lio_iekf.cc:137] pred: p: -0.0285529 00.0211308 00.0354642, v: -0.00535027 0-0.0607453 000.0594799, q: 0.00259265 0.00254714 0.00105252 000.999993, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.901063 209159 lio_iekf.cc:155] post: p: 00.00100406 -0.00416539 000.0161755, v: 000.0956111 00-0.156076 -0.00129277, q: 000.00738294 000.00131746 -0.000780396 00000.999972, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.904808 209159 lio_iekf.cc:136] === frame 4
I1118 17:15:11.904814 209159 lio_iekf.cc:137] pred: p: 00.0105809 -0.0201856 00.0160716, v: 0000.0983173 000-0.168396 -0.000867878, q: 000.00823418 000.00128613 -0.000717163 00000.999965, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.906029 209159 lio_iekf.cc:155] post: p: 0.00843266 0-0.014723 00.0206042, v: 0.0923196 -0.152179 0.0138562, q: 000.00789518 000.00152049 -0.000322546 00000.999968, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.908725 209159 lio_iekf.cc:136] === frame 5
I1118 17:15:11.908728 209159 lio_iekf.cc:137] pred: p: 00.0178833 -0.0306593 00.0220171, v: 0.0955118 -0.164659 0.0141552, q: 000.00763498 000.00160957 -0.000344936 00000.999969, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.909430 209159 lio_iekf.cc:155] post: p: -0.00470344 00.00639892 000.0288955, v: 00.0365583 -0.0654695 000.033711, q: 000.00457161 000.00261937 -0.000129411 00000.999986, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.912588 209159 lio_iekf.cc:136] === frame 6
I1118 17:15:11.912595 209159 lio_iekf.cc:137] pred: p: -0.000848782 -0.000381971 0000.0322354, v: 00.0416474 -0.0721619 000.034013, q: 000.00303829 000.00259847 -0.000513327 00000.999992, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.912993 209159 lio_iekf.cc:155] post: p: -0.0108165 00.0115985 00.0430028, v: 00.0178083 -0.0410502 00.0622496, q: 00.00150854 00.00396589 0.000674097 0000.999991, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.914862 209159 lio_iekf.cc:136] === frame 7
I1118 17:15:11.914866 209159 lio_iekf.cc:137] pred: p: -0.00864492 000.0073022 000.0492498, v: 00.0255669 -0.0447473 000.062585, q: -0.00036176 00.00389803 0.000664088 0000.999992, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.915254 209159 lio_iekf.cc:155] post: p: -0.00998406 000.0018454 000.0376213, v: 00.0202328 -0.0594128 00.0338251, q: 03.33809e-06 000.00321527 -2.83811e-05 00000.999995, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.917377 209159 lio_iekf.cc:136] === frame 8
I1118 17:15:11.917410 209159 lio_iekf.cc:137] pred: p: -0.00759479 -0.00440334 000.0410847, v: 00.0268193 -0.0636386 00.0343541, q: 00.000523725 000.00313919 -0.000204003 00000.999995, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.918063 209159 lio_iekf.cc:155] post: p: 0.000752396 000.0207922 000.0334292, v: 00.0477717 -0.0110044 00.0142291, q: 000.00227905 000.00266603 -5.74178e-05 00000.999994, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.919911 209159 lio_iekf.cc:136] === frame 9
I1118 17:15:11.919914 209159 lio_iekf.cc:137] pred: p: 0.00570258 00.0193502 00.0348469, v: 00.0532229 -0.0184184 00.0146345, q: 000.00206386 000.00261745 -7.93742e-06 00000.999994, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.920490 209159 lio_iekf.cc:155] post: p: 0.00191145 00.0201858 00.0180808, v: 00.0400973 -0.0187426 -0.0308192, q: 00.00149519 00.00122873 0.000768057 0000.999998, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.922688 209159 lio_iekf.cc:136] === frame 10
I1118 17:15:11.922690 209159 lio_iekf.cc:137] pred: p: 0.00649512 00.0177244 00.0147163, v: 00.0431613 0-0.025969 -0.0303075, q: 00.00203652 000.0013238 0.000903168 0000.999997, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.923228 209159 lio_iekf.cc:155] post: p: -0.00150556 0-0.0025184 -0.00414911, v: 00.0155007 -0.0860391 -0.0822575, q: 000.00599938 -0.000183419 00.000153602 00000.999982, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.924659 209159 lio_iekf.cc:136] === frame 11
I1118 17:15:11.924662 209159 lio_iekf.cc:137] pred: p: -0.000104462 00-0.0109358 00-0.0116132, v: 00.0153269 -0.0991016 -0.0819059, q: 000.00708928 -0.000185432 00.000385193 00000.999975, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.924885 209159 lio_iekf.cc:155] post: p: 00.00224026 0-0.0206669 0.000693994, v: 00.0241425 00-0.11959 -0.0453051, q: 00.00488904 0.000805058 -0.00062767 0000.999988, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.926889 209159 lio_iekf.cc:136] === frame 12
I1118 17:15:11.926892 209159 lio_iekf.cc:137] pred: p: 00.00499685 0-0.0344078 -0.00424487, v: 00.0260986 0-0.130902 -0.0447542, q: 0000.0064845 00.000871289 -0.000509421 00000.999978, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.927434 209159 lio_iekf.cc:155] post: p: -0.0013856 -0.0180147 0.00077609, v: 0.00733519 -0.0920896 -0.0286954, q: 000.00923425 00.000369769 -0.000686988 00000.999957, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.929165 209159 lio_iekf.cc:136] === frame 13
I1118 17:15:11.929169 209159 lio_iekf.cc:137] pred: p: -0.000683904 00-0.0270661 0-0.00182275, v: 00.0080873 0-0.106958 -0.0285007, q: 000.00924388 00.000358166 -0.000710999 00000.999957, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.929667 209159 lio_iekf.cc:155] post: p: -0.00578867 00.00215956 00.00700372, v: 0-0.0030371 0-0.0229413 -0.00447093, q: 00.00563486 00.00243229 5.64186e-05 0000.999981, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.931541 209159 lio_iekf.cc:136] === frame 14
I1118 17:15:11.931545 209159 lio_iekf.cc:137] pred: p: 0-0.00583285 -0.000895607 000.00654662, v: 00.00220287 0-0.0335465 -0.00401146, q: 00.00605858 00.00232979 2.20827e-05 0000.999979, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.932104 209159 lio_iekf.cc:155] post: p: -0.00278004 00.00313029 000.0128976, v: 0.00997104 -0.0254463 00.0132513, q: 00.00821921 00.00118624 0.000199534 0000.999965, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.933557 209159 lio_iekf.cc:136] === frame 15
I1118 17:15:11.933580 209159 lio_iekf.cc:137] pred: p: 0-0.00164956 -0.000153588 0000.0142476, v: 00.0125478 -0.0399885 00.0135861, q: 00.00860753 00.00122725 0.000401781 0000.999962, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.933826 209159 lio_iekf.cc:155] post: p: 00.00919599 -0.00954942 000.0229229, v: 000.045598 -0.0595688 00.0382537, q: 0.00620488 0.00203302 0.00163873 000.999977, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.935776 209159 lio_iekf.cc:136] === frame 16
I1118 17:15:11.935781 209159 lio_iekf.cc:137] pred: p: 00.0136039 -0.0155058 00.0264916, v: 0.0494435 -0.068921 0.0386612, q: 0.00690578 0.00203102 0.00175541 000.999973, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.936312 209159 lio_iekf.cc:155] post: p: 000.0101917 0.000459489 0000.019997, v: 00.0386058 -0.0351932 00.0207056, q: 000.0101891 00.00142608 0.000216748 0000.999947, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.938040 209159 lio_iekf.cc:136] === frame 17
I1118 17:15:11.938045 209159 lio_iekf.cc:137] pred: p: 000.0144818 -0.00425185 000.0222271, v: 00.0417763 -0.0531204 00.0210255, q: 000.0106075 00.00139463 -6.4051e-05 0000.999943, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.938546 209159 lio_iekf.cc:155] post: p: -0.00199832 -0.00755048 000.0180761, v: -0.00519676 0-0.0611281 00.00960466, q: 00.00943818 00.00015313 -0.00162587 0000.999954, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.940439 209159 lio_iekf.cc:136] === frame 18
I1118 17:15:11.940443 209159 lio_iekf.cc:137] pred: p: -0.00249975 0-0.0144568 000.0190582, v: -0.00474474 0-0.0758661 00.00980882, q: 00.00890815 0.000114341 0-0.0015747 0000.999959, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.940969 209159 lio_iekf.cc:155] post: p: -0.0150766 0000.01334 00.0178194, v: 0-0.0374328 -0.00465068 00.00703531, q: 000.00740015 000.00137108 -0.000344167 00000.999972, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.942432 209159 lio_iekf.cc:136] === frame 19
I1118 17:15:11.942435 209159 lio_iekf.cc:137] pred: p: -0.0185532 00.0123449 00.0185101, v: -0.0346348 0-0.015953 0.00724182, q: 000.00627905 000.00135494 -0.000248159 00000.999979, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.942687 209159 lio_iekf.cc:155] post: p: -0.0106226 0.00490207 00.0236433, v: -0.0107037 -0.0278636 00.0203764, q: 0.00249327 0.00215172 0.00130186 000.999994, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.944468 209159 lio_iekf.cc:136] === frame 20
I1118 17:15:11.944471 209159 lio_iekf.cc:137] pred: p: -0.0114897 0.00179978 00.0257821, v: -0.00602089 00-0.031967 000.0208662, q: 0.00312941 0.00215868 0.00132527 000.999992, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.944958 209159 lio_iekf.cc:155] post: p: -0.00345185 -0.00717943 000.0165726, v: 000.0135267 0-0.0619038 -0.00248835, q: 000.00715173 000.00140423 -0.000189458 00000.999973, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.946609 209159 lio_iekf.cc:136] === frame 21
I1118 17:15:11.946614 209159 lio_iekf.cc:137] pred: p: -0.00211647 0-0.0132064 000.0163657, v: 000.0162605 0-0.0725589 -0.00216985, q: 000.00740939 000.00137473 -0.000170376 00000.999972, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.947124 209159 lio_iekf.cc:155] post: p: -0.00651473 -0.00215236 000.0153109, v: 00.00488036 0-0.0455272 -0.00529396, q: 00.00698527 00.00175434 0.000569922 0000.999974, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.948858 209159 lio_iekf.cc:136] === frame 22
I1118 17:15:11.948877 209159 lio_iekf.cc:137] pred: p: -0.00574653 -0.00782332 000.0147551, v: 00.00909847 0-0.0576614 -0.00485595, q: 000.0076439 00.00177245 0.000680381 0000.999969, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.949421 209159 lio_iekf.cc:155] post: p: -0.00382868 000.0130476 0000.017737, v: 000.0149043 -0.00876365 00.00267306, q: 00.00969612 00.00157242 0.000840327 0000.999951, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.951212 209159 lio_iekf.cc:136] === frame 23
I1118 17:15:11.951216 209159 lio_iekf.cc:137] pred: p: -0.00227757 000.0115498 000.0180063, v: 00.0180914 -0.0231501 0.00300576, q: 00000.01088 000.0015886 0.000719275 0000.999939, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.951570 209159 lio_iekf.cc:155] post: p: -0.0145005 00.0174192 00.0175176, v: 0-0.0158562 -0.00520393 00.00199439, q: 00.00838138 00.00142165 -0.00218195 0000.999961, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.953176 209159 lio_iekf.cc:136] === frame 24
I1118 17:15:11.953179 209159 lio_iekf.cc:137] pred: p: -0.016024 0.0162531 0.0177543, v: 0-0.012738 -0.0167324 0.00236756, q: 00.00892732 00.00137161 -0.00163546 0000.999958, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.953544 209159 lio_iekf.cc:155] post: p: 00-0.017527 0.000705961 000.0373498, v: -0.0126885 -0.0465573 00.0543639, q: 000.00589359 000.00350919 -0.000831264 00000.999976, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.955387 209159 lio_iekf.cc:136] === frame 25
I1118 17:15:11.955391 209159 lio_iekf.cc:137] pred: p: 0-0.0183067 -0.00314154 000.0416631, v: -0.00701791 0-0.0507588 000.0546663, q: 00.00594996 00.00350621 -0.00100196 0000.999976, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.955890 209159 lio_iekf.cc:155] post: p: -0.0147757 0.00129334 00.0224162, v: -0.00105843 0-0.0430689 00.00257092, q: 000.00602707 000.00175077 -0.000476096 000000.99998, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.957621 209159 lio_iekf.cc:136] === frame 26
I1118 17:15:11.957624 209159 lio_iekf.cc:137] pred: p: 0-0.0146316 -0.00430057 000.0227596, v: 0.00346116 -0.0499088 0.00308568, q: 000.00660257 000.00179712 -0.000478638 00000.999976, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.958143 209159 lio_iekf.cc:155] post: p: -0.00955999 000.0164956 000.0221032, v: 00000.018325 -0.000787753 00.000941459, q: 0.00804765 0.00199508 0.00148244 000.999965, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.960180 209159 lio_iekf.cc:136] === frame 27
I1118 17:15:11.960184 209159 lio_iekf.cc:137] pred: p: -0.00752655 000.0159748 000.0222158, v: 000.0224374 -0.00968724 00.00125442, q: 0.00751929 0.00198793 0.00124661 000.999969, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.960719 209159 lio_iekf.cc:155] post: p: -0.00960678 0000.023717 000.0129138, v: 00.0149583 0.00793828 -0.0244125, q: 0.00745896 0.00091034 0.00116203 000.999971, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.962118 209159 lio_iekf.cc:136] === frame 28
I1118 17:15:11.962121 209159 lio_iekf.cc:137] pred: p: -0.00802248 000.0240688 000.0104961, v: 0000.0168653 -0.000846577 00-0.0241823, q: 00.00610736 0.000903238 00.00135748 00000.99998, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.962345 209159 lio_iekf.cc:155] post: p: -0.013008 0.0198995 0.0130335, v: 00.00317311 -0.00774547 0-0.0173752, q: 000.00382281 000.00123227 -0.000341855 00000.999992, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.964265 209159 lio_iekf.cc:136] === frame 29
I1118 17:15:11.964282 209159 lio_iekf.cc:137] pred: p: -0.0126156 00.0190335 00.0114875, v: 0.00553383 0-0.011468 -0.0170193, q: 000.00397387 000.00114611 -0.000728046 00000.999991, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.964799 209159 lio_iekf.cc:155] post: p: -0.0204661 0.00885791 00.0107593, v: -0.0168295 -0.0401255 -0.0182122, q: 00.00567619 000.0011173 -0.00140513 0000.999982, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.966563 209159 lio_iekf.cc:136] === frame 30
I1118 17:15:11.966567 209159 lio_iekf.cc:137] pred: p: -0.0221681 0.00400176 0.00878148, v: -0.0141208 -0.0482997 -0.0178408, q: 00.00524587 00.00125018 -0.00112132 0000.999985, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.967144 209159 lio_iekf.cc:155] post: p: -0.0308646 00.0273728 0.00321953, v: -0.0392401 0.00365444 0-0.032642, q: 000.00804325 04.84804e-05 -9.67534e-05 00000.999968, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.969038 209159 lio_iekf.cc:136] === frame 31
I1118 17:15:11.969040 209159 lio_iekf.cc:137] pred: p: 00-0.0348125 0000.0270795 -5.76973e-05, v: 0-0.0390182 -0.00944722 0-0.0323957, q: 000.00806638 000.00010777 -0.000151537 00000.999967, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.969590 209159 lio_iekf.cc:155] post: p: -0.0275045 00.0168235 00.0158835, v: -0.0159905 -0.0285702 00.0117468, q: 00.00628074 00.00183553 -0.00171256 0000.999977, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.971078 209159 lio_iekf.cc:136] === frame 32
I1118 17:15:11.971081 209159 lio_iekf.cc:137] pred: p: -0.0289343 00.0134623 00.0170917, v: -0.0123124 -0.0380222 00.0121408, q: 00.00663759 00.00190125 -0.00125578 0000.999975, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.971312 209159 lio_iekf.cc:155] post: p: -0.0335291 000.011422 00.0223288, v: -0.0248202 -0.0431123 00.0269799, q: 00.00706802 00.00210039 -0.00271434 0000.999969, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.973183 209159 lio_iekf.cc:136] === frame 33
I1118 17:15:11.973186 209159 lio_iekf.cc:137] pred: p: -0.0353633 0.00768912 00.0244735, v: -0.0216154 -0.0514039 00.0272669, q: 00.00723896 00.00212892 -0.00285546 0000.999967, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.973711 209159 lio_iekf.cc:155] post: p: -0.0406646 0.00223471 00.0242756, v: 0-0.036042 -0.0627385 00.0266659, q: 0.00573758 0.00235501 -0.0025138 000.999978, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.975383 209159 lio_iekf.cc:136] === frame 34
I1118 17:15:11.975385 209159 lio_iekf.cc:137] pred: p: 0-0.0446373 -0.00579038 000.0274872, v: -0.0306631 -0.0720615 00.0272026, q: 000.0065722 00.00245001 -0.00270113 0000.999972, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.975878 209159 lio_iekf.cc:155] post: p: 0-0.032354 0.00698154 00.0176309, v: -0.000250842 00-0.0473594 000.00104029, q: 000.0099747 00.00066549 -0.00199537 0000.999948, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.977705 209159 lio_iekf.cc:136] === frame 35
I1118 17:15:11.977710 209159 lio_iekf.cc:137] pred: p: -0.0323301 0.00138484 00.0177552, v: 0.000730281 0-0.0625483 000.0013314, q: 000.0103759 0.000646453 -0.00159964 0000.999945, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.978232 209159 lio_iekf.cc:155] post: p: -0.0337782 0.00874701 00.0137585, v: -0.00282612 00-0.047824 -0.00861813, q: 00.0121455 0.00113379 -0.0016715 000.999924, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.979840 209159 lio_iekf.cc:136] === frame 36
I1118 17:15:11.979858 209159 lio_iekf.cc:137] pred: p: -0.0339622 0.00314178 00.0129249, v: -0.000910109 00-0.0662699 0-0.00838114, q: 00.0127904 0.00112578 -0.0016166 000.999916, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.980145 209159 lio_iekf.cc:155] post: p: -0.0470016 00.0168451 00.0147551, v: 0-0.0365023 0-0.0275621 -0.00325529, q: 00.00927839 00.00127644 -0.00159512 0000.999955, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.981890 209159 lio_iekf.cc:136] === frame 37
I1118 17:15:11.981894 209159 lio_iekf.cc:137] pred: p: -0.0507362 000.013244 00.0144328, v: 0-0.0340429 0-0.0405466 -0.00290648, q: 0.00998882 0.00136084 -0.0015252 000.999948, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.982342 209159 lio_iekf.cc:155] post: p: -0.0253281 00.0107317 00.0289837, v: 0.0373826 -0.043742 0.0349919, q: 0000.0106274 000.00246312 -2.56926e-05 000000.99994, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.984136 209159 lio_iekf.cc:136] === frame 38
I1118 17:15:11.984139 209159 lio_iekf.cc:137] pred: p: -0.0216145 00.0060108 000.032282, v: 00.0417066 -0.0568265 00.0351998, q: 0000.0100201 000.00245466 -0.000286008 00000.999947, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.984676 209159 lio_iekf.cc:155] post: p: -0.0224169 00.0115669 00.0327741, v: 00.0402784 -0.0394379 00.0357205, q: 000.00776671 0000.0027215 -0.000371586 00000.999966, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.986378 209159 lio_iekf.cc:136] === frame 39
I1118 17:15:11.986382 209159 lio_iekf.cc:137] pred: p: -0.0181039 0.00710886 00.0363843, v: 00.0453899 -0.0491096 00.0358994, q: 000.00627731 000.00267927 -0.000191602 00000.999977, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.986899 209159 lio_iekf.cc:155] post: p: -0.0072182 00.0117463 00.0271725, v: 00.073863 -0.040899 0.0106082, q: 00.00807588 00.00184913 0.000308965 0000.999966, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.988760 209159 lio_iekf.cc:136] === frame 40
I1118 17:15:11.988763 209159 lio_iekf.cc:137] pred: p: 0.000428815 00.00693913 000.0282592, v: 0.0773209 -0.054111 0.0108618, q: 00.00714625 00.00178196 0.000468223 0000.999973, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.989274 209159 lio_iekf.cc:155] post: p: -0.00274461 -0.00475901 000.0343976, v: 00.0697324 -0.0743786 00.0272192, q: 000.0017629 00.00290237 0.000580373 0000.999994, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.990716 209159 lio_iekf.cc:136] === frame 41
I1118 17:15:11.990720 209159 lio_iekf.cc:137] pred: p: 0.00512994 -0.0129419 00.0373723, v: 00.0757732 -0.0768013 00.0276777, q: 00.00176315 00.00295099 0.000712146 0000.999994, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.990968 209159 lio_iekf.cc:155] post: p: 0.00382828 -0.0119629 00.0286474, v: 0000.07057 -0.0802147 0.00467209, q: 00.00465419 00.00240829 0.000473393 0000.999986, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.992880 209159 lio_iekf.cc:136] === frame 42
I1118 17:15:11.992884 209159 lio_iekf.cc:137] pred: p: 00.0111183 -0.0203906 00.0291311, v: 00.0751165 -0.0882057 0.00495365, q: 00.00368231 00.00244324 0.000759635 00000.99999, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.993322 209159 lio_iekf.cc:155] post: p: 00.0142869 -0.0046058 00.0252783, v: 000.0836109 0-0.0527855 -0.00587547, q: 0.00539567 00.0022904 0.00113727 000.999982, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.995007 209159 lio_iekf.cc:136] === frame 43
I1118 17:15:11.995010 209159 lio_iekf.cc:137] pred: p: 00.0229552 -0.0105195 00.0247032, v: 000.0879199 0-0.0642372 -0.00554224, q: 0.00516391 0.00231061 0.00124638 000.999983, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.995541 209159 lio_iekf.cc:155] post: p: -0.00125801 00.00602515 0000.029749, v: 00.0240558 -0.0155837 0.00774458, q: -0.000774922 000.00317774 00.000926218 00000.999994, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.997438 209159 lio_iekf.cc:136] === frame 44
I1118 17:15:11.997442 209159 lio_iekf.cc:137] pred: p: 0.00145917 00.0044732 00.0305545, v: 00.0300416 0-0.015309 0.00828491, q: 0.000186713 00.00325627 0.000800919 0000.999994, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.997939 209159 lio_iekf.cc:155] post: p: 0.00409701 -0.0103695 00.0280371, v: 00.0348392 -0.0608119 0.00215272, q: 000.00559483 000.00244804 -0.000129847 00000.999981, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:11.999414 209159 lio_iekf.cc:136] === frame 45
I1118 17:15:11.999418 209159 lio_iekf.cc:137] pred: p: 0.00761241 -0.0166219 00.0282709, v: 00.0390343 0-0.070634 0.00270157, q: 00.00832741 00.00252936 0.000170725 0000.999962, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:11.999653 209159 lio_iekf.cc:155] post: p: 0.00852682 -0.0219793 00.0325859, v: 00.0422356 -0.0823777 00.0143882, q: 00.00764677 00.00296251 -0.00063212 0000.999966, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:12.001551 209159 lio_iekf.cc:136] === frame 46
I1118 17:15:12.001555 209159 lio_iekf.cc:137] pred: p: 00.0131904 -0.0310293 00.0341103, v: 00.0477363 -0.0924026 00.0149382, q: 000.00978727 000.00303871 -0.000357891 00000.999947, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:12.002097 209159 lio_iekf.cc:155] post: p: -0.00571204 -0.00826453 0000.017529, v: -0.00684843 0-0.0381937 0-0.0281482, q: 00.00882581 00.00140222 0.000619265 00000.99996, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:12.003772 209159 lio_iekf.cc:136] === frame 47
I1118 17:15:12.003774 209159 lio_iekf.cc:137] pred: p: -0.00624681 0-0.0120631 0000.015004, v: -0.00501371 0-0.0460501 0-0.0278583, q: 00.00944439 00.00136209 0.000965105 0000.999954, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:12.004245 209159 lio_iekf.cc:155] post: p: -0.00217722 0.000370151 00.00765073, v: 0.00408428 -0.0184988 -0.0479597, q: 0000.0110169 -0.000504146 000.00151735 00000.999938, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:12.006052 209159 lio_iekf.cc:136] === frame 48
I1118 17:15:12.006054 209159 lio_iekf.cc:137] pred: p: -0.00182268 0-0.0023869 00.00241462, v: 0.00241319 -0.0318412 -0.0475872, q: 00.0125397 -0.0004074 0.00128603 0000.99992, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:12.006580 209159 lio_iekf.cc:155] post: p: 00.00712729 0.000455928 000.0108929, v: 00.0305433 -0.0181527 0-0.024302, q: 0.00981948 0.00130322 0.00107983 0000.99995, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:12.008213 209159 lio_iekf.cc:136] === frame 49
I1118 17:15:12.008215 209159 lio_iekf.cc:137] pred: p: 00.00994338 -0.00146657 00.00872675, v: 00.0322507 -0.0247646 -0.0240732, q: 00.00928355 00.00125963 0.000998656 0000.999956, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:12.008530 209159 lio_iekf.cc:155] post: p: 000.0147639 2.52168e-05 000.0175967, v: 000.0469827 00-0.017585 0.000277126, q: 00.00804375 00.00144093 0.000434819 0000.999967, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
Failed to find match for field 'intensity'.
I1118 17:15:12.010309 209159 lio_iekf.cc:136] === frame 50
I1118 17:15:12.010313 209159 lio_iekf.cc:137] pred: p: 000.0201475 -0.00226075 000.0176451, v: 000.0493982 0-0.0233594 0.000536465, q: 000.0062863 000.0013803 0.000230497 0000.999979, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014
I1118 17:15:12.010778 209159 lio_iekf.cc:155] post: p: 00.0148923 -0.0203415 000.016958, v: 0000.0345795 00-0.0711472 -5.14172e-05, q: 00.00817267 00.00122795 6.93271e-05 0000.999966, bg: 0.000293669 00.00302745 -0.00353565, ba: 6.72201e-05 2.65046e-06 -0.00493014

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 MiB

View File

@@ -0,0 +1,8 @@
echo "please make sure running this script in root dir of slam_in_autonomous_driving. current working dir: $PWD"
docker run -it \
-e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \
-v "$PWD":/sad \
--gpus all \
--name sad \
sad:v1 \
/bin/bash

View File

@@ -0,0 +1,17 @@
# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
# deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse
# # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
# deb-src http://security.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse
# 预发布软件源,不建议启用
# deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse
# # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse

View File

@@ -0,0 +1,304 @@
## SLAM in Autonomous Driving Book (SAD Book)
This book systematically introduces readers to inertial navigation, integrated navigation, LiDAR mapping, LiDAR localization, LiDAR-inertial odometry, and related knowledge. This repository contains the source code accompanying the book and is publicly available for use.
The English translation is almost done and is open-source, check it out here: https://github.com/gaoxiang12/slam-in-ad-en/blob/main/sad-en.pdf
<img src="https://github.com/gaoxiang12/slam_in_autonomous_driving/assets/6635511/734af25b-d866-4dcf-a155-773190ba03d8" width="300" />
## Notes
- The book began printing on July 10, 2023, and is expected to be available within two weeks. I will update the links to various platforms at that time.
- August 9, 2023: The book is currently in its second printing, with some content corrections from the first edition (though without signatures). For details, see the code updates.
- Official page from Electronic Industry Publishing House: https://item.jd.com/10080292102089.html
- JD.com self-operated store: https://item.jd.com/13797963.html
## Book Content Structure
- Chapter 1: Overview
- Chapter 2: Review of mathematical fundamentals: geometry, kinematics, KF filter theory, and matrix Lie groups
- Chapter 3: Error-state Kalman filter, inertial navigation, satellite navigation, and integrated navigation
- Chapter 4: Pre-integration, graph optimization, and pre-integration-based integrated navigation
- Chapter 5: Point cloud basics, nearest neighbor structures, and point cloud linear fitting
- Chapter 6: 2D LiDAR mapping: scan matching, likelihood fields, submaps, 2D loop closure detection, and pose graph
- Chapter 7: 3D LiDAR mapping: ICP, ICP variants, NDT, NDT LO, Loam-like LO, and loosely-coupled LIO
- Chapter 8: Tightly-coupled LIO, IESKF, and pre-integration tightly-coupled LIO
- Chapter 9: Offline mapping: frontend, backend, batch loop closure detection, map optimization, and tile export
- Chapter 10: Fusion localization: LiDAR localization, initialization search, tile map loading, and EKF fusion
## Features of This Book
- This book likely offers the simplest mathematical derivations and code implementations among similar materials.
- In this book, you will reproduce many classic algorithms and data structures in LiDAR SLAM:
- You will derive and implement an Error-State Kalman Filter (ESKF), feed it IMU and GNSS data, and observe how it estimates its state.
- You will also implement a pre-integration system for the same purpose and compare their performance.
- Next, you will implement common algorithms in 2D LiDAR SLAM: scan matching, likelihood fields, submaps, occupancy grids, and use loop closure detection to build larger maps—all by yourself.
- In LiDAR SLAM, you will implement a K-d tree for approximate nearest neighbor searches, then use it for ICP and point-to-plane ICP, discussing potential improvements.
- You will implement the classic NDT algorithm, test its registration performance, and use it to build a LiDAR odometer—much faster than most existing LOs.
- You will also implement a point-to-plane ICP LiDAR odometer, which is similarly fast and works similarly to Loam but simpler.
- You will integrate IMU into the LiDAR odometer, implementing both loosely-coupled and tightly-coupled LIO systems, including derivations for iterative Kalman filters and pre-integration graph optimization.
- You will adapt the system for offline operation to allow thorough loop closure detection, ultimately creating an offline mapping system.
- Finally, you will segment the resulting map for real-time localization.
- Most implementations in this book are significantly simpler than those in comparable libraries, making it easier to understand their workings without dealing with complex interfaces.
- The book employs convenient concurrent programming, often resulting in more efficient implementations than existing algorithms (partly due to historical reasons).
- Each chapter includes dynamic demonstrations like these:
![](./doc/lio_demo.gif)
![](./doc/2dmapping_demo.gif)
![](./doc/lo_demo.gif)
We hope you enjoy the minimalist style of this book and discover the joy of algorithms.
## Datasets
- Dataset download links:
- Baidu Cloud: https://pan.baidu.com/s/1ELOcF1UTKdfiKBAaXnE8sQ?pwd=feky (Extraction code: feky)
- OneDrive: https://1drv.ms/u/s!AgNFVSzSYXMahcEZejoUwCaHRcactQ?e=YsOYy2
- Includes the following datasets (total 270GB; download selectively based on storage capacity):
- UrbanLoco (ULHK, 3D LiDAR, urban road scenarios)
- NCLT (3D LiDAR, RTK, campus scenarios)
- WXB (3D LiDAR, campus scenarios)
- 2dmapping (2D LiDAR, mall scenarios)
- AVIA (DJI solid-state LiDAR)
- UTBM (3D LiDAR, road scenarios)
- Other built-in data:
- Chapters 34 use text-formatted IMU and RTK data.
- Chapter 7 uses some EPFL data for point cloud registration.
- Store datasets in `./dataset/sad/` for default paths to work. Alternatively, manually specify paths or create symlinks if storage is limited.
## Compilation
- Recommended environment: Ubuntu 20.04. Older versions require GCC adjustments (C++17 support). For newer Ubuntu, install the corresponding ROS version.
- Prerequisites:
- ROS Noetic: http://wiki.ros.org/noetic/Installation/Ubuntu
- Additional libraries:
```bash
sudo apt install -y ros-noetic-pcl-ros ros-noetic-velodyne-msgs libopencv-dev libgoogle-glog-dev libeigen3-dev libsuitesparse-dev libpcl-dev libyaml-cpp-dev libbtbb-dev libgmock-dev
```
- Pangolin: Compile `thirdparty/pangolin.zip` or install from https://github.com/stevenlovegrove/Pangolin
- g2o: Compile `thirdparty/g2o` or install from https://github.com/RainerKuemmerle/g2o
- Build commands:
```bash
mkdir build
cd build
cmake ..
make -j8
```
- Executables are located in the `bin` directory.
### Ubuntu 18.04 Adaptation
For Ubuntu 18.04, install GCC-9 and compatible TBB, or use Docker.
**Install GCC-9:**
```bash
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo update-alternatives --remove-all gcc
sudo update-alternatives --remove-all g++
# Priority values (1 and 10); auto-mode defaults to higher priority.
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 1
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 10
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 1
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 10
```
**Check version:**
```bash
g++ -v
```
**Build:**
```bash
mkdir build
cd build
cmake .. -DBUILD_WITH_UBUNTU1804=ON
make -j8
```
**Docker setup:**
```bash
docker build -t sad:v1 .
./docker/docker_run.sh
```
Inside the container:
```bash
cd ./thirdparty/g2o
mkdir build
cd build
cmake ..
make -j8
cd /sad
mkdir build
cd build
cmake ..
make -j8
```
## FAQs
1. GUI crashes on certain 2023+ laptop models (GL hardware compatibility): https://github.com/gaoxiang12/slam_in_autonomous_driving/issues/67
2. Chapter 5 `test_nn` GMock errors during compilation: https://github.com/gaoxiang12/slam_in_autonomous_driving/issues/18
3. Compiler version issues: https://github.com/gaoxiang12/slam_in_autonomous_driving/issues/4
4. g2o compilation (`config.h` not found): https://github.com/gaoxiang12/slam_in_autonomous_driving/issues/95
---
## SLAM in Autonomous Driving book (SAD book)
本书向读者系统介绍了惯性导航、组合导航、激光建图、激光定位、激光惯导里程计等知识。本仓库是书籍对应的源代码仓库,可以公开使用。
<img src="https://github.com/gaoxiang12/slam_in_autonomous_driving/assets/6635511/734af25b-d866-4dcf-a155-773190ba03d8" width="300" />
## 注意
- 本书已于2023.7.10开始印刷,预计在两周内上架。届时我会更新各平台的链接信息。
- 2023.8.9 本书目前是第二次印刷,在第一次上修正了一部分内容(但没有签名了),详情见代码的推送。
- 电子工业出版社官方https://item.jd.com/10080292102089.html
- 京东自营: https://item.jd.com/13797963.html
## 本书的内容编排
- 第1章概述
- 第2章数学基础知识回顾几何学、运动学、KF滤波器理论矩阵李群
- 第3章误差状态卡尔曼滤波器惯性导航、卫星导航、组合导航
- 第4章预积分图优化基于预积分的组合导航
- 第5章点云基础处理各种最近邻结构点云线性拟合
- 第6章2D激光建图scan matching, 似然场子地图2D回环检测pose graph
- 第7章3D激光建图ICP变种ICPNDTNDT LO, Loam-like LOLIO松耦合
- 第8章紧耦合LIOIESKF预积分紧耦合LIO
- 第9章离线建图前端后端批量回环检测地图优化切片导出
- 第10章融合定位激光定位初始化搜索切片地图加载EKF融合
## 本书的特点
- 本书大概是您能找到的类似材料中,数学推导和代码实现最为简单的书籍。
- 在这本书里您会复现许多激光SLAM中的经典算法和数据结构。
- 您需要自己推导、实现一个误差状态卡尔曼滤波器(ESKF)把IMU和GNSS的数据喂给它看它如何推算自己的状态。
- 您还会用预积分系统实现一样的功能,然后对比它们的运行方式。
- 接下来您会实现一遍2D激光SLAM中的常见算法扫描匹配、似然场、子地图占据栅格再用回环检测来构建一个更大的地图。这些都需要您自己来完成。
- 在激光SLAM中您也会自己实现一遍Kd树处理近似最近邻然后用这个Kd树来实现ICP点面ICP讨论它们有什么可以改进的地方。
- 然后您会实现经典的NDT算法测试它的配准性能然后用它来搭建一个激光里程计。它比大部分现有LO快得多。
- 您也会实现一个点面ICP的激光里程计它也非常快。它工作的方式类似于Loam但更简单。
- 您会想要把IMU系统也放到激光里程计中。我们会实现松耦合和紧耦合的LIO系统。同样地您需要推导一遍迭代卡尔曼滤波器和预积分图优化。
- 您需要把上面的系统改成离线运行的,让回环检测运行地充分一些。最后将它做成一个离线的建图系统。
- 最后,您可以对上述地图进行切分,然后用来做实时定位。
- 本书的大部分实现都要比类似的算法库简单的多。您可以快速地理解它们的工作方式,不需要面对复杂的接口。
- 本书会使用非常方便的并发编程。您会发现,本书的实现往往比现有算法更高效。当然这有一部分是历史原因造成的。
- 本书每章都会配有动态演示,像这样:
![](./doc/lio_demo.gif)
![](./doc/2dmapping_demo.gif)
![](./doc/lo_demo.gif)
希望您能喜欢本书的极简风格,发现算法的乐趣所在。
## 数据集
- 数据集下载链接:
- 百度云链接: https://pan.baidu.com/s/1ELOcF1UTKdfiKBAaXnE8sQ?pwd=feky 提取码: feky
- OneDrive链接https://1drv.ms/u/s!AgNFVSzSYXMahcEZejoUwCaHRcactQ?e=YsOYy2
- 包含以下数据集。总量较大(270GB),请视自己硬盘容量下载。
- UrbanLoco (ULHK3D激光道路场景)
- NCLT (3D激光RTK校园场景)
- WXB (3D激光园区场景)
- 2dmapping (2D激光商场场景)
- AVIA (大疆固态激光)
- UTBM (3D激光道路场景)
- 其他的内置数据
- 第3,4章使用文本格式的IMURTK数据
- 第7章使用了一部分EPFL的数据作为配准点云来源
- 您应该将上述数据下载至./dataset/sad/目录下,这样许多默认参数可以正常工作。如果不那么做,您也可以手动指定这些文件路径。如果您硬盘容量不足,可以将其他硬盘的目录软链至此处。
## 编译
- 本书推荐的编译环境是Ubuntu 20.04。更老的Ubuntu版本需要适配gcc编译器主要是C++17标准。更新的Ubuntu则需要您自己安装对应的ROS版本。
- 在编译本书代码之前,请先安装以下库(如果您机器上没有安装的话)
- ROS Noetic: http://wiki.ros.org/noetic/Installation/Ubuntu
- 使用以下指令安装其余的库
```bash
sudo apt install -y ros-noetic-pcl-ros ros-noetic-velodyne-msgs libopencv-dev libgoogle-glog-dev libeigen3-dev libsuitesparse-dev libpcl-dev libyaml-cpp-dev libbtbb-dev libgmock-dev
```
- Pangolin: 编译安装thirdparty/pangolin.zip或者 https://github.com/stevenlovegrove/Pangolin
- 编译thirdparty/g2o或者自行编译安装 https://github.com/RainerKuemmerle/g2o
- 通过cmake, make安装本repo下的`thirdparty/g2o`库
- 之后使用通常的cmake, make方式就可以编译本书所有内容了。例如
```bash
mkdir build
cd build
cmake ..
make -j8
```
- 编译后各章的可执行文件位于`bin`目录下
### 适配Ubuntu18.04
为了在Ubuntu18.04上编译运行需要安装gcc-9并且使用对应版本的TBB。或者在docker环境下使用。
**安装gcc-9**
```bash
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo update-alternatives --remove-all gcc
sudo update-alternatives --remove-all g++
#命令最后的1和10是优先级如果使用auto选择模式系统将默认使用优先级高的
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 1
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 10
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 1
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 10
```
**检查版本**
```bash
g++ -v
```
**编译程序**
```bash
mkdir build
cd build
cmake .. -DBUILD_WITH_UBUNTU1804=ON
make -j8
```
**在docker环境下使用**
```bash
docker build -t sad:v1 .
./docker/docker_run.sh
```
进入docker容器后
```bash
cd ./thirdparty/g2o
mkdir build
cd build
cmake ..
make -j8
cd /sad
mkdir build
cd build
cmake ..
make -j8
```
## 常见问题
1. 图形界面在2023年以后特定型号的笔记本端导致桌面卡死GL硬件兼容性https://github.com/gaoxiang12/slam_in_autonomous_driving/issues/67
2. 第5章test_nn编译时gtest报gmock错误https://github.com/gaoxiang12/slam_in_autonomous_driving/issues/18
3. 编译器版本问题https://github.com/gaoxiang12/slam_in_autonomous_driving/issues/4
4. g2o编译问题config.h找不到 https://github.com/gaoxiang12/slam_in_autonomous_driving/issues/95
## TODO项
- LioPreiteg在某些数据集上不收敛
## NOTES
- [已确认] ULHK的IMU似乎和别家的不一样已经去了gravity, iekf初期可能有问题
- [已确认] NCLT的IMU在转包的时候转成了Lidar系于是Lidar与IMU之间没有旋转的外参本来Lidar是转了90度的现在Lidar是X左Y后Z下原车是X前Y右Z下。本书使用的NCLT数据均基于点云系,
IMU的杆臂被忽略。
- [已确认] NCLT的rtk fix并不是非常稳定平均误差在米级

View File

@@ -0,0 +1,58 @@
# coding=UTF-8
import sys
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def PlotPath(keyframes):
fig = plt.figure('All path 3d')
ax = Axes3D(fig)
# lidar pose
p00, = ax.plot(keyframes[:, 5], keyframes[:, 6], keyframes[:, 7], 'y-')
# gps pose
p01, = ax.plot(keyframes[:, 12], keyframes[:, 13], keyframes[:, 14], 'g-')
# opti1
p02, = ax.plot(keyframes[:, 19], keyframes[:, 20], keyframes[:, 21], 'b-')
# opti2
# p03, = ax.plot(keyframes[:, 26], keyframes[:, 27], keyframes[:, 28], 'r-')
plt.title("All path", fontsize=16)
plt.grid()
fig = plt.figure('All path 2d')
# lidar
p0, = plt.plot(keyframes[:, 5], keyframes[:, 6], 'y-')
# gps
p1, = plt.plot(keyframes[:, 12], keyframes[:, 13], 'g-')
# opti1
p2, = plt.plot(keyframes[:, 19], keyframes[:, 20], 'b-')
# opti2
# p3, = plt.plot(keyframes[:, 26], keyframes[:, 27], 'r-')
# plt.legend(['Lidar', 'RTK', 'Opti1', 'Opti2'])
plt.legend(['Lidar', 'RTK', 'Opti1'])
plt.show()
def LoadMappingTxt(filepath):
keyframes = np.loadtxt(filepath)
PlotPath(keyframes)
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Please input vaild param !!!')
exit(1)
else:
path = sys.argv[1]
LoadMappingTxt(path)
exit(1)
exit(1)

View File

@@ -0,0 +1,25 @@
# coding=UTF-8
import sys
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 格式t x y z qx qy qz qw
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Please input valid file')
exit(1)
else:
path = sys.argv[1]
path_data = np.loadtxt(path)
plt.rcParams['figure.figsize'] = (16.0, 12.0)
# 2D轨迹
plt.scatter(path_data[:, 1], path_data[:, 2], s=2)
plt.xlabel('X')
plt.ylabel('Y')
plt.grid()
plt.title('2D trajectory')
plt.show()
exit(1)

View File

@@ -0,0 +1,20 @@
# coding=UTF-8
import sys
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 格式t x y z qx qy qz qw
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Please input valid file')
exit(1)
else:
path = sys.argv[1]
path_data = np.loadtxt(path)
fig = plt.figure()
ax = Axes3D(fig)
ax.plot(path_data[:, 1], path_data[:, 2], path_data[:, 3])
plt.title('3D path')
plt.show()
exit(1)

View File

@@ -0,0 +1,43 @@
# coding=UTF-8
import sys
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 格式t x y z qx qy qz qw vx vy vz
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Please input valid file')
exit(1)
else:
path = sys.argv[1]
path_data = np.loadtxt(path)
plt.rcParams['figure.figsize'] = (16.0, 12.0)
# 轨迹
plt.subplot(121)
plt.scatter(path_data[:, 1], path_data[:, 2], s=2)
plt.xlabel('X')
plt.ylabel('Y')
plt.grid()
plt.title('2D trajectory')
# 姿态
plt.subplot(222)
plt.plot(path_data[:, 0], path_data[:, 4], 'r')
plt.plot(path_data[:, 0], path_data[:, 5], 'g')
plt.plot(path_data[:, 0], path_data[:, 6], 'b')
plt.plot(path_data[:, 0], path_data[:, 7], 'k')
plt.title('q')
plt.legend(['qw', 'qx', 'qy', 'qz'])
# 速度
plt.subplot(224)
plt.plot(path_data[:, 0], path_data[:, 8], 'r')
plt.plot(path_data[:, 0], path_data[:, 9], 'g')
plt.plot(path_data[:, 0], path_data[:, 10], 'b')
plt.title('v')
plt.legend(['vx', 'vy', 'vz'])
plt.show()
exit(1)

View File

@@ -0,0 +1,53 @@
# coding=UTF-8
import sys
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Please input valid file')
exit(1)
else:
path = sys.argv[1]
path_data = np.loadtxt(path)
plt.rcParams['figure.figsize'] = (16.0, 12.0)
# 轨迹
plt.subplot(121)
plt.scatter(path_data[:, 1], path_data[:, 2], s=2)
plt.xlabel('X')
plt.ylabel('Y')
plt.grid()
plt.title('2D trajectory')
# 速度
plt.subplot(322)
plt.plot(path_data[:, 0], path_data[:, 8], 'r')
plt.plot(path_data[:, 0], path_data[:, 9], 'g')
plt.plot(path_data[:, 0], path_data[:, 10], 'b')
plt.title('v')
plt.legend(['vx', 'vy', 'vz'])
# 零偏
plt.subplot(324)
plt.plot(path_data[:, 0], path_data[:, 11], 'r')
plt.plot(path_data[:, 0], path_data[:, 12], 'g')
plt.plot(path_data[:, 0], path_data[:, 13], 'b')
plt.title('bias gyro')
plt.legend(['bg_x', 'bg_y', 'bg_z'])
plt.subplot(326)
plt.plot(path_data[:, 0], path_data[:, 14], 'r')
plt.plot(path_data[:, 0], path_data[:, 15], 'g')
plt.plot(path_data[:, 0], path_data[:, 16], 'b')
plt.title('bias acce')
plt.legend(['ba_x', 'ba_y', 'ba_z'])
# 3D轨迹
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(path_data[:, 1], path_data[:, 2], path_data[:, 3], s=2)
plt.show()
exit(1)

View File

@@ -0,0 +1,18 @@
# coding=UTF-8
import sys
import numpy as np
import matplotlib.pyplot as plt
method_names = ['Brute-force', 'Brute-force MT', '2D Grid8', '2D Grid8 MT', '3D Grid', '3D Grid MT', 'KD Tree',
'KD Tree MT',
'KD Tree PCL', 'Octo Tree', 'Octo Tree MT']
time_usage = [821.109, 102.074, 7.6908, 0.7044, 3.1735, 0.3916, 9.6234, 2.0651, 11.8725, 31.9253, 4.7698]
fig, ax = plt.subplots()
plt.bar(method_names, time_usage, color='rgbky')
plt.title('NN methods')
ax.set_yscale('log')
# plt.xlabel("method name")
plt.ylabel("runtime(s)")
plt.grid()
plt.show()

View File

@@ -0,0 +1,39 @@
# coding=UTF-8
import sys
import numpy as np
import matplotlib.pyplot as plt
method_names = ['Point-Point ICP', 'Point-Plane ICP', 'Point-Line ICP', 'NDT', 'PCL ICP', 'PCL NDT']
time_usage = [399.808, 334.409, 492.718, 142.278, 2774.16, 255.947]
res_icp_point = [0.211044, 0.155513, 0.114218, 0.0837873, 0.0611407, 0.0444493, 0.0320967, 0.0229813]
res_icp_plane = [0.0946098, 0.00664127, 0.000288226]
res_icp_line = [0.20137, 0.138774, 0.0931768, 0.0618093, 0.0410299, 0.0276539, 0.0192892]
res_ndt = [0.148351, 0.0597357, 0.0227735, 0.0100937, 0.00690136]
res_icp_pcl = [0.0483]
res_ndt_pcl = [0.1603]
# 用时
plt.figure()
plt.bar(method_names, time_usage, color='rgbkyc')
plt.title('Align Time Usage')
plt.xlabel("method name")
plt.ylabel("runtime(ms)")
plt.grid()
plt.show()
# 收敛曲线
plt.figure()
plt.plot(range(0, len(res_icp_point)), res_icp_point, color='r', marker='o')
plt.plot(range(0, len(res_icp_plane)), res_icp_plane, color='g', marker='o')
plt.plot(range(0, len(res_icp_line)), res_icp_line, color='b', marker='o')
plt.plot(range(0, len(res_ndt)), res_ndt, color='k', marker='o')
plt.axhline(res_icp_pcl, color='y')
plt.axhline(res_ndt_pcl, color='c')
# plt.yscale('log')
plt.title('Pose Convergence Speed')
plt.xlabel("iteration")
plt.ylabel("Pose error")
plt.legend(method_names)
plt.grid()
plt.show()

View File

@@ -0,0 +1,12 @@
add_subdirectory(common)
add_subdirectory(ch2)
add_subdirectory(ch3)
add_subdirectory(ch4)
add_subdirectory(ch5)
add_subdirectory(ch6)
add_subdirectory(ch7)
add_subdirectory(ch8)
add_subdirectory(ch9)
add_subdirectory(ch10)
add_subdirectory(tools)

View File

@@ -0,0 +1,15 @@
add_library(${PROJECT_NAME}.ch10
fusion.cc
)
target_link_libraries(${PROJECT_NAME}.ch10
${PROJECT_NAME}.ch3
${PROJECT_NAME}.ch7
${third_party_libs}
)
add_executable(run_fusion_offline run_fusion_offline.cc)
target_link_libraries(run_fusion_offline
${PROJECT_NAME}.ch10
${third_party_libs}
)

View File

@@ -0,0 +1,316 @@
//
// Created by xiang on 22-12-20.
//
#include <yaml-cpp/yaml.h>
#include <execution>
#include "common/lidar_utils.h"
#include "fusion.h"
namespace sad {
Fusion::Fusion(const std::string& config_yaml) {
config_yaml_ = config_yaml;
StaticIMUInit::Options imu_init_options;
imu_init_options.use_speed_for_static_checking_ = false; // 本节数据不需要轮速计
imu_init_ = StaticIMUInit(imu_init_options);
ndt_.setResolution(1.0);
}
bool Fusion::Init() {
// 地图原点
auto yaml = YAML::LoadFile(config_yaml_);
auto origin_data = yaml["origin"].as<std::vector<double>>();
map_origin_ = Vec3d(origin_data[0], origin_data[1], origin_data[2]);
// 地图目录
data_path_ = yaml["map_data"].as<std::string>();
LoadMapIndex();
// lidar和IMU消息同步
sync_ = std::make_shared<MessageSync>([this](const MeasureGroup& m) { ProcessMeasurements(m); });
sync_->Init(config_yaml_);
// lidar和IMU外参
std::vector<double> ext_t = yaml["mapping"]["extrinsic_T"].as<std::vector<double>>();
std::vector<double> ext_r = yaml["mapping"]["extrinsic_R"].as<std::vector<double>>();
Vec3d lidar_T_wrt_IMU = math::VecFromArray(ext_t);
Mat3d lidar_R_wrt_IMU = math::MatFromArray(ext_r);
TIL_ = SE3(lidar_R_wrt_IMU, lidar_T_wrt_IMU);
// ui
ui_ = std::make_shared<ui::PangolinWindow>();
ui_->Init();
ui_->SetCurrentScanSize(50);
return true;
}
void Fusion::ProcessRTK(GNSSPtr gnss) {
gnss->utm_pose_.translation() -= map_origin_; // 减掉地图原点
last_gnss_ = gnss;
}
void Fusion::ProcessMeasurements(const MeasureGroup& meas) {
measures_ = meas;
if (imu_need_init_) {
TryInitIMU();
return;
}
/// 以下三步与LIO一致只是align完成地图匹配工作
if (status_ == Status::WORKING) {
Predict();
Undistort();
} else {
scan_undistort_ = measures_.lidar_;
}
Align();
}
void Fusion::TryInitIMU() {
for (auto imu : measures_.imu_) {
imu_init_.AddIMU(*imu);
}
if (imu_init_.InitSuccess()) {
// 读取初始零偏设置ESKF
sad::ESKFD::Options options;
// 噪声由初始化器估计
// options.gyro_var_ = sqrt(imu_init_.GetCovGyro()[0]);
// options.acce_var_ = sqrt(imu_init_.GetCovAcce()[0]);
options.update_bias_acce_ = false;
options.update_bias_gyro_ = false;
eskf_.SetInitialConditions(options, imu_init_.GetInitBg(), imu_init_.GetInitBa(), imu_init_.GetGravity());
imu_need_init_ = false;
LOG(INFO) << "IMU初始化成功";
}
}
void Fusion::Predict() {
imu_states_.clear();
imu_states_.emplace_back(eskf_.GetNominalState());
/// 对IMU状态进行预测
for (auto& imu : measures_.imu_) {
eskf_.Predict(*imu);
imu_states_.emplace_back(eskf_.GetNominalState());
}
}
void Fusion::Undistort() {
auto cloud = measures_.lidar_;
auto imu_state = eskf_.GetNominalState(); // 最后时刻的状态
SE3 T_end = SE3(imu_state.R_, imu_state.p_);
/// 将所有点转到最后时刻状态上
std::for_each(std::execution::par_unseq, cloud->points.begin(), cloud->points.end(), [&](auto& pt) {
SE3 Ti = T_end;
NavStated match;
// 根据pt.time查找时间pt.time是该点打到的时间与雷达开始时间之差单位为毫秒
math::PoseInterp<NavStated>(
measures_.lidar_begin_time_ + pt.time * 1e-3, imu_states_, [](const NavStated& s) { return s.timestamp_; },
[](const NavStated& s) { return s.GetSE3(); }, Ti, match);
Vec3d pi = ToVec3d(pt);
Vec3d p_compensate = TIL_.inverse() * T_end.inverse() * Ti * TIL_ * pi;
pt.x = p_compensate(0);
pt.y = p_compensate(1);
pt.z = p_compensate(2);
});
scan_undistort_ = cloud;
}
void Fusion::Align() {
FullCloudPtr scan_undistort_trans(new FullPointCloudType);
pcl::transformPointCloud(*scan_undistort_, *scan_undistort_trans, TIL_.matrix());
scan_undistort_ = scan_undistort_trans;
current_scan_ = ConvertToCloud<FullPointType>(scan_undistort_);
current_scan_ = VoxelCloud(current_scan_, 0.5);
if (status_ == Status::WAITING_FOR_RTK) {
// 若存在最近的RTK信号则尝试初始化
if (last_gnss_ != nullptr) {
if (SearchRTK()) {
status_ == Status::WORKING;
ui_->UpdateScan(current_scan_, eskf_.GetNominalSE3());
ui_->UpdateNavState(eskf_.GetNominalState());
}
}
} else {
LidarLocalization();
ui_->UpdateScan(current_scan_, eskf_.GetNominalSE3());
ui_->UpdateNavState(eskf_.GetNominalState());
}
}
bool Fusion::SearchRTK() {
if (init_has_failed_) {
if ((last_gnss_->utm_pose_.translation() - last_searched_pos_.translation()).norm() < 20.0) {
LOG(INFO) << "skip this position";
return false;
}
}
// 由于RTK不带姿态我们必须先搜索一定的角度范围
std::vector<GridSearchResult> search_poses;
LoadMap(last_gnss_->utm_pose_);
/// 由于RTK不带角度这里按固定步长扫描RTK角度
double grid_ang_range = 360.0, grid_ang_step = 10; // 角度搜索范围与步长
for (double ang = 0; ang < grid_ang_range; ang += grid_ang_step) {
SE3 pose(SO3::rotZ(ang * math::kDEG2RAD), Vec3d(0, 0, 0) + last_gnss_->utm_pose_.translation());
GridSearchResult gr;
gr.pose_ = pose;
search_poses.emplace_back(gr);
}
LOG(INFO) << "grid search poses: " << search_poses.size();
std::for_each(std::execution::par_unseq, search_poses.begin(), search_poses.end(),
[this](GridSearchResult& gr) { AlignForGrid(gr); });
// 选择最优的匹配结果
auto max_ele = std::max_element(search_poses.begin(), search_poses.end(),
[](const auto& g1, const auto& g2) { return g1.score_ < g2.score_; });
LOG(INFO) << "max score: " << max_ele->score_ << ", pose: \n" << max_ele->result_pose_.matrix();
if (max_ele->score_ > rtk_search_min_score_) {
LOG(INFO) << "初始化成功, score: " << max_ele->score_ << ">" << rtk_search_min_score_;
status_ = Status::WORKING;
/// 重置滤波器状态
auto state = eskf_.GetNominalState();
state.R_ = max_ele->result_pose_.so3();
state.p_ = max_ele->result_pose_.translation();
state.v_.setZero();
eskf_.SetX(state, eskf_.GetGravity());
ESKFD::Mat18T cov;
cov = ESKFD::Mat18T::Identity() * 1e-4;
cov.block<12, 12>(6, 6) = Eigen::Matrix<double, 12, 12>::Identity() * 1e-6;
eskf_.SetCov(cov);
return true;
}
init_has_failed_ = true;
last_searched_pos_ = last_gnss_->utm_pose_;
return false;
}
void Fusion::AlignForGrid(sad::Fusion::GridSearchResult& gr) {
/// 多分辨率
pcl::NormalDistributionsTransform<PointType, PointType> ndt;
ndt.setTransformationEpsilon(0.05);
ndt.setStepSize(0.7);
ndt.setMaximumIterations(40);
ndt.setInputSource(current_scan_);
auto map = ref_cloud_;
CloudPtr output(new PointCloudType);
std::vector<double> res{10.0, 5.0, 4.0, 3.0};
Mat4f T = gr.pose_.matrix().cast<float>();
for (auto& r : res) {
auto rough_map = VoxelCloud(map, r * 0.1);
ndt.setInputTarget(rough_map);
ndt.setResolution(r);
ndt.align(*output, T);
T = ndt.getFinalTransformation();
}
gr.score_ = ndt.getTransformationProbability();
gr.result_pose_ = Mat4ToSE3(ndt.getFinalTransformation());
}
bool Fusion::LidarLocalization() {
SE3 pred = eskf_.GetNominalSE3();
LoadMap(pred);
ndt_.setInputCloud(current_scan_);
CloudPtr output(new PointCloudType);
ndt_.align(*output, pred.matrix().cast<float>());
SE3 pose = Mat4ToSE3(ndt_.getFinalTransformation());
eskf_.ObserveSE3(pose, 1e-1, 1e-2);
LOG(INFO) << "lidar loc score: " << ndt_.getTransformationProbability();
return true;
}
void Fusion::LoadMap(const SE3& pose) {
int gx = floor((pose.translation().x() - 50.0) / 100);
int gy = floor((pose.translation().y() - 50.0) / 100);
Vec2i key(gx, gy);
// 一个区域的周边地图我们认为9个就够了
std::set<Vec2i, less_vec<2>> surrounding_index{
key + Vec2i(0, 0), key + Vec2i(-1, 0), key + Vec2i(-1, -1), key + Vec2i(-1, 1), key + Vec2i(0, -1),
key + Vec2i(0, 1), key + Vec2i(1, 0), key + Vec2i(1, -1), key + Vec2i(1, 1),
};
// 加载必要区域
bool map_data_changed = false;
int cnt_new_loaded = 0, cnt_unload = 0;
for (auto& k : surrounding_index) {
if (map_data_index_.find(k) == map_data_index_.end()) {
// 该地图数据不存在
continue;
}
if (map_data_.find(k) == map_data_.end()) {
// 加载这个区块
CloudPtr cloud(new PointCloudType);
pcl::io::loadPCDFile(data_path_ + std::to_string(k[0]) + "_" + std::to_string(k[1]) + ".pcd", *cloud);
map_data_.emplace(k, cloud);
map_data_changed = true;
cnt_new_loaded++;
}
}
// 卸载不需要的区域,这个稍微加大一点,不需要频繁卸载
for (auto iter = map_data_.begin(); iter != map_data_.end();) {
if ((iter->first - key).cast<float>().norm() > 3.0) {
// 卸载本区块
iter = map_data_.erase(iter);
cnt_unload++;
map_data_changed = true;
} else {
iter++;
}
}
LOG(INFO) << "new loaded: " << cnt_new_loaded << ", unload: " << cnt_unload;
if (map_data_changed) {
// rebuild ndt target map
ref_cloud_.reset(new PointCloudType);
for (auto& mp : map_data_) {
*ref_cloud_ += *mp.second;
}
LOG(INFO) << "rebuild global cloud, grids: " << map_data_.size();
ndt_.setInputTarget(ref_cloud_);
}
ui_->UpdatePointCloudGlobal(map_data_);
}
void Fusion::LoadMapIndex() {
std::ifstream fin(data_path_ + "/map_index.txt");
while (!fin.eof()) {
int x, y;
fin >> x >> y;
map_data_index_.emplace(Vec2i(x, y));
}
fin.close();
}
void Fusion::ProcessIMU(IMUPtr imu) { sync_->ProcessIMU(imu); }
void Fusion::ProcessPointCloud(sensor_msgs::PointCloud2::Ptr cloud) { sync_->ProcessCloud(cloud); }
} // namespace sad

View File

@@ -0,0 +1,128 @@
//
// Created by xiang on 22-12-20.
//
#ifndef SLAM_IN_AUTO_DRIVING_FUSION_H
#define SLAM_IN_AUTO_DRIVING_FUSION_H
#include "ch3/eskf.hpp"
#include "ch3/static_imu_init.h"
#include "common/eigen_types.h"
#include "common/gnss.h"
#include "common/imu.h"
#include "common/message_def.h"
#include "common/point_types.h"
#include "ch7/loosely_coupled_lio/cloud_convert.h"
#include "ch7/loosely_coupled_lio/measure_sync.h"
#include "tools/ui/pangolin_window.h"
#include <pcl/registration/ndt.h>
#include <sensor_msgs/PointCloud2.h>
namespace sad {
/**
* 第10章显示的高精度融合定位融合IMU、RTK、激光点云定位功能
*
* - NOTE 一些IMU的异常处理没有加在这里有可能会被IMU带歪。
*/
class Fusion {
public:
explicit Fusion(const std::string& config_yaml);
enum class Status {
WAITING_FOR_RTK, // 等待初始的RTK
WORKING, // 正常工作
};
/// 初始化,读取参数
bool Init();
/// 处理输入的RTK
void ProcessRTK(GNSSPtr gnss);
void ProcessIMU(IMUPtr imu);
void ProcessPointCloud(sensor_msgs::PointCloud2::Ptr cloud);
private:
/// 读取某个点附近的地图
void LoadMap(const SE3& pose);
/// 处理同步之后的IMU和雷达数据
void ProcessMeasurements(const MeasureGroup& meas);
/// 读取地图的索引文件
void LoadMapIndex();
/// 网格搜索时的结构
struct GridSearchResult {
SE3 pose_;
SE3 result_pose_;
double score_ = 0.0;
};
/// 在初始RTK附近搜索车辆位置
bool SearchRTK();
/// 对网格搜索的某个点进行配准,得到配准后位姿与配准分值
void AlignForGrid(GridSearchResult& gr);
/// 激光定位
bool LidarLocalization();
/// 使用IMU初始化
void TryInitIMU();
/// 利用IMU预测状态信息
/// 这段时间的预测数据会放入imu_states_里
void Predict();
/// 对measures_中的点云去畸变
void Undistort();
/// 执行一次配准和观测
void Align();
/// 标志位
Status status_ = Status::WAITING_FOR_RTK;
/// 数据
std::string config_yaml_; // 配置文件路径
Vec3d map_origin_ = Vec3d::Zero(); // 地图原点
std::string data_path_; // 地图数据目录
std::set<Vec2i, less_vec<2>> map_data_index_; // 哪些格子存在地图数据
std::map<Vec2i, CloudPtr, less_vec<2>> map_data_; // 第9章建立的地图数据
std::shared_ptr<MessageSync> sync_ = nullptr; // 消息同步器
StaticIMUInit imu_init_; // IMU静止初始化
/// 滤波器
ESKFD eskf_;
std::vector<NavStated> imu_states_; // ESKF预测期间的状态
FullCloudPtr scan_undistort_{new FullPointCloudType()}; // scan after undistortion
CloudPtr current_scan_ = nullptr;
SE3 TIL_;
MeasureGroup measures_; // sync IMU and lidar scan
GNSSPtr last_gnss_ = nullptr;
bool init_has_failed_ = false; // 初始化是否失败过
SE3 last_searched_pos_; // 上次搜索的GNSS位置
/// 激光定位
bool imu_need_init_ = true; // 是否需要估计IMU初始零偏
CloudPtr ref_cloud_ = nullptr; // NDT用于参考的点云
pcl::NormalDistributionsTransform<PointType, PointType> ndt_;
/// 参数
double rtk_search_min_score_ = 4.5;
// ui
std::shared_ptr<ui::PangolinWindow> ui_ = nullptr;
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_FUSION_H

View File

@@ -0,0 +1,48 @@
//
// Created by xiang on 22-12-20.
//
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <yaml-cpp/yaml.h>
#include "common/io_utils.h"
#include "fusion.h"
DEFINE_string(config_yaml, "./config/mapping.yaml", "配置文件");
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
sad::Fusion fusion(FLAGS_config_yaml);
if (!fusion.Init()) {
return -1;
}
auto yaml = YAML::LoadFile(FLAGS_config_yaml);
auto bag_path = yaml["bag_path"].as<std::string>();
sad::RosbagIO rosbag_io(bag_path, sad::DatasetType::NCLT);
/// 把各种消息交给fusion
rosbag_io
.AddAutoRTKHandle([&fusion](GNSSPtr gnss) {
fusion.ProcessRTK(gnss);
return true;
})
.AddAutoPointCloudHandle([&](sensor_msgs::PointCloud2::Ptr cloud) -> bool {
fusion.ProcessPointCloud(cloud);
return true;
})
.AddImuHandle([&](IMUPtr imu) {
fusion.ProcessIMU(imu);
return true;
})
.Go();
LOG(INFO) << "done.";
sleep(10);
return 0;
}

View File

@@ -0,0 +1,5 @@
add_executable(motion motion.cc)
target_link_libraries(motion
${PROJECT_NAME}.common
${PROJECT_NAME}.tools
)

View File

@@ -0,0 +1,59 @@
//
// Created by xiang on 22-12-29.
//
#include <gflags/gflags.h>
#include <glog/logging.h>
#include "common/eigen_types.h"
#include "common/math_utils.h"
#include "tools/ui/pangolin_window.h"
/// 本节程序演示一个正在作圆周运动的车辆
/// 车辆的角速度与线速度可以在flags中设置
DEFINE_double(angular_velocity, 10.0, "角速度(角度)制");
DEFINE_double(linear_velocity, 5.0, "车辆前进线速度 m/s");
DEFINE_bool(use_quaternion, false, "是否使用四元数计算");
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
/// 可视化
sad::ui::PangolinWindow ui;
if (ui.Init() == false) {
return -1;
}
double angular_velocity_rad = FLAGS_angular_velocity * sad::math::kDEG2RAD; // 弧度制角速度
SE3 pose; // TWB表示的位姿
Vec3d omega(0, 0, angular_velocity_rad); // 角速度矢量
Vec3d v_body(FLAGS_linear_velocity, 0, 0); // 本体系速度
const double dt = 0.05; // 每次更新的时间
while (ui.ShouldQuit() == false) {
// 更新自身位置
Vec3d v_world = pose.so3() * v_body;
pose.translation() += v_world * dt;
// 更新自身旋转
if (FLAGS_use_quaternion) {
Quatd q = pose.unit_quaternion() * Quatd(1, 0.5 * omega[0] * dt, 0.5 * omega[1] * dt, 0.5 * omega[2] * dt);
q.normalize();
pose.so3() = SO3(q);
} else {
pose.so3() = pose.so3() * SO3::exp(omega * dt);
}
LOG(INFO) << "pose: " << pose.translation().transpose();
ui.UpdateNavState(sad::NavStated(0, pose, v_world));
usleep(dt * 1e6);
}
ui.Quit();
return 0;
}

View File

@@ -0,0 +1,32 @@
add_executable(run_imu_integration
run_imu_integration.cc
)
target_link_libraries(run_imu_integration
glog gflags ${PROJECT_NAME}.common
)
add_executable(run_eskf_gins run_eskf_gins.cc)
target_link_libraries(run_eskf_gins
glog gflags ${PROJECT_NAME}.common ${PROJECT_NAME}.ch3
)
add_executable(process_gnss process_gnss.cc)
target_link_libraries(process_gnss
glog gflags ${PROJECT_NAME}.common ${PROJECT_NAME}.ch3
)
add_library(${PROJECT_NAME}.ch3
static_imu_init.cc
utm_convert.cc
# ieskf/nav_state_manifold.cc
# ieskf/ieskf.cc
${PROJECT_SOURCE_DIR}/thirdparty/utm_convert/utm.cc
${PROJECT_SOURCE_DIR}/thirdparty/utm_convert/tranmerc.cc
)
target_link_libraries(${PROJECT_NAME}.ch3
glog gflags ${PROJECT_NAME}.common
)

View File

@@ -0,0 +1,336 @@
//
// Created by xiang on 2021/11/11.
//
#ifndef SLAM_IN_AUTO_DRIVING_ESKF_HPP
#define SLAM_IN_AUTO_DRIVING_ESKF_HPP
#include "common/eigen_types.h"
#include "common/gnss.h"
#include "common/imu.h"
#include "common/math_utils.h"
#include "common/nav_state.h"
#include "common/odom.h"
#include <glog/logging.h>
#include <iomanip>
namespace sad {
/**
* 书本第3章介绍的误差卡尔曼滤波器
* 可以指定观测GNSS的读数GNSS应该事先转换到车体坐标系
*
* 本书使用18维的ESKF标量类型可以由S指定默认取double
* 变量顺序p, v, R, bg, ba, grav与书本对应
* @tparam S 状态变量的精度取float或double
*/
template <typename S = double>
class ESKF {
public:
/// 类型定义
using SO3 = Sophus::SO3<S>; // 旋转变量类型
using VecT = Eigen::Matrix<S, 3, 1>; // 向量类型
using Vec18T = Eigen::Matrix<S, 18, 1>; // 18维向量类型
using Mat3T = Eigen::Matrix<S, 3, 3>; // 3x3矩阵类型
using MotionNoiseT = Eigen::Matrix<S, 18, 18>; // 运动噪声类型
using OdomNoiseT = Eigen::Matrix<S, 3, 3>; // 里程计噪声类型
using GnssNoiseT = Eigen::Matrix<S, 6, 6>; // GNSS噪声类型
using Mat18T = Eigen::Matrix<S, 18, 18>; // 18维方差类型
using NavStateT = NavState<S>; // 整体名义状态变量类型
struct Options {
Options() = default;
/// IMU 测量与零偏参数
double imu_dt_ = 0.01; // IMU测量间隔
// NOTE IMU噪声项都为离散时间不需要再乘dt可以由初始化器指定IMU噪声
double gyro_var_ = 1e-5; // 陀螺测量标准差
double acce_var_ = 1e-2; // 加计测量标准差
double bias_gyro_var_ = 1e-6; // 陀螺零偏游走标准差
double bias_acce_var_ = 1e-4; // 加计零偏游走标准差
/// 里程计参数
double odom_var_ = 0.5;
double odom_span_ = 0.1; // 里程计测量间隔
double wheel_radius_ = 0.155; // 轮子半径
double circle_pulse_ = 1024.0; // 编码器每圈脉冲数
/// RTK 观测参数
double gnss_pos_noise_ = 0.1; // GNSS位置噪声
double gnss_height_noise_ = 0.1; // GNSS高度噪声
double gnss_ang_noise_ = 1.0 * math::kDEG2RAD; // GNSS旋转噪声
/// 其他配置
bool update_bias_gyro_ = true; // 是否更新陀螺bias
bool update_bias_acce_ = true; // 是否更新加计bias
};
/**
* 初始零偏取零
*/
ESKF(Options option = Options()) : options_(option) { BuildNoise(option); }
/**
* 设置初始条件
* @param options 噪声项配置
* @param init_bg 初始零偏 陀螺
* @param init_ba 初始零偏 加计
* @param gravity 重力
*/
void SetInitialConditions(Options options, const VecT& init_bg, const VecT& init_ba,
const VecT& gravity = VecT(0, 0, -9.8)) {
BuildNoise(options);
options_ = options;
bg_ = init_bg;
ba_ = init_ba;
g_ = gravity;
cov_ = Mat18T::Identity() * 1e-4;
}
/// 使用IMU递推
bool Predict(const IMU& imu);
/// 使用轮速计观测
bool ObserveWheelSpeed(const Odom& odom);
/// 使用GPS观测
bool ObserveGps(const GNSS& gnss);
/**
* 使用SE3进行观测
* @param pose 观测位姿
* @param trans_noise 平移噪声
* @param ang_noise 角度噪声
* @return
*/
bool ObserveSE3(const SE3& pose, double trans_noise = 0.1, double ang_noise = 1.0 * math::kDEG2RAD);
/// accessors
/// 获取全量状态
NavStateT GetNominalState() const { return NavStateT(current_time_, R_, p_, v_, bg_, ba_); }
/// 获取SE3 状态
SE3 GetNominalSE3() const { return SE3(R_, p_); }
/// 设置状态X
void SetX(const NavStated& x, const Vec3d& grav) {
current_time_ = x.timestamp_;
R_ = x.R_;
p_ = x.p_;
v_ = x.v_;
bg_ = x.bg_;
ba_ = x.ba_;
g_ = grav;
}
/// 设置协方差
void SetCov(const Mat18T& cov) { cov_ = cov; }
/// 获取重力
Vec3d GetGravity() const { return g_; }
private:
void BuildNoise(const Options& options) {
double ev = options.acce_var_;
double et = options.gyro_var_;
double eg = options.bias_gyro_var_;
double ea = options.bias_acce_var_;
double ev2 = ev; // * ev;
double et2 = et; // * et;
double eg2 = eg; // * eg;
double ea2 = ea; // * ea;
// 设置过程噪声
Q_.diagonal() << 0, 0, 0, ev2, ev2, ev2, et2, et2, et2, eg2, eg2, eg2, ea2, ea2, ea2, 0, 0, 0;
// 设置里程计噪声
double o2 = options_.odom_var_ * options_.odom_var_;
odom_noise_.diagonal() << o2, o2, o2;
// 设置GNSS状态
double gp2 = options.gnss_pos_noise_ * options.gnss_pos_noise_;
double gh2 = options.gnss_height_noise_ * options.gnss_height_noise_;
double ga2 = options.gnss_ang_noise_ * options.gnss_ang_noise_;
gnss_noise_.diagonal() << gp2, gp2, gh2, ga2, ga2, ga2;
}
/// 更新名义状态变量重置error state
void UpdateAndReset() {
p_ += dx_.template block<3, 1>(0, 0);
v_ += dx_.template block<3, 1>(3, 0);
R_ = R_ * SO3::exp(dx_.template block<3, 1>(6, 0));
if (options_.update_bias_gyro_) {
bg_ += dx_.template block<3, 1>(9, 0);
}
if (options_.update_bias_acce_) {
ba_ += dx_.template block<3, 1>(12, 0);
}
g_ += dx_.template block<3, 1>(15, 0);
ProjectCov();
dx_.setZero();
}
/// 对P阵进行投影参考式(3.63)
void ProjectCov() {
Mat18T J = Mat18T::Identity();
J.template block<3, 3>(6, 6) = Mat3T::Identity() - 0.5 * SO3::hat(dx_.template block<3, 1>(6, 0));
cov_ = J * cov_ * J.transpose();
}
/// 成员变量
double current_time_ = 0.0; // 当前时间
/// 名义状态
VecT p_ = VecT::Zero();
VecT v_ = VecT::Zero();
SO3 R_;
VecT bg_ = VecT::Zero();
VecT ba_ = VecT::Zero();
VecT g_{0, 0, -9.8};
/// 误差状态
Vec18T dx_ = Vec18T::Zero();
/// 协方差阵
Mat18T cov_ = Mat18T::Identity();
/// 噪声阵
MotionNoiseT Q_ = MotionNoiseT::Zero();
OdomNoiseT odom_noise_ = OdomNoiseT::Zero();
GnssNoiseT gnss_noise_ = GnssNoiseT::Zero();
/// 标志位
bool first_gnss_ = true; // 是否为第一个gnss数据
/// 配置项
Options options_;
};
using ESKFD = ESKF<double>;
using ESKFF = ESKF<float>;
template <typename S>
bool ESKF<S>::Predict(const IMU& imu) {
assert(imu.timestamp_ >= current_time_);
double dt = imu.timestamp_ - current_time_;
if (dt > (5 * options_.imu_dt_) || dt < 0) {
// 时间间隔不对可能是第一个IMU数据没有历史信息
LOG(INFO) << "skip this imu because dt_ = " << dt;
current_time_ = imu.timestamp_;
return false;
}
// nominal state 递推
VecT new_p = p_ + v_ * dt + 0.5 * (R_ * (imu.acce_ - ba_)) * dt * dt + 0.5 * g_ * dt * dt;
VecT new_v = v_ + R_ * (imu.acce_ - ba_) * dt + g_ * dt;
SO3 new_R = R_ * SO3::exp((imu.gyro_ - bg_) * dt);
R_ = new_R;
v_ = new_v;
p_ = new_p;
// 其余状态维度不变
// error state 递推
// 计算运动过程雅可比矩阵 F见(3.47)
// F实际上是稀疏矩阵也可以不用矩阵形式进行相乘而是写成散装形式这里为了教学方便使用矩阵形式
Mat18T F = Mat18T::Identity(); // 主对角线
F.template block<3, 3>(0, 3) = Mat3T::Identity() * dt; // p 对 v
F.template block<3, 3>(3, 6) = -R_.matrix() * SO3::hat(imu.acce_ - ba_) * dt; // v对theta
F.template block<3, 3>(3, 12) = -R_.matrix() * dt; // v 对 ba
F.template block<3, 3>(3, 15) = Mat3T::Identity() * dt; // v 对 g
F.template block<3, 3>(6, 6) = SO3::exp(-(imu.gyro_ - bg_) * dt).matrix(); // theta 对 theta
F.template block<3, 3>(6, 9) = -Mat3T::Identity() * dt; // theta 对 bg
// mean and cov prediction
dx_ = F * dx_; // 这行其实没必要算dx_在重置之后应该为零因此这步可以跳过但F需要参与Cov部分计算所以保留
cov_ = F * cov_.eval() * F.transpose() + Q_;
current_time_ = imu.timestamp_;
return true;
}
template <typename S>
bool ESKF<S>::ObserveWheelSpeed(const Odom& odom) {
assert(odom.timestamp_ >= current_time_);
// odom 修正以及雅可比
// 使用三维的轮速观测H为3x18大部分为零
Eigen::Matrix<S, 3, 18> H = Eigen::Matrix<S, 3, 18>::Zero();
H.template block<3, 3>(0, 3) = Mat3T::Identity();
// 卡尔曼增益
Eigen::Matrix<S, 18, 3> K = cov_ * H.transpose() * (H * cov_ * H.transpose() + odom_noise_).inverse();
// velocity obs
double velo_l = options_.wheel_radius_ * odom.left_pulse_ / options_.circle_pulse_ * 2 * M_PI / options_.odom_span_;
double velo_r =
options_.wheel_radius_ * odom.right_pulse_ / options_.circle_pulse_ * 2 * M_PI / options_.odom_span_;
double average_vel = 0.5 * (velo_l + velo_r);
VecT vel_odom(average_vel, 0.0, 0.0);
VecT vel_world = R_ * vel_odom;
dx_ = K * (vel_world - v_);
// update cov
cov_ = (Mat18T::Identity() - K * H) * cov_;
UpdateAndReset();
return true;
}
template <typename S>
bool ESKF<S>::ObserveGps(const GNSS& gnss) {
/// GNSS 观测的修正
assert(gnss.unix_time_ >= current_time_);
if (first_gnss_) {
R_ = gnss.utm_pose_.so3();
p_ = gnss.utm_pose_.translation();
first_gnss_ = false;
current_time_ = gnss.unix_time_;
return true;
}
assert(gnss.heading_valid_);
ObserveSE3(gnss.utm_pose_, options_.gnss_pos_noise_, options_.gnss_ang_noise_);
current_time_ = gnss.unix_time_;
return true;
}
template <typename S>
bool ESKF<S>::ObserveSE3(const SE3& pose, double trans_noise, double ang_noise) {
/// 既有旋转,也有平移
/// 观测状态变量中的p, RH为6x18其余为零
Eigen::Matrix<S, 6, 18> H = Eigen::Matrix<S, 6, 18>::Zero();
H.template block<3, 3>(0, 0) = Mat3T::Identity(); // P部分
H.template block<3, 3>(3, 6) = Mat3T::Identity(); // R部分3.66)
// 卡尔曼增益和更新过程
Vec6d noise_vec;
noise_vec << trans_noise, trans_noise, trans_noise, ang_noise, ang_noise, ang_noise;
Mat6d V = noise_vec.asDiagonal();
Eigen::Matrix<S, 18, 6> K = cov_ * H.transpose() * (H * cov_ * H.transpose() + V).inverse();
// 更新x和cov
Vec6d innov = Vec6d::Zero();
innov.template head<3>() = (pose.translation() - p_); // 平移部分
innov.template tail<3>() = (R_.inverse() * pose.so3()).log(); // 旋转部分(3.67)
dx_ = K * innov;
cov_ = (Mat18T::Identity() - K * H) * cov_;
UpdateAndReset();
return true;
}
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_ESKF_HPP

View File

@@ -0,0 +1,60 @@
//
// Created by xiang on 2021/11/5.
//
#ifndef SLAM_IN_AUTO_DRIVING_IMU_INTEGRATION_H
#define SLAM_IN_AUTO_DRIVING_IMU_INTEGRATION_H
#include "common/eigen_types.h"
#include "common/imu.h"
#include "common/nav_state.h"
namespace sad {
/**
* 本程序演示单纯靠IMU的积分
*/
class IMUIntegration {
public:
IMUIntegration(const Vec3d& gravity, const Vec3d& init_bg, const Vec3d& init_ba)
: gravity_(gravity), bg_(init_bg), ba_(init_ba) {}
// 增加imu读数
void AddIMU(const IMU& imu) {
double dt = imu.timestamp_ - timestamp_;
if (dt > 0 && dt < 0.1) {
// 假设IMU时间间隔在0至0.1以内
p_ = p_ + v_ * dt + 0.5 * gravity_ * dt * dt + 0.5 * (R_ * (imu.acce_ - ba_)) * dt * dt;
v_ = v_ + R_ * (imu.acce_ - ba_) * dt + gravity_ * dt;
R_ = R_ * Sophus::SO3d::exp((imu.gyro_ - bg_) * dt);
}
// 更新时间
timestamp_ = imu.timestamp_;
}
/// 组成NavState
NavStated GetNavState() const { return NavStated(timestamp_, R_, p_, v_, bg_, ba_); }
SO3 GetR() const { return R_; }
Vec3d GetV() const { return v_; }
Vec3d GetP() const { return p_; }
private:
// 累计量
SO3 R_;
Vec3d v_ = Vec3d::Zero();
Vec3d p_ = Vec3d::Zero();
double timestamp_ = 0.0;
// 零偏,由外部设定
Vec3d bg_ = Vec3d::Zero();
Vec3d ba_ = Vec3d::Zero();
Vec3d gravity_ = Vec3d(0, 0, -9.8); // 重力
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_IMU_INTEGRATION_H

View File

@@ -0,0 +1,94 @@
//
// Created by xiang on 2022/1/4.
//
#include <glog/logging.h>
#include <iomanip>
#include <memory>
#include "common/gnss.h"
#include "common/io_utils.h"
#include "tools/ui/pangolin_window.h"
#include "utm_convert.h"
DEFINE_string(txt_path, "./data/ch3/10.txt", "数据文件路径");
// 以下参数仅针对本书提供的数据
DEFINE_double(antenna_angle, 12.06, "RTK天线安装偏角角度");
DEFINE_double(antenna_pox_x, -0.17, "RTK天线安装偏移X");
DEFINE_double(antenna_pox_y, -0.20, "RTK天线安装偏移Y");
DEFINE_bool(with_ui, true, "是否显示图形界面");
/**
* 本程序演示如何处理GNSS数据
* 我们将GNSS原始读数处理成能够进行后续处理的6自由度Pose
* 需要处理UTM转换、RTK天线外参、坐标系转换三个步骤
*
* 我们将结果保存在文件中然后用python脚本进行可视化
*/
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
if (fLS::FLAGS_txt_path.empty()) {
return -1;
}
sad::TxtIO io(fLS::FLAGS_txt_path);
std::ofstream fout("./data/ch3/gnss_output.txt");
Vec2d antenna_pos(FLAGS_antenna_pox_x, FLAGS_antenna_pox_y);
auto save_result = [](std::ofstream& fout, double timestamp, const SE3& pose) {
auto save_vec3 = [](std::ofstream& fout, const Vec3d& v) { fout << v[0] << " " << v[1] << " " << v[2] << " "; };
auto save_quat = [](std::ofstream& fout, const Quatd& q) {
fout << q.w() << " " << q.x() << " " << q.y() << " " << q.z() << " ";
};
fout << std::setprecision(18) << timestamp << " " << std::setprecision(9);
save_vec3(fout, pose.translation());
save_quat(fout, pose.unit_quaternion());
fout << std::endl;
};
std::shared_ptr<sad::ui::PangolinWindow> ui = nullptr;
if (FLAGS_with_ui) {
ui = std::make_shared<sad::ui::PangolinWindow>();
ui->Init();
}
bool first_gnss_set = false;
Vec3d origin = Vec3d::Zero();
io.SetGNSSProcessFunc([&](const sad::GNSS& gnss) {
sad::GNSS gnss_out = gnss;
if (sad::ConvertGps2UTM(gnss_out, antenna_pos, FLAGS_antenna_angle)) {
if (!first_gnss_set) {
origin = gnss_out.utm_pose_.translation();
first_gnss_set = true;
}
/// 减掉一个原点
gnss_out.utm_pose_.translation() -= origin;
save_result(fout, gnss_out.unix_time_, gnss_out.utm_pose_);
if (ui) {
ui->UpdateNavState(
sad::NavStated(gnss_out.unix_time_, gnss_out.utm_pose_.so3(), gnss_out.utm_pose_.translation()));
usleep(1e3);
}
}
}).Go();
if (ui) {
while (!ui->ShouldQuit()) {
usleep(1e5);
}
ui->Quit();
}
return 0;
}

View File

@@ -0,0 +1,154 @@
//
// Created by xiang on 2021/11/11.
//
#include "ch3/eskf.hpp"
#include "ch3/static_imu_init.h"
#include "common/io_utils.h"
#include "tools/ui/pangolin_window.h"
#include "utm_convert.h"
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <fstream>
#include <iomanip>
DEFINE_string(txt_path, "./data/ch3/10.txt", "数据文件路径");
DEFINE_double(antenna_angle, 12.06, "RTK天线安装偏角角度");
DEFINE_double(antenna_pox_x, -0.17, "RTK天线安装偏移X");
DEFINE_double(antenna_pox_y, -0.20, "RTK天线安装偏移Y");
DEFINE_bool(with_ui, true, "是否显示图形界面");
DEFINE_bool(with_odom, false, "是否加入轮速计信息");
/**
* 本程序演示使用RTK+IMU进行组合导航
*/
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
if (fLS::FLAGS_txt_path.empty()) {
return -1;
}
// 初始化器
sad::StaticIMUInit imu_init; // 使用默认配置
sad::ESKFD eskf;
sad::TxtIO io(FLAGS_txt_path);
Vec2d antenna_pos(FLAGS_antenna_pox_x, FLAGS_antenna_pox_y);
auto save_vec3 = [](std::ofstream& fout, const Vec3d& v) { fout << v[0] << " " << v[1] << " " << v[2] << " "; };
auto save_quat = [](std::ofstream& fout, const Quatd& q) {
fout << q.w() << " " << q.x() << " " << q.y() << " " << q.z() << " ";
};
auto save_result = [&save_vec3, &save_quat](std::ofstream& fout, const sad::NavStated& save_state) {
fout << std::setprecision(18) << save_state.timestamp_ << " " << std::setprecision(9);
save_vec3(fout, save_state.p_);
save_quat(fout, save_state.R_.unit_quaternion());
save_vec3(fout, save_state.v_);
save_vec3(fout, save_state.bg_);
save_vec3(fout, save_state.ba_);
fout << std::endl;
};
std::ofstream fout("./data/ch3/gins.txt");
bool imu_inited = false, gnss_inited = false;
std::shared_ptr<sad::ui::PangolinWindow> ui = nullptr;
if (FLAGS_with_ui) {
ui = std::make_shared<sad::ui::PangolinWindow>();
ui->Init();
}
/// 设置各类回调函数
bool first_gnss_set = false;
Vec3d origin = Vec3d::Zero();
io.SetIMUProcessFunc([&](const sad::IMU& imu) {
/// IMU 处理函数
if (!imu_init.InitSuccess()) {
imu_init.AddIMU(imu);
return;
}
/// 需要IMU初始化
if (!imu_inited) {
// 读取初始零偏设置ESKF
sad::ESKFD::Options options;
// 噪声由初始化器估计
options.gyro_var_ = sqrt(imu_init.GetCovGyro()[0]);
options.acce_var_ = sqrt(imu_init.GetCovAcce()[0]);
eskf.SetInitialConditions(options, imu_init.GetInitBg(), imu_init.GetInitBa(), imu_init.GetGravity());
imu_inited = true;
return;
}
if (!gnss_inited) {
/// 等待有效的RTK数据
return;
}
/// GNSS 也接收到之后,再开始进行预测
eskf.Predict(imu);
/// predict就会更新ESKF所以此时就可以发送数据
auto state = eskf.GetNominalState();
if (ui) {
ui->UpdateNavState(state);
}
/// 记录数据以供绘图
save_result(fout, state);
usleep(1e3);
})
.SetGNSSProcessFunc([&](const sad::GNSS& gnss) {
/// GNSS 处理函数
if (!imu_inited) {
return;
}
sad::GNSS gnss_convert = gnss;
if (!sad::ConvertGps2UTM(gnss_convert, antenna_pos, FLAGS_antenna_angle) || !gnss_convert.heading_valid_) {
return;
}
/// 去掉原点
if (!first_gnss_set) {
origin = gnss_convert.utm_pose_.translation();
first_gnss_set = true;
}
gnss_convert.utm_pose_.translation() -= origin;
// 要求RTK heading有效才能合入ESKF
eskf.ObserveGps(gnss_convert);
auto state = eskf.GetNominalState();
if (ui) {
ui->UpdateNavState(state);
}
save_result(fout, state);
gnss_inited = true;
})
.SetOdomProcessFunc([&](const sad::Odom& odom) {
/// Odom 处理函数本章Odom只给初始化使用
imu_init.AddOdom(odom);
if (FLAGS_with_odom && imu_inited && gnss_inited) {
eskf.ObserveWheelSpeed(odom);
}
})
.Go();
while (ui && !ui->ShouldQuit()) {
usleep(1e5);
}
if (ui) {
ui->Quit();
}
return 0;
}

View File

@@ -0,0 +1,77 @@
//
// Created by xiang on 2021/11/5.
//
#include <glog/logging.h>
#include <iomanip>
#include "ch3/imu_integration.h"
#include "common/io_utils.h"
#include "tools/ui/pangolin_window.h"
DEFINE_string(imu_txt_path, "./data/ch3/10.txt", "数据文件路径");
DEFINE_bool(with_ui, true, "是否显示图形界面");
/// 本程序演示如何对IMU进行直接积分
/// 该程序需要输入data/ch3/下的文本文件同时它将状态输出到data/ch3/state.txt中在UI中也可以观察到车辆运动
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_imu_txt_path.empty()) {
return -1;
}
sad::TxtIO io(FLAGS_imu_txt_path);
// 该实验中,我们假设零偏已知
Vec3d gravity(0, 0, -9.8); // 重力方向
Vec3d init_bg(00.000224886, -7.61038e-05, -0.000742259);
Vec3d init_ba(-0.165205, 0.0926887, 0.0058049);
sad::IMUIntegration imu_integ(gravity, init_bg, init_ba);
std::shared_ptr<sad::ui::PangolinWindow> ui = nullptr;
if (FLAGS_with_ui) {
ui = std::make_shared<sad::ui::PangolinWindow>();
ui->Init();
}
/// 记录结果
auto save_result = [](std::ofstream& fout, double timestamp, const Sophus::SO3d& R, const Vec3d& v,
const Vec3d& p) {
auto save_vec3 = [](std::ofstream& fout, const Vec3d& v) { fout << v[0] << " " << v[1] << " " << v[2] << " "; };
auto save_quat = [](std::ofstream& fout, const Quatd& q) {
fout << q.w() << " " << q.x() << " " << q.y() << " " << q.z() << " ";
};
fout << std::setprecision(18) << timestamp << " " << std::setprecision(9);
save_vec3(fout, p);
save_quat(fout, R.unit_quaternion());
save_vec3(fout, v);
fout << std::endl;
};
std::ofstream fout("./data/ch3/state.txt");
io.SetIMUProcessFunc([&imu_integ, &save_result, &fout, &ui](const sad::IMU& imu) {
imu_integ.AddIMU(imu);
save_result(fout, imu.timestamp_, imu_integ.GetR(), imu_integ.GetV(), imu_integ.GetP());
if (ui) {
ui->UpdateNavState(imu_integ.GetNavState());
usleep(1e2);
}
}).Go();
// 打开了可视化的话,等待界面退出
while (ui && !ui->ShouldQuit()) {
usleep(1e4);
}
if (ui) {
ui->Quit();
}
return 0;
}

View File

@@ -0,0 +1,104 @@
//
// Created by xiang on 2021/11/11.
//
#include "ch3/static_imu_init.h"
#include "common/math_utils.h"
#include <glog/logging.h>
namespace sad {
bool StaticIMUInit::AddIMU(const IMU& imu) {
if (init_success_) {
return true;
}
if (options_.use_speed_for_static_checking_ && !is_static_) {
LOG(WARNING) << "等待车辆静止";
init_imu_deque_.clear();
return false;
}
if (init_imu_deque_.empty()) {
// 记录初始静止时间
init_start_time_ = imu.timestamp_;
}
// 记入初始化队列
init_imu_deque_.push_back(imu);
double init_time = imu.timestamp_ - init_start_time_; // 初始化经过时间
if (init_time > options_.init_time_seconds_) {
// 尝试初始化逻辑
TryInit();
}
// 维持初始化队列长度
while (init_imu_deque_.size() > options_.init_imu_queue_max_size_) {
init_imu_deque_.pop_front();
}
current_time_ = imu.timestamp_;
return false;
}
bool StaticIMUInit::AddOdom(const Odom& odom) {
// 判断车辆是否静止
if (init_success_) {
return true;
}
if (odom.left_pulse_ < options_.static_odom_pulse_ && odom.right_pulse_ < options_.static_odom_pulse_) {
is_static_ = true;
} else {
is_static_ = false;
}
current_time_ = odom.timestamp_;
return true;
}
bool StaticIMUInit::TryInit() {
if (init_imu_deque_.size() < 10) {
return false;
}
// 计算均值和方差
Vec3d mean_gyro, mean_acce;
math::ComputeMeanAndCovDiag(init_imu_deque_, mean_gyro, cov_gyro_, [](const IMU& imu) { return imu.gyro_; });
math::ComputeMeanAndCovDiag(init_imu_deque_, mean_acce, cov_acce_, [this](const IMU& imu) { return imu.acce_; });
// 以acce均值为方向取9.8长度为重力
LOG(INFO) << "mean acce: " << mean_acce.transpose();
gravity_ = -mean_acce / mean_acce.norm() * options_.gravity_norm_;
// 重新计算加计的协方差
math::ComputeMeanAndCovDiag(init_imu_deque_, mean_acce, cov_acce_,
[this](const IMU& imu) { return imu.acce_ + gravity_; });
// 检查IMU噪声
if (cov_gyro_.norm() > options_.max_static_gyro_var) {
LOG(ERROR) << "陀螺仪测量噪声太大" << cov_gyro_.norm() << " > " << options_.max_static_gyro_var;
return false;
}
if (cov_acce_.norm() > options_.max_static_acce_var) {
LOG(ERROR) << "加计测量噪声太大" << cov_acce_.norm() << " > " << options_.max_static_acce_var;
return false;
}
// 估计测量噪声和零偏
init_bg_ = mean_gyro;
init_ba_ = mean_acce;
LOG(INFO) << "IMU 初始化成功,初始化时间= " << current_time_ - init_start_time_ << ", bg = " << init_bg_.transpose()
<< ", ba = " << init_ba_.transpose() << ", gyro sq = " << cov_gyro_.transpose()
<< ", acce sq = " << cov_acce_.transpose() << ", grav = " << gravity_.transpose()
<< ", norm: " << gravity_.norm();
LOG(INFO) << "mean gyro: " << mean_gyro.transpose() << " acce: " << mean_acce.transpose();
init_success_ = true;
return true;
}
} // namespace sad

View File

@@ -0,0 +1,74 @@
//
// Created by xiang on 2021/11/11.
//
#ifndef SLAM_IN_AUTO_DRIVING_STATIC_IMU_INIT_H
#define SLAM_IN_AUTO_DRIVING_STATIC_IMU_INIT_H
#include "common/eigen_types.h"
#include "common/imu.h"
#include "common/odom.h"
#include <deque>
namespace sad {
/**
* IMU水平静止状态下初始化器
* 使用方法调用AddIMU, AddOdom添加数据使用InitSuccess获取初始化是否成功
* 成功后使用各Get函数获取内部参数
*
* 初始化器在每次调用AddIMU时尝试对系统进行初始化。在有odom的场合初始化要求odom轮速读数接近零没有时假设车辆初期静止。
* 初始化器收集一段时间内的IMU读数按照书本3.5.4节估计初始零偏和噪声参数提供给ESKF或者其他滤波器
*/
class StaticIMUInit {
public:
struct Options {
Options() {}
double init_time_seconds_ = 10.0; // 静止时间
int init_imu_queue_max_size_ = 2000; // 初始化IMU队列最大长度
int static_odom_pulse_ = 5; // 静止时轮速计输出噪声
double max_static_gyro_var = 0.5; // 静态下陀螺测量方差
double max_static_acce_var = 0.05; // 静态下加计测量方差
double gravity_norm_ = 9.81; // 重力大小
bool use_speed_for_static_checking_ = true; // 是否使用odom来判断车辆静止部分数据集没有odom选项
};
/// 构造函数
StaticIMUInit(Options options = Options()) : options_(options) {}
/// 添加IMU数据
bool AddIMU(const IMU& imu);
/// 添加轮速数据
bool AddOdom(const Odom& odom);
/// 判定初始化是否成功
bool InitSuccess() const { return init_success_; }
/// 获取各Cov, bias, gravity
Vec3d GetCovGyro() const { return cov_gyro_; }
Vec3d GetCovAcce() const { return cov_acce_; }
Vec3d GetInitBg() const { return init_bg_; }
Vec3d GetInitBa() const { return init_ba_; }
Vec3d GetGravity() const { return gravity_; }
private:
/// 尝试对系统初始化
bool TryInit();
Options options_; // 选项信息
bool init_success_ = false; // 初始化是否成功
Vec3d cov_gyro_ = Vec3d::Zero(); // 陀螺测量噪声协方差(初始化时评估)
Vec3d cov_acce_ = Vec3d::Zero(); // 加计测量噪声协方差(初始化时评估)
Vec3d init_bg_ = Vec3d::Zero(); // 陀螺初始零偏
Vec3d init_ba_ = Vec3d::Zero(); // 加计初始零偏
Vec3d gravity_ = Vec3d::Zero(); // 重力
bool is_static_ = false; // 标志车辆是否静止
std::deque<IMU> init_imu_deque_; // 初始化用的数据
double current_time_ = 0.0; // 当前时间
double init_start_time_ = 0.0; // 静止的初始时间
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_STATIC_IMU_INIT_H

View File

@@ -0,0 +1,84 @@
//
// Created by xiang on 2022/1/4.
//
#include "ch3/utm_convert.h"
#include "common/math_utils.h"
#include "utm_convert/utm.h"
#include <glog/logging.h>
namespace sad {
bool LatLon2UTM(const Vec2d& latlon, UTMCoordinate& utm_coor) {
long zone = 0;
char char_north = 0;
long ret = Convert_Geodetic_To_UTM(latlon[0] * math::kDEG2RAD, latlon[1] * math::kDEG2RAD, &zone, &char_north,
&utm_coor.xy_[0], &utm_coor.xy_[1]);
utm_coor.zone_ = (int)zone;
utm_coor.north_ = char_north == 'N';
return ret == 0;
}
bool UTM2LatLon(const UTMCoordinate& utm_coor, Vec2d& latlon) {
bool ret = Convert_UTM_To_Geodetic((long)utm_coor.zone_, utm_coor.north_ ? 'N' : 'S', utm_coor.xy_[0],
utm_coor.xy_[1], &latlon[0], &latlon[1]);
latlon *= math::kRAD2DEG;
return ret == 0;
}
bool ConvertGps2UTM(GNSS& gps_msg, const Vec2d& antenna_pos, const double& antenna_angle, const Vec3d& map_origin) {
/// 经纬高转换为UTM
UTMCoordinate utm_rtk;
if (!LatLon2UTM(gps_msg.lat_lon_alt_.head<2>(), utm_rtk)) {
return false;
}
utm_rtk.z_ = gps_msg.lat_lon_alt_[2];
/// GPS heading 转成弧度
double heading = 0;
if (gps_msg.heading_valid_) {
heading = (90 - gps_msg.heading_) * math::kDEG2RAD; // 北东地转到东北天
}
/// TWG 转到 TWB
SE3 TBG(SO3::rotZ(antenna_angle * math::kDEG2RAD), Vec3d(antenna_pos[0], antenna_pos[1], 0));
SE3 TGB = TBG.inverse();
/// 若指明地图原点,则减去地图原点
double x = utm_rtk.xy_[0] - map_origin[0];
double y = utm_rtk.xy_[1] - map_origin[1];
double z = utm_rtk.z_ - map_origin[2];
SE3 TWG(SO3::rotZ(heading), Vec3d(x, y, z));
SE3 TWB = TWG * TGB;
gps_msg.utm_valid_ = true;
gps_msg.utm_.xy_[0] = TWB.translation().x();
gps_msg.utm_.xy_[1] = TWB.translation().y();
gps_msg.utm_.z_ = TWB.translation().z();
if (gps_msg.heading_valid_) {
// 组装为带旋转的位姿
gps_msg.utm_pose_ = TWB;
} else {
// 组装为仅有平移的SE3
// 注意当安装偏移存在时,并不能实际推出车辆位姿
gps_msg.utm_pose_ = SE3(SO3(), TWB.translation());
}
return true;
}
bool ConvertGps2UTMOnlyTrans(GNSS& gps_msg) {
/// 经纬高转换为UTM
UTMCoordinate utm_rtk;
LatLon2UTM(gps_msg.lat_lon_alt_.head<2>(), utm_rtk);
gps_msg.utm_valid_ = true;
gps_msg.utm_.xy_ = utm_rtk.xy_;
gps_msg.utm_.z_ = gps_msg.lat_lon_alt_[2];
gps_msg.utm_pose_ = SE3(SO3(), Vec3d(gps_msg.utm_.xy_[0], gps_msg.utm_.xy_[1], gps_msg.utm_.z_));
return true;
}
} // namespace sad

View File

@@ -0,0 +1,49 @@
//
// Created by xiang on 2022/1/4.
//
#ifndef SLAM_IN_AUTO_DRIVING_UTM_CONVERT_H
#define SLAM_IN_AUTO_DRIVING_UTM_CONVERT_H
#include "common/gnss.h"
namespace sad {
/**
* 计算本书的GNSS读数对应的UTM pose和六自由度Pose
* @param gnss_reading 输入gnss读数
* @param antenna_pos 安装位置
* @param antenna_angle 安装偏角
* @param map_origin 地图原点指定时将从UTM位置中减掉坐标原点
* @return
*/
bool ConvertGps2UTM(GNSS& gnss_reading, const Vec2d& antenna_pos, const double& antenna_angle,
const Vec3d& map_origin = Vec3d::Zero());
/**
* 仅转换平移部分的经纬度,不作外参和角度处理
* @param gnss_reading
* @return
*/
bool ConvertGps2UTMOnlyTrans(GNSS& gnss_reading);
/**
* 经纬度转UTM
* NOTE 经纬度单位为度数
* @param latlon
* @param utm_coor
* @return
*/
bool LatLon2UTM(const Vec2d& latlon, UTMCoordinate& utm_coor);
/**
* UTM转经纬度
* @param utm_coor
* @param latlon
* @return
*/
bool UTM2LatLon(const UTMCoordinate& utm_coor, Vec2d& latlon);
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_UTM_CONVERT_H

View File

@@ -0,0 +1,19 @@
add_library(${PROJECT_NAME}.ch4
gins_pre_integ.cc
imu_preintegration.cc
g2o_types.cc
)
ADD_EXECUTABLE(test_preintegration test_preintegration.cc)
ADD_TEST(test_preintegration test_preintegration)
target_link_libraries(test_preintegration
gtest pthread glog gflags ${PROJECT_NAME}.ch4 ${PROJECT_NAME}.ch3 ${PROJECT_NAME}.common
)
add_executable(run_gins_pre_integ run_gins_pre_integ.cc)
target_link_libraries(run_gins_pre_integ
${PROJECT_NAME}.ch3
${PROJECT_NAME}.ch4
${g2o_libs}
)

View File

@@ -0,0 +1,138 @@
//
// Created by xiang on 23-1-19.
//
#include "ch4/g2o_types.h"
#include "common/g2o_types.h"
namespace sad {
EdgeInertial::EdgeInertial(std::shared_ptr<IMUPreintegration> preinteg, const Vec3d& gravity, double weight)
: preint_(preinteg), dt_(preinteg->dt_) {
resize(6); // 6个关联顶点
grav_ = gravity;
setInformation(preinteg->cov_.inverse() * weight);
}
void EdgeInertial::computeError() {
auto* p1 = dynamic_cast<const VertexPose*>(_vertices[0]);
auto* v1 = dynamic_cast<const VertexVelocity*>(_vertices[1]);
auto* bg1 = dynamic_cast<const VertexGyroBias*>(_vertices[2]);
auto* ba1 = dynamic_cast<const VertexAccBias*>(_vertices[3]);
auto* p2 = dynamic_cast<const VertexPose*>(_vertices[4]);
auto* v2 = dynamic_cast<const VertexVelocity*>(_vertices[5]);
Vec3d bg = bg1->estimate();
Vec3d ba = ba1->estimate();
const SO3 dR = preint_->GetDeltaRotation(bg);
const Vec3d dv = preint_->GetDeltaVelocity(bg, ba);
const Vec3d dp = preint_->GetDeltaPosition(bg, ba);
/// 预积分误差项4.41
const Vec3d er = (dR.inverse() * p1->estimate().so3().inverse() * p2->estimate().so3()).log();
Mat3d RiT = p1->estimate().so3().inverse().matrix();
const Vec3d ev = RiT * (v2->estimate() - v1->estimate() - grav_ * dt_) - dv;
const Vec3d ep = RiT * (p2->estimate().translation() - p1->estimate().translation() - v1->estimate() * dt_ -
grav_ * dt_ * dt_ / 2) -
dp;
_error << er, ev, ep;
}
void EdgeInertial::linearizeOplus() {
auto* p1 = dynamic_cast<const VertexPose*>(_vertices[0]);
auto* v1 = dynamic_cast<const VertexVelocity*>(_vertices[1]);
auto* bg1 = dynamic_cast<const VertexGyroBias*>(_vertices[2]);
auto* ba1 = dynamic_cast<const VertexAccBias*>(_vertices[3]);
auto* p2 = dynamic_cast<const VertexPose*>(_vertices[4]);
auto* v2 = dynamic_cast<const VertexVelocity*>(_vertices[5]);
Vec3d bg = bg1->estimate();
Vec3d ba = ba1->estimate();
Vec3d dbg = bg - preint_->bg_;
// 一些中间符号
const SO3 R1 = p1->estimate().so3();
const SO3 R1T = R1.inverse();
const SO3 R2 = p2->estimate().so3();
auto dR_dbg = preint_->dR_dbg_;
auto dv_dbg = preint_->dV_dbg_;
auto dp_dbg = preint_->dP_dbg_;
auto dv_dba = preint_->dV_dba_;
auto dp_dba = preint_->dP_dba_;
// 估计值
Vec3d vi = v1->estimate();
Vec3d vj = v2->estimate();
Vec3d pi = p1->estimate().translation();
Vec3d pj = p2->estimate().translation();
const SO3 dR = preint_->GetDeltaRotation(bg);
const SO3 eR = SO3(dR).inverse() * R1T * R2;
const Vec3d er = eR.log();
const Mat3d invJr = SO3::jr_inv(eR);
/// 雅可比矩阵
/// 注意有3个index, 顶点的,自己误差的,顶点内部变量的
/// 变量顺序pose1(R1,p1), v1, bg1, ba1, pose2(R2,p2), v2
/// 残差顺序eR, ev, ep残差顺序为行变量顺序为列
// | R1 | p1 | v1 | bg1 | ba1 | R2 | p2 | v2 |
// vert | 0 | 1 | 2 | 3 | 4 | 5 |
// col | 0 3 | 0 | 0 | 0 | 0 3 | 0 |
// row
// eR 0 |
// ev 3 |
// ep 6 |
/// 残差对R1, 9x3
_jacobianOplus[0].setZero();
// dR/dR1, 4.42
_jacobianOplus[0].block<3, 3>(0, 0) = -invJr * (R2.inverse() * R1).matrix();
// dv/dR1, 4.47
_jacobianOplus[0].block<3, 3>(3, 0) = SO3::hat(R1T * (vj - vi - grav_ * dt_));
// dp/dR1, 4.48d
_jacobianOplus[0].block<3, 3>(6, 0) = SO3::hat(R1T * (pj - pi - v1->estimate() * dt_ - 0.5 * grav_ * dt_ * dt_));
/// 残差对p1, 9x3
// dp/dp1, 4.48a
_jacobianOplus[0].block<3, 3>(6, 3) = -R1T.matrix();
/// 残差对v1, 9x3
_jacobianOplus[1].setZero();
// dv/dv1, 4.46a
_jacobianOplus[1].block<3, 3>(3, 0) = -R1T.matrix();
// dp/dv1, 4.48c
_jacobianOplus[1].block<3, 3>(6, 0) = -R1T.matrix() * dt_;
/// 残差对bg1
_jacobianOplus[2].setZero();
// dR/dbg1, 4.45
_jacobianOplus[2].block<3, 3>(0, 0) = -invJr * eR.inverse().matrix() * SO3::jr((dR_dbg * dbg).eval()) * dR_dbg;
// dv/dbg1
_jacobianOplus[2].block<3, 3>(3, 0) = -dv_dbg;
// dp/dbg1
_jacobianOplus[2].block<3, 3>(6, 0) = -dp_dbg;
/// 残差对ba1
_jacobianOplus[3].setZero();
// dv/dba1
_jacobianOplus[3].block<3, 3>(3, 0) = -dv_dba;
// dp/dba1
_jacobianOplus[3].block<3, 3>(6, 0) = -dp_dba;
/// 残差对pose2
_jacobianOplus[4].setZero();
// dr/dr2, 4.43
_jacobianOplus[4].block<3, 3>(0, 0) = invJr;
// dp/dp2, 4.48b
_jacobianOplus[4].block<3, 3>(6, 3) = R1T.matrix();
/// 残差对v2
_jacobianOplus[5].setZero();
// dv/dv2, 4,46b
_jacobianOplus[5].block<3, 3>(3, 0) = R1T.matrix(); // OK
}
} // namespace sad

View File

@@ -0,0 +1,64 @@
//
// Created by xiang on 23-1-19.
//
#ifndef SLAM_IN_AUTO_DRIVING_CH4_G2O_TYPES_H
#define SLAM_IN_AUTO_DRIVING_CH4_G2O_TYPES_H
#include <g2o/core/base_binary_edge.h>
#include <g2o/core/base_multi_edge.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/base_vertex.h>
#include <g2o/core/robust_kernel.h>
#include "ch4/imu_preintegration.h"
#include "common/eigen_types.h"
namespace sad {
/// 与预积分相关的vertex, edge
/**
* 预积分边
* 连接6个顶点上一帧的pose, v, bg, ba下一帧的pose, v
* 观测量为9维即预积分残差, 顺序R, v, p
* information从预积分类中获取构造函数中计算
*/
class EdgeInertial : public g2o::BaseMultiEdge<9, Vec9d> {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
/**
* 构造函数中需要指定预积分类对象
* @param preinteg 预积分对象指针
* @param gravity 重力矢量
* @param weight 权重
*/
EdgeInertial(std::shared_ptr<IMUPreintegration> preinteg, const Vec3d& gravity, double weight = 1.0);
bool read(std::istream& is) override { return false; }
bool write(std::ostream& os) const override { return false; }
void computeError() override;
void linearizeOplus() override;
Eigen::Matrix<double, 24, 24> GetHessian() {
linearizeOplus();
Eigen::Matrix<double, 9, 24> J;
J.block<9, 6>(0, 0) = _jacobianOplus[0];
J.block<9, 3>(0, 6) = _jacobianOplus[1];
J.block<9, 3>(0, 9) = _jacobianOplus[2];
J.block<9, 3>(0, 12) = _jacobianOplus[3];
J.block<9, 6>(0, 15) = _jacobianOplus[4];
J.block<9, 3>(0, 21) = _jacobianOplus[5];
return J.transpose() * information() * J;
}
private:
const double dt_;
std::shared_ptr<IMUPreintegration> preint_ = nullptr;
Vec3d grav_;
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_G2O_TYPES_H

View File

@@ -0,0 +1,266 @@
//
// Created by xiang on 2021/7/19.
//
#include "ch4/gins_pre_integ.h"
#include "ch4/g2o_types.h"
#include "common/g2o_types.h"
#include <glog/logging.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/core/robust_kernel.h>
#include <g2o/solvers/eigen/linear_solver_eigen.h>
namespace sad {
void GinsPreInteg::AddImu(const IMU& imu) {
if (first_gnss_received_ && first_imu_received_) {
pre_integ_->Integrate(imu, imu.timestamp_ - last_imu_.timestamp_);
}
first_imu_received_ = true;
last_imu_ = imu;
current_time_ = imu.timestamp_;
}
void GinsPreInteg::SetOptions(sad::GinsPreInteg::Options options) {
double bg_rw2 = 1.0 / (options_.bias_gyro_var_ * options_.bias_gyro_var_);
options_.bg_rw_info_.diagonal() << bg_rw2, bg_rw2, bg_rw2;
double ba_rw2 = 1.0 / (options_.bias_acce_var_ * options_.bias_acce_var_);
options_.ba_rw_info_.diagonal() << ba_rw2, ba_rw2, ba_rw2;
double gp2 = options_.gnss_pos_noise_ * options_.gnss_pos_noise_;
double gh2 = options_.gnss_height_noise_ * options_.gnss_height_noise_;
double ga2 = options_.gnss_ang_noise_ * options_.gnss_ang_noise_;
options_.gnss_info_.diagonal() << 1.0 / ga2, 1.0 / ga2, 1.0 / ga2, 1.0 / gp2, 1.0 / gp2, 1.0 / gh2;
pre_integ_ = std::make_shared<IMUPreintegration>(options_.preinteg_options_);
double o2 = 1.0 / (options_.odom_var_ * options_.odom_var_);
options_.odom_info_.diagonal() << o2, o2, o2;
prior_info_.block<6, 6>(9, 9) = Mat6d ::Identity() * 1e6;
if (this_frame_) {
this_frame_->bg_ = options_.preinteg_options_.init_bg_;
this_frame_->ba_ = options_.preinteg_options_.init_ba_;
}
}
void GinsPreInteg::AddGnss(const GNSS& gnss) {
this_frame_ = std::make_shared<NavStated>(current_time_);
this_gnss_ = gnss;
if (!first_gnss_received_) {
if (!gnss.heading_valid_) {
// 要求首个GNSS必须有航向
return;
}
// 首个gnss信号将初始pose设置为该gnss信号
this_frame_->timestamp_ = gnss.unix_time_;
this_frame_->p_ = gnss.utm_pose_.translation();
this_frame_->R_ = gnss.utm_pose_.so3();
this_frame_->v_.setZero();
this_frame_->bg_ = options_.preinteg_options_.init_bg_;
this_frame_->ba_ = options_.preinteg_options_.init_ba_;
pre_integ_ = std::make_shared<IMUPreintegration>(options_.preinteg_options_);
last_frame_ = this_frame_;
last_gnss_ = this_gnss_;
first_gnss_received_ = true;
current_time_ = gnss.unix_time_;
return;
}
// 积分到GNSS时刻
pre_integ_->Integrate(last_imu_, gnss.unix_time_ - current_time_);
current_time_ = gnss.unix_time_;
*this_frame_ = pre_integ_->Predict(*last_frame_, options_.gravity_);
Optimize();
last_frame_ = this_frame_;
last_gnss_ = this_gnss_;
}
void GinsPreInteg::AddOdom(const sad::Odom& odom) {
last_odom_ = odom;
last_odom_set_ = true;
}
void GinsPreInteg::Optimize() {
if (pre_integ_->dt_ < 1e-3) {
// 未得到积分
return;
}
using BlockSolverType = g2o::BlockSolverX;
using LinearSolverType = g2o::LinearSolverEigen<BlockSolverType::PoseMatrixType>;
auto* solver = new g2o::OptimizationAlgorithmLevenberg(
g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));
g2o::SparseOptimizer optimizer;
optimizer.setAlgorithm(solver);
// 上时刻顶点, pose, v, bg, ba
auto v0_pose = new VertexPose();
v0_pose->setId(0);
v0_pose->setEstimate(last_frame_->GetSE3());
optimizer.addVertex(v0_pose);
auto v0_vel = new VertexVelocity();
v0_vel->setId(1);
v0_vel->setEstimate(last_frame_->v_);
optimizer.addVertex(v0_vel);
auto v0_bg = new VertexGyroBias();
v0_bg->setId(2);
v0_bg->setEstimate(last_frame_->bg_);
optimizer.addVertex(v0_bg);
auto v0_ba = new VertexAccBias();
v0_ba->setId(3);
v0_ba->setEstimate(last_frame_->ba_);
optimizer.addVertex(v0_ba);
// 本时刻顶点pose, v, bg, ba
auto v1_pose = new VertexPose();
v1_pose->setId(4);
v1_pose->setEstimate(this_frame_->GetSE3());
optimizer.addVertex(v1_pose);
auto v1_vel = new VertexVelocity();
v1_vel->setId(5);
v1_vel->setEstimate(this_frame_->v_);
optimizer.addVertex(v1_vel);
auto v1_bg = new VertexGyroBias();
v1_bg->setId(6);
v1_bg->setEstimate(this_frame_->bg_);
optimizer.addVertex(v1_bg);
auto v1_ba = new VertexAccBias();
v1_ba->setId(7);
v1_ba->setEstimate(this_frame_->ba_);
optimizer.addVertex(v1_ba);
// 预积分边
auto edge_inertial = new EdgeInertial(pre_integ_, options_.gravity_);
edge_inertial->setVertex(0, v0_pose);
edge_inertial->setVertex(1, v0_vel);
edge_inertial->setVertex(2, v0_bg);
edge_inertial->setVertex(3, v0_ba);
edge_inertial->setVertex(4, v1_pose);
edge_inertial->setVertex(5, v1_vel);
auto* rk = new g2o::RobustKernelHuber();
rk->setDelta(200.0);
edge_inertial->setRobustKernel(rk);
optimizer.addEdge(edge_inertial);
// 两个零偏随机游走
auto* edge_gyro_rw = new EdgeGyroRW();
edge_gyro_rw->setVertex(0, v0_bg);
edge_gyro_rw->setVertex(1, v1_bg);
edge_gyro_rw->setInformation(options_.bg_rw_info_);
optimizer.addEdge(edge_gyro_rw);
auto* edge_acc_rw = new EdgeAccRW();
edge_acc_rw->setVertex(0, v0_ba);
edge_acc_rw->setVertex(1, v1_ba);
edge_acc_rw->setInformation(options_.ba_rw_info_);
optimizer.addEdge(edge_acc_rw);
// 上时刻先验
auto* edge_prior = new EdgePriorPoseNavState(*last_frame_, prior_info_);
edge_prior->setVertex(0, v0_pose);
edge_prior->setVertex(1, v0_vel);
edge_prior->setVertex(2, v0_bg);
edge_prior->setVertex(3, v0_ba);
optimizer.addEdge(edge_prior);
// GNSS边
auto edge_gnss0 = new EdgeGNSS(v0_pose, last_gnss_.utm_pose_);
edge_gnss0->setInformation(options_.gnss_info_);
optimizer.addEdge(edge_gnss0);
auto edge_gnss1 = new EdgeGNSS(v1_pose, this_gnss_.utm_pose_);
edge_gnss1->setInformation(options_.gnss_info_);
optimizer.addEdge(edge_gnss1);
// Odom边
EdgeEncoder3D* edge_odom = nullptr;
Vec3d vel_world = Vec3d::Zero();
Vec3d vel_odom = Vec3d::Zero();
if (last_odom_set_) {
// velocity obs
double velo_l =
options_.wheel_radius_ * last_odom_.left_pulse_ / options_.circle_pulse_ * 2 * M_PI / options_.odom_span_;
double velo_r =
options_.wheel_radius_ * last_odom_.right_pulse_ / options_.circle_pulse_ * 2 * M_PI / options_.odom_span_;
double average_vel = 0.5 * (velo_l + velo_r);
vel_odom = Vec3d(average_vel, 0.0, 0.0);
vel_world = this_frame_->R_ * vel_odom;
edge_odom = new EdgeEncoder3D(v1_vel, vel_world);
edge_odom->setInformation(options_.odom_info_);
optimizer.addEdge(edge_odom);
// 重置odom数据到达标志位等待最新的odom数据
last_odom_set_ = false;
}
optimizer.setVerbose(options_.verbose_);
optimizer.initializeOptimization();
optimizer.optimize(20);
if (options_.verbose_) {
// 获取结果,统计各类误差
LOG(INFO) << "chi2/error: ";
LOG(INFO) << "preintegration: " << edge_inertial->chi2() << "/" << edge_inertial->error().transpose();
// LOG(INFO) << "gnss0: " << edge_gnss0->chi2() << ", " << edge_gnss0->error().transpose();
LOG(INFO) << "gnss1: " << edge_gnss1->chi2() << ", " << edge_gnss1->error().transpose();
LOG(INFO) << "bias: " << edge_gyro_rw->chi2() << "/" << edge_acc_rw->error().transpose();
LOG(INFO) << "prior: " << edge_prior->chi2() << "/" << edge_prior->error().transpose();
if (edge_odom) {
LOG(INFO) << "body vel: " << (v1_pose->estimate().so3().inverse() * v1_vel->estimate()).transpose();
LOG(INFO) << "meas: " << vel_odom.transpose();
LOG(INFO) << "odom: " << edge_odom->chi2() << "/" << edge_odom->error().transpose();
}
}
last_frame_->R_ = v0_pose->estimate().so3();
last_frame_->p_ = v0_pose->estimate().translation();
last_frame_->v_ = v0_vel->estimate();
last_frame_->bg_ = v0_bg->estimate();
last_frame_->ba_ = v0_ba->estimate();
this_frame_->R_ = v1_pose->estimate().so3();
this_frame_->p_ = v1_pose->estimate().translation();
this_frame_->v_ = v1_vel->estimate();
this_frame_->bg_ = v1_bg->estimate();
this_frame_->ba_ = v1_ba->estimate();
// 重置integ
options_.preinteg_options_.init_bg_ = this_frame_->bg_;
options_.preinteg_options_.init_ba_ = this_frame_->ba_;
pre_integ_ = std::make_shared<IMUPreintegration>(options_.preinteg_options_);
}
NavStated GinsPreInteg::GetState() const {
if (this_frame_ == nullptr) {
return {};
}
if (pre_integ_ == nullptr) {
return *this_frame_;
}
return pre_integ_->Predict(*this_frame_, options_.gravity_);
}
} // namespace sad

View File

@@ -0,0 +1,119 @@
//
// Created by xiang on 2021/7/19.
//
#ifndef MAPPING_DR_PRE_INTEG_H
#define MAPPING_DR_PRE_INTEG_H
#include <deque>
#include <fstream>
#include <memory>
#include "common/eigen_types.h"
#include "common/gnss.h"
#include "common/imu.h"
#include "common/math_utils.h"
#include "common/odom.h"
#include "ch4/imu_preintegration.h"
namespace sad {
/**
* 使用预积分优化的GINS
*
* 本章仍使用两帧模型不会去做全量优化尽管也可以做两帧模型和ESKF更像一些
* IMU到达时累计到预积分器中
* 每次RTK到达时触发一次优化并做边缘化。边缘化结果写入下一帧先验中。
* 里程计方面,使用最近时间的里程计信息作为速度观测量。
*/
class GinsPreInteg {
public:
/// GINS 配置项
struct Options {
Options() {}
Vec3d gravity_ = Vec3d(0, 0, -9.8); // 重力方向
/// IMU相关噪声参数在preinteg内部配置
IMUPreintegration::Options preinteg_options_;
// 噪声
double bias_gyro_var_ = 1e-6; // 陀螺零偏游走标准差
double bias_acce_var_ = 1e-4; // 加计零偏游走标准差
Mat3d bg_rw_info_ = Mat3d::Identity(); // 陀螺随机游走信息阵
Mat3d ba_rw_info_ = Mat3d::Identity(); // 加计随机游走信息阵
double gnss_pos_noise_ = 0.1; // GNSS位置方差
double gnss_height_noise_ = 0.1; // GNSS高度方差
double gnss_ang_noise_ = 1.0 * math::kDEG2RAD; // GNSS角度方差
Mat6d gnss_info_ = Mat6d::Identity(); // 6D gnss信息矩阵
/// 轮速计相关
double odom_var_ = 0.05;
Mat3d odom_info_ = Mat3d::Identity();
double odom_span_ = 0.1; // 里程计测量间隔
double wheel_radius_ = 0.155; // 轮子半径
double circle_pulse_ = 1024.0; // 编码器每圈脉冲数
bool verbose_ = true; // 是否输出调试信息
};
/// Option 可以在构造时设置,也可以在后续设置
GinsPreInteg(Options options = Options()) : options_(options) { SetOptions(options_); }
/**
* IMU 处理函数,要求初始零偏已经设置过,再调用此函数
* @param imu imu读数
*/
void AddImu(const IMU& imu);
/**
* GNSS 处理函数
* @param gnss
*/
void AddGnss(const GNSS& gnss);
/**
* 轮速计处理函数
* @param odom
*/
void AddOdom(const Odom& odom);
/// 设置gins的各种配置项可以在构建时调用也可以构造完成后静止初始化结束时调用
void SetOptions(Options options);
/**
* 获取当前状态
* 如果IMU没有积分就返回最后优化的状态
* 如果有利用IMU积分预测自己的状态
* @return
*/
NavStated GetState() const;
private:
// 优化
void Optimize();
Options options_;
double current_time_ = 0.0; // 当前时间
std::shared_ptr<IMUPreintegration> pre_integ_ = nullptr;
std::shared_ptr<NavStated> last_frame_ = nullptr; // 上一个时刻状态
std::shared_ptr<NavStated> this_frame_ = nullptr; // 当前时刻状态
Mat15d prior_info_ = Mat15d::Identity() * 1e2; // 当前时刻先验
/// 两帧GNSS观测
GNSS last_gnss_;
GNSS this_gnss_;
IMU last_imu_; // 上时刻IMU
Odom last_odom_; // 上时刻odom
bool last_odom_set_ = false;
/// 标志位
bool first_gnss_received_ = false; // 是否已收到第一个gnss信号
bool first_imu_received_ = false; // 是否已收到第一个imu信号
};
} // namespace sad
#endif // MAPPING_DR_PRE_INTEG_H

View File

@@ -0,0 +1,89 @@
#include "imu_preintegration.h"
#include <glog/logging.h>
namespace sad {
IMUPreintegration::IMUPreintegration(Options options) {
bg_ = options.init_bg_;
ba_ = options.init_ba_;
const float ng2 = options.noise_gyro_ * options.noise_gyro_;
const float na2 = options.noise_acce_ * options.noise_acce_;
noise_gyro_acce_.diagonal() << ng2, ng2, ng2, na2, na2, na2;
}
void IMUPreintegration::Integrate(const IMU &imu, double dt) {
// 去掉零偏的测量
Vec3d gyr = imu.gyro_ - bg_; // 陀螺
Vec3d acc = imu.acce_ - ba_; // 加计
// 更新dv, dp, 见(4.13), (4.16)
dp_ = dp_ + dv_ * dt + 0.5f * dR_.matrix() * acc * dt * dt;
dv_ = dv_ + dR_ * acc * dt;
// dR先不更新因为A, B阵还需要现在的dR
// 运动方程雅可比矩阵系数A,B阵见(4.29)
// 另外两项在后面
Eigen::Matrix<double, 9, 9> A;
A.setIdentity();
Eigen::Matrix<double, 9, 6> B;
B.setZero();
Mat3d acc_hat = SO3::hat(acc);
double dt2 = dt * dt;
// NOTE A, B左上角块与公式稍有不同
A.block<3, 3>(3, 0) = -dR_.matrix() * dt * acc_hat;
A.block<3, 3>(6, 0) = -0.5f * dR_.matrix() * acc_hat * dt2;
A.block<3, 3>(6, 3) = dt * Mat3d::Identity();
B.block<3, 3>(3, 3) = dR_.matrix() * dt;
B.block<3, 3>(6, 3) = 0.5f * dR_.matrix() * dt2;
// 更新各雅可比,见式(4.39)
dP_dba_ = dP_dba_ + dV_dba_ * dt - 0.5f * dR_.matrix() * dt2; // (4.39d)
dP_dbg_ = dP_dbg_ + dV_dbg_ * dt - 0.5f * dR_.matrix() * dt2 * acc_hat * dR_dbg_; // (4.39e)
dV_dba_ = dV_dba_ - dR_.matrix() * dt; // (4.39b)
dV_dbg_ = dV_dbg_ - dR_.matrix() * dt * acc_hat * dR_dbg_; // (4.39c)
// 旋转部分
Vec3d omega = gyr * dt; // 转动量
Mat3d rightJ = SO3::jr(omega); // 右雅可比
SO3 deltaR = SO3::exp(omega); // exp后
dR_ = dR_ * deltaR; // (4.9)
A.block<3, 3>(0, 0) = deltaR.matrix().transpose();
B.block<3, 3>(0, 0) = rightJ * dt;
// 更新噪声项
cov_ = A * cov_ * A.transpose() + B * noise_gyro_acce_ * B.transpose();
// 更新dR_dbg
dR_dbg_ = deltaR.matrix().transpose() * dR_dbg_ - rightJ * dt; // (4.39a)
// 增量积分时间
dt_ += dt;
}
SO3 IMUPreintegration::GetDeltaRotation(const Vec3d &bg) { return dR_ * SO3::exp(dR_dbg_ * (bg - bg_)); }
Vec3d IMUPreintegration::GetDeltaVelocity(const Vec3d &bg, const Vec3d &ba) {
return dv_ + dV_dbg_ * (bg - bg_) + dV_dba_ * (ba - ba_);
}
Vec3d IMUPreintegration::GetDeltaPosition(const Vec3d &bg, const Vec3d &ba) {
return dp_ + dP_dbg_ * (bg - bg_) + dP_dba_ * (ba - ba_);
}
NavStated IMUPreintegration::Predict(const sad::NavStated &start, const Vec3d &grav) const {
SO3 Rj = start.R_ * dR_;
Vec3d vj = start.R_ * dv_ + start.v_ + grav * dt_;
Vec3d pj = start.R_ * dp_ + start.p_ + start.v_ * dt_ + 0.5f * grav * dt_ * dt_;
auto state = NavStated(start.timestamp_ + dt_, Rj, pj, vj);
state.bg_ = bg_;
state.ba_ = ba_;
return state;
}
} // namespace sad

View File

@@ -0,0 +1,79 @@
#ifndef IMUTYPES_H
#define IMUTYPES_H
#include <mutex>
#include <opencv2/core/core.hpp>
#include <utility>
#include <vector>
#include "common/eigen_types.h"
#include "common/imu.h"
#include "common/nav_state.h"
namespace sad {
/**
* IMU 预积分器
*
* 调用Integrate来插入新的IMU读数然后通过Get函数得到预积分的值
* 雅可比也可以通过本类获得可用于构建g2o的边类
*/
class IMUPreintegration {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
/// 参数配置项
/// 初始的零偏需要设置,其他可以不改
struct Options {
Options() {}
Vec3d init_bg_ = Vec3d::Zero(); // 初始零偏
Vec3d init_ba_ = Vec3d::Zero(); // 初始零偏
double noise_gyro_ = 1e-2; // 陀螺噪声,标准差
double noise_acce_ = 1e-1; // 加计噪声,标准差
};
IMUPreintegration(Options options = Options());
/**
* 插入新的IMU数据
* @param imu imu 读数
* @param dt 时间差
*/
void Integrate(const IMU &imu, double dt);
/**
* 从某个起始点开始预测积分之后的状态
* @param start 起始时时刻状态
* @return 预测的状态
*/
NavStated Predict(const NavStated &start, const Vec3d &grav = Vec3d(0, 0, -9.81)) const;
/// 获取修正之后的观测量bias可以与预积分时期的不同会有一阶修正
SO3 GetDeltaRotation(const Vec3d &bg);
Vec3d GetDeltaVelocity(const Vec3d &bg, const Vec3d &ba);
Vec3d GetDeltaPosition(const Vec3d &bg, const Vec3d &ba);
public:
double dt_ = 0; // 整体预积分时间
Mat9d cov_ = Mat9d::Zero(); // 累计噪声矩阵
Mat6d noise_gyro_acce_ = Mat6d::Zero(); // 测量噪声矩阵
// 零偏
Vec3d bg_ = Vec3d::Zero();
Vec3d ba_ = Vec3d::Zero();
// 预积分观测量
SO3 dR_;
Vec3d dv_ = Vec3d::Zero();
Vec3d dp_ = Vec3d::Zero();
// 雅可比矩阵
Mat3d dR_dbg_ = Mat3d::Zero();
Mat3d dV_dbg_ = Mat3d::Zero();
Mat3d dV_dba_ = Mat3d::Zero();
Mat3d dP_dbg_ = Mat3d::Zero();
Mat3d dP_dba_ = Mat3d::Zero();
};
} // namespace sad
#endif // IMUTYPES_H

View File

@@ -0,0 +1,152 @@
//
// Created by xiang on 2022/1/21.
//
#include "ch3/static_imu_init.h"
#include "ch3/utm_convert.h"
#include "ch4/gins_pre_integ.h"
#include "common/io_utils.h"
#include "tools/ui/pangolin_window.h"
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <fstream>
#include <iomanip>
/**
* 运行由预积分构成的GINS系统
*/
DEFINE_string(txt_path, "./data/ch3/10.txt", "数据文件路径");
DEFINE_double(antenna_angle, 12.06, "RTK天线安装偏角角度");
DEFINE_double(antenna_pox_x, -0.17, "RTK天线安装偏移X");
DEFINE_double(antenna_pox_y, -0.20, "RTK天线安装偏移Y");
DEFINE_bool(with_ui, true, "是否显示图形界面");
DEFINE_bool(debug, false, "是否打印调试信息");
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
if (fLS::FLAGS_txt_path.empty()) {
return -1;
}
// 初始化器
sad::StaticIMUInit imu_init; // 使用默认配置
sad::TxtIO io(fLS::FLAGS_txt_path);
Vec2d antenna_pos(fLD::FLAGS_antenna_pox_x, fLD::FLAGS_antenna_pox_y);
auto save_vec3 = [](std::ofstream& fout, const Vec3d& v) { fout << v[0] << " " << v[1] << " " << v[2] << " "; };
auto save_quat = [](std::ofstream& fout, const Quatd& q) {
fout << q.w() << " " << q.x() << " " << q.y() << " " << q.z() << " ";
};
auto save_result = [&save_vec3, &save_quat](std::ofstream& fout, const sad::NavStated& save_state) {
fout << std::setprecision(18) << save_state.timestamp_ << " " << std::setprecision(9);
save_vec3(fout, save_state.p_);
save_quat(fout, save_state.R_.unit_quaternion());
save_vec3(fout, save_state.v_);
save_vec3(fout, save_state.bg_);
save_vec3(fout, save_state.ba_);
fout << std::endl;
};
std::ofstream fout("./data/ch4/gins_preintg.txt");
bool imu_inited = false, gnss_inited = false;
sad::GinsPreInteg::Options gins_options;
gins_options.verbose_ = FLAGS_debug;
sad::GinsPreInteg gins(gins_options);
bool first_gnss_set = false;
Vec3d origin = Vec3d::Zero();
std::shared_ptr<sad::ui::PangolinWindow> ui = nullptr;
if (FLAGS_with_ui) {
ui = std::make_shared<sad::ui::PangolinWindow>();
ui->Init();
}
/// 设置各类回调函数
io.SetIMUProcessFunc([&](const sad::IMU& imu) {
/// IMU 处理函数
if (!imu_init.InitSuccess()) {
imu_init.AddIMU(imu);
return;
}
/// 需要IMU初始化
if (!imu_inited) {
// 读取初始零偏设置GINS
sad::GinsPreInteg::Options options;
options.preinteg_options_.init_bg_ = imu_init.GetInitBg();
options.preinteg_options_.init_ba_ = imu_init.GetInitBa();
options.gravity_ = imu_init.GetGravity();
gins.SetOptions(options);
imu_inited = true;
return;
}
if (!gnss_inited) {
/// 等待有效的RTK数据
return;
}
/// GNSS 也接收到之后,再开始进行预测
gins.AddImu(imu);
auto state = gins.GetState();
save_result(fout, state);
if (ui) {
ui->UpdateNavState(state);
usleep(5e2);
}
})
.SetGNSSProcessFunc([&](const sad::GNSS& gnss) {
/// GNSS 处理函数
if (!imu_inited) {
return;
}
sad::GNSS gnss_convert = gnss;
if (!sad::ConvertGps2UTM(gnss_convert, antenna_pos, FLAGS_antenna_angle) || !gnss_convert.heading_valid_) {
return;
}
/// 去掉原点
if (!first_gnss_set) {
origin = gnss_convert.utm_pose_.translation();
first_gnss_set = true;
}
gnss_convert.utm_pose_.translation() -= origin;
gins.AddGnss(gnss_convert);
auto state = gins.GetState();
save_result(fout, state);
if (ui) {
ui->UpdateNavState(state);
usleep(1e3);
}
gnss_inited = true;
})
.SetOdomProcessFunc([&](const sad::Odom& odom) {
imu_init.AddOdom(odom);
if (imu_inited && gnss_inited) {
gins.AddOdom(odom);
}
})
.Go();
while (ui && !ui->ShouldQuit()) {
usleep(1e5);
}
if (ui) {
ui->Quit();
}
return 0;
}

View File

@@ -0,0 +1,397 @@
//
// Created by xiang on 2021/7/16.
//
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "ch3/eskf.hpp"
#include "ch3/static_imu_init.h"
#include "ch4/imu_preintegration.h"
#include "ch4/g2o_types.h"
#include "common/g2o_types.h"
#include "common/io_utils.h"
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_gauss_newton.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/core/robust_kernel.h>
#include <g2o/solvers/eigen/linear_solver_eigen.h>
DEFINE_string(txt_path, "./data/ch3/10.txt", "数据文件路径");
DEFINE_double(antenna_angle, 12.06, "RTK天线安装偏角角度");
DEFINE_double(antenna_pox_x, -0.17, "RTK天线安装偏移X");
DEFINE_double(antenna_pox_y, -0.20, "RTK天线安装偏移Y");
DEFINE_bool(with_ui, true, "是否显示图形界面");
TEST(PREINTEGRATION_TEST, ROTATION_TEST) {
// 测试在恒定角速度运转下的预积分情况
double imu_time_span = 0.01; // IMU测量间隔
Vec3d constant_omega(0, 0, M_PI); // 角速度为180度/s转1秒应该等于转180度
Vec3d gravity(0, 0, -9.8); // Z 向上,重力方向为负
sad::NavStated start_status(0), end_status(1.0);
sad::IMUPreintegration pre_integ;
// 对比直接积分
Sophus::SO3d R;
Vec3d t = Vec3d::Zero();
Vec3d v = Vec3d::Zero();
for (int i = 1; i <= 100; ++i) {
double time = imu_time_span * i;
Vec3d acce = -gravity; // 加速度计应该测量到一个向上的力
pre_integ.Integrate(sad::IMU(time, constant_omega, acce), imu_time_span);
sad::NavStated this_status = pre_integ.Predict(start_status, gravity);
t = t + v * imu_time_span + 0.5 * gravity * imu_time_span * imu_time_span +
0.5 * (R * acce) * imu_time_span * imu_time_span;
v = v + gravity * imu_time_span + (R * acce) * imu_time_span;
R = R * Sophus::SO3d::exp(constant_omega * imu_time_span);
// 验证在简单情况下,直接积分和预积分结果相等
EXPECT_NEAR(t[0], this_status.p_[0], 1e-2);
EXPECT_NEAR(t[1], this_status.p_[1], 1e-2);
EXPECT_NEAR(t[2], this_status.p_[2], 1e-2);
EXPECT_NEAR(v[0], this_status.v_[0], 1e-2);
EXPECT_NEAR(v[1], this_status.v_[1], 1e-2);
EXPECT_NEAR(v[2], this_status.v_[2], 1e-2);
EXPECT_NEAR(R.unit_quaternion().x(), this_status.R_.unit_quaternion().x(), 1e-4);
EXPECT_NEAR(R.unit_quaternion().y(), this_status.R_.unit_quaternion().y(), 1e-4);
EXPECT_NEAR(R.unit_quaternion().z(), this_status.R_.unit_quaternion().z(), 1e-4);
EXPECT_NEAR(R.unit_quaternion().w(), this_status.R_.unit_quaternion().w(), 1e-4);
}
end_status = pre_integ.Predict(start_status);
LOG(INFO) << "preinteg result: ";
LOG(INFO) << "end rotation: \n" << end_status.R_.matrix();
LOG(INFO) << "end trans: \n" << end_status.p_.transpose();
LOG(INFO) << "end v: \n" << end_status.v_.transpose();
LOG(INFO) << "direct integ result: ";
LOG(INFO) << "end rotation: \n" << R.matrix();
LOG(INFO) << "end trans: \n" << t.transpose();
LOG(INFO) << "end v: \n" << v.transpose();
SUCCEED();
}
TEST(PREINTEGRATION_TEST, ACCELERATION_TEST) {
// 测试在恒定加速度运行下的预积分情况
double imu_time_span = 0.01; // IMU测量间隔
Vec3d gravity(0, 0, -9.8); // Z 向上,重力方向为负
Vec3d constant_acce(0.1, 0, 0); // x 方向上的恒定加速度
sad::NavStated start_status(0), end_status(1.0);
sad::IMUPreintegration pre_integ;
// 对比直接积分
Sophus::SO3d R;
Vec3d t = Vec3d::Zero();
Vec3d v = Vec3d::Zero();
for (int i = 1; i <= 100; ++i) {
double time = imu_time_span * i;
Vec3d acce = constant_acce - gravity;
pre_integ.Integrate(sad::IMU(time, Vec3d::Zero(), acce), imu_time_span);
sad::NavStated this_status = pre_integ.Predict(start_status, gravity);
t = t + v * imu_time_span + 0.5 * gravity * imu_time_span * imu_time_span +
0.5 * (R * acce) * imu_time_span * imu_time_span;
v = v + gravity * imu_time_span + (R * acce) * imu_time_span;
// 验证在简单情况下,直接积分和预积分结果相等
EXPECT_NEAR(t[0], this_status.p_[0], 1e-2);
EXPECT_NEAR(t[1], this_status.p_[1], 1e-2);
EXPECT_NEAR(t[2], this_status.p_[2], 1e-2);
EXPECT_NEAR(v[0], this_status.v_[0], 1e-2);
EXPECT_NEAR(v[1], this_status.v_[1], 1e-2);
EXPECT_NEAR(v[2], this_status.v_[2], 1e-2);
EXPECT_NEAR(R.unit_quaternion().x(), this_status.R_.unit_quaternion().x(), 1e-4);
EXPECT_NEAR(R.unit_quaternion().y(), this_status.R_.unit_quaternion().y(), 1e-4);
EXPECT_NEAR(R.unit_quaternion().z(), this_status.R_.unit_quaternion().z(), 1e-4);
EXPECT_NEAR(R.unit_quaternion().w(), this_status.R_.unit_quaternion().w(), 1e-4);
}
end_status = pre_integ.Predict(start_status);
LOG(INFO) << "preinteg result: ";
LOG(INFO) << "end rotation: \n" << end_status.R_.matrix();
LOG(INFO) << "end trans: \n" << end_status.p_.transpose();
LOG(INFO) << "end v: \n" << end_status.v_.transpose();
LOG(INFO) << "direct integ result: ";
LOG(INFO) << "end rotation: \n" << R.matrix();
LOG(INFO) << "end trans: \n" << t.transpose();
LOG(INFO) << "end v: \n" << v.transpose();
SUCCEED();
}
void Optimize(sad::NavStated& last_state, sad::NavStated& this_state, sad::GNSS& last_gnss, sad::GNSS& this_gnss,
std::shared_ptr<sad::IMUPreintegration>& preinteg, const Vec3d& grav);
/// 使用ESKF的Predict, Update来验证预积分的优化过程
TEST(PREINTEGRATION_TEST, ESKF_TEST) {
if (fLS::FLAGS_txt_path.empty()) {
FAIL();
}
// 初始化器
sad::StaticIMUInit imu_init; // 使用默认配置
sad::ESKFD eskf;
sad::TxtIO io(FLAGS_txt_path);
Vec2d antenna_pos(FLAGS_antenna_pox_x, FLAGS_antenna_pox_y);
std::ofstream fout("./data/ch3/gins.txt");
bool imu_inited = false, gnss_inited = false;
/// 设置各类回调函数
bool first_gnss_set = false;
Vec3d origin = Vec3d::Zero();
std::shared_ptr<sad::IMUPreintegration> preinteg = nullptr;
sad::NavStated last_state;
bool last_state_set = false;
sad::GNSS last_gnss;
bool last_gnss_set = false;
io.SetIMUProcessFunc([&](const sad::IMU& imu) {
/// IMU 处理函数
if (!imu_init.InitSuccess()) {
imu_init.AddIMU(imu);
return;
}
/// 需要IMU初始化
if (!imu_inited) {
// 读取初始零偏设置ESKF
sad::ESKFD::Options options;
// 噪声由初始化器估计
options.gyro_var_ = sqrt(imu_init.GetCovGyro()[0]);
options.acce_var_ = sqrt(imu_init.GetCovAcce()[0]);
eskf.SetInitialConditions(options, imu_init.GetInitBg(), imu_init.GetInitBa(), imu_init.GetGravity());
imu_inited = true;
return;
}
if (!gnss_inited) {
/// 等待有效的RTK数据
return;
}
/// GNSS 也接收到之后,再开始进行预测
double current_time = eskf.GetNominalState().timestamp_;
eskf.Predict(imu);
if (preinteg) {
preinteg->Integrate(imu, imu.timestamp_ - current_time);
if (last_state_set) {
auto pred_of_preinteg = preinteg->Predict(last_state, eskf.GetGravity());
auto pred_of_eskf = eskf.GetNominalState();
/// 这两个预测值的误差应该非常接近
EXPECT_NEAR((pred_of_preinteg.p_ - pred_of_eskf.p_).norm(), 0, 1e-2);
EXPECT_NEAR((pred_of_preinteg.R_.inverse() * pred_of_eskf.R_).log().norm(), 0, 1e-2);
EXPECT_NEAR((pred_of_preinteg.v_ - pred_of_eskf.v_).norm(), 0, 1e-2);
}
}
})
.SetGNSSProcessFunc([&](const sad::GNSS& gnss) {
/// GNSS 处理函数
if (!imu_inited) {
return;
}
sad::GNSS gnss_convert = gnss;
if (!sad::ConvertGps2UTM(gnss_convert, antenna_pos, FLAGS_antenna_angle) || !gnss_convert.heading_valid_) {
return;
}
/// 去掉原点
if (!first_gnss_set) {
origin = gnss_convert.utm_pose_.translation();
first_gnss_set = true;
}
gnss_convert.utm_pose_.translation() -= origin;
// 要求RTK heading有效才能合入EKF
auto state_bef_update = eskf.GetNominalState();
eskf.ObserveGps(gnss_convert);
// 验证优化过程是否正确
if (last_state_set && last_gnss_set) {
auto update_state = eskf.GetNominalState();
LOG(INFO) << "state before eskf update: " << state_bef_update;
LOG(INFO) << "state after eskf update: " << update_state;
auto state_pred = preinteg->Predict(last_state, eskf.GetGravity());
LOG(INFO) << "state in pred: " << state_pred;
Optimize(last_state, update_state, last_gnss, gnss_convert, preinteg, eskf.GetGravity());
}
last_state = eskf.GetNominalState();
last_state_set = true;
// 重置预积分
sad::IMUPreintegration::Options options;
options.init_bg_ = last_state.bg_;
options.init_ba_ = last_state.ba_;
preinteg = std::make_shared<sad::IMUPreintegration>(options);
gnss_inited = true;
last_gnss = gnss_convert;
last_gnss_set = true;
})
.SetOdomProcessFunc([&](const sad::Odom& odom) { imu_init.AddOdom(odom); })
.Go();
SUCCEED();
}
void Optimize(sad::NavStated& last_state, sad::NavStated& this_state, sad::GNSS& last_gnss, sad::GNSS& this_gnss,
std::shared_ptr<sad::IMUPreintegration>& pre_integ, const Vec3d& grav) {
assert(pre_integ != nullptr);
if (pre_integ->dt_ < 1e-3) {
// 未得到积分
return;
}
using BlockSolverType = g2o::BlockSolverX;
using LinearSolverType = g2o::LinearSolverEigen<BlockSolverType::PoseMatrixType>;
auto* solver = new g2o::OptimizationAlgorithmGaussNewton(
g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));
g2o::SparseOptimizer optimizer;
optimizer.setAlgorithm(solver);
// 上时刻顶点, pose, v, bg, ba
auto v0_pose = new sad::VertexPose();
v0_pose->setId(0);
v0_pose->setEstimate(last_state.GetSE3());
optimizer.addVertex(v0_pose);
auto v0_vel = new sad::VertexVelocity();
v0_vel->setId(1);
v0_vel->setEstimate(last_state.v_);
optimizer.addVertex(v0_vel);
auto v0_bg = new sad::VertexGyroBias();
v0_bg->setId(2);
v0_bg->setEstimate(last_state.bg_);
optimizer.addVertex(v0_bg);
auto v0_ba = new sad::VertexAccBias();
v0_ba->setId(3);
v0_ba->setEstimate(last_state.ba_);
optimizer.addVertex(v0_ba);
// 本时刻顶点pose, v, bg, ba
auto v1_pose = new sad::VertexPose();
v1_pose->setId(4);
v1_pose->setEstimate(this_state.GetSE3());
optimizer.addVertex(v1_pose);
auto v1_vel = new sad::VertexVelocity();
v1_vel->setId(5);
v1_vel->setEstimate(this_state.v_);
optimizer.addVertex(v1_vel);
auto v1_bg = new sad::VertexGyroBias();
v1_bg->setId(6);
v1_bg->setEstimate(this_state.bg_);
optimizer.addVertex(v1_bg);
auto v1_ba = new sad::VertexAccBias();
v1_ba->setId(7);
v1_ba->setEstimate(this_state.ba_);
optimizer.addVertex(v1_ba);
// 预积分边
auto edge_inertial = new sad::EdgeInertial(pre_integ, grav);
edge_inertial->setVertex(0, v0_pose);
edge_inertial->setVertex(1, v0_vel);
edge_inertial->setVertex(2, v0_bg);
edge_inertial->setVertex(3, v0_ba);
edge_inertial->setVertex(4, v1_pose);
edge_inertial->setVertex(5, v1_vel);
auto* rk = new g2o::RobustKernelHuber();
rk->setDelta(200.0);
edge_inertial->setRobustKernel(rk);
optimizer.addEdge(edge_inertial);
edge_inertial->computeError();
LOG(INFO) << "inertial init err: " << edge_inertial->chi2();
auto* edge_gyro_rw = new sad::EdgeGyroRW();
edge_gyro_rw->setVertex(0, v0_bg);
edge_gyro_rw->setVertex(1, v1_bg);
edge_gyro_rw->setInformation(Mat3d::Identity() * 1e6);
optimizer.addEdge(edge_gyro_rw);
edge_gyro_rw->computeError();
LOG(INFO) << "inertial bg rw: " << edge_gyro_rw->chi2();
auto* edge_acc_rw = new sad::EdgeAccRW();
edge_acc_rw->setVertex(0, v0_ba);
edge_acc_rw->setVertex(1, v1_ba);
edge_acc_rw->setInformation(Mat3d::Identity() * 1e6);
optimizer.addEdge(edge_acc_rw);
edge_acc_rw->computeError();
LOG(INFO) << "inertial ba rw: " << edge_acc_rw->chi2();
// GNSS边
auto edge_gnss0 = new sad::EdgeGNSS(v0_pose, last_gnss.utm_pose_);
edge_gnss0->setInformation(Mat6d::Identity() * 1e2);
optimizer.addEdge(edge_gnss0);
edge_gnss0->computeError();
LOG(INFO) << "gnss0 init err: " << edge_gnss0->chi2();
auto edge_gnss1 = new sad::EdgeGNSS(v1_pose, this_gnss.utm_pose_);
edge_gnss1->setInformation(Mat6d::Identity() * 1e2);
optimizer.addEdge(edge_gnss1);
edge_gnss1->computeError();
LOG(INFO) << "gnss1 init err: " << edge_gnss1->chi2();
optimizer.setVerbose(true);
optimizer.initializeOptimization();
optimizer.optimize(10);
sad::NavStated corr_state(this_state.timestamp_, v1_pose->estimate().so3(), v1_pose->estimate().translation(),
v1_vel->estimate(), v1_bg->estimate(), v1_ba->estimate());
LOG(INFO) << "corr state in opt: " << corr_state;
// 获取结果,统计各类误差
LOG(INFO) << "chi2/error: ";
LOG(INFO) << "preintegration: " << edge_inertial->chi2() << "/" << edge_inertial->error().transpose();
LOG(INFO) << "gnss0: " << edge_gnss0->chi2() << ", " << edge_gnss0->error().transpose();
LOG(INFO) << "gnss1: " << edge_gnss1->chi2() << ", " << edge_gnss1->error().transpose();
LOG(INFO) << "bias: " << edge_gyro_rw->chi2() << "/" << edge_acc_rw->error().transpose();
}
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,46 @@
add_executable(point_cloud_load_and_vis point_cloud_load_and_vis.cc)
target_link_libraries(point_cloud_load_and_vis
${PCL_LIBRARIES}
${GLOG_LIBRARIES}
gflags
)
add_executable(pcd_to_bird_eye pcd_to_bird_eye.cc)
target_link_libraries(pcd_to_bird_eye
${PCL_LIBRARIES}
${GLOG_LIBRARIES}
${OpenCV_LIBS}
gflags
)
add_executable(scan_to_range_image scan_to_range_image.cc)
target_link_libraries(scan_to_range_image
${PCL_LIBRARIES}
${GLOG_LIBRARIES}
${OpenCV_LIBS}
gflags
)
add_library(${PROJECT_NAME}.ch5
bfnn.cc
kdtree.cc
octo_tree.cc
)
target_link_libraries(${PROJECT_NAME}.ch5
tbb
)
add_executable(linear_fitting linear_fitting.cc)
target_link_libraries(linear_fitting
${PCL_LIBRARIES}
${GLOG_LIBRARIES}
${OpenCV_LIBS}
gflags
)
ADD_EXECUTABLE(test_nn test_nn.cc)
ADD_TEST(test_nn test_bfnn)
target_link_libraries(test_nn
gtest pthread glog gflags ${PROJECT_NAME}.ch5 ${PROJECT_NAME}.common ${PCL_LIBRARIES} tbb
)

View File

@@ -0,0 +1,80 @@
//
// Created by xiang on 2021/8/18.
//
#include "ch5/bfnn.h"
#include <execution>
namespace sad {
int bfnn_point(CloudPtr cloud, const Vec3f& point) {
return std::min_element(cloud->points.begin(), cloud->points.end(),
[&point](const PointType& pt1, const PointType& pt2) -> bool {
return (pt1.getVector3fMap() - point).squaredNorm() <
(pt2.getVector3fMap() - point).squaredNorm();
}) -
cloud->points.begin();
}
std::vector<int> bfnn_point_k(CloudPtr cloud, const Vec3f& point, int k) {
struct IndexAndDis {
IndexAndDis() {}
IndexAndDis(int index, double dis2) : index_(index), dis2_(dis2) {}
int index_ = 0;
double dis2_ = 0;
};
std::vector<IndexAndDis> index_and_dis(cloud->size());
for (int i = 0; i < cloud->size(); ++i) {
index_and_dis[i] = {i, (cloud->points[i].getVector3fMap() - point).squaredNorm()};
}
std::sort(index_and_dis.begin(), index_and_dis.end(),
[](const auto& d1, const auto& d2) { return d1.dis2_ < d2.dis2_; });
std::vector<int> ret;
std::transform(index_and_dis.begin(), index_and_dis.begin() + k, std::back_inserter(ret),
[](const auto& d1) { return d1.index_; });
return ret;
}
void bfnn_cloud_mt(CloudPtr cloud1, CloudPtr cloud2, std::vector<std::pair<size_t, size_t>>& matches) {
// 先生成索引
std::vector<size_t> index(cloud2->size());
std::for_each(index.begin(), index.end(), [idx = 0](size_t& i) mutable { i = idx++; });
// 并行化for_each
matches.resize(index.size());
std::for_each(std::execution::par_unseq, index.begin(), index.end(), [&](auto idx) {
matches[idx].second = idx;
matches[idx].first = bfnn_point(cloud1, ToVec3f(cloud2->points[idx]));
});
}
void bfnn_cloud(CloudPtr cloud1, CloudPtr cloud2, std::vector<std::pair<size_t, size_t>>& matches) {
// 单线程版本
std::vector<size_t> index(cloud2->size());
std::for_each(index.begin(), index.end(), [idx = 0](size_t& i) mutable { i = idx++; });
matches.resize(index.size());
std::for_each(std::execution::seq, index.begin(), index.end(), [&](auto idx) {
matches[idx].second = idx;
matches[idx].first = bfnn_point(cloud1, ToVec3f(cloud2->points[idx]));
});
}
void bfnn_cloud_mt_k(CloudPtr cloud1, CloudPtr cloud2, std::vector<std::pair<size_t, size_t>>& matches, int k) {
// 先生成索引
std::vector<size_t> index(cloud2->size());
std::for_each(index.begin(), index.end(), [idx = 0](size_t& i) mutable { i = idx++; });
// 并行化for_each
matches.resize(index.size() * k);
std::for_each(std::execution::par_unseq, index.begin(), index.end(), [&](auto idx) {
auto v = bfnn_point_k(cloud1, ToVec3f(cloud2->points[idx]), k);
for (int i = 0; i < v.size(); ++i) {
matches[idx * k + i].first = v[i];
matches[idx * k + i].second = idx;
}
});
}
} // namespace sad

View File

@@ -0,0 +1,58 @@
//
// Created by xiang on 2021/8/18.
//
#ifndef SLAM_IN_AUTO_DRIVING_BFNN_H
#define SLAM_IN_AUTO_DRIVING_BFNN_H
#include "common/eigen_types.h"
#include "common/point_types.h"
#include <thread>
namespace sad {
/**
* Brute-force Nearest Neighbour
* @param cloud 点云
* @param point 待查找点
* @return 找到的最近点索引
*/
int bfnn_point(CloudPtr cloud, const Vec3f& point);
/**
* Brute-force Nearest Neighbour, k近邻
* @param cloud 点云
* @param point 待查找点
* @param k 近邻数
* @return 找到的最近点索引
*/
std::vector<int> bfnn_point_k(CloudPtr cloud, const Vec3f& point, int k = 5);
/**
* 对点云进行BF最近邻
* @param cloud1 目标点云
* @param cloud2 被查找点云
* @param matches 两个点云内的匹配关系
* @return
*/
void bfnn_cloud(CloudPtr cloud1, CloudPtr cloud2, std::vector<std::pair<size_t, size_t>>& matches);
/**
* 对点云进行BF最近邻 多线程版本
* @param cloud1
* @param cloud2
* @param matches
*/
void bfnn_cloud_mt(CloudPtr cloud1, CloudPtr cloud2, std::vector<std::pair<size_t, size_t>>& matches);
/**
* 对点云进行BF最近邻 多线程版本k近邻
* @param cloud1
* @param cloud2
* @param matches
*/
void bfnn_cloud_mt_k(CloudPtr cloud1, CloudPtr cloud2, std::vector<std::pair<size_t, size_t>>& matches, int k = 5);
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_BFNN_H

View File

@@ -0,0 +1,215 @@
//
// Created by xiang on 2021/8/25.
//
#ifndef SLAM_IN_AUTO_DRIVING_GRID2D_HPP
#define SLAM_IN_AUTO_DRIVING_GRID2D_HPP
#include "common/eigen_types.h"
#include "common/math_utils.h"
#include "common/point_types.h"
#include <glog/logging.h>
#include <execution>
#include <map>
namespace sad {
/**
* 栅格法最近邻
* @tparam dim 模板参数使用2D或3D栅格
*/
template <int dim>
class GridNN {
public:
using KeyType = Eigen::Matrix<int, dim, 1>;
using PtType = Eigen::Matrix<float, dim, 1>;
enum class NearbyType {
CENTER, // 只考虑中心
// for 2D
NEARBY4, // 上下左右
NEARBY8, // 上下左右+四角
// for 3D
NEARBY6, // 上下左右前后
};
/**
* 构造函数
* @param resolution 分辨率
* @param nearby_type 近邻判定方法
*/
explicit GridNN(float resolution = 0.1, NearbyType nearby_type = NearbyType::NEARBY4)
: resolution_(resolution), nearby_type_(nearby_type) {
inv_resolution_ = 1.0 / resolution_;
// check dim and nearby
if (dim == 2 && nearby_type_ == NearbyType::NEARBY6) {
LOG(INFO) << "2D grid does not support nearby6, using nearby4 instead.";
nearby_type_ = NearbyType::NEARBY4;
} else if (dim == 3 && (nearby_type_ != NearbyType::NEARBY6 && nearby_type_ != NearbyType::CENTER)) {
LOG(INFO) << "3D grid does not support nearby4/8, using nearby6 instead.";
nearby_type_ = NearbyType::NEARBY6;
}
GenerateNearbyGrids();
}
/// 设置点云,建立栅格
bool SetPointCloud(CloudPtr cloud);
/// 获取最近邻
bool GetClosestPoint(const PointType& pt, PointType& closest_pt, size_t& idx);
/// 对比两个点云
bool GetClosestPointForCloud(CloudPtr ref, CloudPtr query, std::vector<std::pair<size_t, size_t>>& matches);
bool GetClosestPointForCloudMT(CloudPtr ref, CloudPtr query, std::vector<std::pair<size_t, size_t>>& matches);
private:
/// 根据最近邻的类型,生成附近网格
void GenerateNearbyGrids();
/// 空间坐标转到grid
KeyType Pos2Grid(const PtType& pt);
float resolution_ = 0.1; // 分辨率
float inv_resolution_ = 10.0; // 分辨率倒数
NearbyType nearby_type_ = NearbyType::NEARBY4;
std::unordered_map<KeyType, std::vector<size_t>, hash_vec<dim>> grids_; // 栅格数据
CloudPtr cloud_;
std::vector<KeyType> nearby_grids_; // 附近的栅格
};
// 实现
template <int dim>
bool GridNN<dim>::SetPointCloud(CloudPtr cloud) {
std::vector<size_t> index(cloud->size());
std::for_each(index.begin(), index.end(), [idx = 0](size_t& i) mutable { i = idx++; });
std::for_each(index.begin(), index.end(), [&cloud, this](const size_t& idx) {
auto pt = cloud->points[idx];
auto key = Pos2Grid(ToEigen<float, dim>(pt));
if (grids_.find(key) == grids_.end()) {
grids_.insert({key, {idx}});
} else {
grids_[key].emplace_back(idx);
}
});
cloud_ = cloud;
LOG(INFO) << "grids: " << grids_.size();
return true;
}
template <int dim>
Eigen::Matrix<int, dim, 1> GridNN<dim>::Pos2Grid(const Eigen::Matrix<float, dim, 1>& pt) {
return pt.array().template round().template cast<int>();
// Eigen::Matrix<int, dim, 1> ret;
// for (int i = 0; i < dim; ++i) {
// ret(i, 0) = round(pt[i] * inv_resolution_);
// }
// return ret;
}
template <>
void GridNN<2>::GenerateNearbyGrids() {
if (nearby_type_ == NearbyType::CENTER) {
nearby_grids_.emplace_back(KeyType::Zero());
} else if (nearby_type_ == NearbyType::NEARBY4) {
nearby_grids_ = {Vec2i(0, 0), Vec2i(-1, 0), Vec2i(1, 0), Vec2i(0, 1), Vec2i(0, -1)};
} else if (nearby_type_ == NearbyType::NEARBY8) {
nearby_grids_ = {
Vec2i(0, 0), Vec2i(-1, 0), Vec2i(1, 0), Vec2i(0, 1), Vec2i(0, -1),
Vec2i(-1, -1), Vec2i(-1, 1), Vec2i(1, -1), Vec2i(1, 1),
};
}
}
template <>
void GridNN<3>::GenerateNearbyGrids() {
if (nearby_type_ == NearbyType::CENTER) {
nearby_grids_.emplace_back(KeyType::Zero());
} else if (nearby_type_ == NearbyType::NEARBY6) {
nearby_grids_ = {KeyType(0, 0, 0), KeyType(-1, 0, 0), KeyType(1, 0, 0), KeyType(0, 1, 0),
KeyType(0, -1, 0), KeyType(0, 0, -1), KeyType(0, 0, 1)};
}
}
template <int dim>
bool GridNN<dim>::GetClosestPoint(const PointType& pt, PointType& closest_pt, size_t& idx) {
// 在pt栅格周边寻找最近邻
std::vector<size_t> idx_to_check;
auto key = Pos2Grid(ToEigen<float, dim>(pt));
std::for_each(nearby_grids_.begin(), nearby_grids_.end(), [&key, &idx_to_check, this](const KeyType& delta) {
auto dkey = key + delta;
auto iter = grids_.find(dkey);
if (iter != grids_.end()) {
idx_to_check.insert(idx_to_check.end(), iter->second.begin(), iter->second.end());
}
});
if (idx_to_check.empty()) {
return false;
}
// brute force nn in cloud_[idx]
CloudPtr nearby_cloud(new PointCloudType);
std::vector<size_t> nearby_idx;
for (auto& idx : idx_to_check) {
nearby_cloud->points.template emplace_back(cloud_->points[idx]);
nearby_idx.emplace_back(idx);
}
size_t closest_point_idx = bfnn_point(nearby_cloud, ToVec3f(pt));
idx = nearby_idx.at(closest_point_idx);
closest_pt = cloud_->points[idx];
return true;
}
template <int dim>
bool GridNN<dim>::GetClosestPointForCloud(CloudPtr ref, CloudPtr query,
std::vector<std::pair<size_t, size_t>>& matches) {
matches.clear();
std::vector<size_t> index(query->size());
std::for_each(index.begin(), index.end(), [idx = 0](size_t& i) mutable { i = idx++; });
std::for_each(index.begin(), index.end(), [this, &matches, &query](const size_t& idx) {
PointType cp;
size_t cp_idx;
if (GetClosestPoint(query->points[idx], cp, cp_idx)) {
matches.emplace_back(cp_idx, idx);
}
});
return true;
}
template <int dim>
bool GridNN<dim>::GetClosestPointForCloudMT(CloudPtr ref, CloudPtr query,
std::vector<std::pair<size_t, size_t>>& matches) {
matches.clear();
// 与串行版本基本一样但matches需要预先生成匹配失败时填入非法匹配
std::vector<size_t> index(query->size());
std::for_each(index.begin(), index.end(), [idx = 0](size_t& i) mutable { i = idx++; });
matches.resize(index.size());
std::for_each(std::execution::par_unseq, index.begin(), index.end(), [this, &matches, &query](const size_t& idx) {
PointType cp;
size_t cp_idx;
if (GetClosestPoint(query->points[idx], cp, cp_idx)) {
matches[idx] = {cp_idx, idx};
} else {
matches[idx] = {math::kINVALID_ID, math::kINVALID_ID};
}
});
return true;
}
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_GRID2D_HPP

View File

@@ -0,0 +1,236 @@
//
// Created by xiang on 2021/9/22.
//
#include "ch5/kdtree.h"
#include "common/math_utils.h"
#include <glog/logging.h>
#include <execution>
#include <set>
namespace sad {
bool KdTree::BuildTree(const CloudPtr &cloud) {
if (cloud->empty()) {
return false;
}
cloud_.clear();
cloud_.resize(cloud->size());
for (size_t i = 0; i < cloud->points.size(); ++i) {
cloud_[i] = ToVec3f(cloud->points[i]);
}
Clear();
Reset();
IndexVec idx(cloud->size());
for (int i = 0; i < cloud->points.size(); ++i) {
idx[i] = i;
}
Insert(idx, root_.get());
return true;
}
void KdTree::Insert(const IndexVec &points, KdTreeNode *node) {
nodes_.insert({node->id_, node});
if (points.empty()) {
return;
}
if (points.size() == 1) {
size_++;
node->point_idx_ = points[0];
return;
}
IndexVec left, right;
if (!FindSplitAxisAndThresh(points, node->axis_index_, node->split_thresh_, left, right)) {
size_++;
node->point_idx_ = points[0];
return;
}
const auto create_if_not_empty = [&node, this](KdTreeNode *&new_node, const IndexVec &index) {
if (!index.empty()) {
new_node = new KdTreeNode;
new_node->id_ = tree_node_id_++;
Insert(index, new_node);
}
};
create_if_not_empty(node->left_, left);
create_if_not_empty(node->right_, right);
}
bool KdTree::GetClosestPoint(const PointType &pt, std::vector<int> &closest_idx, int k) {
if (k > size_) {
LOG(ERROR) << "cannot set k larger than cloud size: " << k << ", " << size_;
return false;
}
k_ = k;
std::priority_queue<NodeAndDistance> knn_result;
Knn(ToVec3f(pt), root_.get(), knn_result);
// 排序并返回结果
closest_idx.resize(knn_result.size());
for (int i = closest_idx.size() - 1; i >= 0; --i) {
// 倒序插入
closest_idx[i] = knn_result.top().node_->point_idx_;
knn_result.pop();
}
return true;
}
bool KdTree::GetClosestPointMT(const CloudPtr &cloud, std::vector<std::pair<size_t, size_t>> &matches, int k) {
matches.resize(cloud->size() * k);
// 索引
std::vector<int> index(cloud->size());
for (int i = 0; i < cloud->points.size(); ++i) {
index[i] = i;
}
std::for_each(std::execution::par_unseq, index.begin(), index.end(), [this, &cloud, &matches, &k](int idx) {
std::vector<int> closest_idx;
GetClosestPoint(cloud->points[idx], closest_idx, k);
for (int i = 0; i < k; ++i) {
matches[idx * k + i].second = idx;
if (i < closest_idx.size()) {
matches[idx * k + i].first = closest_idx[i];
} else {
matches[idx * k + i].first = math::kINVALID_ID;
}
}
});
return true;
}
void KdTree::Knn(const Vec3f &pt, KdTreeNode *node, std::priority_queue<NodeAndDistance> &knn_result) const {
if (node->IsLeaf()) {
// 如果是叶子,检查叶子是否能插入
ComputeDisForLeaf(pt, node, knn_result);
return;
}
// 看pt落在左还是右优先搜索pt所在的子树
// 然后再看另一侧子树是否需要搜索
KdTreeNode *this_side, *that_side;
if (pt[node->axis_index_] < node->split_thresh_) {
this_side = node->left_;
that_side = node->right_;
} else {
this_side = node->right_;
that_side = node->left_;
}
Knn(pt, this_side, knn_result);
if (NeedExpand(pt, node, knn_result)) { // 注意这里是跟自己比
Knn(pt, that_side, knn_result);
}
}
bool KdTree::NeedExpand(const Vec3f &pt, KdTreeNode *node, std::priority_queue<NodeAndDistance> &knn_result) const {
if (knn_result.size() < k_) {
return true;
}
if (approximate_) {
float d = pt[node->axis_index_] - node->split_thresh_;
if ((d * d) < knn_result.top().distance2_ * alpha_) {
return true;
} else {
return false;
}
} else {
// 检测切面距离,看是否有比现在更小的
float d = pt[node->axis_index_] - node->split_thresh_;
if ((d * d) < knn_result.top().distance2_) {
return true;
} else {
return false;
}
}
}
void KdTree::ComputeDisForLeaf(const Vec3f &pt, KdTreeNode *node,
std::priority_queue<NodeAndDistance> &knn_result) const {
// 比较与结果队列的差异,如果优于最远距离,则插入
float dis2 = Dis2(pt, cloud_[node->point_idx_]);
if (knn_result.size() < k_) {
// results 不足k
knn_result.emplace(node, dis2);
} else {
// results等于k比较current与max_dis_iter之间的差异
if (dis2 < knn_result.top().distance2_) {
knn_result.emplace(node, dis2);
knn_result.pop();
}
}
}
bool KdTree::FindSplitAxisAndThresh(const IndexVec &point_idx, int &axis, float &th, IndexVec &left, IndexVec &right) {
// 计算三个轴上的散布情况我们使用math_utils.h里的函数
Vec3f var;
Vec3f mean;
math::ComputeMeanAndCovDiag(point_idx, mean, var, [this](int idx) { return cloud_[idx]; });
int max_i, max_j;
var.maxCoeff(&max_i, &max_j);
axis = max_i;
th = mean[axis];
for (const auto &idx : point_idx) {
if (cloud_[idx][axis] < th) {
// 中位数可能向左取整
left.emplace_back(idx);
} else {
right.emplace_back(idx);
}
}
// 边界情况检查输入的points等于同一个值上面的判定是>=号,所以都进了右侧
// 这种情况不需要继续展开,直接将当前节点设为叶子就行
// if (point_idx.size() > 1 && (left.empty() || right.empty())) {
// return false;
// }
return true;
}
void KdTree::Reset() {
tree_node_id_ = 0;
root_.reset(new KdTreeNode());
root_->id_ = tree_node_id_++;
size_ = 0;
}
void KdTree::Clear() {
for (const auto &np : nodes_) {
if (np.second != root_.get()) {
delete np.second;
}
}
nodes_.clear();
root_ = nullptr;
size_ = 0;
tree_node_id_ = 0;
}
void KdTree::PrintAll() {
for (const auto &np : nodes_) {
auto node = np.second;
if (node->left_ == nullptr && node->right_ == nullptr) {
LOG(INFO) << "leaf node: " << node->id_ << ", idx: " << node->point_idx_;
} else {
LOG(INFO) << "node: " << node->id_ << ", axis: " << node->axis_index_ << ", th: " << node->split_thresh_;
}
}
}
} // namespace sad

View File

@@ -0,0 +1,133 @@
//
// Created by xiang on 2021/9/22.
//
#ifndef SLAM_IN_AUTO_DRIVING_KDTREE_H
#define SLAM_IN_AUTO_DRIVING_KDTREE_H
#include "common/eigen_types.h"
#include "common/point_types.h"
#include <glog/logging.h>
#include <map>
#include <queue>
namespace sad {
/// Kd树节点二叉树结构内部用祼指针对外一个root的shared_ptr
struct KdTreeNode {
int id_ = -1;
int point_idx_ = 0; // 点的索引
int axis_index_ = 0; // 分割轴
float split_thresh_ = 0.0; // 分割位置
KdTreeNode* left_ = nullptr; // 左子树
KdTreeNode* right_ = nullptr; // 右子树
bool IsLeaf() const { return left_ == nullptr && right_ == nullptr; } // 是否为叶子
};
/// 用于记录knn结果
struct NodeAndDistance {
NodeAndDistance(KdTreeNode* node, float dis2) : node_(node), distance2_(dis2) {}
KdTreeNode* node_ = nullptr;
float distance2_ = 0; // 平方距离,用于比较
bool operator<(const NodeAndDistance& other) const { return distance2_ < other.distance2_; }
};
/**
* 手写kd树
* 测试这个kd树的召回!
*/
class KdTree {
public:
explicit KdTree() = default;
~KdTree() { Clear(); }
bool BuildTree(const CloudPtr& cloud);
/// 获取k最近邻
bool GetClosestPoint(const PointType& pt, std::vector<int>& closest_idx, int k = 5);
/// 并行为点云寻找最近邻
bool GetClosestPointMT(const CloudPtr& cloud, std::vector<std::pair<size_t, size_t>>& matches, int k = 5);
/// 这个被用于计算最近邻的倍数
void SetEnableANN(bool use_ann = true, float alpha = 0.1) {
approximate_ = use_ann;
alpha_ = alpha;
}
/// 返回节点数量
size_t size() const { return size_; }
/// 清理数据
void Clear();
/// 打印所有节点信息
void PrintAll();
private:
/// kdtree 构建相关
/**
* 在node处插入点
* @param points
* @param node
*/
void Insert(const IndexVec& points, KdTreeNode* node);
/**
* 计算点集的分割面
* @param points 输入点云
* @param axis 轴
* @param th 阈值
* @param left 左子树
* @param right 右子树
* @return
*/
bool FindSplitAxisAndThresh(const IndexVec& point_idx, int& axis, float& th, IndexVec& left, IndexVec& right);
void Reset();
/// 两个点的平方距离
static inline float Dis2(const Vec3f& p1, const Vec3f& p2) { return (p1 - p2).squaredNorm(); }
// Knn 相关
/**
* 检查给定点在kdtree node上的knn可以递归调用
* @param pt 查询点
* @param node kdtree 节点
*/
void Knn(const Vec3f& pt, KdTreeNode* node, std::priority_queue<NodeAndDistance>& result) const;
/**
* 对叶子节点,计算它和查询点的距离,尝试放入结果中
* @param pt 查询点
* @param node Kdtree 节点
*/
void ComputeDisForLeaf(const Vec3f& pt, KdTreeNode* node, std::priority_queue<NodeAndDistance>& result) const;
/**
* 检查node下是否需要展开
* @param pt 查询点
* @param node Kdtree 节点
* @return true if 需要展开
*/
bool NeedExpand(const Vec3f& pt, KdTreeNode* node, std::priority_queue<NodeAndDistance>& knn_result) const;
int k_ = 5; // knn最近邻数量
std::shared_ptr<KdTreeNode> root_ = nullptr; // 根节点
std::vector<Vec3f> cloud_; // 输入点云
std::unordered_map<int, KdTreeNode*> nodes_; // for bookkeeping
size_t size_ = 0; // 叶子节点数量
int tree_node_id_ = 0; // 为kdtree node 分配id
// 近似最近邻
bool approximate_ = true;
float alpha_ = 0.1;
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_KDTREE_H

View File

@@ -0,0 +1,82 @@
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <opencv2/opencv.hpp>
#include "common/eigen_types.h"
#include "common/math_utils.h"
DEFINE_int32(num_tested_points_plane, 10, "number of tested points in plane fitting");
DEFINE_int32(num_tested_points_line, 100, "number of tested points in line fitting");
DEFINE_double(noise_sigma, 0.01, "noise of generated samples");
void PlaneFittingTest();
void LineFittingTest();
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
LOG(INFO) << "testing plane fitting";
PlaneFittingTest();
LOG(INFO) << "testing line fitting";
LineFittingTest();
}
void PlaneFittingTest() {
Vec4d true_plane_coeffs(0.1, 0.2, 0.3, 0.4);
true_plane_coeffs.normalize();
std::vector<Vec3d> points;
// 随机生成仿真平面点
cv::RNG rng;
for (int i = 0; i < FLAGS_num_tested_points_plane; ++i) {
// 先生成一个随机点,计算第四维,增加噪声,再归一化
Vec3d p(rng.uniform(0.0, 1.0), rng.uniform(0.0, 1.0), rng.uniform(0.0, 1.0));
double n4 = -p.dot(true_plane_coeffs.head<3>()) / true_plane_coeffs[3];
p = p / (n4 + std::numeric_limits<double>::min()); // 防止除零
p += Vec3d(rng.gaussian(FLAGS_noise_sigma), rng.gaussian(FLAGS_noise_sigma), rng.gaussian(FLAGS_noise_sigma));
points.emplace_back(p);
// 验证在平面上
LOG(INFO) << "res of p: " << p.dot(true_plane_coeffs.head<3>()) + true_plane_coeffs[3];
}
Vec4d estimated_plane_coeffs;
if (sad::math::FitPlane(points, estimated_plane_coeffs)) {
LOG(INFO) << "estimated coeffs: " << estimated_plane_coeffs.transpose()
<< ", true: " << true_plane_coeffs.transpose();
} else {
LOG(INFO) << "plane fitting failed";
}
}
void LineFittingTest() {
// 直线拟合参数真值
Vec3d true_line_origin(0.1, 0.2, 0.3);
Vec3d true_line_dir(0.4, 0.5, 0.6);
true_line_dir.normalize();
// 随机生成直线点,利用参数方程
std::vector<Vec3d> points;
cv::RNG rng;
for (int i = 0; i < fLI::FLAGS_num_tested_points_line; ++i) {
double t = rng.uniform(-1.0, 1.0);
Vec3d p = true_line_origin + true_line_dir * t;
p += Vec3d(rng.gaussian(FLAGS_noise_sigma), rng.gaussian(FLAGS_noise_sigma), rng.gaussian(FLAGS_noise_sigma));
points.emplace_back(p);
}
Vec3d esti_origin, esti_dir;
if (sad::math::FitLine(points, esti_origin, esti_dir)) {
LOG(INFO) << "estimated origin: " << esti_origin.transpose() << ", true: " << true_line_origin.transpose();
LOG(INFO) << "estimated dir: " << esti_dir.transpose() << ", true: " << true_line_dir.transpose();
} else {
LOG(INFO) << "line fitting failed";
}
}

View File

@@ -0,0 +1,269 @@
//
// Created by xiang on 2021/9/27.
//
#include "octo_tree.h"
#include "common/math_utils.h"
#include <execution>
namespace sad {
bool OctoTree::BuildTree(const CloudPtr &cloud) {
if (cloud->empty()) {
return false;
}
cloud_.clear();
cloud_.resize(cloud->size());
for (size_t i = 0; i < cloud->points.size(); ++i) {
cloud_[i] = ToVec3f(cloud->points[i]);
}
Clear();
Reset();
IndexVec idx(cloud->size());
for (int i = 0; i < cloud->points.size(); ++i) {
idx[i] = i;
}
// 生成根节点的边界框
root_->box_ = ComputeBoundingBox();
Insert(idx, root_.get());
return true;
}
void OctoTree::Insert(const IndexVec &points, OctoTreeNode *node) {
nodes_.insert({node->id_, node});
if (points.empty()) {
return;
}
if (points.size() == 1) {
size_++;
node->point_idx_ = points[0];
return;
}
/// 只要点数不为1,就继续展开这个节点
std::vector<IndexVec> children_points;
ExpandNode(node, points, children_points);
/// 对子节点进行插入操作
for (size_t i = 0; i < 8; ++i) {
Insert(children_points[i], node->children[i]);
}
}
void OctoTree::ExpandNode(OctoTreeNode *node, const IndexVec &parent_idx, std::vector<IndexVec> &children_idx) {
children_idx.resize(8);
for (int i = 0; i < 8; ++i) {
node->children[i] = new OctoTreeNode();
node->children[i]->id_ = tree_node_id_++;
}
const Box3D &b = node->box_; // 本节点的box
// 中心点
float c_x = 0.5 * (node->box_.min_[0] + node->box_.max_[0]);
float c_y = 0.5 * (node->box_.min_[1] + node->box_.max_[1]);
float c_z = 0.5 * (node->box_.min_[2] + node->box_.max_[2]);
// 8个外框示意图
// clang-format off
// 第一层左上1 右上2 左下3 右下4
// 第二层左上5 右上6 左下7 右下8
// ---> x /-------/-------/|
// /| /-------/-------/||
// / | /-------/-------/ ||
// y |z | | | /|
// |_______|_______|/|/
// | | | /
// |_______|_______|/
// clang-format on
node->children[0]->box_ = {b.min_[0], c_x, b.min_[1], c_y, b.min_[2], c_z};
node->children[1]->box_ = {c_x, b.max_[0], b.min_[1], c_y, b.min_[2], c_z};
node->children[2]->box_ = {b.min_[0], c_x, c_y, b.max_[1], b.min_[2], c_z};
node->children[3]->box_ = {c_x, b.max_[0], c_y, b.max_[1], b.min_[2], c_z};
node->children[4]->box_ = {b.min_[0], c_x, b.min_[1], c_y, c_z, b.max_[2]};
node->children[5]->box_ = {c_x, b.max_[0], b.min_[1], c_y, c_z, b.max_[2]};
node->children[6]->box_ = {b.min_[0], c_x, c_y, b.max_[1], c_z, b.max_[2]};
node->children[7]->box_ = {c_x, b.max_[0], c_y, b.max_[1], c_z, b.max_[2]};
// 把点云归到子节点中
for (int idx : parent_idx) {
const auto pt = cloud_[idx];
for (int i = 0; i < 8; ++i) {
if (node->children[i]->box_.Inside(pt)) {
children_idx[i].emplace_back(idx);
break;
}
}
}
}
Box3D OctoTree::ComputeBoundingBox() {
float min_values[3] = {std::numeric_limits<float>::max(),std::numeric_limits<float>::max(),std::numeric_limits<float>::max()};
float max_values[3] = {-std::numeric_limits<float>::max(),-std::numeric_limits<float>::max(),-std::numeric_limits<float>::max()};
for (const auto &p : cloud_) {
for (int i = 0; i < 3; ++i) {
max_values[i] = p[i] > max_values[i] ? p[i] : max_values[i];
min_values[i] = p[i] < min_values[i] ? p[i] : min_values[i];
}
}
return {min_values[0], max_values[0], min_values[1], max_values[1], min_values[2], max_values[2]};
}
bool OctoTree::GetClosestPoint(const PointType &pt, std::vector<int> &closest_idx, int k) const {
if (k > size_) {
LOG(ERROR) << "cannot set k larger than cloud size: " << k << ", " << size_;
return false;
}
std::priority_queue<NodeAndDistanceOcto> knn_result;
Knn(ToVec3f(pt), root_.get(), knn_result);
// 排序并返回结果
closest_idx.resize(knn_result.size());
for (int i = closest_idx.size() - 1; i >= 0; --i) {
// 倒序插入
closest_idx[i] = knn_result.top().node_->point_idx_;
knn_result.pop();
}
return true;
}
bool OctoTree::GetClosestPointMT(const CloudPtr &cloud, std::vector<std::pair<size_t, size_t>> &matches, int k) {
k_ = k;
matches.resize(cloud->size() * k);
// 索引
std::vector<int> index(cloud->size());
for (int i = 0; i < cloud->points.size(); ++i) {
index[i] = i;
}
std::for_each(std::execution::par_unseq, index.begin(), index.end(), [this, &cloud, &matches, &k](int idx) {
std::vector<int> closest_idx;
GetClosestPoint(cloud->points[idx], closest_idx, k);
for (int i = 0; i < k; ++i) {
matches[idx * k + i].second = idx;
if (i < closest_idx.size()) {
matches[idx * k + i].first = closest_idx[i];
} else {
matches[idx * k + i].first = math::kINVALID_ID;
}
}
});
return true;
}
void OctoTree::Clear() {
for (const auto &np : nodes_) {
if (np.second != root_.get()) {
delete np.second;
}
}
root_ = nullptr;
size_ = 0;
tree_node_id_ = 0;
}
void OctoTree::Reset() {
tree_node_id_ = 0;
root_.reset(new OctoTreeNode());
root_->id_ = tree_node_id_++;
size_ = 0;
}
void OctoTree::Knn(const Vec3f &pt, OctoTreeNode *node, std::priority_queue<NodeAndDistanceOcto> &knn_result) const {
if (node->IsLeaf()) {
if (node->point_idx_ != -1) {
// 如果是叶子,看该点是否为最近邻
ComputeDisForLeaf(pt, node, knn_result);
return;
}
return;
}
// 看pt落在哪一格优先搜索pt所在的子树
// 然后再看其他子树是否需要搜索
// 如果pt在外边优先搜索最近的子树
int idx_child = -1;
float min_dis = std::numeric_limits<float>::max();
for (int i = 0; i < 8; ++i) {
if (node->children[i]->box_.Inside(pt)) {
idx_child = i;
break;
} else {
float d = node->children[i]->box_.Dis(pt);
if (d < min_dis) {
idx_child = i;
min_dis = d;
}
}
}
// 先检查idx_child
Knn(pt, node->children[idx_child], knn_result);
// 再检查其他的
for (int i = 0; i < 8; ++i) {
if (i == idx_child) {
continue;
}
if (NeedExpand(pt, node->children[i], knn_result)) {
Knn(pt, node->children[i], knn_result);
}
}
}
void OctoTree::ComputeDisForLeaf(const Vec3f &pt, OctoTreeNode *node,
std::priority_queue<NodeAndDistanceOcto> &knn_result) const {
// 比较与结果队列的差异,如果优于最远距离,则插入
float dis2 = Dis2(pt, cloud_[node->point_idx_]);
if (knn_result.size() < k_) {
// results 不足k
knn_result.push({node, dis2});
} else {
// results等于k比较current与max_dis_iter之间的差异
if (dis2 < knn_result.top().distance_) {
knn_result.push({node, dis2});
knn_result.pop();
}
}
}
bool OctoTree::NeedExpand(const Vec3f &pt, OctoTreeNode *node,
std::priority_queue<NodeAndDistanceOcto> &knn_result) const {
if (knn_result.size() < k_) {
return true;
}
if (approximate_) {
float d = node->box_.Dis(pt);
if ((d * d) < knn_result.top().distance_ * alpha_) {
return true;
} else {
return false;
}
} else {
// 不用flann时按通常情况查找
float d = node->box_.Dis(pt);
if ((d * d) < knn_result.top().distance_) {
return true;
} else {
return false;
}
}
}
} // namespace sad

View File

@@ -0,0 +1,164 @@
//
// Created by xiang on 2021/9/27.
//
#ifndef SLAM_IN_AUTO_DRIVING_OCTO_TREE_H
#define SLAM_IN_AUTO_DRIVING_OCTO_TREE_H
#include "common/eigen_types.h"
#include "common/point_types.h"
#include <glog/logging.h>
#include <map>
#include <queue>
namespace sad {
// 3D Box 记录各轴上的最大最小值
struct Box3D {
Box3D() = default;
Box3D(float min_x, float max_x, float min_y, float max_y, float min_z, float max_z)
: min_{min_x, min_y, min_z}, max_{max_x, max_y, max_z} {}
/// 判断pt是否在内部
bool Inside(const Vec3f& pt) const {
return pt[0] <= max_[0] && pt[0] >= min_[0] && pt[1] <= max_[1] && pt[1] >= min_[1] && pt[2] <= max_[2] &&
pt[2] >= min_[2];
}
/// 点到3D Box距离
/// 我们取外侧点到边界的最大值
float Dis(const Vec3f& pt) const {
float ret = 0;
for (int i = 0; i < 3; ++i) {
if (pt[i] < min_[i]) {
float d = min_[i] - pt[i];
ret = d > ret ? d : ret;
} else if (pt[i] > max_[i]) {
float d = pt[i] - max_[i];
ret = d > ret ? d : ret;
}
}
assert(ret >= 0);
return ret;
}
float min_[3] = {0};
float max_[3] = {0};
};
/// octo tree 节点
struct OctoTreeNode {
int id_ = -1;
int point_idx_ = -1; // 点的索引,-1为无效
Box3D box_; // 边界框
OctoTreeNode* children[8] = {nullptr}; // 子节点
bool IsLeaf() const {
for (const OctoTreeNode* n : children) {
if (n != nullptr) {
return false;
}
}
return true;
}
};
/// 用于记录knn结果
struct NodeAndDistanceOcto {
NodeAndDistanceOcto(OctoTreeNode* node, float dis2) : node_(node), distance_(dis2) {}
OctoTreeNode* node_ = nullptr;
float distance_ = 0; // 平方距离,用于比较
bool operator<(const NodeAndDistanceOcto& other) const { return distance_ < other.distance_; }
};
class OctoTree {
public:
explicit OctoTree() = default;
~OctoTree() { Clear(); }
bool BuildTree(const CloudPtr& cloud);
/// 获取k最近邻
bool GetClosestPoint(const PointType& pt, std::vector<int>& closest_idx, int k = 5) const;
/// 并行为点云寻找最近邻
bool GetClosestPointMT(const CloudPtr& cloud, std::vector<std::pair<size_t, size_t>>& match, int k = 5);
/// 设置近似最近邻参数
void SetApproximate(bool use_ann = true, float alpha = 0.1) {
approximate_ = use_ann;
alpha_ = alpha;
}
/// 返回节点数量
size_t size() const { return size_; }
/// 清理数据
void Clear();
private:
/// kdtree 构建相关
/**
* 在node处插入点
* @param points
* @param node
*/
void Insert(const IndexVec& points, OctoTreeNode* node);
/// 为全局点云生成边界框
Box3D ComputeBoundingBox();
/**
* 展开一个节点
* @param [in] node 被展开的节点
* @param [in] parent_idx 父节点的点云索引
* @param [out] children_idx 子节点的点云索引
*/
void ExpandNode(OctoTreeNode* node, const IndexVec& parent_idx, std::vector<IndexVec>& children_idx);
void Reset();
/// 两个点的平方距离
static inline float Dis2(const Vec3f& p1, const Vec3f& p2) { return (p1 - p2).squaredNorm(); }
// Knn 相关
/**
* 检查给定点在kdtree node上的knn可以递归调用
* @param pt 查询点
* @param node kdtree 节点
*/
void Knn(const Vec3f& pt, OctoTreeNode* node, std::priority_queue<NodeAndDistanceOcto>& result) const;
/**
* 对叶子节点,计算它和查询点的距离,尝试放入结果中
* @param pt 查询点
* @param node Kdtree 节点
*/
void ComputeDisForLeaf(const Vec3f& pt, OctoTreeNode* node, std::priority_queue<NodeAndDistanceOcto>& result) const;
/**
* 检查node下是否需要展开
* @param pt 查询点
* @param node Kdtree 节点
* @return true if 需要展开
*/
bool NeedExpand(const Vec3f& pt, OctoTreeNode* node, std::priority_queue<NodeAndDistanceOcto>& knn_result) const;
int k_ = 5; // knn最近邻数量
std::shared_ptr<OctoTreeNode> root_ = nullptr; // 叶子节点
std::vector<Vec3f> cloud_; // 输入点云
std::map<int, OctoTreeNode*> nodes_; // for bookkeeping
size_t size_ = 0; // 叶子节点数量
int tree_node_id_ = 0; // 为kdtree node 分配id
// flann
bool approximate_ = false;
float alpha_ = 1.0; // flann的距离倍数
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_OCTO_TREE_H

View File

@@ -0,0 +1,85 @@
//
// Created by xiang on 2021/8/9.
//
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using PointType = pcl::PointXYZI;
using PointCloudType = pcl::PointCloud<PointType>;
DEFINE_string(pcd_path, "./data/ch5/map_example.pcd", "点云文件路径");
DEFINE_double(image_resolution, 0.1, "俯视图分辨率");
DEFINE_double(min_z, 0.2, "俯视图最低高度");
DEFINE_double(max_z, 2.5, "俯视图最高高度");
/// 本节演示如何将一个点云转换为俯视图像
void GenerateBEVImage(PointCloudType::Ptr cloud) {
// 计算点云边界
auto minmax_x = std::minmax_element(cloud->points.begin(), cloud->points.end(),
[](const PointType& p1, const PointType& p2) { return p1.x < p2.x; });
auto minmax_y = std::minmax_element(cloud->points.begin(), cloud->points.end(),
[](const PointType& p1, const PointType& p2) { return p1.y < p2.y; });
double min_x = minmax_x.first->x;
double max_x = minmax_x.second->x;
double min_y = minmax_y.first->y;
double max_y = minmax_y.second->y;
const double inv_r = 1.0 / FLAGS_image_resolution;
const int image_rows = int((max_y - min_y) * inv_r);
const int image_cols = int((max_x - min_x) * inv_r);
float x_center = 0.5 * (max_x + min_x);
float y_center = 0.5 * (max_y + min_y);
float x_center_image = image_cols / 2;
float y_center_image = image_rows / 2;
// 生成图像
cv::Mat image(image_rows, image_cols, CV_8UC3, cv::Scalar(255, 255, 255));
for (const auto& pt : cloud->points) {
int x = int((pt.x - x_center) * inv_r + x_center_image);
int y = int((pt.y - y_center) * inv_r + y_center_image);
if (x < 0 || x >= image_cols || y < 0 || y >= image_rows || pt.z < FLAGS_min_z || pt.z > FLAGS_max_z) {
continue;
}
image.at<cv::Vec3b>(y, x) = cv::Vec3b(227, 143, 79);
}
cv::imwrite("./bev.png", image);
}
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_pcd_path.empty()) {
LOG(ERROR) << "pcd path is empty";
return -1;
}
// 读取点云
PointCloudType::Ptr cloud(new PointCloudType);
pcl::io::loadPCDFile(FLAGS_pcd_path, *cloud);
if (cloud->empty()) {
LOG(ERROR) << "cannot load cloud file";
return -1;
}
LOG(INFO) << "cloud points: " << cloud->size();
GenerateBEVImage(cloud);
return 0;
}

View File

@@ -0,0 +1,49 @@
//
// Created by xiang on 2021/8/6.
//
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/visualization/pcl_visualizer.h>
using PointType = pcl::PointXYZI;
using PointCloudType = pcl::PointCloud<PointType>;
DEFINE_string(pcd_path, "./data/ch5/map_example.pcd", "点云文件路径");
/// 本程序可用于显示单个点云演示PCL的基本用法
/// 实际上就是调用了pcl的可视化库类似于pcl_viewer
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_pcd_path.empty()) {
LOG(ERROR) << "pcd path is empty";
return -1;
}
// 读取点云
PointCloudType::Ptr cloud(new PointCloudType);
pcl::io::loadPCDFile(FLAGS_pcd_path, *cloud);
if (cloud->empty()) {
LOG(ERROR) << "cannot load cloud file";
return -1;
}
LOG(INFO) << "cloud points: " << cloud->size();
// visualize
pcl::visualization::PCLVisualizer viewer("cloud viewer");
pcl::visualization::PointCloudColorHandlerGenericField<PointType> handle(cloud, "z"); // 使用高度来着色
viewer.addPointCloud<PointType>(cloud, handle);
viewer.spin();
return 0;
}

View File

@@ -0,0 +1,84 @@
// // Created by xiang on 2021/8/9.
//
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <opencv2/opencv.hpp>
using PointType = pcl::PointXYZI;
using PointCloudType = pcl::PointCloud<PointType>;
DEFINE_string(pcd_path, "./data/ch5/scan_example.pcd", "点云文件路径");
DEFINE_double(azimuth_resolution_deg, 0.3, "方位角分辨率(度)");
DEFINE_int32(elevation_rows, 16, "俯仰角对应的行数");
DEFINE_double(elevation_range, 15.0, "俯仰角范围"); // VLP-16 上下各15度范围
DEFINE_double(lidar_height, 1.128, "雷达安装高度");
void GenerateRangeImage(PointCloudType::Ptr cloud) {
int image_cols = int(360 / FLAGS_azimuth_resolution_deg); // 水平为360度按分辨率切分即可
int image_rows = FLAGS_elevation_rows; // 图像行数固定
LOG(INFO) << "range image: " << image_rows << "x" << image_cols;
// 我们生成一个HSV图像以更好地显示图像
cv::Mat image(image_rows, image_cols, CV_8UC3, cv::Scalar(0, 0, 0));
double ele_resolution = FLAGS_elevation_range * 2 / FLAGS_elevation_rows; // elevation分辨率
for (const auto& pt : cloud->points) {
double azimuth = atan2(pt.y, pt.x) * 180 / M_PI;
double range = sqrt(pt.x * pt.x + pt.y * pt.y);
double elevation = asin((pt.z - FLAGS_lidar_height) / range) * 180 / M_PI;
// keep in 0~360
if (azimuth < 0) {
azimuth += 360;
}
int x = int(azimuth / FLAGS_azimuth_resolution_deg); // 行
int y = int((elevation + FLAGS_elevation_range) / ele_resolution + 0.5); // 列
if (x >= 0 && x < image.cols && y >= 0 && y < image.rows) {
image.at<cv::Vec3b>(y, x) = cv::Vec3b(uchar(range / 100 * 255.0), 255, 127);
}
}
// 沿Y轴翻转因为我们希望Z轴朝上时Y朝上
cv::Mat image_flipped;
cv::flip(image, image_flipped, 0);
// hsv to rgb
cv::Mat image_rgb;
cv::cvtColor(image_flipped, image_rgb, cv::COLOR_HSV2BGR);
cv::imwrite("./range_image.png", image_rgb);
}
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
google::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_pcd_path.empty()) {
LOG(ERROR) << "pcd path is empty";
return -1;
}
// 读取点云
PointCloudType::Ptr cloud(new PointCloudType);
pcl::io::loadPCDFile(FLAGS_pcd_path, *cloud);
if (cloud->empty()) {
LOG(ERROR) << "cannot load cloud file";
return -1;
}
LOG(INFO) << "cloud points: " << cloud->size();
GenerateRangeImage(cloud);
return 0;
}

View File

@@ -0,0 +1,348 @@
//
// Created by xiang on 2021/8/19.
//
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <pcl/io/pcd_io.h>
#include <pcl/search/kdtree.h>
#include "ch5/bfnn.h"
#include "ch5/gridnn.hpp"
#include "ch5/kdtree.h"
#include "ch5/octo_tree.h"
#include "common/point_cloud_utils.h"
#include "common/point_types.h"
#include "common/sys_utils.h"
DEFINE_string(first_scan_path, "./data/ch5/first.pcd", "第一个点云路径");
DEFINE_string(second_scan_path, "./data/ch5/second.pcd", "第二个点云路径");
DEFINE_double(ANN_alpha, 1.0, "AAN的比例因子");
TEST(CH5_TEST, BFNN) {
sad::CloudPtr first(new sad::PointCloudType), second(new sad::PointCloudType);
pcl::io::loadPCDFile(FLAGS_first_scan_path, *first);
pcl::io::loadPCDFile(FLAGS_second_scan_path, *second);
if (first->empty() || second->empty()) {
LOG(ERROR) << "cannot load cloud";
FAIL();
}
// voxel grid 至 0.05
sad::VoxelGrid(first);
sad::VoxelGrid(second);
LOG(INFO) << "points: " << first->size() << ", " << second->size();
// 评价单线程和多线程版本的暴力匹配
sad::evaluate_and_call(
[&first, &second]() {
std::vector<std::pair<size_t, size_t>> matches;
sad::bfnn_cloud(first, second, matches);
},
"暴力匹配(单线程)", 5);
sad::evaluate_and_call(
[&first, &second]() {
std::vector<std::pair<size_t, size_t>> matches;
sad::bfnn_cloud_mt(first, second, matches);
},
"暴力匹配(多线程)", 5);
SUCCEED();
}
/**
* 评测最近邻的正确性
* @param truth 真值
* @param esti 估计
*/
void EvaluateMatches(const std::vector<std::pair<size_t, size_t>>& truth,
const std::vector<std::pair<size_t, size_t>>& esti) {
int fp = 0; // false-positiveesti存在但truth中不存在
int fn = 0; // false-negative, truth存在但esti不存在
LOG(INFO) << "truth: " << truth.size() << ", esti: " << esti.size();
/// 检查某个匹配在另一个容器中存不存在
auto exist = [](const std::pair<size_t, size_t>& data, const std::vector<std::pair<size_t, size_t>>& vec) -> bool {
return std::find(vec.begin(), vec.end(), data) != vec.end();
};
int effective_esti = 0;
for (const auto& d : esti) {
if (d.first != sad::math::kINVALID_ID && d.second != sad::math::kINVALID_ID) {
effective_esti++;
if (!exist(d, truth)) {
fp++;
}
}
}
for (const auto& d : truth) {
if (!exist(d, esti)) {
fn++;
}
}
float precision = 1.0 - float(fp) / effective_esti;
float recall = 1.0 - float(fn) / truth.size();
LOG(INFO) << "precision: " << precision << ", recall: " << recall << ", fp: " << fp << ", fn: " << fn;
}
TEST(CH5_TEST, GRID_NN) {
sad::CloudPtr first(new sad::PointCloudType), second(new sad::PointCloudType);
pcl::io::loadPCDFile(FLAGS_first_scan_path, *first);
pcl::io::loadPCDFile(FLAGS_second_scan_path, *second);
if (first->empty() || second->empty()) {
LOG(ERROR) << "cannot load cloud";
FAIL();
}
// voxel grid 至 0.05
sad::VoxelGrid(first);
sad::VoxelGrid(second);
LOG(INFO) << "points: " << first->size() << ", " << second->size();
std::vector<std::pair<size_t, size_t>> truth_matches;
sad::bfnn_cloud(first, second, truth_matches);
// 对比不同种类的grid
sad::GridNN<2> grid0(0.1, sad::GridNN<2>::NearbyType::CENTER), grid4(0.1, sad::GridNN<2>::NearbyType::NEARBY4),
grid8(0.1, sad::GridNN<2>::NearbyType::NEARBY8);
sad::GridNN<3> grid3(0.1, sad::GridNN<3>::NearbyType::NEARBY6);
grid0.SetPointCloud(first);
grid4.SetPointCloud(first);
grid8.SetPointCloud(first);
grid3.SetPointCloud(first);
// 评价各种版本的Grid NN
// sorry没有C17的template lambda... 下面必须写的啰嗦一些
LOG(INFO) << "===================";
std::vector<std::pair<size_t, size_t>> matches;
sad::evaluate_and_call(
[&first, &second, &grid0, &matches]() { grid0.GetClosestPointForCloud(first, second, matches); },
"Grid0 单线程", 10);
EvaluateMatches(truth_matches, matches);
LOG(INFO) << "===================";
sad::evaluate_and_call(
[&first, &second, &grid0, &matches]() { grid0.GetClosestPointForCloudMT(first, second, matches); },
"Grid0 多线程", 10);
EvaluateMatches(truth_matches, matches);
LOG(INFO) << "===================";
sad::evaluate_and_call(
[&first, &second, &grid4, &matches]() { grid4.GetClosestPointForCloud(first, second, matches); },
"Grid4 单线程", 10);
EvaluateMatches(truth_matches, matches);
LOG(INFO) << "===================";
sad::evaluate_and_call(
[&first, &second, &grid4, &matches]() { grid4.GetClosestPointForCloudMT(first, second, matches); },
"Grid4 多线程", 10);
EvaluateMatches(truth_matches, matches);
LOG(INFO) << "===================";
sad::evaluate_and_call(
[&first, &second, &grid8, &matches]() { grid8.GetClosestPointForCloud(first, second, matches); },
"Grid8 单线程", 10);
EvaluateMatches(truth_matches, matches);
LOG(INFO) << "===================";
sad::evaluate_and_call(
[&first, &second, &grid8, &matches]() { grid8.GetClosestPointForCloudMT(first, second, matches); },
"Grid8 多线程", 10);
EvaluateMatches(truth_matches, matches);
LOG(INFO) << "===================";
sad::evaluate_and_call(
[&first, &second, &grid3, &matches]() { grid3.GetClosestPointForCloud(first, second, matches); },
"Grid 3D 单线程", 10);
EvaluateMatches(truth_matches, matches);
LOG(INFO) << "===================";
sad::evaluate_and_call(
[&first, &second, &grid3, &matches]() { grid3.GetClosestPointForCloudMT(first, second, matches); },
"Grid 3D 多线程", 10);
EvaluateMatches(truth_matches, matches);
SUCCEED();
}
TEST(CH5_TEST, KDTREE_BASICS) {
sad::CloudPtr cloud(new sad::PointCloudType);
sad::PointType p1, p2, p3, p4;
p1.x = 0;
p1.y = 0;
p1.z = 0;
p2.x = 1;
p2.y = 0;
p2.z = 0;
p3.x = 0;
p3.y = 1;
p3.z = 0;
p4.x = 1;
p4.y = 1;
p4.z = 0;
cloud->points.push_back(p1);
cloud->points.push_back(p2);
cloud->points.push_back(p3);
cloud->points.push_back(p4);
sad::KdTree kdtree;
kdtree.BuildTree(cloud);
kdtree.PrintAll();
SUCCEED();
}
TEST(CH5_TEST, KDTREE_KNN) {
sad::CloudPtr first(new sad::PointCloudType), second(new sad::PointCloudType);
pcl::io::loadPCDFile(FLAGS_first_scan_path, *first);
pcl::io::loadPCDFile(FLAGS_second_scan_path, *second);
if (first->empty() || second->empty()) {
LOG(ERROR) << "cannot load cloud";
FAIL();
}
// voxel grid 至 0.05
sad::VoxelGrid(first);
sad::VoxelGrid(second);
sad::KdTree kdtree;
sad::evaluate_and_call([&first, &kdtree]() { kdtree.BuildTree(first); }, "Kd Tree build", 1);
kdtree.SetEnableANN(true, FLAGS_ANN_alpha);
LOG(INFO) << "Kd tree leaves: " << kdtree.size() << ", points: " << first->size();
// 比较 bfnn
std::vector<std::pair<size_t, size_t>> true_matches;
sad::bfnn_cloud_mt_k(first, second, true_matches);
// 对第2个点云执行knn
std::vector<std::pair<size_t, size_t>> matches;
sad::evaluate_and_call([&first, &second, &kdtree, &matches]() { kdtree.GetClosestPointMT(second, matches, 5); },
"Kd Tree 5NN 多线程", 1);
EvaluateMatches(true_matches, matches);
LOG(INFO) << "building kdtree pcl";
// 对比PCL
pcl::search::KdTree<sad::PointType> kdtree_pcl;
sad::evaluate_and_call([&first, &kdtree_pcl]() { kdtree_pcl.setInputCloud(first); }, "Kd Tree build", 1);
LOG(INFO) << "searching pcl";
matches.clear();
std::vector<int> search_indices(second->size());
for (int i = 0; i < second->points.size(); i++) {
search_indices[i] = i;
}
std::vector<std::vector<int>> result_index;
std::vector<std::vector<float>> result_distance;
sad::evaluate_and_call(
[&]() { kdtree_pcl.nearestKSearch(*second, search_indices, 5, result_index, result_distance); },
"Kd Tree 5NN in PCL", 1);
for (int i = 0; i < second->points.size(); i++) {
for (int j = 0; j < result_index[i].size(); ++j) {
int m = result_index[i][j];
double d = result_distance[i][j];
matches.push_back({m, i});
}
}
EvaluateMatches(true_matches, matches);
LOG(INFO) << "done.";
SUCCEED();
}
TEST(CH5_TEST, OCTREE_BASICS) {
sad::CloudPtr cloud(new sad::PointCloudType);
sad::PointType p1, p2, p3, p4;
p1.x = 0;
p1.y = 0;
p1.z = 0;
p2.x = 1;
p2.y = 0;
p2.z = 0;
p3.x = 0;
p3.y = 1;
p3.z = 0;
p4.x = 1;
p4.y = 1;
p4.z = 0;
cloud->points.push_back(p1);
cloud->points.push_back(p2);
cloud->points.push_back(p3);
cloud->points.push_back(p4);
sad::OctoTree octree;
octree.BuildTree(cloud);
octree.SetApproximate(false);
LOG(INFO) << "Octo tree leaves: " << octree.size() << ", points: " << cloud->size();
SUCCEED();
}
TEST(CH5_TEST, OCTREE_KNN) {
sad::CloudPtr first(new sad::PointCloudType), second(new sad::PointCloudType);
pcl::io::loadPCDFile(FLAGS_first_scan_path, *first);
pcl::io::loadPCDFile(FLAGS_second_scan_path, *second);
if (first->empty() || second->empty()) {
LOG(ERROR) << "cannot load cloud";
FAIL();
}
// voxel grid 至 0.05
sad::VoxelGrid(first);
sad::VoxelGrid(second);
sad::OctoTree octree;
sad::evaluate_and_call([&first, &octree]() { octree.BuildTree(first); }, "Octo Tree build", 1);
octree.SetApproximate(true, FLAGS_ANN_alpha);
LOG(INFO) << "Octo tree leaves: " << octree.size() << ", points: " << first->size();
/// 测试KNN
LOG(INFO) << "testing knn";
std::vector<std::pair<size_t, size_t>> matches;
sad::evaluate_and_call([&first, &second, &octree, &matches]() { octree.GetClosestPointMT(second, matches, 5); },
"Octo Tree 5NN 多线程", 1);
LOG(INFO) << "comparing with bfnn";
/// 比较真值
std::vector<std::pair<size_t, size_t>> true_matches;
sad::bfnn_cloud_mt_k(first, second, true_matches);
EvaluateMatches(true_matches, matches);
LOG(INFO) << "done.";
SUCCEED();
}
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
FLAGS_stderrthreshold = google::INFO;
FLAGS_colorlogtostderr = true;
testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,93 @@
add_library(${PROJECT_NAME}.ch6
icp_2d.cc
lidar_2d_utils.cc
likelihood_field.cc
occupancy_map.cc
submap.cc
mapping_2d.cc
multi_resolution_likelihood_field.cc
loop_closing.cc
frame.cc
)
target_link_libraries(${PROJECT_NAME}.ch6
${PCL_LIBRARIES}
${PROJECT_NAME}.common
)
add_executable(test_2dlidar_io
test_2dlidar_io.cc
)
target_link_libraries(test_2dlidar_io
${PROJECT_NAME}.ch6
${OpenCV_LIBS}
${catkin_LIBRARIES}
glog gflags
${PCL_LIBRARIES}
${third_party_libs}
)
add_executable(test_2d_icp_s2s
test_2d_icp_s2s.cc
)
target_link_libraries(test_2d_icp_s2s
${PROJECT_NAME}.ch6
${OpenCV_LIBS}
${catkin_LIBRARIES}
glog gflags
${PCL_LIBRARIES}
)
add_executable(test_2d_icp_likelihood
test_2d_icp_likelihood.cc
)
target_link_libraries(test_2d_icp_likelihood
${PROJECT_NAME}.ch6
${OpenCV_LIBS}
${catkin_LIBRARIES}
glog gflags
${PCL_LIBRARIES}
${g2o_libs}
)
add_executable(test_occupancy_grid
test_occupancy_grid.cc
)
target_link_libraries(test_occupancy_grid
${PROJECT_NAME}.ch6
${OpenCV_LIBS}
${catkin_LIBRARIES}
glog gflags
${PCL_LIBRARIES}
${g2o_libs}
)
add_executable(test_2d_mapping
test_2d_mapping.cc
)
target_link_libraries(test_2d_mapping
${PROJECT_NAME}.ch6
${OpenCV_LIBS}
${catkin_LIBRARIES}
glog gflags
${PCL_LIBRARIES}
${g2o_libs}
)
add_executable(test_mr_matching
test_mr_matching.cc
)
target_link_libraries(test_mr_matching
${PROJECT_NAME}.ch6
${OpenCV_LIBS}
${catkin_LIBRARIES}
glog gflags
${PCL_LIBRARIES}
${g2o_libs}
)

View File

@@ -0,0 +1,47 @@
//
// Created by xiang on 2022/4/7.
//
#include "frame.h"
#include <glog/logging.h>
#include <fstream>
namespace sad {
void Frame::Dump(const std::string& filename) {
std::ofstream fout(filename);
fout << id_ << " " << keyframe_id_ << " " << timestamp_ << std::endl;
fout << pose_.translation()[0] << " " << pose_.translation()[1] << " " << pose_.so2().log() << std::endl;
fout << scan_->angle_min << " " << scan_->angle_max << " " << scan_->angle_increment << " " << scan_->range_min
<< " " << scan_->range_max << " " << scan_->ranges.size() << std::endl;
for (auto& r : scan_->ranges) {
fout << r << " ";
}
fout.close();
}
void Frame::Load(const std::string& filename) {
std::ifstream fin(filename);
if (!fin) {
LOG(ERROR) << "cannot load from " << filename;
return;
}
fin >> id_ >> keyframe_id_ >> timestamp_ >> pose_.translation()[0] >> pose_.translation()[1];
double theta = 0;
fin >> theta;
pose_.so2() = SO2::exp(theta);
scan_.reset(new Scan2d);
fin >> scan_->angle_min >> scan_->angle_max >> scan_->angle_increment >> scan_->range_min >> scan_->range_max;
int range_size;
fin >> range_size;
for (int i = 0; i < range_size; ++i) {
double r;
fin >> r;
scan_->ranges.emplace_back(r);
}
}
} // namespace sad

View File

@@ -0,0 +1,36 @@
//
// Created by xiang on 2022/3/23.
//
#ifndef SLAM_IN_AUTO_DRIVING_FRAME_H
#define SLAM_IN_AUTO_DRIVING_FRAME_H
#include "common/eigen_types.h"
#include "common/lidar_utils.h"
namespace sad {
/**
* 一次2D lidar扫描
*/
struct Frame {
Frame() {}
Frame(Scan2d::Ptr scan) : scan_(scan) {}
/// 将当前帧存入文本文件以供离线调用
void Dump(const std::string& filename);
/// 从文件读取frame数据
void Load(const std::string& filename);
size_t id_ = 0; // scan id
size_t keyframe_id_ = 0; // 关键帧 id
double timestamp_ = 0; // 时间戳,一般不用
Scan2d::Ptr scan_ = nullptr; // 激光扫描数据
SE2 pose_; // 位姿world to scan, T_w_c
SE2 pose_submap_; // 位姿submap to scan, T_s_c
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_FRAME_H

View File

@@ -0,0 +1,133 @@
//
// Created by xiang on 2022/3/22.
//
#ifndef SLAM_IN_AUTO_DRIVING_G2O_TYPES_H
#define SLAM_IN_AUTO_DRIVING_G2O_TYPES_H
#include <g2o/core/base_binary_edge.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/base_vertex.h>
#include <glog/logging.h>
#include <opencv2/core.hpp>
#include "common/eigen_types.h"
#include "common/math_utils.h"
namespace sad {
class VertexSE2 : public g2o::BaseVertex<3, SE2> {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
void setToOriginImpl() override { _estimate = SE2(); }
void oplusImpl(const double* update) override {
_estimate.translation()[0] += update[0];
_estimate.translation()[1] += update[1];
_estimate.so2() = _estimate.so2() * SO2::exp(update[2]);
}
bool read(std::istream& is) override { return true; }
bool write(std::ostream& os) const override { return true; }
};
class EdgeSE2LikelihoodFiled : public g2o::BaseUnaryEdge<1, double, VertexSE2> {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
EdgeSE2LikelihoodFiled(const cv::Mat& field_image, double range, double angle, float resolution = 10.0)
: field_image_(field_image), range_(range), angle_(angle), resolution_(resolution) {}
/// 判定此条边是否在field image外面
bool IsOutSide() {
VertexSE2* v = (VertexSE2*)_vertices[0];
SE2 pose = v->estimate();
Vec2d pw = pose * Vec2d(range_ * std::cos(angle_), range_ * std::sin(angle_));
Vec2i pf = (pw * resolution_ + Vec2d(field_image_.rows / 2, field_image_.cols / 2)).cast<int>(); // 图像坐标
if (pf[0] >= image_boarder_ && pf[0] < field_image_.cols - image_boarder_ && pf[1] >= image_boarder_ &&
pf[1] < field_image_.rows - image_boarder_) {
return false;
} else {
return true;
}
}
void computeError() override {
VertexSE2* v = (VertexSE2*)_vertices[0];
SE2 pose = v->estimate();
Vec2d pw = pose * Vec2d(range_ * std::cos(angle_), range_ * std::sin(angle_));
Vec2d pf =
pw * resolution_ + Vec2d(field_image_.rows / 2, field_image_.cols / 2) - Vec2d(0.5, 0.5); // 图像坐标
if (pf[0] >= image_boarder_ && pf[0] < field_image_.cols - image_boarder_ && pf[1] >= image_boarder_ &&
pf[1] < field_image_.rows - image_boarder_) {
_error[0] = math::GetPixelValue<float>(field_image_, pf[0], pf[1]);
} else {
_error[0] = 0;
setLevel(1);
}
}
void linearizeOplus() override {
VertexSE2* v = (VertexSE2*)_vertices[0];
SE2 pose = v->estimate();
float theta = pose.so2().log();
Vec2d pw = pose * Vec2d(range_ * std::cos(angle_), range_ * std::sin(angle_));
Vec2d pf =
pw * resolution_ + Vec2d(field_image_.rows / 2, field_image_.cols / 2) - Vec2d(0.5, 0.5); // 图像坐标
if (pf[0] >= image_boarder_ && pf[0] < field_image_.cols - image_boarder_ && pf[1] >= image_boarder_ &&
pf[1] < field_image_.rows - image_boarder_) {
// 图像梯度
float dx = 0.5 * (math::GetPixelValue<float>(field_image_, pf[0] + 1, pf[1]) -
math::GetPixelValue<float>(field_image_, pf[0] - 1, pf[1]));
float dy = 0.5 * (math::GetPixelValue<float>(field_image_, pf[0], pf[1] + 1) -
math::GetPixelValue<float>(field_image_, pf[0], pf[1] - 1));
_jacobianOplusXi << resolution_ * dx, resolution_ * dy,
-resolution_ * dx * range_ * std::sin(angle_ + theta) +
resolution_ * dy * range_ * std::cos(angle_ + theta);
} else {
_jacobianOplusXi.setZero();
setLevel(1);
}
}
bool read(std::istream& is) override { return true; }
bool write(std::ostream& os) const override { return true; }
private:
const cv::Mat& field_image_;
double range_ = 0;
double angle_ = 0;
float resolution_ = 10.0;
inline static const int image_boarder_ = 10;
};
/**
* SE2 pose graph使用
* error = v1.inv * v2 * meas.inv
*/
class EdgeSE2 : public g2o::BaseBinaryEdge<3, SE2, VertexSE2, VertexSE2> {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
EdgeSE2() {}
void computeError() override {
VertexSE2* v1 = (VertexSE2*)_vertices[0];
VertexSE2* v2 = (VertexSE2*)_vertices[1];
_error = (v1->estimate().inverse() * v2->estimate() * measurement().inverse()).log();
}
// TODO jacobian
bool read(std::istream& is) override { return true; }
bool write(std::ostream& os) const override { return true; }
private:
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_G2O_TYPES_H

View File

@@ -0,0 +1,204 @@
//
// Created by xiang on 2022/3/15.
//
#include "ch6/icp_2d.h"
#include "common/math_utils.h"
#include <glog/logging.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/search/impl/kdtree.hpp>
namespace sad {
bool Icp2d::AlignGaussNewton(SE2& init_pose) {
int iterations = 10;
double cost = 0, lastCost = 0;
SE2 current_pose = init_pose;
const float max_dis2 = 0.01; // 最近邻时的最远距离(平方)
const int min_effect_pts = 20; // 最小有效点数
for (int iter = 0; iter < iterations; ++iter) {
Mat3d H = Mat3d::Zero();
Vec3d b = Vec3d::Zero();
cost = 0;
int effective_num = 0; // 有效点数
// 遍历source
for (size_t i = 0; i < source_scan_->ranges.size(); ++i) {
float r = source_scan_->ranges[i];
if (r < source_scan_->range_min || r > source_scan_->range_max) {
continue;
}
float angle = source_scan_->angle_min + i * source_scan_->angle_increment;
float theta = current_pose.so2().log();
Vec2d pw = current_pose * Vec2d(r * std::cos(angle), r * std::sin(angle));
Point2d pt;
pt.x = pw.x();
pt.y = pw.y();
// 最近邻
std::vector<int> nn_idx;
std::vector<float> dis;
kdtree_.nearestKSearch(pt, 1, nn_idx, dis);
if (nn_idx.size() > 0 && dis[0] < max_dis2) {
effective_num++;
Mat32d J;
J << 1, 0, 0, 1, -r * std::sin(angle + theta), r * std::cos(angle + theta);
H += J * J.transpose();
Vec2d e(pt.x - target_cloud_->points[nn_idx[0]].x, pt.y - target_cloud_->points[nn_idx[0]].y);
b += -J * e;
cost += e.dot(e);
}
}
if (effective_num < min_effect_pts) {
return false;
}
// solve for dx
Vec3d dx = H.ldlt().solve(b);
if (isnan(dx[0])) {
break;
}
cost /= effective_num;
if (iter > 0 && cost >= lastCost) {
break;
}
LOG(INFO) << "iter " << iter << " cost = " << cost << ", effect num: " << effective_num;
current_pose.translation() += dx.head<2>();
current_pose.so2() = current_pose.so2() * SO2::exp(dx[2]);
lastCost = cost;
}
init_pose = current_pose;
LOG(INFO) << "estimated pose: " << current_pose.translation().transpose()
<< ", theta: " << current_pose.so2().log();
return true;
}
bool Icp2d::AlignGaussNewtonPoint2Plane(SE2& init_pose) {
int iterations = 10;
double cost = 0, lastCost = 0;
SE2 current_pose = init_pose;
const float max_dis = 0.3; // 最近邻时的最远距离
const int min_effect_pts = 20; // 最小有效点数
for (int iter = 0; iter < iterations; ++iter) {
Mat3d H = Mat3d::Zero();
Vec3d b = Vec3d::Zero();
cost = 0;
int effective_num = 0; // 有效点数
// 遍历source
for (size_t i = 0; i < source_scan_->ranges.size(); ++i) {
float r = source_scan_->ranges[i];
if (r < source_scan_->range_min || r > source_scan_->range_max) {
continue;
}
float angle = source_scan_->angle_min + i * source_scan_->angle_increment;
float theta = current_pose.so2().log();
Vec2d pw = current_pose * Vec2d(r * std::cos(angle), r * std::sin(angle));
Point2d pt;
pt.x = pw.x();
pt.y = pw.y();
// 查找5个最近邻
std::vector<int> nn_idx;
std::vector<float> dis;
kdtree_.nearestKSearch(pt, 5, nn_idx, dis);
std::vector<Vec2d> effective_pts; // 有效点
for (int j = 0; j < nn_idx.size(); ++j) {
if (dis[j] < max_dis) {
effective_pts.emplace_back(
Vec2d(target_cloud_->points[nn_idx[j]].x, target_cloud_->points[nn_idx[j]].y));
}
}
if (effective_pts.size() < 3) {
continue;
}
// 拟合直线组装J、H和误差
Vec3d line_coeffs;
if (math::FitLine2D(effective_pts, line_coeffs)) {
effective_num++;
Vec3d J;
J << line_coeffs[0], line_coeffs[1],
-line_coeffs[0] * r * std::sin(angle + theta) + line_coeffs[1] * r * std::cos(angle + theta);
H += J * J.transpose();
double e = line_coeffs[0] * pw[0] + line_coeffs[1] * pw[1] + line_coeffs[2];
b += -J * e;
cost += e * e;
}
}
if (effective_num < min_effect_pts) {
return false;
}
// solve for dx
Vec3d dx = H.ldlt().solve(b);
if (isnan(dx[0])) {
break;
}
cost /= effective_num;
if (iter > 0 && cost >= lastCost) {
break;
}
LOG(INFO) << "iter " << iter << " cost = " << cost << ", effect num: " << effective_num;
current_pose.translation() += dx.head<2>();
current_pose.so2() = current_pose.so2() * SO2::exp(dx[2]);
lastCost = cost;
}
init_pose = current_pose;
LOG(INFO) << "estimated pose: " << current_pose.translation().transpose()
<< ", theta: " << current_pose.so2().log();
return true;
}
void Icp2d::BuildTargetKdTree() {
if (target_scan_ == nullptr) {
LOG(ERROR) << "target is not set";
return;
}
target_cloud_.reset(new Cloud2d);
for (size_t i = 0; i < target_scan_->ranges.size(); ++i) {
if (target_scan_->ranges[i] < target_scan_->range_min || target_scan_->ranges[i] > target_scan_->range_max) {
continue;
}
double real_angle = target_scan_->angle_min + i * target_scan_->angle_increment;
Point2d p;
p.x = target_scan_->ranges[i] * std::cos(real_angle);
p.y = target_scan_->ranges[i] * std::sin(real_angle);
target_cloud_->points.push_back(p);
}
target_cloud_->width = target_cloud_->points.size();
target_cloud_->is_dense = false;
kdtree_.setInputCloud(target_cloud_);
}
} // namespace sad

View File

@@ -0,0 +1,53 @@
//
// Created by xiang on 2022/3/15.
//
#ifndef SLAM_IN_AUTO_DRIVING_ICP_2D_H
#define SLAM_IN_AUTO_DRIVING_ICP_2D_H
#include "common/eigen_types.h"
#include "common/lidar_utils.h"
#include <pcl/search/kdtree.h>
namespace sad {
/**
* 第六章谈到的各种类型的ICP代码实现
* 用法先SetTarget, 此时构建target点云的KD树再SetSource然后调用Align*方法
*/
class Icp2d {
public:
using Point2d = pcl::PointXY;
using Cloud2d = pcl::PointCloud<Point2d>;
Icp2d() {}
/// 设置目标的Scan
void SetTarget(Scan2d::Ptr target) {
target_scan_ = target;
BuildTargetKdTree();
}
/// 设置被配准的Scan
void SetSource(Scan2d::Ptr source) { source_scan_ = source; }
/// 使用高斯牛顿法进行配准
bool AlignGaussNewton(SE2& init_pose);
/// 使用高斯牛顿法进行配准, Point-to-Plane
bool AlignGaussNewtonPoint2Plane(SE2& init_pose);
private:
// 建立目标点云的Kdtree
void BuildTargetKdTree();
pcl::search::KdTree<Point2d> kdtree_;
Cloud2d::Ptr target_cloud_; // PCL 形式的target cloud
Scan2d::Ptr target_scan_ = nullptr;
Scan2d::Ptr source_scan_ = nullptr;
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_ICP_2D_H

View File

@@ -0,0 +1,44 @@
//
// Created by xiang on 2022/3/15.
//
#include "ch6/lidar_2d_utils.h"
#include <opencv2/imgproc.hpp>
namespace sad {
void Visualize2DScan(Scan2d::Ptr scan, const SE2& pose, cv::Mat& image, const Vec3b& color, int image_size,
float resolution, const SE2& pose_submap) {
if (image.data == nullptr) {
image = cv::Mat(image_size, image_size, CV_8UC3, cv::Vec3b(255, 255, 255));
}
for (size_t i = 0; i < scan->ranges.size(); ++i) {
if (scan->ranges[i] < scan->range_min || scan->ranges[i] > scan->range_max) {
continue;
}
double real_angle = scan->angle_min + i * scan->angle_increment;
double x = scan->ranges[i] * std::cos(real_angle);
double y = scan->ranges[i] * std::sin(real_angle);
if (real_angle < scan->angle_min + 30 * M_PI / 180.0 || real_angle > scan->angle_max - 30 * M_PI / 180.0) {
continue;
}
Vec2d psubmap = pose_submap.inverse() * (pose * Vec2d(x, y));
int image_x = int(psubmap[0] * resolution + image_size / 2);
int image_y = int(psubmap[1] * resolution + image_size / 2);
if (image_x >= 0 && image_x < image.cols && image_y >= 0 && image_y < image.rows) {
image.at<cv::Vec3b>(image_y, image_x) = cv::Vec3b(color[0], color[1], color[2]);
}
}
// 同时画出pose自身所在位置
Vec2d pose_in_image =
pose_submap.inverse() * (pose.translation()) * double(resolution) + Vec2d(image_size / 2, image_size / 2);
cv::circle(image, cv::Point2f(pose_in_image[0], pose_in_image[1]), 5, cv::Scalar(color[0], color[1], color[2]), 2);
}
} // namespace sad

View File

@@ -0,0 +1,30 @@
//
// Created by xiang on 2022/3/15.
//
#ifndef SLAM_IN_AUTO_DRIVING_LIDAR_2D_UTILS_H
#define SLAM_IN_AUTO_DRIVING_LIDAR_2D_UTILS_H
#include "common/eigen_types.h"
#include "common/lidar_utils.h"
#include <opencv2/core/core.hpp>
/// 为2D lidar的一些辅助函数
namespace sad {
/**
* 在image上绘制一个2D scan
* @param scan
* @param pose
* @param image
* @param image_size 图片大小
* @param resolution 分辨率,一米多少个像素
* @param pose_submap 如果是子地图提供子地图的pose
*/
void Visualize2DScan(Scan2d::Ptr scan, const SE2& pose, cv::Mat& image, const Vec3b& color, int image_size = 800,
float resolution = 20.0, const SE2& pose_submap = SE2());
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_LIDAR_2D_UTILS_H

View File

@@ -0,0 +1,230 @@
//
// Created by xiang on 2022/3/18.
//
#include "ch6/g2o_types.h"
#include "ch6/likelihood_filed.h"
#include <glog/logging.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/core/robust_kernel.h>
#include <g2o/core/robust_kernel_impl.h>
#include <g2o/core/sparse_optimizer.h>
#include <g2o/solvers/cholmod/linear_solver_cholmod.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
namespace sad {
void LikelihoodField::SetTargetScan(Scan2d::Ptr scan) {
target_ = scan;
// 在target点上生成场函数
field_ = cv::Mat(1000, 1000, CV_32F, 30.0);
for (size_t i = 0; i < scan->ranges.size(); ++i) {
if (scan->ranges[i] < scan->range_min || scan->ranges[i] > scan->range_max) {
continue;
}
double real_angle = scan->angle_min + i * scan->angle_increment;
double x = scan->ranges[i] * std::cos(real_angle) * resolution_ + 500;
double y = scan->ranges[i] * std::sin(real_angle) * resolution_ + 500;
// 在(x,y)附近填入场函数
for (auto& model_pt : model_) {
int xx = int(x + model_pt.dx_);
int yy = int(y + model_pt.dy_);
if (xx >= 0 && xx < field_.cols && yy >= 0 && yy < field_.rows &&
field_.at<float>(yy, xx) > model_pt.residual_) {
field_.at<float>(yy, xx) = model_pt.residual_;
}
}
}
}
void LikelihoodField::BuildModel() {
const int range = 20; // 生成多少个像素的模板
for (int x = -range; x <= range; ++x) {
for (int y = -range; y <= range; ++y) {
model_.emplace_back(x, y, std::sqrt((x * x) + (y * y)));
}
}
}
void LikelihoodField::SetSourceScan(Scan2d::Ptr scan) { source_ = scan; }
bool LikelihoodField::AlignGaussNewton(SE2& init_pose) {
int iterations = 10;
double cost = 0, lastCost = 0;
SE2 current_pose = init_pose;
const int min_effect_pts = 20; // 最小有效点数
const int image_boarder = 20; // 预留图像边界
has_outside_pts_ = false;
for (int iter = 0; iter < iterations; ++iter) {
Mat3d H = Mat3d::Zero();
Vec3d b = Vec3d::Zero();
cost = 0;
int effective_num = 0; // 有效点数
// 遍历source
for (size_t i = 0; i < source_->ranges.size(); ++i) {
float r = source_->ranges[i];
if (r < source_->range_min || r > source_->range_max) {
continue;
}
float angle = source_->angle_min + i * source_->angle_increment;
if (angle < source_->angle_min + 30 * M_PI / 180.0 || angle > source_->angle_max - 30 * M_PI / 180.0) {
continue;
}
float theta = current_pose.so2().log();
Vec2d pw = current_pose * Vec2d(r * std::cos(angle), r * std::sin(angle));
// 在field中的图像坐标
Vec2i pf = (pw * resolution_ + Vec2d(500, 500)).cast<int>();
if (pf[0] >= image_boarder && pf[0] < field_.cols - image_boarder && pf[1] >= image_boarder &&
pf[1] < field_.rows - image_boarder) {
effective_num++;
// 图像梯度
float dx = 0.5 * (field_.at<float>(pf[1], pf[0] + 1) - field_.at<float>(pf[1], pf[0] - 1));
float dy = 0.5 * (field_.at<float>(pf[1] + 1, pf[0]) - field_.at<float>(pf[1] - 1, pf[0]));
Vec3d J;
J << resolution_ * dx, resolution_ * dy,
-resolution_ * dx * r * std::sin(angle + theta) + resolution_ * dy * r * std::cos(angle + theta);
H += J * J.transpose();
float e = field_.at<float>(pf[1], pf[0]);
b += -J * e;
cost += e * e;
} else {
has_outside_pts_ = true;
}
}
if (effective_num < min_effect_pts) {
return false;
}
// solve for dx
Vec3d dx = H.ldlt().solve(b);
if (isnan(dx[0])) {
break;
}
cost /= effective_num;
if (iter > 0 && cost >= lastCost) {
break;
}
LOG(INFO) << "iter " << iter << " cost = " << cost << ", effect num: " << effective_num;
current_pose.translation() += dx.head<2>();
current_pose.so2() = current_pose.so2() * SO2::exp(dx[2]);
lastCost = cost;
}
init_pose = current_pose;
return true;
}
cv::Mat LikelihoodField::GetFieldImage() {
cv::Mat image(field_.rows, field_.cols, CV_8UC3);
for (int x = 0; x < field_.cols; ++x) {
for (int y = 0; y < field_.rows; ++y) {
float r = field_.at<float>(y, x) * 255.0 / 30.0;
image.at<cv::Vec3b>(y, x) = cv::Vec3b(uchar(r), uchar(r), uchar(r));
}
}
return image;
}
bool LikelihoodField::AlignG2O(SE2& init_pose) {
using BlockSolverType = g2o::BlockSolver<g2o::BlockSolverTraits<3, 1>>;
using LinearSolverType = g2o::LinearSolverCholmod<BlockSolverType::PoseMatrixType>;
auto* solver = new g2o::OptimizationAlgorithmLevenberg(
g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));
g2o::SparseOptimizer optimizer;
optimizer.setAlgorithm(solver);
auto* v = new VertexSE2();
v->setId(0);
v->setEstimate(init_pose);
optimizer.addVertex(v);
const double range_th = 15.0; // 不考虑太远的scan不准
const double rk_delta = 0.8;
has_outside_pts_ = false;
// 遍历source
for (size_t i = 0; i < source_->ranges.size(); ++i) {
float r = source_->ranges[i];
if (r < source_->range_min || r > source_->range_max) {
continue;
}
if (r > range_th) {
continue;
}
float angle = source_->angle_min + i * source_->angle_increment;
if (angle < source_->angle_min + 30 * M_PI / 180.0 || angle > source_->angle_max - 30 * M_PI / 180.0) {
continue;
}
auto e = new EdgeSE2LikelihoodFiled(field_, r, angle, resolution_);
e->setVertex(0, v);
if (e->IsOutSide()) {
has_outside_pts_ = true;
delete e;
continue;
}
e->setInformation(Eigen::Matrix<double, 1, 1>::Identity());
auto rk = new g2o::RobustKernelHuber;
rk->setDelta(rk_delta);
e->setRobustKernel(rk);
optimizer.addEdge(e);
}
optimizer.setVerbose(false);
optimizer.initializeOptimization();
optimizer.optimize(10);
init_pose = v->estimate();
return true;
}
void LikelihoodField::SetFieldImageFromOccuMap(const cv::Mat& occu_map) {
const int boarder = 25;
field_ = cv::Mat(1000, 1000, CV_32F, 30.0);
for (int x = boarder; x < occu_map.cols - boarder; ++x) {
for (int y = boarder; y < occu_map.rows - boarder; ++y) {
if (occu_map.at<uchar>(y, x) < 127) {
// 在该点生成一个model
for (auto& model_pt : model_) {
int xx = int(x + model_pt.dx_);
int yy = int(y + model_pt.dy_);
if (xx >= 0 && xx < field_.cols && yy >= 0 && yy < field_.rows &&
field_.at<float>(yy, xx) > model_pt.residual_) {
field_.at<float>(yy, xx) = model_pt.residual_;
}
}
}
}
}
}
} // namespace sad

View File

@@ -0,0 +1,70 @@
//
// Created by xiang on 2022/3/18.
//
#ifndef SLAM_IN_AUTO_DRIVING_LIKELIHOOD_FILED_H
#define SLAM_IN_AUTO_DRIVING_LIKELIHOOD_FILED_H
#include <opencv2/core.hpp>
#include "common/eigen_types.h"
#include "common/lidar_utils.h"
namespace sad {
class LikelihoodField {
public:
/// 2D 场的模板在设置target scan或map的时候生成
struct ModelPoint {
ModelPoint(int dx, int dy, float res) : dx_(dx), dy_(dy), residual_(res) {}
int dx_ = 0;
int dy_ = 0;
float residual_ = 0;
};
LikelihoodField() { BuildModel(); }
/// 增加一个2D的目标scan
void SetTargetScan(Scan2d::Ptr scan);
/// 设置被配准的那个scan
void SetSourceScan(Scan2d::Ptr scan);
/// 从占据栅格地图生成一个似然场地图
void SetFieldImageFromOccuMap(const cv::Mat& occu_map);
/// 使用高斯牛顿法配准
bool AlignGaussNewton(SE2& init_pose);
/**
* 使用g2o配准
* @param init_pose 初始位姿 NOTE 使用submap时给定相对于该submap的位姿估计结果也是针对于这个submap的位姿
* @return
*/
bool AlignG2O(SE2& init_pose);
/// 获取场函数转换为RGB图像
cv::Mat GetFieldImage();
bool HasOutsidePoints() const { return has_outside_pts_; }
void SetPose(const SE2& pose) { pose_ = pose; }
private:
void BuildModel();
SE2 pose_; // T_W_S
Scan2d::Ptr target_ = nullptr;
Scan2d::Ptr source_ = nullptr;
std::vector<ModelPoint> model_; // 2D 模板
cv::Mat field_; // 场函数
bool has_outside_pts_ = false; // 是否含有出了这个场的点
// 参数配置
inline static const float resolution_ = 20; // 每米多少个像素
};
} // namespace sad
#endif // SLAM_IN_AUTO_DRIVING_LIKELIHOOD_FILED_H

View File

@@ -0,0 +1,223 @@
//
// Created by xiang on 2022/4/7.
//
#include "loop_closing.h"
#include "ch6/g2o_types.h"
#include "lidar_2d_utils.h"
#include <glog/logging.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui.hpp>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/core/robust_kernel.h>
#include <g2o/core/robust_kernel_impl.h>
#include <g2o/core/sparse_optimizer.h>
#include <g2o/solvers/cholmod/linear_solver_cholmod.h>
#include <opencv2/opencv.hpp>
namespace sad {
void LoopClosing::AddFinishedSubmap(std::shared_ptr<Submap> submap) {
auto mr_field = std::make_shared<MRLikelihoodField>();
mr_field->SetPose(submap->GetPose());
mr_field->SetFieldImageFromOccuMap(submap->GetOccuMap().GetOccupancyGrid());
submap_to_field_.emplace(submap, mr_field);
}
void LoopClosing::AddNewSubmap(std::shared_ptr<Submap> submap) {
submaps_.emplace(submap->GetId(), submap);
last_submap_id_ = submap->GetId();
}
void LoopClosing::AddNewFrame(std::shared_ptr<Frame> frame) {
current_frame_ = frame;
if (!DetectLoopCandidates()) {
return;
}
MatchInHistorySubmaps();
if (has_new_loops_) {
Optimize();
}
}
bool LoopClosing::DetectLoopCandidates() {
// 要求当前帧与历史submap有一定间隔
has_new_loops_ = false;
if (last_submap_id_ < submap_gap_) {
return false;
}
current_candidates_.clear();
for (auto& sp : submaps_) {
if ((last_submap_id_ - sp.first) <= submap_gap_) {
// 不检查最近的几个submap
continue;
}
// 如果这个submap和历史submap已经存在有效的关联也忽略之
auto hist_iter = loop_constraints_.find(std::pair<size_t, size_t>(sp.first, last_submap_id_));
if (hist_iter != loop_constraints_.end() && hist_iter->second.valid_) {
continue;
}
Vec2d center = sp.second->GetPose().translation();
Vec2d frame_pos = current_frame_->pose_.translation();
double dis = (center - frame_pos).norm();
if (dis < candidate_distance_th_) {
/// 如果这个frame离submap中心差距小于阈值则检查
LOG(INFO) << "taking " << current_frame_->keyframe_id_ << " with " << sp.first
<< ", last submap id: " << last_submap_id_;
current_candidates_.emplace_back(sp.first);
}
}
return !current_candidates_.empty();
}
void LoopClosing::MatchInHistorySubmaps() {
// 我们先把要检查的scan, pose和submap存到离线文件, 把mr match调完了再实际上线
// current_frame_->Dump("./data/ch6/frame_" + std::to_string(current_frame_->id_) + ".txt");
for (const size_t& can : current_candidates_) {
auto mr = submap_to_field_.at(submaps_[can]);
mr->SetSourceScan(current_frame_->scan_);
auto submap = submaps_[can];
SE2 pose_in_target_submap = submap->GetPose().inverse() * current_frame_->pose_; // T_S1_C
if (mr->AlignG2O(pose_in_target_submap)) {
// set constraints from current submap to target submap
// T_S1_S2 = T_S1_C * T_C_W * T_W_S2
SE2 T_this_cur =
pose_in_target_submap * current_frame_->pose_.inverse() * submaps_[last_submap_id_]->GetPose();
loop_constraints_.emplace(std::pair<size_t, size_t>(can, last_submap_id_),
LoopConstraints(can, last_submap_id_, T_this_cur));
LOG(INFO) << "adding loop from submap " << can << " to " << last_submap_id_;
/// 可视化显示
auto occu_image = submap->GetOccuMap().GetOccupancyGridBlackWhite();
Visualize2DScan(current_frame_->scan_, pose_in_target_submap, occu_image, Vec3b(0, 0, 255), 1000, 20.0,
SE2());
cv::putText(occu_image, "loop submap " + std::to_string(submap->GetId()), cv::Point2f(20, 20),
cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(0, 255, 0));
cv::putText(occu_image, "keyframes " + std::to_string(submap->NumFrames()), cv::Point2f(20, 50),
cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(0, 255, 0));
cv::imshow("loop closure", occu_image);
has_new_loops_ = true;
}
debug_fout_ << current_frame_->id_ << " " << can << " " << submaps_[can]->GetPose().translation().x() << " "
<< submaps_[can]->GetPose().translation().y() << " " << submaps_[can]->GetPose().so2().log()
<< std::endl;
}
current_candidates_.clear();
}
void LoopClosing::Optimize() {
// pose graph optimization
using BlockSolverType = g2o::BlockSolver<g2o::BlockSolverTraits<3, 1>>;
using LinearSolverType = g2o::LinearSolverCholmod<BlockSolverType::PoseMatrixType>;
auto* solver = new g2o::OptimizationAlgorithmLevenberg(
g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));
g2o::SparseOptimizer optimizer;
optimizer.setAlgorithm(solver);
for (auto& sp : submaps_) {
auto* v = new VertexSE2();
v->setId(sp.first);
v->setEstimate(sp.second->GetPose());
optimizer.addVertex(v);
}
/// 连续约束
for (int i = 0; i < last_submap_id_; ++i) {
auto first_submap = submaps_[i];
auto next_submap = submaps_[i + 1];
EdgeSE2* e = new EdgeSE2();
e->setVertex(0, optimizer.vertex(i));
e->setVertex(1, optimizer.vertex(i + 1));
e->setMeasurement(first_submap->GetPose().inverse() * next_submap->GetPose());
e->setInformation(Mat3d::Identity() * 1e4);
optimizer.addEdge(e);
}
/// 回环约束
std::map<std::pair<size_t, size_t>, EdgeSE2*> loop_edges;
for (auto& c : loop_constraints_) {
if (!c.second.valid_) {
continue;
}
auto first_submap = submaps_[c.first.first];
auto second_submap = submaps_[c.first.second];
EdgeSE2* e = new EdgeSE2();
e->setVertex(0, optimizer.vertex(first_submap->GetId()));
e->setVertex(1, optimizer.vertex(second_submap->GetId()));
e->setMeasurement(c.second.T12_);
e->setInformation(Mat3d::Identity());
auto rk = new g2o::RobustKernelCauchy;
rk->setDelta(loop_rk_delta_);
e->setRobustKernel(rk);
optimizer.addEdge(e);
loop_edges.emplace(c.first, e);
}
optimizer.setVerbose(true);
optimizer.initializeOptimization();
optimizer.optimize(10);
// validate the loop constraints
int inliers = 0;
for (auto& ep : loop_edges) {
if (ep.second->chi2() < loop_rk_delta_) {
LOG(INFO) << "loop from " << ep.first.first << " to " << ep.first.second
<< " is correct, chi2: " << ep.second->chi2();
ep.second->setRobustKernel(nullptr);
loop_constraints_.at(ep.first).valid_ = true;
inliers++;
} else {
ep.second->setLevel(1);
LOG(INFO) << "loop from " << ep.first.first << " to " << ep.first.second
<< " is invalid, chi2: " << ep.second->chi2();
loop_constraints_.at(ep.first).valid_ = false;
}
}
optimizer.optimize(5);
for (auto& sp : submaps_) {
VertexSE2* v = (VertexSE2*)optimizer.vertex(sp.first);
sp.second->SetPose(v->estimate());
// 更新所属的frame world pose
sp.second->UpdateFramePoseWorld();
}
LOG(INFO) << "loop inliers: " << inliers << "/" << loop_constraints_.size();
// 移除错误的匹配
for (auto iter = loop_constraints_.begin(); iter != loop_constraints_.end();) {
if (!iter->second.valid_) {
iter = loop_constraints_.erase(iter);
} else {
iter++;
}
}
}
} // namespace sad

Some files were not shown because too many files have changed in this diff Show More