Repository: mpkuse/cerebro Branch: master Commit: 1dea0b09e12e Files: 154 Total size: 1.9 MB Directory structure: gitextract_xwomkigw/ ├── .gitignore ├── CMakeLists.txt ├── README.md ├── config/ │ ├── echo__terminator_config_vins_fusion_cerebro │ ├── euroc/ │ │ └── euroc_config_no_extrinsic.yaml │ ├── fly │ ├── mynteye/ │ │ ├── camera_left.yaml │ │ ├── camera_right.yaml │ │ ├── extrinsics.yaml │ │ └── mynteye_config.yaml │ ├── mynteye__terminator_config_vins_fusion_cerebro │ ├── mynteye_kannala_brandt/ │ │ ├── camera_left.yaml │ │ ├── camera_right.yaml │ │ ├── extrinsics.yaml │ │ └── mynteye_config.yaml │ ├── mynteye_pinhole/ │ │ ├── camera_left.yaml │ │ ├── camera_right.yaml │ │ ├── extrinsics.yaml │ │ └── mynteye_config.yaml │ ├── realsense__terminator_config_vins_fusion_cerebro │ ├── vinsfusion/ │ │ ├── djirosimu_realsense_d435i/ │ │ │ ├── README.md │ │ │ ├── extrinsics.yaml │ │ │ ├── left.yaml │ │ │ ├── realsense_stereo_imu_config.yaml │ │ │ └── right.yaml │ │ ├── euroc/ │ │ │ ├── cam0_pinhole.yaml │ │ │ ├── cam1_pinhole.yaml │ │ │ ├── euroc_stereo_imu_config.yaml │ │ │ └── extrinsics.yaml │ │ ├── mynteye/ │ │ │ ├── camera_left.yaml │ │ │ ├── camera_left_pinhole.yaml │ │ │ ├── camera_right.yaml │ │ │ ├── camera_right_pinhole.yaml │ │ │ ├── extrinsics.yaml │ │ │ └── mynteye_stereo_imu_config.yaml │ │ └── realsense_d435i/ │ │ ├── extrinsics.yaml │ │ ├── left.yaml │ │ ├── realsense_stereo_imu_config.yaml │ │ └── right.yaml │ └── vinsmono_debug_config.yaml ├── launch/ │ ├── debugging_vinsmono.launch │ ├── euroc_vinsfusion.launch │ ├── keras_server.launch │ ├── mynteye_vinsfusion.launch │ ├── mynteye_vinsfusion_loadstate.launch │ ├── mynteye_vinsfusion_ondrone.launch │ ├── mynteye_vinsfusion_savestate.launch │ ├── realsense_camera.launch │ ├── realsense_vinsfusion.launch │ ├── realsense_vinsfusion_ondrone_repeat.launch │ └── realsense_vinsfusion_ondrone_teach.launch ├── msg/ │ └── LoopEdge.msg ├── package.xml ├── rviz/ │ ├── dev-config.rviz │ ├── good-viz.rviz │ └── vins-fusion.rviz ├── scripts/ │ ├── TerminalColors.py │ ├── keras.models/ │ │ ├── .gitignore │ │ ├── Apr2019/ │ │ │ └── gray_conv6_K16__centeredinput/ │ │ │ ├── core_model.1000.keras │ │ │ └── model.json │ │ ├── June2019/ │ │ │ ├── centeredinput-m1to1-240x320x1__mobilenetv2-block_9_add__K16__allpairloss/ │ │ │ │ └── modelarch_and_weights.2000.h5 │ │ │ ├── centeredinput-m1to1-240x320x3__mobilenet-conv_pw_6_relu__K16__allpairloss/ │ │ │ │ └── modelarch_and_weights.700.h5 │ │ │ └── centeredinput-m1to1-240x320x3__mobilenetv2-block_9_add__K16__allpairloss/ │ │ │ └── modelarch_and_weights.800.h5 │ │ ├── README.md │ │ ├── core_model.keras │ │ ├── mobilenet_conv7_allpairloss.keras │ │ ├── model.json │ │ └── modelarch_and_weights.h5 │ ├── keras_helpers.py │ ├── predict_utils.py │ ├── unittest/ │ │ ├── demo_superpoint.py │ │ └── rtry_superpoint.py │ ├── whole_image_desc_compute_client.py │ └── whole_image_desc_compute_server.py ├── src/ │ ├── Cerebro.cpp │ ├── Cerebro.h │ ├── DataManager.cpp │ ├── DataManager.h │ ├── DataNode.cpp │ ├── DataNode.h │ ├── DlsPnpWithRansac.cpp │ ├── DlsPnpWithRansac.h │ ├── HypothesisManager.cpp │ ├── HypothesisManager.h │ ├── ImageDataManager.cpp │ ├── ImageDataManager.h │ ├── PNPCeresCostFunctions.h │ ├── PinholeCamera.cpp │ ├── PinholeCamera.h │ ├── ProcessedLoopCandidate.cpp │ ├── ProcessedLoopCandidate.h │ ├── Visualization.cpp │ ├── Visualization.h │ ├── cerebro_node.cpp │ ├── unittest/ │ │ ├── unittest_camera_geom_class_usage.cpp │ │ ├── unittest_camodocal_proj.cpp │ │ ├── unittest_plot2mat.cpp │ │ ├── unittest_pose_tester.cpp │ │ ├── unittest_rosservice_client.cpp │ │ ├── unittest_termcolor.cpp │ │ ├── unittest_theia.cpp │ │ └── unittest_theia_ransac.cpp │ └── utils/ │ ├── CameraGeometry.cpp │ ├── CameraGeometry.h │ ├── ElapsedTime.h │ ├── GMSMatcher/ │ │ ├── gms_matcher.cpp │ │ └── gms_matcher.h │ ├── MiscUtils.cpp │ ├── MiscUtils.h │ ├── Plot2Mat.cpp │ ├── Plot2Mat.h │ ├── PointFeatureMatching.cpp │ ├── PointFeatureMatching.h │ ├── PoseManipUtils.cpp │ ├── PoseManipUtils.h │ ├── RawFileIO.cpp │ ├── RawFileIO.h │ ├── RosMarkerUtils.cpp │ ├── RosMarkerUtils.h │ ├── SafeQueue.h │ ├── TermColor.h │ ├── camera_geometry_usage.cpp │ ├── camodocal/ │ │ ├── README.md │ │ ├── include/ │ │ │ └── camodocal/ │ │ │ ├── calib/ │ │ │ │ └── CameraCalibration.h │ │ │ ├── camera_models/ │ │ │ │ ├── Camera.h │ │ │ │ ├── CameraFactory.h │ │ │ │ ├── CataCamera.h │ │ │ │ ├── CostFunctionFactory.h │ │ │ │ ├── EquidistantCamera.h │ │ │ │ ├── PinholeCamera.h │ │ │ │ └── ScaramuzzaCamera.h │ │ │ ├── chessboard/ │ │ │ │ ├── Chessboard.h │ │ │ │ ├── ChessboardCorner.h │ │ │ │ ├── ChessboardQuad.h │ │ │ │ └── Spline.h │ │ │ ├── gpl/ │ │ │ │ ├── EigenQuaternionParameterization.h │ │ │ │ ├── EigenUtils.h │ │ │ │ └── gpl.h │ │ │ └── sparse_graph/ │ │ │ └── Transform.h │ │ └── src/ │ │ ├── calib/ │ │ │ └── CameraCalibration.cc │ │ ├── camera_models/ │ │ │ ├── Camera.cc │ │ │ ├── CameraFactory.cc │ │ │ ├── CataCamera.cc │ │ │ ├── CostFunctionFactory.cc │ │ │ ├── EquidistantCamera.cc │ │ │ ├── PinholeCamera.cc │ │ │ └── ScaramuzzaCamera.cc │ │ ├── chessboard/ │ │ │ └── Chessboard.cc │ │ ├── gpl/ │ │ │ ├── EigenQuaternionParameterization.cc │ │ │ └── gpl.cc │ │ ├── intrinsic_calib.cc │ │ └── sparse_graph/ │ │ └── Transform.cc │ └── nlohmann/ │ └── json.hpp └── srv/ └── WholeImageDescriptorCompute.srv ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.data *.pyc *.npy *.npz *.pth *.bak ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 2.8.3) project(cerebro) # Catkin components find_package(catkin REQUIRED COMPONENTS cv_bridge image_transport roscpp rospy std_msgs message_generation ) ## System dependencies are found with CMake's conventions # find_package(Boost REQUIRED COMPONENTS system) set( CMAKE_CXX_STANDARD 11 ) find_package(Threads) find_package(Eigen3 REQUIRED) find_package(Ceres REQUIRED) find_package(OpenCV 3 REQUIRED) #set( CMAKE_CXX_FLAGS "-fpermissive -std=c++11 -O3 -Wall -Wextra -pedantic" ) # Removing the dependence on theia for the production code. # If you want to compile unit tests, some of those need theia-sfm as dependence. find_package(Theia REQUIRED) include_directories(${THEIA_INCLUDE_DIRS}) find_package(Boost REQUIRED COMPONENTS filesystem program_options system) include_directories(${Boost_INCLUDE_DIRS}) #----------START gperftools # install : sudo apt-get install google-perftools libgoogle-perftools-dev # get cmake file: https://raw.githubusercontent.com/vast-io/vast/master/cmake/FindGperftools.cmake # save to '/usr/local/lib/cmake' #list(APPEND CMAKE_MODULE_PATH "/usr/local/lib/cmake") #find_package(Gperftools REQUIRED) #message( "----- GPERF") #message( "----- "${GPERFTOOLS_LIBRARIES} ) set (CMAKE_CXX_FLAGS " -Wl,-no-as-needed") #set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Release) #set(CMAKE_BUILD_TYPE RelWithDebInfo) #----------END GPERFTOOLS option(COMPILE_UNIT_TEST "Compile unit tests" OFF) #OFF by default. catkin_make -DCOMPILE_UNIT_TEST=ON will compile it. OFF will skip it ################################################ ## Declare ROS messages, services and actions ## ################################################ ## To declare and build messages, services or actions from within this ## package, follow these steps: ## * Let MSG_DEP_SET be the set of packages whose message types you use in ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). ## * In the file package.xml: ## * add a build_depend tag for "message_generation" ## * add a build_depend and a run_depend tag for each package in MSG_DEP_SET ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in ## but can be declared for certainty nonetheless: ## * add a run_depend tag for "message_runtime" ## * In this file (CMakeLists.txt): ## * add "message_generation" and every package in MSG_DEP_SET to ## find_package(catkin REQUIRED COMPONENTS ...) ## * add "message_runtime" and every package in MSG_DEP_SET to ## catkin_package(CATKIN_DEPENDS ...) ## * uncomment the add_*_files sections below as needed ## and list every .msg/.srv/.action file to be processed ## * uncomment the generate_messages entry below ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) ## Generate messages in the 'msg' folder add_message_files( FILES LoopEdge.msg ) ## Generate services in the 'srv' folder add_service_files( FILES WholeImageDescriptorCompute.srv ) ## Generate added messages and services with any dependencies listed here generate_messages( DEPENDENCIES std_msgs sensor_msgs ) ################################### ## catkin specific configuration ## ################################### ## The catkin_package macro generates cmake config files for your package ## Declare things to be passed to dependent projects ## INCLUDE_DIRS: uncomment this if you package contains header files ## LIBRARIES: libraries you create in this project that dependent projects also need ## CATKIN_DEPENDS: catkin_packages dependent projects also need ## DEPENDS: system dependencies of this project that dependent projects also need catkin_package( INCLUDE_DIRS include # LIBRARIES cerebro # CATKIN_DEPENDS cv_bridge image_transport roscpp rospy std_msgs # DEPENDS system_lib CATKIN_DEPENDS message_runtime ) ########### ## Build ## ########### ## Specify additional locations of header files ## Your package locations should be listed before other locations include_directories(include) include_directories( ${catkin_INCLUDE_DIRS} ${CERES_INCLUDE_DIRS} ${EIGEN3_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ) include_directories( src/utils/camodocal/include src/utils/ ) FILE(GLOB CamodocalCameraModelSources src/utils/camodocal/src/chessboard/Chessboard.cc src/utils/camodocal/src/calib/CameraCalibration.cc src/utils/camodocal/src/camera_models/Camera.cc src/utils/camodocal/src/camera_models/CameraFactory.cc src/utils/camodocal/src/camera_models/CostFunctionFactory.cc src/utils/camodocal/src/camera_models/PinholeCamera.cc src/utils/camodocal/src/camera_models/CataCamera.cc src/utils/camodocal/src/camera_models/EquidistantCamera.cc src/utils/camodocal/src/camera_models/ScaramuzzaCamera.cc src/utils/camodocal/src/sparse_graph/Transform.cc src/utils/camodocal/src/gpl/gpl.cc src/utils/camodocal/src/gpl/EigenQuaternionParameterization.cc ) FILE( GLOB KuseUtilsSources src/utils/PoseManipUtils.cpp src/utils/RosMarkerUtils.cpp src/utils/RawFileIO.cpp src/utils/MiscUtils.cpp src/utils/Plot2Mat.cpp src/utils/CameraGeometry.cpp src/utils/PointFeatureMatching.cpp src/utils/GMSMatcher/gms_matcher.cpp ) ## Declare a C++ executable add_executable( cerebro_node src/cerebro_node.cpp src/PinholeCamera.cpp src/DataManager.cpp src/DataNode.cpp src/Cerebro.cpp src/Visualization.cpp src/ProcessedLoopCandidate.cpp src/DlsPnpWithRansac.cpp src/HypothesisManager.cpp src/ImageDataManager.cpp ${KuseUtilsSources} ${CamodocalCameraModelSources} ) # this is needed to indicate this this executable depends on the messages of the pkg. add_dependencies(cerebro_node cerebro_generate_messages_cpp) target_link_libraries(cerebro_node ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${CERES_LIBRARIES} ${Boost_LIBRARIES} ${THEIA_LIBRARIES} #${GPERFTOOLS_LIBRARIES} #/usr/local/lib/libfaiss.so ) if(COMPILE_UNIT_TEST) #add some compilation flags ## Unit Tests add_executable ( unittest_termcolor src/unittest/unittest_termcolor.cpp ) add_executable ( unittest_rosservice_client src/unittest/unittest_rosservice_client.cpp ) add_executable ( unittest_camodocal_proj src/unittest/unittest_camodocal_proj.cpp ${KuseUtilsSources} ${CamodocalCameraModelSources} ) add_executable ( unittest_camera_geom_class_usage src/unittest/unittest_camera_geom_class_usage.cpp ${KuseUtilsSources} ${CamodocalCameraModelSources} ) add_executable ( unittest_theia src/unittest/unittest_theia.cpp ${KuseUtilsSources} ${CamodocalCameraModelSources} ) add_executable ( unittest_pose_tester src/unittest/unittest_pose_tester.cpp ${KuseUtilsSources} ${CamodocalCameraModelSources} src/DlsPnpWithRansac.cpp ) add_executable ( unittest_plot2mat src/unittest/unittest_plot2mat.cpp src/utils/Plot2Mat.cpp ) target_link_libraries(unittest_rosservice_client ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${CERES_LIBRARIES} ) target_link_libraries(unittest_camodocal_proj ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${CERES_LIBRARIES} ) target_link_libraries(unittest_camera_geom_class_usage ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${CERES_LIBRARIES} ) target_link_libraries(unittest_theia ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${CERES_LIBRARIES} ${THEIA_LIBRARIES} ) target_link_libraries(unittest_pose_tester ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${CERES_LIBRARIES} ${THEIA_LIBRARIES} ) target_link_libraries(unittest_plot2mat ${catkin_LIBRARIES} ${OpenCV_LIBRARIES} ) else() #add some other compilation flags endif(COMPILE_UNIT_TEST) unset(COMPILE_UNIT_TEST CACHE) # <---- this is the important!! ############# ## Install ## ############# # all install targets should use catkin DESTINATION variables # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html ## Mark executable scripts (Python etc.) for installation ## in contrast to setup.py, you can choose the destination # install(PROGRAMS # scripts/my_python_script # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # ) ## Mark executables and/or libraries for installation # install(TARGETS cerebro cerebro_node # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # ) ## Mark cpp header files for installation # install(DIRECTORY include/${PROJECT_NAME}/ # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} # FILES_MATCHING PATTERN "*.h" # PATTERN ".svn" EXCLUDE # ) ## Mark other files for installation (e.g. launch and bag files, etc.) # install(FILES # # myfile1 # # myfile2 # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} # ) ############# ## Testing ## ############# ## Add gtest based cpp test target and link libraries # catkin_add_gtest(${PROJECT_NAME}-test test/test_cerebro.cpp) # if(TARGET ${PROJECT_NAME}-test) # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) # endif() ## Add folders to be run by python nosetests # catkin_add_nosetests(test) ================================================ FILE: README.md ================================================ # VINS-Fusion with Cerebro This is the cerebro module for VINS-Fusion. The aim of this project is better loop detection and recover from kidnap. The cerebro node connects to the vins_estimator nodes of VINS-Fusion (with ros interface). The DataManager class handles all the incoming data. Visualization handles all the visualization. Cerebro class handles the loop closure intelligence part. It publishes a LoopMsg which contains timestamps of the identified loopcandidate along with the computed relative pose between the pair. The pose computation needs a stereo pair for reliable pose computation. This is a multi-threaded object oriented implementation and I observe a CPU load factor of about 2.0. A separate node handles pose graph solver (it is in [github-repo](https://github.com/mpkuse/solve_keyframe_pose_graph) ). This repo also support for saving the trajectories to file and later loading it from file for relocalization. See [launch/realsense_vinsfusion_ondrone_teach.launch](launch/realsense_vinsfusion_ondrone_teach.launch) on how to do it. If you use this work in your research, please cite: Manohar Kuse and Shaojie Shen, *“Learning Whole-Image Descriptors for Real-time Loop Detection and Kidnap Recovery under Large Viewpoint Difference“*, https://arxiv.org/abs/1904.06962 ## Highlight Video [![IMAGE ALT TEXT](http://img.youtube.com/vi/lDzDHZkInos/0.jpg)](http://www.youtube.com/watch?v=lDzDHZkInos "Video Title") ## MyntEye Demo (Using VINS-Fusion as Odometry Estimator) We showcase the kidnap recovery and relocalization. [![IMAGE ALT TEXT](http://img.youtube.com/vi/3YQF4_v7AEg/0.jpg)](http://www.youtube.com/watch?v=3YQF4_v7AEg "Video Title") [![IMAGE ALT TEXT](http://img.youtube.com/vi/sTd_rZdW4DQ/0.jpg)](http://www.youtube.com/watch?v=sTd_rZdW4DQ "Video Title") ## In Plane Rotation Test (Uses Realsense Camera) [![IMAGE ALT TEXT](http://img.youtube.com/vi/8bsRCNF2rnA/0.jpg)](http://www.youtube.com/watch?v=8bsRCNF2rnA "Video Title") ## Relocalization from Previously Constructed Trajectories. We show the accurary of the system by plotting a previously constructed surfel map (in gray color) and the newly constructed surfel map (rainbox colored). We can observe that the boxes for example in the environment overlap. Similarly other objects in the seen also overlap. [![IMAGE ALT TEXT](http://img.youtube.com/vi/OViEEB3rINo/0.jpg)](http://www.youtube.com/watch?v=OViEEB3rINo "Video Title") ## MyntEye Demo (Using VINS-Mono as Odometry Estimator) [![IMAGE ALT TEXT](http://img.youtube.com/vi/KDRo9LpL6Hs/0.jpg)](http://www.youtube.com/watch?v=KDRo9LpL6Hs "Video Title") [![IMAGE ALT TEXT](http://img.youtube.com/vi/XvoCrLFq99I/0.jpg)](http://www.youtube.com/watch?v=XvoCrLFq99I "Video Title") ## EuRoC MAV Dataset live merge MH-01, ... MH-05. [![IMAGE ALT TEXT](http://img.youtube.com/vi/mnnoAlAIsN8/0.jpg)](http://www.youtube.com/watch?v=mnnoAlAIsN8 "Video Title") ## EuRoC MAV Dataset live merge V1_01, V1_02, V1_03, V2_01, V2_02 [![IMAGE ALT TEXT](http://img.youtube.com/vi/rIaANkd74cQ/0.jpg)](http://www.youtube.com/watch?v=rIaANkd74cQ "Video Title") For more demonstration, have a look at my [youtube playlist](https://www.youtube.com/playlist?list=PLWyydx20vdPzs5VVhZu0TGsReT7U17Fxp) ## Visual-Inertial Datasets - [ETHZ EuroC Dataset](https://projects.asl.ethz.ch/datasets/doku.php?id=kmavvisualinertialdatasets) - [TUM Visual Inertial Dataset](https://vision.in.tum.de/data/datasets/visual-inertial-dataset) - UPenn - [Penncosyvio](https://github.com/daniilidis-group/penncosyvio) - [ADVIO](https://github.com/AaltoVision/ADVIO) ARKIT, Tango logging. - Our MyntEye (Stereo+IMU) [One-drive-link](https://hkustconnect-my.sharepoint.com/:f:/g/personal/mpkuse_connect_ust_hk/EkTisuLkXLFBs_WHYkxoH2oBeVIkdLc3-5a_t1J9c_4wkg?e=h9cifx) ## How to run - Docker I highly recommend the already deployed packages with docker. Run the roscore on your host pc and all the packages run inside of docker container. rviz runs on the host pc. I assume you have a PC with a graphics card and cuda9 working smoothly and nvidia-docker installed. ``` $(host) export ROS_HOSTNAME=`hostname` $(host) roscore # assume that host has the ip address 172.17.0.1 in docker-network aka docker0 $(host) docker run --runtime=nvidia -it --rm \ --add-host `hostname`:172.17.0.1 \ --env ROS_MASTER_URI=http://`hostname`:11311/ \ --env CUDA_VISIBLE_DEVICES=0 \ --hostname happy_go \ --name happy_go \ mpkuse/kusevisionkit:ros-kinetic-vins-tf-faiss bash $(host) rviz # inside rviz open config cerebro/config/good-viz.rviz. If you open rviz in a new tab you might need to do set ROS_HOSTNAME again. $(docker) roslaunch cerebro mynteye_vinsfusion.launch OR $(docker) roslaunch cerebro euroc_vinsfusion.launch $(host) rosbag play 1.bag ``` If you are unfamiliar with docker, you may want to read [my blog post](https://kusemanohar.wordpress.com/2018/10/03/docker-for-computer-vision-researchers/) on using docker for computer vision researchers. You might want to have a look at my test ros-package to ensure things work with docker [docker_ros_test](https://github.com/mpkuse/docker_ros_test). List of all my docker-images can be found [here](https://hub.docker.com/r/mpkuse/kusevisionkit). Other docker images that work: - mpkuse/kusevisionkit:ros-kinetic-vins-tf-faiss (preferred) - mpkuse/kusevisionkit:vins-kidnap ## How to compile (from scratch) You will need a) VINS-Fusion (with modification for reset by mpkuse), b) cerebro and c) solve_keyframe_pose_graph. Besure to setup a `catkin_ws` and make sure your ROS works correctly. ### Dependencies - ROS Kinetic - Eigen3 - Ceres - OpenCV3 (should also work with 2.4 (not tested), 3.3 (tested Ok) and 3.4 (tested ok)) - [Theia-sfm](http://theia-sfm.org/). *As of May2019 dependence on theia-sfm has been eliminated. The code is able to compile even when you do not have Theia-sfm installed. The pose estimation PNP/P3P has been implemented from scratch using ceres. * - [OpenImageIO](https://github.com/OpenImageIO/oiio) (Release-1.7.6RC1)
- [RocksDB](https://github.com/facebook/rocksdb) (v5.9.2)
- OpenExr
- Tensorflow (tested on 1.11.0) - Keras (atleast 2.2.4), I noticed issues in jsonmodel with 2.2.2, 2.2.2 still works though! ### Get VINS-Fusion Working I recommend you use my fork of VINS-Fusion, in which I have fixed some bugs and added mechanism for reseting the VINS. ``` cd catkin_ws/src #git clone https://github.com/HKUST-Aerial-Robotics/VINS-Fusion.git git clone https://github.com/mpkuse/VINS-Fusion cd ../ catkin_make source catkin_ws/devel/setup.bash ``` Make sure your vins-fusion can compile and run correctly. See vins-fusion github repo for the latest information on prerequisites and compilation instructions. For compatibility I recommend using my fork of vins-mono/vins-fusion. Some minor modifications have been made by me for working with kidnap cases. ### Cerebro ``` cd catkin_ws/src/ git clone https://github.com/mpkuse/cerebro cd ../ catkin_make ``` This has 2 exectables. **a)** ros server that takes as input an image and returns a image descriptor. **b)** cerebro_node, this finds the loop candidates and computes the relative poses. I have also included my trained model (about 4 MB) in this package (located scripts/keras.model). The pose computation uses the stereo pair in this node. This node publishes the loopcandidate's relative pose which is expected to be consumed the pose-graph solver. If you wish to train your own model, you may use [my learning code here](https://github.com/mpkuse/cartwheel_train). **Threads in cerebro_node:** - *Main Thread* : ros-callbacks - *data_association_th* : Sync the incoming image data and incoming data vins_estimator. - *desc_th* : Consumes the images to produce whole-image-descriptors. - *dot_product_th* : Dot product of current image descriptor with all the previous ones. - *loopcandidate_consumer_th* : Computes the relative pose at the loopcandidates. Publishes the loopEdge. - *kidnap_th* : Identifies kidnap. If kidnap publishes the reset signals for vins_estimator. - *viz_th* : Publishes the image-pair, and more things for debugging and analysis. - *dm_cleanup_th* : Deallocate/Store to file images to reduce RAM consumption. **Nvidia TX2**: Often times for live run, you might want to run the ros-server on a Nvidia-TX2. I recommend compiling tensorflow from scratch. The thing is prebuilt binaries may not be compatible with the version of CUDA and CUDNN on your device. Also some binaries may not be compatible to arm (could likely be built for x86). Before you can compile tensorflow you need java, bazel. See this [gist](https://gist.github.com/vellamike/7c26158c93e89ef155c1cc953bbba956). Also tools and repos from [jetsonhacks](https://github.com/jetsonhacks) might come in handy. ### Pose Graph Solver Use my pose graph solver, [github-repo](https://github.com/mpkuse/solve_keyframe_pose_graph). The differences between this implementation and the original from VINS-Fusion is that mine can handle kidnap cases, handles multiple world co-ordinate frames and it uses a switch-constraint formulation of the pose-graph problem. ``` cd catkin_ws/src/ git clone https://github.com/mpkuse/solve_keyframe_pose_graph cd ../ catkin_make ``` **Threads:** - *Main thread* : ros-callbacks for odometry poses (from vins_estimator) and LoopMsg (from cerebro). - *SLAM* : Monitors the node-poses and loop-edges, on new loop-edges constructs and solves the pose-graph optimization problem. - *th4* : Publish latest odometry. - *th5* : Display image to visualize disjoint set datastructure. - *th6* : Publish corrected poses, Different color for nodes in different co-ordinate systems. ### vins_mono_debug_pkg (optional, needed only if you wish to debug vins-mono/vins-fusion) With cerebro node it is possible to live run the vins and make it log all the details to file for further analysis/debugging. This might be useful for researchers and other Ph.D. student to help VINS-Fusion improve further. see [github/mpkuse/vins_mono](https://github.com/mpkuse/vins_mono_debug_pkg). It basically contains unit tests and some standalone tools which might come in handy. If you are looking to help improve VINS-fusion or cerebro also look at 'Development Guidelines'. ## How to run the Full System ``` roslaunch cerebro mynteye_vinsfusion.launch ``` You can get some of my bag files collected with the mynteye camera HERE. More example launch files in folder `launch`, all the config files which contains calibration info are in folder `config`. ## Development Guidelines If you are developing I still recommend using docker. with -v flags in docker you could mount your pc's folders on the docker. I recommend keeping all the packages in folder `docker_ws_slam/catkin_ws/src` on your host pc. And all the rosbags in folder `/media/mpkuse/Bulk_Data`. And then mount these two folders on the docker-container. Edit the following command as needed. ``` docker run --runtime=nvidia -it -v /media/mpkuse/Bulk_Data/:/Bulk_Data -v /home/mpkuse/docker_ws_slam:/app --add-host `hostname`:172.17.0.1 --env ROS_MASTER_URI=http://`hostname`:11311/ --env CUDA_VISIBLE_DEVICES=0 --hostname happy_go --name happy_go mpkuse/kusevisionkit:ros-kinetic-vins bash ``` Use the following if you wish to use xhost(gui) from inside docker ``` $(host) xhost +local:root $(host) docker run --runtime=nvidia -it -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix -v /media/mpkuse/Bulk_Data/:/Bulk_Data -v /home/mpkuse/docker_ws_slam:/app --add-host `hostname`:172.17.0.1 --env ROS_MASTER_URI=http://`hostname`:11311/ --env CUDA_VISIBLE_DEVICES=0 --hostname happy_go --name happy_go mpkuse/kusevisionkit:ros-kinetic-vins bash ``` Other docker images that work: - mpkuse/kusevisionkit:ros-kinetic-vins-tf-faiss (preferred) - mpkuse/kusevisionkit:vins-kidnap Each of my classes can export the data they hold as json objects and image files. Look at the end of `main()` in `cerebro_node.cpp` and modify as needed to extract more debug data. Similarly the pose graph solver can also be debugged. If writing a lot of debug data (usually taking more than 10sec) roslaunch force-kills the process. To get around this issue, you need to increase the timeout: ``` For kinetic this can be found in: /opt/ros/kinetic/lib/python2.7/dist-packages/roslaunch In the file nodeprocess.py there will be 2 variables on line 57 and 58. _TIMEOUT_SIGINT = 15.0 #seconds _TIMEOUT_SIGTERM = 2.0 #seconds ``` For streamlining printing messages I have preprocessor macros at the start of function implementation (of classes DataManager and Cerebro), read the comments there and edit as per need. Try to implement your algorithms in object oriented way and using the producer-consumer paradigm. Look at my thread-mains for example. Finally, sensible PR with bug fixes, enhancements are welcome! ![](doc/rosgraph.png) ## Authors Manohar Kuse ================================================ FILE: config/echo__terminator_config_vins_fusion_cerebro ================================================ [global_config] [keybindings] [layouts] [[default]] [[[child0]]] fullscreen = False last_active_term = 1cc3df64-f997-4766-9317-0ce75d5dc2cd last_active_window = True maximised = True order = 0 parent = "" position = 65:24 size = 1855, 1056 title = dji@MANIFOLD-2-C: ~ type = Window [[[child1]]] order = 0 parent = child0 position = 525 ratio = 0.5 type = VPaned [[[child2]]] order = 0 parent = child1 position = 513 ratio = 0.278167115903 type = HPaned [[[child4]]] order = 1 parent = child2 position = 648 ratio = 0.487275449102 type = HPaned [[[child7]]] order = 1 parent = child1 position = 513 ratio = 0.278167115903 type = HPaned [[[child9]]] order = 1 parent = child7 position = 644 ratio = 0.484281437126 type = HPaned [[[terminal10]]] command = echo "roslaunch cerebro realsense_vinsfusion_ondrone.launch"; echo "roslaunch cerebro mynteye_vinsfusion_ondrone.launch"; bash order = 0 parent = child9 profile = default type = Terminal uuid = 8fc7a21c-3feb-468d-aae5-c363bc5c9f80 [[[terminal11]]] command = echo "roslaunch cerebro realsense_camera.launch"; echo "roslaunch mynt_eye_ros_wrapper mynteye.launch"; echo "rosbag play "; bash order = 1 parent = child9 profile = default type = Terminal uuid = a63545ab-f3c1-4426-a8ef-173f57ca8f28 [[[terminal3]]] command = echo "roscore"; bash order = 0 parent = child2 profile = default type = Terminal uuid = 27ae967a-b56c-4d92-a56e-67a2abe6b22d [[[terminal5]]] command = '''echo "ssh root@dji-tx2 'nvpmodel -m 0 && jetson_clocks'"; echo ""; echo "ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch tx2_whole_image_desc_server py3_tf_server_realsense.launch'" ; echo "ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch tx2_whole_image_desc_server py3_tf_server_mynteye.launch'"; bash''' order = 0 parent = child4 profile = default type = Terminal uuid = 6184900a-1468-4ffa-96a0-b1c00563ead7 [[[terminal6]]] command = '''echo "ssh dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && rosrun solve_keyframe_pose_graph keyframe_pose_graph_slam' "; bash''' order = 1 parent = child4 profile = default type = Terminal uuid = 09e32ec9-5e11-4bb0-980d-4c1abeb77791 [[[terminal8]]] command = echo "rviz -d catkin_ws/src/cerebro/rviz/vins-fusion.rviz"; bash order = 0 parent = child7 profile = default type = Terminal uuid = 1cc3df64-f997-4766-9317-0ce75d5dc2cd [[vins-cerebro]] [[[child0]]] fullscreen = False last_active_term = b015529f-251d-4563-81f8-86f2d24badbe last_active_window = True maximised = False order = 0 parent = "" position = 289:106 size = 642, 378 title = dji@MANIFOLD-2-C: ~ type = Window [[[terminal1]]] order = 0 parent = child0 profile = vins-cerebro type = Terminal uuid = b015529f-251d-4563-81f8-86f2d24badbe [plugins] [profiles] [[default]] background_image = None scrollback_lines = 5000 [[vins-cerebro]] background_image = None ================================================ FILE: config/euroc/euroc_config_no_extrinsic.yaml ================================================ %YAML:1.0 #common parameters imu_topic: "/imu0" image_topic: "/cam0/image_raw" output_path: "/home/tony-ws1/output/" #camera calibration model_type: PINHOLE camera_name: camera image_width: 752 image_height: 480 distortion_parameters: k1: -2.917e-01 k2: 8.228e-02 p1: 5.333e-05 p2: -1.578e-04 projection_parameters: fx: 4.616e+02 fy: 4.603e+02 cx: 3.630e+02 cy: 2.481e+02 # Extrinsic parameter between IMU and Camera. estimate_extrinsic: 2 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. #If you choose 0 or 1, you should write down the following matrix. #feature traker paprameters max_cnt: 150 # max feature number in feature tracking min_dist: 30 # min distance between two features freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image F_threshold: 1.0 # ransac threshold (pixel) show_track: 1 # publish tracking image as topic equalize: 1 # if image is too dark or light, trun on equalize to find enough features fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points #optimization parameters max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time max_num_iterations: 8 # max solver itrations, to guarantee real time keyframe_parallax: 10.0 # keyframe selection threshold (pixel) #imu parameters The more accurate parameters you provide, the better performance acc_n: 0.2 # accelerometer measurement noise standard deviation. #0.2 gyr_n: 0.02 # gyroscope measurement noise standard deviation. #0.05 acc_w: 0.0002 # accelerometer bias random work noise standard deviation. #0.02 gyr_w: 2.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 g_norm: 9.81007 # gravity magnitude #loop closure parameters loop_closure: 1 # start loop closure load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' fast_relocalization: 0 # useful in real-time and large project pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path #unsynchronization parameters estimate_td: 0 # online estimate time offset between camera and imu td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) #rolling shutter parameters rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). #visualization parameters save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results visualize_camera_size: 0.4 # size of camera marker in RVIZ ================================================ FILE: config/fly ================================================ [global_config] [keybindings] [layouts] [[default]] [[[child0]]] fullscreen = False last_active_term = 1cc3df64-f997-4766-9317-0ce75d5dc2cd last_active_window = True maximised = True order = 0 parent = "" position = 65:24 size = 1855, 1056 title = dji@MANIFOLD-2-C: ~ type = Window [[[child1]]] order = 0 parent = child0 position = 525 ratio = 0.5 type = VPaned [[[child2]]] order = 0 parent = child1 position = 513 ratio = 0.278167115903 type = HPaned [[[child4]]] order = 1 parent = child2 position = 648 ratio = 0.487275449102 type = HPaned [[[child7]]] order = 1 parent = child1 position = 513 ratio = 0.278167115903 type = HPaned [[[child9]]] order = 1 parent = child7 position = 644 ratio = 0.484281437126 type = HPaned [[[terminal10]]] command = echo "roslaunch cerebro realsense_vinsfusion.launch"; sleep 15s; roslaunch --wait cerebro realsense_vinsfusion.launch; bash order = 0 parent = child9 profile = default type = Terminal uuid = 8fc7a21c-3feb-468d-aae5-c363bc5c9f80 [[[terminal11]]] command = echo "roslaunch cerebro realsense_camera.launch"; sleep 5s; roslaunch --wait cerebro realsense_camera.launch; bash order = 1 parent = child9 profile = default type = Terminal uuid = a63545ab-f3c1-4426-a8ef-173f57ca8f28 [[[terminal3]]] command = echo "roscore"; roscore; bash order = 0 parent = child2 profile = default type = Terminal uuid = 27ae967a-b56c-4d92-a56e-67a2abe6b22d [[[terminal5]]] #command = '''echo "ssh root@dji-tx2 'nvpmodel -m 0 && jetson_clocks'"; echo ""; echo "ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch tx2_whole_image_desc_server py3_tf_server_realsense.launch'" ; echo "ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch tx2_whole_image_desc_server py3_tf_server_mynteye.launch'"; bash''' command = ''' coproc rviz -d catkin_ws/src/cerebro/rviz/vins-fusion.rviz ; ssh root@dji-tx2 'nvpmodel -m 0 && jetson_clocks'; ssh -t dji@dji-tx2 "export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; export DJIROS_APPID='1014582' ; export DJIROS_ENCKEY='821c8c3abeb6e340637b01f908ddeefb6c76d18d5f289e5cdb3cd5756a17bef5'; source $HOME/catkin_ws/devel/setup.bash && roslaunch djiros djiros.launch" ; bash ''' order = 0 parent = child4 profile = default type = Terminal uuid = 6184900a-1468-4ffa-96a0-b1c00563ead7 [[[terminal6]]] #command = '''echo "ssh dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && rosrun solve_keyframe_pose_graph keyframe_pose_graph_slam' "; bash''' command = " sleep 3s; echo 'rosrun trajectory_node trajectory_node' ; export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; rosrun trajectory_node trajectory_node ; bash " order = 1 parent = child4 profile = default type = Terminal uuid = 09e32ec9-5e11-4bb0-980d-4c1abeb77791 [[[terminal8]]] command = echo "roslaunch --wait machine_defined ctrl_md.launch"; roslaunch --wait machine_defined ctrl_md.launch ; bash order = 0 parent = child7 profile = default type = Terminal uuid = 1cc3df64-f997-4766-9317-0ce75d5dc2cd [[vins-cerebro]] [[[child0]]] fullscreen = False last_active_term = b015529f-251d-4563-81f8-86f2d24badbe last_active_window = True maximised = False order = 0 parent = "" position = 289:106 size = 642, 378 title = dji@MANIFOLD-2-C: ~ type = Window [[[terminal1]]] order = 0 parent = child0 profile = vins-cerebro type = Terminal uuid = b015529f-251d-4563-81f8-86f2d24badbe [plugins] [profiles] [[default]] background_image = None scrollback_lines = 5000 [[vins-cerebro]] background_image = None ================================================ FILE: config/mynteye/camera_left.yaml ================================================ %YAML:1.0 --- model_type: MEI camera_name: camera_left image_width: 752 image_height: 480 mirror_parameters: xi: 9.1080215802208964e-01 distortion_parameters: k1: -4.1813210333143819e-01 k2: 1.7403431210939191e-01 p1: 9.9413203823357906e-04 p2: 7.3177696678596699e-04 projection_parameters: gamma1: 7.0261362770226128e+02 gamma2: 7.0163099053315807e+02 u0: 3.7079141993914720e+02 v0: 2.3833755987148535e+02 ================================================ FILE: config/mynteye/camera_right.yaml ================================================ %YAML:1.0 --- model_type: MEI camera_name: camera_right image_width: 752 image_height: 480 mirror_parameters: xi: 1.0430682060403200e+00 distortion_parameters: k1: -4.1052639675713765e-01 k2: 1.5594870262700564e-01 p1: 1.0690635619397511e-03 p2: -1.2541037785533519e-04 projection_parameters: gamma1: 7.5079430544256968e+02 gamma2: 7.5002280082558298e+02 u0: 3.6187005259990548e+02 v0: 2.4784480598161096e+02 ================================================ FILE: config/mynteye/extrinsics.yaml ================================================ %YAML:1.0 --- transform: q_x: -1.8252509868889259e-04 q_y: -1.6291774489779708e-03 q_z: -1.2462127842978489e-03 q_w: 9.9999787970731446e-01 t_x: -1.2075905420832895e+02 t_y: 5.4110610639412482e-01 t_z: 2.4484815673909591e-01 ================================================ FILE: config/mynteye/mynteye_config.yaml ================================================ %YAML:1.0 #common parameters imu_topic: "/mynteye/imu/data_raw" image_topic: "/mynteye/left/image_raw" output_path: "/Bulk_Data/vins-mono-output/" # Additional camera (stereo) for cerebro - added my mpkuse image_topic_1: "/mynteye/right/image_raw" camera_yaml_1: "camera_right.yaml" # this yaml file will be searched in directory where this yaml file is. extrinsic_1_T_0: "extrinsics.yaml" # TODO contains the right_T_left, ie. stereo baseline. I need right_T_left that a transform that transforms 3d points of left to 3d points of right. in other words, if you sir of right the position of left camera. #camera calibration model_type: MEI camera_name: camera_left image_width: 752 image_height: 480 mirror_parameters: xi: 9.1080215802208964e-01 distortion_parameters: k1: -4.1813210333143819e-01 k2: 1.7403431210939191e-01 p1: 9.9413203823357906e-04 p2: 7.3177696678596699e-04 projection_parameters: gamma1: 7.0261362770226128e+02 gamma2: 7.0163099053315807e+02 u0: 3.7079141993914720e+02 v0: 2.3833755987148535e+02 # Extrinsic parameter between IMU and Camera. estimate_extrinsic: 1 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. #If you choose 0 or 1, you should write down the following matrix. #Rotation from camera frame to imu frame, imu^R_cam extrinsicRotation: !!opencv-matrix rows: 3 cols: 3 dt: d data: [ 4.2739206953025244e-03, -9.9997227378122833e-01, -6.0979726705474944e-03, 9.9998919890819160e-01, 4.2626966744552242e-03, 1.8524265207998580e-03, -1.8263813521932205e-03, -6.1058239298286089e-03, 9.9997969141642784e-01 ] extrinsicTranslation: !!opencv-matrix rows: 3 cols: 1 dt: d data: [ 6.1040452082857912e-03, -4.8408978130397302e-02, 2.2594515206886469e-02 ] #feature traker paprameters max_cnt: 150 # max feature number in feature tracking min_dist: 25 # min distance between two features freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image F_threshold: 1.0 # ransac threshold (pixel) show_track: 1 # publish tracking image as topic equalize: 1 # if image is too dark or light, trun on equalize to find enough features fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points #optimization parameters max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time max_num_iterations: 8 # max solver itrations, to guarantee real time keyframe_parallax: 10.0 # keyframe selection threshold (pixel) #imu parameters The more accurate parameters you provide, the better performance acc_n: 0.04 # accelerometer measurement noise standard deviation. #0.2 0.04 gyr_n: 0.004 # gyroscope measurement noise standard deviation. #0.05 0.004 acc_w: 0.0004 # accelerometer bias random work noise standard deviation. #0.02 gyr_w: 2.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 g_norm: 9.80766 # gravity magnitude #loop closure parameters loop_closure: 0 # start loop closure load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' fast_relocalization: 0 # useful in real-time and large project pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path #unsynchronization parameters estimate_td: 0 # online estimate time offset between camera and imu td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) #rolling shutter parameters rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). #visualization parameters save_image: 0 # save image in pose graph for visualization prupose; you can close this function by setting 0 visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results visualize_camera_size: 0.4 # size of camera marker in RVIZ ================================================ FILE: config/mynteye__terminator_config_vins_fusion_cerebro ================================================ [global_config] [keybindings] [layouts] [[default]] [[[child0]]] fullscreen = False last_active_term = 1cc3df64-f997-4766-9317-0ce75d5dc2cd last_active_window = True maximised = True order = 0 parent = "" position = 65:24 size = 1855, 1056 title = dji@MANIFOLD-2-C: ~ type = Window [[[child1]]] order = 0 parent = child0 position = 525 ratio = 0.5 type = VPaned [[[child2]]] order = 0 parent = child1 position = 513 ratio = 0.278167115903 type = HPaned [[[child4]]] order = 1 parent = child2 position = 648 ratio = 0.487275449102 type = HPaned [[[child7]]] order = 1 parent = child1 position = 513 ratio = 0.278167115903 type = HPaned [[[child9]]] order = 1 parent = child7 position = 644 ratio = 0.484281437126 type = HPaned [[[terminal10]]] command = '''echo "roslaunch cerebro realsense_vinsfusion_ondrone.launch"; echo "roslaunch cerebro mynteye_vinsfusion_ondrone.launch"; export KUSE_CMD="roslaunch cerebro mynteye_vinsfusion_ondrone.launch"; roslaunch --wait cerebro mynteye_vinsfusion_ondrone.launch; bash''' order = 0 parent = child9 profile = default type = Terminal uuid = 8fc7a21c-3feb-468d-aae5-c363bc5c9f80 [[[terminal11]]] command = '''#echo "roslaunch cerebro realsense_camera.launch"; echo "roslaunch mynt_eye_ros_wrapper mynteye.launch"; echo "rosbag play "; export KUSE_CMD="roslaunch mynt_eye_ros_wrapper mynteye.launch or rosbag play" bash''' order = 1 parent = child9 profile = default type = Terminal uuid = a63545ab-f3c1-4426-a8ef-173f57ca8f28 [[[terminal3]]] command = echo "roscore"; exec "roscore"; bash order = 0 parent = child2 profile = default type = Terminal uuid = 27ae967a-b56c-4d92-a56e-67a2abe6b22d [[[terminal5]]] command = '''echo "ssh root@dji-tx2 'nvpmodel -m 0 && jetson_clocks'"; ssh root@dji-tx2 'nvpmodel -m 0 && jetson_clocks'; echo ""; #echo "ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch --wait tx2_whole_image_desc_server py3_tf_server_realsense.launch'"; echo "ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch --wait tx2_whole_image_desc_server py3_tf_server_mynteye.launch'"; export KUSE_CMD="ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch --wait tx2_whole_image_desc_server py3_tf_server_mynteye.launch'"; ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch --wait tx2_whole_image_desc_server py3_tf_server_mynteye.launch' bash''' order = 0 parent = child4 profile = default type = Terminal uuid = 6184900a-1468-4ffa-96a0-b1c00563ead7 [[[terminal6]]] command = '''echo "ssh dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && rosrun solve_keyframe_pose_graph keyframe_pose_graph_slam'"; export KUSE_CMD="ssh dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && rosrun solve_keyframe_pose_graph keyframe_pose_graph_slam'"; ssh dji@dji-tx2 "export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && rosrun solve_keyframe_pose_graph keyframe_pose_graph_slam"; bash''' order = 1 parent = child4 profile = default type = Terminal uuid = 09e32ec9-5e11-4bb0-980d-4c1abeb77791 [[[terminal8]]] command = '''echo "rviz -d catkin_ws/src/cerebro/rviz/vins-fusion.rviz"; rviz -d catkin_ws/src/cerebro/rviz/vins-fusion.rviz bash''' order = 0 parent = child7 profile = default type = Terminal uuid = 1cc3df64-f997-4766-9317-0ce75d5dc2cd [[vins-cerebro]] [[[child0]]] fullscreen = False last_active_term = b015529f-251d-4563-81f8-86f2d24badbe last_active_window = True maximised = False order = 0 parent = "" position = 289:106 size = 642, 378 title = dji@MANIFOLD-2-C: ~ type = Window [[[terminal1]]] order = 0 parent = child0 profile = vins-cerebro type = Terminal uuid = b015529f-251d-4563-81f8-86f2d24badbe [plugins] [profiles] [[default]] background_image = None scrollback_lines = 5000 [[vins-cerebro]] background_image = None ================================================ FILE: config/mynteye_kannala_brandt/camera_left.yaml ================================================ %YAML:1.0 --- model_type: KANNALA_BRANDT camera_name: camera_left image_width: 752 image_height: 480 projection_parameters: k2: -1.9372136316513616e-02 k3: -9.8140934466631590e-03 k4: 5.2090950264238800e-03 k5: -2.5384805224148016e-03 mu: 3.6779833417894611e+02 mv: 3.6735717027889308e+02 u0: 3.7150938938370604e+02 v0: 2.3994369620747401e+02 ================================================ FILE: config/mynteye_kannala_brandt/camera_right.yaml ================================================ %YAML:1.0 --- model_type: KANNALA_BRANDT camera_name: camera_right image_width: 752 image_height: 480 projection_parameters: k2: -1.6877708507279571e-02 k3: -2.2125250367976849e-02 k4: 2.5629804328110812e-02 k5: -1.3352680211095494e-02 mu: 3.6746513955323167e+02 mv: 3.6703089274525593e+02 u0: 3.6200946050656484e+02 v0: 2.4919721516867821e+02 ================================================ FILE: config/mynteye_kannala_brandt/extrinsics.yaml ================================================ %YAML:1.0 --- transform: q_x: -7.0955103716253032e-04 q_y: -1.5775578725333590e-03 q_z: -1.2732644461763854e-03 q_w: 9.9999769332040711e-01 t_x: -1.2006984141573309e+02 t_y: 3.3956264524978619e-01 t_z: -1.6784055634087214e-01 ================================================ FILE: config/mynteye_kannala_brandt/mynteye_config.yaml ================================================ %YAML:1.0 #common parameters imu_topic: "/mynteye/imu/data_raw" image_topic: "/mynteye/left/image_raw" output_path: "/Bulk_Data/vins-mono-output/" # Additional camera (stereo) for cerebro - added my mpkuse image_topic_1: "/mynteye/right/image_raw" camera_yaml_1: "camera_right.yaml" # this yaml file will be searched in directory where this yaml file is. extrinsic_1_T_0: "extrinsics.yaml" # contains the right_T_left, ie. stereo baseline. I need right_T_left that a transform that transforms 3d points of left to 3d points of right. in other words, if you sir of right the position of left camera. # publish image pair on loopcandidate detection #camera calibration model_type: KANNALA_BRANDT camera_name: camera_left image_width: 752 image_height: 480 projection_parameters: k2: -1.9372136316513616e-02 k3: -9.8140934466631590e-03 k4: 5.2090950264238800e-03 k5: -2.5384805224148016e-03 mu: 3.6779833417894611e+02 mv: 3.6735717027889308e+02 u0: 3.7150938938370604e+02 v0: 2.3994369620747401e+02 # Extrinsic parameter between IMU and Camera. estimate_extrinsic: 1 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. #If you choose 0 or 1, you should write down the following matrix. #Rotation from camera frame to imu frame, imu^R_cam extrinsicRotation: !!opencv-matrix rows: 3 cols: 3 dt: d data: [ 4.2739206953025244e-03, -9.9997227378122833e-01, -6.0979726705474944e-03, 9.9998919890819160e-01, 4.2626966744552242e-03, 1.8524265207998580e-03, -1.8263813521932205e-03, -6.1058239298286089e-03, 9.9997969141642784e-01 ] extrinsicTranslation: !!opencv-matrix rows: 3 cols: 1 dt: d data: [ 6.1040452082857912e-03, -4.8408978130397302e-02, 2.2594515206886469e-02 ] #feature traker paprameters max_cnt: 150 # max feature number in feature tracking min_dist: 25 # min distance between two features freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image F_threshold: 1.0 # ransac threshold (pixel) show_track: 1 # publish tracking image as topic equalize: 1 # if image is too dark or light, trun on equalize to find enough features fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points #optimization parameters max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time max_num_iterations: 10 # max solver itrations, to guarantee real time keyframe_parallax: 10.0 # keyframe selection threshold (pixel) #imu parameters The more accurate parameters you provide, the better performance acc_n: 0.04 # accelerometer measurement noise standard deviation. #0.2 0.04 gyr_n: 0.004 # gyroscope measurement noise standard deviation. #0.05 0.004 acc_w: 0.0004 # accelerometer bias random work noise standard deviation. #0.02 gyr_w: 2.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 g_norm: 9.80766 # gravity magnitude #loop closure parameters loop_closure: 1 # start loop closure load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' fast_relocalization: 1 # useful in real-time and large project pose_graph_save_path: "/Bulk_Data/vins-mono-output/pose_graph/" # save and load path #unsynchronization parameters estimate_td: 0 # online estimate time offset between camera and imu td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) #rolling shutter parameters rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). #visualization parameters save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results visualize_camera_size: 0.4 # size of camera marker in RVIZ ================================================ FILE: config/mynteye_pinhole/camera_left.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera_left image_width: 752 image_height: 480 distortion_parameters: k1: -3.0304011877067349e-01 k2: 7.8611376314970768e-02 p1: 1.3200427858719645e-03 p2: -1.2942361409192899e-03 projection_parameters: fx: 3.7560167589977470e+02 fy: 3.7552589588133230e+02 cx: 3.7887177487131697e+02 cy: 2.3402525474822104e+02 ================================================ FILE: config/mynteye_pinhole/camera_right.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera_right image_width: 752 image_height: 480 distortion_parameters: k1: -3.0790744874410708e-01 k2: 8.3037204977051693e-02 p1: 1.1347651021695062e-03 p2: -7.8671202943393290e-04 projection_parameters: fx: 3.7573448135250288e+02 fy: 3.7536464618331070e+02 cx: 3.6634215933104940e+02 cy: 2.4325470902936044e+02 ================================================ FILE: config/mynteye_pinhole/extrinsics.yaml ================================================ %YAML:1.0 --- transform: q_x: -7.8278125678242136e-04 q_y: 2.4666921736014214e-03 q_z: -1.4477856356187120e-03 q_w: 9.9999560329032322e-01 t_x: -1.1985630585227842e+02 t_y: 1.2186198275182303e-01 t_z: -1.4431533628123798e+00 ================================================ FILE: config/mynteye_pinhole/mynteye_config.yaml ================================================ %YAML:1.0 #common parameters imu_topic: "/mynteye/imu/data_raw" image_topic: "/mynteye/left/image_raw" output_path: "/Bulk_Data/vins-mono-output/" # Additional camera (stereo) for cerebro - added my mpkuse image_topic_1: "/mynteye/right/image_raw" camera_yaml_1: "camera_right.yaml" # this yaml file will be searched in directory where this yaml file is. extrinsic_1_T_0: "extrinsics.yaml" # contains the right_T_left, ie. stereo baseline. I need right_T_left that a transform that transforms 3d points of left to 3d points of right. in other words, if you sir of right the position of left camera. #camera calibration model_type: PINHOLE camera_name: camera_left image_width: 752 image_height: 480 distortion_parameters: k1: -3.0304011877067349e-01 k2: 7.8611376314970768e-02 p1: 1.3200427858719645e-03 p2: -1.2942361409192899e-03 projection_parameters: fx: 3.7560167589977470e+02 fy: 3.7552589588133230e+02 cx: 3.7887177487131697e+02 cy: 2.3402525474822104e+02 # Extrinsic parameter between IMU and Camera. estimate_extrinsic: 1 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. #If you choose 0 or 1, you should write down the following matrix. #Rotation from camera frame to imu frame, imu^R_cam extrinsicRotation: !!opencv-matrix rows: 3 cols: 3 dt: d data: [ 4.2739206953025244e-03, -9.9997227378122833e-01, -6.0979726705474944e-03, 9.9998919890819160e-01, 4.2626966744552242e-03, 1.8524265207998580e-03, -1.8263813521932205e-03, -6.1058239298286089e-03, 9.9997969141642784e-01 ] extrinsicTranslation: !!opencv-matrix rows: 3 cols: 1 dt: d data: [ 6.1040452082857912e-03, -4.8408978130397302e-02, 2.2594515206886469e-02 ] #feature traker paprameters max_cnt: 130 # max feature number in feature tracking min_dist: 25 # min distance between two features freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image F_threshold: 1.0 # ransac threshold (pixel) show_track: 1 # publish tracking image as topic equalize: 1 # if image is too dark or light, trun on equalize to find enough features fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points #optimization parameters max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time max_num_iterations: 8 # max solver itrations, to guarantee real time keyframe_parallax: 10.0 # keyframe selection threshold (pixel) #imu parameters The more accurate parameters you provide, the better performance acc_n: 0.04 # accelerometer measurement noise standard deviation. #0.2 0.04 gyr_n: 0.004 # gyroscope measurement noise standard deviation. #0.05 0.004 acc_w: 0.0004 # accelerometer bias random work noise standard deviation. #0.02 gyr_w: 2.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 g_norm: 9.80766 # gravity magnitude #loop closure parameters loop_closure: 0 # start loop closure load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' fast_relocalization: 0 # useful in real-time and large project pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path #unsynchronization parameters estimate_td: 0 # online estimate time offset between camera and imu td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) #rolling shutter parameters rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). #visualization parameters save_image: 0 # save image in pose graph for visualization prupose; you can close this function by setting 0 visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results visualize_camera_size: 0.4 # size of camera marker in RVIZ ================================================ FILE: config/realsense__terminator_config_vins_fusion_cerebro ================================================ [global_config] [keybindings] [layouts] [[default]] [[[child0]]] fullscreen = False last_active_term = 1cc3df64-f997-4766-9317-0ce75d5dc2cd last_active_window = True maximised = True order = 0 parent = "" position = 65:24 size = 1855, 1056 title = dji@MANIFOLD-2-C: ~ type = Window [[[child1]]] order = 0 parent = child0 position = 525 ratio = 0.5 type = VPaned [[[child2]]] order = 0 parent = child1 position = 513 ratio = 0.278167115903 type = HPaned [[[child4]]] order = 1 parent = child2 position = 648 ratio = 0.487275449102 type = HPaned [[[child7]]] order = 1 parent = child1 position = 513 ratio = 0.278167115903 type = HPaned [[[child9]]] order = 1 parent = child7 position = 644 ratio = 0.484281437126 type = HPaned [[[terminal10]]] command = '''echo "roslaunch cerebro realsense_vinsfusion_ondrone.launch"; roslaunch --wait cerebro realsense_vinsfusion_ondrone.launch #echo "roslaunch cerebro mynteye_vinsfusion_ondrone.launch"; #roslaunch --wait cerebro mynteye_vinsfusion_ondrone.launch; bash''' order = 0 parent = child9 profile = default type = Terminal uuid = 8fc7a21c-3feb-468d-aae5-c363bc5c9f80 [[[terminal11]]] command = '''echo "roslaunch cerebro realsense_camera.launch"; #echo "roslaunch mynt_eye_ros_wrapper mynteye.launch"; echo "rosbag play "; bash''' order = 1 parent = child9 profile = default type = Terminal uuid = a63545ab-f3c1-4426-a8ef-173f57ca8f28 [[[terminal3]]] command = echo "roscore"; exec "roscore"; bash order = 0 parent = child2 profile = default type = Terminal uuid = 27ae967a-b56c-4d92-a56e-67a2abe6b22d [[[terminal5]]] command = '''echo "ssh root@dji-tx2 'nvpmodel -m 0 && jetson_clocks'"; ssh root@dji-tx2 'nvpmodel -m 0 && jetson_clocks'; echo ""; #echo "ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch --wait tx2_whole_image_desc_server py3_tf_server_realsense.launch'"; ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch --wait tx2_whole_image_desc_server py3_tf_server_realsense.launch' #echo "ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch --wait tx2_whole_image_desc_server py3_tf_server_mynteye.launch'"; #ssh -t dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && roslaunch --wait tx2_whole_image_desc_server py3_tf_server_mynteye.launch' bash''' order = 0 parent = child4 profile = default type = Terminal uuid = 6184900a-1468-4ffa-96a0-b1c00563ead7 [[[terminal6]]] command = '''echo "ssh dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && rosrun solve_keyframe_pose_graph keyframe_pose_graph_slam'"; ssh dji@dji-tx2 'export ROS_MASTER_URI=http://MANIFOLD-2-C:11311/; source $HOME/catkin_ws/devel/setup.bash && rosrun solve_keyframe_pose_graph keyframe_pose_graph_slam'; bash''' order = 1 parent = child4 profile = default type = Terminal uuid = 09e32ec9-5e11-4bb0-980d-4c1abeb77791 [[[terminal8]]] command = '''echo "rviz -d catkin_ws/src/cerebro/rviz/vins-fusion.rviz"; rviz -d catkin_ws/src/cerebro/rviz/vins-fusion.rviz bash''' order = 0 parent = child7 profile = default type = Terminal uuid = 1cc3df64-f997-4766-9317-0ce75d5dc2cd [[vins-cerebro]] [[[child0]]] fullscreen = False last_active_term = b015529f-251d-4563-81f8-86f2d24badbe last_active_window = True maximised = False order = 0 parent = "" position = 289:106 size = 642, 378 title = dji@MANIFOLD-2-C: ~ type = Window [[[terminal1]]] order = 0 parent = child0 profile = vins-cerebro type = Terminal uuid = b015529f-251d-4563-81f8-86f2d24badbe [plugins] [profiles] [[default]] background_image = None scrollback_lines = 5000 [[vins-cerebro]] background_image = None ================================================ FILE: config/vinsfusion/djirosimu_realsense_d435i/README.md ================================================ The configuration when using realsense stereo camera and djiros's imu (ie. imu data from N3) ================================================ FILE: config/vinsfusion/djirosimu_realsense_d435i/extrinsics.yaml ================================================ %YAML:1.0 --- transform: q_x: 0. q_y: 0. q_z: 0. q_w: 1. t_x: -49.8 t_y: 0. t_z: 0. #4.98 cm for the realsense d435i device. This device has hardware undistort so can safely use the pinhole model ================================================ FILE: config/vinsfusion/djirosimu_realsense_d435i/left.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera image_width: 640 image_height: 480 distortion_parameters: k1: 0.0 k2: 0.0 p1: 0.0 p2: 0.0 projection_parameters: fx: 385.7544860839844 fy: 385.7544860839844 cx: 323.1204833984375 cy: 236.7432098388672 ================================================ FILE: config/vinsfusion/djirosimu_realsense_d435i/realsense_stereo_imu_config.yaml ================================================ %YAML:1.0 #common parameters #support: 1 imu 1 cam; 1 imu 2 cam: 2 cam; imu: 1 num_of_cam: 2 # imu_topic: "/camera/imu" imu_topic: "/djiros/imu" image0_topic: "/camera/infra1/image_rect_raw" image1_topic: "/camera/infra2/image_rect_raw" output_path: "/home/dji/vinsfusion-output/" cam0_calib: "left.yaml" cam1_calib: "right.yaml" image_width: 640 image_height: 480 # contains the right_T_left aka 1_T_0, ie. stereo baseline. I need right_T_left that a transform that transforms 3d points of left to 3d points of right. in other words, if you sure of right the position of left camera. # This is an optional argument even if using `num_of_cam:=2`. If I cannot # find this key I will use `body_T_cam0` and `body_T_cam1` to compute cam1_T_cam0. # But if this key exists, we will use these values as stereo baseline. # **In this file, I assume translation re specified ****in mm**** (and not in meters).** extrinsic_1_T_0: "extrinsics.yaml" # Extrinsic parameter between IMU and Camera. estimate_extrinsic: 1 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. body_T_cam0: !!opencv-matrix rows: 4 cols: 4 dt: d data: [ -1.0406548080980293e-02, -6.8339359975994052e-04, 9.9994561688635142e-01, 5.2600599061337287e-02, -9.9994461366801901e-01, -1.5656625000493030e-03, -1.0407607662295648e-02, 2.5510536758700533e-02, 1.5726898469127781e-03, -9.9999854083599726e-01, -6.6706260700977182e-04, 9.5887981235633201e-03, 0., 0., 0., 1. ] body_T_cam1: !!opencv-matrix rows: 4 cols: 4 dt: d data: [ -1.3185778504660295e-02, 1.2513252883544768e-04, 9.9991305601390978e-01, 4.2975722207919849e-02, -9.9984703667423980e-01, 1.1490149582040754e-02, -1.3186345829069746e-02, -5.4397185804457168e-02, -1.1490800623434938e-02, -9.9993397822277874e-01, -2.6393179226591457e-05, 1.8167602224138771e-02, 0., 0., 0., 1. ] #Multiple thread support multiple_thread: 1 #feature traker paprameters max_cnt: 150 # max feature number in feature tracking min_dist: 30 # min distance between two features freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image F_threshold: 1.0 # ransac threshold (pixel) show_track: 0 # publish tracking image as topic flow_back: 1 # perform forward and backward optical flow to improve feature tracking accuracy #optimization parameters max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time max_num_iterations: 8 # max solver itrations, to guarantee real time keyframe_parallax: 10.0 # keyframe selection threshold (pixel) #imu parameters The more accurate parameters you provide, the better performance acc_n: 0.1 # accelerometer measurement noise standard deviation. #0.2 0.04 gyr_n: 0.02 # gyroscope measurement noise standard deviation. #0.05 0.004 acc_w: 0.002 # accelerometer bias random work noise standard deviation. #0.002 gyr_w: 0.00005 # gyroscope bias random work noise standard deviation. #4.0e-5 g_norm: 9.805 # gravity magnitude #unsynchronization parameters estimate_td: 1 # online estimate time offset between camera and imu td: 0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) #loop closure parameters load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' pose_graph_save_path: "/home/dji/output/pose_graph/" # save and load path save_image: 0 # save image in pose graph for visualization prupose; you can close this function by setting 0 ================================================ FILE: config/vinsfusion/djirosimu_realsense_d435i/right.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera image_width: 640 image_height: 480 distortion_parameters: k1: 0.0 k2: 0.0 p1: 0.0 p2: 0.0 projection_parameters: fx: 385.7544860839844 fy: 385.7544860839844 cx: 323.1204833984375 cy: 236.7432098388672 ================================================ FILE: config/vinsfusion/euroc/cam0_pinhole.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera image_width: 752 image_height: 480 distortion_parameters: k1: -2.9545645106987750e-01 k2: 8.6623215640186171e-02 p1: 2.0132892276082517e-06 p2: 1.3924531371276508e-05 projection_parameters: fx: 4.6115862106007575e+02 fy: 4.5975286598073296e+02 cx: 3.6265929181685937e+02 cy: 2.4852105668448124e+02 ================================================ FILE: config/vinsfusion/euroc/cam1_pinhole.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera image_width: 752 image_height: 480 distortion_parameters: k1: -2.9294124381930947e-01 k2: 8.4798002331543665e-02 p1: -2.9984646536002372e-04 p2: 3.0028216325237329e-04 projection_parameters: fx: 4.6009781682258682e+02 fy: 4.5890983492218902e+02 cx: 3.7314916359808268e+02 cy: 2.5440734973672119e+02 ================================================ FILE: config/vinsfusion/euroc/euroc_stereo_imu_config.yaml ================================================ %YAML:1.0 #common parameters #support: 1 imu 1 cam; 1 imu 2 cam: 2 cam; imu: 1 num_of_cam: 2 imu_topic: "/imu0" image0_topic: "/cam0/image_raw" image1_topic: "/cam1/image_raw" output_path: "~/output/" #cam0_calib: "cam0_mei.yaml" #cam1_calib: "cam1_mei.yaml" cam0_calib: "cam0_pinhole.yaml" cam1_calib: "cam1_pinhole.yaml" image_width: 752 image_height: 480 # contains the right_T_left aka 1_T_0, ie. stereo baseline. I need right_T_left that a transform that transforms 3d points of left to 3d points of right. in other words, if you sure of right the position of left camera. # This is an optional argument even if using `num_of_cam:=2`. If I cannot # find this key I will use `body_T_cam0` and `body_T_cam1` to compute cam1_T_cam0. # But if this key exists, we will use these values as stereo baseline. # **In this file, I assume translation re specified ****in mm**** (and not in meters).** extrinsic_1_T_0: "extrinsics.yaml" # Extrinsic parameter between IMU and Camera. estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. body_T_cam0: !!opencv-matrix rows: 4 cols: 4 dt: d data: [0.0148655429818, -0.999880929698, 0.00414029679422, -0.0216401454975, 0.999557249008, 0.0149672133247, 0.025715529948, -0.064676986768, -0.0257744366974, 0.00375618835797, 0.999660727178, 0.00981073058949, 0, 0, 0, 1] body_T_cam1: !!opencv-matrix rows: 4 cols: 4 dt: d data: [0.0125552670891, -0.999755099723, 0.0182237714554, -0.0198435579556, 0.999598781151, 0.0130119051815, 0.0251588363115, 0.0453689425024, -0.0253898008918, 0.0179005838253, 0.999517347078, 0.00786212447038, 0, 0, 0, 1] #Multiple thread support multiple_thread: 1 #feature traker paprameters max_cnt: 150 # max feature number in feature tracking min_dist: 30 # min distance between two features freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image F_threshold: 1.0 # ransac threshold (pixel) show_track: 1 # publish tracking image as topic flow_back: 0 # perform forward and backward optical flow to improve feature tracking accuracy #optimization parameters max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time max_num_iterations: 8 # max solver itrations, to guarantee real time keyframe_parallax: 10.0 # keyframe selection threshold (pixel) #imu parameters The more accurate parameters you provide, the better performance acc_n: 0.1 # accelerometer measurement noise standard deviation. gyr_n: 0.01 # gyroscope measurement noise standard deviation. acc_w: 0.001 # accelerometer bias random work noise standard deviation. gyr_w: 0.0001 # gyroscope bias random work noise standard deviation. g_norm: 9.81007 # gravity magnitude #unsynchronization parameters estimate_td: 0 # online estimate time offset between camera and imu td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) #loop closure parameters load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' pose_graph_save_path: "~/output/pose_graph/" # save and load path save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 ================================================ FILE: config/vinsfusion/euroc/extrinsics.yaml ================================================ %YAML:1.0 --- # this is computed for euric using body_T_cam0 and body_T_cam1. transform: q_x: -0.00704531 q_y: 0.000179855 q_z: -0.00115733 q_w: 0.999974 t_x: -110.074 t_y: 0.399122 t_z: -0.853703 ================================================ FILE: config/vinsfusion/mynteye/camera_left.yaml ================================================ %YAML:1.0 --- model_type: KANNALA_BRANDT camera_name: camera_left image_width: 752 image_height: 480 projection_parameters: k2: -1.9372136316513616e-02 k3: -9.8140934466631590e-03 k4: 5.2090950264238800e-03 k5: -2.5384805224148016e-03 mu: 3.6779833417894611e+02 mv: 3.6735717027889308e+02 u0: 3.7150938938370604e+02 v0: 2.3994369620747401e+02 ================================================ FILE: config/vinsfusion/mynteye/camera_left_pinhole.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera_left image_width: 752 image_height: 480 distortion_parameters: k1: -3.0304011877067349e-01 k2: 7.8611376314970768e-02 p1: 1.3200427858719645e-03 p2: -1.2942361409192899e-03 projection_parameters: fx: 3.7560167589977470e+02 fy: 3.7552589588133230e+02 cx: 3.7887177487131697e+02 cy: 2.3402525474822104e+02 ================================================ FILE: config/vinsfusion/mynteye/camera_right.yaml ================================================ %YAML:1.0 --- model_type: KANNALA_BRANDT camera_name: camera_right image_width: 752 image_height: 480 projection_parameters: k2: -1.6877708507279571e-02 k3: -2.2125250367976849e-02 k4: 2.5629804328110812e-02 k5: -1.3352680211095494e-02 mu: 3.6746513955323167e+02 mv: 3.6703089274525593e+02 u0: 3.6200946050656484e+02 v0: 2.4919721516867821e+02 ================================================ FILE: config/vinsfusion/mynteye/camera_right_pinhole.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera_right image_width: 752 image_height: 480 distortion_parameters: k1: -3.0790744874410708e-01 k2: 8.3037204977051693e-02 p1: 1.1347651021695062e-03 p2: -7.8671202943393290e-04 projection_parameters: fx: 3.7573448135250288e+02 fy: 3.7536464618331070e+02 cx: 3.6634215933104940e+02 cy: 2.4325470902936044e+02 ================================================ FILE: config/vinsfusion/mynteye/extrinsics.yaml ================================================ %YAML:1.0 --- transform: q_x: -7.0955103716253032e-04 q_y: -1.5775578725333590e-03 q_z: -1.2732644461763854e-03 q_w: 9.9999769332040711e-01 t_x: -1.2006984141573309e+02 t_y: 3.3956264524978619e-01 t_z: -1.6784055634087214e-01 ================================================ FILE: config/vinsfusion/mynteye/mynteye_stereo_imu_config.yaml ================================================ %YAML:1.0 #common parameters #support: 1 imu 1 cam; 1 imu 2 cam: 2 cam; imu: 1 num_of_cam: 2 imu_topic: "/mynteye/imu/data_raw" image0_topic: "/mynteye/left/image_raw" image1_topic: "/mynteye/right/image_raw" output_path: "/Bulk_Data/vins-mono-output/" #cam0_calib: "camera_left_pinhole.yaml" #cam1_calib: "camera_right_pinhole.yaml" cam0_calib: "camera_left.yaml" cam1_calib: "camera_right.yaml" image_width: 752 image_height: 480 # contains the right_T_left aka 1_T_0, ie. stereo baseline. I need right_T_left that a transform that transforms 3d points of left to 3d points of right. in other words, if you sure of right the position of left camera. # This is an optional argument even if using `num_of_cam:=2`. If I cannot # find this key I will use `body_T_cam0` and `body_T_cam1` to compute cam1_T_cam0. # But if this key exists, we will use these values as stereo baseline. # **In this file, I assume translation re specified ****in mm**** (and not in meters).** extrinsic_1_T_0: "extrinsics.yaml" # Extrinsic parameter between IMU and Camera. estimate_extrinsic: 0 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. body_T_cam0: !!opencv-matrix rows: 4 cols: 4 dt: d data: [4.2739206953025244e-03, -9.9997227378122833e-01,-6.0979726705474944e-03,6.1040452082857912e-03, 9.9998919890819160e-01, 4.2626966744552242e-03, 1.8524265207998580e-03,-4.8408978130397302e-02, -1.8263813521932205e-03, -6.1058239298286089e-03, 9.9997969141642784e-01,2.2594515206886469e-02, 0,0,0,1] # ===> imu_T_cam0 * inv( cam1_T_cam0 ) body_T_cam1: !!opencv-matrix rows: 4 cols: 4 dt: d data: [ 0.00174443, -0.99998733, -0.0046694 , 0.00665227, 0.99998578, 0.00172106, 0.00500326, 0.07165957, -0.00499517, -0.00467806, 0.9999766 , 0.02216417, 0. , 0. , 0. , 1. ] #Multiple thread support multiple_thread: 1 #feature traker paprameters max_cnt: 100 # max feature number in feature tracking min_dist: 30 # min distance between two features freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image F_threshold: 1.1 # ransac threshold (pixel) show_track: 0 # publish tracking image as topic flow_back: 1 # perform forward and backward optical flow to improve feature tracking accuracy #optimization parameters max_solver_time: 0.05 # max solver itration time (ms), to guarantee real time max_num_iterations: 12 # max solver itrations, to guarantee real time keyframe_parallax: 10.0 # keyframe selection threshold (pixel) #imu parameters The more accurate parameters you provide, the better performance acc_n: 0.04 # accelerometer measurement noise standard deviation. #0.2 0.04 gyr_n: 0.004 # gyroscope measurement noise standard deviation. #0.05 0.004 acc_w: 0.0004 # accelerometer bias random work noise standard deviation. #0.02 gyr_w: 2.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 g_norm: 9.80766 # gravity magnitude #unsynchronization parameters estimate_td: 0 # online estimate time offset between camera and imu td: 0.0 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) #loop closure parameters load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' pose_graph_save_path: "~/output/pose_graph/" # save and load path save_image: 0 # save image in pose graph for visualization prupose; you can close this function by setting 0 ================================================ FILE: config/vinsfusion/realsense_d435i/extrinsics.yaml ================================================ %YAML:1.0 --- transform: q_x: 0. q_y: 0. q_z: 0. q_w: 1. t_x: -49.8 t_y: 0. t_z: 0. #4.98 cm for the realsense d435i device. This device has hardware undistort so can safely use the pinhole model ================================================ FILE: config/vinsfusion/realsense_d435i/left.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera image_width: 640 image_height: 480 distortion_parameters: k1: 0.0 k2: 0.0 p1: 0.0 p2: 0.0 projection_parameters: fx: 385.7544860839844 fy: 385.7544860839844 cx: 323.1204833984375 cy: 236.7432098388672 ================================================ FILE: config/vinsfusion/realsense_d435i/realsense_stereo_imu_config.yaml ================================================ %YAML:1.0 #common parameters #support: 1 imu 1 cam; 1 imu 2 cam: 2 cam; imu: 1 num_of_cam: 2 #imu_topic: "/camera/imu" imu_topic: "/djiros/imu" image0_topic: "/camera/infra1/image_rect_raw" image1_topic: "/camera/infra2/image_rect_raw" output_path: "/home/dji/output/" cam0_calib: "left.yaml" cam1_calib: "right.yaml" image_width: 640 image_height: 480 # contains the right_T_left aka 1_T_0, ie. stereo baseline. I need right_T_left that a transform that transforms 3d points of left to 3d points of right. in other words, if you sure of right the position of left camera. # This is an optional argument even if using `num_of_cam:=2`. If I cannot # find this key I will use `body_T_cam0` and `body_T_cam1` to compute cam1_T_cam0. # But if this key exists, we will use these values as stereo baseline. # **In this file, I assume translation re specified ****in mm**** (and not in meters).** extrinsic_1_T_0: "extrinsics.yaml" # Extrinsic parameter between IMU and Camera. estimate_extrinsic: 1 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. body_T_cam0: !!opencv-matrix rows: 4 cols: 4 dt: d data: [ -1.0406548080980293e-02, -6.8339359975994052e-04, 9.9994561688635142e-01, 5.2600599061337287e-02, -9.9994461366801901e-01, -1.5656625000493030e-03, -1.0407607662295648e-02, 2.5510536758700533e-02, 1.5726898469127781e-03, -9.9999854083599726e-01, -6.6706260700977182e-04, 9.5887981235633201e-03, 0., 0., 0., 1. ] body_T_cam1: !!opencv-matrix rows: 4 cols: 4 dt: d data: [ -1.3185778504660295e-02, 1.2513252883544768e-04, 9.9991305601390978e-01, 4.2975722207919849e-02, -9.9984703667423980e-01, 1.1490149582040754e-02, -1.3186345829069746e-02, -5.4397185804457168e-02, -1.1490800623434938e-02, -9.9993397822277874e-01, -2.6393179226591457e-05, 1.8167602224138771e-02, 0., 0., 0., 1. ] #Multiple thread support multiple_thread: 1 #feature traker paprameters max_cnt: 100 # max feature number in feature tracking min_dist: 30 # min distance between two features freq: 10 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image F_threshold: 1.0 # ransac threshold (pixel) show_track: 0 # publish tracking image as topic flow_back: 1 # perform forward and backward optical flow to improve feature tracking accuracy #optimization parameters max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time max_num_iterations: 8 # max solver itrations, to guarantee real time keyframe_parallax: 10.0 # keyframe selection threshold (pixel) #imu parameters The more accurate parameters you provide, the better performance acc_n: 0.2 # accelerometer measurement noise standard deviation. #0.2 0.04 gyr_n: 0.02 # gyroscope measurement noise standard deviation. #0.05 0.004 acc_w: 0.001 # accelerometer bias random work noise standard deviation. #0.002 gyr_w: 0.0001 # gyroscope bias random work noise standard deviation. #4.0e-5 g_norm: 9.805 # gravity magnitude #unsynchronization parameters estimate_td: 0 # online estimate time offset between camera and imu td: 0.00 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) #loop closure parameters load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' pose_graph_save_path: "/home/dji/output/pose_graph/" # save and load path save_image: 0 # save image in pose graph for visualization prupose; you can close this function by setting 0 ================================================ FILE: config/vinsfusion/realsense_d435i/right.yaml ================================================ %YAML:1.0 --- model_type: PINHOLE camera_name: camera image_width: 640 image_height: 480 distortion_parameters: k1: 0.0 k2: 0.0 p1: 0.0 p2: 0.0 projection_parameters: fx: 385.7544860839844 fy: 385.7544860839844 cx: 323.1204833984375 cy: 236.7432098388672 ================================================ FILE: config/vinsmono_debug_config.yaml ================================================ %YAML:1.0 ####### ## This is config file for running vins-mono. We use this for imu_T_camera calibration. ## Set the estimate_extrinsic to 2. If vins mono works on, just copy (written in `output_path`) ####### # imu_topic: "/camera/imu" imu_topic: "/djiros/imu" image_topic: "/camera/infra1/image_rect_raw" #image_topic: "/camera/infra2/image_rect_raw" # output_path: "/home/tony-ws1/output/" output_path: "/home/dji/output/" #camera calibration - left , right same for realsense model_type: PINHOLE camera_name: camera image_width: 640 image_height: 480 distortion_parameters: k1: 0.0 k2: 0.0 p1: 0.0 p2: 0.0 projection_parameters: fx: 385.7544860839844 fy: 385.7544860839844 cx: 323.1204833984375 cy: 236.7432098388672 # Extrinsic parameter between IMU and Camera. estimate_extrinsic: 2 # 0 Have an accurate extrinsic parameters. We will trust the following imu^R_cam, imu^T_cam, don't change it. # 1 Have an initial guess about extrinsic parameters. We will optimize around your initial guess. # 2 Don't know anything about extrinsic parameters. You don't need to give R,T. We will try to calibrate it. Do some rotation movement at beginning. #If you choose 0 or 1, you should write down the following matrix. #Rotation from camera frame to imu frame, imu^R_cam extrinsicRotation: !!opencv-matrix rows: 3 cols: 3 dt: d data: [ -1.0406548080980293e-02, -6.8339359975994052e-04, 9.9994561688635142e-01, -9.9994461366801901e-01, -1.5656625000493030e-03, -1.0407607662295648e-02, 1.5726898469127781e-03, -9.9999854083599726e-01, -6.6706260700977182e-04 ] extrinsicTranslation: !!opencv-matrix rows: 3 cols: 1 dt: d data: [ 5.2600599061337287e-02, 2.5510536758700533e-02, 9.5887981235633201e-03 ] #feature traker paprameters max_cnt: 200 # max feature number in feature tracking min_dist: 25 # min distance between two features freq: 15 # frequence (Hz) of publish tracking result. At least 10Hz for good estimation. If set 0, the frequence will be same as raw image F_threshold: 1.0 # ransac threshold (pixel) show_track: 1 # publish tracking image as topic equalize: 1 # if image is too dark or light, trun on equalize to find enough features fisheye: 0 # if using fisheye, trun on it. A circle mask will be loaded to remove edge noisy points #optimization parameters max_solver_time: 0.04 # max solver itration time (ms), to guarantee real time max_num_iterations: 8 # max solver itrations, to guarantee real time keyframe_parallax: 10.0 # keyframe selection threshold (pixel) #imu parameters The more accurate parameters you provide, the better performance acc_n: 0.05 # accelerometer measurement noise standard deviation. #0.2 0.04 gyr_n: 0.01 # gyroscope measurement noise standard deviation. #0.05 0.004 acc_w: 0.002 # accelerometer bias random work noise standard deviation. #0.02 gyr_w: 2.0e-5 # gyroscope bias random work noise standard deviation. #4.0e-5 g_norm: 9.805 # gravity magnitude #loop closure parameters loop_closure: 0 # start loop closure load_previous_pose_graph: 0 # load and reuse previous pose graph; load from 'pose_graph_save_path' fast_relocalization: 0 # useful in real-time and large project pose_graph_save_path: "/home/tony-ws1/output/pose_graph/" # save and load path #unsynchronization parameters estimate_td: 1 # online estimate time offset between camera and imu td: 0.038 # initial value of time offset. unit: s. readed image clock + td = real image clock (IMU clock) #rolling shutter parameters rolling_shutter: 0 # 0: global shutter camera, 1: rolling shutter camera rolling_shutter_tr: 0 # unit: s. rolling shutter read out time per frame (from data sheet). #visualization parameters save_image: 1 # save image in pose graph for visualization prupose; you can close this function by setting 0 visualize_imu_forward: 0 # output imu forward propogation to achieve low latency and high frequence results visualize_camera_size: 0.4 # size of camera marker in RVIZ ================================================ FILE: launch/debugging_vinsmono.launch ================================================ ================================================ FILE: launch/euroc_vinsfusion.launch ================================================ ================================================ FILE: launch/keras_server.launch ================================================ ================================================ FILE: launch/mynteye_vinsfusion.launch ================================================ ================================================ FILE: launch/mynteye_vinsfusion_loadstate.launch ================================================ ================================================ FILE: launch/mynteye_vinsfusion_ondrone.launch ================================================ ================================================ FILE: launch/mynteye_vinsfusion_savestate.launch ================================================ ================================================ FILE: launch/realsense_camera.launch ================================================ /camera/stereo_module/emitter_enabled: false ================================================ FILE: launch/realsense_vinsfusion.launch ================================================ ================================================ FILE: launch/realsense_vinsfusion_ondrone_repeat.launch ================================================ ================================================ FILE: launch/realsense_vinsfusion_ondrone_teach.launch ================================================ ================================================ FILE: msg/LoopEdge.msg ================================================ time timestamp0 time timestamp1 geometry_msgs/Pose pose_1T0 # pose of 0 as observed from 1. float32 weight string description ================================================ FILE: package.xml ================================================ cerebro 0.0.1 The cerebro package. There is a multi-threaded node which stores all the incoming info from vins. There is a service to compute image level descriptor. Geometry is also a separate service. mpkuse TODO catkin cv_bridge image_transport roscpp rospy std_msgs message_generation cv_bridge image_transport roscpp rospy std_msgs message_runtime ================================================ FILE: rviz/dev-config.rviz ================================================ Panels: - Class: rviz/Displays Help Height: 78 Name: Displays Property Tree Widget: Expanded: - /Global Options1 - /Status1 - /Ref1 - /Ref1/Grid1 - /Feat Tracker1/Input Image1 - /Feat Tracker1/Input Image1/Status1 - /VIO1 - /VIO1/Odom In Cam-frame-of-ref1/Cam Odometry1 - /Cerebro1 - /Cerebro1/Marker1 - /Cerebro1/Marker1/Namespaces1 - /AR_demo1 - /AR_demo1/AR_image_odm1/Status1 - /Solve Pose Graph Opt1 - /Solve Pose Graph Opt1/Marker1 - /Solve Pose Graph Opt1/Marker1/Status1 - /Solve Pose Graph Opt1/Marker1/Namespaces1 - /Solve Pose Graph Opt1/Debug Node Marker1/Namespaces1 Splitter Ratio: 0.62647754 Tree Height: 697 - Class: rviz/Selection Name: Selection - Class: rviz/Tool Properties Expanded: - /2D Pose Estimate1 - /2D Nav Goal1 - /Publish Point1 Name: Tool Properties Splitter Ratio: 0.588679016 - Class: rviz/Time Experimental: false Name: Time SyncMode: 0 SyncSource: Tracked Features - Class: rviz/Displays Help Height: 78 Name: Displays Property Tree Widget: Expanded: ~ Splitter Ratio: 0.5 Tree Height: 141 Visualization Manager: Class: "" Displays: - Class: rviz/Group Displays: - Class: rviz/Axes Enabled: true Length: 1 Name: Axes Radius: 0.200000003 Reference Frame: Value: true - Alpha: 0.5 Cell Size: 1 Class: rviz/Grid Color: 160; 160; 164 Enabled: true Line Style: Line Width: 0.0299999993 Value: Lines Name: Grid Normal Cell Count: 0 Offset: X: 0 Y: 0 Z: 0 Plane: XY Plane Cell Count: 20 Reference Frame: Value: true Enabled: true Name: Ref - Class: rviz/Group Displays: - Class: rviz/Image Enabled: false Image Topic: /cam0/image_raw Max Value: 1 Median window: 5 Min Value: 0 Name: Input Image Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: false - Class: rviz/Image Enabled: true Image Topic: /feature_tracker/feature_img Max Value: 1 Median window: 5 Min Value: 0 Name: Tracked Features Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true Enabled: true Name: Feat Tracker - Class: rviz/Group Displays: - Alpha: 1 Buffer Length: 1 Class: rviz/Path Color: 255; 85; 255 Enabled: true Head Diameter: 0.300000012 Head Length: 0.200000003 Length: 0.300000012 Line Style: Lines Line Width: 0.0299999993 Name: VIO Path Offset: X: 0 Y: 0 Z: 0 Pose Style: None Radius: 0.0299999993 Shaft Diameter: 0.100000001 Shaft Length: 0.100000001 Topic: /vins_estimator/path Unreliable: false Value: true - Class: rviz/MarkerArray Enabled: true Marker Topic: /vins_estimator/camera_pose_visual Name: Visual Namespaces: {} Queue Size: 100 Value: true - Alpha: 1 Autocompute Intensity Bounds: true Autocompute Value Bounds: Max Value: 1.46719348 Min Value: -0.613372922 Value: true Axis: Z Channel Name: intensity Class: rviz/PointCloud Color: 0; 255; 0 Color Transformer: FlatColor Decay Time: 100 Enabled: false Invert Rainbow: false Max Color: 255; 255; 255 Max Intensity: 4096 Min Color: 0; 0; 0 Min Intensity: 0 Name: KF PointCloud Position Transformer: XYZ Queue Size: 100 Selectable: true Size (Pixels): 3 Size (m): 0.00999999978 Style: Points Topic: /vins_estimator/keyframe_point Unreliable: false Use Fixed Frame: true Use rainbow: true Value: false - Alpha: 1 Autocompute Intensity Bounds: true Autocompute Value Bounds: Max Value: 10 Min Value: -10 Value: true Axis: Z Channel Name: intensity Class: rviz/PointCloud Color: 255; 255; 255 Color Transformer: Intensity Decay Time: 100 Enabled: false Invert Rainbow: false Max Color: 255; 255; 255 Max Intensity: 4096 Min Color: 0; 0; 0 Min Intensity: 0 Name: PointCloud Position Transformer: XYZ Queue Size: 10 Selectable: true Size (Pixels): 3 Size (m): 0.00999999978 Style: Points Topic: /vins_estimator/point_cloud Unreliable: false Use Fixed Frame: true Use rainbow: true Value: false - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 170; 85; 255 Enabled: false Keep: 100 Length: 1 Name: imu_T_cam Position Tolerance: 0.100000001 Topic: /vins_estimator/extrinsic Value: false - Class: rviz/Group Displays: - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 255; 25; 0 Enabled: true Keep: 100 Length: 1 Name: Cam Odometry Position Tolerance: 0.100000001 Topic: /vins_estimator/camera_pose Value: true Enabled: false Name: Odom In Cam-frame-of-ref - Class: rviz/Group Displays: - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 0; 255; 0 Enabled: true Keep: 100 Length: 1 Name: KF Odometry Position Tolerance: 0.100000001 Topic: /vins_estimator/keyframe_pose Value: true - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 0; 170; 255 Enabled: true Keep: 100 Length: 1 Name: IMU Odometry Position Tolerance: 0.100000001 Topic: /vins_estimator/odometry Value: true - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 255; 25; 0 Enabled: true Keep: 100 Length: 1 Name: IMU Prop 200hz Position Tolerance: 0.100000001 Topic: /vins_estimator/imu_propagate Value: true Enabled: false Name: Odom In IMU-Frame-of-ref - Class: rviz/Group Displays: - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 255; 25; 0 Enabled: false Keep: 100 Length: 1 Name: vinsmono-reloc-odom Position Tolerance: 0.100000001 Topic: /vins_estimator/relo_relative_pose Value: false - Alpha: 1 Buffer Length: 1 Class: rviz/Path Color: 255; 85; 255 Enabled: true Head Diameter: 0.300000012 Head Length: 0.200000003 Length: 0.300000012 Line Style: Lines Line Width: 0.0299999993 Name: vinsmono-relocalized_path Offset: X: 0 Y: 0 Z: 0 Pose Style: None Radius: 0.0299999993 Shaft Diameter: 0.100000001 Shaft Length: 0.100000001 Topic: /vins_estimator/relocalization_path Unreliable: false Value: true - Class: rviz/Image Enabled: true Image Topic: /pose_graph/match_image Max Value: 1 Median window: 5 Min Value: 0 Name: vins-monomatch-images Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/MarkerArray Enabled: true Marker Topic: /pose_graph/pose_graph Name: vinsmono loop viz Namespaces: {} Queue Size: 100 Value: true Enabled: false Name: vins-mono relocalized Enabled: false Name: VIO - Class: rviz/Group Displays: - Class: rviz/Marker Enabled: false Marker Topic: /cerebro_node/viz/framedata Name: Marker Namespaces: {} Queue Size: 100 Value: false - Class: rviz/Image Enabled: true Image Topic: /cerebro_node/viz/imagepaire Max Value: 1 Median window: 5 Min Value: 0 Name: imagepaire Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true Enabled: true Name: Cerebro - Class: rviz/Group Displays: - Class: rviz/Image Enabled: true Image Topic: /ar_demo_node3_corrected/AR_image_corrected Max Value: 1 Median window: 5 Min Value: 0 Name: AR_image_corrected Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/Image Enabled: true Image Topic: /ar_demo_node3_odom/AR_image_odom Max Value: 1 Median window: 5 Min Value: 0 Name: AR_image_odm Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/MarkerArray Enabled: false Marker Topic: /points_and_lines/AR_object Name: AR_object Namespaces: {} Queue Size: 100 Value: false - Class: rviz/InteractiveMarkers Enable Transparency: true Enabled: true Name: InteractiveMarkers Show Axes: false Show Descriptions: true Show Visual Aids: false Update Topic: /interactive_marker_server/update Value: true Enabled: true Name: AR_demo - Class: rviz/Group Displays: - Class: rviz/Marker Enabled: true Marker Topic: /keyframe_pose_graph_slam_node/viz/visualization_marker Name: Marker Namespaces: latest_odometry: true world##_cam_visual: true world#-1: true world#-2: true world#-3: true world#-4: true world#0: true world#1: true world#2: true world#3: true world#4: true Queue Size: 100 Value: true - Class: rviz/Marker Enabled: false Marker Topic: /debug_pose_graph_solver/visualization_marker Name: Debug Node Marker Namespaces: {} Queue Size: 100 Value: false Enabled: true Name: Solve Pose Graph Opt Enabled: true Global Options: Background Color: 48; 48; 48 Fixed Frame: world Frame Rate: 30 Name: root Tools: - Class: rviz/Interact Hide Inactive Objects: true - Class: rviz/MoveCamera - Class: rviz/Select - Class: rviz/FocusCamera - Class: rviz/Measure - Class: rviz/SetInitialPose Topic: /initialpose - Class: rviz/SetGoal Topic: /move_base_simple/goal - Class: rviz/PublishPoint Single click: true Topic: /clicked_point Value: true Views: Current: Class: rviz/Orbit Distance: 46.7297897 Enable Stereo Rendering: Stereo Eye Separation: 0.0599999987 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false Focal Point: X: 3.76500964 Y: 2.58213377 Z: -2.90765786 Focal Shape Fixed Size: false Focal Shape Size: 0.0500000007 Name: Current View Near Clip Distance: 0.00999999978 Pitch: 1.0647974 Target Frame: Value: Orbit (rviz) Yaw: 4.69420815 Saved: - Class: rviz/Orbit Distance: 49.6446877 Enable Stereo Rendering: Stereo Eye Separation: 0.0599999987 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false Focal Point: X: 22.8474731 Y: 20.4583664 Z: -6.84013748 Focal Shape Fixed Size: true Focal Shape Size: 0.0500000007 Name: Orbit Near Clip Distance: 0.00999999978 Pitch: 1.16479671 Target Frame: Value: Orbit (rviz) Yaw: 5.02046442 Window Geometry: AR_image_corrected: collapsed: false AR_image_odm: collapsed: false Displays: collapsed: false Height: 1003 Hide Left Dock: false Hide Right Dock: false Input Image: collapsed: false QMainWindow State: 000000ff00000000fd00000004000000000000016a00000348fc0200000017fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006400fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afc00000041000001e00000000000fffffffa000000000100000001fb0000000a0049006d0061006700650000000000ffffffff0000000000000000fb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000a0049006d00610067006501000000410000005f0000000000000000fb0000000a0049006d00610067006501000002c4000000c60000000000000000fb0000001a0043006c0075007300740065007200200041007300730067006e0100000357000000160000000000000000fb0000002c0050006f007300650020004700720061007000680020004d006100740063006800200049006d0061006700650100000373000000160000000000000000fb0000001e0043006f006c006f0063006100740069006f006e002000500061006900720000000000ffffffff0000000000000000fb0000000c004600720061006d006500730100000041000000420000000000000000fb000000160049006e00700075007400200049006d0061006700650000000041000001670000001600fffffffb000000200054007200610063006b006500640020004600650061007400750072006500730100000041000000a60000001600fffffffb0000000a0049006d00610067006501000002c3000000c60000000000000000fb0000001800410052005f0069006d006100670065005f006f0064006d01000000ed000001110000001600fffffffb0000001a004c006f006f007000430061006e0064006900640061007400650100000373000000160000000000000000fb000000140069006d0061006700650070006100690072006501000002040000009d0000001600fffffffb0000002400410052005f0069006d006100670065005f0063006f007200720065006300740065006401000002a7000000e20000001600fffffffb0000002a00760069006e0073002d006d006f006e006f006d0061007400630068002d0069006d006100670065007300000002c4000000c50000001600fffffffb000000100044006900730070006c006100790073020000021a000002b40000016c0000011e00000001000001b900000348fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fc0000004100000348000000dd00fffffffa000000000100000002fb000000100044006900730070006c0061007900730100000000ffffffff0000016a00fffffffb0000000a00560069006500770073010000066f0000010f0000000000000000fb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e100000197000000030000077e0000003efc0100000002fb0000000800540069006d006501000000000000077e0000030000fffffffb0000000800540069006d006501000000000000045000000000000000000000044f0000034800000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 Selection: collapsed: false Time: collapsed: false Tool Properties: collapsed: false Tracked Features: collapsed: false Width: 1918 X: 0 Y: 24 imagepaire: collapsed: false vins-monomatch-images: collapsed: false ================================================ FILE: rviz/good-viz.rviz ================================================ Panels: - Class: rviz/Displays Help Height: 78 Name: Displays Property Tree Widget: Expanded: - /Global Options1 - /Status1 - /Ref1 - /Ref1/Axes1 - /Feat Tracker1/Input Image1 - /Feat Tracker1/Input Image1/Status1 - /VIO1 - /VIO1/Odom In Cam-frame-of-ref1/Cam Odometry1 - /Cerebro1 - /Cerebro1/Marker1 - /Cerebro1/Marker1/Namespaces1 - /AR_demo1 - /Solve Pose Graph Opt1 - /Solve Pose Graph Opt1/Marker1 - /Solve Pose Graph Opt1/Marker1/Namespaces1 - /Solve Pose Graph Opt1/Debug Node Marker1/Namespaces1 - /Ref21 - /Ref21/Grid1/Offset1 Splitter Ratio: 0.62647754 Tree Height: 805 - Class: rviz/Selection Name: Selection - Class: rviz/Tool Properties Expanded: - /2D Pose Estimate1 - /2D Nav Goal1 - /Publish Point1 Name: Tool Properties Splitter Ratio: 0.588679016 - Class: rviz/Time Experimental: false Name: Time SyncMode: 0 SyncSource: Tracked Features - Class: rviz/Displays Help Height: 78 Name: Displays Property Tree Widget: Expanded: ~ Splitter Ratio: 0.5 Tree Height: 359 Visualization Manager: Class: "" Displays: - Class: rviz/Group Displays: - Class: rviz/Axes Enabled: true Length: 1 Name: Axes Radius: 0.200000003 Reference Frame: Value: true - Alpha: 0.5 Cell Size: 1 Class: rviz/Grid Color: 160; 160; 164 Enabled: true Line Style: Line Width: 0.0299999993 Value: Lines Name: Grid Normal Cell Count: 0 Offset: X: 0 Y: 0 Z: 0 Plane: XY Plane Cell Count: 20 Reference Frame: Value: true Enabled: true Name: Ref - Class: rviz/Group Displays: - Class: rviz/Image Enabled: false Image Topic: /cam0/image_raw Max Value: 1 Median window: 5 Min Value: 0 Name: Input Image Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: false - Class: rviz/Image Enabled: true Image Topic: /feature_tracker/feature_img Max Value: 1 Median window: 5 Min Value: 0 Name: Tracked Features Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true Enabled: true Name: Feat Tracker - Class: rviz/Group Displays: - Alpha: 1 Buffer Length: 1 Class: rviz/Path Color: 255; 85; 255 Enabled: true Head Diameter: 0.300000012 Head Length: 0.200000003 Length: 0.300000012 Line Style: Lines Line Width: 0.0299999993 Name: VIO Path Offset: X: 0 Y: 0 Z: 0 Pose Style: None Radius: 0.0299999993 Shaft Diameter: 0.100000001 Shaft Length: 0.100000001 Topic: /vins_estimator/path Unreliable: false Value: true - Class: rviz/MarkerArray Enabled: true Marker Topic: /vins_estimator/camera_pose_visual Name: Visual Namespaces: {} Queue Size: 100 Value: true - Alpha: 1 Autocompute Intensity Bounds: true Autocompute Value Bounds: Max Value: 1.46719348 Min Value: -0.613372922 Value: true Axis: Z Channel Name: intensity Class: rviz/PointCloud Color: 0; 255; 0 Color Transformer: FlatColor Decay Time: 100 Enabled: false Invert Rainbow: false Max Color: 255; 255; 255 Max Intensity: 4096 Min Color: 0; 0; 0 Min Intensity: 0 Name: KF PointCloud Position Transformer: XYZ Queue Size: 100 Selectable: true Size (Pixels): 3 Size (m): 0.00999999978 Style: Points Topic: /vins_estimator/keyframe_point Unreliable: false Use Fixed Frame: true Use rainbow: true Value: false - Alpha: 1 Autocompute Intensity Bounds: true Autocompute Value Bounds: Max Value: 10 Min Value: -10 Value: true Axis: Z Channel Name: intensity Class: rviz/PointCloud Color: 255; 255; 255 Color Transformer: Intensity Decay Time: 100 Enabled: false Invert Rainbow: false Max Color: 255; 255; 255 Max Intensity: 4096 Min Color: 0; 0; 0 Min Intensity: 0 Name: PointCloud Position Transformer: XYZ Queue Size: 10 Selectable: true Size (Pixels): 3 Size (m): 0.00999999978 Style: Points Topic: /vins_estimator/point_cloud Unreliable: false Use Fixed Frame: true Use rainbow: true Value: false - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 170; 85; 255 Enabled: false Keep: 100 Length: 1 Name: imu_T_cam Position Tolerance: 0.100000001 Topic: /vins_estimator/extrinsic Value: false - Class: rviz/Group Displays: - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 255; 25; 0 Enabled: true Keep: 100 Length: 1 Name: Cam Odometry Position Tolerance: 0.100000001 Topic: /vins_estimator/camera_pose Value: true Enabled: false Name: Odom In Cam-frame-of-ref - Class: rviz/Group Displays: - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 0; 255; 0 Enabled: true Keep: 100 Length: 1 Name: KF Odometry Position Tolerance: 0.100000001 Topic: /vins_estimator/keyframe_pose Value: true - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 0; 170; 255 Enabled: true Keep: 100 Length: 1 Name: IMU Odometry Position Tolerance: 0.100000001 Topic: /vins_estimator/odometry Value: true - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 255; 25; 0 Enabled: true Keep: 100 Length: 1 Name: IMU Prop 200hz Position Tolerance: 0.100000001 Topic: /vins_estimator/imu_propagate Value: true Enabled: false Name: Odom In IMU-Frame-of-ref - Class: rviz/Group Displays: - Angle Tolerance: 0.100000001 Class: rviz/Odometry Color: 255; 25; 0 Enabled: false Keep: 100 Length: 1 Name: vinsmono-reloc-odom Position Tolerance: 0.100000001 Topic: /vins_estimator/relo_relative_pose Value: false - Alpha: 1 Buffer Length: 1 Class: rviz/Path Color: 255; 85; 255 Enabled: true Head Diameter: 0.300000012 Head Length: 0.200000003 Length: 0.300000012 Line Style: Lines Line Width: 0.0299999993 Name: vinsmono-relocalized_path Offset: X: 0 Y: 0 Z: 0 Pose Style: None Radius: 0.0299999993 Shaft Diameter: 0.100000001 Shaft Length: 0.100000001 Topic: /vins_estimator/relocalization_path Unreliable: false Value: true - Class: rviz/Image Enabled: true Image Topic: /pose_graph/match_image Max Value: 1 Median window: 5 Min Value: 0 Name: vins-monomatch-images Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/MarkerArray Enabled: true Marker Topic: /pose_graph/pose_graph Name: vinsmono loop viz Namespaces: {} Queue Size: 100 Value: true Enabled: false Name: vins-mono relocalized Enabled: false Name: VIO - Class: rviz/Group Displays: - Class: rviz/Marker Enabled: true Marker Topic: /cerebro_node/viz/framedata Name: Marker Namespaces: {} Queue Size: 100 Value: true - Class: rviz/Image Enabled: true Image Topic: /cerebro_node/viz/imagepaire Max Value: 1 Median window: 5 Min Value: 0 Name: imagepaire Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true Enabled: true Name: Cerebro - Class: rviz/Group Displays: - Class: rviz/Image Enabled: true Image Topic: /ar_demo_node3_corrected/AR_image_corrected Max Value: 1 Median window: 5 Min Value: 0 Name: AR_image_corrected Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/Image Enabled: true Image Topic: /ar_demo_node3_odom/AR_image_odom Max Value: 1 Median window: 5 Min Value: 0 Name: AR_image_odm Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/MarkerArray Enabled: false Marker Topic: /points_and_lines/AR_object Name: AR_object Namespaces: {} Queue Size: 100 Value: false - Class: rviz/InteractiveMarkers Enable Transparency: true Enabled: true Name: InteractiveMarkers Show Axes: false Show Descriptions: true Show Visual Aids: false Update Topic: /interactive_marker_server/update Value: true Enabled: false Name: AR_demo - Class: rviz/Group Displays: - Class: rviz/Marker Enabled: true Marker Topic: /keyframe_pose_graph_slam_node/viz/visualization_marker Name: Marker Namespaces: {} Queue Size: 100 Value: true - Class: rviz/Image Enabled: true Image Topic: /keyframe_pose_graph_slam_node/viz/disjoint_set_status_image Max Value: 1 Median window: 5 Min Value: 0 Name: disjoint_set_status_image Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/Marker Enabled: false Marker Topic: /debug_pose_graph_solver/visualization_marker Name: Debug Node Marker Namespaces: {} Queue Size: 100 Value: false Enabled: true Name: Solve Pose Graph Opt - Class: rviz/Group Displays: - Alpha: 0.5 Cell Size: 1 Class: rviz/Grid Color: 40; 164; 44 Enabled: true Line Style: Line Width: 0.0299999993 Value: Lines Name: Grid Normal Cell Count: 0 Offset: X: 30 Y: 0 Z: 0 Plane: XY Plane Cell Count: 20 Reference Frame: Value: true Enabled: true Name: Ref2 Enabled: true Global Options: Background Color: 48; 48; 48 Fixed Frame: world Frame Rate: 30 Name: root Tools: - Class: rviz/Interact Hide Inactive Objects: true - Class: rviz/MoveCamera - Class: rviz/Select - Class: rviz/FocusCamera - Class: rviz/Measure - Class: rviz/SetInitialPose Topic: /initialpose - Class: rviz/SetGoal Topic: /move_base_simple/goal - Class: rviz/PublishPoint Single click: true Topic: /clicked_point Value: true Views: Current: Class: rviz/Orbit Distance: 49.2109833 Enable Stereo Rendering: Stereo Eye Separation: 0.0599999987 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false Focal Point: X: 3.41268897 Y: -0.305300832 Z: -1.72543097 Focal Shape Fixed Size: false Focal Shape Size: 0.0500000007 Name: Current View Near Clip Distance: 0.00999999978 Pitch: 0.884797573 Target Frame: Value: Orbit (rviz) Yaw: 3.05431199 Saved: - Class: rviz/Orbit Distance: 49.6446877 Enable Stereo Rendering: Stereo Eye Separation: 0.0599999987 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false Focal Point: X: 22.8474731 Y: 20.4583664 Z: -6.84013748 Focal Shape Fixed Size: true Focal Shape Size: 0.0500000007 Name: Orbit Near Clip Distance: 0.00999999978 Pitch: 1.16479671 Target Frame: Value: Orbit (rviz) Yaw: 5.02046442 Window Geometry: AR_image_corrected: collapsed: false AR_image_odm: collapsed: false Displays: collapsed: false Height: 1003 Hide Left Dock: false Hide Right Dock: false Input Image: collapsed: false QMainWindow State: 000000ff00000000fd00000004000000000000016a000003b4fc0200000017fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006400fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afc00000041000001e00000000000fffffffa000000000100000001fb0000000a0049006d0061006700650000000000ffffffff0000000000000000fb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000a0049006d00610067006501000000410000005f0000000000000000fb0000000a0049006d00610067006501000002c4000000c60000000000000000fb0000001a0043006c0075007300740065007200200041007300730067006e0100000357000000160000000000000000fb0000002c0050006f007300650020004700720061007000680020004d006100740063006800200049006d0061006700650100000373000000160000000000000000fb0000001e0043006f006c006f0063006100740069006f006e002000500061006900720000000000ffffffff0000000000000000fb0000000c004600720061006d006500730100000041000000420000000000000000fb000000160049006e00700075007400200049006d0061006700650000000041000001670000001600fffffffb000000200054007200610063006b006500640020004600650061007400750072006500730100000019000001470000001600fffffffb0000000a0049006d00610067006501000002c3000000c60000000000000000fb0000001800410052005f0069006d006100670065005f006f0064006d000000011a0000002d0000001600fffffffb0000001a004c006f006f007000430061006e0064006900640061007400650100000373000000160000000000000000fb000000140069006d006100670065007000610069007200650100000166000001790000001600fffffffb0000002400410052005f0069006d006100670065005f0063006f00720072006500630074006500640000000202000000a00000001600fffffffb0000002a00760069006e0073002d006d006f006e006f006d0061007400630068002d0069006d006100670065007300000002c4000000c50000001600fffffffb00000032006400690073006a006f0069006e0074005f007300650074005f007300740061007400750073005f0069006d00610067006501000002e5000000e80000001600ffffff000000010000016a000003b4fc0200000004fb000000100044006900730070006c0061007900730100000019000003b4000000dd00fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fc00000041000003480000000000fffffffa000000010100000002fb000000100044006900730070006c0061007900730000000000ffffffff0000016a00fffffffb0000000a00560069006500770073010000066f0000010f0000000000000000fb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e100000197000000030000077e0000003bfc0100000002fb0000000800540069006d006500000000000000077e0000030000fffffffb0000000800540069006d006501000000000000045000000000000000000000049e000003b400000004000000040000000800000008fc000000030000000000000000000000020000000000000003000000010000000a0054006f006f006c00730000000000ffffffff0000000000000000 Selection: collapsed: false Time: collapsed: false Tool Properties: collapsed: false Tracked Features: collapsed: false Width: 1918 X: 0 Y: 24 disjoint_set_status_image: collapsed: false imagepaire: collapsed: false vins-monomatch-images: collapsed: false ================================================ FILE: rviz/vins-fusion.rviz ================================================ Panels: - Class: rviz/Displays Help Height: 78 Name: Displays Property Tree Widget: Expanded: - /Global Options1 - /Status1 - /raw_input_image1 - /Ref1 - /Ref1/Axes1 - /Ref1/Grid1 - /Feat Tracker1/Input Image1 - /AR_demo1 - /AR_demo1/AR_image_corrected1 - /AR_demo1/AR_image_odm1 - /AR_demo1/ModelViz1 - /AR_demo1/ModelViz1/Status1 - /AR_demo1/ModelViz1/Namespaces1 - /Cerebro1 - /Cerebro1/Marker1 - /Cerebro1/Marker1/Namespaces1 - /Solve Pose Graph Opt1/Marker1 - /Solve Pose Graph Opt1/Marker1/Namespaces1 - /Solve Pose Graph Opt1/Debug Node Marker1/Namespaces1 - /VINS-fusion1/MarkerArray1/Namespaces1 - /VINS-fusion1/VIO Path1 - /VINS-fusion1/VIO Path1/Offset1 - /VINS-fusion1/After Loop Fusion1 - /VINS-fusion1/Loop Fusion Edges1 - /Marker1 - /Surfel Mapping1/PointCloud21 Splitter Ratio: 0.62647754 Tree Height: 853 - Class: rviz/Selection Name: Selection - Class: rviz/Tool Properties Expanded: - /2D Pose Estimate1 - /2D Nav Goal1 - /Publish Point1 Name: Tool Properties Splitter Ratio: 0.588679016 - Class: rviz/Time Experimental: false Name: Time SyncMode: 0 SyncSource: raw_input_image - Class: rviz/Displays Help Height: 78 Name: Displays Property Tree Widget: Expanded: ~ Splitter Ratio: 0.5 Tree Height: 359 - Class: rviz/Views Expanded: - /Current View1 Name: Views Splitter Ratio: 0.5 Visualization Manager: Class: "" Displays: - Class: rviz/Image Enabled: true Image Topic: /camera/infra1/image_rect_raw Max Value: 1 Median window: 5 Min Value: 0 Name: raw_input_image Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/Group Displays: - Class: rviz/Axes Enabled: false Length: 1 Name: Axes Radius: 0.200000003 Reference Frame: Value: false - Alpha: 0.5 Cell Size: 1 Class: rviz/Grid Color: 160; 160; 164 Enabled: true Line Style: Line Width: 0.0299999993 Value: Lines Name: Grid Normal Cell Count: 0 Offset: X: 0 Y: 0 Z: 0 Plane: XY Plane Cell Count: 20 Reference Frame: Value: true Enabled: true Name: Ref - Class: rviz/Group Displays: - Alpha: 0.5 Cell Size: 1 Class: rviz/Grid Color: 40; 164; 44 Enabled: true Line Style: Line Width: 0.0299999993 Value: Lines Name: Grid Normal Cell Count: 0 Offset: X: 30 Y: 0 Z: 0 Plane: XY Plane Cell Count: 20 Reference Frame: Value: true Enabled: true Name: Ref2 - Class: rviz/Group Displays: - Class: rviz/Image Enabled: true Image Topic: /cam0/image_raw Max Value: 1 Median window: 5 Min Value: 0 Name: Input Image Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/Image Enabled: true Image Topic: /feature_tracker/feature_img Max Value: 1 Median window: 5 Min Value: 0 Name: Tracked Features Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true Enabled: false Name: Feat Tracker - Class: rviz/Group Displays: - Class: rviz/Image Enabled: true Image Topic: /ar_demo_node3_odom/AR_image_corrected Max Value: 1 Median window: 5 Min Value: 0 Name: AR_image_corrected Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/Image Enabled: false Image Topic: /ar_demo_node3_odom/AR_image_odom Max Value: 1 Median window: 5 Min Value: 0 Name: AR_image_odm Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: false - Class: rviz/MarkerArray Enabled: false Marker Topic: /points_and_lines/AR_object Name: AR_object Namespaces: {} Queue Size: 100 Value: false - Alpha: 1 Autocompute Intensity Bounds: true Autocompute Value Bounds: Max Value: 10 Min Value: -10 Value: true Axis: Z Channel Name: x Class: rviz/PointCloud2 Color: 255; 255; 255 Color Transformer: Intensity Decay Time: 0 Enabled: true Invert Rainbow: false Max Color: 255; 255; 255 Max Intensity: 9.04478359 Min Color: 0; 0; 0 Min Intensity: 3.50075197 Name: debug ptcld Position Transformer: XYZ Queue Size: 10 Selectable: true Size (Pixels): 3 Size (m): 0.0199999996 Style: Flat Squares Topic: /unit_test_estimate_ground_plane/pt_cld Unreliable: false Use Fixed Frame: true Use rainbow: true Value: true - Class: rviz/Marker Enabled: true Marker Topic: /ar_demo_node3_odom/models Name: ModelViz Namespaces: {} Queue Size: 100 Value: true - Class: rviz/InteractiveMarkers Enable Transparency: true Enabled: true Name: InteractiveMarkers Show Axes: false Show Descriptions: true Show Visual Aids: false Update Topic: /interactive_marker_server/update Value: true Enabled: true Name: AR_demo - Class: rviz/Group Displays: - Class: rviz/Marker Enabled: false Marker Topic: /cerebro_node/viz/framedata Name: Marker Namespaces: {} Queue Size: 100 Value: false - Class: rviz/Image Enabled: true Image Topic: /cerebro_node/viz/imagepaire Max Value: 1 Median window: 5 Min Value: 0 Name: cerebro - imagepaire Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true Enabled: true Name: Cerebro - Class: rviz/Group Displays: - Class: rviz/Marker Enabled: true Marker Topic: /keyframe_pose_graph_slam_node/viz/visualization_marker Name: Marker Namespaces: {} Queue Size: 100 Value: true - Class: rviz/Image Enabled: true Image Topic: /keyframe_pose_graph_slam_node/viz/disjoint_set_status_image Max Value: 1 Median window: 5 Min Value: 0 Name: disjoint_set_status_image Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/Marker Enabled: false Marker Topic: /debug_pose_graph_solver/visualization_marker Name: Debug Node Marker Namespaces: {} Queue Size: 100 Value: false Enabled: true Name: Solve Pose Graph Opt - Class: rviz/Group Displays: - Class: rviz/Image Enabled: true Image Topic: /loop_fusion/match_image Max Value: 1 Median window: 5 Min Value: 0 Name: DBOW Match Image Normalize Range: true Queue Size: 2 Transport Hint: raw Unreliable: false Value: true - Class: rviz/MarkerArray Enabled: true Marker Topic: /vins_estimator/camera_pose_visual Name: MarkerArray Namespaces: {} Queue Size: 100 Value: true - Alpha: 1 Buffer Length: 1 Class: rviz/Path Color: 255; 85; 255 Enabled: true Head Diameter: 0.300000012 Head Length: 0.200000003 Length: 0.300000012 Line Style: Lines Line Width: 0.0299999993 Name: VIO Path Offset: X: 0 Y: 0 Z: 0 Pose Style: None Radius: 0.0299999993 Shaft Diameter: 0.100000001 Shaft Length: 0.100000001 Topic: /vins_estimator/path Unreliable: false Value: true - Alpha: 1 Buffer Length: 1 Class: rviz/Path Color: 255; 85; 255 Enabled: true Head Diameter: 0.300000012 Head Length: 0.200000003 Length: 0.300000012 Line Style: Lines Line Width: 0.0299999993 Name: After Loop Fusion Offset: X: 0 Y: 0 Z: 0 Pose Style: None Radius: 0.0299999993 Shaft Diameter: 0.100000001 Shaft Length: 0.100000001 Topic: /loop_fusion/pose_graph_path Unreliable: false Value: true - Class: rviz/MarkerArray Enabled: true Marker Topic: /loop_fusion/pose_graph Name: Loop Fusion Edges Namespaces: {} Queue Size: 100 Value: true Enabled: false Name: VINS-fusion - Class: rviz/Marker Enabled: false Marker Topic: /keyframe_pose_graph_slam_node/hz200/visualization_marker Name: Marker Namespaces: {} Queue Size: 100 Value: false - Class: rviz/Group Displays: - Alpha: 1 Autocompute Intensity Bounds: true Autocompute Value Bounds: Max Value: 10 Min Value: -10 Value: true Axis: Z Channel Name: z Class: rviz/PointCloud2 Color: 255; 255; 255 Color Transformer: Intensity Decay Time: 0 Enabled: true Invert Rainbow: false Max Color: 255; 255; 255 Max Intensity: 0.863711178 Min Color: 0; 0; 0 Min Intensity: -2.19427705 Name: PointCloud2 Position Transformer: XYZ Queue Size: 10 Selectable: true Size (Pixels): 3 Size (m): 0.0500000007 Style: Flat Squares Topic: /surfel_fusion/pointcloud Unreliable: false Use Fixed Frame: true Use rainbow: true Value: true - Alpha: 1 Autocompute Intensity Bounds: true Autocompute Value Bounds: Max Value: 10 Min Value: -10 Value: true Axis: Z Channel Name: intensity Class: rviz/PointCloud2 Color: 255; 255; 255 Color Transformer: Intensity Decay Time: 0 Enabled: false Invert Rainbow: false Max Color: 255; 255; 255 Max Intensity: 255 Min Color: 0; 0; 0 Min Intensity: 17 Name: current Position Transformer: XYZ Queue Size: 10 Selectable: true Size (Pixels): 3 Size (m): 0.00999999978 Style: Flat Squares Topic: /surfel_fusion/raw_pointcloud Unreliable: false Use Fixed Frame: true Use rainbow: true Value: false - Alpha: 1 Autocompute Intensity Bounds: true Autocompute Value Bounds: Max Value: 10 Min Value: -10 Value: true Axis: Z Channel Name: z Class: rviz/PointCloud2 Color: 255; 255; 255 Color Transformer: Intensity Decay Time: 0 Enabled: false Invert Rainbow: true Max Color: 255; 255; 255 Max Intensity: 1.4563067 Min Color: 0; 0; 0 Min Intensity: -1.5742451 Name: filtered Position Transformer: XYZ Queue Size: 10 Selectable: true Size (Pixels): 3 Size (m): 0.00999999978 Style: Points Topic: /map_server/filtered_map Unreliable: false Use Fixed Frame: true Use rainbow: true Value: false Enabled: true Name: Surfel Mapping Enabled: true Global Options: Background Color: 48; 48; 48 Fixed Frame: world Frame Rate: 30 Name: root Tools: - Class: rviz/Interact Hide Inactive Objects: true - Class: rviz/MoveCamera - Class: rviz/Select - Class: rviz/FocusCamera - Class: rviz/Measure - Class: rviz/SetInitialPose Topic: /initialpose - Class: rviz/SetGoal Topic: /move_base_simple/goal - Class: rviz/PublishPoint Single click: true Topic: /clicked_point Value: true Views: Current: Class: rviz/Orbit Distance: 88.2317963 Enable Stereo Rendering: Stereo Eye Separation: 0.0599999987 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false Focal Point: X: 14.8281975 Y: -0.740946651 Z: 0.155234724 Focal Shape Fixed Size: false Focal Shape Size: 0.0500000007 Name: Current View Near Clip Distance: 0.00999999978 Pitch: 1.29479516 Target Frame: Value: Orbit (rviz) Yaw: 4.72152758 Saved: - Class: rviz/Orbit Distance: 49.6446877 Enable Stereo Rendering: Stereo Eye Separation: 0.0599999987 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false Focal Point: X: 22.8474731 Y: 20.4583664 Z: -6.84013748 Focal Shape Fixed Size: true Focal Shape Size: 0.0500000007 Name: Orbit Near Clip Distance: 0.00999999978 Pitch: 1.16479671 Target Frame: Value: Orbit (rviz) Yaw: 5.02046442 Window Geometry: AR_image_corrected: collapsed: false AR_image_odm: collapsed: false DBOW Match Image: collapsed: false Displays: collapsed: false Height: 1056 Hide Left Dock: false Hide Right Dock: false Input Image: collapsed: false QMainWindow State: 000000ff00000000fd00000004000000000000016a00000402fc020000001afb0000001200530065006c0065006300740069006f006e0000000019000000840000006400fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001b500000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afc000000000000013c0000001600fffffffa000000000100000001fb0000001e007200610077005f0069006e007000750074005f0069006d0061006700650100000000ffffffff000000a800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000a0049006d0061006700650100000051000000270000000000000000fb0000000a0049006d006100670065010000007e000000510000000000000000fb0000001a0043006c0075007300740065007200200041007300730067006e0100000357000000160000000000000000fb0000002c0050006f007300650020004700720061007000680020004d006100740063006800200049006d0061006700650100000373000000160000000000000000fb0000001e0043006f006c006f0063006100740069006f006e002000500061006900720000000000ffffffff0000000000000000fb0000000c004600720061006d006500730100000041000000420000000000000000fb000000160049006e00700075007400200049006d0061006700650000000041000001670000001600fffffffb000000200054007200610063006b006500640020004600650061007400750072006500730000000019000001340000001600fffffffb0000000a0049006d00610067006501000002c3000000c60000000000000000fb0000001a004c006f006f007000430061006e0064006900640061007400650100000373000000160000000000000000fb00000028006300650072006500620072006f0020002d00200069006d006100670065007000610069007200650100000142000000ce0000001600fffffffb00000032006400690073006a006f0069006e0074005f007300650074005f007300740061007400750073005f0069006d0061006700650100000216000000e20000001600fffffffb0000001800410052005f0069006d006100670065005f006f0064006d000000025b000000d60000001600fffffffb0000002400410052005f0069006d006100670065005f0063006f007200720065006300740065006401000002fe000001040000001600fffffffb0000002a00760069006e0073002d006d006f006e006f006d0061007400630068002d0069006d006100670065007300000002c4000000c50000000000000000fb00000020006c006f006f0070005f006d0061007400630068005f0049006d00610067006501000003b7000000160000000000000000fb0000000a0049006d0061006700650100000301000000cc0000000000000000fb0000002000440042004f00570020004d006100740063006800200049006d0061006700650000000334000000ce0000001600ffffff000000010000016a00000402fc0200000004fc0000000000000402000000fb0100001dfa000000010100000002fb0000000a005600690065007700730100000000ffffffff0000010f00fffffffb000000100044006900730070006c00610079007301000006140000016a0000016a00fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb000000100044006900730070006c0061007900730000000212000001bb000000dd00fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e100000197000000030000077e0000003bfc0100000002fb0000000800540069006d006500000000000000077e0000030000fffffffb0000000800540069006d006501000000000000045000000000000000000000045f0000040200000004000000040000000800000008fc000000030000000000000000000000020000000000000003000000010000000a0054006f006f006c00730000000000ffffffff0000000000000000 Selection: collapsed: false Time: collapsed: false Tool Properties: collapsed: false Tracked Features: collapsed: false Views: collapsed: false Width: 1855 X: 65 Y: 24 cerebro - imagepaire: collapsed: false disjoint_set_status_image: collapsed: false raw_input_image: collapsed: false ================================================ FILE: scripts/TerminalColors.py ================================================ class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' # USage #``` # from TerminalColors import bcolors # tcol = bcolors() # print tcol.OKGREEN, "hello in green color", tcol.ENDC #``` ================================================ FILE: scripts/keras.models/.gitignore ================================================ ================================================ FILE: scripts/keras.models/Apr2019/gray_conv6_K16__centeredinput/model.json ================================================ "{\"class_name\": \"Model\", \"keras_version\": \"2.2.4\", \"config\": {\"layers\": [{\"class_name\": \"InputLayer\", \"config\": {\"dtype\": \"float32\", \"batch_input_shape\": [null, 240, 320, 1], \"name\": \"input_1\", \"sparse\": false}, \"inbound_nodes\": [], \"name\": \"input_1\"}, {\"class_name\": \"ZeroPadding2D\", \"config\": {\"padding\": [[0, 1], [0, 1]], \"trainable\": true, \"name\": \"conv1_pad\", \"data_format\": \"channels_last\"}, \"inbound_nodes\": [[[\"input_1\", 0, 0, {}]]], \"name\": \"conv1_pad\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv1\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"valid\", \"strides\": [2, 2], \"dilation_rate\": [1, 1], \"kernel_regularizer\": {\"class_name\": \"L1L2\", \"config\": {\"l2\": 9.999999747378752e-05, \"l1\": 0.0}}, \"filters\": 32, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv1_pad\", 0, 0, {}]]], \"name\": \"conv1\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv1_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv1\", 0, 0, {}]]], \"name\": \"conv1_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv1_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv1_bn\", 0, 0, {}]]], \"name\": \"conv1_relu\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_1\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv1_relu\", 0, 0, {}]]], \"name\": \"conv_dw_1\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_1_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_1\", 0, 0, {}]]], \"name\": \"conv_dw_1_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_1_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_1_bn\", 0, 0, {}]]], \"name\": \"conv_dw_1_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_1\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": {\"class_name\": \"L1L2\", \"config\": {\"l2\": 9.999999747378752e-05, \"l1\": 0.0}}, \"filters\": 64, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_1_relu\", 0, 0, {}]]], \"name\": \"conv_pw_1\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_1_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_1\", 0, 0, {}]]], \"name\": \"conv_pw_1_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_1_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_1_bn\", 0, 0, {}]]], \"name\": \"conv_pw_1_relu\"}, {\"class_name\": \"ZeroPadding2D\", \"config\": {\"padding\": [[0, 1], [0, 1]], \"trainable\": true, \"name\": \"conv_pad_2\", \"data_format\": \"channels_last\"}, \"inbound_nodes\": [[[\"conv_pw_1_relu\", 0, 0, {}]]], \"name\": \"conv_pad_2\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_2\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"valid\", \"strides\": [2, 2], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pad_2\", 0, 0, {}]]], \"name\": \"conv_dw_2\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_2_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_2\", 0, 0, {}]]], \"name\": \"conv_dw_2_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_2_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_2_bn\", 0, 0, {}]]], \"name\": \"conv_dw_2_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_2\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": {\"class_name\": \"L1L2\", \"config\": {\"l2\": 9.999999747378752e-05, \"l1\": 0.0}}, \"filters\": 128, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_2_relu\", 0, 0, {}]]], \"name\": \"conv_pw_2\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_2_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_2\", 0, 0, {}]]], \"name\": \"conv_pw_2_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_2_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_2_bn\", 0, 0, {}]]], \"name\": \"conv_pw_2_relu\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_3\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pw_2_relu\", 0, 0, {}]]], \"name\": \"conv_dw_3\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_3_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_3\", 0, 0, {}]]], \"name\": \"conv_dw_3_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_3_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_3_bn\", 0, 0, {}]]], \"name\": \"conv_dw_3_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_3\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": {\"class_name\": \"L1L2\", \"config\": {\"l2\": 9.999999747378752e-05, \"l1\": 0.0}}, \"filters\": 128, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_3_relu\", 0, 0, {}]]], \"name\": \"conv_pw_3\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_3_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_3\", 0, 0, {}]]], \"name\": \"conv_pw_3_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_3_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_3_bn\", 0, 0, {}]]], \"name\": \"conv_pw_3_relu\"}, {\"class_name\": \"ZeroPadding2D\", \"config\": {\"padding\": [[0, 1], [0, 1]], \"trainable\": true, \"name\": \"conv_pad_4\", \"data_format\": \"channels_last\"}, \"inbound_nodes\": [[[\"conv_pw_3_relu\", 0, 0, {}]]], \"name\": \"conv_pad_4\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_4\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"valid\", \"strides\": [2, 2], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pad_4\", 0, 0, {}]]], \"name\": \"conv_dw_4\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_4_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_4\", 0, 0, {}]]], \"name\": \"conv_dw_4_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_4_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_4_bn\", 0, 0, {}]]], \"name\": \"conv_dw_4_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_4\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": {\"class_name\": \"L1L2\", \"config\": {\"l2\": 9.999999747378752e-05, \"l1\": 0.0}}, \"filters\": 256, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_4_relu\", 0, 0, {}]]], \"name\": \"conv_pw_4\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_4_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_4\", 0, 0, {}]]], \"name\": \"conv_pw_4_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_4_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_4_bn\", 0, 0, {}]]], \"name\": \"conv_pw_4_relu\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_5\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pw_4_relu\", 0, 0, {}]]], \"name\": \"conv_dw_5\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_5_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_5\", 0, 0, {}]]], \"name\": \"conv_dw_5_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_5_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_5_bn\", 0, 0, {}]]], \"name\": \"conv_dw_5_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_5\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": {\"class_name\": \"L1L2\", \"config\": {\"l2\": 9.999999747378752e-05, \"l1\": 0.0}}, \"filters\": 256, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_5_relu\", 0, 0, {}]]], \"name\": \"conv_pw_5\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_5_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_5\", 0, 0, {}]]], \"name\": \"conv_pw_5_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_5_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_5_bn\", 0, 0, {}]]], \"name\": \"conv_pw_5_relu\"}, {\"class_name\": \"ZeroPadding2D\", \"config\": {\"padding\": [[0, 1], [0, 1]], \"trainable\": true, \"name\": \"conv_pad_6\", \"data_format\": \"channels_last\"}, \"inbound_nodes\": [[[\"conv_pw_5_relu\", 0, 0, {}]]], \"name\": \"conv_pad_6\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_6\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"valid\", \"strides\": [2, 2], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pad_6\", 0, 0, {}]]], \"name\": \"conv_dw_6\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_6_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_6\", 0, 0, {}]]], \"name\": \"conv_dw_6_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_6_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_6_bn\", 0, 0, {}]]], \"name\": \"conv_dw_6_relu\"}, {\"class_name\": \"NetVLADLayer\", \"config\": {\"num_clusters\": 16, \"trainable\": true, \"name\": \"net_vlad_layer_1\"}, \"inbound_nodes\": [[[\"conv_dw_6_relu\", 0, 0, {}]]], \"name\": \"net_vlad_layer_1\"}], \"input_layers\": [[\"input_1\", 0, 0]], \"output_layers\": [[\"net_vlad_layer_1\", 0, 0]], \"name\": \"model_1\"}, \"backend\": \"tensorflow\"}" ================================================ FILE: scripts/keras.models/README.md ================================================ # Pretrained Models for Place Recognition. This folder contains tested models for place recognition. The models have been tested for real-time performance. the files a) mobilenet_conv7_allpairloss.keras b) model.json c) modelarch_and_weights.h5 d) core_model.keras e) xcore.png are associated with the default model. ================================================ FILE: scripts/keras.models/model.json ================================================ "{\"class_name\": \"Model\", \"keras_version\": \"2.2.4\", \"config\": {\"layers\": [{\"class_name\": \"InputLayer\", \"config\": {\"dtype\": \"float32\", \"batch_input_shape\": [null, 480, 752, 3], \"name\": \"input_1\", \"sparse\": false}, \"inbound_nodes\": [], \"name\": \"input_1\"}, {\"class_name\": \"ZeroPadding2D\", \"config\": {\"padding\": [[0, 1], [0, 1]], \"trainable\": true, \"name\": \"conv1_pad\", \"data_format\": \"channels_last\"}, \"inbound_nodes\": [[[\"input_1\", 0, 0, {}]]], \"name\": \"conv1_pad\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv1\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"valid\", \"strides\": [2, 2], \"dilation_rate\": [1, 1], \"kernel_regularizer\": null, \"filters\": 32, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv1_pad\", 0, 0, {}]]], \"name\": \"conv1\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv1_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv1\", 0, 0, {}]]], \"name\": \"conv1_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv1_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv1_bn\", 0, 0, {}]]], \"name\": \"conv1_relu\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_1\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv1_relu\", 0, 0, {}]]], \"name\": \"conv_dw_1\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_1_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_1\", 0, 0, {}]]], \"name\": \"conv_dw_1_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_1_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_1_bn\", 0, 0, {}]]], \"name\": \"conv_dw_1_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_1\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": null, \"filters\": 64, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_1_relu\", 0, 0, {}]]], \"name\": \"conv_pw_1\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_1_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_1\", 0, 0, {}]]], \"name\": \"conv_pw_1_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_1_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_1_bn\", 0, 0, {}]]], \"name\": \"conv_pw_1_relu\"}, {\"class_name\": \"ZeroPadding2D\", \"config\": {\"padding\": [[0, 1], [0, 1]], \"trainable\": true, \"name\": \"conv_pad_2\", \"data_format\": \"channels_last\"}, \"inbound_nodes\": [[[\"conv_pw_1_relu\", 0, 0, {}]]], \"name\": \"conv_pad_2\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_2\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"valid\", \"strides\": [2, 2], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pad_2\", 0, 0, {}]]], \"name\": \"conv_dw_2\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_2_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_2\", 0, 0, {}]]], \"name\": \"conv_dw_2_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_2_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_2_bn\", 0, 0, {}]]], \"name\": \"conv_dw_2_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_2\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": null, \"filters\": 128, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_2_relu\", 0, 0, {}]]], \"name\": \"conv_pw_2\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_2_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_2\", 0, 0, {}]]], \"name\": \"conv_pw_2_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_2_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_2_bn\", 0, 0, {}]]], \"name\": \"conv_pw_2_relu\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_3\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pw_2_relu\", 0, 0, {}]]], \"name\": \"conv_dw_3\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_3_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_3\", 0, 0, {}]]], \"name\": \"conv_dw_3_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_3_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_3_bn\", 0, 0, {}]]], \"name\": \"conv_dw_3_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_3\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": null, \"filters\": 128, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_3_relu\", 0, 0, {}]]], \"name\": \"conv_pw_3\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_3_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_3\", 0, 0, {}]]], \"name\": \"conv_pw_3_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_3_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_3_bn\", 0, 0, {}]]], \"name\": \"conv_pw_3_relu\"}, {\"class_name\": \"ZeroPadding2D\", \"config\": {\"padding\": [[0, 1], [0, 1]], \"trainable\": true, \"name\": \"conv_pad_4\", \"data_format\": \"channels_last\"}, \"inbound_nodes\": [[[\"conv_pw_3_relu\", 0, 0, {}]]], \"name\": \"conv_pad_4\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_4\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"valid\", \"strides\": [2, 2], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pad_4\", 0, 0, {}]]], \"name\": \"conv_dw_4\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_4_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_4\", 0, 0, {}]]], \"name\": \"conv_dw_4_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_4_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_4_bn\", 0, 0, {}]]], \"name\": \"conv_dw_4_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_4\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": null, \"filters\": 256, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_4_relu\", 0, 0, {}]]], \"name\": \"conv_pw_4\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_4_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_4\", 0, 0, {}]]], \"name\": \"conv_pw_4_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_4_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_4_bn\", 0, 0, {}]]], \"name\": \"conv_pw_4_relu\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_5\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pw_4_relu\", 0, 0, {}]]], \"name\": \"conv_dw_5\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_5_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_5\", 0, 0, {}]]], \"name\": \"conv_dw_5_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_5_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_5_bn\", 0, 0, {}]]], \"name\": \"conv_dw_5_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_5\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": null, \"filters\": 256, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_5_relu\", 0, 0, {}]]], \"name\": \"conv_pw_5\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_5_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_5\", 0, 0, {}]]], \"name\": \"conv_pw_5_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_5_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_5_bn\", 0, 0, {}]]], \"name\": \"conv_pw_5_relu\"}, {\"class_name\": \"ZeroPadding2D\", \"config\": {\"padding\": [[0, 1], [0, 1]], \"trainable\": true, \"name\": \"conv_pad_6\", \"data_format\": \"channels_last\"}, \"inbound_nodes\": [[[\"conv_pw_5_relu\", 0, 0, {}]]], \"name\": \"conv_pad_6\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_6\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"valid\", \"strides\": [2, 2], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pad_6\", 0, 0, {}]]], \"name\": \"conv_dw_6\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_6_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_6\", 0, 0, {}]]], \"name\": \"conv_dw_6_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_6_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_6_bn\", 0, 0, {}]]], \"name\": \"conv_dw_6_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_6\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": null, \"filters\": 512, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_6_relu\", 0, 0, {}]]], \"name\": \"conv_pw_6\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_6_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_6\", 0, 0, {}]]], \"name\": \"conv_pw_6_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_6_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_6_bn\", 0, 0, {}]]], \"name\": \"conv_pw_6_relu\"}, {\"class_name\": \"DepthwiseConv2D\", \"config\": {\"use_bias\": false, \"trainable\": true, \"name\": \"conv_dw_7\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"depth_multiplier\": 1, \"depthwise_constraint\": null, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"depthwise_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"depthwise_regularizer\": null, \"activity_regularizer\": null, \"kernel_size\": [3, 3]}, \"inbound_nodes\": [[[\"conv_pw_6_relu\", 0, 0, {}]]], \"name\": \"conv_dw_7\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_dw_7_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_dw_7\", 0, 0, {}]]], \"name\": \"conv_dw_7_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_dw_7_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_dw_7_bn\", 0, 0, {}]]], \"name\": \"conv_dw_7_relu\"}, {\"class_name\": \"Conv2D\", \"config\": {\"kernel_constraint\": null, \"kernel_initializer\": {\"class_name\": \"VarianceScaling\", \"config\": {\"distribution\": \"uniform\", \"scale\": 1.0, \"seed\": null, \"mode\": \"fan_avg\"}}, \"name\": \"conv_pw_7\", \"bias_regularizer\": null, \"bias_constraint\": null, \"activation\": \"linear\", \"trainable\": true, \"data_format\": \"channels_last\", \"padding\": \"same\", \"strides\": [1, 1], \"dilation_rate\": [1, 1], \"kernel_regularizer\": null, \"filters\": 512, \"bias_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"use_bias\": false, \"activity_regularizer\": null, \"kernel_size\": [1, 1]}, \"inbound_nodes\": [[[\"conv_dw_7_relu\", 0, 0, {}]]], \"name\": \"conv_pw_7\"}, {\"class_name\": \"BatchNormalization\", \"config\": {\"beta_constraint\": null, \"gamma_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"moving_mean_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"name\": \"conv_pw_7_bn\", \"epsilon\": 0.001, \"trainable\": true, \"moving_variance_initializer\": {\"class_name\": \"Ones\", \"config\": {}}, \"beta_initializer\": {\"class_name\": \"Zeros\", \"config\": {}}, \"scale\": true, \"axis\": -1, \"gamma_constraint\": null, \"gamma_regularizer\": null, \"beta_regularizer\": null, \"momentum\": 0.99, \"center\": true}, \"inbound_nodes\": [[[\"conv_pw_7\", 0, 0, {}]]], \"name\": \"conv_pw_7_bn\"}, {\"class_name\": \"ReLU\", \"config\": {\"threshold\": 0.0, \"max_value\": 6.0, \"trainable\": true, \"name\": \"conv_pw_7_relu\", \"negative_slope\": 0.0}, \"inbound_nodes\": [[[\"conv_pw_7_bn\", 0, 0, {}]]], \"name\": \"conv_pw_7_relu\"}, {\"class_name\": \"NetVLADLayer\", \"config\": {\"num_clusters\": 16, \"trainable\": true, \"name\": \"net_vlad_layer_1\"}, \"inbound_nodes\": [[[\"conv_pw_7_relu\", 0, 0, {}]]], \"name\": \"net_vlad_layer_1\"}], \"input_layers\": [[\"input_1\", 0, 0]], \"output_layers\": [[\"net_vlad_layer_1\", 0, 0]], \"name\": \"model_1\"}, \"backend\": \"tensorflow\"}" ================================================ FILE: scripts/keras_helpers.py ================================================ from keras import backend as K from keras.engine.topology import Layer import keras import code import numpy as np import cv2 import code #--------------------------------------------------------------------------------- # My Layers # NetVLADLayer #--------------------------------------------------------------------------------- # # Writing your own custom layers # class MyLayer(Layer): # # def __init__(self, output_dim, **kwargs): # self.output_dim = output_dim # super(MyLayer, self).__init__(**kwargs) # # def build(self, input_shape): # # Create a trainable weight variable for this layer. # self.kernel = self.add_weight(name='kernel', # shape=(input_shape[1], self.output_dim), # initializer='uniform', # trainable=True) # super(MyLayer, self).build(input_shape) # Be sure to call this at the end # # def call(self, x): # return [K.dot(x, self.kernel), K.dot(x, self.kernel)] # # def compute_output_shape(self, input_shape): # return [(input_shape[0], self.output_dim), (input_shape[0], self.output_dim)] # # # class NetVLADLayer( Layer ): # # def __init__( self, num_clusters, **kwargs ): # self.num_clusters = num_clusters # super(NetVLADLayer, self).__init__(**kwargs) # # def build( self, input_shape ): # self.K = self.num_clusters # self.D = input_shape[-1] # # self.kernel = self.add_weight( name='kernel', # shape=(1,1,self.D,self.K), # initializer='uniform', # trainable=True ) # # self.bias = self.add_weight( name='bias', # shape=(1,1,self.K), # initializer='uniform', # trainable=True ) # # self.C = self.add_weight( name='cluster_centers', # shape=[1,1,1,self.D,self.K], # initializer='uniform', # trainable=True) # # def call( self, x ): # # soft-assignment. # s = K.conv2d( x, self.kernel, padding='same' ) + self.bias # a = K.softmax( s ) # self.amap = K.argmax( a, -1 ) # print 'amap.shape', self.amap.shape # # # Dims used hereafter: batch, H, W, desc_coeff, cluster # a = K.expand_dims( a, -2 ) # # print 'a.shape=',a.shape # # # Core # v = K.expand_dims(x, -1) + self.C # # print 'v.shape', v.shape # v = a * v # # print 'v.shape', v.shape # v = K.sum(v, axis=[1, 2]) # # print 'v.shape', v.shape # v = K.permute_dimensions(v, pattern=[0, 2, 1]) # # print 'v.shape', v.shape # #v.shape = None x K x D # # # Normalize v (Intra Normalization) # v = K.l2_normalize( v, axis=-1 ) # v = K.batch_flatten( v ) # v = K.l2_normalize( v, axis=-1 ) # # return [v, self.amap] # # def compute_output_shape( self, input_shape ): # # return (input_shape[0], self.v.shape[-1].value ) # # return [(input_shape[0], self.K*self.D ), (input_shape[0], self.amap.shape[1].value, self.amap.shape[2].value) ] # return [(input_shape[0], self.K*self.D ), (input_shape[0], input_shape[1], input_shape[2]) ] #-------------------------------------------------------------------------------- # Base CNNs #-------------------------------------------------------------------------------- # def make_vgg( input_img ): # r_l2=keras.regularizers.l2(0.01) # r_l1=keras.regularizers.l1(0.01) # # x_64 = keras.layers.Conv2D( 64, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( input_img ) # x_64 = keras.layers.normalization.BatchNormalization()( x_64 ) # x_64 = keras.layers.Conv2D( 64, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_64 ) # x_64 = keras.layers.normalization.BatchNormalization()( x_64 ) # x_64 = keras.layers.MaxPooling2D( pool_size=(2,2), padding='same' )( x_64 ) # # # x_128 = keras.layers.Conv2D( 128, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_64 ) # x_128 = keras.layers.normalization.BatchNormalization()( x_128 ) # x_128 = keras.layers.Conv2D( 128, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_128 ) # x_128 = keras.layers.normalization.BatchNormalization()( x_128 ) # x_128 = keras.layers.MaxPooling2D( pool_size=(2,2), padding='same' )( x_128 ) # # # # x_256 = keras.layers.Conv2D( 256, (3,3), padding='same', activation='relu' )( x_128 ) # # x_256 = keras.layers.normalization.BatchNormalization()( x_256 ) # # x_256 = keras.layers.Conv2D( 256, (3,3), padding='same', activation='relu' )( x_256 ) # # x_256 = keras.layers.normalization.BatchNormalization()( x_256 ) # # x_256 = keras.layers.MaxPooling2D( pool_size=(2,2), padding='same' )( x_256 ) # # # # # x_512 = keras.layers.Conv2D( 512, (3,3), padding='same', activation='relu' )( x_256 ) # # # BN # # x_512 = keras.layers.Conv2D( 512, (3,3), padding='same', activation='relu' )( x_512 ) # # # BN # # x_512 = keras.layers.MaxPooling2D( pool_size=(2,2), padding='same' )( x_512 ) # # # x = keras.layers.Conv2DTranspose( 32, (5,5), strides=4, padding='same' )( x_128 ) # # x = x_128 # # return x # # # def make_upsampling_vgg( input_img ): # r_l2=keras.regularizers.l2(0.01) # r_l1=keras.regularizers.l1(0.01) # # x_64 = keras.layers.Conv2D( 64, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( input_img ) # x_64 = keras.layers.normalization.BatchNormalization()( x_64 ) # x_64 = keras.layers.Conv2D( 64, (3,3), strides=2, padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_64 ) # x_64 = keras.layers.normalization.BatchNormalization()( x_64 ) # # x_128 = keras.layers.Conv2D( 128, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_64 ) # x_128 = keras.layers.normalization.BatchNormalization()( x_128 ) # x_128 = keras.layers.Conv2D( 128, (3,3), strides=2, padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_128 ) # x_128 = keras.layers.normalization.BatchNormalization()( x_128 ) # # x_256 = keras.layers.Conv2D( 128, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_128 ) # x_256 = keras.layers.normalization.BatchNormalization()( x_256 ) # x_256 = keras.layers.Conv2D( 128, (3,3), strides=2, padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_256 ) # x_256 = keras.layers.normalization.BatchNormalization()( x_256 ) # # z = keras.layers.Conv2DTranspose( 32, (11,11), strides=8, padding='same' )( x_256 ) # x = keras.layers.Conv2DTranspose( 32, (9,9), strides=4, padding='same' )( x_128 ) # y = keras.layers.Conv2DTranspose( 32, (7,7), strides=2, padding='same' )( x_64 ) # # out = keras.layers.Add()( [x,y,z] ) # return out # # def make_from_vgg19( input_img, trainable=True, weights=None ): # base_model = keras.applications.vgg19.VGG19(weights=weights, include_top=False, input_tensor=input_img) # # for l in base_model.layers: # l.trainable = trainable # # base_model_out = base_model.get_layer('block2_pool').output # # z = keras.layers.Conv2DTranspose( 32, (9,9), strides=4, padding='same' )( base_model_out ) # return z # # def make_from_vgg19_multiconvup( input_img, trainable=True, weights=None ): # base_model = keras.applications.vgg19.VGG19(weights=weights, include_top=False, input_tensor=input_img) # # for l in base_model.layers: # l.trainable = trainable # #TODO : add kernel regularizers and activity_regularizer to conv layers # # base_model_out = base_model.get_layer('block2_pool').output # # up_conv_out = keras.layers.Conv2DTranspose( 32, (9,9), strides=2, padding='same', activation='relu' )( base_model_out ) # up_conv_out = keras.layers.normalization.BatchNormalization()( up_conv_out ) # # up_conv_out = keras.layers.Conv2DTranspose( 32, (9,9), strides=2, padding='same', activation='relu' )( up_conv_out ) # up_conv_out = keras.layers.normalization.BatchNormalization()( up_conv_out ) # # # return up_conv_out # # # def make_from_mobilenet( input_img=None, weights=None ): # # input_img = keras.layers.BatchNormalization()(input_img) # # base_model = keras.applications.mobilenet.MobileNet( weights=weights, include_top=False, input_tensor=input_img ) # # base_model = keras.applications.mobilenet.MobileNet( weights=None, include_top=False, input_tensor=input_img ) # # keras.utils.plot_model( base_model, to_file='base_model.png', show_shapes=True ) # # # Pull out a layer from original network # base_model_out = base_model.get_layer( 'conv_pw_7_relu').output # can also try conv_pw_7_relu etc. # # # Up-sample # # Basic idea is to try upsampling without using the transposed-conv layers. Instead use either of # # a) upsampling2d # # b) depth to space # # followed by CBR (conv-BN-relu) # # TODO # ups_out = base_model_out # # ups_out = keras.layers.UpSampling2D( size=(4,4) )( base_model_out ) # # # model = keras.models.Model( inputs=input_img, outputs=ups_out ) # # model.summary() # # keras.utils.plot_model( model, to_file='base_model.png', show_shapes=True ) # # code.interact( local=locals() ) # # return ups_out # # #-------------------------------------------------------------------------------- # Base CNNs #-------------------------------------------------------------------------------- def make_vgg( input_img ): r_l2=keras.regularizers.l2(0.01) r_l1=keras.regularizers.l1(0.01) x_64 = keras.layers.Conv2D( 64, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( input_img ) x_64 = keras.layers.normalization.BatchNormalization()( x_64 ) x_64 = keras.layers.Conv2D( 64, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_64 ) x_64 = keras.layers.normalization.BatchNormalization()( x_64 ) x_64 = keras.layers.MaxPooling2D( pool_size=(2,2), padding='same' )( x_64 ) x_128 = keras.layers.Conv2D( 128, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_64 ) x_128 = keras.layers.normalization.BatchNormalization()( x_128 ) x_128 = keras.layers.Conv2D( 128, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_128 ) x_128 = keras.layers.normalization.BatchNormalization()( x_128 ) x_128 = keras.layers.MaxPooling2D( pool_size=(2,2), padding='same' )( x_128 ) # x_256 = keras.layers.Conv2D( 256, (3,3), padding='same', activation='relu' )( x_128 ) # x_256 = keras.layers.normalization.BatchNormalization()( x_256 ) # x_256 = keras.layers.Conv2D( 256, (3,3), padding='same', activation='relu' )( x_256 ) # x_256 = keras.layers.normalization.BatchNormalization()( x_256 ) # x_256 = keras.layers.MaxPooling2D( pool_size=(2,2), padding='same' )( x_256 ) # # x_512 = keras.layers.Conv2D( 512, (3,3), padding='same', activation='relu' )( x_256 ) # # BN # x_512 = keras.layers.Conv2D( 512, (3,3), padding='same', activation='relu' )( x_512 ) # # BN # x_512 = keras.layers.MaxPooling2D( pool_size=(2,2), padding='same' )( x_512 ) x = keras.layers.Conv2DTranspose( 32, (5,5), strides=4, padding='same' )( x_128 ) # x = x_128 return x def make_upsampling_vgg( input_img ): r_l2=keras.regularizers.l2(0.01) r_l1=keras.regularizers.l1(0.01) x_64 = keras.layers.Conv2D( 64, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( input_img ) x_64 = keras.layers.normalization.BatchNormalization()( x_64 ) x_64 = keras.layers.Conv2D( 64, (3,3), strides=2, padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_64 ) x_64 = keras.layers.normalization.BatchNormalization()( x_64 ) x_128 = keras.layers.Conv2D( 128, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_64 ) x_128 = keras.layers.normalization.BatchNormalization()( x_128 ) x_128 = keras.layers.Conv2D( 128, (3,3), strides=2, padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_128 ) x_128 = keras.layers.normalization.BatchNormalization()( x_128 ) x_256 = keras.layers.Conv2D( 128, (3,3), padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_128 ) x_256 = keras.layers.normalization.BatchNormalization()( x_256 ) x_256 = keras.layers.Conv2D( 128, (3,3), strides=2, padding='same', activation='relu', kernel_regularizer=r_l2, activity_regularizer=r_l1 )( x_256 ) x_256 = keras.layers.normalization.BatchNormalization()( x_256 ) z = keras.layers.Conv2DTranspose( 32, (11,11), strides=8, padding='same' )( x_256 ) x = keras.layers.Conv2DTranspose( 32, (9,9), strides=4, padding='same' )( x_128 ) y = keras.layers.Conv2DTranspose( 32, (7,7), strides=2, padding='same' )( x_64 ) out = keras.layers.Add()( [x,y,z] ) return out def make_from_vgg19_multiconvup( input_img, trainable=False ): base_model = keras.applications.vgg19.VGG19(weights='imagenet', include_top=False, input_tensor=input_img) for l in base_model.layers: l.trainable = trainable #TODO : add kernel regularizers and activity_regularizer to conv layers base_model_out = base_model.get_layer('block2_pool').output up_conv_out = keras.layers.Conv2DTranspose( 32, (9,9), strides=2, padding='same', activation='relu' )( base_model_out ) up_conv_out = keras.layers.normalization.BatchNormalization()( up_conv_out ) up_conv_out = keras.layers.Conv2DTranspose( 32, (9,9), strides=2, padding='same', activation='relu' )( up_conv_out ) up_conv_out = keras.layers.normalization.BatchNormalization()( up_conv_out ) return up_conv_out def make_from_mobilenet( input_img, weights=None, layer_name='conv_pw_7_relu' ): # input_img = keras.layers.BatchNormalization()(input_img) base_model = keras.applications.mobilenet.MobileNet( weights=weights, include_top=False, input_tensor=input_img ) # Pull out a layer from original network base_model_out = base_model.get_layer( layer_name ).output # can also try conv_pw_7_relu etc. return base_model_out def make_from_vgg16( input_img, weights='imagenet', trainable=False, layer_name='block2_pool' ): base_model = keras.applications.vgg16.VGG16(weights=weights, include_top=False, input_tensor=input_img) for l in base_model.layers: l.trainable = trainable base_model_out = base_model.get_layer(layer_name).output return base_model_out ================================================ FILE: scripts/predict_utils.py ================================================ import keras import json import pprint import numpy as np import cv2 import code from keras import backend as K from keras.engine.topology import Layer # This was copied from the training code's CustomNets.py class NetVLADLayer( Layer ): def __init__( self, num_clusters, **kwargs ): self.num_clusters = num_clusters super(NetVLADLayer, self).__init__(**kwargs) def build( self, input_shape ): self.K = self.num_clusters self.D = input_shape[-1] self.kernel = self.add_weight( name='kernel', shape=(1,1,self.D,self.K), initializer='uniform', trainable=True ) self.bias = self.add_weight( name='bias', shape=(1,1,self.K), initializer='uniform', trainable=True ) self.C = self.add_weight( name='cluster_centers', shape=[1,1,1,self.D,self.K], initializer='uniform', trainable=True) def call( self, x ): # soft-assignment. s = K.conv2d( x, self.kernel, padding='same' ) + self.bias a = K.softmax( s ) self.amap = K.argmax( a, -1 ) # print 'amap.shape', self.amap.shape # Dims used hereafter: batch, H, W, desc_coeff, cluster a = K.expand_dims( a, -2 ) # print 'a.shape=',a.shape # Core v = K.expand_dims(x, -1) + self.C # print 'v.shape', v.shape v = a * v # print 'v.shape', v.shape v = K.sum(v, axis=[1, 2]) # print 'v.shape', v.shape v = K.permute_dimensions(v, pattern=[0, 2, 1]) # print 'v.shape', v.shape #v.shape = None x K x D # Normalize v (Intra Normalization) v = K.l2_normalize( v, axis=-1 ) v = K.batch_flatten( v ) v = K.l2_normalize( v, axis=-1 ) # return [v, self.amap] return v def compute_output_shape( self, input_shape ): # return [(input_shape[0], self.K*self.D ), (input_shape[0], input_shape[1], input_shape[2]) ] return (input_shape[0], self.K*self.D ) def get_config( self ): pass # base_config = super(NetVLADLayer, self).get_config() # return dict(list(base_config.items())) # As suggested by: https://github.com/keras-team/keras/issues/4871#issuecomment-269731817 config = {'num_clusters': self.num_clusters} base_config = super(NetVLADLayer, self).get_config() return dict(list(base_config.items()) + list(config.items())) class GhostVLADLayer( Layer ): def __init__( self, num_clusters, num_ghost_clusters, **kwargs ): self.num_clusters = num_clusters self.num_ghost_clusters = num_ghost_clusters super(GhostVLADLayer, self).__init__(**kwargs) def build( self, input_shape ): # self.K = self.num_clusters self.K = self.num_clusters + self.num_ghost_clusters self.D = input_shape[-1] self.kernel = self.add_weight( name='kernel', shape=(1,1,self.D,self.K), initializer='uniform', trainable=True ) self.bias = self.add_weight( name='bias', shape=(1,1,self.K), initializer='uniform', trainable=True ) self.C = self.add_weight( name='cluster_centers', shape=[1,1,1,self.D,self.K], initializer='uniform', trainable=True) def call( self, x ): # soft-assignment. s = K.conv2d( x, self.kernel, padding='same' ) + self.bias a = K.softmax( s ) self.amap = K.argmax( a, -1 ) # print 'amap.shape', self.amap.shape # Dims used hereafter: batch, H, W, desc_coeff, cluster a = K.expand_dims( a, -2 ) # print 'a.shape=',a.shape # Core v = K.expand_dims(x, -1) + self.C # print 'v.shape', v.shape v = a * v # print 'v.shape', v.shape v = K.sum(v, axis=[1, 2]) # print 'v.shape', v.shape v = K.permute_dimensions(v, pattern=[0, 2, 1]) # print 'v.shape', v.shape #v.shape = None x K x D # Normalize v (Intra Normalization) v = v[:,0:self.num_clusters,:] # print 'after ghosting v.shape', v.shape v = K.l2_normalize( v, axis=-1 ) v = K.batch_flatten( v ) v = K.l2_normalize( v, axis=-1 ) # return [v, self.amap] return v def compute_output_shape( self, input_shape ): # return [(input_shape[0], self.num_clusters*self.D ), (input_shape[0], input_shape[1], input_shape[2]) ] return (input_shape[0], self.num_clusters*self.D ) def get_config( self ): pass # As suggested by: https://github.com/keras-team/keras/issues/4871#issuecomment-269731817 config = {'num_clusters': self.num_clusters, 'num_ghost_clusters': self.num_ghost_clusters} base_config = super(GhostVLADLayer, self).get_config() return dict(list(base_config.items()) + list(config.items())) #--------------------------- UTILS --------------------------------------------# def open_json_file( fname ): print 'Load JSON file: ', fname jsonX = json.loads(open(fname).read()) return jsonX def change_model_inputshape(model, new_input_shape=(None, 40, 40, 3), verbose=False): """ Given a model and new input shape it changes all the allocations. Note: It uses custom_objects={'NetVLAD': NetVLADLayer}. If you have any other custom-layer change the code here accordingly. """ print '[change_model_inputshape], new_input_shape=', new_input_shape # replace input shape of first layer model._layers[0].batch_input_shape = new_input_shape # feel free to modify additional parameters of other layers, for example... # model._layers[2].pool_size = (8, 8) # model._layers[2].strides = (8, 8) # rebuild model architecture by exporting and importing via json new_model = keras.models.model_from_json(model.to_json(), custom_objects={'NetVLADLayer': NetVLADLayer, 'GhostVLADLayer':GhostVLADLayer} ) # new_model.summary() # copy weights from old model to new one print 'copy weights from old model to new one....this usually takes upto 10 sec' for layer in new_model.layers: try: if verbose: print( 'transfer weights for layer.name='+layer.name ) layer.set_weights(model.get_layer(name=layer.name).get_weights()) except: print '[Almost certainly FATAL] ' print 'If you see the warning like wieghts cannot be transfer, this is usually bad. In this case make sure the desired input dimensions and those in the modeljson file are compatible' print("Could not transfer weights for layer {}".format(layer.name)) quit() # test new model on a random input image # X = np.random.rand(new_input_shape[0], new_input_shape[1], new_input_shape[2], new_input_shape[3] ) # y_pred = new_model.predict(X) # print('try predict with a random input_img with shape='+str(X.shape)+ str(y_pred) ) return new_model #--------------------------- END UTILS ----------------------------------------# ================================================ FILE: scripts/unittest/demo_superpoint.py ================================================ #!/usr/bin/env python # # %BANNER_BEGIN% # --------------------------------------------------------------------- # %COPYRIGHT_BEGIN% # # Magic Leap, Inc. ("COMPANY") CONFIDENTIAL # # Unpublished Copyright (c) 2018 # Magic Leap, Inc., All Rights Reserved. # # NOTICE: All information contained herein is, and remains the property # of COMPANY. The intellectual and technical concepts contained herein # are proprietary to COMPANY and may be covered by U.S. and Foreign # Patents, patents in process, and are protected by trade secret or # copyright law. Dissemination of this information or reproduction of # this material is strictly forbidden unless prior written permission is # obtained from COMPANY. Access to the source code contained herein is # hereby forbidden to anyone except current COMPANY employees, managers # or contractors who have executed Confidentiality and Non-disclosure # agreements explicitly covering such access. # # The copyright notice above does not evidence any actual or intended # publication or disclosure of this source code, which includes # information that is confidential and/or proprietary, and is a trade # secret, of COMPANY. ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, # PUBLIC PERFORMANCE, OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS # SOURCE CODE WITHOUT THE EXPRESS WRITTEN CONSENT OF COMPANY IS # STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE LAWS AND # INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE # CODE AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS # TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, # USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. # # %COPYRIGHT_END% # ---------------------------------------------------------------------- # %AUTHORS_BEGIN% # # Originating Authors: Daniel DeTone (ddetone) # Tomasz Malisiewicz (tmalisiewicz) # # %AUTHORS_END% # --------------------------------------------------------------------*/ # %BANNER_END% import argparse import glob import numpy as np import os import time import code import cv2 import torch # Stub to warn about opencv version. if int(cv2.__version__[0]) < 3: # pragma: no cover print('Warning: OpenCV 3 is not installed') # Jet colormap for visualization. myjet = np.array([[0. , 0. , 0.5 ], [0. , 0. , 0.99910873], [0. , 0.37843137, 1. ], [0. , 0.83333333, 1. ], [0.30044276, 1. , 0.66729918], [0.66729918, 1. , 0.30044276], [1. , 0.90123457, 0. ], [1. , 0.48002905, 0. ], [0.99910873, 0.07334786, 0. ], [0.5 , 0. , 0. ]]) class SuperPointNet(torch.nn.Module): """ Pytorch definition of SuperPoint Network. """ def __init__(self): super(SuperPointNet, self).__init__() self.relu = torch.nn.ReLU(inplace=True) self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2) c1, c2, c3, c4, c5, d1 = 64, 64, 128, 128, 256, 256 # Shared Encoder. self.conv1a = torch.nn.Conv2d(1, c1, kernel_size=3, stride=1, padding=1) self.conv1b = torch.nn.Conv2d(c1, c1, kernel_size=3, stride=1, padding=1) self.conv2a = torch.nn.Conv2d(c1, c2, kernel_size=3, stride=1, padding=1) self.conv2b = torch.nn.Conv2d(c2, c2, kernel_size=3, stride=1, padding=1) self.conv3a = torch.nn.Conv2d(c2, c3, kernel_size=3, stride=1, padding=1) self.conv3b = torch.nn.Conv2d(c3, c3, kernel_size=3, stride=1, padding=1) self.conv4a = torch.nn.Conv2d(c3, c4, kernel_size=3, stride=1, padding=1) self.conv4b = torch.nn.Conv2d(c4, c4, kernel_size=3, stride=1, padding=1) # Detector Head. self.convPa = torch.nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1) self.convPb = torch.nn.Conv2d(c5, 65, kernel_size=1, stride=1, padding=0) # Descriptor Head. self.convDa = torch.nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1) self.convDb = torch.nn.Conv2d(c5, d1, kernel_size=1, stride=1, padding=0) def forward(self, x): """ Forward pass that jointly computes unprocessed point and descriptor tensors. Input x: Image pytorch tensor shaped N x 1 x H x W. Output semi: Output point pytorch tensor shaped N x 65 x H/8 x W/8. desc: Output descriptor pytorch tensor shaped N x 256 x H/8 x W/8. """ # Shared Encoder. x = self.relu(self.conv1a(x)) x = self.relu(self.conv1b(x)) x = self.pool(x) x = self.relu(self.conv2a(x)) x = self.relu(self.conv2b(x)) x = self.pool(x) x = self.relu(self.conv3a(x)) x = self.relu(self.conv3b(x)) x = self.pool(x) x = self.relu(self.conv4a(x)) x = self.relu(self.conv4b(x)) # Detector Head. cPa = self.relu(self.convPa(x)) semi = self.convPb(cPa) # Descriptor Head. cDa = self.relu(self.convDa(x)) desc = self.convDb(cDa) dn = torch.norm(desc, p=2, dim=1) # Compute the norm. desc = desc.div(torch.unsqueeze(dn, 1)) # Divide by norm to normalize. return semi, desc class SuperPointFrontend(object): """ Wrapper around pytorch net to help with pre and post image processing. """ def __init__(self, weights_path, nms_dist, conf_thresh, nn_thresh, cuda=False): self.name = 'SuperPoint' self.cuda = cuda self.nms_dist = nms_dist self.conf_thresh = conf_thresh self.nn_thresh = nn_thresh # L2 descriptor distance for good match. self.cell = 8 # Size of each output cell. Keep this fixed. self.border_remove = 4 # Remove points this close to the border. # Load the network in inference mode. self.net = SuperPointNet() if cuda: # Train on GPU, deploy on GPU. self.net.load_state_dict(torch.load(weights_path)) self.net = self.net.cuda() else: # Train on GPU, deploy on CPU. self.net.load_state_dict(torch.load(weights_path, map_location=lambda storage, loc: storage)) self.net.eval() def nms_fast(self, in_corners, H, W, dist_thresh): """ Run a faster approximate Non-Max-Suppression on numpy corners shaped: 3xN [x_i,y_i,conf_i]^T Algo summary: Create a grid sized HxW. Assign each corner location a 1, rest are zeros. Iterate through all the 1's and convert them either to -1 or 0. Suppress points by setting nearby values to 0. Grid Value Legend: -1 : Kept. 0 : Empty or suppressed. 1 : To be processed (converted to either kept or supressed). NOTE: The NMS first rounds points to integers, so NMS distance might not be exactly dist_thresh. It also assumes points are within image boundaries. Inputs in_corners - 3xN numpy array with corners [x_i, y_i, confidence_i]^T. H - Image height. W - Image width. dist_thresh - Distance to suppress, measured as an infinty norm distance. Returns nmsed_corners - 3xN numpy matrix with surviving corners. nmsed_inds - N length numpy vector with surviving corner indices. """ grid = np.zeros((H, W)).astype(int) # Track NMS data. inds = np.zeros((H, W)).astype(int) # Store indices of points. # Sort by confidence and round to nearest int. inds1 = np.argsort(-in_corners[2,:]) corners = in_corners[:,inds1] rcorners = corners[:2,:].round().astype(int) # Rounded corners. # Check for edge case of 0 or 1 corners. if rcorners.shape[1] == 0: return np.zeros((3,0)).astype(int), np.zeros(0).astype(int) if rcorners.shape[1] == 1: out = np.vstack((rcorners, in_corners[2])).reshape(3,1) return out, np.zeros((1)).astype(int) # Initialize the grid. for i, rc in enumerate(rcorners.T): grid[rcorners[1,i], rcorners[0,i]] = 1 inds[rcorners[1,i], rcorners[0,i]] = i # Pad the border of the grid, so that we can NMS points near the border. pad = dist_thresh grid = np.pad(grid, ((pad,pad), (pad,pad)), mode='constant') # Iterate through points, highest to lowest conf, suppress neighborhood. count = 0 for i, rc in enumerate(rcorners.T): # Account for top and left padding. pt = (rc[0]+pad, rc[1]+pad) if grid[pt[1], pt[0]] == 1: # If not yet suppressed. grid[pt[1]-pad:pt[1]+pad+1, pt[0]-pad:pt[0]+pad+1] = 0 grid[pt[1], pt[0]] = -1 count += 1 # Get all surviving -1's and return sorted array of remaining corners. keepy, keepx = np.where(grid==-1) keepy, keepx = keepy - pad, keepx - pad inds_keep = inds[keepy, keepx] out = corners[:, inds_keep] values = out[-1, :] inds2 = np.argsort(-values) out = out[:, inds2] out_inds = inds1[inds_keep[inds2]] return out, out_inds def run(self, img): """ Process a numpy image to extract points and descriptors. Input img - HxW numpy float32 input image in range [0,1]. Output corners - 3xN numpy array with corners [x_i, y_i, confidence_i]^T. desc - 256xN numpy array of corresponding unit normalized descriptors. heatmap - HxW numpy heatmap in range [0,1] of point confidences. """ assert img.ndim == 2, 'Image must be grayscale.' assert img.dtype == np.float32, 'Image must be float32.' H, W = img.shape[0], img.shape[1] inp = img.copy() inp = (inp.reshape(1, H, W)) inp = torch.from_numpy(inp) inp = torch.autograd.Variable(inp).view(1, 1, H, W) if self.cuda: inp = inp.cuda() # Forward pass of network. outs = self.net.forward(inp) semi, coarse_desc = outs[0], outs[1] # Convert pytorch -> numpy. semi = semi.data.cpu().numpy().squeeze() # --- Process points. dense = np.exp(semi) # Softmax. dense = dense / (np.sum(dense, axis=0)+.00001) # Should sum to 1. # Remove dustbin. nodust = dense[:-1, :, :] # Reshape to get full resolution heatmap. Hc = int(H / self.cell) Wc = int(W / self.cell) nodust = nodust.transpose(1, 2, 0) heatmap = np.reshape(nodust, [Hc, Wc, self.cell, self.cell]) heatmap = np.transpose(heatmap, [0, 2, 1, 3]) heatmap = np.reshape(heatmap, [Hc*self.cell, Wc*self.cell]) xs, ys = np.where(heatmap >= self.conf_thresh) # Confidence threshold. if len(xs) == 0: return np.zeros((3, 0)), None, None pts = np.zeros((3, len(xs))) # Populate point data sized 3xN. pts[0, :] = ys pts[1, :] = xs pts[2, :] = heatmap[xs, ys] pts, _ = self.nms_fast(pts, H, W, dist_thresh=self.nms_dist) # Apply NMS. inds = np.argsort(pts[2,:]) pts = pts[:,inds[::-1]] # Sort by confidence. # Remove points along border. bord = self.border_remove toremoveW = np.logical_or(pts[0, :] < bord, pts[0, :] >= (W-bord)) toremoveH = np.logical_or(pts[1, :] < bord, pts[1, :] >= (H-bord)) toremove = np.logical_or(toremoveW, toremoveH) pts = pts[:, ~toremove] # --- Process descriptor. D = coarse_desc.shape[1] if pts.shape[1] == 0: desc = np.zeros((D, 0)) else: # Interpolate into descriptor map using 2D point locations. samp_pts = torch.from_numpy(pts[:2, :].copy()) samp_pts[0, :] = (samp_pts[0, :] / (float(W)/2.)) - 1. samp_pts[1, :] = (samp_pts[1, :] / (float(H)/2.)) - 1. samp_pts = samp_pts.transpose(0, 1).contiguous() samp_pts = samp_pts.view(1, 1, -1, 2) samp_pts = samp_pts.float() if self.cuda: samp_pts = samp_pts.cuda() desc = torch.nn.functional.grid_sample(coarse_desc, samp_pts) desc = desc.data.cpu().numpy().reshape(D, -1) desc /= np.linalg.norm(desc, axis=0)[np.newaxis, :] return pts, desc, heatmap class PointTracker(object): """ Class to manage a fixed memory of points and descriptors that enables sparse optical flow point tracking. Internally, the tracker stores a 'tracks' matrix sized M x (2+L), of M tracks with maximum length L, where each row corresponds to: row_m = [track_id_m, avg_desc_score_m, point_id_0_m, ..., point_id_L-1_m]. """ def __init__(self, max_length, nn_thresh): if max_length < 2: raise ValueError('max_length must be greater than or equal to 2.') self.maxl = max_length self.nn_thresh = nn_thresh self.all_pts = [] for n in range(self.maxl): self.all_pts.append(np.zeros((2, 0))) self.last_desc = None self.tracks = np.zeros((0, self.maxl+2)) self.track_count = 0 self.max_score = 9999 def nn_match_two_way(self, desc1, desc2, nn_thresh): """ Performs two-way nearest neighbor matching of two sets of descriptors, such that the NN match from descriptor A->B must equal the NN match from B->A. Inputs: desc1 - NxM numpy matrix of N corresponding M-dimensional descriptors. desc2 - NxM numpy matrix of N corresponding M-dimensional descriptors. nn_thresh - Optional descriptor distance below which is a good match. Returns: matches - 3xL numpy array, of L matches, where L <= N and each column i is a match of two descriptors, d_i in image 1 and d_j' in image 2: [d_i index, d_j' index, match_score]^T """ assert desc1.shape[0] == desc2.shape[0] if desc1.shape[1] == 0 or desc2.shape[1] == 0: return np.zeros((3, 0)) if nn_thresh < 0.0: raise ValueError('\'nn_thresh\' should be non-negative') # Compute L2 distance. Easy since vectors are unit normalized. dmat = np.dot(desc1.T, desc2) dmat = np.sqrt(2-2*np.clip(dmat, -1, 1)) # Get NN indices and scores. idx = np.argmin(dmat, axis=1) scores = dmat[np.arange(dmat.shape[0]), idx] # Threshold the NN matches. keep = scores < nn_thresh # Check if nearest neighbor goes both directions and keep those. idx2 = np.argmin(dmat, axis=0) keep_bi = np.arange(len(idx)) == idx2[idx] keep = np.logical_and(keep, keep_bi) idx = idx[keep] scores = scores[keep] # Get the surviving point indices. m_idx1 = np.arange(desc1.shape[1])[keep] m_idx2 = idx # Populate the final 3xN match data structure. matches = np.zeros((3, int(keep.sum()))) matches[0, :] = m_idx1 matches[1, :] = m_idx2 matches[2, :] = scores return matches def get_offsets(self): """ Iterate through list of points and accumulate an offset value. Used to index the global point IDs into the list of points. Returns offsets - N length array with integer offset locations. """ # Compute id offsets. offsets = [] offsets.append(0) for i in range(len(self.all_pts)-1): # Skip last camera size, not needed. offsets.append(self.all_pts[i].shape[1]) offsets = np.array(offsets) offsets = np.cumsum(offsets) return offsets def update(self, pts, desc): """ Add a new set of point and descriptor observations to the tracker. Inputs pts - 3xN numpy array of 2D point observations. desc - DxN numpy array of corresponding D dimensional descriptors. """ if pts is None or desc is None: print('PointTracker: Warning, no points were added to tracker.') return assert pts.shape[1] == desc.shape[1] # Initialize last_desc. if self.last_desc is None: self.last_desc = np.zeros((desc.shape[0], 0)) # Remove oldest points, store its size to update ids later. remove_size = self.all_pts[0].shape[1] self.all_pts.pop(0) self.all_pts.append(pts) # Remove oldest point in track. self.tracks = np.delete(self.tracks, 2, axis=1) # Update track offsets. for i in range(2, self.tracks.shape[1]): self.tracks[:, i] -= remove_size self.tracks[:, 2:][self.tracks[:, 2:] < -1] = -1 offsets = self.get_offsets() # Add a new -1 column. self.tracks = np.hstack((self.tracks, -1*np.ones((self.tracks.shape[0], 1)))) # Try to append to existing tracks. matched = np.zeros((pts.shape[1])).astype(bool) matches = self.nn_match_two_way(self.last_desc, desc, self.nn_thresh) for match in matches.T: # Add a new point to it's matched track. id1 = int(match[0]) + offsets[-2] id2 = int(match[1]) + offsets[-1] found = np.argwhere(self.tracks[:, -2] == id1) if found.shape[0] > 0: matched[int(match[1])] = True row = int(found) self.tracks[row, -1] = id2 if self.tracks[row, 1] == self.max_score: # Initialize track score. self.tracks[row, 1] = match[2] else: # Update track score with running average. # NOTE(dd): this running average can contain scores from old matches # not contained in last max_length track points. track_len = (self.tracks[row, 2:] != -1).sum() - 1. frac = 1. / float(track_len) self.tracks[row, 1] = (1.-frac)*self.tracks[row, 1] + frac*match[2] # Add unmatched tracks. new_ids = np.arange(pts.shape[1]) + offsets[-1] new_ids = new_ids[~matched] new_tracks = -1*np.ones((new_ids.shape[0], self.maxl + 2)) new_tracks[:, -1] = new_ids new_num = new_ids.shape[0] new_trackids = self.track_count + np.arange(new_num) new_tracks[:, 0] = new_trackids new_tracks[:, 1] = self.max_score*np.ones(new_ids.shape[0]) self.tracks = np.vstack((self.tracks, new_tracks)) self.track_count += new_num # Update the track count. # Remove empty tracks. keep_rows = np.any(self.tracks[:, 2:] >= 0, axis=1) self.tracks = self.tracks[keep_rows, :] # Store the last descriptors. self.last_desc = desc.copy() return def get_tracks(self, min_length): """ Retrieve point tracks of a given minimum length. Input min_length - integer >= 1 with minimum track length Output returned_tracks - M x (2+L) sized matrix storing track indices, where M is the number of tracks and L is the maximum track length. """ if min_length < 1: raise ValueError('\'min_length\' too small.') valid = np.ones((self.tracks.shape[0])).astype(bool) good_len = np.sum(self.tracks[:, 2:] != -1, axis=1) >= min_length # Remove tracks which do not have an observation in most recent frame. not_headless = (self.tracks[:, -1] != -1) keepers = np.logical_and.reduce((valid, good_len, not_headless)) returned_tracks = self.tracks[keepers, :].copy() return returned_tracks def draw_tracks(self, out, tracks): """ Visualize tracks all overlayed on a single image. Inputs out - numpy uint8 image sized HxWx3 upon which tracks are overlayed. tracks - M x (2+L) sized matrix storing track info. """ # Store the number of points per camera. pts_mem = self.all_pts N = len(pts_mem) # Number of cameras/images. # Get offset ids needed to reference into pts_mem. offsets = self.get_offsets() # Width of track and point circles to be drawn. stroke = 1 # Iterate through each track and draw it. for track in tracks: clr = myjet[int(np.clip(np.floor(track[1]*10), 0, 9)), :]*255 for i in range(N-1): if track[i+2] == -1 or track[i+3] == -1: continue offset1 = offsets[i] offset2 = offsets[i+1] idx1 = int(track[i+2]-offset1) idx2 = int(track[i+3]-offset2) pt1 = pts_mem[i][:2, idx1] pt2 = pts_mem[i+1][:2, idx2] p1 = (int(round(pt1[0])), int(round(pt1[1]))) p2 = (int(round(pt2[0])), int(round(pt2[1]))) cv2.line(out, p1, p2, clr, thickness=stroke, lineType=16) # Draw end points of each track. if i == N-2: clr2 = (255, 0, 0) cv2.circle(out, p2, stroke, clr2, -1, lineType=16) class VideoStreamer(object): """ Class to help process image streams. Three types of possible inputs:" 1.) USB Webcam. 2.) A directory of images (files in directory matching 'img_glob'). 3.) A video file, such as an .mp4 or .avi file. """ def __init__(self, basedir, camid, height, width, skip, img_glob): self.cap = [] self.camera = False self.video_file = False self.listing = [] self.sizer = [height, width] self.i = 0 self.skip = skip self.maxlen = 1000000 # If the "basedir" string is the word camera, then use a webcam. if basedir == "camera/" or basedir == "camera": print('==> Processing Webcam Input.') self.cap = cv2.VideoCapture(camid) self.listing = range(0, self.maxlen) self.camera = True else: # Try to open as a video. self.cap = cv2.VideoCapture(basedir) lastbit = basedir[-4:len(basedir)] if (type(self.cap) == list or not self.cap.isOpened()) and (lastbit == '.mp4'): raise IOError('Cannot open movie file') elif type(self.cap) != list and self.cap.isOpened() and (lastbit != '.txt'): print('==> Processing Video Input.') num_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT)) self.listing = range(0, num_frames) self.listing = self.listing[::self.skip] self.camera = True self.video_file = True self.maxlen = len(self.listing) else: print('==> Processing Image Directory Input.') search = os.path.join(basedir, img_glob) self.listing = glob.glob(search) self.listing.sort() self.listing = self.listing[::self.skip] self.maxlen = len(self.listing) if self.maxlen == 0: raise IOError('No images were found (maybe bad \'--img_glob\' parameter?)') def read_image(self, impath, img_size): """ Read image as grayscale and resize to img_size. Inputs impath: Path to input image. img_size: (W, H) tuple specifying resize size. Returns grayim: float32 numpy array sized H x W with values in range [0, 1]. """ grayim = cv2.imread(impath, 0) if grayim is None: raise Exception('Error reading image %s' % impath) # Image is resized via opencv. interp = cv2.INTER_AREA grayim = cv2.resize(grayim, (img_size[1], img_size[0]), interpolation=interp) grayim = (grayim.astype('float32') / 255.) return grayim def next_frame(self): """ Return the next frame, and increment internal counter. Returns image: Next H x W image. status: True or False depending whether image was loaded. """ if self.i == self.maxlen: return (None, False) if self.camera: ret, input_image = self.cap.read() if ret is False: print('VideoStreamer: Cannot get image from camera (maybe bad --camid?)') return (None, False) if self.video_file: self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.listing[self.i]) input_image = cv2.resize(input_image, (self.sizer[1], self.sizer[0]), interpolation=cv2.INTER_AREA) input_image = cv2.cvtColor(input_image, cv2.COLOR_RGB2GRAY) input_image = input_image.astype('float')/255.0 else: image_file = self.listing[self.i] input_image = self.read_image(image_file, self.sizer) # Increment internal counter. self.i = self.i + 1 input_image = input_image.astype('float32') return (input_image, True) if __name__ == '__main__': # Parse command line arguments. parser = argparse.ArgumentParser(description='PyTorch SuperPoint Demo.') parser.add_argument('input', type=str, default='', help='Image directory or movie file or "camera" (for webcam).') parser.add_argument('--weights_path', type=str, default='superpoint_v1.pth', help='Path to pretrained weights file (default: superpoint_v1.pth).') parser.add_argument('--img_glob', type=str, default='*.png', help='Glob match if directory of images is specified (default: \'*.png\').') parser.add_argument('--skip', type=int, default=1, help='Images to skip if input is movie or directory (default: 1).') parser.add_argument('--show_extra', action='store_true', help='Show extra debug outputs (default: False).') parser.add_argument('--H', type=int, default=120, help='Input image height (default: 120).') parser.add_argument('--W', type=int, default=160, help='Input image width (default:160).') parser.add_argument('--display_scale', type=int, default=2, help='Factor to scale output visualization (default: 2).') parser.add_argument('--min_length', type=int, default=2, help='Minimum length of point tracks (default: 2).') parser.add_argument('--max_length', type=int, default=5, help='Maximum length of point tracks (default: 5).') parser.add_argument('--nms_dist', type=int, default=4, help='Non Maximum Suppression (NMS) distance (default: 4).') parser.add_argument('--conf_thresh', type=float, default=0.015, help='Detector confidence threshold (default: 0.015).') parser.add_argument('--nn_thresh', type=float, default=0.7, help='Descriptor matching threshold (default: 0.7).') parser.add_argument('--camid', type=int, default=0, help='OpenCV webcam video capture ID, usually 0 or 1 (default: 0).') parser.add_argument('--waitkey', type=int, default=1, help='OpenCV waitkey time in ms (default: 1).') parser.add_argument('--cuda', action='store_true', help='Use cuda GPU to speed up network processing speed (default: False)') parser.add_argument('--no_display', action='store_true', help='Do not display images to screen. Useful if running remotely (default: False).') parser.add_argument('--write', action='store_true', help='Save output frames to a directory (default: False)') parser.add_argument('--write_dir', type=str, default='tracker_outputs/', help='Directory where to write output frames (default: tracker_outputs/).') opt = parser.parse_args() print(opt) # code.interact( local=locals() ) # This class helps load input images from different sources. vs = VideoStreamer(opt.input, opt.camid, opt.H, opt.W, opt.skip, opt.img_glob) print('==> Loading pre-trained network.') # This class runs the SuperPoint network and processes its outputs. fe = SuperPointFrontend(weights_path=opt.weights_path, nms_dist=opt.nms_dist, conf_thresh=opt.conf_thresh, nn_thresh=opt.nn_thresh, cuda=opt.cuda) print('==> Successfully loaded pre-trained network.') # This class helps merge consecutive point matches into tracks. tracker = PointTracker(opt.max_length, nn_thresh=fe.nn_thresh) # Create a window to display the demo. if not opt.no_display: win = 'SuperPoint Tracker' cv2.namedWindow(win) else: print('Skipping visualization, will not show a GUI.') # Font parameters for visualizaton. font = cv2.FONT_HERSHEY_DUPLEX font_clr = (255, 255, 255) font_pt = (4, 12) font_sc = 0.4 # Create output directory if desired. if opt.write: print('==> Will write outputs to %s' % opt.write_dir) if not os.path.exists(opt.write_dir): os.makedirs(opt.write_dir) print('==> Running Demo.') while True: start = time.time() # Get a new image. img, status = vs.next_frame() if status is False: break # Get points and descriptors. start1 = time.time() pts, desc, heatmap = fe.run(img) # import code # code.interact( local=locals() ) end1 = time.time() # Add points and descriptors to the tracker. tracker.update(pts, desc) # Get tracks for points which were match successfully across all frames. tracks = tracker.get_tracks(opt.min_length) # Primary output - Show point tracks overlayed on top of input image. out1 = (np.dstack((img, img, img)) * 255.).astype('uint8') tracks[:, 1] /= float(fe.nn_thresh) # Normalize track scores to [0,1]. tracker.draw_tracks(out1, tracks) if opt.show_extra: cv2.putText(out1, 'Point Tracks', font_pt, font, font_sc, font_clr, lineType=16) # Extra output -- Show current point detections. out2 = (np.dstack((img, img, img)) * 255.).astype('uint8') for pt in pts.T: pt1 = (int(round(pt[0])), int(round(pt[1]))) cv2.circle(out2, pt1, 1, (0, 255, 0), -1, lineType=16) cv2.putText(out2, 'Raw Point Detections', font_pt, font, font_sc, font_clr, lineType=16) # Extra output -- Show the point confidence heatmap. if heatmap is not None: min_conf = 0.001 heatmap[heatmap < min_conf] = min_conf heatmap = -np.log(heatmap) heatmap = (heatmap - heatmap.min()) / (heatmap.max() - heatmap.min() + .00001) out3 = myjet[np.round(np.clip(heatmap*10, 0, 9)).astype('int'), :] out3 = (out3*255).astype('uint8') else: out3 = np.zeros_like(out2) cv2.putText(out3, 'Raw Point Confidences', font_pt, font, font_sc, font_clr, lineType=16) # Resize final output. if opt.show_extra: out = np.hstack((out1, out2, out3)) out = cv2.resize(out, (3*opt.display_scale*opt.W, opt.display_scale*opt.H)) else: out = cv2.resize(out1, (opt.display_scale*opt.W, opt.display_scale*opt.H)) # Display visualization image to screen. if not opt.no_display: cv2.imshow(win, out) key = cv2.waitKey(opt.waitkey) & 0xFF if key == ord('q'): print('Quitting, \'q\' pressed.') break # Optionally write images to disk. if opt.write: out_file = os.path.join(opt.write_dir, 'frame_%05d.png' % vs.i) print('Writing image to %s' % out_file) cv2.imwrite(out_file, out) end = time.time() net_t = (1./ float(end1 - start)) total_t = (1./ float(end - start)) if opt.show_extra: print('Processed image %d (net+post_process: %.2f FPS, total: %.2f FPS).'\ % (vs.i, net_t, total_t)) # Close any remaining windows. cv2.destroyAllWindows() print('==> Finshed Demo.') ================================================ FILE: scripts/unittest/rtry_superpoint.py ================================================ #!/usr/bin/env python import numpy as np import code import cv2 import time import json import torch # Stub to warn about opencv version. if int(cv2.__version__[0]) < 3: # pragma: no cover print('Warning: OpenCV 3 is not installed') from demo_superpoint import * class KSuperPointExp: def __init__(self): #------------------------------------------------------------------------ # Setup Neural Net weights_path='superpoint_v1.pth' nms_dist=4 conf_thresh=0.015 nn_thresh=0.7 cuda=True print('==> Loading pre-trained network.') # This class runs the SuperPoint network and processes its outputs. self.fe = SuperPointFrontend(weights_path=weights_path, nms_dist=nms_dist, conf_thresh=conf_thresh, nn_thresh=nn_thresh, cuda=cuda) print('==> Successfully loaded pre-trained network.') #----------------------------------------------------------------------- # Tracker - NN comparison for desc max_length = 5 # default as in the demo file self.tracker = PointTracker(max_length, nn_thresh=self.fe.nn_thresh) def plot_pts( self, xcanvas, pts ): # pts: [x_i, y_i, confidence_i]. 3xN for i in range( pts.shape[1] ): cv2.circle( xcanvas, (int(pts[0,i]), int(pts[1,i]) ), 1, (0,0,255), -1 ) return xcanvas def plot_point_sets( self, im0, pts0, im1, pts1 ): # pts: [x_i, y_i, confidence_i]. 3xN # try: xcanvas = np.concatenate( (im0, im1), axis=1 ) if pts0.shape[0] == 0: return xcanvas # except: # code.interact( local=locals() ) assert( pts0.shape[1] == pts1.shape[1] ) assert( pts0.shape[0] == 3 or pts0.shape[0] == 2 ) assert( pts1.shape[0] == 3 or pts1.shape[0] == 2 ) for i in range( pts0.shape[1] ): pt0 = (int(pts0[0,i]), int(pts0[1,i]) ) pt1 = (int(pts1[0,i] + im0.shape[1] ), int(pts1[1,i]) ) cv2.circle( xcanvas, pt0, 1, (0,255,0), -1) cv2.circle( xcanvas, pt1, 1, (0,255,0), -1) cv2.line( xcanvas, pt0, pt1, (0,255,0) ) return xcanvas def read_image(self, impath, img_size): """ Read image as grayscale and resize to img_size. Inputs impath: Path to input image. img_size: (W, H) tuple specifying resize size. Returns grayim: float32 numpy array sized H x W with values in range [0, 1]. """ grayim = cv2.imread(impath, 0) if grayim is None: raise Exception('Error reading image %s' % impath) # Image is resized via opencv. interp = cv2.INTER_AREA grayim = cv2.resize(grayim, (img_size[1], img_size[0]), interpolation=interp) grayim = (grayim.astype('float32') / 255.) return grayim def get_descriptor( self, image ): """ Given an image returns the keypoints and descriptors. Input img - HxW numpy float32 input image in range [0,1]. Output corners - 3xN numpy array with corners [x_i, y_i, confidence_i]^T. desc - 256xN numpy array of corresponding unit normalized descriptors. heatmap - HxW numpy heatmap in range [0,1] of point confidences. """ start1 = time.time() pts, desc, heatmap = self.fe.run(image) print 'superpoint computed in %4.2fms' %(1000. * (time.time() - start1)) return pts, desc, heatmap def match_image_pair( self, fname0, fname1 ): """ Given two image file names, read the two images (as per SuperPoint's requirement). Computes descriptors and then does NN comparison. """ ksp_image0 = ksp.read_image(fname0, [240,320] ) ksp_image1 = ksp.read_image(fname1, [240,320] ) image0 = cv2.imread( fname0 ) image1 = cv2.imread( fname1 ) pts0, desc0, heatmap0 = ksp.get_descriptor( ksp_image0 ) pts1, desc1, heatmap1 = ksp.get_descriptor( ksp_image1 ) print 'len(pts0)=%d, len(pts1)=%d, desc0.shape=%s, desc1.shape=%s' %(pts0.shape[1], pts1.shape[1], str(desc0.shape), str(desc1.shape) ) start_matching = time.time() matches = self.tracker.nn_match_two_way( desc0, desc1, self.fe.nn_thresh ) sel_pts0 = np.array([ pts0[:,i] for i in matches[0,:].astype('int32') ]).transpose() sel_pts1 = np.array([ pts1[:,i] for i in matches[1,:].astype('int32') ]).transpose() print 'matching dones in %4.2fms' %(1000. * (time.time() - start_matching) ) print 'matches=%d' %( matches.shape[1] ) if matches.shape[1] == 0: print 'NO MATCHES' return None, None # Plotting # xcanvas0 = self.plot_pts(image0.copy(), pts0 ) # xcanvas1 = self.plot_pts(image1.copy(), pts1 ) # xcanvas_ = self.plot_point_sets( xcanvas0, sel_pts0, xcanvas1, sel_pts1 ) # xcanvas_ = self.plot_point_sets( image0.copy(), sel_pts0, image1.copy(), sel_pts1 ) # cv2.imshow( 'pts0', xcanvas0 ) # cv2.imshow( 'pts1', xcanvas1 ) # cv2.imshow( 'joint', xcanvas_ ) return sel_pts0[0:2,:].transpose() , sel_pts1[0:2,:].transpose() # code.interact( local=locals() ) if __name__ == '__main__': BASE = '/Bulk_Data/_tmp_cerebro/bb4_multiple_loops_in_lab/' # # Open Log File # LOG_FILE_NAME = BASE+'/log.json' print 'Open file: ', LOG_FILE_NAME with open(LOG_FILE_NAME) as data_file: data = json.load(data_file) # # Init Keras Model ksp = KSuperPointExp() if True: i = 534 im = cv2.imread( BASE+'%d.jpg' %(i) ) im_float = cv2.cvtColor( cv2.resize(im, (0,0), fx=0.5, fy=0.5), cv2.COLOR_BGR2GRAY ).astype('float32') / 255. print 'im_float.shape=', im_float.shape, '\tdtype=', im_float.dtype sup_pts, sup_desc, sup_heatmap = ksp.get_descriptor( im_float ) cv2.imshow( str(i), ksp.plot_pts( im, sup_pts*2 ) ) if True: i = 1638 im = cv2.imread( BASE+'%d.jpg' %(i) ) im_float = cv2.cvtColor( cv2.resize(im, (0,0), fx=0.5, fy=0.5), cv2.COLOR_BGR2GRAY ).astype('float32') / 255. print 'im_float.shape=', im_float.shape, '\tdtype=', im_float.dtype sup_pts, sup_desc, sup_heatmap = ksp.get_descriptor( im_float ) cv2.imshow( str(i), ksp.plot_pts( im, sup_pts*2 ) ) cv2.waitKey(0) if __name__ == "__m1ain__": BASE = '/Bulk_Data/_tmp_cerebro/bb4_multiple_loops_in_lab/' # # Open Log File # LOG_FILE_NAME = BASE+'/log.json' print 'Open file: ', LOG_FILE_NAME with open(LOG_FILE_NAME) as data_file: data = json.load(data_file) # # Init Keras Model ksp = KSuperPointExp() # Loops over all images and precomputes their netvlad vector for i in range( len(data['DataNodes']) ): a = data['DataNodes'][i]['isKeyFrame'] b = data['DataNodes'][i]['isImageAvailable'] c = data['DataNodes'][i]['isPoseAvailable'] d = data['DataNodes'][i]['isPtCldAvailable'] if not ( a==1 and b==1 and c==1 and d==1 ): #only process keyframes which have pose and ptcld info continue im = cv2.imread( BASE+'%d.jpg' %(i) ) im_float = cv2.cvtColor( cv2.resize(im, (0,0), fx=0.5, fy=0.5), cv2.COLOR_BGR2GRAY ).astype('float32') / 255. print 'im_float.shape=', im_float.shape, '\tdtype=', im_float.dtype start_time = time.time() sup_pts, sup_desc, sup_heatmap = ksp.get_descriptor( im_float ) print '---', i , '\n', print 'Done in %4.4fms' %( 1000. * (time.time() - start_time ) ) cv2.imshow( 'im_superpoints', ksp.plot_pts( im, sup_pts*2 ) ) cv2.imshow( 'im', im ) key = cv2.waitKey(10) if key == ord( 'q' ): break ================================================ FILE: scripts/whole_image_desc_compute_client.py ================================================ #!/usr/bin/env python # Sample Client to call the `whole_image_descriptor_compute_server` from cerebro.srv import * import rospy import numpy as np import cv2 from cv_bridge import CvBridge, CvBridgeError from sensor_msgs.msg import Image rospy.wait_for_service( 'whole_image_descriptor_compute' ) try: res = rospy.ServiceProxy( 'whole_image_descriptor_compute', WholeImageDescriptorCompute ) # X = np.zeros( (100, 100), dtype=np.uint8 ) X = cv2.resize( cv2.imread( '/app/lena.jpg' ), (752,480) ) i = CvBridge().cv2_to_imgmsg( X ) u = res( i, 23 ) print 'received: ', u except rospy.ServiceException, e: print 'failed', e ================================================ FILE: scripts/whole_image_desc_compute_server.py ================================================ #!/usr/bin/env python from cerebro.srv import * import rospy from cv_bridge import CvBridge, CvBridgeError import cv2 import numpy as np import code import time import os import scipy.io import hashlib import keras from keras_helpers import * from predict_utils import * from TerminalColors import bcolors tcol = bcolors() import rospkg THIS_PKG_BASE_PATH = rospkg.RosPack().get_path('cerebro') from numpy.random import seed seed(42) class SampleGPUComputer: def __init__(self): self.model = keras.applications.vgg16.VGG16( weights=None) self.model._make_predict_function() self.model.summary() def handle_req( self, req ): print 'Handle request ' # print req cv_image = CvBridge().imgmsg_to_cv2( req.ima ) print 'cv_image.shape', cv_image.shape # cv2.imshow( 'cv_image from server', cv_image ) # cv2.waitKey(0) # # compute descriptor # # code.interact( local=locals() ) im = cv2.resize( cv_image, (224,224) ) self.model._make_predict_function() preds = self.model.predict( np.expand_dims(im,0).astype('float32') ) print preds # # Return the descriptor in the message # result = WholeImageDescriptorComputeResponse() result.desc = [ cv_image.shape[0], cv_image.shape[1] ] return result class ReljaNetVLAD: def __init__(self, im_rows=600, im_cols=960, im_chnls=3): ## Build net from keras.backend.tensorflow_backend import set_session import tensorflow as tf tf.set_random_seed(42) config = tf.ConfigProto() config.gpu_options.per_process_gpu_memory_fraction = 0.1 # config.gpu_options.visible_device_list = "0" set_session(tf.Session(config=config)) self.im_rows = int(im_rows) self.im_cols = int(im_cols) self.im_chnls = int(im_chnls) input_img = keras.layers.Input( batch_shape=(1,self.im_rows, self.im_cols, self.im_chnls ) ) cnn = make_from_vgg16( input_img, weights=None, layer_name='block5_pool' ) out, out_amap = NetVLADLayer(num_clusters = 64)( cnn ) model = keras.models.Model( inputs=input_img, outputs=out ) DATA_DIR = '/app_learning/cartwheel_train/relja_matlab_weight.dump/' print 'Relja Data Dir: ', DATA_DIR model.load_weights( DATA_DIR+'/matlab_model.keras' ) WPCA_M = scipy.io.loadmat( DATA_DIR+'/WPCA_1.mat' )['the_mat'] # 1x1x32768x4096 WPCA_b = scipy.io.loadmat( DATA_DIR+'/WPCA_2.mat' )['the_mat'] # 4096x1 WPCA_M = WPCA_M[0,0] # 32768x4096 WPCA_b = np.transpose(WPCA_b) #1x4096 self.model = model self.WPCA_M = WPCA_M self.WPCA_b = WPCA_b self.model_type = 'relja_matlab_model' # Doing this is a hack to force keras to allocate GPU memory. Don't comment this, tmp_zer = np.zeros( (1,self.im_rows,self.im_cols,self.im_chnls), dtype='float32' ) tmp_zer_out = self.model.predict( tmp_zer ) tmp_zer_out = np.matmul( tmp_zer_out, self.WPCA_M ) + self.WPCA_b tmp_zer_out /= np.linalg.norm( tmp_zer_out ) print 'model input.shape=', tmp_zer.shape, '\toutput.shape=', tmp_zer_out.shape print 'model_type=', self.model_type print '-----' print '\tinput_image.shape=', tmp_zer.shape print '\toutput.shape=', tmp_zer_out.shape print '\tminmax=', np.min( tmp_zer_out ), np.max( tmp_zer_out ) print '\tnorm=', np.linalg.norm( tmp_zer_out ) print '\tdtype=', tmp_zer_out.dtype print '-----' def handle_req( self, req ): ## Get Image out of req cv_image = CvBridge().imgmsg_to_cv2( req.ima ) print '[Handle Request] cv_image.shape', cv_image.shape, '\ta=', req.a, '\tt=', req.ima.header.stamp assert (cv_image.shape[0] == self.im_rows and cv_image.shape[1] == self.im_cols and cv_image.shape[2] == self.im_chnls),\ "\n[whole_image_descriptor_compute_server] Input shape of the image \ does not match with the allocated GPU memory. Expecting an input image of \ size %dx%dx%d, but received : %s" %(self.im_rows, self.im_cols, self.im_chnls, str(cv_image.shape) ) # cv2.imshow( 'whole_image_descriptor_compute_server:imshow', cv_image.astype('uint8') ) # cv2.waitKey(10) # cv2.imwrite( '/app/tmp/%s.jpg' %( str(req.ima.header.stamp) ), cv_image ) ## Compute Descriptor start_time = time.time() # Normalize image cv_image_rgb = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB) avg_image = [122.6778, 116.6522, 103.9997] cv_image_rgb[:,:,0]= cv_image_rgb[:,:,0] - avg_image[0] cv_image_rgb[:,:,1]= cv_image_rgb[:,:,1] - avg_image[1] cv_image_rgb[:,:,2]= cv_image_rgb[:,:,2] - avg_image[2] # predict u = self.model.predict( np.expand_dims( cv_image_rgb.astype('float32'), 0 ) ) # WPCA u = np.matmul( u, self.WPCA_M ) + self.WPCA_b u /= np.linalg.norm( u ) print 'Descriptor Computed in %4.4fms' %( 1000. *(time.time() - start_time) ), print '\tdesc.shape=', u.shape, print '\tinput_image.shape=', cv_image.shape, print '\tminmax=', np.min( u ), np.max( u ), print '\tmodel_type=', self.model_type, print '\tdtype=', cv_image.dtype ## Populate output message result = WholeImageDescriptorComputeResponse() # result.desc = [ cv_image.shape[0], cv_image.shape[1] ] result.desc = u[0,:] result.model_type = self.model_type return result class NetVLADImageDescriptor: def __init__(self, im_rows=600, im_cols=960, im_chnls=3): ## Build net from keras.backend.tensorflow_backend import set_session import tensorflow as tf tf.set_random_seed(42) config = tf.ConfigProto() config.gpu_options.per_process_gpu_memory_fraction = 0.1 # config.gpu_options.visible_device_list = "0" set_session(tf.Session(config=config)) # Blackbox 4 # self.im_rows = 512 # self.im_cols = 640 # self.im_chnls = 3 # point grey # self.im_rows = 600 # self.im_cols = 960 # self.im_chnls = 3 # EuroC # self.im_rows = 480 # self.im_cols = 752 # self.im_chnls = 3 self.im_rows = int(im_rows) self.im_cols = int(im_cols) self.im_chnls = int(im_chnls) #----- @ INPUT LAYER input_img = keras.layers.Input( shape=(self.im_rows, self.im_cols, self.im_chnls) ) #----- @ CNN # cnn = make_from_vgg16( input_img, weights=None, layer_name='block5_pool' ) cnn = make_from_mobilenet( input_img, weights=None, layer_name='conv_pw_7_relu' ) #----- @ DOWN-SAMPLE LAYER (OPTINAL) if False: #Downsample last layer (Reduce nChannels of the output.) cnn_dwn = keras.layers.Conv2D( 256, (1,1), padding='same', activation='relu' )( cnn ) cnn_dwn = keras.layers.normalization.BatchNormalization()( cnn_dwn ) cnn_dwn = keras.layers.Conv2D( 64, (1,1), padding='same', activation='relu' )( cnn_dwn ) cnn_dwn = keras.layers.normalization.BatchNormalization()( cnn_dwn ) cnn = cnn_dwn #----- @ NetVLADLayer # out, out_amap = NetVLADLayer(num_clusters = 16)( cnn ) out = NetVLADLayer(num_clusters = 16)( cnn ) model = keras.models.Model( inputs=input_img, outputs=out ) model.summary() # `model_visual_fname` as None will disable writing the image file. model_visual_fname = None # model_visual_fname = '/app/core.png' if model_visual_fname is not None: print 'Writing Model Visual to: ', model_visual_fname keras.utils.plot_model( model, to_file=model_visual_fname, show_shapes=True ) #----- @ Load Model Weights #TODO Read from config file the path of keras model # model_file = '/app/catkin_ws/src/cerebro/scripts/keras.models/core_model.keras' # model_type = 'test_keras_model' # model_file = '/app_learning/cartwheel_train/models.keras/vgg16/block5_pool_k16_tripletloss2/core_model.keras' # model_type = 'block5_pool_k16_tripletloss2' # model_file = '/app_learning/cartwheel_train/models.keras/mobilenet_conv7_quash_chnls_allpairloss/core_model.keras' # model_type = 'mobilenet_conv7_quash_chnls_allpairloss' #model_file = '/app_learning/cartwheel_train/models.keras/mobilenet_conv7_allpairloss/core_model.keras' #model_type = 'mobilenet_conv7_allpairloss' # model_file = '/app_learning/cartwheel_train/models.keras/mobilenet_new/pw13_quash_chnls_k16_allpairloss/core_model.1800.keras' # model_type = 'pw13_quash_chnls_k16_allpairloss' # model_file = '/app_learning/cartwheel_train/models.keras/vgg16_new/block5_pool_k16_tripletloss2/core_model.keras' # model_type = 'block5_pool_k16_tripletloss2' # model_file = '/app_learning/cartwheel_train/models.keras/mobilenet_new/pw13_quash_chnls_k16_allpairloss/core_model.800.keras' # model_type = 'pw13_quash_chnls_k16_allpairloss' model_file = THIS_PKG_BASE_PATH+'/scripts/keras.models/mobilenet_conv7_allpairloss.keras' model_type = 'mobilenet_conv7_allpairloss' print 'model_file: ', model_file model.load_weights( model_file ) # Write model as json to file, for info. # if False: # model.summary() # keras.utils.plot_model( model, to_file='/tmp/xcore.png', show_shapes=True ) # with open('/tmp/xmodel.json', 'w') as outfile: # json.dump(model.to_json(), outfile, indent=4 ) self.model = model self.model_type = model_type # ! Done...! # Doing this is a hack to force keras to allocate GPU memory. Don't comment this, tmp_zer = np.zeros( (1,self.im_rows,self.im_cols,self.im_chnls), dtype='float32' ) tmp_zer_out = self.model.predict( tmp_zer ) print 'model input.shape=', tmp_zer.shape, '\toutput.shape=', tmp_zer_out.shape print 'model_type=', self.model_type print '-----' print '\tinput_image.shape=', tmp_zer.shape print '\toutput.shape=', tmp_zer_out.shape print '\tminmax=', np.min( tmp_zer_out ), np.max( tmp_zer_out ) print '\tdtype=', tmp_zer_out.dtype print '-----' def handle_req( self, req ): ## Get Image out of req cv_image = CvBridge().imgmsg_to_cv2( req.ima ) print '[Handle Request] cv_image.shape', cv_image.shape, '\ta=', req.a, '\tt=', req.ima.header.stamp if len(cv_image.shape) == 2: cv_image = np.expand_dims( cv_image, -1 ); assert (cv_image.shape[0] == self.im_rows and cv_image.shape[1] == self.im_cols and cv_image.shape[2] == self.im_chnls),\ "\n[whole_image_descriptor_compute_server] Input shape of the image \ does not match with the allocated GPU memory. Expecting an input image of \ size %dx%dx%d, but received : %s" %(self.im_rows, self.im_cols, self.im_chnls, str(cv_image.shape) ) # cv2.imshow( 'whole_image_descriptor_compute_server:imshow', cv_image.astype('uint8') ) # cv2.waitKey(10) # cv2.imwrite( '/app/tmp/%s.jpg' %( str(req.ima.header.stamp) ), cv_image ) ## Compute Descriptor start_time = time.time() u = self.model.predict( np.expand_dims( cv_image.astype('float32'), 0 ) ) print 'Descriptor Computed in %4.4fms' %( 1000. *(time.time() - start_time) ), print '\tdesc.shape=', u.shape, print '\tinput_image.shape=', cv_image.shape, print '\tminmax=', np.min( u ), np.max( u ), print '\tnorm=', np.linalg.norm(u[0]), print '\tmodel_type=', self.model_type, print '\tdtype=', cv_image.dtype ## Populate output message result = WholeImageDescriptorComputeResponse() # result.desc = [ cv_image.shape[0], cv_image.shape[1] ] result.desc = u[0,:] result.model_type = self.model_type return result class JSONModelImageDescriptor: """ This class loads the net structure from the json file, model.json and weights from core_model.??.keras. In the argument `kerasmodel_file` you need to specify the core_model.??.keras full path (keras model file). The model.json need to exist in that folder. """ def __init__(self, kerasmodel_file, im_rows=600, im_cols=960, im_chnls=3): ## Build net from keras.backend.tensorflow_backend import set_session import tensorflow as tf tf.set_random_seed(42) config = tf.ConfigProto() config.gpu_options.per_process_gpu_memory_fraction = 0.2 # config.gpu_options.visible_device_list = "0" set_session(tf.Session(config=config)) # Blackbox 4 # self.im_rows = 512 # self.im_cols = 640 # self.im_chnls = 3 # point grey # self.im_rows = 600 # self.im_cols = 960 # self.im_chnls = 3 # EuroC # self.im_rows = 480 # self.im_cols = 752 # self.im_chnls = 3 self.im_rows = int(im_rows) self.im_cols = int(im_cols) self.im_chnls = int(im_chnls) LOG_DIR = '/'.join( kerasmodel_file.split('/')[0:-1] ) print '+++++++++++++++++++++++++++++++++++++++++++++++++++++++' print '++++++++++ (JSONModelImageDescriptor) LOG_DIR=', LOG_DIR print '++++++++++ im_rows=', im_rows, ' im_cols=', im_cols, ' im_chnls=', im_chnls print '+++++++++++++++++++++++++++++++++++++++++++++++++++++++' model_type = LOG_DIR.split('/')[-1] # code.interact( local=locals() ) assert os.path.isdir( LOG_DIR ), "The LOG_DIR doesnot exist, or there is a permission issue. LOG_DIR="+LOG_DIR assert os.path.isfile( LOG_DIR+'/model.json' ), "model.json does not exist in LOG_DIR="+LOG_DIR+'. This file is needed to load the network architecture from json format. This file should have been created when you learned the model using script in github.com/mpkuse/cartwheel_train' #----- @ Load Model Structure from JSON # Load JSON formatted model json_string = open_json_file( LOG_DIR+'/model.json' ) # print '=======================' # pprint.pprint( json_string, indent=4 ) # print '=======================' model = keras.models.model_from_json(str(json_string), custom_objects={'NetVLADLayer': NetVLADLayer, 'GhostVLADLayer':GhostVLADLayer} ) old_input_shape = model._layers[0].input_shape model._layers[0] print 'OLD MODEL: ', 'input_shape=', str(old_input_shape) model.summary() model_visual_fname = None # model_visual_fname = '/app/core.png' if model_visual_fname is not None: print 'Writing Model Visual to: ', model_visual_fname keras.utils.plot_model( model, to_file=model_visual_fname, show_shapes=True ) #----- @ Load Weights assert os.path.isfile( kerasmodel_file ), 'The model weights file doesnot exists or there is a permission issue.'+"kerasmodel_file="+kerasmodel_file model_fname = kerasmodel_file print tcol.OKGREEN, 'Load model: ', model_fname, tcol.ENDC model.load_weights( model_fname ) # Replace Input Layer new_model = change_model_inputshape( model, new_input_shape=(1,self.im_rows,self.im_cols,self.im_chnls) ) new_input_shape = new_model._layers[0].input_shape print 'OLD MODEL: ', 'input_shape=', str(old_input_shape) print 'NEW MODEL: input_shape=', str(new_input_shape) self.model = new_model self.model_type = model_type # Doing this is a hack to force keras to allocate GPU memory. Don't comment this, tmp_zer = np.zeros( (1,self.im_rows,self.im_cols,self.im_chnls), dtype='float32' ) tmp_zer_out = self.model.predict( tmp_zer ) print 'model input.shape=', tmp_zer.shape, '\toutput.shape=', tmp_zer_out.shape print 'model_type=', self.model_type print '-----' print '\tinput_image.shape=', tmp_zer.shape print '\toutput.shape=', tmp_zer_out.shape print '\tminmax=', np.min( tmp_zer_out ), np.max( tmp_zer_out ) print '\tnorm=', np.linalg.norm( tmp_zer_out ) print '\tdtype=', tmp_zer_out.dtype print '-----' def handle_req( self, req ): """ The received image from CV bridge has to be [0,255]. In function makes it to intensity range [-0.5 to 0.5] """ ## Get Image out of req cv_image = CvBridge().imgmsg_to_cv2( req.ima ) print '[Handle Request] cv_image.shape', cv_image.shape, '\ta=', req.a, '\tt=', req.ima.header.stamp if len(cv_image.shape)==2: # print 'Input dimensions are NxM but I am expecting it to be NxMxC, so np.expand_dims' cv_image = np.expand_dims( cv_image, -1 ) elif len( cv_image.shape )==3: pass else: assert( False ) assert (cv_image.shape[0] == self.im_rows and cv_image.shape[1] == self.im_cols and cv_image.shape[2] == self.im_chnls),\ "\n[whole_image_descriptor_compute_server] Input shape of the image \ does not match with the allocated GPU memory. Expecting an input image of \ size %dx%dx%d, but received : %s" %(self.im_rows, self.im_cols, self.im_chnls, str(cv_image.shape) ) # cv2.imshow( 'whole_image_descriptor_compute_server:imshow', cv_image.astype('uint8') ) # cv2.waitKey(10) # cv2.imwrite( '/app/tmp/%s.jpg' %( str(req.ima.header.stamp) ), cv_image ) ## Compute Descriptor start_time = time.time() # i__image = np.expand_dims( cv_image.astype('float32'), 0 ) # i__image = (np.expand_dims( cv_image.astype('float32'), 0 ) - 128.)/255. [-0.5,0.5] i__image = (np.expand_dims( cv_image.astype('float32'), 0 ) - 128.)*2.0/255. #[-1,1] u = self.model.predict( i__image ) print tcol.HEADER, 'Descriptor Computed in %4.4fms' %( 1000. *(time.time() - start_time) ), tcol.ENDC print '\tinput_image.shape=', cv_image.shape, print '\tinput_image dtype=', cv_image.dtype print tcol.OKBLUE, '\tinput image (to neuralnet) minmax=', np.min( i__image ), np.max( i__image ), tcol.ENDC print '\tdesc.shape=', u.shape, print '\tdesc minmax=', np.min( u ), np.max( u ), print '\tnorm=', np.linalg.norm(u[0]) print '\tmodel_type=', self.model_type ## Populate output message result = WholeImageDescriptorComputeResponse() # result.desc = [ cv_image.shape[0], cv_image.shape[1] ] result.desc = u[0,:] result.model_type = self.model_type return result class HDF5ModelImageDescriptor: """ This class loads the net structure from the .h5 file. This file contains the model weights as well as architecture details. In the argument `kerasmodel_file` you need to specify the core_model.??.keras full path (keras model file). """ def __init__(self, kerasmodel_file, im_rows=600, im_cols=960, im_chnls=3): ## Build net from keras.backend.tensorflow_backend import set_session import tensorflow as tf tf.set_random_seed(42) config = tf.ConfigProto() config.gpu_options.per_process_gpu_memory_fraction = 0.2 # config.gpu_options.visible_device_list = "0" set_session(tf.Session(config=config)) keras.backend.tensorflow_backend.set_learning_phase(0) self.request_count = 0 # Blackbox 4 # self.im_rows = 512 # self.im_cols = 640 # self.im_chnls = 3 # point grey # self.im_rows = 600 # self.im_cols = 960 # self.im_chnls = 3 # EuroC # self.im_rows = 480 # self.im_cols = 752 # self.im_chnls = 3 self.im_rows = int(im_rows) self.im_cols = int(im_cols) self.im_chnls = int(im_chnls) LOG_DIR = '/'.join( kerasmodel_file.split('/')[0:-1] ) print '+++++++++++++++++++++++++++++++++++++++++++++++++++++++' print '++++++++++ (HDF5ModelImageDescriptor) LOG_DIR=', LOG_DIR print '++++++++++ im_rows=', im_rows, ' im_cols=', im_cols, ' im_chnls=', im_chnls print '+++++++++++++++++++++++++++++++++++++++++++++++++++++++' model_type = LOG_DIR.split('/')[-1] assert os.path.isdir( LOG_DIR ), "The LOG_DIR doesnot exist, or there is a permission issue. LOG_DIR="+LOG_DIR # See if cached file exists, if yes load it else do as usual digest = '/tmp/'+str(hashlib.sha256(kerasmodel_file).hexdigest())+'.h5' print 'Check for exisitance of file', digest if os.path.isfile(digest): print 'The cached file, ', digest, ' seems to exists, load that file' print tcol.OKGREEN, 'Load model (from cache): ', digest, tcol.ENDC model = keras.models.load_model( digest, custom_objects={'NetVLADLayer': NetVLADLayer, 'GhostVLADLayer':GhostVLADLayer} ) model.summary() print tcol.OKGREEN, 'Load model (from cache): ', digest, tcol.ENDC self.model = model self.model_type = model_type else: # Load from HDF5 assert os.path.isfile( kerasmodel_file ), 'The model weights file doesnot exists or there is a permission issue.'+"kerasmodel_file="+kerasmodel_file model_fname = kerasmodel_file print tcol.OKGREEN, 'Load model: ', model_fname, tcol.ENDC model = keras.models.load_model( model_fname, custom_objects={'NetVLADLayer': NetVLADLayer, 'GhostVLADLayer':GhostVLADLayer} ) old_input_shape = model._layers[0].input_shape print 'OLD MODEL: ', 'input_shape=', str(old_input_shape) model.summary() model_visual_fname = None if model_visual_fname is not None: print 'Writing Model Visual to: ', model_visual_fname keras.utils.plot_model( model, to_file=model_visual_fname, show_shapes=True ) # Replace Input Layer new_model = change_model_inputshape( model, new_input_shape=(1,self.im_rows,self.im_cols,self.im_chnls) ) new_input_shape = new_model._layers[0].input_shape print 'OLD MODEL: ', 'input_shape=', str(old_input_shape) print 'NEW MODEL: input_shape=', str(new_input_shape) self.model = new_model self.model_type = model_type # Write to cache if True: digest = '/tmp/'+str(hashlib.sha256(kerasmodel_file).hexdigest())+'.h5' print tcol.WARNING, '}}}}}}}}\nWrite out cache in '+digest+'\n}}}}}}}}', tcol.ENDC self.model.save( digest ) # Doing this is a hack to force keras to allocate GPU memory. Don't comment this, tmp_zer = np.zeros( (1,self.im_rows,self.im_cols,self.im_chnls), dtype='float32' ) tmp_zer_out = self.model.predict( tmp_zer ) print 'model input.shape=', tmp_zer.shape, '\toutput.shape=', tmp_zer_out.shape print 'model_type=', self.model_type print '-----' print '\tinput_image.shape=', tmp_zer.shape print '\toutput.shape=', tmp_zer_out.shape print '\tminmax=', np.min( tmp_zer_out ), np.max( tmp_zer_out ) print '\tnorm=', np.linalg.norm( tmp_zer_out ) print '\tdtype=', tmp_zer_out.dtype print '-----' def handle_req( self, req ): """ The received image from CV bridge has to be [0,255]. In function makes it to intensity range [-0.5 to 0.5] """ ## Get Image out of req cv_image = CvBridge().imgmsg_to_cv2( req.ima ) print '[HDF5ModelImageDescriptor Handle Request#%d] cv_image.shape' %(self.request_count), cv_image.shape, '\ta=', req.a, '\tt=', req.ima.header.stamp if len(cv_image.shape)==2: # print 'Input dimensions are NxM but I am expecting it to be NxMxC, so np.expand_dims' cv_image = np.expand_dims( cv_image, -1 ) elif len( cv_image.shape )==3: pass else: assert( False ) assert (cv_image.shape[0] == self.im_rows and cv_image.shape[1] == self.im_cols and cv_image.shape[2] == self.im_chnls),\ "\n[whole_image_descriptor_compute_server] Input shape of the image \ does not match with the allocated GPU memory. Expecting an input image of \ size %dx%dx%d, but received : %s" %(self.im_rows, self.im_cols, self.im_chnls, str(cv_image.shape) ) # cv2.imshow( 'whole_image_descriptor_compute_server:imshow', cv_image.astype('uint8') ) # cv2.waitKey(10) # cv2.imwrite( '/app/tmp/%s.jpg' %( str(req.ima.header.stamp) ), cv_image ) ## Compute Descriptor start_time = time.time() # i__image = np.expand_dims( cv_image.astype('float32'), 0 ) # i__image = (np.expand_dims( cv_image.astype('float32'), 0 ) - 128.)/255. [-0.5,0.5] i__image = (np.expand_dims( cv_image.astype('float32'), 0 ) - 128.)*2.0/255. #[-1,1] u = self.model.predict( i__image ) print tcol.HEADER, 'Descriptor Computed in %4.4fms' %( 1000. *(time.time() - start_time) ), tcol.ENDC print '\tinput_image.shape=', cv_image.shape, print '\tinput_image dtype=', cv_image.dtype print tcol.OKBLUE, '\tinput image (to neuralnet) minmax=', np.min( i__image ), np.max( i__image ), tcol.ENDC print '\tdesc.shape=', u.shape, print '\tdesc minmax=', np.min( u ), np.max( u ), print '\tnorm=', np.linalg.norm(u[0]) print '\tmodel_type=', self.model_type self.request_count += 1 ## Populate output message result = WholeImageDescriptorComputeResponse() # result.desc = [ cv_image.shape[0], cv_image.shape[1] ] result.desc = u[0,:] result.model_type = self.model_type return result rospy.init_node( 'whole_image_descriptor_compute_server' ) ## ## Load the config file and read image row, col ## fs_image_width = -1 fs_image_height = -1 fs_image_chnls = 1 if True: # read from param `config_file` if not rospy.has_param( '~config_file'): print 'FATAL...cannot find param ~config_file. This is needed to determine size of the input image to allocate GPU memory. If you do not specify the config_file, you need to atleast specify the nrows, ncols, nchnls' rospy.logerr( '[whole_image_descriptor_compute_server]FATAL...cannot find param ~config_file. This is needed to determine size of the input image to allocate GPU memory. If you do not specify the config_file, you need to atleast specify the nrows, ncols, nchnls' ) if ( rospy.has_param( '~nrows') and rospy.has_param( '~ncols') ): print tcol.OKGREEN, 'However, you seem to have set the parameters nrows and ncols, so will read those.', tcol.ENDC else: quit() # quit only if you cannot see nrows and ncols else: config_file = rospy.get_param('~config_file') print '++++++++\n++++++++ config_file: ', config_file if not os.path.isfile(config_file): print 'FATAL...cannot find config_file: ', config_file rospy.logerr( '[whole_image_descriptor_compute_server]FATAL...cannot find config_file: '+ config_file ) quit() print '++++++++ READ opencv-yaml file: ', config_file fs = cv2.FileStorage(config_file, cv2.FILE_STORAGE_READ) fs_image_width = int( fs.getNode( "image_width" ).real() ) fs_image_height = int( fs.getNode( "image_height" ).real() ) print '++++++++ opencv-yaml:: image_width=', fs_image_width, ' image_height=', fs_image_height print '++++++++' ## ## Load nrows and ncols directly as config ## if True: # read from param `nrows` and `ncols` if fs_image_width < 0 : if ( not rospy.has_param( '~nrows') or not rospy.has_param( '~ncols') or not rospy.has_param( '~nchnls') ): print 'FATAL...cannot find param either of ~nrows, ~ncols, ~nchnls. This is needed to determine size of the input image to allocate GPU memory' rospy.logerr( '[whole_image_descriptor_compute_server] FATAL...cannot find param either of ~nrows, ~ncols, nchnls. This is needed to determine size of the input image to allocate GPU memory' ) quit() else: fs_image_height = rospy.get_param('~nrows') fs_image_width = rospy.get_param('~ncols') fs_image_chnls = rospy.get_param('~nchnls') print '~~~~~~~~~~~~~~~~' print '~nrows = ', fs_image_height, '\t~ncols = ', fs_image_width, '\t~nchnls = ', fs_image_chnls print '~~~~~~~~~~~~~~~~' ## ## Load Channels ## if True: if not rospy.has_param( '~nchnls' ): rospy.logerr( "[whole_image_descriptor_compute_server] FATAL....cannot file cmd param nchnls.") quit() else: fs_image_chnls = rospy.get_param('~nchnls') ## ## Start Server ## # Something wrong with jsonfile loading or with change_model_inputshape(). if True: # kerasmodel_file = '/models.keras/Apr2019/gray_conv6_K16Ghost1__centeredinput/core_model.%d.keras' %(500) if rospy.has_param( '~kerasmodel_file'): kerasmodel_file = rospy.get_param('~kerasmodel_file') else: print tcol.ERROR, 'FATAL...missing specification of model file. You need to specify ~kerasmodel_file', tcol.ENDC quit() # gpu_netvlad = JSONModelImageDescriptor( kerasmodel_file=kerasmodel_file, im_rows=fs_image_height, im_cols=fs_image_width, im_chnls=fs_image_chnls ) gpu_netvlad = HDF5ModelImageDescriptor( kerasmodel_file=kerasmodel_file, im_rows=fs_image_height, im_cols=fs_image_width, im_chnls=fs_image_chnls ) else: #gpu_s = SampleGPUComputer() gpu_netvlad = NetVLADImageDescriptor( im_rows=fs_image_height, im_cols=fs_image_width ) # gpu_netvlad = ReljaNetVLAD( im_rows=fs_image_height, im_cols=fs_image_width ) s = rospy.Service( 'whole_image_descriptor_compute', WholeImageDescriptorCompute, gpu_netvlad.handle_req ) print 'whole_image_descriptor_compute_server is running' # image = cv2.resize( cv2.imread( '/app/lena.jpg'), (224,224) ) # print gpu_s.model.predict( np.expand_dims(image,0) ) rospy.spin() ================================================ FILE: src/Cerebro.cpp ================================================ #include "Cerebro.h" //-------------------------------------------------------------// //--- Setup Cerebro //-------------------------------------------------------------// Cerebro::Cerebro( ros::NodeHandle& nh ) { b_run_thread = false; b_descriptor_computer_thread = false; this->nh = nh; connected_to_descriptor_server = false; descriptor_size_available = false; } void Cerebro::setDataManager( DataManager* dataManager ) { this->dataManager = dataManager; m_dataManager_available = true; } void Cerebro::setPublishers( const string base_topic_name ) { string pub_loopedge_topic = base_topic_name+"/loopedge"; ROS_INFO( "[Cerebro::setPublishers] Publish %s", pub_loopedge_topic.c_str() ); pub_loopedge = nh.advertise( pub_loopedge_topic, 1000 ); m_pub_available = true; } //-------------------------------------------------------------// //--- END Setup Cerebro //-------------------------------------------------------------// //------------------------------------------------------------------// // per Keyframe Descriptor Computation // Can have more threads to compute other aspects for keyframes // like texts, objects visible etc. TODO //------------------------------------------------------------------// #define __Cerebro__descriptor_computer_thread( msg ) ; // #define __Cerebro__descriptor_computer_thread( msg ) msg #define __Cerebro__descriptor_computer_thread__imp( msg ) ; // #define __Cerebro__descriptor_computer_thread__imp( msg ) msg; void Cerebro::descriptor_computer_thread() { assert( m_dataManager_available && "You need to set the DataManager in class Cerebro before execution of the run() thread can begin. You can set the dataManager by call to Cerebro::setDataManager()\n"); assert( b_descriptor_computer_thread && "You need to call descriptor_computer_thread_enable() before spawning the thread\n" ); //--------- // Send a zeros image to the server just to know the descriptor size int nrows=-1, ncols=-1, desc_size=-1; int nChannels = 1; //< ***This needs to be set correctly depending on the model_type. If you need error messages from server about channels size, you need to change this. **** //---------- // Service Call // Sample Code : https://github.com/mpkuse/cerebro/blob/master/src/unittest/unittest_rosservice_client.cpp connected_to_descriptor_server = false; descriptor_size_available = false; int n_sec_wait_for_connection = 71; cout << TermColor::iYELLOW() << "[Cerebro::descriptor_computer_thread]Attempt connecting to ros-service for " << n_sec_wait_for_connection << " sec (will give up after that)\n" << TermColor::RESET(); ros::ServiceClient client = nh.serviceClient( "/whole_image_descriptor_compute" ); client.waitForExistence( ros::Duration(n_sec_wait_for_connection, 0) ); //wait maximum 10 sec if( !client.exists() ) { ROS_ERROR( "[Cerebro::descriptor_computer_thread]Connection to server NOT successful. I tried waiting for %d sec, still couldnt establish connection. Quiting the thread.", n_sec_wait_for_connection ); return; } else std::cout << TermColor::GREEN() << "Connection to ros-service ``" << client.getService() << "`` established" << TermColor::RESET() << endl; connected_to_descriptor_server = true; ElapsedTime _est_desc_compute_time_ms; { auto _abs_cam = dataManager->getAbstractCameraRef(); assert( _abs_cam && "[Cerebro::descriptor_computer_thread] request from cerebro to access camera from dataManager is invalid. This means that camera was not yet set in dataManager\n"); nrows = _abs_cam->imageHeight(); ncols = _abs_cam->imageWidth(); cv::Mat zero_image; if( nChannels == 3 ) zero_image = cv::Mat::zeros( nrows, ncols, CV_8UC3 ); else if( nChannels == 1 ) zero_image = cv::Mat::zeros( nrows, ncols, CV_8UC1 ); else { assert( false && "Invalid number of channels specified in Cerebro::descriptor_computer_thread() "); cout << TermColor::RED() << "[ERROR] Ax Invalid number of channels specified in Cerebro::descriptor_computer_thread() " << endl << TermColor::RESET(); exit(3); } // create zero image sensor_msgs::Image sensor_msgs::ImagePtr image_msg; if( nChannels == 3 ) image_msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", zero_image).toImageMsg(); else if( nChannels == 1 ) image_msg = cv_bridge::CvImage(std_msgs::Header(), "mono8", zero_image).toImageMsg(); else { assert( false && "Invalid number of channels specified in Cerebro::descriptor_computer_thread() "); cout << TermColor::RED() << "[ERROR] Bx Invalid number of channels specified in Cerebro::descriptor_computer_thread() " << endl << TermColor::RESET(); exit(3); } image_msg->header.stamp = ros::Time::now(); cerebro::WholeImageDescriptorCompute srv; //service message srv.request.ima = *image_msg; srv.request.a = 986; // make request to server _est_desc_compute_time_ms.tic(); if( client.call( srv ) ) { __Cerebro__descriptor_computer_thread(std::cout << "Received response from server\t";) __Cerebro__descriptor_computer_thread(std::cout << "desc.size=" << srv.response.desc.size() << std::endl;) assert( srv.response.desc.size() > 0 && "The received descriptor appear to be of zero length. This is a fatal error.\n" ); desc_size = int( srv.response.desc.size() ); } } assert( nrows > 0 && ncols > 0 && desc_size > 0 ); int estimated_descriptor_compute_time_ms = _est_desc_compute_time_ms.toc_milli(); cout << "[Cerebro::descriptor_computer_thread] nrows=" << nrows << " ncols=" << ncols << " nChannels=" << nChannels << " ===> desc_size=" << desc_size << "\t estimated_descriptor_compute_time_ms=" << estimated_descriptor_compute_time_ms << endl; this->descriptor_size = int(desc_size); descriptor_size_available = true; ros::Rate rate(20); auto data_map = dataManager->getDataMapRef(); auto img_data_mgr = dataManager->getImageManagerRef(); #if 1 //***************** // If loadStateFromDisk, ie. if data_map already has a lot of items means that the // state was preloaded. in this case just setup this thread appropriately. //************************** if( data_map->begin() == data_map->end() ) { // This means the data_map is empty, which inturn means fresh run cout << TermColor::iBLUE() << "[Cerebro::descriptor_computer_thread] Looks like data_map is empty which means this is a fresh run" << TermColor::RESET() << endl;; } else { // This means, state was preloaded from file cout << TermColor::iGREEN() << "[Cerebro::descriptor_computer_thread] Looks like data_map is NOT empty which means state was loaded from disk" << TermColor::RESET() << endl;; // loop over data_map and make a note of all the timestamps where descriptor is available. int n_whlimgdescavai = 0; // img_data_mgr->print_status(); for( auto itd=data_map->begin() ; itd!=data_map->end() ; itd++ ) { if( itd->second->isWholeImageDescriptorAvailable() ) { n_whlimgdescavai++; wholeImageComputedList_pushback( itd->first ); // Also make sure these images are rechable. bool kxxx = img_data_mgr->isImageRetrivable( "left_image", itd->first ); if( kxxx == false ) // this is bad { cout << "[Cerebro::descriptor_computer_thread] THIS IS BAD. REPORT IT TO AUTHORS WITH CODE jewbcjsmn\nEXIT.....\n"; exit(1); } } } cout << "[Cerebro::descriptor_computer_thread]i find "<< n_whlimgdescavai << " datanodes where image descrptor is available and images are retrivable through the ImageDataManager\n"; } #endif ros::Time last_proc_timestamp =ros::Time(); int n_computed=0; ElapsedTime _time; int incoming_diff_ms = 0; while( b_descriptor_computer_thread ) { if( data_map->begin() == data_map->end() ) { __Cerebro__descriptor_computer_thread( cout << "nothing to compute descriptor\n" ); std::this_thread::sleep_for( std::chrono::milliseconds( 1000 ) ); continue; } ros::Time lb = data_map->rbegin()->first - ros::Duration(10, 0); // look at recent 10sec. auto S = data_map->lower_bound( lb ); auto E = data_map->end(); __Cerebro__descriptor_computer_thread(cout << "[Cerebro::descriptor_computer_thread]] S=" << S->first << " E=" << (data_map->rbegin())->first << endl;) for( auto it = S; it != E ; it++ ) { // cout << "[Cerebro::descriptor_computer_thread]try : " << it->first << "\t"; // cout << "isWholeImageDescriptorAvailable=" << it->second->isWholeImageDescriptorAvailable() << "\t"; // cout << "isKeyFrame=" << it->second->isKeyFrame() << "\t"; // cout << endl; //descriptor does not exisit at this stamp, so compute it. // Here I compute the whole image descriptor only at keyframes, you may try something like, if the pose is available compute it. if( it->second->isWholeImageDescriptorAvailable() == false && it->second->isKeyFrame() && it->first > last_proc_timestamp ) { __Cerebro__descriptor_computer_thread(cout << TermColor::MAGENTA() << "[Cerebro::descriptor_computer_thread]--- process t=" << it->first << "\trel_t=" << it->first - dataManager->getPose0Stamp() << TermColor::RESET() << " n_computed=" << n_computed << endl;) n_computed++; incoming_diff_ms = (it->first - last_proc_timestamp).toSec() * 1000. ; float skip_frac = 1.0 - incoming_diff_ms/float(estimated_descriptor_compute_time_ms) ; __Cerebro__descriptor_computer_thread( cout << "incoming_diff_ms = " << incoming_diff_ms << "\testimated_descriptor_compute_time_ms=" << estimated_descriptor_compute_time_ms << " so, skip_frac="<< skip_frac << endl; ) last_proc_timestamp = it->first; // if( n_computed%2 == 0 ) // simple skip if( n_computed>4 && ( rand()/ float(RAND_MAX) ) < skip_frac ) // dynamic skip { __Cerebro__descriptor_computer_thread( cout << "\t\t...SKIP...\n"; ) continue; } if( it->second->getNumberOfSuccessfullyTrackedFeatures() < 20 ) { __Cerebro__descriptor_computer_thread( cout << "[Cerebro::descriptor_computer_thread] skip computing whole-image-descriptor for this image because it appears to be a kidnapped image frame.\n" ; ) continue; } _time.tic(); // use it->second->getImage() to compute descriptor. call the service cv::Mat image_curr; #if 0 // set this to 1 to get image from data_map (deprecated way), 0 to get image from image_manager. // old code in which images were stored in nodes image_curr = it->second->getImage(); #else // using image data manager { //dont remove these braces, it is used for scoping and automatic deallocation cv::Mat tmp_img; bool getimstatus = img_data_mgr->getImage( "left_image", it->first, tmp_img ); if( getimstatus == false ) { __Cerebro__descriptor_computer_thread( cout << "since I cannot getImage, goto hurr_here\n"; ); goto huur_here; } if( nChannels == 3 && tmp_img.channels() == 1 ) cv::cvtColor( tmp_img, image_curr, CV_GRAY2BGR ); else if( nChannels==1 && tmp_img.channels() == 3) cv::cvtColor( tmp_img, image_curr, CV_BGR2GRAY ); else image_curr = tmp_img; } __Cerebro__descriptor_computer_thread( cout << "image from mgr info: " << MiscUtils::cvmat_info(image_curr) << endl; cout << "nChannels=" << nChannels << endl; ) #endif sensor_msgs::ImagePtr image_msg; if( nChannels == 3 ) { image_msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image_curr).toImageMsg(); } else if( nChannels == 1 ) { // cout << "cv_bridge mono8 encoding\n"; image_msg = cv_bridge::CvImage(std_msgs::Header(), "mono8", image_curr).toImageMsg(); } else { assert( false && "Invalid number of channels specified in Cerebro::descriptor_computer_thread() "); cout << TermColor::RED() << "[ERROR] Cx Invalid number of channels specified in Cerebro::descriptor_computer_thread() " << endl << TermColor::RESET(); exit(3); } image_msg->header.stamp = ros::Time( it->first ); cerebro::WholeImageDescriptorCompute srv; //service message srv.request.ima = *image_msg; srv.request.a = 986; if( client.call( srv ) ) { // __Cerebro__descriptor_computer_thread(std::cout << "Received response from server\t";) // __Cerebro__descriptor_computer_thread(std::cout << "desc.size=" << srv.response.desc.size() << std::endl;) assert( srv.response.desc.size() > 0 && "The received descriptor appear to be of zero length. This is a fatal error.\n" ); VectorXd vec( srv.response.desc.size() ); // allocated a vector for( int j=0 ; jsecond->setWholeImageDescriptor( vec ); wholeImageComputedList_pushback( it->first ); // __Cerebro__descriptor_computer_thread(cout << "Computed descriptor at t=" << it->first - dataManager->getPose0Stamp() << "\t" << it->first << endl;) // __Cerebro__descriptor_computer_thread(std::cout << "Computed descriptor in (millisec) = " << _time.toc_milli() << endl;) // __Cerebro__descriptor_computer_thread__imp(cout << TermColor::CYAN() << "Computed descriptor at t=" << it->first - dataManager->getPose0Stamp() << "\t" << it->first << TermColor::RESET() << endl); estimated_descriptor_compute_time_ms = _time.toc_milli(); __Cerebro__descriptor_computer_thread__imp( cout << "Computed descriptor at t=" << it->first << " of dim=" << srv.response.desc.size() << " in millisec=" << estimated_descriptor_compute_time_ms << endl; ) } else { ROS_ERROR( "Failed to call ros service" ); } } huur_here: int ddddd=0; //this is here for the goto statement. } rate.sleep(); } cout << "[Cerebro::descriptor_computer_thread] Finished\n"; } // these both functions are newly added. There are still some raw access // limited only to the function ``descrip_N__dot__descrip_0_N()``. const int Cerebro::wholeImageComputedList_size() const { std::lock_guard lk(m_wholeImageComputedList); return wholeImageComputedList.size(); } const ros::Time Cerebro::wholeImageComputedList_at(int k) const { std::lock_guard lk(m_wholeImageComputedList); assert( k>=0 && k lk(m_wholeImageComputedList); this->wholeImageComputedList.push_back( __tx ); // note down where it was computed. } //------------------------------------------------------------------// // END per Keyframe Descriptor Computation //------------------------------------------------------------------// //------------------------------------------------------------------// //---------------- Populate Loop Candidates --------------------// //------------------------------------------------------------------// // #define __Cerebro__run__( msg ) msg ; #define __Cerebro__run__( msg ) ; // #define __Cerebro__run__debug( msg ) msg ; #define __Cerebro__run__debug( msg ) ; /// TODO: In the future more intelligent schemes can be experimented with. Besure to run those in new threads and disable this thread. /// wholeImageComputedList is a list for which descriptors are computed. Similarly other threads can compute /// scene-object labels, text etc etc in addition to currently computed whole-image-descriptor void Cerebro::run() { descrip_N__dot__descrip_0_N(); // faiss__naive_loopcandidate_generator(); // faiss_clique_loopcandidate_generator(); // faiss_multihypothesis_tracking(); } #ifdef HAVE_FAISS ///----------------------------------------------------------------- /// Nearest neighbors search using facebook research's faiss library's IndexFlatIP /// Roughly follows : https://github.com/mpkuse/vins_mono_debug_pkg/blob/master/src_place_recog/faiss_try1.py /// This function is similar in functionality to `descrip_N__dot__descrip_0_N` /// but using faiss void Cerebro::faiss__naive_loopcandidate_generator() { assert( m_dataManager_available && "You need to set the DataManager in class Cerebro before execution of the run() thread can begin. You can set the dataManager by call to Cerebro::setDataManager()\n"); assert( b_run_thread && "you need to call run_thread_enable() before run() can start executing\n" ); //-----------------// //---- Settings ---// //-----------------// const int start_adding_descriptors_to_index_after = 150; const int LOCALITY_THRESH = 12; const float DOT_PROD_THRESH = 0.9; //------ END -----// // wait until connected_to_descriptor_server=true and descriptor_size_available=true if( wait_until__connectedToDescServer_and_descSizeAvailable( 71 ) == false ) { cout << TermColor::RED() << "[Cerebro::faiss__naive_loopcandidate_generator ERROR] wait_until__connectedToDescServer_and_descSizeAvailable returned false implying a timeout.\n" << TermColor::RESET(); return; } __Cerebro__run__( cout << TermColor::GREEN() <<"[Cerebro::faiss__naive_loopcandidate_generator] descriptor_size=" << this->descriptor_size << " connected_to_descriptor_server && descriptor_size_available" << TermColor::RESET() << endl; ) assert( this->descriptor_size> 0 ); cout << TermColor::GREEN() << "init a faiss::IndexFlatIP("<< this->descriptor_size << ")" << TermColor::RESET() << endl; faiss::IndexFlatIP index(this->descriptor_size); ros::Rate rate(10); int l=0, last_l=0; int l_last_added_to_index = 0; auto data_map = dataManager->getDataMapRef(); while( b_run_thread ) { l=wholeImageComputedList_size(); if( l - last_l < 3 ) { __Cerebro__run__debug( cout << "[Cerebro::faiss__naive_loopcandidate_generator]nothing new\n"; ); rate.sleep(); continue; } __Cerebro__run__( cout << TermColor::RED() << "---" << TermColor::RESET() << endl; ) __Cerebro__run__( cout << "l=" << l << endl; ) __Cerebro__run__debug( cout << "[Cerebro::faiss__naive_loopcandidate_generator] data_map.size() = " << data_map->size() << "\tper_image_descriptor_size=" << this->descriptor_size << endl; ) // add __Cerebro__run__(cout << TermColor::YELLOW();) if( l> start_adding_descriptors_to_index_after ) { for( int j=l_last_added_to_index ; jat( wholeImageComputedList_at( j ) )->getWholeImageDescriptor(); VectorXf X_float = X.cast(); float * X_raw = X_float.data(); #if 0 //see if the type casting was correct. for( int g=0;g<5;g++ ) { // cout << "(" << X(g) << "," << X_float(g) << ") "; cout << "(" << X(g) << "," << X_raw[g] << ") "; } cout << endl; #endif index.add( 1, X_raw ); //-TODO } l_last_added_to_index = l-start_adding_descriptors_to_index_after; } else { __Cerebro__run__(cout << "not seen enough. so,, add nothing. Will start adding after "<< start_adding_descriptors_to_index_after<< " descriptors\n";) } __Cerebro__run__(cout << TermColor::RESET();) // search vector tmp_; vector tmp_i; for( int l_i=last_l ; l_iat( wholeImageComputedList_at( l_i ) )->getWholeImageDescriptor(); VectorXf X_float = X.cast(); float * X_raw = X_float.data(); float distances[5]; faiss::Index::idx_t labels[5]; ElapsedTime time_to_search = ElapsedTime(); index.search( 1, X_raw, 5, distances, labels ); //TODO __Cerebro__run__(cout << "search done in (ms): " << time_to_search.toc_milli() << endl;) for( int g=0 ; g<5; g++ ) { __Cerebro__run__( // cout << g << " labels=" << labels[g] << " distances=" << distances[g] << endl; // cout << l_i << "<--("<< distances[g] << ")-->" << labels[g] << "\t"; printf( "%4d<--(%4.2f)-->%d\t", l_i, distances[g], labels[g] ); ) } __Cerebro__run__(cout << endl;) __Cerebro__run__(cout << TermColor::RESET();) tmp_.push_back( distances[0] ); tmp_i.push_back( labels[0] ); } int _n = tmp_.size(); if( _n ==3 && tmp_[_n-1] > DOT_PROD_THRESH && abs(tmp_i[0] - tmp_i[1]) < LOCALITY_THRESH && abs(tmp_i[0] - tmp_i[2]) < LOCALITY_THRESH ) { __Cerebro__run__( cout << TermColor::RED() << "loop found" << l-1 << "<--->" << tmp_i[2] << TermColor::RESET(); ) { std::lock_guard lk_foundloops(m_foundLoops); foundLoops.push_back( std::make_tuple( wholeImageComputedList_at(l-1), wholeImageComputedList_at(tmp_i[2]), tmp_[2] ) ); } } last_l = l; rate.sleep(); } cout << "[Cerebro::faiss__naive_loopcandidate_generator()] Finished\n"; } // #define __faiss_clique_loopcandidate_generator__imp(msg) msg; #define __faiss_clique_loopcandidate_generator__imp(msg) ; // #define __faiss_clique_loopcandidate_generator__debug(msg) msg; #define __faiss_clique_loopcandidate_generator__debug(msg) ; // #define __faiss_clique_loopcandidate_generator__addtoindex(msg) msg; #define __faiss_clique_loopcandidate_generator__addtoindex(msg) ; #define __faiss_clique_loopcandidate_generator__search(msg) msg; // #define __faiss_clique_loopcandidate_generator__search(msg) ; void Cerebro::faiss_clique_loopcandidate_generator() { assert( m_dataManager_available && "You need to set the DataManager in class Cerebro before execution of the run() thread can begin. You can set the dataManager by call to Cerebro::setDataManager()\n"); assert( b_run_thread && "you need to call run_thread_enable() before run() can start executing\n" ); //-----------------// //---- Settings ---// //-----------------// const int start_adding_descriptors_to_index_after = 150; const int K_NEAREST_NEIGHBOURS=5; const double DOT_PROD_THRESH = 0.85; //0.85 const int LOCALITY = 7;//7 const int reset_accumulation_every_n_frames = 4; //4 // wait until connected_to_descriptor_server=true and descriptor_size_available=true if( wait_until__connectedToDescServer_and_descSizeAvailable( 71 ) == false ) { cout << TermColor::RED() << "[Cerebro::faiss__naive_loopcandidate_generator ERROR] wait_until__connectedToDescServer_and_descSizeAvailable returned false implying a timeout.\n" << TermColor::RESET(); return; } __faiss_clique_loopcandidate_generator__imp( cout << TermColor::GREEN() <<"[Cerebro::faiss__naive_loopcandidate_generator] descriptor_size=" << this->descriptor_size << " connected_to_descriptor_server && descriptor_size_available" << TermColor::RESET() << endl; ) assert( this->descriptor_size> 0 ); // init faiss index __faiss_clique_loopcandidate_generator__imp( cout << TermColor::GREEN() << "init a faiss::IndexFlatIP("<< this->descriptor_size << ")" << TermColor::RESET() << endl; ) faiss::IndexFlatIP index(this->descriptor_size); ros::Rate rate(10); auto data_map = dataManager->getDataMapRef(); int l=0, last_l=0, l_last_added_to_index=0; std::map< int, std::map< int, float> > data_strc; float * distances = new float[K_NEAREST_NEIGHBOURS]; faiss::Index::idx_t * labels = new faiss::Index::idx_t[K_NEAREST_NEIGHBOURS]; map retained; //< Accumulation map. reset every say 3 indices. tune this as per amount of computation available while( b_run_thread ) { l = wholeImageComputedList_size(); if( l<= last_l ) { __faiss_clique_loopcandidate_generator__debug( cout << "[Cerebro::faiss_clique_loopcandidate_generator] nothing new\n";) rate.sleep(); continue; } __faiss_clique_loopcandidate_generator__imp( cout << TermColor::RED() << "--- " << TermColor::RESET() << l << "\t"; cout << "new [" << last_l << " to " << l << ")\n"; ) //--------- add __faiss_clique_loopcandidate_generator__addtoindex(cout << TermColor::YELLOW();) if( l>start_adding_descriptors_to_index_after) { for( int j=l_last_added_to_index ; jat( wholeImageComputedList_at( j ) )->getWholeImageDescriptor(); VectorXf X_float = X.cast(); float * X_raw = X_float.data(); #if 0 //see if the type casting was correct. for( int g=0;g<5;g++ ) { // cout << "(" << X(g) << "," << X_float(g) << ") "; cout << "(" << X(g) << "," << X_raw[g] << ") "; } cout << endl; #endif index.add( 1, X_raw ); } l_last_added_to_index = l-start_adding_descriptors_to_index_after; } else { __faiss_clique_loopcandidate_generator__addtoindex( cout << "not seen enough. so,, add nothing. Will start adding after "<< start_adding_descriptors_to_index_after<< " descriptors\n"; ) } __faiss_clique_loopcandidate_generator__addtoindex(cout << TermColor::RESET();) //----------- search __faiss_clique_loopcandidate_generator__search(cout << TermColor::MAGENTA();) for( int l_i = last_l ; l_i < l ; l_i++ ) { __faiss_clique_loopcandidate_generator__search( cout << "\t\tindex.search(" << l_i << ")\t"; cout << "index.search(" << wholeImageComputedList_at(l_i) << ")\t"; cout << "index.ntotal=" << index.ntotal << "\n"; ) if( index.ntotal < K_NEAREST_NEIGHBOURS ) break; // l_i's descriptor VectorXd X = data_map->at( wholeImageComputedList_at( l_i ) )->getWholeImageDescriptor(); VectorXf X_float = X.cast(); float * X_raw = X_float.data(); // ElapsedTime time_to_search = ElapsedTime(); index.search( 1, X_raw, K_NEAREST_NEIGHBOURS, distances, labels ); // cout << "search done in (ms): " << time_to_search.toc_milli() << endl; __faiss_clique_loopcandidate_generator__search( // Printing for( int g=0 ; g DOT_PROD_THRESH ) cout << TermColor::GREEN(); else cout << TermColor::MAGENTA(); printf( "%4d<--(%4.2f)-->%d\t", l_i, distances[g], labels[g] ); cout << TermColor::RESET(); } cout << endl; ) // Go thru each nearest neighbours and add to list separated ones. vv same as 168, ignore // for example, if the neighbours: `802<--(0.92)-->606 802<--(0.89)-->168 802<--(0.89)-->172 802<--(0.89)-->169 802<--(0.88)-->607` // ^^ ^^ ^^same as 168, so ignore ^^ same as 606, ignore // cout << "loop thru " << K_NEAREST_NEIGHBOURS << "\n"; for( int g=0 ; g < K_NEAREST_NEIGHBOURS ; g++ ) { if( distances[g] < DOT_PROD_THRESH ) //since distances are arranged in decending order, if you start seeing below my threshold its time to not process further. break; // cout << "g="<first=" << ity->first << endl; if( (ity->first - labels[g]) < LOCALITY ) { // cout << "\tcond satisfied\n"; duplicate = ity->first; break; } } if( duplicate != -1 ) { // cout << "\tthis was not uniq so increment the key=" << duplicate << " by 1\n"; retained[ duplicate ]++; } else { // cout << "\tadd new key at " << labels[g] << endl; retained[ labels[g] ] = 1; } } if( retained.size() > 0 && l_i%reset_accumulation_every_n_frames == 0) { __faiss_clique_loopcandidate_generator__search( cout << TermColor::iYELLOW() << "l_i=" << l_i << " retained (ie. added to `foundLoops`) cout all: "; for( auto ity=retained.begin() ; ity!=retained.end() ; ity++ ) { cout << ity->first << ":" << ity->second << ", "; } cout << TermColor::RESET() << endl; ) //%%%% //%%%% NOTE: If you put all the feasible candidates into the foundLoops it causes //%%%% The pose computation to queue-up, since the number of candidates are too many. //%%%% Usually addition of more than 2 candidates at a time spells trouble. //%%%% I use simple heuristics when there are more than 3 items in the retained. if( retained.size() == 1 ) { __faiss_clique_loopcandidate_generator__search( cout << "only 1 item in retained, pushback " << wholeImageComputedList_at(l-1) << "<--->" << wholeImageComputedList_at(retained.begin()->first) << endl; ) std::lock_guard lk_foundloops(m_foundLoops); // __faiss_clique_loopcandidate_generator__search( cout << "pushback: " << wholeImageComputedList_at(l-1) << "<--->" << wholeImageComputedList_at(retained.begin()->first) << endl; ) foundLoops.push_back( std::make_tuple( wholeImageComputedList_at(l-1), wholeImageComputedList_at(retained.begin()->first ), 0.9 ) ); } if( retained.size() > 1 ) { int percent = 100. / retained.size(); //will retain this many __faiss_clique_loopcandidate_generator__search( cout << "Retain %=" << percent << endl; ) for( auto ity=retained.begin() ; ity!=retained.end() ; ity++ ) { if( rand()%100 < percent ) { std::lock_guard lk_foundloops(m_foundLoops); __faiss_clique_loopcandidate_generator__search( cout << "pushback: " << wholeImageComputedList_at(l-1) << "<--->" << wholeImageComputedList_at(ity->first) << endl; ) foundLoops.push_back( std::make_tuple( wholeImageComputedList_at(l-1), wholeImageComputedList_at(ity->first ), 0.9 ) ); } } } retained.clear(); } } last_l = l; rate.sleep(); } delete [] distances; delete [] labels; cout << "[Cerebro::faiss_clique_loopcandidate_generator] Finished Thread\n"; } #define ___faiss_multihypothesis_tracking___add(msg) msg; // #define ___faiss_multihypothesis_tracking___add(msg) ; #define ___faiss_multihypothesis_tracking___search(msg) msg; // #define ___faiss_multihypothesis_tracking___search(msg) ; void Cerebro::faiss_multihypothesis_tracking() { assert( m_dataManager_available && "You need to set the DataManager in class Cerebro before execution of the run() thread can begin. You can set the dataManager by call to Cerebro::setDataManager()\n"); assert( b_run_thread && "you need to call run_thread_enable() before run() can start executing\n" ); //-----------------// //---- Settings ---// //-----------------// const int start_adding_descriptors_to_index_after = 150; const int K_NEAREST_NEIGHBOURS=5; //-----------------// //---- Wait ----// //-----------------// // wait until connected_to_descriptor_server=true and descriptor_size_available=true if( wait_until__connectedToDescServer_and_descSizeAvailable( 71 ) == false ) { cout << TermColor::RED() << "[Cerebro::faiss__naive_loopcandidate_generator ERROR] wait_until__connectedToDescServer_and_descSizeAvailable returned false implying a timeout.\n" << TermColor::RESET(); return; } cout << TermColor::GREEN() <<"[Cerebro::faiss_multihypothesis_tracking] descriptor_size=" << this->descriptor_size << " connected_to_descriptor_server && descriptor_size_available" << TermColor::RESET() << endl; assert( this->descriptor_size> 0 ); //---------------------// //--- init faiss index // //---------------------// cout << TermColor::GREEN() << "[[Cerebro::faiss_multihypothesis_tracking]] init a faiss::IndexFlatIP("<< this->descriptor_size << ")" << TermColor::RESET() << endl; faiss::IndexFlatIP index(this->descriptor_size); // init (misc) ros::Rate rate(10); auto data_map = dataManager->getDataMapRef(); HypothesisManager * hyp_manager = new HypothesisManager(); // there is a delete at the end of this function // Start monitoring thread hyp_manager->monitoring_thread_enable(); // hyp_manager->monitoring_thread_disable(); std::thread hyp_monitoring_th( &HypothesisManager::monitoring_thread, hyp_manager ); int l=0, last_l=0, l_last_added_to_index=0; float * distances = new float[K_NEAREST_NEIGHBOURS]; faiss::Index::idx_t * labels = new faiss::Index::idx_t[K_NEAREST_NEIGHBOURS]; //---------------------// // While // index.add( i-150 ) // index.search( i ) //---------------------// while( b_run_thread ) { l = wholeImageComputedList_size(); if( l<= last_l ) { cout << "[Cerebro::faiss_multihypothesis_tracking] nothing new\n"; rate.sleep(); continue; } // looks like new descriptors are available. cout << TermColor::RED() << "--- " << TermColor::RESET() << l << "\t"; cout << "new [" << last_l << " to " << l << ")\n"; //--------- add ___faiss_multihypothesis_tracking___add(cout << TermColor::YELLOW();) if( l>start_adding_descriptors_to_index_after) { for( int j=l_last_added_to_index ; jat( wholeImageComputedList_at( j ) )->getWholeImageDescriptor(); VectorXf X_float = X.cast(); float * X_raw = X_float.data(); #if 0 //see if the type casting was correct. for( int g=0;g<5;g++ ) { // cout << "(" << X(g) << "," << X_float(g) << ") "; cout << "(" << X(g) << "," << X_raw[g] << ") "; } cout << endl; #endif index.add( 1, X_raw ); } l_last_added_to_index = l-start_adding_descriptors_to_index_after; } else { ___faiss_multihypothesis_tracking___add( cout << "not seen enough. so,, add nothing to the index. Will start adding after "<< start_adding_descriptors_to_index_after<< " descriptors\n"; ) } ___faiss_multihypothesis_tracking___add(cout << TermColor::RESET();) //----------- search cout << TermColor::MAGENTA(); for( int l_i = last_l ; l_i < l ; l_i++ ) { cout << "\t\tindex.search(" << l_i << ")\t"; cout << "index.search(" << wholeImageComputedList_at(l_i) << ")\t"; cout << "index.ntotal=" << index.ntotal << "\n"; if( index.ntotal < K_NEAREST_NEIGHBOURS ) break; // l_i's descriptor VectorXd X = data_map->at( wholeImageComputedList_at( l_i ) )->getWholeImageDescriptor(); VectorXf X_float = X.cast(); float * X_raw = X_float.data(); // ElapsedTime time_to_search = ElapsedTime(); index.search( 1, X_raw, K_NEAREST_NEIGHBOURS, distances, labels ); // cout << "search done in (ms): " << time_to_search.toc_milli() << endl; // Loop over all the nearest neighbours cout << "\t\t"; for( int g=0 ; g 0.85 ) cout << TermColor::GREEN(); else cout << TermColor::MAGENTA(); printf( "g=%d: %4d<--(%4.2f)-->%d\t", g, l_i, distances[g], labels[g] ); cout << TermColor::RESET(); if( distances[g] > 0.85 ) { // Send (l_i, labels[g], distances[g] ) to HypothesisManager hyp_manager->add_node( l_i, labels[g], distances[g] ); } } cout << endl ; // Loop through each hypothesis and decrement the time-to-live hyp_manager->digest(); } last_l = l; rate.sleep(); } hyp_manager->monitoring_thread_disable(); hyp_monitoring_th.join(); delete [] distances; delete [] labels; delete hyp_manager; cout << "[Cerebro::faiss_multihypothesis_tracking] Finished Thread\n"; } #endif //HAVE_FAISS ///----------------------------------------------------------------- ///----------------------------------------------------------------- /// This implements a simple loopclosure detection scheme based on dot-product of descriptor-vectors. /// // 1 will plot the result of dot product as image. 0 will not plot to image // #define __Cerebro__descrip_N__dot__descrip_0_N__implotting 0 #define __Cerebro__descrip_N__dot__descrip_0_N__implotting 1 void Cerebro::descrip_N__dot__descrip_0_N() { assert( m_dataManager_available && "You need to set the DataManager in class Cerebro before execution of the run() thread can begin. You can set the dataManager by call to Cerebro::setDataManager()\n"); assert( b_run_thread && "you need to call run_thread_enable() before run() can start executing\n" ); cout << TermColor::GREEN() << "[Cerebro::descrip_N__dot__descrip_0_N] Start thread" << TermColor::RESET() << endl; //--- //--- Main settings for this function //--- int LOCALITY_THRESH = 12; float DOT_PROD_THRESH = 0.85; const int start_adding_descriptors_to_index_after = 50; ros::Rate rate(10); #if 0 // wait until connected_to_descriptor_server=true and descriptor_size_available=true int wait_itr = 0; while( true ) { if( this->connected_to_descriptor_server && this->descriptor_size_available) break; __Cerebro__run__(cout << wait_itr << " [Cerebro::descrip_N__dot__descrip_0_N]waiting for `descriptor_size_available` to be true\n";) rate.sleep(); wait_itr++; if( wait_itr > 157 ) { __Cerebro__run__( cout << TermColor::RED() << "[Cerebro::descrip_N__dot__descrip_0_N] `this->connected_to_descriptor_server && this->descriptor_size_available` has not become true dispite waiting for about 15sec. So quiting the run thread.\n" << TermColor::RESET(); ) return; } } #endif if( wait_until__connectedToDescServer_and_descSizeAvailable( 71 ) == false ) { cout << TermColor::RED() << "[ Cerebro::descrip_N__dot__descrip_0_N ERROR] wait_until__connectedToDescServer_and_descSizeAvailable returned false implying a timeout.\n" << TermColor::RESET(); return; } __Cerebro__run__( cout << TermColor::GREEN() <<"[Cerebro::descrip_N__dot__descrip_0_N] descriptor_size=" << this->descriptor_size << " connected_to_descriptor_server && descriptor_size_available" << TermColor::RESET() << endl; ) assert( this->descriptor_size> 0 ); int l=0, last_l=0; int last_processed=0; MatrixXd M = MatrixXd::Zero( this->descriptor_size, 29000 ); // TODO: Need dynamic allocation here. cout << "[Cerebro::descrip_N__dot__descrip_0_N] M.rows = " << M.rows() << " M.cols=" << M.cols() << endl; cout << "[Cerebro::descrip_N__dot__descrip_0_N] TODO: Need dynamic allocation here.\n"; #if __Cerebro__descrip_N__dot__descrip_0_N__implotting > 0 // Plotting image Plot2Mat handle(320,240, cv::Scalar(80,80,80) ); // Plot2Mat handle; handle.setYminmax( -0.1, 1.1); #endif while( b_run_thread ) { auto data_map = dataManager->getDataMapRef(); // this needs to be get every iteration else i dont get the ubdated values which are constantly being updated by other threads. l = wholeImageComputedList_size(); if( l - last_l < 3 ) { __Cerebro__run__debug( cout << "[Cerebro::descrip_N__dot__descrip_0_N]nothing new\n"; ); rate.sleep(); continue; } __Cerebro__run__( cout << TermColor::RED() << "---" << TermColor::RESET() << endl; ) __Cerebro__run__( cout << "l=" << l << endl; ) __Cerebro__run__debug( cout << "[Cerebro::descrip_N__dot__descrip_0_N] data_map.size() = " << data_map->size() << "\tper_image_descriptor_size="<< this->descriptor_size << endl; ) ///------------ Extract v, v-1, v-2. The latest 3 descriptors. VectorXd v, vm, vmm; ros::Time i_v, i_vm, i_vmm; { // std::lock_guard lk(m_wholeImageComputedList); assert( data_map.count( wholeImageComputedList_at(l-1) ) > 0 && data_map.count( wholeImageComputedList_at(l-2) ) > 0 && data_map.count( wholeImageComputedList_at(l-3) ) > 0 && "either of l, l-1, l-2 is not available in the datamap" ); v = data_map->at( wholeImageComputedList_at(l-1) )->getWholeImageDescriptor(); vm = data_map->at( wholeImageComputedList_at(l-2) )->getWholeImageDescriptor(); vmm = data_map->at( wholeImageComputedList_at(l-3) )->getWholeImageDescriptor(); i_v = wholeImageComputedList_at(l-1); i_vm = wholeImageComputedList_at(l-2); i_vmm = wholeImageComputedList_at(l-3); } // This is very inefficient. Better to have a matrix-vector product and not getWholeImageDescriptor() all the time. assert( M.rows() == v.size() ); assert( l < M.cols() ); ////-------------- Fill descriptors [last_l, l) into M. { // std::lock_guard lk(m_wholeImageComputedList); //no need for this as we switched to _at functions which are threadsafe for( int _s=last_l ; _sat( wholeImageComputedList_at(_s) )->getWholeImageDescriptor(); __Cerebro__run__debug( cout << "M.col(" << _s << ") = data_map[ " << wholeImageComputedList_at(_s) << " ]. \t"; cout << " isWholeImageDescriptorAvailable = " << data_map->at( wholeImageComputedList_at(_s) )->isWholeImageDescriptorAvailable() << endl; ) } } ////////////////////////////////////// ////----------- DOT PRODUCT---------- ///////////////////////////////////// int k = l - start_adding_descriptors_to_index_after; // given a stamp, l, get another stamp k. better make this to 200. //usable size of M is 8192xl, let k (k 5 ) { ElapsedTime timer; timer.tic(); // dot VectorXd u = v.transpose() * M.leftCols( k ); VectorXd um = vm.transpose() * M.leftCols( k ); VectorXd umm = vmm.transpose() * M.leftCols( k ); __Cerebro__run__( cout << " ";) __Cerebro__run__( cout << " ";) __Cerebro__run__( cout << " ";) __Cerebro__run__( cout << "Done in (ms): " << timer.toc_milli() << endl; ) // max coefficient and the index of maxcoeff. double u_max = u.maxCoeff(); double um_max = um.maxCoeff(); double umm_max = umm.maxCoeff(); int u_argmax=-1, um_argmax=-1, umm_argmax=-1; for( int ii=0 ; ii 0 && um_argmax > 0 && umm_argmax > 0 ); #if __Cerebro__descrip_N__dot__descrip_0_N__implotting > 0 // plot handle.resetCanvas(); handle.plot( u ); #endif // Criteria for a recognized place if( abs(u_argmax - um_argmax) < LOCALITY_THRESH && abs(u_argmax-umm_argmax) < LOCALITY_THRESH && u_max > DOT_PROD_THRESH ) { // std::lock_guard lk(m_wholeImageComputedList); //no need of lock here as we switched to _at functions for whlist __Cerebro__run__( cout << TermColor::RED() << "Loop FOUND!! a_idx=" << l-1 << "<-----> (" << u_argmax << "," << um_argmax << "," << umm_argmax << ")"<< TermColor::RESET() << endl; cout << TermColor::RED() << "loop FOUND!! " << "t_l=" << wholeImageComputedList_at(l-1) << " " << "t_argmax=" << wholeImageComputedList_at(u_argmax) << TermColor::RESET() << endl; ) #if __Cerebro__descrip_N__dot__descrip_0_N__implotting > 0 handle.mark( u_argmax ); #endif //TODO : // publish the image pair based on a config_file_flag // read flags for publish image, threshold(0.92), locality threshold (8) from file. { std::lock_guard lk_foundloops(m_foundLoops); foundLoops.push_back( std::make_tuple( wholeImageComputedList_at(l-1), wholeImageComputedList_at(u_argmax), u_max ) ); } } #if __Cerebro__descrip_N__dot__descrip_0_N__implotting > 0 cv::imshow("canvas", handle.getCanvasConstPtr() ); cv::waitKey(20); #endif } else { __Cerebro__run__( cout << "[Cerebro::descrip_N__dot__descrip_0_N] do nothing. not seen enough yet.\n"; ) } last_l = l; rate.sleep(); } cout << TermColor::RED() << "[Cerebro::descrip_N__dot__descrip_0_N] Finished thread" << TermColor::RESET() << endl; } //------------------------------------------------------------------// //---------------- END Populate Loop Candidates --------------------// //------------------------------------------------------------------// const int Cerebro::foundLoops_count() const { std::lock_guard lk(m_foundLoops); return foundLoops.size(); } const std::tuple Cerebro::foundLoops_i( int i) const { std::lock_guard lk(m_foundLoops); assert( i >= 0 && i lk(m_foundLoops); json jsonout_obj; auto data_map = dataManager->getDataMapRef(); int n_foundloops = foundLoops.size(); for( int i=0 ; i(u); ros::Time t_prev = std::get<1>(u); double score = std::get<2>(u); assert( data_map.count( t_curr ) > 0 && data_map.count( t_prev ) > 0 && "One or both of the timestamps in foundloops where not in the data_map. This cannot be happening...fatal...\n" ); int idx_1 = std::distance( data_map->begin(), data_map->find( t_curr ) ); int idx_2 = std::distance( data_map->begin(), data_map->find( t_prev ) ); // cout << "loop#" << i << " of " << n_foundloops << ": "; // cout << t_curr << "(" << score << ")" << t_prev << endl; // cout << idx_1 << "<--->" << idx_2 << endl; json _cur_json_obj; _cur_json_obj["time_sec_a"] = t_curr.sec; _cur_json_obj["time_nsec_a"] = t_curr.nsec; _cur_json_obj["time_sec_b"] = t_prev.sec; _cur_json_obj["time_nsec_b"] = t_prev.nsec; _cur_json_obj["time_double_a"] = t_curr.toSec(); _cur_json_obj["time_double_b"] = t_prev.toSec(); _cur_json_obj["global_a"] = idx_1; _cur_json_obj["global_b"] = idx_2; _cur_json_obj["score"] = score; jsonout_obj.push_back( _cur_json_obj ); } return jsonout_obj; } //--------------------------------------------------------------// //------------------ Geometry Thread ---------------------------// //--------------------------------------------------------------// // #define __Cerebro__loopcandi_consumer__(msg) msg; #define __Cerebro__loopcandi_consumer__(msg) ; // ^This will also imshow image-pairs with gms-matches marked. // #define __Cerebro__loopcandi_consumer__IMP( msg ) msg; #define __Cerebro__loopcandi_consumer__IMP( msg ) ; // ^Important Text only printing #define __Cerebro__loopcandi_consumer__IMSHOW 0 // will not produce the images (ofcourse will not show as well) // #define __Cerebro__loopcandi_consumer__IMSHOW 1 // produce the images and log them, will not imshow // #define __Cerebro__loopcandi_consumer__IMSHOW 2 // produce the images and imshow them, don't log // Just uncomment it to disable consistency check. // #define __Cerebro__loopcandi_consumer__no_pose_consistency_check void Cerebro::loopcandiate_consumer_thread() { assert( m_dataManager_available && "You need to set the DataManager in class Cerebro before execution of the run() thread can begin. You can set the dataManager by call to Cerebro::setDataManager()\n"); assert( b_loopcandidate_consumer && "you need to call loopcandidate_consumer_enable() before loopcandiate_consumer_thread() can start executing\n" ); cout << TermColor::GREEN() << "[Cerebro::loopcandiate_consumer_thread] Start thread" << TermColor::RESET() << endl; // init StereoGeometry bool stereogeom_status = init_stereogeom(); if( !stereogeom_status ) { assert( false && "[Cerebro::loopcandiate_consumer_thread()] cannot init_stereogeom\n"); return; } // stereogeom->set_K( 375.0, 375.0, 376.0, 240.0 ); // set this to something sensible, best is to use left camera's K // init pt-feature-matcher ros::Rate rate(1); int prev_count = 0; int new_count = 0; ElapsedTime timer; while( b_loopcandidate_consumer ) { new_count = foundLoops_count(); if( new_count == prev_count ) { rate.sleep(); continue; } __Cerebro__loopcandi_consumer__IMP( cout << TermColor::iGREEN() ; cout << "I see "<< new_count - prev_count << " new candidates. from i=["<< prev_count << "," << new_count-1 << "]\n"; // cout << "Cerebro::loopcandiate_consumer_thread(); #loops=" << new_count << TermColor::RESET() << endl; cout << TermColor::RESET() ; ) for( int j= prev_count ; j<=new_count-1 ; j++ ) { ProcessedLoopCandidate proc_candi; timer.tic() ; #ifdef __Cerebro__loopcandi_consumer__no_pose_consistency_check bool proc___status = process_loop_candidate_imagepair( j, proc_candi ); #else bool proc___status = process_loop_candidate_imagepair_consistent_pose_compute( j, proc_candi ); #endif __Cerebro__loopcandi_consumer__IMP( cout << "\t" << timer.toc_milli() << "(ms) !! process_loop_candidate_imagepair()\n"; ) // the data is in the `proc_candi` which is put into a vector which is a class variable. if( proc___status ) { std::lock_guard lk(m_processedLoops); // publish proc_candi cerebro::LoopEdge loopedge_msg; #ifdef __Cerebro__loopcandi_consumer__no_pose_consistency_check bool __makemsg_status = proc_candi.makeLoopEdgeMsg( loopedge_msg ); #else bool __makemsg_status = proc_candi.makeLoopEdgeMsgWithConsistencyCheck( loopedge_msg ); #endif __Cerebro__loopcandi_consumer__IMP( cout << TermColor::iBLUE() << "__makemsg_status: " << __makemsg_status << " publish loopedge_msg" << TermColor::RESET() << endl; ) // publish only if msg was successfully made ==> all the candidate relative poses are consistent. if( __makemsg_status ) { __Cerebro__loopcandi_consumer__IMP( cout << TermColor::GREEN() << "publish loopedge_msg" << TermColor::RESET() << endl; ) pub_loopedge.publish( loopedge_msg ); } else { __Cerebro__loopcandi_consumer__IMP( cout << TermColor::RED() << "not publishing because __makemsg_status was false" << TermColor::RESET() << endl; ) } // irrespective of where the poses are consistent or not add it to the list. processedloopcandi_list.push_back( proc_candi ); __Cerebro__loopcandi_consumer__IMP(cout << "\tAdded to `processedloopcandi_list`" << endl;) } else { __Cerebro__loopcandi_consumer__IMP( cout << "\tNot added to `processedloopcandi_list`, status was false\n" << endl; ) } } prev_count = new_count; rate.sleep(); } cout << TermColor::RED() << "[Cerebro::loopcandiate_consumer_thread] disable called, quitting loopcandiate_consumer_thread" << TermColor::RESET() << endl; } const int Cerebro::processedLoops_count() const { std::lock_guard lk(m_processedLoops); return processedloopcandi_list.size(); } const ProcessedLoopCandidate& Cerebro::processedLoops_i( int i ) const { std::lock_guard lk(m_processedLoops); assert( i>=0 && i< processedloopcandi_list.size() && "[processedLoops_i] input i is not in the correct range" ); if( i>=0 && i< processedloopcandi_list.size() ) { return processedloopcandi_list[ i ]; } else { cout << TermColor::RED() << "[Cerebro::processedLoops_i] error you requested for processedloopcandidate idx=" << i << " but the length of the vector was " << processedloopcandi_list.size() << endl; exit(1); } } bool Cerebro::init_stereogeom() { auto left_camera = dataManager->getAbstractCameraRef(0); if( !dataManager->isAbstractCameraSet(1) ) { ROS_ERROR( "Stereo-camera right doesn't appear to be set. to use this thread, you need to have StereoGeometry. Edit the config file to set the 2nd camera and its baseline\n"); return false; } auto right_camera = dataManager->getAbstractCameraRef(1); __Cerebro__loopcandi_consumer__( cout << TermColor::iGREEN() ; cout << "[Cerebro::loopcandiate_consumer_thread] left_camera\n" << left_camera->parametersToString() << endl; cout << "[Cerebro::loopcandiate_consumer_thread] right_camera\n" << right_camera->parametersToString() << endl; cout << TermColor::RESET(); ) Matrix4d right_T_left; if( !dataManager->isCameraRelPoseSet( std::make_pair(1,0)) ) { ROS_ERROR( "stereo camera appears to be set but the baseline (stereo-extrinsic) is not specified in the config_file. In config file you need to set `extrinsic_1_T_0`"); return false; } right_T_left = dataManager->getCameraRelPose( std::make_pair(1,0) ); __Cerebro__loopcandi_consumer__( cout << TermColor::iGREEN() ; cout << "[Cerebro::loopcandiate_consumer_thread] right_T_left: " << PoseManipUtils::prettyprintMatrix4d( right_T_left ) << endl; cout << TermColor::RESET(); ) stereogeom = std::make_shared( left_camera,right_camera, right_T_left ); return true; } bool Cerebro::retrive_stereo_pair( DataNode* node, cv::Mat& left_image, cv::Mat& right_image, bool bgr2gray ) { #if 0 // raw stereo-images in gray if( !(node->isImageAvailable()) || !(node->isImageAvailable(1)) ) { cout << TermColor::RED() << "[Cerebro::retrive_stereo_pair] Either of the node images (stereo-pair was not available). This is probably not fatal, this loopcandidate will be skipped." << TermColor::RESET() << endl; return false; } const cv::Mat& bgr_left_image = node->getImage(); const cv::Mat& bgr_right_image = node->getImage(1); #else auto img_data_mgr = dataManager->getImageManagerRef(); if( img_data_mgr->isImageRetrivable( "left_image", node->getT() )==false ) { cout << TermColor::RED() << "[Cerebro::retrive_stereo_pair] img_data_mgr->isImageRetrivable( \"left_image\"," << node->getT() << ") returned false" << TermColor::RESET() << endl; cout << TermColor::RED() << "[Cerebro::retrive_stereo_pair] Either of the node images (stereo-pair was not available). This is probably not fatal, this loopcandidate will be skipped." << TermColor::RESET() << endl; return false; } if ( img_data_mgr->isImageRetrivable( "right_image", node->getT() ) == false ) { cout << TermColor::RED() << "[Cerebro::retrive_stereo_pair] img_data_mgr->isImageRetrivable( \"right_image\"," << node->getT() << ") returned false" << TermColor::RESET() << endl; cout << TermColor::RED() << "[Cerebro::retrive_stereo_pair] Either of the node images (stereo-pair was not available). This is probably not fatal, this loopcandidate will be skipped." << TermColor::RESET() << endl; // img_data_mgr->print_status(); // cout << TermColor::RED() << "[Cerebro::retrive_stereo_pair] img_data_mgr->isImageRetrivable( \"right_image\"," << node->getT() << ") returned false" << TermColor::RESET() << endl; return false; } cv::Mat bgr_left_image, bgr_right_image; img_data_mgr->getImage( "left_image", node->getT(), bgr_left_image ); img_data_mgr->getImage( "right_image", node->getT(), bgr_right_image ); #if 0 cout << "bgr_left_image: " << MiscUtils::cvmat_info( bgr_left_image ) << "\n"; cout << "bgr_right_image: " << MiscUtils::cvmat_info( bgr_right_image ) << "\n"; #endif if( !(bgr_left_image.data) || !(bgr_right_image.data) ) { cout << "[Cerebro::retrive_stereo_pair] Invalid images bfhjreturn false.\n"; return false; } #endif if( bgr2gray ) { if( bgr_left_image.channels() > 1 ) cv::cvtColor( bgr_left_image, left_image, CV_BGR2GRAY ); else left_image = bgr_left_image; if( bgr_right_image.channels() > 1 ) cv::cvtColor( bgr_right_image, right_image, CV_BGR2GRAY ); else right_image = bgr_right_image; } else { left_image = bgr_left_image; right_image = bgr_right_image; } #if 0 cout << "[Cerebro::retrive_stereo_pair]left_image: " << MiscUtils::cvmat_info( left_image ) << "\n"; cout << "[Cerebro::retrive_stereo_pair]right_image: " << MiscUtils::cvmat_info( right_image ) << "\n"; cout << endl; #endif return true; } bool Cerebro::process_loop_candidate_imagepair_consistent_pose_compute( int ii, ProcessedLoopCandidate& proc_candi ) { auto u = foundLoops_i( ii ); ros::Time t_curr = std::get<0>(u); ros::Time t_prev = std::get<1>(u); double score = std::get<2>(u); auto data_map = dataManager->getDataMapRef(); assert( data_map->count( t_curr ) > 0 && data_map->count( t_prev ) > 0 && "One or both of the timestamps in foundloops where not in the data_map. This cannot be happening...fatal...\n" ); int idx_1 = std::distance( data_map->begin(), data_map->find( t_curr ) ); int idx_2 = std::distance( data_map->begin(), data_map->find( t_prev ) ); DataNode * node_1 = data_map->find( t_curr )->second; DataNode * node_2 = data_map->find( t_prev )->second; __Cerebro__loopcandi_consumer__IMP( cout << TermColor::BLUE() << "{"<" << idx_2 << TermColor::RESET() << "\tt_curr=" << t_curr << " <--> t_prev=" << t_prev << endl; ) // if( node_1->getNumberOfSuccessfullyTrackedFeatures() < 20 || node_2->getNumberOfSuccessfullyTrackedFeatures() < 20 ) { // __Cerebro__loopcandi_consumer__IMP( "[Cerebro::process_loop_candidate_imagepair_consistent_pose_compute] skip processing this pair because it is a kidnaped node\n" ); // return false; // } cv::Mat a_imleft_raw, a_imright_raw; bool ret_status_a = retrive_stereo_pair( node_1, a_imleft_raw, a_imright_raw ); if( !ret_status_a ) return false; cv::Mat b_imleft_raw, b_imright_raw; bool ret_status_b = retrive_stereo_pair( node_2, b_imleft_raw, b_imright_raw ); if( !ret_status_b ) return false; //------------------------------------------------ //------ 3d points from frame_a //------------------------------------------------ __Cerebro__loopcandi_consumer__( cout << "[Cerebro::process_loop_candidate_imagepair_consistent_pose_compute] 3d points from frame_a\n"; ) cv::Mat a_imleft_srectified, a_imright_srectified; cv::Mat a_3dImage; MatrixXd a_3dpts; cv::Mat a_disp_viz; stereogeom->get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( a_imleft_raw, a_imright_raw, a_imleft_srectified,a_imright_srectified, a_3dpts, a_3dImage, a_disp_viz ); //----------------------------------------------------- //--------------- 3d points from frame_b //----------------------------------------------------- __Cerebro__loopcandi_consumer__( cout << "[Cerebro::process_loop_candidate_imagepair_consistent_pose_compute] 3d points from frame_b\n"; ) cv::Mat b_imleft_srectified, b_imright_srectified; cv::Mat b_3dImage; MatrixXd b_3dpts; cv::Mat b_disp_viz; stereogeom->get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( b_imleft_raw, b_imright_raw, b_imleft_srectified, b_imright_srectified, b_3dpts, b_3dImage, b_disp_viz ); //--------------------------------------------------------------------- //------------ point matches between a_left, b_left //--------------------------------------------------------------------- __Cerebro__loopcandi_consumer__( cout << "[Cerebro::process_loop_candidate_imagepair_consistent_pose_compute]point matches between a_left, b_left\n"; ) MatrixXd uv, uv_d; // u is from frame_a; ud is from frame_b ElapsedTime timer; timer.tic(); StaticPointFeatureMatching::gms_point_feature_matches(a_imleft_srectified, b_imleft_srectified, uv, uv_d ); string msg_pf_matches = to_string( timer.toc_milli() )+" (ms) elapsed time for point_feature_matches computation and resulted in " + std::to_string(uv.cols()) + " number of point correspondences" ; __Cerebro__loopcandi_consumer__( cout << msg_pf_matches << endl; ) if( uv.cols() < 150 ) { __Cerebro__loopcandi_consumer__IMP( cout << TermColor::RED() << "[Cerebro::process_loop_candidate_imagepair_consistent_pose_compute]too few gms matches between node#" << idx_1 << " and node#" << idx_2; cout << " contains pf_matches=" << uv.cols() << ", thresh=150, so I am rejecting this loopcandidate." << TermColor::RESET() << endl; ) return false; } //---------------------------------------------------------------------- //-------------- PNP and P3P //---------------------------------------------------------------------- Matrix4d odom_b_T_a = node_2->getPose().inverse() * node_1->getPose(); proc_candi = ProcessedLoopCandidate( ii, node_1, node_2 ); proc_candi.idx_from_datamanager_1 = idx_1; proc_candi.idx_from_datamanager_2 = idx_2; proc_candi.pf_matches = uv.cols(); //----- Option-A: __Cerebro__loopcandi_consumer__IMP( cout << TermColor::BLUE() << "Option-A" << TermColor::RESET() << endl; ) std::vector feature_position_uv; std::vector feature_position_uv_d; std::vector world_point_uv; StaticPointFeatureMatching::make_3d_2d_collection__using__pfmatches_and_disparity( stereogeom, uv, a_3dImage, uv_d, feature_position_uv, feature_position_uv_d, world_point_uv); Matrix4d op1__b_T_a; string pnp__msg; #if 1 //-- float pnp_goodness = StaticTheiaPoseCompute::PNP( world_point_uv, feature_position_uv_d, op1__b_T_a, pnp__msg ); //--END #else //-- op1__b_T_a = odom_b_T_a; // setting initial guess as odometry rel pose with translation as zero // op1__b_T_a(0,3) = 0.0; op1__b_T_a(1,3) = 0.0; op1__b_T_a(2,3) = 0.0; float pnp_goodness = StaticCeresPoseCompute::PNP( world_point_uv, feature_position_uv_d, op1__b_T_a, pnp__msg ); //--END #endif __Cerebro__loopcandi_consumer__IMP( cout << TermColor::YELLOW() << "pnp_goodness=" << pnp_goodness << " op1__b_T_a = " << PoseManipUtils::prettyprintMatrix4d( op1__b_T_a ) << TermColor::RESET() << endl; ) __Cerebro__loopcandi_consumer__( cout << pnp__msg << endl; ) // reprojection debug image for op-1 // plot( PI( op1__b_T_a * world_point_uv ) ) on imB #if (__Cerebro__loopcandi_consumer__IMSHOW == 1) || (__Cerebro__loopcandi_consumer__IMSHOW == 2) { MatrixXd PI_world_point_uv, PI_world_point_uv_odom; GeometryUtils::idealProjection( stereogeom->get_K(), op1__b_T_a, world_point_uv, PI_world_point_uv ); GeometryUtils::idealProjection( stereogeom->get_K(), odom_b_T_a, world_point_uv, PI_world_point_uv_odom ); cv::Mat pi_dst_img; MiscUtils::plot_point_sets( b_imleft_srectified, PI_world_point_uv, pi_dst_img, cv::Scalar(0,255,0), false ); MiscUtils::plot_point_sets( pi_dst_img, PI_world_point_uv_odom, cv::Scalar(255,0,0), false ); MiscUtils::plot_point_sets( pi_dst_img, uv, cv::Scalar(0,0,255), false ); MiscUtils::plot_point_sets( pi_dst_img, uv_d, cv::Scalar(255,0,255), false ); cv::resize(pi_dst_img,pi_dst_img, cv::Size(), 0.6, 0.6 ); MiscUtils::append_status_image( pi_dst_img, string( "^this is image b=")+to_string(idx_2)+";plot( PI( op1__b_T_a * world_point_uv ) ) in green on imB;plot( PI( odom__b_T_a * world_point_uv ) ) in blue on imB;plot( uv ) in red on imB;plot( uv_d) in pink on imB;>> green and pink show alignment quality; >> ignore blue and red"); #if __Cerebro__loopcandi_consumer__IMSHOW == 1 proc_candi.debug_images.push_back( pi_dst_img ); proc_candi.debug_images_titles.push_back( "reprojection_debug_image_1" ); #endif #if __Cerebro__loopcandi_consumer__IMSHOW == 2 cv::imshow( "plot( PI( op1__b_T_a * world_point_uv ) ) on imB", pi_dst_img ); #endif } #endif //----- Option-B: __Cerebro__loopcandi_consumer__IMP( cout << TermColor::BLUE() << "Option-B" << TermColor::RESET() << endl; ) std::vector world_point_uv_d; StaticPointFeatureMatching::make_3d_2d_collection__using__pfmatches_and_disparity( stereogeom, uv_d, b_3dImage, uv, feature_position_uv_d, feature_position_uv, world_point_uv_d); Matrix4d op2__a_T_b, op2__b_T_a; string pnp__msg_option_B; #if 1 //-- float pnp_goodness_optioN_B = StaticTheiaPoseCompute::PNP( world_point_uv_d, feature_position_uv, op2__a_T_b, pnp__msg_option_B ); //--END #else //-- op2__a_T_b = odom_b_T_a.inverse(); //initial guess same as odometry // op2__a_T_b(0,3)=0.0;op2__a_T_b(1,3)=0.0;op2__a_T_b(2,3)=0.0; float pnp_goodness_optioN_B = StaticCeresPoseCompute::PNP( world_point_uv_d, feature_position_uv, op2__a_T_b, pnp__msg_option_B ); //--END #endif op2__b_T_a = op2__a_T_b.inverse(); __Cerebro__loopcandi_consumer__IMP( cout << TermColor::YELLOW() << pnp_goodness_optioN_B << " op2__a_T_b = " << PoseManipUtils::prettyprintMatrix4d( op2__a_T_b ) << TermColor::RESET() << endl; cout << TermColor::YELLOW() << pnp_goodness_optioN_B << " op2__b_T_a = " << PoseManipUtils::prettyprintMatrix4d( op2__b_T_a ) << TermColor::RESET() << endl; ) __Cerebro__loopcandi_consumer__( cout << pnp__msg_option_B << endl; ) // reprojection debug image for op-2 // plot( PI( op2__a_T_b * world_point_uv_d ) ) on imA #if (__Cerebro__loopcandi_consumer__IMSHOW == 1) || (__Cerebro__loopcandi_consumer__IMSHOW == 2) { MatrixXd PI_world_point_uvd, PI_world_point_uvd_odom; GeometryUtils::idealProjection( stereogeom->get_K(), op2__a_T_b, world_point_uv_d, PI_world_point_uvd ); GeometryUtils::idealProjection( stereogeom->get_K(), odom_b_T_a.inverse(), world_point_uv_d, PI_world_point_uvd_odom ); cv::Mat pi_dst_img; MiscUtils::plot_point_sets( a_imleft_srectified, PI_world_point_uvd, pi_dst_img, cv::Scalar(0,255,0), false ); MiscUtils::plot_point_sets( pi_dst_img, PI_world_point_uvd_odom, cv::Scalar(255,0,0), false ); MiscUtils::plot_point_sets( pi_dst_img, uv, cv::Scalar(0,0,255), false ); MiscUtils::plot_point_sets( pi_dst_img, uv_d, cv::Scalar(255,0,255), false ); cv::resize(pi_dst_img,pi_dst_img, cv::Size(), 0.6, 0.6 ); MiscUtils::append_status_image( pi_dst_img, string( "^this is image a=")+to_string(idx_1)+";plot( PI( op2__a_T_b * world_point_uv_d ) ) in green on imA;plot( PI( odom__b_T_a * world_point_uv ) ) in blue on imA ;plot( uv ) in red on imA;plot( uv_d) in pink on imA;>> green and red show alignment quality;>> ignore blue and pink"); #if __Cerebro__loopcandi_consumer__IMSHOW == 1 proc_candi.debug_images.push_back( pi_dst_img ); proc_candi.debug_images_titles.push_back( "reprojection_debug_image_2" ); #endif #if __Cerebro__loopcandi_consumer__IMSHOW == 2 cv::imshow( "plot( PI( op2__a_T_b * world_point_uv_d ) ) on imA", pi_dst_img ); #endif } #endif //----- Option-C __Cerebro__loopcandi_consumer__IMP( cout << TermColor::BLUE() << "Option-C" << TermColor::RESET() << endl; ) vector< Vector3d> uv_X; vector< Vector3d> uvd_Y; StaticPointFeatureMatching::make_3d_3d_collection__using__pfmatches_and_disparity( uv, a_3dImage, uv_d, b_3dImage, uv_X, uvd_Y ); Matrix4d icp_b_T_a; string p3p__msg; #if 1 //-- float p3p_goodness = StaticTheiaPoseCompute::P3P_ICP( uv_X, uvd_Y, icp_b_T_a, p3p__msg ); //--END #else //-- icp_b_T_a = odom_b_T_a; // icp_b_T_a(0,3) = 0.0; icp_b_T_a(1,3) = 0.0; icp_b_T_a(2,3) = 0.0; float p3p_goodness = StaticCeresPoseCompute::P3P_ICP( uv_X, uvd_Y, icp_b_T_a, p3p__msg ); //--END #endif __Cerebro__loopcandi_consumer__IMP( cout << TermColor::YELLOW() << p3p_goodness << " icp_b_T_a = " << PoseManipUtils::prettyprintMatrix4d( icp_b_T_a ) << TermColor::RESET() << endl; ) __Cerebro__loopcandi_consumer__( cout << p3p__msg << endl; ) // reprojection debug image for op-3 // plot( PI( icp_b_T_a * world_point_uv ) ) on imB #if (__Cerebro__loopcandi_consumer__IMSHOW == 1) || (__Cerebro__loopcandi_consumer__IMSHOW == 2) { MatrixXd PI_world_point_uv, PI_world_point_uv_odom; GeometryUtils::idealProjection( stereogeom->get_K(), icp_b_T_a, world_point_uv, PI_world_point_uv ); GeometryUtils::idealProjection( stereogeom->get_K(), odom_b_T_a, world_point_uv, PI_world_point_uv_odom ); cv::Mat pi_dst_img; MiscUtils::plot_point_sets( b_imleft_srectified, PI_world_point_uv, pi_dst_img, cv::Scalar(0,255,0), false ); MiscUtils::plot_point_sets( pi_dst_img, PI_world_point_uv_odom, cv::Scalar(255,0,0), false ); MiscUtils::plot_point_sets( pi_dst_img, uv, cv::Scalar(0,0,255), false ); MiscUtils::plot_point_sets( pi_dst_img, uv_d, cv::Scalar(255,0,255), false ); cv::resize(pi_dst_img,pi_dst_img, cv::Size(), 0.6, 0.6 ); MiscUtils::append_status_image( pi_dst_img, string( "^this is image b=")+to_string(idx_2)+";plot( PI( icp_b_T_a * world_point_uv ) ) in green on imB ;plot( PI( odom__b_T_a * world_point_uv ) ) in blue on imB;plot( uv ) in red on imB;plot( uv_d) in pink on imB"); #if __Cerebro__loopcandi_consumer__IMSHOW == 1 proc_candi.debug_images.push_back( pi_dst_img ); proc_candi.debug_images_titles.push_back( "reprojection_debug_image_3" ); #endif #if __Cerebro__loopcandi_consumer__IMSHOW == 2 cv::imshow( "plot( PI( icp_b_T_a * world_point_uv ) ) on imB", pi_dst_img ); #endif } #endif // if( isnan( op1__b_T_a ) ) { if( op1__b_T_a != op1__b_T_a || op2__b_T_a != op2__b_T_a || icp_b_T_a != icp_b_T_a ) { __Cerebro__loopcandi_consumer__IMP( cout << "=======++++ one of the 3 ways had inf or nan in them. This is bad, so skip this loop candidate.\n"; ); return false; } __Cerebro__loopcandi_consumer__IMP( cout << TermColor::YELLOW() << "odom_b_T_a = " << PoseManipUtils::prettyprintMatrix4d( odom_b_T_a ) << TermColor::RESET() << endl; cout << "|op1 - op2|" << PoseManipUtils::prettyprintMatrix4d( op1__b_T_a.inverse() * op2__b_T_a ) << endl; cout << "|op1 - icp|" << PoseManipUtils::prettyprintMatrix4d( op1__b_T_a.inverse() * icp_b_T_a ) << endl; cout << "|op2 - icp|" << PoseManipUtils::prettyprintMatrix4d( op2__b_T_a.inverse() * icp_b_T_a ) << endl; ) //----------------------------------------------------------------- // Fill the output //----------------------------------------------------------------- // final pose // proc_candi._3d2d__2T1 = op1__b_T_a; // proc_candi.isSet_3d2d__2T1 = true; // proc_candi._3d2d__2T1__ransac_confidence = pnp_goodness; // Fill up all the poses which were computed // option-A proc_candi.opX_b_T_a.push_back( op1__b_T_a ); proc_candi.opX_goodness.push_back( pnp_goodness ); proc_candi.opX_b_T_a_name.push_back( "op1__b_T_a" ); proc_candi.opX_b_T_a_debugmsg.push_back( pnp__msg ); // Option-B proc_candi.opX_b_T_a.push_back( op2__b_T_a ); proc_candi.opX_goodness.push_back( pnp_goodness_optioN_B ); proc_candi.opX_b_T_a_name.push_back( "op2__b_T_a" ); proc_candi.opX_b_T_a_debugmsg.push_back( pnp__msg_option_B ); // Option-C proc_candi.opX_b_T_a.push_back( icp_b_T_a ); proc_candi.opX_goodness.push_back( p3p_goodness ); proc_candi.opX_b_T_a_name.push_back( "icp_b_T_a" ); proc_candi.opX_b_T_a_debugmsg.push_back( p3p__msg ); //----------------------------------------------------------------- // Build Viz Images //----------------------------------------------------------------- #if (__Cerebro__loopcandi_consumer__IMSHOW == 1) || (__Cerebro__loopcandi_consumer__IMSHOW == 2) ///------A : pf matching cv::Mat dst_feat_matches; MiscUtils::plot_point_pair( a_imleft_srectified, uv, idx_1, b_imleft_srectified, uv_d, idx_2, dst_feat_matches, 3, msg_pf_matches+";#pf-matches: "+to_string( uv.cols() ) ); cv::resize(dst_feat_matches, dst_feat_matches, cv::Size(), 0.5, 0.5 ); ///----------B : Disparities cv::Mat dst_disp; MiscUtils::side_by_side( a_disp_viz, b_disp_viz, dst_disp ); MiscUtils::append_status_image( dst_disp, "a="+ to_string(idx_1)+" b="+to_string(idx_2), .8 ); cv::resize(dst_disp, dst_disp, cv::Size(), 0.5, 0.5 ); // cv::imshow( "dst_disp", dst_disp ); MiscUtils::append_status_image( dst_disp, "[b_T_a <-- PNP( 3d(a), uv(b))];"+pnp__msg+";[a_T_b <-- PNP( 3d(b), uv(a))];"+pnp__msg_option_B+";[icp_b_T_a <-- ICP(3d(a), 3d(b))];"+p3p__msg ); MiscUtils::append_status_image( dst_disp, "odom_b_T_a:"+PoseManipUtils::prettyprintMatrix4d( odom_b_T_a ) ); ///---------C: Reprojections //TODO #if __Cerebro__loopcandi_consumer__IMSHOW == 1 proc_candi.debug_images.push_back( dst_feat_matches ); proc_candi.debug_images_titles.push_back( "apf_matches" ); proc_candi.debug_images.push_back( dst_disp ); proc_candi.debug_images_titles.push_back( "dsparity_and_poses" ); #endif #if __Cerebro__loopcandi_consumer__IMSHOW == 2 cv::imshow( "dst_feat_matches", dst_feat_matches ); cv::imshow( "dst_disp", dst_disp ); cv::waitKey(30); #endif #endif return true; } #if 0 bool Cerebro::process_loop_candidate_imagepair( int ii, ProcessedLoopCandidate& proc_candi ) { auto u = foundLoops_i( ii ); ros::Time t_curr = std::get<0>(u); ros::Time t_prev = std::get<1>(u); double score = std::get<2>(u); auto data_map = dataManager->getDataMapRef(); assert( data_map->count( t_curr ) > 0 && data_map->count( t_prev ) > 0 && "One or both of the timestamps in foundloops where not in the data_map. This cannot be happening...fatal...\n" ); int idx_1 = std::distance( data_map->begin(), data_map->find( t_curr ) ); int idx_2 = std::distance( data_map->begin(), data_map->find( t_prev ) ); DataNode * node_1 = data_map->find( t_curr )->second; DataNode * node_2 = data_map->find( t_prev )->second; __Cerebro__loopcandi_consumer__IMP( cout << TermColor::BLUE() << "{"<" << idx_2 << TermColor::RESET() << endl; ) // cv::imshow( "im_1", im_1); // cv::imshow( "im_2", im_2); //---------------------------------------- //---------Disparity of `idx_1` //---------------------------------------- cv::Mat grey_im_1_left, grey_im_1_right; bool ret_status = retrive_stereo_pair( node_1, grey_im_1_left, grey_im_1_right ); if( !ret_status ) return false; // will get 3d points, stereo-rectified image, and disparity false colormap MatrixXd _3dpts__im1; //4xN. 3d points of im1 cv::Mat _3dImage__im1; cv::Mat imleft_srectified, imright_srectified; cv::Mat disparity_for_visualization; ElapsedTime timer; timer.tic(); stereogeom->get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( grey_im_1_left, grey_im_1_right, imleft_srectified,imright_srectified, _3dpts__im1, _3dImage__im1, disparity_for_visualization ); __Cerebro__loopcandi_consumer__( cout << timer.toc_milli() << " (ms)!! get_srectifiedim_and_3dpoints_and_disparity_from_raw_images\n"; ) //---------------- END Disparity of `idx_1` //-------------------------------------------------------------- //------------ srectified of idx_2 image pair needed //-------------------------------------------------------------- cv::Mat grey_im_2_left, grey_im_2_right; bool ret_status_2 = retrive_stereo_pair( node_2, grey_im_2_left, grey_im_2_right ); if( !ret_status_2 ) return false; cv::Mat im2_left_srectified, im2_right_srectified; timer.tic(); // TODO: If feasible can also compute depth of im2, so that the pose computed can be verified for consistency. // TODO: accept the pose if pnp( 3d of idx_1, 2d of idx_2 ) === pnp( 2d of idx_1, 3d of idx_2 ) stereogeom->do_stereo_rectification_of_raw_images( grey_im_2_left, grey_im_2_right, im2_left_srectified, im2_right_srectified ); __Cerebro__loopcandi_consumer__( cout << timer.toc_milli() << " (ms)!! do_stereo_rectification_of_raw_images\n"; ) // cv::imshow( "im2_left_srectified", im2_left_srectified ); //------------- END srectified of idx_2 image pair needed //----------------------------------------------------------------------- //----------point_feature_matches for `idx_1` <--> `idx_2` // gms matcher //----------------------------------------------------------------------- MatrixXd uv, uv_d; // u is from frame_a; ud is from frame_b timer.tic(); StaticPointFeatureMatching::gms_point_feature_matches( imleft_srectified, im2_left_srectified, uv, uv_d ); auto elapsed_time_gms = timer.toc_milli(); __Cerebro__loopcandi_consumer__( cout << elapsed_time_gms << " (ms)!! gms_point_feature_matches\n"; ) //--------------------- END Matcher ---------------------------------- //---------------- make collection of 3d 2d points // 3d of `idx_1` <---> 2d of `idx_2` std::vector feature_position_uv; std::vector feature_position_uv_d; std::vector world_point; StaticPointFeatureMatching::make_3d_2d_collection__using__pfmatches_and_disparity( stereogeom, uv, _3dImage__im1, uv_d, feature_position_uv, feature_position_uv_d, world_point ); // make_3d_2d_collection__using__pfmatches_and_disparity( uv, _3dImage__im1, uv_d, // feature_position_uv, feature_position_uv_d, world_point ); int n_valid_depths = world_point.size(); // TODO 2d of idx_1 <---> 3d of idx_2 // See if the matching can be rejected due to insufficient # of matches if( uv.cols() < 150 ) { __Cerebro__loopcandi_consumer__IMP( cout << TermColor::YELLOW() << "of the total "+std::to_string(uv.cols())+" point feature correspondences " +std::to_string(n_valid_depths)+ " had valid depths. " << " too few pf-matches from gms. Skip this. TH=80" << TermColor::RESET() << endl; ) return false; } if( n_valid_depths < 150 ) { __Cerebro__loopcandi_consumer__IMP( cout << TermColor::YELLOW() << "of the total "+std::to_string(uv.cols())+" point feature correspondences " +std::to_string(n_valid_depths)+ " had valid depths. " << "too few pf-matches depths from gms. Skip this. TH=80" << TermColor::RESET() << endl; ) return false; } // TODO assess u, ud, world_point for histogram spreads. a more spread in measurements will give more precise more. Also will be helpful to weedout bad poses //------------------------------------------ //-------------- theia::pnp //------------------------------------------ Matrix4d b_T_a; //< RESULT string pnp__msg = string(""); //< msg about pnp #if 1 //--- DlsPnp std::vector solution_rotations; std::vector solution_translations; timer.tic(); theia::DlsPnp( feature_position_uv_d, world_point, &solution_rotations, &solution_translations ); auto elapsed_dls_pnp = timer.toc_milli() ; __Cerebro__loopcandi_consumer__( cout << elapsed_dls_pnp << " : (ms) : theia::DlsPnp done in\n"; cout << "solutions count = " << solution_rotations.size() << " " << solution_translations.size() << endl; ) if( solution_rotations.size() == 0 ) { __Cerebro__loopcandi_consumer__IMP( cout << TermColor::RED() << " theia::DlsPnp returns no solution" << TermColor::RESET() << endl; ) return false; } if( solution_rotations.size() > 1 ) { __Cerebro__loopcandi_consumer__IMP( cout << TermColor::RED() << " theia::DlsPnp returns multiple solution" << TermColor::RESET() << endl; ) return false; } // retrive solution b_T_a = Matrix4d::Identity(); b_T_a.topLeftCorner(3,3) = solution_rotations[0].toRotationMatrix(); b_T_a.col(3).topRows(3) = solution_translations[0]; __Cerebro__loopcandi_consumer__IMP( // cout << "solution_T " << b_T_a << endl; cout << TermColor::GREEN() << "DlsPnp (b_T_a): " << PoseManipUtils::prettyprintMatrix4d( b_T_a ) << TermColor::RESET() << endl; // out_b_T_a = b_T_a; pnp__msg += "DlsPnp (b_T_a): " + PoseManipUtils::prettyprintMatrix4d( b_T_a ) + ";"; pnp__msg += " elapsed_dls_pnp="+to_string(elapsed_dls_pnp)+";"; ) #endif #if 1 //--- DlsPnpWithRansac __Cerebro__loopcandi_consumer__( timer.tic() ); // prep data vector data_r; for( int i=0 ; i ransac_estimator(params, dlspnp_estimator); // Initialize must always be called! ransac_estimator.Initialize(); theia::RansacSummary summary; ransac_estimator.Estimate(data_r, &best_rel_pose, &summary); auto elapsed_dls_pnp_ransac=timer.toc_milli(); __Cerebro__loopcandi_consumer__IMP( cout << elapsed_dls_pnp_ransac << "(ms)!! DlsPnpWithRansac ElapsedTime includes data prep\n"; cout << "Ransac:"; // for( int i=0; iget_K() * _X_i; //< scaling with camera-intrinsic matrix Vector3d _tmp; _tmp << feature_position_uv_d[i], 1.0 ; _detectedpts_of_B.col(i) = stereogeom->get_K() * _tmp; _tmp << feature_position_uv[i], 1.0 ; _detectedpts_of_A.col(i) = stereogeom->get_K() * _tmp; Vector3d delta = _3dpts_of_A_projectedonB.col(i) - _detectedpts_of_B.col(i); if( abs(delta(0)) < 2. && abs(delta(1)) < 2. ) { // cout << " delta=" << delta.transpose(); n_good++; } else { // cout << TermColor::RED() << "delta=" << delta.transpose() << TermColor::RESET(); } // cout << endl; } // cout << "n_good=" <isPoseAvailable() && node_2->isPoseAvailable() ) { Matrix4d odom_wTA = node_1->getPose(); // cout << "wTa(odom)" << PoseManipUtils::prettyprintMatrix4d( wTa ) << endl; Matrix4d odom_wTB = node_2->getPose(); // cout << "wTb(odom)" << PoseManipUtils::prettyprintMatrix4d( wTb ) << endl; Matrix4d odom_b_T_a = odom_wTB.inverse() * odom_wTA; __Cerebro__loopcandi_consumer__( cout << "odom_b_T_a" << PoseManipUtils::prettyprintMatrix4d( odom_b_T_a ) << endl; ) pnp__msg += "odom_b_T_a "+PoseManipUtils::prettyprintMatrix4d( odom_b_T_a ) +";"; auto a_timestamp = node_1->getT(); auto b_timestamp = node_2->getT(); pnp__msg += "a.timestamp: " + to_string(a_timestamp.toSec()); pnp__msg += " b.timestamp: " + to_string(b_timestamp.toSec()) + ";"; // PI( odom_b_T_a * a_X ) for( int i=0 ; iget_K() * _X_i; //< scaling with camera-intrinsic matrix } } else { cout << TermColor::RED() << "[Cerebro::process_loop_candidate_imagepair] In plotting part needs poses from DataManager which is not available." << TermColor::RESET() << endl; } #endif // plot( B, ud ) cv::Mat __dst; MiscUtils::plot_point_sets( im2_left_srectified, _detectedpts_of_B, __dst, cv::Scalar( 0,0,255), false, "_detectedpts_of_B (red) npts="+to_string(_detectedpts_of_B.cols()) ); // cv::imshow( "plot( B, ud )", __dst ); // plot( B, _3dpts_of_A_projectedonB ) MiscUtils::plot_point_sets( __dst, _3dpts_of_A_projectedonB, cv::Scalar( 0,255,255), false, ";_3dpts_of_A_projectedonB, PI( b_T_a * X),(yellow)" ); // MiscUtils::plot_point_sets( b_imleft_srectified, _3dpts_of_A_projectedonB, __dst, cv::Scalar( 0,255,255), false, "_3dpts_of_A_projectedonB" ); MiscUtils::plot_point_sets( __dst, _detectedpts_of_A, cv::Scalar( 255,255,255), false, ";;_detectedpts_of_A(white)" ); MiscUtils::plot_point_sets( __dst, _3dpts_of_A_projectedonB_with_odom_rel_pose, cv::Scalar( 255,0,0), false, ";;;_3dpts_of_A_projectedonB_with_odom_rel_pose, PI( odom_b_T_a * X),(blue)" ); // status image for __dst. string status_msg = "im2_left_srectified;"; status_msg += "_detectedpts_of_B (red);"; status_msg += "_3dpts_of_A_projectedonB, PI( b_T_a * X),(yellow);"; status_msg += "_detectedpts_of_A(white);"; status_msg += "_3dpts_of_A_projectedonB_with_odom_rel_pose, PI( odom_b_T_a * X),(blue);"; MiscUtils::append_status_image( __dst, status_msg+";"+pnp__msg ); // proc_candi.pnp_verification_image = __dst; proc_candi.debug_images.push_back( __dst ); proc_candi.debug_images_titles.push_back( "pnp_verification_image" ); #if __Cerebro__loopcandi_consumer__IMSHOW == 2 cv::imshow( "im2_left_srectified", __dst ); #endif #endif // disparity and other images #if (__Cerebro__loopcandi_consumer__IMSHOW == 1) || (__Cerebro__loopcandi_consumer__IMSHOW == 2) // proc_candi.node_1_disparity_viz = disparity_for_visualization; // this is disparity of im1. see code way above proc_candi.debug_images.push_back( disparity_for_visualization ); proc_candi.debug_images_titles.push_back( "node_1_disparity_viz" ); #if __Cerebro__loopcandi_consumer__IMSHOW == 2 // cv::imshow( "imleft_srectified", imleft_srectified ); // cv::imshow( "imright_srectified", imright_srectified ); cv::imshow( "im1_disparity_for_visualization", disparity_for_visualization ); #endif #endif // Done #if __Cerebro__loopcandi_consumer__IMSHOW == 2 cv::waitKey(50); #endif return true; } #endif //////////// END pose computation ////////////////////////////// //--------------------------------------------------------------// //------------------ END Geometry Thread -----------------------// //--------------------------------------------------------------// /////////////////////////////////////////////////////////// //---------------------- KIDNAP -------------------------// /////////////////////////////////////////////////////////// // Kidnap identification thread. This thread monitors dataManager->getDataMapRef().size // for every new node added if there are zero tracked features means that, I have // been kidnaped. It however declares kidnap only after 2 sec of kidnaped // #define __Cerebro__kidnaped_thread__( msg ) msg; #define __Cerebro__kidnaped_thread__( msg ) ; // #define __Cerebro__kidnaped_thread__debug( msg ) msg; #define __Cerebro__kidnaped_thread__debug( msg ) ; void Cerebro::kidnaped_thread( int loop_rate_hz ) { if( loop_rate_hz <= 0 || loop_rate_hz >= 30 ) { cout << TermColor::RED() << "[Cerebro::kidnaped_thead] Invalid loop_rate_hz. Expected to be between [1,30]\n" << TermColor::RESET() << endl; return; } cout << TermColor::GREEN() << "Start Cerebro::kipnaped_thead\n" << TermColor::RESET() << endl; if( dataManager->isKidnapIndicatorPubSet() == false ) { cout << TermColor::RED() << "FATAL ERROR in [Cerebro::kidnaped_thread] kidnap indicator ros::Publishers were not set\n"; exit(2); } //--- //--- Main Settings for this thread //--- const int THRESH_N_FEATS = 15; //declare kidnap if number of tracked features fall below 15 const ros::Duration WAIT_BEFORE_DECLARING_AS_KIDNAP = ros::Duration(3.0); // wait this many seconds, of low feature tracking count before declaring kidnap ros::Rate loop_rate( loop_rate_hz ); int prev_count = 0, new_count = 0; ros::Time last_known_keyframe; // TODO: Move both of these to class variables and make them atomic. is_kidnapped_start is valid only when is_kidnapped==true bool is_kidnapped = false; bool is_kidnapped_more_than_n_sec = false; ros::Time is_kidnapped_start; // Related to handling to playing multiple bags one-after-another. bool first_data_received = false; bool waiting_for_next_bag_to_start = false; while( b_kidnaped_thread_enable ) { // Book Keeping prev_count = new_count; loop_rate.sleep(); auto data_map = dataManager->getDataMapRef(); //< map< ros::Time, DataNode*> new_count = data_map->size(); if( new_count <= prev_count ) { __Cerebro__kidnaped_thread__(cout << "[Cerebro::kidnaped_thread]Nothing new\n";) continue; } ros::Time lb = data_map->rbegin()->first - ros::Duration(5, 0); // look at recent 5sec. // auto S = data_map.begin(); auto S = data_map->lower_bound( lb ); auto E = data_map->end(); __Cerebro__kidnaped_thread__debug( cout << "[Cerebro::kidnaped_thread] S=" << S->first << " E=" << E->first << endl; ) for( auto it = S ; it != E ; it++ ) { #if 0 if( it->second->isImageAvailable() ) { cout << TermColor::GREEN(); } else { cout << TermColor::BLUE() ; } if( it->second->isPoseAvailable() && it->second->getNumberOfSuccessfullyTrackedFeatures() < 0 ) { cout << "A"; } if( !it->second->isPoseAvailable() && it->second->getNumberOfSuccessfullyTrackedFeatures() < 0 ) { cout << "B"; } if( it->second->isPoseAvailable() && ! (it->second->getNumberOfSuccessfullyTrackedFeatures() < 0 ) ) { cout << "C"; } if( !it->second->isPoseAvailable() && ! (it->second->getNumberOfSuccessfullyTrackedFeatures() < 0 ) ){ cout << "D"; } cout << TermColor::RESET() ; #endif int n_feats = it->second->getNumberOfSuccessfullyTrackedFeatures(); if( it->first <= last_known_keyframe || n_feats < 0 ) continue; __Cerebro__kidnaped_thread__debug( if( n_feats > 50 ) cout <first << ": n_feats=" << TermColor::iGREEN() << n_feats << TermColor::RESET() << endl; if( n_feats > 30 && n_feats<=50 ) cout <first << ": n_feats=" << TermColor::GREEN() << n_feats << TermColor::RESET() << endl; if( n_feats > 20 && n_feats<=30 ) cout <first << ": n_feats=" << TermColor::YELLOW() << n_feats << TermColor::RESET() << endl; if( n_feats > 10 && n_feats<=20 ) cout <first << ": n_feats=" << TermColor::RED() << n_feats << TermColor::RESET() << endl; if( n_feats<=10 ) cout <first << ": n_feats=" << TermColor::iRED() << n_feats << TermColor::RESET() << endl; ) last_known_keyframe = it->first; if( is_kidnapped==false && n_feats < THRESH_N_FEATS ) { is_kidnapped = true; is_kidnapped_start = it->first; __Cerebro__kidnaped_thread__( cout << TermColor::RED() << "I am kidnapped t=" << it->first << TermColor::RESET() << endl; cout << "I think so because the number of tracked features (from feature tracker) have fallen to only " << n_feats << ", the threshold was " << THRESH_N_FEATS << ". However, I will wait for " << WAIT_BEFORE_DECLARING_AS_KIDNAP << " sec to declare kidnapped to vins_estimator." << endl; ) } __Cerebro__kidnaped_thread__( if( is_kidnapped ) { cout << "is_kidnapped=true t=" << it->first << " n_feats=" << n_feats << endl; } ) if( is_kidnapped && !is_kidnapped_more_than_n_sec && (it->first - is_kidnapped_start) > WAIT_BEFORE_DECLARING_AS_KIDNAP ) { __Cerebro__kidnaped_thread__( cout << "Kidnapped for more than " << WAIT_BEFORE_DECLARING_AS_KIDNAP << "sec. I am quite confident that I have been kidnapped\n"; cout << "PUBLISH FALSE\n"; ) is_kidnapped_more_than_n_sec = true; dataManager->PUBLISH__FALSE( is_kidnapped_start ); } if( is_kidnapped && n_feats > THRESH_N_FEATS ) { __Cerebro__kidnaped_thread__( cout << TermColor::GREEN() << "Looks like i have been unkidnapped t=" << it->first << TermColor::RESET() << endl; ) if( is_kidnapped_more_than_n_sec ) { // publish true to vins_estimator to indicate that it may resume the estimation with a new co-ordinate system. dataManager->PUBLISH__TRUE( it->first ); } is_kidnapped = false; is_kidnapped_more_than_n_sec = false; } } // cout << endl; } cout << TermColor::RED() << "Cerebro::kipnaped_thead Ends\n" << TermColor::RESET() << endl; } bool Cerebro::kidnap_info( int i, ros::Time& start_, ros::Time& end_ ) { std::lock_guard lk(mutex_kidnap); if( i>=0 && i lk(mutex_kidnap); json json_obj; json_obj["meta_info"]["state_is_kidnapped"] = (bool) state_is_kidnapped; json_obj["meta_info"]["len_start_of_kidnap"] = start_of_kidnap.size(); json_obj["meta_info"]["len_end_of_kidnap"] = end_of_kidnap.size(); for( int i=0 ; i lk(mutex_kidnap); return start_of_kidnap.size(); } bool Cerebro::is_kidnapped(){ state_is_kidnapped; } #define __CEREBRO_kidnap_callbacks(msg) msg; // #define __CEREBRO_kidnap_callbacks(msg) ; void Cerebro::kidnap_bool_callback( const std_msgs::BoolConstPtr& rcvd_bool ) { return; // ignore this. __CEREBRO_kidnap_callbacks( cout << TermColor::iCYAN() << "[Cerebro::kidnap_bool_callback] msg=" << (bool)rcvd_bool->data << TermColor::RESET() << endl; ) } void Cerebro::kidnap_header_callback( const std_msgs::HeaderConstPtr& rcvd_header ) { __CEREBRO_kidnap_callbacks( cout << TermColor::iCYAN() << "[Cerebro::kidnap_header_callback]" ; cout << rcvd_header->stamp << ":" << rcvd_header->frame_id ; cout << TermColor::RESET() << endl; ) if( rcvd_header->frame_id == "kidnapped" ) { std::lock_guard lk(mutex_kidnap); this->state_is_kidnapped = true; start_of_kidnap.push_back( rcvd_header->stamp ); __CEREBRO_kidnap_callbacks( cout << TermColor::iCYAN() << "start_of_kidnap.push_back("<< rcvd_header->stamp << ")" << TermColor::RESET() << endl; ) return; } if( rcvd_header->frame_id == "unkidnapped" ) { std::lock_guard lk(mutex_kidnap); this->state_is_kidnapped = false; end_of_kidnap.push_back( rcvd_header->stamp ); __CEREBRO_kidnap_callbacks( cout << TermColor::iCYAN() << "end_of_kidnap.push_back("<< rcvd_header->stamp << ")" << TermColor::RESET() << endl; ) return; } assert( false && "in Cerebro::kidnap_header_callback. rcvd_header->frame_id is something other than `kidnapped` or `unkidnapped`."); } /////////////////////////////////////////////////////////// //---------------------- KIDNAP -------------------------// /////////////////////////////////////////////////////////// //----------------------------------------------------------------- // Cerebro Private Utils //----------------------------------------------------------------- // private function for descriptor_dot_product thread bool Cerebro::wait_until__connectedToDescServer_and_descSizeAvailable( int timeout_in_sec ) //blocking call { assert( timeout_in_sec > 2 ); ros::Rate rate(10); // wait until connected_to_descriptor_server=true and descriptor_size_available=true int wait_itr = 0; while( true ) { if( this->connected_to_descriptor_server && this->descriptor_size_available) break; __Cerebro__run__(cout << wait_itr << " [Cerebro::wait_until__connectedToDescServer_and_descSizeAvailable] waiting for `descriptor_size_available` to be true. timeout_in_sec=" << timeout_in_sec << "\n";) rate.sleep(); wait_itr++; if( wait_itr > timeout_in_sec*10 ) { __Cerebro__run__( cout << TermColor::RED() << "[Cerebro::wait_until__connectedToDescServer_and_descSizeAvailable] `this->connected_to_descriptor_server && this->descriptor_size_available` has not become true dispite waiting for about "<< timeout_in_sec << " sec. So quiting the run thread.\n" << TermColor::RESET(); ) return false; } } return true; } //----------------------------------------------------------------- // END Cerebro Private Utils //----------------------------------------------------------------- ================================================ FILE: src/Cerebro.h ================================================ #pragma once /** Cerebro Class This class is suppose to be the main-brain of this package. It has to run its own threads (should not block) It can access DataManager::camera, DataManager::imu_T_cam, DataManager::data_map. Author : Manohar Kuse Created : 29th Oct, 2018 */ #include #include #include #include #include #include "PinholeCamera.h" #include "DataManager.h" #include "ProcessedLoopCandidate.h" #include "DlsPnpWithRansac.h" #include "HypothesisManager.h" #include "utils/TermColor.h" #include "utils/ElapsedTime.h" #include "utils/Plot2Mat.h" #include "utils/CameraGeometry.h" #include "utils/PointFeatureMatching.h" #include "utils/nlohmann/json.hpp" using json = nlohmann::json; // ROS-Service Defination #include // #include #include #include using namespace Eigen; using namespace std; //comment this out to remove dependence on faiss. // If using faiss, also remember to link to libfaiss.so. See my CMakeList file to know how to do it. // #define HAVE_FAISS #ifdef HAVE_FAISS // faiss is only used for generating loopcandidates. // only function that uses faiss is `Cerebro::faiss__naive_loopcandidate_generator()` // Although faiss is strictly not needed, it is a much // faster way for nearest neighbour search. If you don't want it // just use the naive implementation: descrip_N__dot__descrip_0_N(). // Note: Cerebro::descrip_N__dot__descrip_0_N() and Cerebro::faiss__naive_loopcandidate_generator() // are exactly same in functionality. #include #endif //HAVE_FAISS class Cerebro { //-------------- Constructor --------------------// public: Cerebro( ros::NodeHandle& nh ); // TODO removal of nh argument. void setDataManager( DataManager* dataManager ); void setPublishers( const string base_topic_name ); private: // global private variables bool m_dataManager_available=false; DataManager * dataManager; ros::NodeHandle nh; ///< This is here so that I can set new useful publishers bool m_pub_available = false; ros::Publisher pub_loopedge; //-------------- END Constructor --------------------// //--------------- Descriptor Computation Thread ------------------// public: // This monitors the dataManager->data_map and makes sure the descriptor are uptodate. // descriptors are computed by an external ros-service. in the future can have // more similar threads to compute object bounding boxes, text and other perception related services. void descriptor_computer_thread_enable() { b_descriptor_computer_thread = true; } void descriptor_computer_thread_disable() { b_descriptor_computer_thread = false; } void descriptor_computer_thread(); private: atomic b_descriptor_computer_thread; atomic connected_to_descriptor_server; atomic descriptor_size_available; atomic descriptor_size; // Storage for Intelligence mutable std::mutex m_wholeImageComputedList; vector wholeImageComputedList; ///< A list of stamps where descriptors are computed and available. void wholeImageComputedList_pushback( const ros::Time __tx ); //this is kept private on purpose so that others from outside cannot pushback here. public: const int wholeImageComputedList_size() const; //size of the list. threadsafe const ros::Time wholeImageComputedList_at(int k) const; //< returns kth element of the list. threadsafe //--------------- END Descriptor Computation Thread ------------------// //---------------- Populate Loop Candidates --------------------// public: // This is supposed to be run in a separate thread. void run_thread_enable() { b_run_thread = true; } void run_thread_disable() { b_run_thread = false; } void run(); //< The loopcandidate (geometrically unverified) producer. //### Method-A : Naive method of dot product DIY void descrip_N__dot__descrip_0_N(); //< Naive method of dot product DIY #ifdef HAVE_FAISS //### Method-B : same functionality to descrip_N__dot__descrip_0_N() but uses facebook's faiss library void faiss__naive_loopcandidate_generator(); //### Method-C: //### In this we get say 5 nearest neighbour for every l_i. //### Collect nearest neigbours for say 4 consecutive l_i's. //### Nearby predictions are merged. void faiss_clique_loopcandidate_generator(); //### Method-D: // Uses a separate hypothesis manager. My multihyp framework. // elaborate scheme, still under development. void faiss_multihypothesis_tracking(); #endif //HAVE_FAISS private: // private things to run thread atomic b_run_thread; bool wait_until__connectedToDescServer_and_descSizeAvailable( int timeout_in_sec ); //blocking call public: // These are loop candidates. These calls are thread-safe // producer: `run()` // user: `Visualization::publish_loopcandidates` const int foundLoops_count() const ; const std::tuple foundLoops_i( int i) const; json foundLoops_as_JSON(); private: mutable std::mutex m_foundLoops; vector< std::tuple > foundLoops; // a list containing loop pairs. this is populated by `run()` //---------------- END Populate Loop Candidates --------------------// //------------------ Geometry Thread ---------------------------// // calls this->foundLoops_count() and this->foundLoops_i() and uses dataManager // to geometric verify and to compute the poses of loop-pairs. public: void loopcandidate_consumer_enable() { b_loopcandidate_consumer=true; } void loopcandidate_consumer_disable() { b_loopcandidate_consumer=false; } void loopcandiate_consumer_thread(); const int processedLoops_count() const; const ProcessedLoopCandidate& processedLoops_i( int i ) const; // TODO: have a function which returns a json of the info in processedloopcandi_list. private: atomic b_loopcandidate_consumer; // helpers // Processed foundLoops_i[ j ] and writes the info in the object `proc_candi` // bool process_loop_candidate_imagepair( int j, ProcessedLoopCandidate& proc_candi ); // This function processes the jth loopcandidate and fills in the ProcessedLoopCandidate. // The return status means that some poses were computed. It doesn't mean the poses were consistent. // Infact, nothing about consistency is performed here. It just computes relative poses using 3 indipendent way. bool process_loop_candidate_imagepair_consistent_pose_compute( int j, ProcessedLoopCandidate& proc_candi ); //< enhanced version of the above bool init_stereogeom(); // expected to be called in loopcandiate_consumer_thread. this sets the variable `stereogeom` bool retrive_stereo_pair( DataNode* node, cv::Mat& left_image, cv::Mat& right_image, bool bgr2gray=true ); std::shared_ptr stereogeom; mutable std::mutex m_processedLoops; vector< ProcessedLoopCandidate > processedloopcandi_list; //------------------ END Geometry Thread ---------------------------// //--------------- kidnaped identification thread ------------------// public: void kidnaped_thread( int loop_rate_hz=5); void kidnaped_thread_enable() {b_kidnaped_thread_enable=true;}; void kidnaped_thread_disable() {b_kidnaped_thread_enable=false;}; bool is_kidnapped(); // gives the current (kidnap) status. threadsafe bool kidnap_info( int i, ros::Time& start_, ros::Time& end_ ); //< returns true if i was a valid kidnap index. return false if it was an invalid index, ie. such kidnap didnt exist json kidnap_info_as_json(); int n_kidnaps(); //< will with return length of `start_of_kidnap` current state has to be inferred by call to `is_kidnapped()` // kidnap callbacks void kidnap_bool_callback( const std_msgs::BoolConstPtr& rcvd_header ) ; void kidnap_header_callback( const std_msgs::HeaderConstPtr& rcvd_header ) ; private: atomic b_kidnaped_thread_enable; std::mutex mutex_kidnap; atomic< bool > state_is_kidnapped; vector< ros::Time > start_of_kidnap; vector< ros::Time > end_of_kidnap; ros::Publisher rcvd_flag_pub; ros::Publisher kidnap_indicator_header_pub; bool is_kidnapn_indicator_set = false; }; ================================================ FILE: src/DataManager.cpp ================================================ #include "DataManager.h" DataManager::DataManager(ros::NodeHandle &nh ) //: out_stream(ofstream("/dev/pts/0",ios::out) ) { this->nh = nh; } DataManager::DataManager(const DataManager &obj) //: out_stream(ofstream("/dev/pts/0",ios::out) ) { cout << "Copy constructor allocating ptr." << endl; } ////////////////////////////////////////////////////////////////////////////// //////////////////// Setters and Getters for global info ///////////////////// ////////////////////////////////////////////////////////////////////////////// // void DataManager::setCamera( const PinholeCamera& camera ) // { // this->camera = camera; // // cout << "--- Camera Params from DataManager ---\n"; // this->camera.printCameraInfo(); // // cout << "K\n" << this->camera.e_K << endl; // // cout << "D\n" << this->camera.e_D << endl; // cout << "--- END\n"; // } void DataManager::setAbstractCamera( camodocal::CameraPtr abs_camera, short cam_id ) { assert( cam_id >= 0 && "DataManager, you requested a camera with negative id which is an error. cam_id=0 for default camera and 1,2,.. for additional cameras.\n" ); assert(abs_camera && "[DataManager::setCamera] in datamanager you are trying to set an invalid abstract camera. You need to loadFromYAML before you can set this camera\n"); // this->abstract_camera = abs_camera; this->all_abstract_cameras[cam_id] = abs_camera; cout << "--- Abstract CameraParams(cam_id=" << cam_id << ") from DataManager ---\n"; // cout << this->abstract_camera->parametersToString() << endl; cout << this->all_abstract_cameras[cam_id]->parametersToString() << endl; cout << "--- END\n"; } camodocal::CameraPtr DataManager::getAbstractCameraRef(short cam_id) const { assert( cam_id >= 0 && "DataManager, you requested a camera with negative id which is an error. cam_id=0 for default camera and 1,2,.. for additional cameras.\n" ); // assert( abstract_camera && "[DataManager::getAbstractCameraRef] you are requesting a camera reference before setting.\n" ); // return abstract_camera; assert( isAbstractCameraSet(cam_id) && "[DataManager::getAbstractCameraRef] you are requesting a camera reference before setting.\n"); // return this->all_abstract_cameras[cam_id]; //old non-const access return this->all_abstract_cameras.at(cam_id); } bool DataManager::isAbstractCameraSet(short cam_id) const { assert( cam_id >= 0 && "DataManager, you requested a camera with negative id which is an error. cam_id=0 for default camera and 1,2,.. for additional cameras.\n" ); if( this->all_abstract_cameras.count( cam_id ) > 0 ) { return true; // if( this->all_abstract_cameras[cam_id] ) // return true; // else // return false; } else { return false; } } vector DataManager::getAbstractCameraKeys() const { vector keys; for( auto it=this->all_abstract_cameras.begin(); it!=all_abstract_cameras.end() ; it++ ) { keys.push_back( it->first ); } return keys; } void DataManager::setCameraRelPose( Matrix4d a_T_b, std::pair pair_a_b ) { // assert a and b abstract cameras exisits assert( isAbstractCameraSet(pair_a_b.first) && isAbstractCameraSet(pair_a_b.second) && "in [DataManager::setCameraRelPose] one of the abstract-cameras were not set, even though you asked me to set their relative pose. You need to set the cameras first before you can set the relative poses.\n" ); cout << "---DataManager::setCameraRelPose---\n"; cout << "setting "<< pair_a_b.first << "_T_" << pair_a_b.second << " :::> " << PoseManipUtils::prettyprintMatrix4d( a_T_b ) << endl; cam_relative_poses[ pair_a_b ] = a_T_b; cout << "---DONE---\n"; } bool DataManager::isCameraRelPoseSet( std::pair pair_a_b ) const { if( this->cam_relative_poses.count( pair_a_b ) > 0 ) return true; else return false; } const Matrix4d& DataManager::getCameraRelPose( std::pair pair_a_b ) const { assert( isCameraRelPoseSet( pair_a_b ) && "[DataManager::getCameraRelPose] make sure the rel cam pose you are requesting is available\n" ); if( !isCameraRelPoseSet( pair_a_b) ) { ROS_ERROR( "[DataManager::getCameraRelPose] make sure the rel cam pose you are requesting is available\nYou requested (%d,%d) which is not available", pair_a_b.first, pair_a_b.second ); exit(2); } // return this->cam_relative_poses[ pair_a_b ]; //old non const return this->cam_relative_poses.at( pair_a_b ); } vector< std::pair > DataManager::getCameraRelPoseKeys() { vector< pair > keys; for( auto it=this->cam_relative_poses.begin(); it!=cam_relative_poses.end() ; it++ ) { keys.push_back( it->first ); } return keys; } const Matrix4d& DataManager::getIMUCamExtrinsic() const { std::lock_guard lk(global_vars_mutex); assert( imu_T_cam_available && "[DataManager::getIMUCamExtrinsic] you request the value before setting it\n"); return imu_T_cam; } bool DataManager::isIMUCamExtrinsicAvailable() const { std::lock_guard lk(global_vars_mutex); return imu_T_cam_available; } const ros::Time DataManager::getIMUCamExtrinsicLastUpdated() const { std::lock_guard lk(global_vars_mutex); return imu_T_cam_stamp; } void DataManager::print_datamap_status( string fname ) const { ofstream myfile; myfile.open (fname); myfile << "#Nodes=" << data_map->size(); if( data_map->size() > 0 ) { myfile << "\tBEGIN="<< data_map->begin()->first; myfile << "\tEND="<< data_map->rbegin()->first; } myfile << endl; // TODO other global info like imu_T_cam. etc. for( auto it = data_map->begin() ; it!= data_map->end() ; it++ ) { string s = "|"; s+= (it->second->isKeyFrame())?TermColor::iGREEN():""; s+= (it->second->isPoseAvailable())?"P":"~"; s+= (it->second->isWholeImageDescriptorAvailable())?"D":"~"; myfile << s << TermColor::RESET(); } myfile << endl; myfile.close(); } ////////////////////////////////////////////////////////////////////////////// /////////////////// Kidnap Indicator Publisher /////////////////////////////// ////////////////////////////////////////////////////////////////////////////// bool DataManager::isKidnapIndicatorPubSet() const { return (bool)is_kidnapn_indicator_set ; } void DataManager::setKidnapIndicatorPublishers( ros::Publisher& pub_bool, ros::Publisher& pub_header ) { rcvd_flag_pub = pub_bool; kidnap_indicator_header_pub = pub_header; is_kidnapn_indicator_set = true; } void DataManager::PUBLISH__TRUE( const ros::Time _t ) const { assert( is_kidnapn_indicator_set ); cout << TermColor::RED() << "[Cerebro::PUBLISH__TRUE] PUBLISH TRUE (t= " << _t << " to indicate the vins_estimator to start again.\n" << TermColor::RESET(); std_msgs::Bool bool_msg; bool_msg.data = true; rcvd_flag_pub.publish( bool_msg ); // publish header msg std_msgs::Header header_msg; header_msg.stamp = _t; header_msg.frame_id = "unkidnapped"; kidnap_indicator_header_pub.publish( header_msg ); } void DataManager::PUBLISH__FALSE( const ros::Time _t ) const { assert( is_kidnapn_indicator_set ); cout << TermColor::RED() << "[Cerebro::PUBLISH__FALSE] PUBLISH FALSE (t= " << _t << " to indicate the vins_estimator to stop.\n" << TermColor::RESET(); // publish False (bool msg) std_msgs::Bool bool_msg; bool_msg.data = false; rcvd_flag_pub.publish( bool_msg ); // publish header message std_msgs::Header header_msg; header_msg.stamp = _t; header_msg.frame_id = "kidnapped"; kidnap_indicator_header_pub.publish( header_msg ); } ////////////////////////////////////////////////////////////////////////////// //////////////////////////////// call backs ////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // #define __DATAMANAGER_CALLBACK_PRINT( u ) u; #define __DATAMANAGER_CALLBACK_PRINT( u ) ; void DataManager::camera_pose_callback( const nav_msgs::Odometry::ConstPtr msg ) { if( pose_0_available == false ) { //record the 1st pose pose_0 = msg->header.stamp; pose_0_available = true; } // __DATAMANAGER_CALLBACK_PRINT( cout << TermColor::BLUE() << "[cerebro/camera_pose_callback del]" << msg->header.stamp-pose_0 << TermColor::RESET() << endl; ) __DATAMANAGER_CALLBACK_PRINT( cout << TermColor::BLUE() << "[cerebro/camera_pose_callback]" << msg->header.stamp << "\t" << msg->header.stamp-pose_0 << TermColor::RESET() << endl; ) // push this to queue. Another thread will associate the data pose_buf.push( msg ); return; } // currently not in use. void DataManager::keyframe_pose_callback( const nav_msgs::Odometry::ConstPtr msg ) { //__DATAMANAGER_CALLBACK_PRINT( cout << "[cerebro/camera_pose_callback del]" << msg->header.stamp - pose_0 << endl; ) __DATAMANAGER_CALLBACK_PRINT( cout << TermColor::iBLUE() << "[cerebro/keyframe_pose_callback]" << msg->header.stamp << "\t" << msg->header.stamp-pose_0 << TermColor::RESET() << endl; ) // push this to queue. Another thread will associate the data kf_pose_buf.push( msg ); } void DataManager::raw_image_callback( const sensor_msgs::ImageConstPtr& msg ) { // __DATAMANAGER_CALLBACK_PRINT( cout << TermColor::GREEN() << "[cerebro/raw_image_callback del]" << msg->header.stamp-pose_0 << TermColor::RESET() << endl; ) __DATAMANAGER_CALLBACK_PRINT( cout << TermColor::GREEN() << "[cerebro/raw_image_callback]" << msg->header.stamp << "\t" << msg->header.stamp-pose_0 << TermColor::RESET() << endl; ) if( this->last_image_time != ros::Time() && (msg->header.stamp.toSec() - this->last_image_time.toSec() > 1.0 || msg->header.stamp.toSec() < this->last_image_time.toSec()) ) { cout << TermColor::iBLUE() << "+++++++++++++[DataManager::raw_image_callback] " << " curr_image.stamp - prev_image.stamp > 1.0.\n" << "This means an unstable stream or more usually means a new bag has started with --skip-empty in rosbagplay.\n" << "I will publish a FALSE t=" << 10000 << " then wait for 500ms and publish TRUE t=" << 10000 << " TODO Complete this implementation and verify correctness." << TermColor::RESET() << endl; cout << TermColor::BLUE() << "PUBLISH__FALSE\n" << TermColor::RESET(); PUBLISH__FALSE( this->last_image_time ); cout << TermColor::BLUE() << "Sleep 500ms\n" << TermColor::RESET(); // sleep( 500 ms ); std::this_thread::sleep_for (std::chrono::milliseconds(500)); cout << TermColor::BLUE() << "PUBLISH__TRUE\n" << TermColor::RESET(); PUBLISH__TRUE( msg->header.stamp ); } img_buf.push( msg ); this->last_image_time = msg->header.stamp; return; } void DataManager::raw_image_callback_1( const sensor_msgs::ImageConstPtr& msg ) { // __DATAMANAGER_CALLBACK_PRINT( cout << "[cerebro/raw_image_callback_1 del]" << msg->header.stamp-pose_0 << endl; ) __DATAMANAGER_CALLBACK_PRINT( cout << "[cerebro/raw_image_callback_1]" << msg->header.stamp << "\t" << msg->header.stamp-pose_0 << endl; ) img_1_buf.push( msg ); return; } void DataManager::depth_image_callback( const sensor_msgs::ImageConstPtr& msg ) { __DATAMANAGER_CALLBACK_PRINT( cout << "[cerebro/depth_image_callback]" << msg->header.stamp << "\t" << msg->header.stamp-pose_0 << "\t"; cout << "depth image encoding: " << msg->encoding << endl; ) depth_im_buf.push( msg ); return ; } void DataManager::extrinsic_cam_imu_callback( const nav_msgs::Odometry::ConstPtr msg ) { // __DATAMANAGER_CALLBACK_PRINT( cout << "[cerebro/extrinsic_cam_imu_callback del]" << msg->header.stamp-pose_0 << endl; ) __DATAMANAGER_CALLBACK_PRINT( cout << "[cerebro/extrinsic_cam_imu_callback]" << msg->header.stamp << "\t" << msg->header.stamp-pose_0 << endl; ) extrinsic_cam_imu_buf.push( msg ); } // there will be 5 channels. ch[0]: un, ch[1]: vn, ch[2]: u, ch[3]: v. ch[4]: globalid of the feature. // cout << "\tpoints.size() : "<< msg->points.size(); // this will be N (say 92) // cout << "\tchannels.size() : "<< msg->channels.size(); //this will be N (say 92) // cout << "\tchannels[0].size() : "<< msg->channels[0].values.size(); //this will be 5. // cout << "\n"; // An Example Keypoint msg // --- // header: // seq: 40 // stamp: // secs: 1523613562 // nsecs: 530859947 // frame_id: world // points: // - // x: -7.59081602097 // y: 7.11367511749 // z: 2.85602664948 // . // . // . // - // x: -2.64935922623 // y: 0.853760659695 // z: 0.796766400337 // channels: // - // name: '' // values: [-0.06108921766281128, 0.02294199913740158, 310.8721618652344, 260.105712890625, 2.0] // . // . // . // - // name: '' // values: [-0.47983112931251526, 0.8081198334693909, 218.95481872558594, 435.47357177734375, 654.0] // - // name: '' // values: [0.07728647440671921, 1.0073764324188232, 344.2176208496094, 473.7791442871094, 660.0] // - // name: '' // values: [-0.6801641583442688, 0.10506453365087509, 159.75746154785156, 279.6077575683594, 663.0] void DataManager::ptcld_callback( const sensor_msgs::PointCloud::ConstPtr msg ) { // __DATAMANAGER_CALLBACK_PRINT( cout << "[cerebro/ptcld_callback]" << msg->header.stamp << endl; ) __DATAMANAGER_CALLBACK_PRINT( cout << TermColor::YELLOW() << "[cerebro/ptcld_callback]" << msg->header.stamp << "\t" << msg->header.stamp-pose_0 << TermColor::RESET() << endl; ) ptcld_buf.push( msg ); return; } // currently not in use void DataManager::tracked_feat_callback( const sensor_msgs::PointCloud::ConstPtr msg ) { __DATAMANAGER_CALLBACK_PRINT( cout << "[cerebro/tracked_feat_callback]" << msg->header.stamp << "\t" << msg->header.stamp-pose_0 << endl; ) trackedfeat_buf.push( msg ); } // #define __json_debuggin(msg) msg; #define __json_debuggin(msg) ; json DataManager::asJson() { json obj; auto __data_map = this->getDataMapRef(); IOFormat CSVFormat(StreamPrecision, DontAlignCols, ", ", ",\t\n"); __json_debuggin( cout << "[DataManager::asJson] Start loop through data_map\n"; ) #if 1 // int max_dist = std::distance( __data_map.begin() , __data_map.end() ); //berks__old int max_dist = std::distance( __data_map->begin() , __data_map->end() ); // for( auto it = __data_map.begin() ; it!= __data_map.end() ; it++ ) //berks__old for( auto it = __data_map->begin() ; it!= __data_map->end() ; it++ ) { // int seq_id = std::distance( __data_map.begin() , it ); // berks__old int seq_id = std::distance( __data_map->begin() , it ); __json_debuggin( cout << "[DataManager::asJson] " << seq_id << " max dist=" << max_dist << endl;) json curr_obj; curr_obj["stamp"] = it->first.toSec(); curr_obj["stamp_relative"] = ( it->first - this->getPose0Stamp() ).toSec() ; curr_obj["seq"] = seq_id; __json_debuggin( cout << "A\n"; ) DataNode * __u = it->second; curr_obj["getT"] = __u->getT().toSec(); #if 0 curr_obj["getT_image"] = __u->getT_image().toSec(); #endif // curr_obj["getT_image_1"] = __u->getT_image(1).toSec(); curr_obj["getT_pose"] = __u->getT_pose().toSec(); curr_obj["getT_uv"] = __u->getT_uv().toSec(); __json_debuggin( cout << "B\n"; ) curr_obj["isKeyFrame"] = __u->isKeyFrame(); #if 0 curr_obj["isImageAvailable"] = __u->isImageAvailable(); curr_obj["isImageAvailable_1"] = __u->isImageAvailable(1); #endif curr_obj["isPoseAvailable"] = __u->isPoseAvailable(); curr_obj["isPtCldAvailable"] = __u->isPtCldAvailable(); curr_obj["isUnVnAvailable"] = __u->isUnVnAvailable(); curr_obj["isUVAvailable"] = __u->isUVAvailable(); curr_obj["isFeatIdsAvailable"] = __u->isFeatIdsAvailable(); curr_obj["getNumberOfSuccessfullyTrackedFeatures"] = __u->getNumberOfSuccessfullyTrackedFeatures(); curr_obj["isWholeImageDescriptorAvailable"] = __u->isWholeImageDescriptorAvailable(); __json_debuggin( cout << "C\n"; ) if( __u->isPoseAvailable() ) { const Matrix4d wTc = __u->getPose(); json pose_ifo; pose_ifo["rows"] = wTc.rows(); pose_ifo["cols"] = wTc.cols(); pose_ifo["stamp"] = __u->getT_pose().toSec(); std::stringstream ss; ss << wTc.format(CSVFormat); pose_ifo["data"] = ss.str(); pose_ifo["data_pretty"] = PoseManipUtils::prettyprintMatrix4d(wTc); curr_obj["w_T_c"] = pose_ifo; } __json_debuggin( cout << "D\n"; ) obj["DataNodes"].push_back( curr_obj ); } #endif __json_debuggin( cout << "[DataManager::asJson] End loop through data_map\n"; ) // Global Data __json_debuggin( cout << "[DataManager::asJson] Logging Global data\n"; ) obj["global"]["isPose0Available"] = this->isPose0Available(); obj["global"]["Pose0Stamp"] = this->getPose0Stamp().toSec(); obj["global"]["isIMUCamExtrinsicAvailable"] = this->isIMUCamExtrinsicAvailable(); if( this->isIMUCamExtrinsicAvailable() ) { const Matrix4d __imu_T_cam = this->getIMUCamExtrinsic(); json pose_ifo; pose_ifo["rows"] = __imu_T_cam.rows(); pose_ifo["cols"] = __imu_T_cam.cols(); pose_ifo["stamp"] = this->getIMUCamExtrinsicLastUpdated().toSec(); std::stringstream ss; ss << __imu_T_cam.format(CSVFormat); pose_ifo["data"] = ss.str(); pose_ifo["data_pretty"] = PoseManipUtils::prettyprintMatrix4d(__imu_T_cam); obj["global"]["Pose0Stamp"] = pose_ifo; } __json_debuggin( cout << "[DataManager::asJson] Done asJson()\n"; ) return obj; } string DataManager::print_queue_size( int verbose=1 ) const { std::stringstream buffer; if( verbose == 1) { buffer << "img_buf=" << img_buf.size() << "\t"; buffer << "img_1_buf=" << img_1_buf.size() << "\t"; buffer << "depth_im_buf=" << depth_im_buf.size() << "\t"; buffer << "pose_buf=" << pose_buf.size() << "\t"; buffer << "kf_pose_buf=" << kf_pose_buf.size() << "\t"; buffer << "ptcld_buf=" << ptcld_buf.size() << "\t"; buffer << "trackedfeat_buf=" << trackedfeat_buf.size() << "\t"; buffer << "extrinsic_cam_imu_buf=" << extrinsic_cam_imu_buf.size() << "\t"; buffer << endl; } if( verbose == 2 ) { buffer << "img_buf=" << img_buf.size() << " ("; if( img_buf.size() > 0 ) { buffer << std::fixed << std::setprecision(4) << img_buf.front()->header.stamp-pose_0 << "-->"; buffer << std::fixed << std::setprecision(4) << img_buf.back()->header.stamp-pose_0 << ";"; } buffer << ")\t"; buffer << "img_1_buf=" << img_1_buf.size() << " ("; if( img_1_buf.size() > 0 ) { buffer << std::fixed << std::setprecision(4) << img_1_buf.front()->header.stamp-pose_0 << "-->"; buffer << std::fixed << std::setprecision(4) << img_1_buf.back()->header.stamp-pose_0 << ";"; } buffer << ")\t"; buffer << "depth_im_buf=" << depth_im_buf.size() << " ("; if( depth_im_buf.size() > 0 ) { buffer << std::fixed << std::setprecision(4) << depth_im_buf.front()->header.stamp-pose_0 << "-->"; buffer << std::fixed << std::setprecision(4) << depth_im_buf.back()->header.stamp-pose_0 << ";"; } buffer << ")\t"; buffer << "pose_buf=" << pose_buf.size() << " ("; if( pose_buf.size() > 0 ) { buffer << std::fixed << std::setprecision(4) << pose_buf.front()->header.stamp-pose_0 << "-->"; buffer << std::fixed << std::setprecision(4) << pose_buf.back()->header.stamp-pose_0 << ";"; } buffer << ")\t"; buffer << "kf_pose_buf=" << kf_pose_buf.size() << " ("; if( kf_pose_buf.size() > 0 ) { buffer << std::fixed << std::setprecision(4) << kf_pose_buf.front()->header.stamp-pose_0 << "-->"; buffer << std::fixed << std::setprecision(4) << kf_pose_buf.back()->header.stamp-pose_0 << ";"; } buffer << ")\t"; buffer << "ptcld_buf=" << ptcld_buf.size() << " ("; if( ptcld_buf.size() > 0 ) { buffer << std::fixed << std::setprecision(4) << ptcld_buf.front()->header.stamp-pose_0 << "-->"; buffer << std::fixed << std::setprecision(4) << ptcld_buf.back()->header.stamp-pose_0 << ";"; } buffer << ")\t"; buffer << "trackedfeat_buf=" << trackedfeat_buf.size() << " ("; if( trackedfeat_buf.size() > 0 ) { buffer << std::fixed << std::setprecision(4) << trackedfeat_buf.front()->header.stamp-pose_0 << "-->"; buffer << std::fixed << std::setprecision(4) << trackedfeat_buf.back()->header.stamp-pose_0 << ";"; } buffer << ")\t"; buffer << "extrinsic_cam_imu_buf=" << extrinsic_cam_imu_buf.size() << " ("; if( extrinsic_cam_imu_buf.size() > 0 ) { buffer << std::fixed << std::setprecision(4) << extrinsic_cam_imu_buf.front()->header.stamp-pose_0 << "-->"; buffer << std::fixed << std::setprecision(4) << extrinsic_cam_imu_buf.back()->header.stamp-pose_0 << ";"; } buffer << ")\t"; buffer << "\n"; } if( verbose == 3 ) { buffer << "img_buf.len=" << img_buf.size() << "\t"; buffer << "pose_buf.len=" << pose_buf.size() << "\t"; buffer << "kf_pose_buf.len=" << kf_pose_buf.size() << "\t"; buffer << "ptcld_buf.len=" << ptcld_buf.size() << "\t"; buffer << "trackedfeat_buf.len=" << trackedfeat_buf.size() << "\t"; buffer << "extrinsic_cam_imu_buf.len=" << extrinsic_cam_imu_buf.size() << "\t"; buffer << endl; if( img_buf.size() > 0 ) buffer << "img_buf.back.t=" << img_buf.back()->header.stamp-pose_0 << "\t"; if( pose_buf.size() > 0 ) buffer << "pose_buf.back.t=" << pose_buf.back()->header.stamp-pose_0 << "\t"; if( kf_pose_buf.size() > 0 ) buffer << "kf_pose_buf.back.t=" << kf_pose_buf.back()->header.stamp-pose_0 << "\t"; if( ptcld_buf.size() > 0 ) buffer << "ptcld_buf.back.t=" << ptcld_buf.back()->header.stamp-pose_0 << "\t"; if( trackedfeat_buf.size() > 0 ) buffer << "trackedfeat_buf.back.t=" << trackedfeat_buf.back()->header.stamp-pose_0 << "\t"; if( extrinsic_cam_imu_buf.size() > 0 ) buffer << "extrinsic_cam_imu_buf.back.t=" << extrinsic_cam_imu_buf.back()->header.stamp-pose_0 << "\t"; buffer << endl; if( img_buf.size() > 0 ) buffer << "img_buf.front.t=" << img_buf.front()->header.stamp-pose_0 << "\t"; if( pose_buf.size() > 0 ) buffer << "pose_buf.front.t=" << pose_buf.front()->header.stamp-pose_0 << "\t"; if( kf_pose_buf.size() > 0 ) buffer << "kf_pose_buf.front.t=" << kf_pose_buf.front()->header.stamp-pose_0 << "\t"; if( ptcld_buf.size() > 0 ) buffer << "ptcld_buf.front.t=" << ptcld_buf.front()->header.stamp-pose_0 << "\t"; if( trackedfeat_buf.size() > 0 ) buffer << "trackedfeat_buf.front.t=" << trackedfeat_buf.front()->header.stamp-pose_0 << "\t"; if( extrinsic_cam_imu_buf.size() > 0 ) buffer << "extrinsic_cam_imu_buf.front.t=" << extrinsic_cam_imu_buf.front()->header.stamp-pose_0 << "\t"; buffer << endl; } return buffer.str(); } //////////////////////////// Callback ends ///////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /////////////////////////// Thread mains ///////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// #define _XOUT_ myfile void DataManager::trial_thread( ) { cout << TermColor::GREEN() << "Start DataManager::trial_thread "<< TermColor::RESET() << endl; ros::Rate looprate(10); #if 0 ofstream myfile; myfile.open (fname); cout << "[DataManager::trial_thread] Dump output to file: " << fname << endl; while( b_trial_thread ) { _XOUT_ << "trial_thread\n"; // berks__old // auto S = data_map.begin(); //.lower_bound( lb ); // auto E = data_map.end(); auto S = data_map->begin(); auto E = data_map->end(); _XOUT_ << "S=" << S->first << " E=" << E->first << endl; int n_green = 0, n_cyan = 0 , n_blue = 0; for( auto it = S ; it != E ; it++ ) { #if 1 if( it->second->isImageAvailable() && it->second->isImageAvailable(1) ) { _XOUT_ << TermColor::GREEN();n_green++; } else { if( it->second->isImageAvailable() ) { _XOUT_ << TermColor::CYAN() ;n_cyan++; } else { _XOUT_ << TermColor::BLUE() ;n_blue++; } } if( it->second->isPoseAvailable() && it->second->getNumberOfSuccessfullyTrackedFeatures() < 0 ) { _XOUT_ << "A" ; // cout << it->second->getNumberOfSuccessfullyTrackedFeatures() << ","; } if( !it->second->isPoseAvailable() && it->second->getNumberOfSuccessfullyTrackedFeatures() < 0 ) { _XOUT_ << "B" ; // cout << it->second->getNumberOfSuccessfullyTrackedFeatures() << ","; // this means there thise node has no pose and no tracked features data } if( it->second->isPoseAvailable() && ! (it->second->getNumberOfSuccessfullyTrackedFeatures() < 0 ) ) { _XOUT_ << "C" ; // cout << it->second->getNumberOfSuccessfullyTrackedFeatures() << ","; } if( !it->second->isPoseAvailable() && ! (it->second->getNumberOfSuccessfullyTrackedFeatures() < 0 ) ){ _XOUT_ << "D"; // has no pose data but has tracked features. Pose actually takes some time to arrive. tracked features will be avaiaable first, } _XOUT_ << TermColor::RESET() ; #endif } _XOUT_ << endl; _XOUT_ << "n_green=" << n_green << " "; _XOUT_ << "n_cyan=" << n_cyan << " "; _XOUT_ << "n_blue=" << n_blue << " "; _XOUT_ << endl; looprate.sleep(); } myfile.close(); #endif while( b_trial_thread ) { img_data_mgr->print_status( "/dev/pts/23"); this->print_datamap_status( "/dev/pts/24" ); looprate.sleep(); } cout << TermColor::RED() << "END DataManager::trial_thread "<< TermColor::RESET() << endl; } // #define ___clean_up_cout(msg) msg; #define ___clean_up_cout(msg) ; void DataManager::clean_up_useless_images_thread() { //--- // Settings //--- int keep_last_n_sec_in_ram = 5; //to be safe keep 10. 3-5 usually is fine. cout << TermColor::GREEN() << "[DataManager::clean_up_useless_images_thread] Start thread "<< TermColor::RESET() << endl; ros::Rate looprate(0.3); // data_association_thread_disable(); while( b_clean_up_useless_images_thread ) { looprate.sleep(); // if( data_map.begin() == data_map.end() ) { //berks__old if( data_map->begin() == data_map->end() ) { cout << TermColor::CYAN() << "[clean_up_useless_images_thread] no nodes" << TermColor::RESET() << endl; continue; } // berks__old // auto S = data_map.begin(); // auto E = data_map.upper_bound( data_map.rbegin()->first - ros::Duration( 3.0 ) ); auto S = data_map->upper_bound( data_map->rbegin()->first - ros::Duration( keep_last_n_sec_in_ram+5 ) ); // auto S = data_map->begin(); auto E = data_map->upper_bound( data_map->rbegin()->first - ros::Duration( keep_last_n_sec_in_ram ) ); int q=0; ___clean_up_cout( cout << "[DataManager::clean_up_useless_images_thread] " << S->first << " to " << E->first << "\t"; ) ___clean_up_cout( cout << S->first-getPose0Stamp() << " to " << E->first - getPose0Stamp() << endl; ) // for( auto it = data_map.begin() ; it->first < E->first ; it++ ) { //berks__old for( auto it = S ; it->first < E->first ; it++ ) { #if 0 //old, where images where stored inside datanode. if( it->second->isImageAvailable() && !it->second->isKeyFrame() ) { // if( it->second->isImageAvailable() ) { ___clean_up_cout( cout << TermColor::CYAN() << "deallocate_all_images with t=" << it->first << " " << q++ << TermColor::RESET() << endl; it->second->print_image_cvinfo(); ) it->second->deallocate_all_images(); } else { ___clean_up_cout( cout << TermColor::YELLOW() << "not deallocate coz t=" << it->first << " aka t=" << it->first - getPose0Stamp() << "is a keyframe (has pose info)" << q++ << TermColor::RESET() << endl; ) } #endif if( it->second->isKeyFrame() ) { img_data_mgr->stashImage( "left_image", it->first ); img_data_mgr->stashImage( "right_image", it->first ); img_data_mgr->stashImage( "depth_image", it->first ); } else { img_data_mgr->rmImage( "left_image", it->first ); img_data_mgr->rmImage( "right_image", it->first ); img_data_mgr->rmImage( "depth_image", it->first ); } } } cout << TermColor::RED() << "[DataManager::clean_up_useless_images_thread] Finished Thread"<< TermColor::RESET() << endl; } // #define __DataManager__data_association_thread__( msg ) msg; #define __DataManager__data_association_thread__( msg ) ; void DataManager::data_association_thread( int max_loop_rate_in_hz ) { assert( max_loop_rate_in_hz > 0 && max_loop_rate_in_hz < 200 && "[DataManager::data_association_thread] I am expecting the loop rate to be between 1-50" ); cout << TermColor::GREEN() << "[DataManager::data_association_thread] Start thread" << TermColor::RESET() << endl; assert( b_data_association_thread && "You have not enabled thread execution. Call function DataManager::data_association_thread_enable() before you spawn this thread\n"); float requested_loop_time_ms = 1000. / max_loop_rate_in_hz; while( b_data_association_thread ) { auto loop_start_at = std::chrono::high_resolution_clock::now(); //------------------------ Process here-------------------------------- // std::this_thread::sleep_for( std::chrono::milliseconds(2000) ); __DataManager__data_association_thread__( cout << "---\n" ; cout << print_queue_size(2 ) ; //< sizes of buffer queues cout << "\t\tSize_of_data_map = "+ to_string( data_map->size() ) + "\n" ; ) // deqeue all raw images and make DataNodes of each of them, s while( img_buf.size() > 0 ) { sensor_msgs::ImageConstPtr img_msg = img_buf.front(); img_buf.pop(); __DataManager__data_association_thread__( cout << TermColor::GREEN() << ">>>>>>>>> Added a new DataNode in data_map with poped() rawimage t=" << img_msg->header.stamp << " #####> ie." << img_msg->header.stamp - pose_0 << TermColor::RESET() << endl; ) DataNode * n = new DataNode( img_msg->header.stamp ); #if 0 n->setImageFromMsg( img_msg ); #else img_data_mgr->setNewImageFromMsg( "left_image", img_msg ); #endif // data_map.insert( std::make_pair(img_msg->header.stamp, n) ); //berks__old data_map->insert( std::make_pair(img_msg->header.stamp, n) ); } // dequeue additional raw images while( img_1_buf.size() > 0 ) { sensor_msgs::ImageConstPtr img_1_msg = img_1_buf.front(); ros::Time t = img_1_msg->header.stamp; __DataManager__data_association_thread__( cout << ">> Attempt adding poped() img_1_msg in data_map with t=" << img_1_msg->header.stamp << " ie. #####> " << img_1_msg->header.stamp-pose_0 << endl; ) // if( data_map.count(t) > 0 ) { //berks__old // data_map.at( t )->setImageFromMsg( img_1_msg, 1 ); // } if( data_map->count(t) > 0 ) { #if 0 data_map->at( t )->setImageFromMsg( img_1_msg, 1 ); #else img_data_mgr->setNewImageFromMsg( "right_image", img_1_msg ); #endif img_1_buf.pop(); } else { ros::Time newest_T = data_map->rbegin()->first; __DataManager__data_association_thread__( cout << "newest_T=" << newest_T << endl; ) ; if( newest_T.toNSec() > t.toNSec() ) { cout << "newest_T is head of the t, so just drop this image"; img_1_buf.pop(); } else { // assert( false && "[DataManager::data_association_thread] attempting to set additional image into datanode. However that datanode is not found in the map. This cannot be happening\n"); __DataManager__data_association_thread__( cout << TermColor::RED() << "[DataManager::data_association_thread]"; cout << "attempting to set additional image_1 with t=" << t << " aka " << t-pose_0 << " into datanode. "; cout << "However that datanode is not found in the map. This cannot be happening, but might happen when `image_1` preceeds (even a few milis) than `image`."; cout << TermColor::RESET() << endl ; cout << TermColor::CYAN() << "so dont pop from queue.\n" << TermColor::RESET(); ) break; } } } // depth image while( depth_im_buf.size() > 0 ) { sensor_msgs::ImageConstPtr depth_image_msg = depth_im_buf.front(); ros::Time t = depth_image_msg->header.stamp; __DataManager__data_association_thread__( cout << ">> Attempt adding poped() depth_image_msg in data_map with t=" << depth_image_msg->header.stamp << " ie. #####> " << depth_image_msg->header.stamp-pose_0 << endl; ) // if( data_map.count(t) > 0 ) { //berks__old // data_map.at( t )->setImageFromMsg( img_1_msg, 1 ); // } if( data_map->count(t) > 0 ) { img_data_mgr->setNewImageFromMsg( "depth_image", depth_image_msg ); depth_im_buf.pop(); } else { ros::Time newest_T = data_map->rbegin()->first; __DataManager__data_association_thread__( cout << "newest_T=" << newest_T << endl; ) ; if( newest_T.toNSec() > t.toNSec() ) { cout << "newest_T is ahead of the t, so just drop this depth-image"; depth_im_buf.pop(); } else { // assert( false && "[DataManager::data_association_thread] attempting to set additional image into datanode. However that datanode is not found in the map. This cannot be happening\n"); __DataManager__data_association_thread__( cout << TermColor::RED() << "[DataManager::data_association_thread]"; cout << "attempting to set additional depth_image with t=" << t << " aka " << t-pose_0 << " into datanode. "; cout << "However that datanode is not found in the map. This cannot be happening, but might happen when `depth_image` preceeds (even a few milis) than `image`."; cout << TermColor::RESET() << endl ; cout << TermColor::CYAN() << "so dont pop from queue.\n" << TermColor::RESET(); ) break; } } } // dequeue all poses and set them to data_map while( pose_buf.size() > 0 ) { nav_msgs::Odometry::ConstPtr pose_msg = pose_buf.front(); ros::Time t = pose_msg->header.stamp; __DataManager__data_association_thread__( cout << ">> Attempt adding poped() pose in data_map with t=" << pose_msg->header.stamp << " ie. #####> " << pose_msg->header.stamp -pose_0 << endl; ) // find the DataNode with this timestamp //berks__old // if( data_map.count( t ) > 0 ) { // // a Node seem to exist with this t. // data_map.at( t )->setPoseFromMsg( pose_msg ); // } if( data_map->count( t ) > 0 ) { // a Node seem to exist with this t. data_map->at( t )->setPoseFromMsg( pose_msg ); pose_buf.pop(); } else { if( t > data_map->rbegin()->first ) { __DataManager__data_association_thread__( cout << "\tpose's t was not yet found in datamap. data_map->rbegin()->first=" << data_map->rbegin()->first << " "; cout << "this means a node doesnt exists yet for this pose. Usually this does not happen, but it occurs when Image data manager took too long to insert (thread blocking) and in the meantime more poses got available.\nI will not pop the queue in this.\n"; ); break; } // try range search // t-delta <= x <= t+delta. x is the map key. __DataManager__data_association_thread__( cout << "\tsince the key (for associating pose with data_map) was not found in data_map do range_search\n"; ) //berks__old auto __it = data_map->begin(); for( __it = data_map->begin() ; __it != data_map->end() ; __it++ ) { ros::Duration diff = __it->first-t; if( (diff.sec == 0 && abs(diff.nsec) < 1000000) || (diff.sec == -1 && diff.nsec > (1000000000-1000000) ) ) break; } // berks__old if( __it == data_map->end() ) { cout << TermColor::RED() << "\t`data_association_thread`:pose:(not fouind) range search failed AAA FATAL \n"; assert( false && "\tnot fouind\n"); exit(2); } __DataManager__data_association_thread__( cout << "\tfind pose->t=" << t << " in data_map\n"; std::cout << "\tinsert at position " << (__it->first) << '\n'; ) if( true ) { // berks__old data_map->at( __it->first )->setPoseFromMsg( pose_msg ); pose_buf.pop(); } else { cout << TermColor::RED() << "[DataManager::data_association_thread] data_map does not seem to contain the t of pose_msg. This cannot be happening...fatal quit" << TermColor::RESET() << endl; assert( false && "[DataManager::data_association_thread] data_map does not seem to contain the t of pose_msg. This cannot be happening\n"); exit(2); } } // else of if (data_map->count(t) > 0 } // dequeue all point clouds (these are at keyframes) while( ptcld_buf.size() > 0 ) { sensor_msgs::PointCloudConstPtr ptcld_msg = ptcld_buf.front(); ros::Time t = ptcld_msg->header.stamp; __DataManager__data_association_thread__( cout << ">> Attempt adding poped() pointcloud in data_map at t=" << t << " ie. #####> " << t - pose_0 << endl; ) // find the DataNode with this timestamp // berks__old if( data_map->count( t ) > 0 ) { // a Node seem to exist with this t. //NOte: the ``/vins_estimator/keyframe_point`` and ``/feature_tracker/feature`` // are in different formats so be careful. vins_estimator/keyframe_point has n channels. while feature_tracker/feature has just 5 channels. // but they both convey the same info, so be careful what exact you want. #if 0 data_map.at( t )->setPointCloudFromMsg( ptcld_msg ); data_map.at( t )->setUnVnFromMsg( ptcld_msg ); data_map.at( t )->setUVFromMsg( ptcld_msg ); data_map.at( t )->setTrackedFeatIdsFromMsg( ptcld_msg ); data_map.at( t )->setAsKeyFrame(); ptcld_buf.pop(); #else __DataManager__data_association_thread__( cout << "setNumberOfSuccessfullyTrackedFeatures " << t << " " << ptcld_msg->points.size() << endl; ) //berks__old data_map->at( t )->setNumberOfSuccessfullyTrackedFeatures( ptcld_msg->points.size() ); data_map->at( t )->setAsKeyFrame(); ptcld_buf.pop(); #endif } else { if( t > data_map->rbegin()->first ) { __DataManager__data_association_thread__( cout << "ptcld's t was not yet found in datamap. data_map->rbegin()->first=" << data_map->rbegin()->first << " "; cout << "this means a node doesnt exists yet for this ptcld. Usually this does not happen, but it occurs when Image data manager took too long to insert (thread blocking) and in the meantime more poses got available.\n\tI will not pop the queue in this.\n"; ); break; } // try range search // t-delta <= x <= t+delta. x is the map key. __DataManager__data_association_thread__( cout << "\tsince the key was not found (for ptcld) in data_map do range_search\n"; ) // berks__old auto __it = data_map->begin(); for( __it = data_map->begin() ; __it != data_map->end() ; __it++ ) { ros::Duration diff = __it->first-t; if( (diff.sec == 0 && abs(diff.nsec) < 1000000) || (diff.sec == -1 && diff.nsec > (1000000000-1000000) ) ) break; } // berks__old if( __it == data_map->end() ) { cout << TermColor::RED() << "\trange search failed BBB. if this occurs please report to authors (Manohar Kuse)\n"; assert( false && "\tnot fouind\n"); exit(2); } __DataManager__data_association_thread__( cout << "\tfind ptcld->t=" << t << " in data_map\n"; std::cout << "\tinsert ptcld at position " << (__it->first) << '\n'; ) if( true ) { #if 0 data_map.at( __it->first )->setPointCloudFromMsg( ptcld_msg ); data_map.at( __it->first )->setUnVnFromMsg( ptcld_msg ); data_map.at( __it->first )->setUVFromMsg( ptcld_msg ); data_map.at( __it->first )->setTrackedFeatIdsFromMsg( ptcld_msg ); data_map.at( __it->first )->setAsKeyFrame(); ptcld_buf.pop(); #else //berks__old data_map->at( __it->first )->setNumberOfSuccessfullyTrackedFeatures( ptcld_msg->points.size() ); data_map->at( __it->first )->setAsKeyFrame(); ptcld_buf.pop(); #endif } else { __DataManager__data_association_thread__(cout << TermColor::RED() << "[DataManager::data_association_thread] data_map does not seem to contain the t of ptcld_msg. This cannot be happening." << TermColor::RESET() << endl ;) assert( false && "[DataManager::data_association_thread] data_map does not seem to contain the t of ptcld_msg. This cannot be happening\n"); exit(2); } } } // Deal with cam_imu_extrinsic. Store the last in global variable of this class. bool flag = false; nav_msgs::OdometryConstPtr __msg; while( extrinsic_cam_imu_buf.size() > 0 ) { // dump all __msg = extrinsic_cam_imu_buf.front(); extrinsic_cam_imu_buf.pop(); flag = true; } if( flag ) { std::lock_guard lk(global_vars_mutex); PoseManipUtils::geometry_msgs_Pose_to_eigenmat( __msg->pose.pose, imu_T_cam ); imu_T_cam_available = true; imu_T_cam_stamp = __msg->header.stamp; } //--------------------------- Done processing-------------------------- auto loop_end_at = std::chrono::high_resolution_clock::now(); std::chrono::duration ellapsed = loop_end_at-loop_start_at; // sleep_for( requested_loop_time - ellapsed ) int sleep_for = int(requested_loop_time_ms - (float) ellapsed.count()) ; __DataManager__data_association_thread__( cout << "Loop iteration done in "<< ellapsed.count() << " ms; sleep_for=" << sleep_for << " ms" << endl; ) if( sleep_for > 0 ) std::this_thread::sleep_for( std::chrono::milliseconds( sleep_for ) ); else { __DataManager__data_association_thread__( cout << "Queueing in thread `data_association_thread`\n" ; ) ROS_WARN( "Queueing in thread `data_association_thread`. requested_loop_time_ms=%f; elapsed=%f. If this occurs occasionally, it might be because of image_manager thread getting blocked by cleanup thread. It is perfectly normal.", requested_loop_time_ms, ellapsed.count() ); } } cout << TermColor::RED() << "[DataManager::data_association_thread] Finished thread" << TermColor::RESET() << endl; } // will have DataNodes and ImageDataManager to disk for reloading later. // Camera related info is not to be stored. Just assume next time the camera relatives are the same as the map bool DataManager::saveStateToDisk( const string save_folder_name ) { cout << TermColor::GREEN(); cout << "\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"; cout << "^^^^^^^^^^ DataManager::saveStateToDisk ^^^^^^^^^^\n"; cout << "^^^^^^^^^^ DIR=" << save_folder_name ; cout << "\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"; cout << TermColor::RESET(); // TODO: rm -rf save_folder_name ; mkdir save_folder_name string system_cmd0 = string( "rm -rf ") + save_folder_name + " && mkdir "+ save_folder_name; const int rm_dir_err0 = RawFileIO::exec_cmd( system_cmd0 ); if ( rm_dir_err0 == -1 ) { cout << TermColor::RED() << "[DataManager::saveStateToDiskr] Cannot mkdir folder: " << save_folder_name << "!\n" << TermColor::RESET() << endl; cout << "So not saveing state to disk...return false\n"; return false; } // // ---- Save std::map json x_datamap; auto __data_map = this->getDataMapRef(); IOFormat CSVFormat(FullPrecision, DontAlignCols, ", ", "\n"); cout << "data_map to json\n"; int n_isPoseAvailable = 0; int n_isWholeImageDescriptorAvailable = 0; int n_keyframes = 0; for( auto it = __data_map->begin() ; it!= __data_map->end() ; it++ ) { int seq_id = std::distance( __data_map->begin() , it ); // cout << "seq_id=" << seq_id << endl; json curr_obj; // curr_obj["stamp"] = it->first.toSec(); curr_obj["stampNSec"] = it->first.toNSec(); curr_obj["stamp_relative"] = ( it->first - this->getPose0Stamp() ).toSec() ; curr_obj["seq"] = seq_id; DataNode * __u = it->second; if( __u->isPoseAvailable() ) { const Matrix4d wTc = __u->getPose(); json pose_ifo; pose_ifo["rows"] = wTc.rows(); pose_ifo["cols"] = wTc.cols(); // pose_ifo["stamp"] = __u->getT_pose().toSec(); pose_ifo["stampNSec"] = __u->getT_pose().toNSec(); std::stringstream ss; ss << wTc.format(CSVFormat); pose_ifo["data"] = ss.str(); pose_ifo["data_pretty"] = PoseManipUtils::prettyprintMatrix4d(wTc); curr_obj["w_T_c"] = pose_ifo; n_isPoseAvailable++; } if( __u->isWholeImageDescriptorAvailable() ) { const VectorXd wh_img_desc = __u->getWholeImageDescriptor(); json desc_ifo; desc_ifo["rows"] = wh_img_desc.rows(); desc_ifo["cols"] = wh_img_desc.cols(); std::stringstream ss; ss << wh_img_desc.format(CSVFormat); desc_ifo["data"] = ss.str(); curr_obj["wholeImageDescriptor"] = desc_ifo; n_isWholeImageDescriptorAvailable++; } if( __u->isKeyFrame() ) n_keyframes++; curr_obj["isKeyFrame"] = __u->isKeyFrame(); curr_obj["getNumberOfSuccessfullyTrackedFeatures"] = __u->getNumberOfSuccessfullyTrackedFeatures(); curr_obj["isWholeImageDescriptorAvailable"] = __u->isWholeImageDescriptorAvailable(); curr_obj["isPoseAvailable"] = __u->isPoseAvailable(); x_datamap["DataNodes"].push_back( curr_obj ); } cout << "\t n_isPoseAvailable=" << n_isPoseAvailable << endl; cout << "\t n_isWholeImageDescriptorAvailable=" << n_isWholeImageDescriptorAvailable << endl; cout << "\t n_keyframes=" << n_keyframes << endl; cout << "DONE, data_map to json\n"; // // --- Misc Variables in class DataManager // // --- save img_data_mgr // - go over all status and stash all the images that remain on RAM. // - save ImageDataManager::status to json cout << "img_data_mgr->stashAll()\n"; json img_mgr_json = img_data_mgr->stashAll(); cout << "DONE, img_data_mgr->stashAll()\n"; x_datamap["ImageDataManager"] = img_mgr_json; // mv stash dir to `save_folder_name` string system_cmd = string("mv ") + img_data_mgr->getStashDir() + " " + save_folder_name; const int rm_dir_err = RawFileIO::exec_cmd( system_cmd ); if ( rm_dir_err == -1 ) { cout << TermColor::RED() << "[DataManager::saveStateToDiskr] Error moving stash directory!\n" << TermColor::RESET() << endl; } img_data_mgr->set_rm_stash_dir_in_destructor_as_false(); // // --- Save JSON cout << "x_datamap[\"DataNodes\"].size()=" << x_datamap["DataNodes"].size() << endl; cout << "x_datamap[\"ImageDataManager\"].size()=" << x_datamap["ImageDataManager"].size() << endl; RawFileIO::write_string( save_folder_name+"/state.json", x_datamap.dump(4) ); cout << "DONE........ DataManager::saveStateToDisk ^^^^^^^^^^\n"; return true; } bool DataManager::loadStateFromDisk( const string save_folder_name ) { cout << TermColor::GREEN(); cout << "\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"; cout << "^^^^^^^^^^ DataManager::loadStateFromDisk ^^^^^^^^^^\n"; cout << "^^^^^^^^^^ DIR=" << save_folder_name ; cout << "\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"; cout << TermColor::RESET(); //--- // Load JSON string json_fname = save_folder_name+"/state.json"; cout << TermColor::GREEN() << "[DataManager::loadStateFromDisk]Open file: " << json_fname << TermColor::RESET() << endl; std::ifstream json_fileptr(json_fname); if( !json_fileptr ) { ROS_ERROR( "[DataManager::loadStateFromDisk]Cannot load from previous state" ); cout << TermColor::RED() << "[DataManager::loadStateFromDisk]Fail to open state.json file, perhaps it doesnt exist. Cannot load from previous state.\nEXIT(1)"<< endl; exit(1); } json json_obj; json_fileptr >> json_obj; cout << "[DataManager::loadStateFromDisk]Successfully opened file and loaded data "<< json_fname << endl; json_fileptr.close(); //--- // Setup DataNodes int n_datanodes = json_obj["DataNodes"].size(); int n_isPoseAvailable = 0; int n_isWholeImageDescriptorAvailable = 0; int n_keyframes = 0; cout << "[DataManager::loadStateFromDisk]There appear to be " << n_datanodes << " datanodes\n"; for( int i=0 ; isetPose( stamp_pose,__wtc ); n_isPoseAvailable++; } // descriptor if( isWholeImageDescriptorAvailable ) { // setWholeImageDescriptor() VectorXd __wholeImageDescriptor; // cout << "+++\n" << json_obj["DataNodes"][i]["wholeImageDescriptor"] << "\n+++" << endl; bool status = RawFileIO::read_eigen_vector_fromjson( json_obj["DataNodes"][i]["wholeImageDescriptor"], __wholeImageDescriptor ); if( status == false ) { cout << "[DataManager::loadStateFromDisk] RawFileIO::read_eigen_vector_fromjson returned false, this means I cannot convert from json to Matrix4d, perhaps something wrong with json file\n"; exit(1); } // cout << "__wholeImageDescriptor=\n" << __wholeImageDescriptor.transpose() << endl; // exit(1); n->setWholeImageDescriptor( __wholeImageDescriptor ); n_isWholeImageDescriptorAvailable++; } data_map->insert( std::make_pair(stamp, n) ); } cout << "\t n_isPoseAvailable=" << n_isPoseAvailable << endl; cout << "\t n_isWholeImageDescriptorAvailable=" << n_isWholeImageDescriptorAvailable << endl; cout << "\t n_keyframes=" << n_keyframes << endl; //--- // Misc Variables in class DataManager //--- // Setup ImageDataManager // mv folder to /tmp/... string system_cmd = string("cp -r ")+save_folder_name+"/cerebro_stash "+" /tmp"; // string system_cmd = string("mv ")+save_folder_name+"/cerebro_stash "+" /tmp"; cout << TermColor::YELLOW() << "[DataManager::loadStateFromDisk] " << system_cmd << TermColor::RESET() << endl; const int mv_dir_err = RawFileIO::exec_cmd( system_cmd ); if ( mv_dir_err == -1 ) { cout << TermColor::RED() << "[DataManager::loadStateFromDisk] Error moving directory!\n" << TermColor::RESET() << endl; exit(1); } img_data_mgr->initStashDir(false); img_data_mgr->loadStateFromDisk( json_obj["ImageDataManager"] ); } ================================================ FILE: src/DataManager.h ================================================ #pragma once /** DataManager - Inmemory database of data from VINS-estimator This attempts to store all the data from VINS-estimator in a in-memory database. Note that this has multiple threads. The data will be maintained as a std::map with key as timestamp, value as custom class DataNode. Other global data will also be kept. Callbacks are also handled here. Author : Manohar Kuse Created : 3rd Oct, 2017 Major Update : Jun, 2018 Major Update : Oct, 2018 **/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //needed for std::shared_ptr //opencv #include #include #include #include // ros #include #include // ros msg #include #include #include #include #include #include #include // #include #include #include #include #include // Eigen #include #include #include using namespace Eigen; #include #include "PinholeCamera.h" #include "camodocal/camera_models/Camera.h" // this is in include/camodocal. src files in src/utils/camodocal_src #include "camodocal/camera_models/CameraFactory.h" #include "DataNode.h" #include "ImageDataManager.h" #include "utils/nlohmann/json.hpp" using json = nlohmann::json; #include "utils/PoseManipUtils.h" #include "utils/RawFileIO.h" #include "utils/SafeQueue.h" #include "utils/ElapsedTime.h" typedef std::map< ros::Time, DataNode* > t__DataNode; class DataManager { public: DataManager( ros::NodeHandle &nh ); DataManager(const DataManager &obj); // void setCamera( const PinholeCamera& camera ); // PinholeCamera& getCameraRef() { return camera;} void setAbstractCamera( camodocal::CameraPtr abs_camera, short cam_id=0 ); camodocal::CameraPtr getAbstractCameraRef(short cam_id=0) const; bool isAbstractCameraSet(short cam_id=0) const; vector getAbstractCameraKeys() const; void setCameraRelPose( Matrix4d a_T_b, std::pair pair_a_b ); bool isCameraRelPoseSet( std::pair pair_a_b ) const; const Matrix4d& getCameraRelPose( std::pair pair_a_b ) const; vector< std::pair > getCameraRelPoseKeys(); // std::map< ros::Time, DataNode* >& getDataMapRef() { return data_map; } std::shared_ptr< t__DataNode > getDataMapRef() { return data_map; } std::shared_ptr< ImageDataManager > getImageManagerRef() { return img_data_mgr; } const ros::Time getPose0Stamp() const { return pose_0; } bool isPose0Available() const { return pose_0_available; } const Matrix4d& getIMUCamExtrinsic() const; bool isIMUCamExtrinsicAvailable() const; const ros::Time getIMUCamExtrinsicLastUpdated() const; // generally set fname something like /dev/pts/20, output to a separate terminal. // you can know a terminal's device name with the command `tty` void print_datamap_status( string fname ) const; public: //////// /////// Kidnap Indicator Publisher /////// // publish true and publish false. // The timestamps indicate the start of kidnap and end of kidnap respectively. // each of the function will publish messages on '/feature_tracker/rcvd_flag' // and on '/feature_tracker/rcvd_flag_header' bool isKidnapIndicatorPubSet() const; void setKidnapIndicatorPublishers( ros::Publisher& pub_bool, ros::Publisher& pub_header ); void PUBLISH__TRUE( const ros::Time _t ) const; void PUBLISH__FALSE( const ros::Time _t ) const; private: ros::Publisher rcvd_flag_pub; ros::Publisher kidnap_indicator_header_pub; bool is_kidnapn_indicator_set = false; public: //////// //////// Write Data //////// // returns string as a json. contains everything including wTc, wX, uv, K, D etc. json asJson() ; bool saveStateToDisk( const string save_folder_name ); bool loadStateFromDisk( const string save_folder_name ); private: ///////// ///////// Global Variables ///////// std::map< int, camodocal::CameraPtr > all_abstract_cameras; std::map< std::pair, Matrix4d > cam_relative_poses; //< pair:a,b then a_T_b ros::NodeHandle nh; //< Node Handle, TODO Not sure why this will be needed here. consider removing it from here. // const std::ofstream &out_stream; // std::map< ros::Time, DataNode* > data_map; //original std::shared_ptr< t__DataNode > data_map = std::make_shared(); std::shared_ptr< ImageDataManager> img_data_mgr = std::make_shared(); bool pose_0_available = false; ros::Time pose_0; // time of 1st pose Matrix4d imu_T_cam = Matrix4d::Identity(); bool imu_T_cam_available = false; ros::Time imu_T_cam_stamp; mutable std::mutex global_vars_mutex; public: ///////// ///////// Callbacks ///////// void camera_pose_callback( const nav_msgs::Odometry::ConstPtr msg ); ///< w_T_c. pose of camera in the world-cordinate system. All the cameras. only a subset of this will be keyframes void keyframe_pose_callback( const nav_msgs::Odometry::ConstPtr msg ); //X /// pose of imu at keyframes. Use it just as a marker, dont use the poses. void raw_image_callback( const sensor_msgs::ImageConstPtr& msg ); void raw_image_callback_1( const sensor_msgs::ImageConstPtr& msg ); void depth_image_callback( const sensor_msgs::ImageConstPtr& msg ); void extrinsic_cam_imu_callback( const nav_msgs::Odometry::ConstPtr msg ); void ptcld_callback( const sensor_msgs::PointCloud::ConstPtr msg ); void tracked_feat_callback( const sensor_msgs::PointCloud::ConstPtr msg ); //X private: // double last_image_time=-1; ros::Time last_image_time = ros::Time(); // callback-buffers std::queue img_buf; std::queue img_1_buf; std::queue depth_im_buf; std::queue pose_buf; std::queue kf_pose_buf; std::queue ptcld_buf; std::queue trackedfeat_buf; std::queue extrinsic_cam_imu_buf; string print_queue_size(int verbose ) const; ///////// ///////// Threads ///////// public: // This threads monitors the buffer queues and sync them all in a std::map which is indexed with time and holds all the data void data_association_thread( int max_loop_rate_in_hz ); void data_association_thread_enable() { b_data_association_thread = true; } void data_association_thread_disable() { b_data_association_thread = false; } private: atomic b_data_association_thread; public: // Just a trial thread void trial_thread(); void trial_thread_enable() { b_trial_thread = true; } void trial_thread_disable() { b_trial_thread = false; } private: atomic b_trial_thread; public: // Thread to deallocate images which have no pose info (aka useless images) void clean_up_useless_images_thread(); void clean_up_useless_images_thread_enable() { b_clean_up_useless_images_thread = true; } void clean_up_useless_images_thread_disable() { b_clean_up_useless_images_thread = false; } private: atomic b_clean_up_useless_images_thread; }; ================================================ FILE: src/DataNode.cpp ================================================ #include "DataNode.h" #if 0 void DataNode::setImageFromMsg( const sensor_msgs::ImageConstPtr msg ) { std::lock_guard lk(m); auto tmp = cv_bridge::toCvShare(msg, "bgr8" )->image; // this->image = cv_bridge::toCvShare(msg, "bgr8" )->image; // this->image = (cv_bridge::toCvCopy(msg, "bgr8" )->image).clone(); // this->image = (cv_bridge::toCvShare(msg, "bgr8" )->image).clone(); this->image = tmp.clone(); tmp.release(); // // Note: (on Opencv and cv_bridge, memory) // I was observing a massive memory leak when not using clone(). // See [this link](https://answers.opencv.org/question/14285/how-to-free-memory-through-cvmat/) // So, the point is, if using cv::Mat, it is important to understand who owns the memory. // cv::Mat::clone() does a deep copy. it is important to .clone here for // the deallocation to work correctly. assert( image.rows > 0 && image.cols > 0 && !image.empty() && "In DataNode::setImageFromMsg image that is being set from sensor_msgs::Image is invalid."); this->t_image = msg->header.stamp; this->m_image = true; } void DataNode::setImageFromMsg( const sensor_msgs::ImageConstPtr msg, short cam_id ) { std::lock_guard lk(m); // cv::Mat __image = cv_bridge::toCvShare(msg, "bgr8" )->image; cv::Mat __image = (cv_bridge::toCvCopy(msg, "bgr8" )->image).clone(); this->all_images[cam_id] = __image; // // Note: (on Opencv and cv_bridge, memory) // I was observing a massive memory leak when not using clone(). // See [this link](https://answers.opencv.org/question/14285/how-to-free-memory-through-cvmat/) // So, the point is, if using cv::Mat, it is important to understand who owns the memory. // cv::Mat::clone() does a deep copy. it is important to .clone here for // the deallocation to work correctly. assert( this->all_images.at(cam_id).rows > 0 && this->all_images.at(cam_id).cols > 0 && ! this->all_images.at(cam_id).empty() && "In DataNode::setImageFromMsg with cam_id image that is being set from sensor_msgs::Image is invalid."); this->t_all_images[cam_id] = msg->header.stamp; __image.release(); } void DataNode::print_image_cvinfo() { std::lock_guard lk(m); cout << "[DataNode::print_image_cvinfo]\n"; if( m_image ) { cout << "[DataNode::print_image_cvinfo] main image: " << MiscUtils::cvmat_info( image ) << endl; cout << TermColor::iBLUE() << "[DataNode::print_image_cvinfo] refcount=" << image.u->refcount << TermColor::RESET() << endl; } for( auto it=all_images.begin() ; it!=all_images.end() ; it++ ) { cout << TermColor::iBLUE() << "[DataNode::print_image_cvinfo] image#" << it->first << " refcount=" << it->second.u->refcount << TermColor::RESET() << endl; } cout << "[DataNode::print_image_cvinfo] Done\n"; } // #define __DATANODE__deallocate_all_images( msg ) msg; #define __DATANODE__deallocate_all_images( msg ) ; void DataNode::deallocate_all_images() { __DATANODE__deallocate_all_images( cout << "[DataNode::deallocate_all_images] getT= " << getT() << " getT_image()="<< getT_image() << "\n"; ) std::lock_guard lk(m); // deallocate main image if( m_image ) { image.release(); image.deallocate(); t_image = ros::Time(); m_image = false; } // cout << "[DataNode::deallocate_all_images] After Deallocate image.u->refcount=" << image.u->refcount << endl; // deallocate other images with cam_id for( auto it=all_images.begin() ; it!=all_images.end() ; it++ ) { it->second.release(); it->second.deallocate(); // cout << "[DataNode::deallocate_all_images] image# " << it->first << ", After Deallocate it->second.u->refcount=" << image.u->refcount << endl; } all_images.clear(); t_all_images.clear(); } #endif void DataNode::setPoseFromMsg( const nav_msgs::OdometryConstPtr msg ) { std::lock_guard lk(m); PoseManipUtils::geometry_msgs_Pose_to_eigenmat( msg->pose.pose, wTc ); // vector _q = msg->pose.covariance ; wTc_covariance = MatrixXd::Zero(6,6); for( int i=0 ; i<36 ; i++ ) wTc_covariance(int(i/6), i%6) = msg->pose.covariance[i]; t_wTc = msg->header.stamp; m_wTc = true; } void DataNode::setPose( const Matrix4d __wTc ) { std::lock_guard lk(m); wTc_covariance = MatrixXd::Zero(6,6); wTc = Matrix4d( __wTc ); m_wTc = true; } void DataNode::setPose( const ros::Time __t, const Matrix4d __wTc ) { std::lock_guard lk(m); wTc_covariance = MatrixXd::Zero(6,6); wTc = Matrix4d( __wTc ); t_wTc = __t; m_wTc = true; } void DataNode::setPointCloudFromMsg( const sensor_msgs::PointCloudConstPtr msg ) { std::lock_guard lk(m); int n_pts = msg->points.size() ; if( n_pts == 0 ) { // cout << TermColor::RED() << "[DataNode::setPointCloudFromMsg]npts=0" << TermColor::RESET() << endl; t_ptcld = msg->header.stamp; m_ptcld = true; m_ptcld_zero_pts = true; return ; } assert( n_pts > 0 && "[DataNode::setPointCloudFromMsg] The input pointcloud msg contains zero 3d points, ie. msg->points\n"); ptcld = MatrixXd::Zero( 4, n_pts ); for( int i=0 ; ipoints[i].x; ptcld( 1, i ) = msg->points[i].y; ptcld( 2, i ) = msg->points[i].z; ptcld( 3, i ) = 1.0; } t_ptcld = msg->header.stamp; m_ptcld = true; } void DataNode::setUnVnFromMsg( const sensor_msgs::PointCloudConstPtr msg ) { std::lock_guard lk(m); int n_pts = msg->channels.size() ; if( n_pts == 0 ) { // cout << TermColor::RED() << "[DataNode::setUnVnFromMsg]npts=0" << TermColor::RESET() << endl; t_unvn = msg->header.stamp; m_unvn = true; m_unvn_zero_pts = true; return ; } assert( n_pts > 0 && "[DataNode::setUnVnFromMsg] The input pointcloud msg contains zero unvn points, ie. msg->channels\n"); unvn = MatrixXd::Zero( 3, n_pts ); for( int i=0 ; ichannels[i].values[0]; unvn( 1, i ) = msg->channels[i].values[1]; unvn( 2, i ) = 1.0; } t_unvn = msg->header.stamp; m_unvn = true; } void DataNode::setUVFromMsg( const sensor_msgs::PointCloudConstPtr msg ) { std::lock_guard lk(m); int n_pts = msg->channels.size() ; if( n_pts == 0 ) { // cout << TermColor::RED() << "[DataNode::setUVFromMsg]npts=0" << TermColor::RESET() << endl; t_uv = msg->header.stamp; m_uv = true; m_uv_zero_pts = true; return ; } assert( n_pts > 0 && "[DataNode::setUVFromMsg] The input pointcloud msg contains zero uv points, ie. msg->channels\n"); uv = MatrixXd::Zero( 3, n_pts ); for( int i=0 ; ichannels[i].values[2]; uv( 1, i ) = msg->channels[i].values[3]; uv( 2, i ) = 1.0; } t_uv = msg->header.stamp; m_uv = true; } void DataNode::setTrackedFeatIdsFromMsg( const sensor_msgs::PointCloudConstPtr msg ) { std::lock_guard lk(m); int n_pts = msg->channels.size() ; if( n_pts == 0 ) { // cout << TermColor::RED() << "[DataNode::setTrackedFeatIdsFromMsg]npts=0" << TermColor::RESET() << endl; t_tracked_feat_ids = msg->header.stamp; m_tracked_feat_ids = true; m_tracked_feat_ids_zero_pts = true; return ; } assert( n_pts > 0 && "[DataNode::setTrackedFeatIdsFromMsg] The input pointcloud msg contains zero tracked feature point ids, ie. msg->channels\n"); tracked_feat_ids = VectorXi::Zero( n_pts ); for( int i=0 ; ichannels[i].values[4] ); } t_tracked_feat_ids = msg->header.stamp; m_tracked_feat_ids = true; } void DataNode::setNumberOfSuccessfullyTrackedFeatures( int n ) { std::lock_guard lk(m); assert( n >= 0 && "[DataNode::setNumberOfSuccessfullyTrackedFeatures] you cannot set a negative value"); numberOfSuccessfullyTrackedFeatures = n; } int DataNode::getNumberOfSuccessfullyTrackedFeatures() const { std::lock_guard lk(m); return numberOfSuccessfullyTrackedFeatures; } #if 0 const cv::Mat& DataNode::getImage() const { std::lock_guard lk(m); assert( isImageAvailable() && "[DataNode::getImage] you requested the image before setting it"); return this->image; } const cv::Mat& DataNode::getImage(short cam_id) const { std::lock_guard lk(m); assert( isImageAvailable(cam_id) && "[DataNode::getImage with cam_id] you requested the image before setting it"); // return this->all_images[cam_id]; return this->all_images.at(cam_id); } #endif const Matrix4d& DataNode::getPose() const{ std::lock_guard lk(m); assert( m_wTc && "[DataNode::getPose] you requested the pose before setting it"); #if 0 if( !m_wTc ) { cout << "ASSERT ERROR " << "[DataNode::getPose] you requested the pose before setting it\n"; } #endif return wTc; } const MatrixXd& DataNode::getPoseCovariance() const { std::lock_guard lk(m); assert( m_wTc && "[DataNode::getPoseCovariance] you requested the pose-cov before setting it"); return wTc_covariance; } const MatrixXd& DataNode::getPointCloud() const { std::lock_guard lk(m); assert( m_ptcld && "[DataNode::getPointCloud] you requested the pointcloud before setting it"); return ptcld; } const MatrixXd& DataNode::getUnVn() const { // returns a 3xN matrix std::lock_guard lk(m); assert( m_unvn && "[DataNode::getUnVn] you requested UnVn before setting it.\n" ); return unvn; } const MatrixXd& DataNode::getUV() const { // returns a 3xN matrix std::lock_guard lk(m); assert( m_unvn && "[DataNode::getUV] you requested UnVn before setting it.\n" ); return uv; } const VectorXi& DataNode::getFeatIds() const { // return a N-vector std::lock_guard lk(m); assert( m_unvn && "[DataNode::getFeatIds] you requested UnVn before setting it.\n" ); return tracked_feat_ids; } int DataNode::nPts() const { std::lock_guard lk(m); #if 0 if( m_tracked_feat_ids == false || m_tracked_feat_ids_zero_pts == true ) return 0; return tracked_feat_ids.size(); #endif } const ros::Time DataNode::getT() const { std::lock_guard lk(m); return stamp; } #if 0 const ros::Time DataNode::getT_image() const { std::lock_guard lk(m); assert( isImageAvailable() ); return t_image; } const ros::Time DataNode::getT_image(short cam_id) const { std::lock_guard lk(m); assert( isImageAvailable(cam_id) ); // return t_all_images[cam_id]; return t_all_images.at(cam_id); } #endif const ros::Time DataNode::getT_pose() const { std::lock_guard lk(m); return t_wTc; } const ros::Time DataNode::getT_ptcld() const { std::lock_guard lk(m); return t_ptcld; } const ros::Time DataNode::getT_unvn() const { std::lock_guard lk(m); return t_unvn; } const ros::Time DataNode::getT_uv() const { std::lock_guard lk(m); return t_uv; } void DataNode::prettyPrint() { if( this->isKeyFrame() ) cout << "\033[1;32m"; else cout << "\033[1;31m";; cout << "\t---DataNode"; cout << "\tt="<< this->getT() ; cout << "\tkeyFrame=" << (this->isKeyFrame()?"YES":"NO") << endl ; cout << "\t\twholeImageDesc: "; if( this->isWholeImageDescriptorAvailable() ) { cout << " available and of size: " << this->getWholeImageDescriptor().size() << endl; } else { cout << "Not Available\n" ;} #if 0 if( isImageAvailable() ) { cv::Mat _im = this->getImage(); cout << "\t\tImage: " << _im.rows << "x" << _im.cols << "x" << _im.channels() << " " << MiscUtils::type2str( _im.type() ) << "\tt=" << t_image << endl; } else { cout << "\tImage: N/A\n"; } #endif if( isPoseAvailable() ) { Matrix4d _pose = this->getPose(); cout << "\t\tPose : " << PoseManipUtils::prettyprintMatrix4d( _pose ) << "\tt=" << t_wTc << endl; } else { cout << "\t\tPose: N/A\n"; } if( isPtCldAvailable() ) { MatrixXd _ptcld = this->getPointCloud(); cout << "\t\tPointCloud : " << _ptcld.rows() << "x" << _ptcld.cols() << "\tt=" << t_ptcld << endl; } else { cout << "\t\tPointCloud : N/A\n"; } if( isUnVnAvailable() ) { MatrixXd _unvn = this->getUnVn(); cout << "\t\tUnVn : " << _unvn.rows() << "x" << _unvn.cols() << "\tt=" << t_unvn << endl; } else { cout << "\t\tUnVn : N/A\n"; } if( isUVAvailable() ) { MatrixXd _uv = this->getUV(); cout << "\t\tUV : " << _uv.rows() << "x" << _uv.cols() << "\tt=" << t_uv << endl; } else { cout << "\t\tUV : N/A\n"; } if( isFeatIdsAvailable() ) { VectorXi _ids = this->getFeatIds(); cout << "\t\tFeatIds : " << _ids.rows() << "x" << _ids.cols() << "\tt=" << t_tracked_feat_ids << endl; } else { cout << "\t\tFeatIds : N/A\n"; } cout << "\033[0m\n"; } void DataNode::setWholeImageDescriptor( VectorXd vec ) { std::lock_guard lk(m); assert( !m_img_desc && "[DataNode::setWholeImageDescriptor] You are trying to set image desc. The descriptor is already set and this action might overwrite the exisiting descriptor. If you think this is not an error feel free to comment this assert\n" ); img_desc = vec; m_img_desc = true; } // const VectorXd& DataNode::getWholeImageDescriptor() const VectorXd DataNode::getWholeImageDescriptor() const { std::lock_guard lk(m); assert( m_img_desc && "[DataNode::getWholeImageDescriptor] You are trying to get the image descriptor before setting it." ); return img_desc; } ================================================ FILE: src/DataNode.h ================================================ #pragma once /** This class holds data at 1 instance of time. Author : Manohar Kuse Created : 27th Oct, 2018 **/ #include #include #include #include //opencv #include #include #include #include // ros #include #include // ros msg #include #include #include #include #include #include #include // #include #include #include #include // Eigen #include #include #include using namespace Eigen; #include #include "utils/PoseManipUtils.h" #include "utils/MiscUtils.h" #include "utils/TermColor.h" class DataNode { public: DataNode( ros::Time stamp ): stamp(stamp) { is_key_frame = false; // m_image=false; m_wTc=false; } #if 0 void setImageFromMsg( const sensor_msgs::ImageConstPtr msg ); ///< this sets the primary image void setImageFromMsg( const sensor_msgs::ImageConstPtr msg, short cam_id ); ///< this sets additional image. multiple additional images by Node is possible by using ids #endif void setPoseFromMsg( const nav_msgs::OdometryConstPtr msg ); void setPose( const Matrix4d __wTc ); void setPose( const ros::Time __t, const Matrix4d __wTc ); void setPointCloudFromMsg( const sensor_msgs::PointCloudConstPtr msg ); // uses msg->points[ \forall i].x, .y, .z void setUnVnFromMsg( const sensor_msgs::PointCloudConstPtr msg ); void setUVFromMsg( const sensor_msgs::PointCloudConstPtr msg ); void setTrackedFeatIdsFromMsg( const sensor_msgs::PointCloudConstPtr msg ); void setAsKeyFrame() { is_key_frame = true; } void unsetAsKeyFrame() { is_key_frame = false; } // This is intended to indicate the number of tracked features from /feature_tracker. // TODO: In the future if need be implement to save the tracked points. For now I am not retaining those void setNumberOfSuccessfullyTrackedFeatures( int n ); int getNumberOfSuccessfullyTrackedFeatures() const; bool isKeyFrame() const{ return (bool)is_key_frame; } #if 0 bool isImageAvailable() const { return m_image; } bool isImageAvailable(short cam_id) const { return ((t_all_images.count(cam_id)>0)?true:false); } #endif bool isPoseAvailable() const { return m_wTc; } bool isPtCldAvailable() const { return m_ptcld; } bool isUnVnAvailable() const{ return m_unvn; } bool isUVAvailable() const { return m_uv; } bool isFeatIdsAvailable() const { return m_tracked_feat_ids; } #if 0 const cv::Mat& getImage() const; //< this will give out the default image. const cv::Mat& getImage(short cam_id) const ; // this will give out the image of the cam_id. cam_id=0 will have issues. if you want the default camera image do not pass any argument, which will result in the above call. #endif const Matrix4d& getPose() const ; const MatrixXd& getPoseCovariance() const ; //6x6 matrix const MatrixXd& getPointCloud() const ; // returns a 4xN matrix const MatrixXd& getUnVn() const ; // returns a 3xN matrix const MatrixXd& getUV() const ; // returns a 3xN matrix const VectorXi& getFeatIds() const; // return a N-vector int nPts() const; const ros::Time getT() const; #if 0 const ros::Time getT_image() const; const ros::Time getT_image(short cam_id) const; #endif const ros::Time getT_pose() const; const ros::Time getT_ptcld() const; const ros::Time getT_unvn() const ; const ros::Time getT_uv() const ; // Whole Image descriptors setter and getter void setWholeImageDescriptor( VectorXd vec ); // const VectorXd& getWholeImageDescriptor(); const VectorXd getWholeImageDescriptor() const; //don't know which one is more suitated to me. :( bool isWholeImageDescriptorAvailable() const { return m_img_desc; } void prettyPrint(); #if 0 void deallocate_all_images(); void print_image_cvinfo(); #endif private: const ros::Time stamp; mutable std::mutex m; // bool is_key_frame = false; std::atomic is_key_frame; #if 0 // Raw Image cv::Mat image; ros::Time t_image; // bool m_image=false; // TODO better make this atomic std::atomic m_image; // Additional Raw Images std::map all_images; std::map t_all_images; #endif // Pose (odometry pose from vins estimator) Matrix4d wTc; MatrixXd wTc_covariance; // 6x6 ros::Time t_wTc; // bool m_wTc=false; std::atomic m_wTc; // point cloud (3d data) MatrixXd ptcld; ros::Time t_ptcld; std::atomic m_ptcld; std::atomic m_ptcld_zero_pts; // unvn - imaged points in normalized cords MatrixXd unvn; ros::Time t_unvn; std::atomic m_unvn; std::atomic m_unvn_zero_pts; // uv - imaged points in observed cords. MatrixXd uv; ros::Time t_uv; std::atomic m_uv; std::atomic m_uv_zero_pts; // tracked feat ids VectorXi tracked_feat_ids; ros::Time t_tracked_feat_ids; std::atomic m_tracked_feat_ids; std::atomic m_tracked_feat_ids_zero_pts; int numberOfSuccessfullyTrackedFeatures = -1; // Whole Image Descriptor VectorXd img_desc; bool m_img_desc = false; }; ================================================ FILE: src/DlsPnpWithRansac.cpp ================================================ #include "DlsPnpWithRansac.h" #ifdef __USE_THEIASFM // Theia's ICP // [Input] // uv_X: a 3d point cloud expressed in some frame-of-ref, call it frame-of-ref of `uv` // uvd_Y: a 3d point cloud expressed in another frame-of-ref, call it frame-of-ref of `uvd` // [Output] // uvd_T_uv: Relative pose between the point clouds // [Note] // uv_X <---> uvd_Y // #define ____P3P_ICP_( msg ) msg; #define ____P3P_ICP_( msg ) ; float StaticTheiaPoseCompute::P3P_ICP( const vector& uv_X, const vector& uvd_Y, Matrix4d& uvd_T_uv, string & p3p__msg ) { if( uv_X.size() < 20 ) { ____P3P_ICP_( cout << "[StaticTheiaPoseCompute::P3P_ICP] Too few input points. You provided " << uv_X.size() << endl; ) return -1; // if you give me too few points, i return error. } // call this theia::AlignPointCloudsUmeyamaWithWeights // void AlignPointCloudsUmeyamaWithWeights( // const std::vector& left, // const std::vector& right, // const std::vector& weights, Eigen::Matrix3d* rotation, // Eigen::Vector3d* translation, double* scale) p3p__msg = ""; Matrix3d ____R; Vector3d ____t; double ___s=1.0; ElapsedTime timer; timer.tic(); #if 0 theia::AlignPointCloudsUmeyama( uv_X, uvd_Y, &____R, &____t, &___s ); // all weights = 1. TODO: ideally weights could be proportion to point's Z. // theia::AlignPointCloudsICP( uv_X, uvd_Y, &____R, &____t ); // all weights = 1. TODO: ideally weights could be proportion to point's Z. double elapsed_time_p3p = timer.toc_milli(); ____P3P_ICP_( cout << "___R:\n" << ____R << endl; cout << "___t: " << ____t.transpose() << endl; cout << "___s: " << ___s << endl; ) uvd_T_uv = Matrix4d::Identity(); uvd_T_uv.topLeftCorner<3,3>() = ____R; uvd_T_uv.col(3).topRows(3) = ____t; ____P3P_ICP_( cout << TermColor::GREEN() << "p3p done in (ms) " << elapsed_time_p3p << " p3p_ICP: {uvd}_T_{uv} : " << PoseManipUtils::prettyprintMatrix4d( uvd_T_uv ) << TermColor::RESET() << endl; ) if( min( ___s, 1.0/___s ) < 0.9 ) { cout << TermColor::RED() << "theia::AlignPointCloudsUmeyama scales doesn't look good;this usually implies that estimation is bad. scale= " << ___s << endl; p3p__msg += "p3p_ICP: scale=" +to_string( ___s )+" {uvd}_T_{uv} : " + PoseManipUtils::prettyprintMatrix4d( uvd_T_uv ); p3p__msg += "p3p done in (ms)" + to_string(elapsed_time_p3p)+"; theia::AlignPointCloudsUmeyama scales doesn't look good, this usually implies that estimation is bad. scale= " + to_string(___s); return -1; } p3p__msg += "p3p done in (ms)" + to_string(elapsed_time_p3p)+"; p3p_ICP: {uvd}_T_{uv} : " + PoseManipUtils::prettyprintMatrix4d( uvd_T_uv ); p3p__msg += ";weight="+to_string( min( ___s, 1.0/___s ) ); return min( ___s, 1.0/___s ); #endif // with ransac timer.tic(); vector data_r; for( int i=0 ; i ransac_estimator( params, icp_estimator); ransac_estimator.Initialize(); theia::RansacSummary summary; ransac_estimator.Estimate(data_r, &best_rel_pose, &summary); auto elapsed_time_p3p_ransac = timer.toc_milli(); uvd_T_uv = Matrix4d::Identity(); uvd_T_uv = best_rel_pose.b_T_a ; ____P3P_ICP_( cout << TermColor::iGREEN() << "ICP Ransac:"; // for( int i=0; i& w_X, const std::vector& c_uv_normalized, Matrix4d& c_T_w, string& pnp__msg ) { if( w_X.size() < 20 ) { ___P_N_P__( cout << "[StaticTheiaPoseCompute::PNP] Too few input points. You provided " << w_X.size() << endl; ) return -1; // if you give me too few points, i return error. } pnp__msg = ""; ElapsedTime timer; #if 0 //--- DlsPnp std::vector solution_rotations; std::vector solution_translations; timer.tic(); theia::DlsPnp( c_uv_normalized, w_X, &solution_rotations, &solution_translations ); auto elapsed_dls_pnp = timer.toc_milli() ; ___P_N_P__( cout << elapsed_dls_pnp << " : (ms) : theia::DlsPnp done in\n"; cout << "solutions count = " << solution_rotations.size() << " " << solution_translations.size() << endl; ) if( solution_rotations.size() == 0 ) { ___P_N_P__( cout << TermColor::RED() << " theia::DlsPnp returns no solution" << TermColor::RESET() << endl; ) pnp__msg = " theia::DlsPnp returns no solution"; return -1.; } if( solution_rotations.size() > 1 ) { ___P_N_P__( cout << TermColor::RED() << " theia::DlsPnp returns multiple solution" << TermColor::RESET() << endl; ) pnp__msg = " theia::DlsPnp returns multiple solution"; return -1.; } // retrive solution c_T_w = Matrix4d::Identity(); c_T_w.topLeftCorner(3,3) = solution_rotations[0].toRotationMatrix(); c_T_w.col(3).topRows(3) = solution_translations[0]; ___P_N_P__( // cout << "solution_T " << b_T_a << endl; cout << TermColor::GREEN() << "DlsPnp (c_T_w): " << PoseManipUtils::prettyprintMatrix4d( c_T_w ) << TermColor::RESET() << endl; // out_b_T_a = b_T_a; ) pnp__msg += "DlsPnp (c_T_w): " + PoseManipUtils::prettyprintMatrix4d( c_T_w ) + ";"; pnp__msg += " elapsed_dls_pnp_ms="+to_string(elapsed_dls_pnp)+";"; // return 1.0; #endif #if 1 //--- DlsPnpWithRansac timer.tic(); // prep data vector data_r; for( int i=0 ; i ransac_estimator(params, dlspnp_estimator); // Initialize must always be called! ransac_estimator.Initialize(); theia::RansacSummary summary; ransac_estimator.Estimate(data_r, &best_rel_pose, &summary); auto elapsed_dls_pnp_ransac=timer.toc_milli(); ___P_N_P__( cout << elapsed_dls_pnp_ransac << "(ms)!! DlsPnpWithRansac ElapsedTime includes data prep\n"; cout << "Ransac:"; // for( int i=0; i& w_X, const std::vector& c_uv_normalized, Matrix4d& c_T_w, string& pnp__msg ) { assert( w_X.size() == c_uv_normalized.size() && w_X.size() > 8 ); if( w_X.size() != c_uv_normalized.size() ) { cout << "[StaticCeresPoseCompute::PNP] w_X.size() != c_uv_normalized.size()\n"; exit( 1 ); } __StaticCeresPoseCompute_PNP_( cout << "w_X.size()=" << w_X.size() << " c_uv_normalized.size()=" << c_uv_normalized.size() << endl; ) // setup ceres problem for PNP // minimize_{R,t} \sum_i ( PI( c_(R|t)_w * w_X[i] ) - u[i] ) ceres::Problem problem; // optimization variables double ypr[3], tr[3]; PoseManipUtils::eigenmat_to_rawyprt( c_T_w, ypr, tr ); // double yaw=ypr[0], pitch=ypr[1], roll=ypr[2], tx=tr[0], ty=tr[1], tz=tr[2]; for( int i=0 ; i " << c_uv_normalized[i].transpose() << endl; ceres::CostFunction * cost_function = PNPEulerAngleError::Create( w_X[i], c_uv_normalized[i] ); problem.AddResidualBlock( cost_function, new ceres::HuberLoss(0.1), &ypr[0], &ypr[1], &ypr[2], &tr[0], &tr[1], &tr[2] ); // problem.AddResidualBlock( cost_function, NULL, &ypr[0], &ypr[1], &ypr[2], &tr[0], &tr[1], &tr[2] ); } // Local Parameterization (Angle) ceres::LocalParameterization *angle_parameterization = AngleLocalParameterization::Create(); problem.SetParameterization( &ypr[0], angle_parameterization ); problem.SetParameterization( &ypr[1], angle_parameterization ); problem.SetParameterization( &ypr[2], angle_parameterization ); // Set as constant problem.SetParameterBlockConstant( &ypr[1] ); //pitch problem.SetParameterBlockConstant( &ypr[2] ); //roll ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_SCHUR; options.minimizer_progress_to_stdout = false; // options.trust_region_strategy_type = ceres::DOGLEG; // options.dogleg_type = ceres::SUBSPACE_DOGLEG; ceres::Solver::Summary summary; __StaticCeresPoseCompute_PNP_( cout << "yaw,pitch,roll=" << ypr[0] << " " << ypr[1] << " " << ypr[2] << "; tx,ty,tz=" << tr[0] << " "<< tr[1] << " " << tr[2] << endl; cout << "Ceres::Solve()\n"; ) pnp__msg += "initial_guess: "+PoseManipUtils::prettyprintMatrix4d( c_T_w ) + ";"; ceres::Solve(options, &problem, &summary); __StaticCeresPoseCompute_PNP_( cout << "yaw,pitch,roll=" << ypr[0] << " " << ypr[1] << " " << ypr[2] << "; tx,ty,tz=" << tr[0] << " "<< tr[1] << " " << tr[2] << endl; ) // cout << summary.FullReport() << endl; cout << summary.BriefReport() << endl; pnp__msg += summary.BriefReport() + ";"; PoseManipUtils::rawyprt_to_eigenmat( ypr, tr, c_T_w ); pnp__msg += "final: "+PoseManipUtils::prettyprintMatrix4d( c_T_w ) + ";"; return 1.0; } #define __StaticCeresPoseCompute_P3P_(msg) msg; // #define __StaticCeresPoseCompute_P3P_(msg) ; float StaticCeresPoseCompute::P3P_ICP( const std::vector& a_X, const std::vector& b_X, Matrix4d& b_T_a, string& p3p__msg ) { assert( a_X.size() == b_X.size() && a_X.size() > 8 ); if( a_X.size() != b_X.size() ) { cout << "[StaticCeresPoseCompute::P3P] a_X.size() != b_X.size()\n"; exit( 1 ); } __StaticCeresPoseCompute_P3P_( cout << "a_X.size()=" << a_X.size() << " b_X.size()=" << b_X.size() << endl; ) // setup ceres problem for PNP // minimize_{R,t} \sum_i ( c_(R|t)_w * a_X[i] - b_X[i] ) ceres::Problem problem; // optimization variables double ypr[3], tr[3]; PoseManipUtils::eigenmat_to_rawyprt( b_T_a, ypr, tr ); // double yaw=ypr[0], pitch=ypr[1], roll=ypr[2], tx=tr[0], ty=tr[1], tz=tr[2]; for( int i=0 ; i " << b_X[i].transpose() << endl; ceres::CostFunction * cost_function = P3PEulerAngleError::Create( a_X[i], b_X[i] ); problem.AddResidualBlock( cost_function, new ceres::HuberLoss(0.1), &ypr[0], &ypr[1], &ypr[2], &tr[0], &tr[1], &tr[2] ); // problem.AddResidualBlock( cost_function, NULL, &ypr[0], &ypr[1], &ypr[2], &tr[0], &tr[1], &tr[2] ); } // Local Parameterization (Angle) ceres::LocalParameterization *angle_parameterization = AngleLocalParameterization::Create(); problem.SetParameterization( &ypr[0], angle_parameterization ); problem.SetParameterization( &ypr[1], angle_parameterization ); problem.SetParameterization( &ypr[2], angle_parameterization ); // Set as constant problem.SetParameterBlockConstant( &ypr[1] ); //pitch problem.SetParameterBlockConstant( &ypr[2] ); //roll ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_SCHUR; options.minimizer_progress_to_stdout = false; // options.trust_region_strategy_type = ceres::DOGLEG; // options.dogleg_type = ceres::SUBSPACE_DOGLEG; ceres::Solver::Summary summary; __StaticCeresPoseCompute_P3P_( cout << "yaw,pitch,roll=" << ypr[0] << " " << ypr[1] << " " << ypr[2] << "; tx,ty,tz=" << tr[0] << " "<< tr[1] << " " << tr[2] << endl; cout << "Ceres::Solve()\n"; ) p3p__msg += "initial bTa: "+PoseManipUtils::prettyprintMatrix4d( b_T_a ) + ";"; ceres::Solve(options, &problem, &summary); __StaticCeresPoseCompute_P3P_( cout << "yaw,pitch,roll=" << ypr[0] << " " << ypr[1] << " " << ypr[2] << "; tx,ty,tz=" << tr[0] << " "<< tr[1] << " " << tr[2] << endl; ) __StaticCeresPoseCompute_P3P_( // cout << summary.FullReport() << endl; cout << summary.BriefReport() << endl; ) p3p__msg += summary.BriefReport() + ";"; PoseManipUtils::rawyprt_to_eigenmat( ypr, tr, b_T_a ); p3p__msg += "final: "+PoseManipUtils::prettyprintMatrix4d( b_T_a ) + ";"; return 1.0; } ================================================ FILE: src/DlsPnpWithRansac.h ================================================ #pragma once /* This is a RanSAC wrapper for theia::DlsPnp. Based on sample code on theia-sfm website. The official website has some obivious issues. Author : Manohar Kuse Created : 4th Jan, 2018 */ #include "utils/TermColor.h" #include "utils/ElapsedTime.h" #include "utils/PoseManipUtils.h" #include #include #include #include "PNPCeresCostFunctions.h" using namespace Eigen; using namespace std; #define __USE_THEIASFM //comment this out to compile with theia-sfm #ifdef __USE_THEIASFM #include /////////// DlsPnp-RANSAC /////////////////// // Data struct CorrespondencePair_3d2d { Vector3d a_X; // 3d point expressed in co-ordinate system of `image-a` Vector2d uv_d; //feature point in normalized-image co-ordinates. of the other view (b) }; // Model struct RelativePose { Matrix4d b_T_a; }; class DlsPnpWithRansac: public theia::Estimator { public: // Number of points needed to estimate a line. double SampleSize() const { return 15; } // Estimate a pose from N points bool EstimateModel( const std::vector& data, std::vector* models) const { std::vector feature_position; std::vector world_point; for( int i=0 ; i solution_rotations; std::vector solution_translations; theia::DlsPnp( feature_position, world_point, &solution_rotations, &solution_translations ); if( solution_rotations.size() == 1 ) { RelativePose r; r.b_T_a = Matrix4d::Identity(); r.b_T_a.topLeftCorner(3,3) = solution_rotations[0].toRotationMatrix(); r.b_T_a.col(3).topRows(3) = solution_translations[0]; models->push_back( r ); } else { return false; } } double Error( const CorrespondencePair_3d2d& point, const RelativePose& r ) const { Vector3d b_X = r.b_T_a.topLeftCorner(3,3) * point.a_X + r.b_T_a.col(3).topRows(3); double depth = b_X(2); b_X /= b_X(2); double reproj_error = abs(b_X(0) - point.uv_d(0)) + abs(b_X(1) - point.uv_d(1)); // I want nearby points to have smaller Error than far off points. // So artificially increase the error when depth is higher for the point. double f = 1.; #if 0 if( depth < 1. ) f = .5; if( depth >=1 && depth < 3 ) f = 5.0; if( depth >=3 && depth < 8 ) f = 2.; if( depth >= 8. && depth < 15 ) f = 1.7; if( depth >= 15 ) f = 0.1; #endif return reproj_error*f; } }; /////////// END DlsPnp-RANSAC /////////////////// #endif #ifdef __USE_THEIASFM //////////////////// AlignPointCloudsUmeyama with Ransac /////////////////////// // Data struct CorrespondencePair_3d3d { Vector3d a_X; // 3d point expressed in co-ordinate system of `image-a` Vector3d b_X; // 3d point expressed in co-ordinate system of `image-b` }; // Model // struct RelativePose { // Matrix4d b_T_a; // }; class AlignPointCloudsUmeyamaWithRansac: public theia::Estimator { public: // Number of points needed to estimate a line. double SampleSize() const { return 10; } bool EstimateModel( const std::vector& data, std::vector* models) const { //TODO std::vector a_X; std::vector b_X; for( int i=0 ; i 0.9 ) { RelativePose r; r.b_T_a = Matrix4d::Identity(); r.b_T_a.topLeftCorner(3,3) = ____R; r.b_T_a.col(3).topRows(3) = ____t; models->push_back( r ); // cout << "Estimate s=" << ___s << " " << PoseManipUtils::prettyprintMatrix4d(r.b_T_a)<< endl; return true; } else { return false; } } double Error( const CorrespondencePair_3d3d& point, const RelativePose& r ) const { Vector3d b_X_cap = r.b_T_a.topLeftCorner(3,3) * point.a_X + r.b_T_a.col(3).topRows(3); double err = (b_X_cap - point.b_X).norm(); double f=1.0; if( point.a_X(2) < 1. && point.a_X(2) > 8. ) f = 1.2; // cout << "ICP RAnsac error=" << err << endl; return f*err; } }; class StaticTheiaPoseCompute { public: static float P3P_ICP( const vector& uv_X, const vector& uvd_Y, Matrix4d& uvd_T_uv, string & p3p__msg ); static float PNP( const std::vector& w_X, const std::vector& c_uv_normalized, Matrix4d& c_T_w, string& pnp__msg ); }; //////////////////// AlignPointCloudsUmeyama with Ransac /////////////////////// #endif // Write an ceres based iterative PnP and P3P for better control and 4DOF optimization class StaticCeresPoseCompute { public: // PNP With ceres: minimize_{R,t} \sum_i ( PI( c_(R|t)_w * w_X[i] ) - u[i] ) // [Input] // w_X: World 3d points. // c_uv_normalized: Image points in normalized co-ordinates // c_T_w: initial guess, this will be modified to reflect the optimized solution // [Output] // c_T_w // pnp__msg: debugging string msg static float PNP( const std::vector& w_X, const std::vector& c_uv_normalized, Matrix4d& c_T_w, string& pnp__msg ); // p3p (icp) with ceres: minimize_{R,t} \sum_i ( b_{R,t}_a * a_X - b_X ) // [Input] // a_X : 3d points in co-ordinate frame of a // b_X : 3d points in co-orfinate frame of b // b_T_a: initial guess // [Output] // b_T_a : optimized output // p3p_msg: debug msg static float P3P_ICP( const std::vector& a_X, const std::vector& b_X, Matrix4d& c_T_w, string& pnp__msg ); }; ================================================ FILE: src/HypothesisManager.cpp ================================================ #include "HypothesisManager.h" HypothesisManager::HypothesisManager() { std::cout << "HypothesisManager constructor\n"; } HypothesisManager::~HypothesisManager() { std::cout << "HypothesisManager descrutctor\n"; } bool HypothesisManager::add_node( int a, int b, float dot_prod ) { // See if (a,b) is in a already active hypothesis ? // |---(yes) give strength, possibly increase its time-to-live (ttl) // |---(no ) is it worth creating new hypothesis? // |--- (yes) // |--- (no) discard this std::lock_guard lk(mx); cout << "\n[HypothesisManager::add_node] add_node("<print_active_hypothesis(header_str); #endif for( auto c=it->list_of_nodes_in_this_hypothesis.rbegin() ; c!=it->list_of_nodes_in_this_hypothesis.rend() ; c++ ) { int _a = std::get<0>( *c ); int _b = std::get<1>( *c ); float _dot_prod = std::get<2>( *c ); if( abs(a-_a) < 7 && abs(b-_b) < 7 ) { it->list_of_nodes_in_this_hypothesis.push_back( make_tuple(a,b,dot_prod) ); was_in_one_of_the_active_hyp = true; printf( "Added (%d,%d,%f) in active_hyp#%d\n", a,b,dot_prod, it-active_hyp.begin() ); printf( "Increment time_to_live of this hypothesis\n" ); it->increment_ttl(); break; } } } //-------------b if( was_in_one_of_the_active_hyp == false ) { cout << "New Hypothesis added: "<< a<< ", " << b << ", " << dot_prod << endl; active_hyp.push_back( Hypothesis( a,b, dot_prod) ); } } void HypothesisManager::digest() { std::lock_guard lk(mx); for( auto it=active_hyp.begin() ; it!=active_hyp.end() ; it++ ) { it->decrement_ttl(); it->decrement_ttl(); it->decrement_ttl(); it->decrement_ttl(); // it->decrement_ttl(); } } void HypothesisManager::monitoring_thread() { cout << TermColor::GREEN() << "[HypothesisManager::monitoring_thread] thread started\n" << TermColor::RESET() << endl; while( b_monitoring_thread ) { { // threadsafe zone std::lock_guard lk(mx); ofstream myfile; myfile.open ("/dev/pts/1"); int n_actives = 0; for( int i=0 ; i 0 ) { myfile << TermColor::GREEN() << "Currently active hypothesis=" << n_actives << TermColor::RESET() << endl; } else { myfile << TermColor::RED() << "No active hypothesis" << TermColor::RESET() << endl; } myfile.close(); } // end of threadsafe zone std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } cout << TermColor::RED() << "[HypothesisManager::monitoring_thread] thread Ended\n" << TermColor::RESET() << endl; } ================================================ FILE: src/HypothesisManager.h ================================================ #pragma once // This class will manage all the loop hypothesis. // This is for implementing the heuristics for multi hypothesis tracking. // A new graph-node is created when I have a new putative matching pair. // This matching pair is associated with an existing hypothesis or a new // hypothesis is created. Allthe hypothesis will have a time-to-live. #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/TermColor.h" using namespace std; class Hypothesis { public: Hypothesis(int a, int b, float prod ) { list_of_nodes_in_this_hypothesis.push_back( std::make_tuple(a,b, prod) ); time_to_live = 20; } std::vector< std::tuple > list_of_nodes_in_this_hypothesis; string info_string() { std::stringstream ss; ss << "ttl=" << time_to_live << " "; ss << "\tlen(list) = " << list_of_nodes_in_this_hypothesis.size() << "\t"; //starts and ends of the hyp vector tmp_a, tmp_b; for( auto it= list_of_nodes_in_this_hypothesis.begin() ; it!= list_of_nodes_in_this_hypothesis.end() ; it++ ) { tmp_a.push_back( std::get<0>(*it) ); tmp_b.push_back( std::get<1>(*it) ); } const auto result_a = std::minmax_element( tmp_a.begin(), tmp_a.end() ); const auto result_b = std::minmax_element( tmp_b.begin(), tmp_b.end() ); ss << *result_a.first << "---->" << *result_a.second << " "; ss << *result_b.first << "---->" << *result_b.second << " "; return ss.str(); } #if 0 void print_active_hypothesis( const string header_str ) { ofstream myfile; myfile.open ("/dev/pts/1"); myfile << header_str << "\t\t"; myfile << "ttl=" << time_to_live << " "; myfile << "\tlen(list) = " << list_of_nodes_in_this_hypothesis.size() << "\t"; vector tmp_a, tmp_b; for( auto it= list_of_nodes_in_this_hypothesis.begin() ; it!= list_of_nodes_in_this_hypothesis.end() ; it++ ) { tmp_a.push_back( std::get<0>(*it) ); tmp_b.push_back( std::get<1>(*it) ); } // cout << const auto result_a = std::minmax_element( tmp_a.begin(), tmp_a.end() ); const auto result_b = std::minmax_element( tmp_b.begin(), tmp_b.end() ); myfile << *result_a.first << "---->" << *result_a.second << " "; myfile << *result_b.first << "---->" << *result_b.second << " "; myfile << endl; #if 0 //full details for( auto it= list_of_nodes_in_this_hypothesis.begin() ; it!= list_of_nodes_in_this_hypothesis.end() ; it++ ) { myfile << " (" << std::get<0>(*it) << "," << std::get<1>(*it) << ") "; } myfile << endl; #endif myfile.close(); } #endif public: void decrement_ttl() { if( time_to_live <= 0 ) return; time_to_live--; } void increment_ttl() { // linear increment time_to_live++; if( time_to_live > 100 ) time_to_live++; // multiplicative increment // time_to_live += int(.1*time_to_live); } int get_ttl() { return time_to_live; } bool is_hypothesis_active() { if(time_to_live<=0) return false; else return true; } int n_elements_in_list() { return list_of_nodes_in_this_hypothesis.size(); } private: int time_to_live; }; class HypothesisManager { public: HypothesisManager(); ~HypothesisManager(); // We add a node in the graph for every putative loop candidate. // a,b: Indicates a loop connection between node[a] and node[b] with a dot_product=dot_prod bool add_node( int a, int b, float dot_prod ); //--- // Info functions //--- // int n_active_hypothesis(); //--- // Loop through each active hypothesis and decrement the time-to-live // This is intented to be called every few milisecs. void digest(); private: mutable mutex mx; std::vector active_hyp; //---------------------------- //--- Monitoring Thread //---------------------------- public: void monitoring_thread(); void monitoring_thread_enable() { b_monitoring_thread = true;} void monitoring_thread_disable() {b_monitoring_thread = false;} private: atomic b_monitoring_thread; }; ================================================ FILE: src/ImageDataManager.cpp ================================================ #include "ImageDataManager.h" ImageDataManager::ImageDataManager() { cout << TermColor::iYELLOW() << "Constructor for ImageDataManager\n" << TermColor::RESET(); } bool ImageDataManager::initStashDir( bool clear_dir, const string __dir ) { STASH_DIR = __dir; //string( "/tmp/cerebro_stash" ); if( clear_dir ) // rm -rf && mksir { // // $ rm -rf ${save_dir} // $ mkdir ${save_dir} // #include string system_cmd; system_cmd = string("rm -rf "+STASH_DIR).c_str(); cout << TermColor::YELLOW() << "[ImageDataManager::initStashDir] " << system_cmd << TermColor::RESET() << endl; const int rm_dir_err = RawFileIO::exec_cmd( system_cmd ); if ( rm_dir_err == -1 ) { cout << TermColor::RED() << "[ImageDataManager::initStashDir] Error removing directory!\n" << TermColor::RESET() << endl; exit(1); } system_cmd = string("mkdir -p "+STASH_DIR).c_str(); cout << TermColor::YELLOW() << "[ImageDataManager::initStashDir] " << system_cmd << TermColor::RESET() << endl; // const int dir_err = system( system_cmd.c_str() ); const int dir_err = RawFileIO::exec_cmd( system_cmd ); if ( dir_err == -1 ) { cout << TermColor::RED() << "[ImageDataManager::initStashDir] Error creating directory!\n" << TermColor::RESET() << endl; exit(1); } // done emptying the directory. } else { // make sure the path is a directory if( RawFileIO::is_path_a_directory(STASH_DIR) ) { // ok good! } else { cout << TermColor::RED() << "[ImageDataManager::initStashDir]You asked me to assume the directory ``"<< STASH_DIR << "`` as stash directory, but it doesnt seem to exist...EXIT" << TermColor::RESET() << endl; ROS_ERROR( "[ImageDataManager::initStashDir]You asked me to assume the directory ``%s`` as stash directory, but it doesnt seem to exist...EXIT", STASH_DIR.c_str() ); exit(1); } } m_STASH_DIR = true; } ImageDataManager::~ImageDataManager() { std::lock_guard lk(m); // TODO // rm all available on RAM status.clear(); image_data.clear(); if( rm_stash_dir_in_destructor ) { // rm -rf stash folder string system_cmd; system_cmd = string("rm -rf "+STASH_DIR).c_str(); cout << TermColor::YELLOW() << "[ImageDataManager::~ImageDataManager] " << system_cmd << TermColor::RESET() << endl; const int rm_dir_err = RawFileIO::exec_cmd( system_cmd ); if ( rm_dir_err == -1 ) { cout << TermColor::RED() << "[ImageDataManager::~ImageDataManager] Error removing directory!\n" << TermColor::RESET() << endl; exit(1); } } else { cout << TermColor::YELLOW() << "[ImageDataManager::~ImageDataManager] not doing rm -rf " << STASH_DIR << ", because the bool variable was found to be false\n"; } } bool ImageDataManager::setImage( const string ns, const ros::Time t, const cv::Mat img ) { cout << "[ImageDataManager::setImage] not implemented\n"; exit(1); } // #define __ImageDataManager__setImageFromMsg( msg ) msg; #define __ImageDataManager__setImageFromMsg( msg ) ; bool ImageDataManager::setNewImageFromMsg( const string ns, const sensor_msgs::ImageConstPtr msg ) { std::lock_guard lk(m); cv::Mat __image = (cv_bridge::toCvCopy(msg)->image).clone(); auto key = std::make_pair(ns, msg->header.stamp); __ImageDataManager__setImageFromMsg( cout << TermColor::iWHITE() << "[ImageDataManager::setImageFromMsg] insert at(" << ns << "," << msg->header.stamp << ")" << TermColor::RESET() << endl; ) // TODO: throw error if attempting to over-write status[ key ] = MEMSTAT::AVAILABLE_ON_RAM; image_data[ key ] = __image; return true; } // #define __ImageDataManager__getImage( msg ) msg; #define __ImageDataManager__getImage( msg ) ; bool ImageDataManager::getImage( const string ns, const ros::Time t, cv::Mat& outImg ) { ensure_init(); decrement_hit_counts_and_deallocate_expired(); std::lock_guard lk(m); auto key = std::make_pair(ns,t); if( status.count(key) > 0 ) { if( status.at(key) == MEMSTAT::AVAILABLE_ON_RAM ) { __ImageDataManager__getImage( cout << TermColor::iGREEN() << "[ImageDataManager::getImage] retrived (from ram) at ns=" << ns << " t=" << t << TermColor::RESET() << endl; ) outImg = image_data.at( key ); return true; } else { if( status.at(key) == MEMSTAT::AVAILABLE_ON_DISK ) { const string fname = key_to_imagename(ns, t); __ImageDataManager__getImage( cout << TermColor::iYELLOW() << "[ImageDataManager::getImage] retrived (fname=" << fname << ") at ns=" << ns << " t=" << t << TermColor::RESET() << endl; ) if( ns == "depth_image" ) { outImg = cv::imread( fname+".png", -1 ); } else { outImg = cv::imread( fname, -1 ); //-1 is for read as it is. } if( !outImg.data ) { cout << TermColor::RED() << "[ImageDataManager::getImage] failed to load image " << fname << TermColor::RESET() << endl; exit(1); return false; } //=----------------cache hit this __ImageDataManager__getImage( cout << TermColor::iYELLOW() << "\t\tchange the status to MEMSTAT::AVAILABLE_ON_RAM_DUETO_HIT and set expiry to 10\n" << TermColor::RESET(); ) status[key] = MEMSTAT::AVAILABLE_ON_RAM_DUETO_HIT; image_data[key] = outImg; hit_count[key] = 10; //=---------------- return true; } if( status.at(key) == MEMSTAT::AVAILABLE_ON_RAM_DUETO_HIT ) { __ImageDataManager__getImage( cout << TermColor::iBLUE() << "[ImageDataManager::getImage] retrived (AVAILABLE_ON_RAM_DUETO_HIT) at ns=" << ns << " t=" << t << TermColor::RESET() << endl; ) outImg = image_data.at(key); hit_count[key] +=5; return true; } if( status.at(key) == MEMSTAT::UNAVAILABLE ) { cout << TermColor::iYELLOW() << "[ImageDataManager::getImage] WARNING you requested image which was MEMSTAT::UNAVAILABLE, this should not be happening but for now return false, at ns=" << ns << " t=" << t << TermColor::RESET() << endl; return false; } cout << TermColor::iGREEN() << "[ImageDataManager::getImage] got a corrupt key. Report this error to authors if this occurs\n" << TermColor::RESET(); exit(1); } } else { cout << TermColor::RED() << "[ImageDataManager::getImage] FATAL-ERROR you requested image ns=" << ns << ", t=" << t << "; However it was not found on the map. FATAL ERRR\n" << TermColor::RESET(); exit(1); return false; } return false; } // #define __ImageDataManager__rmImage(msg) msg; #define __ImageDataManager__rmImage(msg) ; bool ImageDataManager::rmImage( const string ns, const ros::Time t ) { ensure_init(); std::lock_guard lk(m); auto key = std::make_pair(ns, t); if( status.count(key) > 0 ) { if( status.at(key) == MEMSTAT::AVAILABLE_ON_RAM ) { __ImageDataManager__rmImage( cout << TermColor::iWHITE() << "[ImageDataManager::rmImage] ns=" << ns << ", t=" << t << ". FOUND, available on ram, do complete removal" << TermColor::RESET() << endl; ) status[key] = MEMSTAT::UNAVAILABLE; auto it_b = image_data.find( key ); image_data.erase( it_b ); return true; } else { __ImageDataManager__rmImage( cout << TermColor::iWHITE() << "[ImageDataManager::rmImage] ns=" << ns << ", t=" << t << "."; cout << "FOUND, however its status is UNAVAILABLE or AVAILABLE_ON_DISK, this means it was previously removed or stashed. No action to take now." << TermColor::RESET() << endl; ) return false; } } else { cout << TermColor::RED() << "[ImageDataManager::rmImage] FATAL-ERROR you requested to remove ns=" << ns << ", t=" << t << "; However it was not found on the map. FATAL ERRR\n" << TermColor::RESET(); // exit(1); cout << "[ImageDataManager::rmImage]No action taken for now.....\n"; return false; } } // #define __ImageDataManager__stashImage(msg) msg; #define __ImageDataManager__stashImage(msg) ; bool ImageDataManager::stashImage( const string ns, const ros::Time t ) { ensure_init(); std::lock_guard lk(m); __ImageDataManager__stashImage( cout << TermColor::iCYAN() << "[ImageDataManager::stashImage] ns=" << ns << ", t=" << t << TermColor::RESET() << endl; ) auto key = std::make_pair( ns, t ); if( status.count( key ) > 0 ) { if( status.at(key) == MEMSTAT::AVAILABLE_ON_RAM ) { // save to disk auto it_b = image_data.find( key ); // string sfname = STASH_DIR+"/"+ns+"__"+to_string(t.toNSec())+".jpg"; string sfname = key_to_imagename( ns, t ); ElapsedTime elp; elp.tic(); __ImageDataManager__stashImage( cout << TermColor::iCYAN() << "[ImageDataManager] imwrite(" << sfname << ")\t elapsed_time_ms=" << elp.toc_milli() << TermColor::RESET() << endl ; ) // todo: tune jpg quality for saving on more disk space. default is 95/100 for jpg. if( it_b->second.type() == CV_16UC1 ) { // write PNG for depth image cv::imwrite( sfname+".png", it_b->second ); }else { cv::imwrite( sfname, it_b->second ); } // erase from map image_data.erase( it_b ); // change status status[key] = MEMSTAT::AVAILABLE_ON_DISK; return true; } else { __ImageDataManager__stashImage( cout << TermColor::iCYAN() << "[ImageDataManager::stashImage] warning status is not AVAILABLE_ON_RAM so do nothing. You asked me to stash an image which doesnt exists on RAM.\n" << TermColor::RESET(); ) return false; } } else { cout << TermColor::RED() << "[ImageDataManager::stashImage] FATAL-ERROR you requested to stash ns=" << ns << ", t=" << t << "; However it was not found on the map. FATAL ERRR\n" << TermColor::RESET(); exit(1); } } bool ImageDataManager::isImageRetrivable( const string ns, const ros::Time t ) const { ensure_init(); std::lock_guard lk(m); auto key = std::make_pair( ns, t ); if( status.count( key ) > 0 ) { if( status.at( key ) == MEMSTAT::AVAILABLE_ON_RAM || status.at( key ) == MEMSTAT::AVAILABLE_ON_DISK || status.at( key ) == MEMSTAT::AVAILABLE_ON_RAM_DUETO_HIT ) { return true; } else { cout << TermColor::iRED() << "[ImageDataManager::isImageRetrivable] returning false because the requested (ns=" << ns << ", t=" << t << ") was on my list, but type was "<< memstat_to_str( status.at( key ) ) << ", which is something other than AVAILABLE_ON_RAM or AVAILABLE_ON_DISK or AVAILABLE_ON_RAM_DUETO_HIT" << TermColor::RESET() << endl; return false; } } else { cout << TermColor::iRED() << "[ImageDataManager::isImageRetrivable] returning false because the requested (ns=" << ns << ", t=" << t << ") was not in my list" << TermColor::RESET() << endl; return false; } } // _ImageDataManager_printstatus_cout cout #define _ImageDataManager_printstatus_cout myfile bool ImageDataManager::print_status( string fname ) const { // std::lock_guard lk(m); //no need of locks in a const function (readonly). ofstream myfile; myfile.open (fname); int AVAILABLE_ON_RAM=0, AVAILABLE_ON_DISK=0, UNAVAILABLE=0, ETC=0; for( auto it=status.begin() ; it!=status.end() ; it++ ) { if( it->second == MEMSTAT::AVAILABLE_ON_RAM ) { _ImageDataManager_printstatus_cout << TermColor::iGREEN() << " " << TermColor::RESET(); AVAILABLE_ON_RAM++; } else if( it->second == MEMSTAT::AVAILABLE_ON_DISK ) { _ImageDataManager_printstatus_cout << TermColor::iYELLOW() << " " << TermColor::RESET(); AVAILABLE_ON_DISK++; } else if( it->second == MEMSTAT::UNAVAILABLE ) { _ImageDataManager_printstatus_cout << TermColor::iMAGENTA() << " " << TermColor::RESET(); UNAVAILABLE++; } else { if( it->second == MEMSTAT::AVAILABLE_ON_RAM_DUETO_HIT ) _ImageDataManager_printstatus_cout << TermColor::iBLUE() << std::setw(2) << hit_count.at(it->first) << TermColor::RESET(); ETC++; } } _ImageDataManager_printstatus_cout << endl; _ImageDataManager_printstatus_cout << TermColor::iGREEN() << "AVAILABLE_ON_RAM=" << AVAILABLE_ON_RAM << " " << TermColor::RESET() ; _ImageDataManager_printstatus_cout << TermColor::iYELLOW() << "AVAILABLE_ON_DISK=" << AVAILABLE_ON_DISK << " " << TermColor::RESET(); _ImageDataManager_printstatus_cout << TermColor::iMAGENTA() << "UNAVAILABLE=" << UNAVAILABLE << " " << TermColor::RESET(); _ImageDataManager_printstatus_cout << TermColor::iBLUE() << "ETC=" << ETC << " " << TermColor::RESET(); _ImageDataManager_printstatus_cout << "TOTAL=" << AVAILABLE_ON_RAM+AVAILABLE_ON_DISK+UNAVAILABLE+ETC << " "; _ImageDataManager_printstatus_cout << endl; myfile.close(); return true; } bool ImageDataManager::print_status( ) const { // std::lock_guard lk(m); //no need of locks in a const function (readonly). int AVAILABLE_ON_RAM=0, AVAILABLE_ON_DISK=0, UNAVAILABLE=0, ETC=0; for( auto it=status.begin() ; it!=status.end() ; it++ ) { if( it->second == MEMSTAT::AVAILABLE_ON_RAM ) { cout << TermColor::iGREEN() << " " << TermColor::RESET(); cout << std::get<0>(it->first) << "," << std::get<1>(it->first) << ": " << "AVAILABLE_ON_RAM" << endl; AVAILABLE_ON_RAM++; } else if( it->second == MEMSTAT::AVAILABLE_ON_DISK ) { cout << TermColor::iYELLOW() << " " << TermColor::RESET(); // cout << std::get<0>(it->first) << "," << std::get<1>(it->first) << ": " << "AVAILABLE_ON_DISK" << endl; AVAILABLE_ON_DISK++; } else if( it->second == MEMSTAT::UNAVAILABLE ) { cout << TermColor::iMAGENTA() << " " << TermColor::RESET(); // cout << std::get<0>(it->first) << "," << std::get<1>(it->first) << ": " << "UNAVAILABLE" << endl; UNAVAILABLE++; } else { if( it->second == MEMSTAT::AVAILABLE_ON_RAM_DUETO_HIT ) cout << TermColor::iBLUE() << std::setw(2) << hit_count.at(it->first) << TermColor::RESET(); // cout << std::get<0>(it->first) << "," << std::get<1>(it->first) << ": " << "AVAILABLE_ON_RAM_DUETO_HIT" << endl; ETC++; } } cout << endl; cout << TermColor::iGREEN() << "AVAILABLE_ON_RAM=" << AVAILABLE_ON_RAM << " " << TermColor::RESET() ; cout << TermColor::iYELLOW() << "AVAILABLE_ON_DISK=" << AVAILABLE_ON_DISK << " " << TermColor::RESET(); cout << TermColor::iMAGENTA() << "UNAVAILABLE=" << UNAVAILABLE << " " << TermColor::RESET(); cout << TermColor::iBLUE() << "ETC=" << ETC << " " << TermColor::RESET(); cout << "TOTAL=" << AVAILABLE_ON_RAM+AVAILABLE_ON_DISK+UNAVAILABLE+ETC << " "; cout << endl; return true; } // #define __ImageDataManager__decrement_hit_counts_and_deallocate_expired(msg) msg; #define __ImageDataManager__decrement_hit_counts_and_deallocate_expired(msg) ; int ImageDataManager::decrement_hit_counts_and_deallocate_expired() { std::lock_guard lk(m); // decrement all by 1 int n_elements_in_hitlist = 0; for( auto it=hit_count.begin() ; it!=hit_count.end() ; it++ ) { it->second--; __ImageDataManager__decrement_hit_counts_and_deallocate_expired( cout << "[decrement_hit_counts_and_deallocate_expired]" << std::get<0>(it->first) << "," << std::get<1>(it->first) << " expry=" << it->second << endl; ) n_elements_in_hitlist++; } // deallocate expired int n_removed = 0; for( auto it=hit_count.begin() ; it!=hit_count.end() ; it++ ) { if( it->second <= 0 ) { __ImageDataManager__decrement_hit_counts_and_deallocate_expired( cout << "[decrement_hit_counts_and_deallocate_expired] REMOVE: " << std::get<0>(it->first) << "," << std::get<1>(it->first) << endl; ) auto key = it->first; image_data.erase(key); hit_count.erase(key); status[key] = MEMSTAT::AVAILABLE_ON_DISK; n_removed++; } } __ImageDataManager__decrement_hit_counts_and_deallocate_expired( cout << "[decrement_hit_counts_and_deallocate_expired] n_elements_in_hitlist=" << n_elements_in_hitlist << "\tn_removed=" << n_removed<< endl; ) return n_removed; } json ImageDataManager::stashAll() { ensure_init(); // std::lock_guard lk(m); //dont lock here as stashImage already locks. If you lock here it will cause a deadlock. json obj; // go over all and stash all int AVAILABLE_ON_RAM = 0; int AVAILABLE_ON_DISK = 0; int UNAVAILABLE = 0; int AVAILABLE_ON_RAM_DUETO_HIT=0; int n_stashed = 0; for( auto it=status.begin() ; it!=status.end() ; it++ ) { string __ns_x = std::get<0>(it->first); ros::Time __t_x = std::get<1>(it->first); if( it->second == MEMSTAT::AVAILABLE_ON_RAM ) { // cout << "[ImageDataManager::stashAll] stash( " << __ns_x << ", " << __t_x << " )\n"; stashImage( __ns_x, __t_x ); n_stashed++; } json this_json; this_json["namespace"] = __ns_x; // this_json["stamp"] = __t_x.toSec(); // this is buggy, have overflow, better stick to using Nsec int64_t this_json["stampNSec"] = __t_x.toNSec(); if( it->second == MEMSTAT::AVAILABLE_ON_RAM ) { this_json["status"] = "AVAILABLE_ON_RAM"; AVAILABLE_ON_RAM++; } else if( it->second == MEMSTAT::AVAILABLE_ON_DISK ) { this_json["status"] = "AVAILABLE_ON_DISK"; AVAILABLE_ON_DISK++; } else if( it->second == MEMSTAT::UNAVAILABLE ) { this_json["status"] = "UNAVAILABLE"; UNAVAILABLE++; } else { if( it->second == MEMSTAT::AVAILABLE_ON_RAM_DUETO_HIT ) this_json["status"] = "AVAILABLE_ON_RAM_DUETO_HIT"; AVAILABLE_ON_RAM_DUETO_HIT++; } obj.push_back( this_json ); } cout << "[ImageDataManager::stashAll]"; cout << "AVAILABLE_ON_RAM=" << AVAILABLE_ON_RAM << "\t"; cout << "AVAILABLE_ON_DISK=" << AVAILABLE_ON_DISK << "\t"; cout << "UNAVAILABLE=" << UNAVAILABLE << "\t"; cout << "AVAILABLE_ON_RAM_DUETO_HIT=" << AVAILABLE_ON_RAM_DUETO_HIT << "\t"; cout << "n_stashed=" << n_stashed << "\t"; cout << endl; return obj; } // // "ImageDataManager": [ // { // "namespace": "left_image", // "stamp": 1542707155.6136055, // "status": "AVAILABLE_ON_DISK" // }, // { // . // }, // . // . // . // ] bool ImageDataManager::loadStateFromDisk( const json json_obj ) { cout << "^^^^^^^^ IN [ImageDataManager::loadStateFromDisk] ^^^^^^^^^^\n"; ensure_init(); // loop thru json object and create the map int n_images = json_obj.size(); cout << "[ImageDataManager::loadStateFromDisk] n_images=" << n_images << endl; int AVAILABLE_ON_DISK = 0; int UNAVAILABLE = 0; for( int i=0 ; i // Created : 12th June, 2019 // #include #include #include #include #include //std::pair #include // threading #include #include #include //opencv #include #include #include #include #include #include "utils/TermColor.h" #include "utils/ElapsedTime.h" #include "utils/RawFileIO.h" #include "utils/nlohmann/json.hpp" using json = nlohmann::json; using namespace std; enum MEMSTAT { AVAILABLE_ON_RAM, AVAILABLE_ON_DISK, UNAVAILABLE, AVAILABLE_ON_RAM_DUETO_HIT }; class ImageDataManager { public: ImageDataManager(); bool initStashDir( bool clear_dir, const string __dir=string("/tmp/cerebro_stash/") ); //< clear_dir: setting this to true will cause `rm -rf DIR && mkdir DIR` ;;;__dir: The directory usually set it to '/tmp/cerebro_stash' . ~ImageDataManager(); bool setImage( const string ns, const ros::Time t, const cv::Mat img ); bool setNewImageFromMsg( const string ns, const sensor_msgs::ImageConstPtr msg ); bool getImage( const string ns, const ros::Time t, cv::Mat& outImg ); bool rmImage( const string ns, const ros::Time t ); bool stashImage( const string ns, const ros::Time t ); bool isImageRetrivable( const string ns, const ros::Time t ) const; bool print_status( string fname ) const; bool print_status( ) const; // - go over all status and stash all the images that remain on RAM. // Also retuns the map status as json object json stashAll(); bool loadStateFromDisk( const json json_obj ); private: mutable std::mutex m; string STASH_DIR = string(""); bool m_STASH_DIR = false; const string key_to_imagename( const string ns, const ros::Time t ) const { return STASH_DIR+"/"+ns+"__"+to_string(t.toNSec())+".jpg"; } const string memstat_to_str( MEMSTAT m ) const { if( m == MEMSTAT::AVAILABLE_ON_RAM ) return "AVAILABLE_ON_RAM"; if( m == MEMSTAT::AVAILABLE_ON_DISK ) return "AVAILABLE_ON_DISK"; if( m == MEMSTAT::UNAVAILABLE ) return "UNAVAILABLE"; if( m == MEMSTAT::AVAILABLE_ON_RAM_DUETO_HIT ) return "AVAILABLE_ON_RAM_DUETO_HIT"; return "N.A."; } // key: (namespace, t) std::map< std::pair, MEMSTAT > status; //status at each timestamp (entris not be esared) std::map< std::pair, cv::Mat > image_data; // when getImage finds that AVAILABLE_ON_DISK, it loads the image (from disk) in the map image_data. // Also it stores say 10 in this map. This 10 means that the image be deleted again if 10 consecutive // getImage requests dont request this image. // CACHE-Algorithm: 5-minute-rule. std::map< std::pair, int > hit_count; int decrement_hit_counts_and_deallocate_expired(); //returns how many expired bool rm_stash_dir_in_destructor = true; public: const string getStashDir() const { return STASH_DIR; } void set_rm_stash_dir_in_destructor_as_false() {rm_stash_dir_in_destructor=false; } void set_rm_stash_dir_in_destructor_as_true() {rm_stash_dir_in_destructor=true; } void ensure_init() const { if( !m_STASH_DIR) { ROS_ERROR( "m_STASH_DIR is false. This means you have not initialized the ImageDataManager. You need to call the function initStashDir() befre you can start using it\n"); exit(1); } } }; ================================================ FILE: src/PNPCeresCostFunctions.h ================================================ #pragma once //ceres #include template T NormalizeAngle(const T& angle_degrees) { if (angle_degrees > T(180.0)) return angle_degrees - T(360.0); else if (angle_degrees < T(-180.0)) return angle_degrees + T(360.0); else return angle_degrees; }; class AngleLocalParameterization { public: template bool operator()(const T* theta_radians, const T* delta_theta_radians, T* theta_radians_plus_delta) const { *theta_radians_plus_delta = NormalizeAngle(*theta_radians + *delta_theta_radians); return true; } static ceres::LocalParameterization* Create() { return (new ceres::AutoDiffLocalParameterization); } }; template void YawPitchRollToRotationMatrix(const T yaw, const T pitch, const T roll, T R[9]) { T y = yaw / T(180.0) * T(M_PI); T p = pitch / T(180.0) * T(M_PI); T r = roll / T(180.0) * T(M_PI); R[0] = cos(y) * cos(p); R[1] = -sin(y) * cos(r) + cos(y) * sin(p) * sin(r); R[2] = sin(y) * sin(r) + cos(y) * sin(p) * cos(r); R[3] = sin(y) * cos(p); R[4] = cos(y) * cos(r) + sin(y) * sin(p) * sin(r); R[5] = -cos(y) * sin(r) + sin(y) * sin(p) * cos(r); R[6] = -sin(p); R[7] = cos(p) * sin(r); R[8] = cos(p) * cos(r); }; template void RotationMatrixTranspose(const T R[9], T inv_R[9]) { inv_R[0] = R[0]; inv_R[1] = R[3]; inv_R[2] = R[6]; inv_R[3] = R[1]; inv_R[4] = R[4]; inv_R[5] = R[7]; inv_R[6] = R[2]; inv_R[7] = R[5]; inv_R[8] = R[8]; }; template void RotationMatrixRotatePoint(const T R[9], const T t[3], T r_t[3]) { r_t[0] = R[0] * t[0] + R[1] * t[1] + R[2] * t[2]; r_t[1] = R[3] * t[0] + R[4] * t[1] + R[5] * t[2]; r_t[2] = R[6] * t[0] + R[7] * t[1] + R[8] * t[2]; }; class PNPEulerAngleError { public: PNPEulerAngleError( Vector3d _w_X, Vector2d _uv_normed_cords ): w_X(_w_X), uv_normed_cords(_uv_normed_cords) {} // // minimize_{R,t} \sum_i ( PI( c_(R|t)_w * w_X[i] ) - u[i] ) template bool operator()( const T* yaw, const T* pitch, const T* roll, const T* tx, const T* ty , const T* tz, T * residue ) const { T R[9]; // c_T_a. YawPitchRollToRotationMatrix( yaw[0], pitch[0], roll[0], R ); T out[3]; out[0] = R[0] * T(w_X(0)) + R[1] * T(w_X(1)) + R[2] * T(w_X(2)) + tx[0]; out[1] = R[3] * T(w_X(0)) + R[4] * T(w_X(1)) + R[5] * T(w_X(2)) + ty[0]; out[2] = R[6] * T(w_X(0)) + R[7] * T(w_X(1)) + R[8] * T(w_X(2)) + tz[0]; // Simple residue[0] = out[0] / out[2] - T(uv_normed_cords(0)); residue[1] = out[1] / out[2] - T(uv_normed_cords(1)); // Weight by Z. A point that is far away need to be down weighted // just search plot 1/ (1 + exp( x-10) ) on google. also called sigmoid function T wt = T( 1.0 / ( 1.0 + exp(w_X(2) - 10.) ) ); residue[0] *= wt; residue[1] *= wt; return true; } static ceres::CostFunction* Create( Vector3d _w_X, Vector2d _uv_normed_cords ) { return (new ceres::AutoDiffCostFunction< PNPEulerAngleError, 2, 1,1,1, 1,1,1 >( new PNPEulerAngleError( _w_X, _uv_normed_cords ) ) ); } private: const Vector3d w_X; const Vector2d uv_normed_cords; }; class P3PEulerAngleError { public: // [Input]: // _a_X: 3dpts expressed in co-ordinate frame of a // _b_X: 3dpts expressed in co-ordinate frame of b P3PEulerAngleError( Vector3d _a_X, Vector3d _b_X ): a_X(_a_X), b_X(_b_X) {} // // minimize_{R,t} \sum_i b_(R|t)_a * a_X[i] - b_X[i] template bool operator()( const T* yaw, const T* pitch, const T* roll, const T* tx, const T* ty , const T* tz, T * residue ) const { T R[9]; // b_T_a. YawPitchRollToRotationMatrix( yaw[0], pitch[0], roll[0], R ); T out[3]; out[0] = R[0] * T(a_X(0)) + R[1] * T(a_X(1)) + R[2] * T(a_X(2)) + tx[0]; out[1] = R[3] * T(a_X(0)) + R[4] * T(a_X(1)) + R[5] * T(a_X(2)) + ty[0]; out[2] = R[6] * T(a_X(0)) + R[7] * T(a_X(1)) + R[8] * T(a_X(2)) + tz[0]; // Simple residue[0] = out[0] - T(b_X(0)); residue[1] = out[1] - T(b_X(1)); residue[2] = out[2] - T(b_X(2)); // Weight by Z. A point that is far away need to be down weighted // just search plot 1/ (1 + exp( x-10) ) on google. also called sigmoid function T wt = T( 1.0 / ( 1.0 + exp(a_X(2) - 10.) ) ); residue[0] *= wt; residue[1] *= wt; residue[2] *= wt; return true; } static ceres::CostFunction* Create( Vector3d _a_X, Vector3d _b_X ) { return (new ceres::AutoDiffCostFunction< P3PEulerAngleError, 3, 1,1,1, 1,1,1 >( new P3PEulerAngleError( _a_X, _b_X ) ) ); } private: const Vector3d a_X; const Vector3d b_X; }; /* struct PNPFourDOFError { PNPFourDOFError(double t_x, double t_y, double t_z, double relative_yaw, double pitch_i, double roll_i) :t_x(t_x), t_y(t_y), t_z(t_z), relative_yaw(relative_yaw), pitch_i(pitch_i), roll_i(roll_i){} template bool operator()(const T* const yaw_i, const T* ti, const T* yaw_j, const T* tj, T* residuals) const { T t_w_ij[3]; t_w_ij[0] = tj[0] - ti[0]; t_w_ij[1] = tj[1] - ti[1]; t_w_ij[2] = tj[2] - ti[2]; // euler to rotation T w_R_i[9]; YawPitchRollToRotationMatrix(yaw_i[0], T(pitch_i), T(roll_i), w_R_i); // rotation transpose T i_R_w[9]; RotationMatrixTranspose(w_R_i, i_R_w); // rotation matrix rotate point T t_i_ij[3]; RotationMatrixRotatePoint(i_R_w, t_w_ij, t_i_ij); residuals[0] = (t_i_ij[0] - T(t_x)); residuals[1] = (t_i_ij[1] - T(t_y)); residuals[2] = (t_i_ij[2] - T(t_z)); residuals[3] = NormalizeAngle(yaw_j[0] - yaw_i[0] - T(relative_yaw)); return true; } static ceres::CostFunction* Create(const double t_x, const double t_y, const double t_z, const double relative_yaw, const double pitch_i, const double roll_i) { return (new ceres::AutoDiffCostFunction< PNPFourDOFError, 4, 1, 3, 1, 3>( new PNPFourDOFError(t_x, t_y, t_z, relative_yaw, pitch_i, roll_i))); } double t_x, t_y, t_z; double relative_yaw, pitch_i, roll_i; }; */ ================================================ FILE: src/PinholeCamera.cpp ================================================ #include "PinholeCamera.h" PinholeCamera::PinholeCamera( string config_file ) { cv::FileStorage fs( config_file, cv::FileStorage::READ ); if( !fs.isOpened() ) { ROS_ERROR( "Cannot open config file : %s", config_file.c_str() ); ROS_ERROR( "Quit"); mValid = false; exit(1); } this->config_file_name = string(config_file); cout << "---Camera Config---\n"; fs["model_type"] >> config_model_type; cout << "config_model_type:"<< config_model_type << endl; fs["camera_name"] >> config_camera_name; cout << "config_camera_name:" << config_camera_name << endl; fs["image_width"] >> config_image_width; cout << "config_image_width:" << config_image_width << endl; fs["image_height"] >> config_image_height; cout << "config_image_height:" << config_image_height << endl; // assert( config_model_type == "PINHOLE" && (bool)"The class PinholeCamera is designed only for PINHOLE camera model. In this config_file: "+config_file + " the camera model was "+config_model_type+" this was not expected\n" ); fs["projection_parameters"]["fx"] >> _fx; fs["projection_parameters"]["fy"] >> _fy; fs["projection_parameters"]["cx"] >> _cx; fs["projection_parameters"]["cy"] >> _cy; cout << "projection_parameters :: " << _fx << " " << _fy << " " << _cx << " " << _cy << " " << endl; fs["distortion_parameters"]["k1"] >> _k1; fs["distortion_parameters"]["k2"] >> _k2; fs["distortion_parameters"]["p1"] >> _p1; fs["distortion_parameters"]["p2"] >> _p2; cout << "distortion_parameters :: " << _k1 << " " << _k2 << " " << _p1 << " " << _p2 << " " << endl; cout << "--- ---\n"; // Define the 3x3 Projection matrix eigen and/or cv::Mat. m_K = cv::Mat::zeros( 3,3,CV_32F ); m_K.at(0,0) = _fx; m_K.at(1,1) = _fy; m_K.at(0,2) = _cx; m_K.at(1,2) = _cy; m_K.at(2,2) = 1.0; m_D = cv::Mat::zeros( 4, 1, CV_32F ); m_D.at(0,0) = _k1; m_D.at(1,0) = _k2; m_D.at(2,0) = _p1; m_D.at(3,0) = _p2; cout << "m_K" << m_K << endl; cout << "m_D" << m_D << endl; // Define 4x1 vector of distortion params eigen and/or cv::Mat. e_K << _fx, 0.0, _cx, 0.0, _fy, _cy, 0.0, 0.0, 1.0; e_D << _k1 , _k2, _p1 , _p2; cout << "e_K:\n" << e_K << endl; cout << "e_D:\n" << e_D << endl; mValid = true; } PinholeCamera::PinholeCamera( double __fx, double __fy, double __cx, double __cy, int __im_rows, int __im_cols, double __k1, double __k2, double __p1, double __p2 ): _fx( __fx ), _fy( __fy ), _cx( __cx ), _cy( __cy ), config_image_width( __im_cols ), config_image_height( __im_rows ), _k1( __k1 ), _k2( __k2 ), _p1( __p1 ), _p2( __p2 ) { ROS_WARN( "[PinholeCamera::PinholeCamera] Setting camera intrinsics explicitly.....\n"); // Define the 3x3 Projection matrix eigen and/or cv::Mat. m_K = cv::Mat::zeros( 3,3,CV_32F ); m_K.at(0,0) = _fx; m_K.at(1,1) = _fy; m_K.at(0,2) = _cx; m_K.at(1,2) = _cy; m_K.at(2,2) = 1.0; m_D = cv::Mat::zeros( 4, 1, CV_32F ); m_D.at(0,0) = _k1; m_D.at(1,0) = _k2; m_D.at(2,0) = _p1; m_D.at(3,0) = _p2; cout << "m_K" << m_K << endl; cout << "m_D" << m_D << endl; // Define 4x1 vector of distortion params eigen and/or cv::Mat. e_K << _fx, 0.0, _cx, 0.0, _fy, _cy, 0.0, 0.0, 1.0; e_D << _k1 , _k2, _p1 , _p2; cout << "e_K:\n" << e_K << endl; cout << "e_D:\n" << e_D << endl; mValid = true; } void PinholeCamera::printCameraInfo( int verbosity ) const { cout << "====== PinholeCamera::printCameraInfo ======\n"; if( !isValid() ) cout << "camera not set\n"; cout << "config_file: " << this->config_file_name << endl; cout << "Image Rows: " << getImageRows() << endl; cout << "Image Cols: " << getImageCols() << endl; cout << "fx: " << fx() << " ; fy: " << fy() << endl; cout << "cx: " << cx() << " ; cy: " << cy() << endl; cout << "(distortion params)k1,k2,p1,p2" << " " << k1() << " " << k2() << " " << p1() << " " << p2() << endl; if( verbosity >= 1 ) { cout << "--cv::Mat \n"; cout << "m_K\n" << m_K << endl; cout << "m_D\n" << m_D << endl; cout << "--Eigen::Matrix \n"; cout << "e_K\n" << e_K << endl; cout << "e_K\n" << e_D << endl; } cout << "================================================\n"; } string PinholeCamera::getCameraInfoAsJson() const { if( !isValid() ) return string("{\"isValid\": 1}\n"); std::stringstream buffer; buffer << "{\n"; buffer << "\"config_file\": \"" << this->config_file_name << "\""<< endl; buffer << ",\"image_rows\": " << getImageRows() << endl; buffer << ",\"image_cols\": " << getImageCols() << endl; buffer << ",\"fx\": " << fx() << " \n,fy: " << fy() << endl; buffer << ",\"cx\": " << cx() << " \n,cy: " << cy() << endl; buffer << ",\"k1\": " << k1() << endl; buffer << ",\"k2\": " << k2() << endl; buffer << ",\"p1\": " << p1() << endl; buffer << ",\"p2\": " << p2() << endl; buffer << "}\n" ; return buffer.str(); } void PinholeCamera::print_cvmat_info( string msg, const cv::Mat& A ) { cout << msg << ":" << "rows=" << A.rows << ", cols=" << A.cols << ", ch=" << A.channels() << ", type=" << type2str( A.type() ) << endl; } string PinholeCamera::type2str(int type) { string r; uchar depth = type & CV_MAT_DEPTH_MASK; uchar chans = 1 + (type >> CV_CN_SHIFT); switch ( depth ) { case CV_8U: r = "8U"; break; case CV_8S: r = "8S"; break; case CV_16U: r = "16U"; break; case CV_16S: r = "16S"; break; case CV_32S: r = "32S"; break; case CV_32F: r = "32F"; break; case CV_64F: r = "64F"; break; default: r = "User"; break; } r += "C"; r += (chans+'0'); return r; } // Input 3d points in homogeneous co-ordinates 4xN matrix. Eigen I/O void PinholeCamera::perspectiveProject3DPoints( const MatrixXd& c_X, MatrixXd& out_pts ) { // DIY - Do It Yourself Projection // c_X.row(0).array() /= c_X.row(3).array(); // c_X.row(1).array() /= c_X.row(3).array(); // c_X.row(2).array() /= c_X.row(3).array(); // c_X.row(3).array() /= c_X.row(3).array(); // K [ I | 0 ] MatrixXd I_0; I_0 = Matrix4d::Identity().topLeftCorner<3,4>(); // MatrixXf P1; // P1 = cam_intrin * I_0; //3x4 // Project and Perspective Divide MatrixXd im_pts; im_pts = I_0 * c_X; //in normalized image co-ordinate. Beware that distortion need to be applied in normalized co-ordinates im_pts.row(0).array() /= im_pts.row(2).array(); im_pts.row(1).array() /= im_pts.row(2).array(); im_pts.row(2).array() /= im_pts.row(2).array(); // Apply Distortion MatrixXd Xdd = MatrixXd( im_pts.rows(), im_pts.cols() ); for( int i=0 ; i Created : 3rd Oct, 2017 Modified: 26th Oct, 2018 */ #include #include #include //opencv #include #include #include #include //ros #include #include // Eigen3 #include #include using namespace Eigen; #include using namespace std; class PinholeCamera { public: PinholeCamera() { mValid = false; } PinholeCamera( string config_file ); PinholeCamera( double __fx, double __fy, double __cx, double __cy, int __im_rows, int __im_cols, double __k1, double __k2, double __p1, double __p2 ); void printCameraInfo( int verbosity=2 ) const; string getCameraInfoAsJson() const; const cv::Mat get_mK() const {return m_K; } const cv::Mat get_mD() const {return m_D; } const Matrix3d get_eK() const {return e_K; } const Vector4d get_eD() const {return e_D; } // have a const after function, signals the compiler that these functions will not attempt to modify the variables inside bool isValid() const { return mValid; } double fx() const { return _fx; } double fy() const { return _fy; } double cx() const { return _cx; } double cy() const { return _cy; } double k1() const { return _k1; } double k2() const { return _k2; } double p1() const { return _p1; } double p2() const { return _p2; } string getModelType() const { return config_model_type; } string getCameraName() const { return config_camera_name; } string getConfigFileName() const { return config_file_name; } int getImageWidth() const { return config_image_width; } int getImageHeight() const { return config_image_height; } int getImageRows() const { return this->getImageHeight(); } int getImageCols() const { return this->getImageWidth(); } // Projection // Input 3d points in homogeneous co-ordinates 4xN matrix. Eigen I/O. // uses the camera matrix and D from this class void perspectiveProject3DPoints( const MatrixXd& c_X, MatrixXd& out_pts ); private: string config_model_type, config_camera_name; string config_file_name; int config_image_width, config_image_height; double _fx, _fy, _cx, _cy; double _k1, _k2, _p1, _p2; bool mValid; void print_cvmat_info( string msg, const cv::Mat& A ); string type2str(int type); cv::Mat m_K; //3x3 cv::Mat m_D; //4x1 Matrix3d e_K; Vector4d e_D; }; ================================================ FILE: src/ProcessedLoopCandidate.cpp ================================================ #include "ProcessedLoopCandidate.h" ProcessedLoopCandidate::ProcessedLoopCandidate( int idx_from_raw_candidates_list, DataNode * node_1, DataNode * node_2 ) { // cout << "ProcessedLoopCandidate::ProcessedLoopCandidate\n"; this->node_1 = node_1; this->node_2 = node_2; this->idx_from_raw_candidates_list = idx_from_raw_candidates_list; opX_b_T_a.clear(); debug_images.clear(); } bool ProcessedLoopCandidate::makeLoopEdgeMsg( cerebro::LoopEdge& msg ) { if( isSet_3d2d__2T1 == false ) { cout << TermColor::RED() << "[ProcessedLoopCandidate::makeLoopEdgeMsg]\ You asked me to make cerebro::LoopEdge of ProcessedLoopCandidate in\ which there is no valid pose data" << TermColor::RESET() << endl; return false; } msg.timestamp0 = node_1->getT(); msg.timestamp1 = node_2->getT(); geometry_msgs::Pose pose; PoseManipUtils::eigenmat_to_geometry_msgs_Pose( _3d2d__2T1, pose ); msg.pose_1T0 = pose; msg.weight = _3d2d__2T1__ransac_confidence; //1.0; msg.description = to_string(idx_from_datamanager_1)+"<=>"+to_string(idx_from_datamanager_2); msg.description += " this pose is: "+to_string(idx_from_datamanager_2)+"_T_"+to_string(idx_from_datamanager_1); return true; } // #define __ProcessedLoopCandidate_makeLoopEdgeMsgWithConsistencyCheck( msg ) msg; #define __ProcessedLoopCandidate_makeLoopEdgeMsgWithConsistencyCheck( msg ) ; bool ProcessedLoopCandidate::makeLoopEdgeMsgWithConsistencyCheck( cerebro::LoopEdge& msg ) { if( opX_b_T_a.size() != 3 ) { cout << TermColor::RED() << "[ProcessedLoopCandidate::makeLoopEdgeMsgWithConsistencyCheck] \ I was expecting only 3 candidates" << TermColor::RED() << endl; return false; } ros::Duration diff = node_1->getT() - node_2->getT(); if( abs(diff.sec) < 10 ) // the candidates are too near in time, so ignore them { __ProcessedLoopCandidate_makeLoopEdgeMsgWithConsistencyCheck( cout << "[ ProcessedLoopCandidate::makeLoopEdgeMsgWithConsistencyCheck] abs(diff.sec) < 10. diff= "<< diff << endl; ) return false; } /////////////////////////////////////////////////////////// // Look at all the options and determine if they are consistent. // if |del_angle| < 2.0 degrees and |del_translation| < 0.1m then not consistent Matrix4d op1__b_T_a = opX_b_T_a[0]; Matrix4d op2__b_T_a = opX_b_T_a[1]; Matrix4d icp_b_T_a = opX_b_T_a[2]; Matrix4d op1_m_op2 = op1__b_T_a.inverse() * op2__b_T_a; Matrix4d op1_m_icp = op1__b_T_a.inverse() * icp_b_T_a; Matrix4d op2_m_icp = op2__b_T_a.inverse() * icp_b_T_a; Vector3d op1_m_op2_ypr, op1_m_op2_tr; // ypr and translation of delta pose Vector3d op1_m_icp_ypr, op1_m_icp_tr; Vector3d op2_m_icp_ypr,op2_m_icp_tr; PoseManipUtils::eigenmat_to_rawyprt( op1_m_op2, op1_m_op2_ypr, op1_m_op2_tr); PoseManipUtils::eigenmat_to_rawyprt( op1_m_icp, op1_m_icp_ypr, op1_m_icp_tr); PoseManipUtils::eigenmat_to_rawyprt( op2_m_icp, op2_m_icp_ypr, op2_m_icp_tr); bool is_consistent_ypr = false, is_consistent_tr=false; if( op1_m_op2_ypr.lpNorm() < 5.0 && op1_m_icp_ypr.lpNorm() < 5.0 && op2_m_icp_ypr.lpNorm() < 5.0 ) { is_consistent_ypr = true;} if( op1_m_icp_tr.lpNorm() < .2 && op1_m_icp_tr.lpNorm() < .2 && op2_m_icp_tr.lpNorm() < .2 ) { is_consistent_tr = true;} #if 0 //just priniting __ProcessedLoopCandidate_makeLoopEdgeMsgWithConsistencyCheck( Matrix4d odom_b_T_a = node_2->getPose().inverse() * node_1->getPose(); cout << "---" << to_string(idx_from_datamanager_1)+"<=>"+to_string(idx_from_datamanager_2); cout << "[ProcessedLoopCandidate::makeLoopEdgeMsgWithConsistencyCheck]\n" << TermColor::YELLOW() << "odom_b_T_a = " << PoseManipUtils::prettyprintMatrix4d( odom_b_T_a ) << TermColor::RESET() << endl; cout << "op1" << "confidence=" << opX_goodness[0]<< " " << PoseManipUtils::prettyprintMatrix4d( op1__b_T_a ) << endl; cout << "op2" << "confidence=" << opX_goodness[1]<< " " << PoseManipUtils::prettyprintMatrix4d( op2__b_T_a ) << endl; cout << "icp" << "confidence=" << opX_goodness[2]<< " " << PoseManipUtils::prettyprintMatrix4d( icp_b_T_a ) << endl; cout << "|op1 - op2|" << PoseManipUtils::prettyprintMatrix4d( op1__b_T_a.inverse() * op2__b_T_a ) << endl; cout << "|op1 - icp|" << PoseManipUtils::prettyprintMatrix4d( op1__b_T_a.inverse() * icp_b_T_a ) << endl; cout << "|op2 - icp|" << PoseManipUtils::prettyprintMatrix4d( op2__b_T_a.inverse() * icp_b_T_a ) << endl; cout << TermColor::YELLOW() ; cout << "is_consistent_ypr=" << is_consistent_ypr << "\t" ; cout << "is_consistent_tr=" << is_consistent_tr << "\t"; cout << TermColor::RESET() << endl; ) #endif ///////////////////////////////// Done with consistency check ///////////////////// if( pf_matches > 800 && (is_consistent_ypr && is_consistent_tr) ) { // put the final pose into `3d2d__2T1` _3d2d__2T1 = opX_b_T_a[0]; isSet_3d2d__2T1 = true; _3d2d__2T1__ransac_confidence = max( max( opX_goodness[0], opX_goodness[1] ), opX_goodness[2] ); // call the above function, return makeLoopEdgeMsg( msg ); } else { cout << "[ ProcessedLoopCandidate::makeLoopEdgeMsgWithConsistencyCheck] Insonsistent poses from 3 methods, so don't publish this pose" << endl; return false; } } bool ProcessedLoopCandidate::asJson( json& out_json ) { json x; if( node_1 == NULL || node_2 == NULL ) return false; x["timestamp_a"] = node_1->getT().toSec(); x["timestamp_b"] = node_2->getT().toSec(); x["idx_a"] = idx_from_datamanager_1; x["idx_b"] = idx_from_datamanager_2; x["pf_matches"] = pf_matches; x["final_pose_available"] = isSet_3d2d__2T1; if( isSet_3d2d__2T1 ) { x["3d2d__2T1"] = PoseManipUtils::prettyprintMatrix4d( _3d2d__2T1 ); x["_3d2d__2T1__ransac_confidence"] = _3d2d__2T1__ransac_confidence; } // write the poses which were computed with various methods for( int i=0 ; i #include #include #include //opencv #include #include #include #include // ros // #include // #include // ros msg // #include // #include // #include // #include // #include // #include // #include // // #include // #include // #include // #include // Eigen #include #include #include using namespace Eigen; #include #include "DataNode.h" #include "utils/PoseManipUtils.h" // #include "utils/MiscUtils.h" #include "utils/TermColor.h" #include "../utils/nlohmann/json.hpp" using json = nlohmann::json; // Publishing message #include class ProcessedLoopCandidate { public: ProcessedLoopCandidate( int idx_from_raw_candidates_list, DataNode * node_1, DataNode * node_2 ); ProcessedLoopCandidate() { idx_from_raw_candidates_list=-1; opX_b_T_a.clear(); debug_images.clear(); } bool makeLoopEdgeMsg( cerebro::LoopEdge& msg ); // Looks at all the candidate relative poses and decides if they are all consistent. // Returns true if consistent, returns false if not consistent bool makeLoopEdgeMsgWithConsistencyCheck( cerebro::LoopEdge& msg ); bool asJson( json& out_json ); // private: DataNode * node_1; DataNode * node_2; int idx_from_datamanager_1; int idx_from_datamanager_2; int idx_from_raw_candidates_list; int pf_matches; //pf==>point-features int _3d2d_n_pfvalid_depth; Matrix4d _3d2d__2T1; //used 3d points from 1st view, 2d points from 2nd view. bool isSet_3d2d__2T1=false; //< final_pose_available float _3d2d__2T1__ransac_confidence; // std::mutex _mutex; // All computed poses vector opX_b_T_a; // b_T_a is same as 2T1 vector opX_goodness; vector opX_b_T_a_name; vector opX_b_T_a_debugmsg; // debug images vector debug_images; vector debug_images_titles; }; ================================================ FILE: src/Visualization.cpp ================================================ #include "Visualization.h" Visualization::Visualization( ros::NodeHandle &nh ) { b_run_thread = false; this->nh = nh; } void Visualization::setDataManager( DataManager* dataManager ) { this->dataManager = dataManager; m_dataManager_available = true; } void Visualization::setCerebro( Cerebro* cerebro ) { this->cerebro = cerebro; m_cerebro_available = true; } void Visualization::setVizPublishers( const string base_topic_name ) { // // Test Publisher string pub_topic_test = base_topic_name+"/chatter"; ROS_INFO( "Visualization Publisher pub_topic_test: %s", pub_topic_test.c_str() ); chatter_pub = nh.advertise(pub_topic_test, 1000); // // Publish Markers of frame data string framedata_pub_topic = base_topic_name+"/framedata"; ROS_INFO( "Visualization Publisher framedata_pub_topic: %s", framedata_pub_topic.c_str() ); framedata_pub = nh.advertise(framedata_pub_topic, 1000); // can add image publish if need be here. string imagepaire_pub_topic = base_topic_name+"/imagepaire"; ROS_INFO( "Visualization Publisher imagepair_pub_topic: %s", imagepaire_pub_topic.c_str() ); imagepaire_pub = nh.advertise(imagepaire_pub_topic, 1000); } void Visualization::run( const int looprate ) { assert( m_dataManager_available && "You need to set the DataManager in class Visualization before execution of the run() thread can begin. You can set the dataManager by call to Visualization::setDataManager()\n"); assert( b_run_thread && "you need to call run_thread_enable() before run() can start executing\n" ); assert( looprate > 0 && "[ Visualization::run] looprate need to be postive\n"); // Adjust these for debugging. bool bpub_framepositions = false; //< This publishes text-marker with node id bool bpub_loopcandidates = true; //< this publishes line marker bool bpub_processed_loopcandidates = true; //this publoshes the image-pair as image. ros::Rate rate( looprate ); while( b_run_thread ) { // cout << "Visualization::run() " << dataManager->getDataMapRef().size() <publish_frames(); if( bpub_loopcandidates ) this->publish_loopcandidates(); //< this publishes marker // this->publish_test_string(); if( bpub_processed_loopcandidates ) this->publish_processed_loopcandidates(); //< this publoshes the image-pair as image. rate.sleep(); } } // #define __Visualization___publish_processed_loopcandidates(msg) msg; #define __Visualization___publish_processed_loopcandidates(msg) ; void Visualization::publish_processed_loopcandidates() { assert( m_dataManager_available && m_cerebro_available && "You need to set cerebro and DataManager in class Visualization before execution of the run() thread can begin. You can set the cerebro by call to Visualization::setCerebro() and dataManager as setDataManager.\n"); static int prev_count = -1; if( prev_count == cerebro->processedLoops_count() || prev_count<0 ) { prev_count = cerebro->processedLoops_count(); // nothing new return; } int new_count = cerebro->processedLoops_count(); // [prev_count , new_count ] are new // cout << "[Visualization::publish_processed_loopcandidates]" << new_count << endl; __Visualization___publish_processed_loopcandidates( cout << "#new procs_loops=" << new_count - prev_count << " from [" << prev_count << "," << new_count-1 << "]\n"; ) visualization_msgs::Marker marker; RosMarkerUtils::init_line_marker( marker ); marker.ns = "processed_loopcandidates_line"; // loop over all the new int loop_start = prev_count; int loop_end = new_count; bool publish_image = true; bool publish_loop_line_marker = false; // if( rand()%100 < 2 ) { // loop_start=0; // publish_image=false; // } for( int i=loop_start ; i< loop_end; i++ ) { // publish green colored line ProcessedLoopCandidate candidate_i = cerebro->processedLoops_i( i ); __Visualization___publish_processed_loopcandidates( cout << "---\nUsing processedLoop["<getT() << "<--->" << candidate_i.node_2->getT() << endl; cout << "isPoseAvailable: " << candidate_i.node_1->isPoseAvailable() << endl; ) if( publish_loop_line_marker ) { Vector4d w_t_1 = candidate_i.node_1->getPose().col(3); Vector4d w_t_2 = candidate_i.node_2->getPose().col(3); RosMarkerUtils::add_point_to_marker( w_t_1, marker, true ); RosMarkerUtils::add_point_to_marker( w_t_2, marker, false ); RosMarkerUtils::setcolor_to_marker( 0.0, 1.0, 0.0 , marker ); marker.ns = "processed_loopcandidates_line"; marker.id = i; framedata_pub.publish( marker ); // Publish marker line if( candidate_i.isSet_3d2d__2T1 == false ) { cout << "[Visualization::publish_processed_loopcandidates] _3d2d__2T1 is not set. This means the final pose is not set. This is because the candidate relative poses do not appear to be consistent with each other. Ignoring this ProcessedLoopCandidate" << endl;; continue; } Matrix4d w_T_2__new = candidate_i.node_1->getPose() * (candidate_i._3d2d__2T1).inverse(); Vector4d w_t_2__new = w_T_2__new.col(3); RosMarkerUtils::add_point_to_marker( w_t_1, marker, true ); RosMarkerUtils::add_point_to_marker( w_t_2__new, marker, false ); RosMarkerUtils::setcolor_to_marker( 1.0, 1.0, 1.0 , marker ); marker.ns = "processed_loopcandidates_new_position_of_2"; marker.id = i; framedata_pub.publish( marker ); } // publish image if( publish_image) { #if 0 if( candidate_i.node_1->isImageAvailable() && candidate_i.node_2->isImageAvailable() ) #else auto img_data_mgr = dataManager->getImageManagerRef(); if( img_data_mgr->isImageRetrivable("left_image", candidate_i.node_1->getT()) && img_data_mgr->isImageRetrivable("left_image", candidate_i.node_2->getT()) ) #endif { #if 0 // code where data_node had image data, mark this for removal cv::Mat _node_1_im = candidate_i.node_1->getImage(); cv::Mat _node_2_im = candidate_i.node_2->getImage(); #else // image manager has the image data cv::Mat _node_1_im, _node_2_im; bool status1 = img_data_mgr->getImage( "left_image", candidate_i.node_1->getT(), _node_1_im ); bool status2 = img_data_mgr->getImage( "left_image", candidate_i.node_2->getT(), _node_2_im ); assert( status1 && status2 && "[Visualization::publish_processed_loopcandidates]\n"); #endif // use image from node cv::Mat side_by_side_impair; MiscUtils::side_by_side( _node_1_im, _node_2_im, side_by_side_impair ); string sgg = std::to_string( candidate_i.idx_from_datamanager_1 ) + " "+ std::to_string( candidate_i.idx_from_datamanager_2 ); string sgg_time = std::to_string( candidate_i.node_1->getT().toSec() ) + " "+ std::to_string( candidate_i.node_2->getT().toSec() ); MiscUtils::append_status_image( side_by_side_impair, ";;"+sgg, 2.0, cv::Scalar(0,0,0), cv::Scalar(255,255,255), 3.0 ); MiscUtils::append_status_image( side_by_side_impair, ";"+sgg_time+";;processedLoop["+to_string(i)+"]", 1.5, cv::Scalar(0,0,0), cv::Scalar(255,255,255), 3.0 ); if( candidate_i.isSet_3d2d__2T1 ) { MiscUtils::append_status_image( side_by_side_impair, ";Pose Estimation was consistent, so Accept", 1.5, cv::Scalar(0,0,0), cv::Scalar(0,255,0), 3.0 ); } else { MiscUtils::append_status_image( side_by_side_impair, ";Inconsistent Pose Estimation, so Reject", 1.5, cv::Scalar(0,0,0), cv::Scalar(0,0,255), 3.0 ); } cv::Mat buffer; cv::resize(side_by_side_impair, buffer, cv::Size(), 0.5, 0.5 ); cv_bridge::CvImage cv_image; cv_image.image = buffer; if( buffer.channels() == 1 ) cv_image.encoding = "mono8"; else if( buffer.channels() == 3 ) cv_image.encoding = "bgr8"; else { cout << TermColor::RED() << "[Visualization::publish_processed_loopcandidates]invalid number of channels EXIT\n"; exit(1); } sensor_msgs::Image ros_image_msg; cv_image.toImageMsg(ros_image_msg); __Visualization___publish_processed_loopcandidates( cout << MiscUtils::imgmsg_info( ros_image_msg ) << endl; ) imagepaire_pub.publish( ros_image_msg ); } } } prev_count = new_count; } // #define __Visualization__publish_loopcandidates(msg) msg #define __Visualization__publish_loopcandidates(msg) ; void Visualization::publish_loopcandidates() { assert( m_dataManager_available && m_cerebro_available && "You need to set cerebro and DataManager in class Visualization before execution of the run() thread can begin. You can set the cerebro by call to Visualization::setCerebro() and dataManager as setDataManager.\n"); // last 10 publish int n = cerebro->foundLoops_count(); int start = max( 0, n - 10 ); // 5% of the time start from 0. if( rand() % 100 < 2 ) start = 0; __Visualization__publish_loopcandidates(cout << "[Visualization::publish_loopcandidates] start=" << start << " end=" << n << endl;) if( n <= 0 ) return; auto data_map = dataManager->getDataMapRef(); visualization_msgs::Marker marker; RosMarkerUtils::init_line_marker( marker ); marker.ns = "loopcandidates_line"; RosMarkerUtils::setcolor_to_marker( 1.0, 0.0, 0.0 , marker ); // TODO If need be can also publish text for each (which could be score of the edge) // for( int i=0 ; ifoundLoops_i( i ); ros::Time t_curr = std::get<0>(u); ros::Time t_prev = std::get<1>(u); double score = std::get<2>(u); assert( data_map.count( t_curr ) > 0 && data_map.count( t_prev ) > 0 && "One or both of the timestamps in foundloops where not in the data_map. This cannot be happening...fatal...\n" ); int idx_1 = std::distance( data_map->begin(), data_map->find( t_curr ) ); int idx_2 = std::distance( data_map->begin(), data_map->find( t_prev ) ); Vector4d w_t_curr = data_map->at(t_curr)->getPose().col(3); Vector4d w_t_prev = data_map->at(t_prev)->getPose().col(3); // TODO - need to test. looks like alright. // add_point_to_marker with w_t_curr // add_point_to_marker with w_t_prev RosMarkerUtils::add_point_to_marker( w_t_curr, marker, true ); RosMarkerUtils::add_point_to_marker( w_t_prev, marker, false ); framedata_pub.publish( marker ); } } // #define __Visualization__publish_frames( cmd ) cmd #define __Visualization__publish_frames( cmd ) ; void Visualization::publish_frames() { assert( m_dataManager_available && "You need to set the DataManager in class Visualization before execution of the run() thread can begin. You can set the dataManager by call to Visualization::setDataManager()\n"); // Adjust these manually to change behaviour bool publish_camera_visual = false; bool publish_camera_as_point = true; bool publish_txt = true; bool publish_verbose_txt = false; static std::map< ros::Time, int > XC; __Visualization__publish_frames(cout << TermColor::RED() << "---" << TermColor::RESET() << endl;) __Visualization__publish_frames(cout << "start... sizeof(XC)=" << XC.size() << endl;) auto data_map = dataManager->getDataMapRef(); auto pose0 = dataManager->getPose0Stamp(); visualization_msgs::Marker cam_vis; //visualize every pose as a cam-visual RosMarkerUtils::init_camera_marker( cam_vis , .5 ); cam_vis.ns = "cam_pose_vis"; visualization_msgs::Marker pt_vis; //visualize every pose as a point. RosMarkerUtils::init_points_marker( pt_vis ); geometry_msgs::Point zer; zer.x =0.; zer.y=0.; zer.z = 0; pt_vis.points.push_back( zer ); pt_vis.ns = "cam_pose_pt"; pt_vis.scale.x = 0.015; pt_vis.scale.y = 0.015; visualization_msgs::Marker txt_vis; RosMarkerUtils::init_text_marker( txt_vis ); txt_vis.ns = "cam_pose_txt"; txt_vis.scale.z = 0.03; for( auto it = data_map->begin() ; it != data_map->end() ; it++ ) { if( XC[ it->first ] < 10 || rand() % 100 < 2 ) { // publish only if not already published 10 times int seq_id = std::distance( data_map->begin() , it ); cam_vis.id = seq_id; pt_vis.id = seq_id; txt_vis.id = seq_id; // Set Colors if( it->second->isKeyFrame() ) { RosMarkerUtils::setcolor_to_marker( 0., 1., 0. , cam_vis ); RosMarkerUtils::setcolor_to_marker( 0., 1., 0. , pt_vis ); RosMarkerUtils::setcolor_to_marker( 1., 1., 1. , txt_vis ); // if( it->second->isWholeImageDescriptorAvailable() ) // RosMarkerUtils::setcolor_to_marker( 0., 0, 1. , cam_vis ); } else { RosMarkerUtils::setcolor_to_marker( 1., 0., 0. , cam_vis ); RosMarkerUtils::setcolor_to_marker( 1., 0., 0. , pt_vis ); RosMarkerUtils::setcolor_to_marker( 1., 1., 1. , txt_vis ); } // Set Pose if( it->second->isPoseAvailable() ) { auto wTc = it->second->getPose(); RosMarkerUtils::setpose_to_marker( wTc , cam_vis ); RosMarkerUtils::setpose_to_marker( wTc , pt_vis ); RosMarkerUtils::setpose_to_marker( wTc , txt_vis ); txt_vis.text = ""; txt_vis.text += std::to_string(seq_id) + ";"; if( publish_verbose_txt ) { txt_vis.text += std::to_string( (it->first - pose0).toSec() ) +";"; txt_vis.text += std::to_string( (it->first).toSec() ) +";"; } if( publish_camera_visual ) framedata_pub.publish( cam_vis ); if( publish_camera_as_point ) framedata_pub.publish( pt_vis ); if( publish_txt || publish_verbose_txt ) framedata_pub.publish( txt_vis ); } if( it->second->isKeyFrame() ) __Visualization__publish_frames(cout << TermColor::GREEN() ); __Visualization__publish_frames( cout << "Publish seq_id=" << seq_id << "\t xc=" << XC[ it->first ] << "\t t="<< it->first - pose0 << "\t" << it->first << TermColor::RESET() << endl; ) XC[ it->first ]++; } } __Visualization__publish_frames( cout << "Done... sizeof(XC)=" << XC.size() << endl; ) } /* void Visualization::publish_frames() { assert( m_dataManager_available && "You need to set the DataManager in class Visualization before execution of the run() thread can begin. You can set the dataManager by call to Visualization::setDataManager()\n"); static std::map XC; static visualization_msgs::Marker linestrip_marker; RosMarkerUtils::init_line_strip_marker( linestrip_marker ); linestrip_marker.ns = "cam_line_strip"; linestrip_marker.id = 0; auto data_map = dataManager->getDataMapRef(); cout << "---\n"; if( data_map.begin() == data_map.end() ) { cout << "nothing to vizualize\n"; return; } // cout << data_map.rbegin()->first << "\t"<< data_map.rbegin()->first - dataManager->getPose0Stamp() << endl; ros::Time lb = data_map.rbegin()->first - ros::Duration(3); for( auto it = data_map.lower_bound( lb ); it != data_map.end() ; it++ ) { // cout << std::distance( it, data_map.begin() ) << "] " << it->first << "\t" << it->first - dataManager->getPose0Stamp() << endl; if( XC.count( it->first) == 0 ) { cout << "sizeof(XC)=" << XC.size() << " "<< it->first << "\t" << it->first - dataManager->getPose0Stamp() << endl; if( it->second->isPoseAvailable() ) { auto w_T_c = it->second->getPose(); geometry_msgs::Point pt; pt.x = w_T_c(0,3); pt.y = w_T_c(1,3); pt.z = w_T_c(2,3); linestrip_marker.points.push_back( pt ); framedata_pub.publish( linestrip_marker ); XC[it->first] = true; } } } return ; } */ void Visualization::publish_test_string() { std_msgs::String msg; std::stringstream ss; ss << "hello world " << ros::Time::now(); msg.data = ss.str(); chatter_pub.publish( msg ); } ================================================ FILE: src/Visualization.h ================================================ #pragma once /** Visualization class This holds a reference to the DataManager. Will publish messages to be seen with rviz. Author : Manohar Kuse Created : 29th Oct, 2018 based on https://github.com/mpkuse/nap/blob/master-desktop/src/DataManager_rviz_visualization.cpp */ #include #include // ros msg #include #include #include "utils/RosMarkerUtils.h" #include "utils/TermColor.h" #include "PinholeCamera.h" #include "DataManager.h" #include "Cerebro.h" class Visualization { public: Visualization( ros::NodeHandle &nh ); void setDataManager( DataManager* dataManager ); void setCerebro( Cerebro* cerebro ); void setVizPublishers( const string base_topic_name ); // This is supposed to be run in a separate thread. void run_thread_enable() { b_run_thread = true; } void run_thread_disable() { b_run_thread = false; } void run( const int looprate ); private: ros::NodeHandle nh; bool m_dataManager_available=false; DataManager * dataManager; bool m_cerebro_available=false; Cerebro * cerebro; atomic b_run_thread; private: void publish_frames(); //< publishes last 10 frames as markers. (occasionally all) void publish_loopcandidates(); //< publishes last 10 loop candidates. cerebro->foundLoops_count(). void publish_processed_loopcandidates(); //< uses Cerebro::processedLoops_i() and publishes only the newly found loops. cerebro->processedLoops_i( i ) void publish_test_string(); //publishers private: ros::Publisher chatter_pub; ros::Publisher framedata_pub; ros::Publisher imagepaire_pub; }; ================================================ FILE: src/cerebro_node.cpp ================================================ /** Main for cerebro node. This contains the main for the cerebro. It subscribes to various topics from VINS-estimator and setups of threads a) vis-thread, b) redis-association-thread, c) etc Author : Manohar Kuse Created : 26th Oct, 2018 **/ #include #include #include "PinholeCamera.h" #include "camodocal/camera_models/Camera.h" // this is in include/camodocal. src files in src/utils/camodocal_src #include "camodocal/camera_models/CameraFactory.h" #include #include "DataManager.h" #include "Cerebro.h" #include "Visualization.h" #include "utils/nlohmann/json.hpp" using json = nlohmann::json; int main( int argc, char ** argv ) { //--- ROS INIT ---// ros::init( argc, argv, "cerebro_node" ); ros::NodeHandle nh("~"); // ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Debug); //-- Set debug directory --// // string debug_output_dir; // nh.getParam( "debug_output_dir", debug_output_dir ); // ROS_WARN( "debug_output_dir : %s", debug_output_dir.c_str() ); //--- loadStateFromDisk, saveStateToDisk ---// string loadStateFromDisk = ""; if( nh.getParam( "loadStateFromDisk", loadStateFromDisk ) == true ) { if( loadStateFromDisk.compare("") == 0 ) { ROS_WARN( "[cerebro_node] loadStateFromDisk cmdline parameter was found, but with a empty string, so I will not loadStateFromDisk()"); } else { // now make sure it is a directory if( RawFileIO::is_path_a_directory(loadStateFromDisk) ) { ROS_INFO( "[cerebro_node] loadStateFromDisk=%s, Directory exists...OK!", loadStateFromDisk.c_str() ); cout << TermColor::GREEN() << "[cerebro_node] loadStateFromDisk=" << loadStateFromDisk << ", Directory exists...OK!" << TermColor::RESET() << endl; } else { ROS_ERROR( "[cerebro_node] You specified a directory for loadStateFromDisk=`%s`, This path need to exist\n...EXIT\n", loadStateFromDisk.c_str()); exit(1); } } } else { ROS_WARN( "[cerebro_node] loadStateFromDisk cmdline parameter was not found, so I will not loadStateFromDisk()"); } string saveStateToDisk = ""; if( nh.getParam( "saveStateToDisk", saveStateToDisk ) == true ) { if( saveStateToDisk.compare("") == 0 ) { ROS_WARN( "[cerebro_node] saveStateToDisk cmdline parameter was found, but with a empty string, so I will not saveStateToDisk()"); } else { // now make sure it is a directory if( RawFileIO::is_path_a_directory(saveStateToDisk) ) { ROS_INFO( "[cerebro_node] saveStateToDisk=%s, Directory exists...OK!", saveStateToDisk.c_str() ); cout << TermColor::GREEN() << "[cerebro_node] saveStateToDisk=" << saveStateToDisk << ", Directory exists...OK!" << TermColor::RESET() << endl; } else { ROS_ERROR( "[cerebro_node] You specified a directory for saveStateToDisk=`%s`, This path need to exist and be writable\n...EXIT\n", saveStateToDisk.c_str()); exit(1); } } } else { ROS_WARN( "[cerebro_node] saveStateToDisk cmdline parameter was not found, so I will not saveStateToDisk()"); } //--- END loadStateFromDisk, saveStateToDisk ---// //--- Config File ---// string config_file; if( !nh.getParam( "config_file", config_file ) ) { ROS_ERROR( "[cerebro_node] config_file cmdline parameter required to read the camera matrix.\nThis is fatal error\n..quit..." ); exit(1); } ROS_WARN( "Config File Name : %s", config_file.c_str() ); // check if file exists - depends on boost if ( !boost::filesystem::exists( config_file ) ) { std::cout << "[cerebro_node] Can't find my file: " << config_file << std::endl; ROS_ERROR_STREAM( "[cerebro_node] Can't find config_file:" << config_file ); exit(1); } cv::FileStorage fs(config_file, cv::FileStorage::READ); if( !fs.isOpened() ) { ROS_ERROR_STREAM( "[cerebro_node] cannot open config_file: "<< config_file << "\nThe file seems to be stated on the cmdline but it cannot be opened, possibly file is not found on the stated path or it does not have read permission\n...fatal...quit()\n" ); exit(1); } // --- Get yaml config for primary camera. (cam0) ---// camodocal::CameraPtr abstract_camera; // abstract_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(config_file); // assert( abstract_camera && "Even after loading yaml the camera seem invalid. Something wrong\n"); { string cam0_calib; if( !fs["cam0_calib"].isString() ) { ROS_WARN_STREAM( "[cerebro_node] cannot find key `cam0_calib` in config_file=" << config_file ); } else { fs["cam0_calib"] >> cam0_calib; ROS_INFO( "cam0_calib : %s", cam0_calib.c_str() ); vector ___path = MiscUtils::split( config_file, '/' ); // does::> $(python) '/'.join( ___path[0:-1] ) string ___cam0_path = string(""); for( int _i = 0 ; _i<___path.size()-1 ; _i++ ) { ___cam0_path += ___path[_i] + "/"; } ___cam0_path += "/"+cam0_calib; cout << "cam0_fullpath=" << ___cam0_path << endl; // check if file exists - depends on boost if ( !boost::filesystem::exists( ___cam0_path ) ) { std::cout << "[cerebro_node] Can't find my file for primary camera: " << ___cam0_path << std::endl; ROS_ERROR_STREAM( "[cerebro_node] Can't find my file for primary camera" << ___cam0_path ); exit(1); } // Make Abstract Camera // camodocal::CameraPtr abstract_camera_1; cout << TermColor::GREEN() << "Load file : " << ___cam0_path << TermColor::RESET() << endl; abstract_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(___cam0_path); assert( abstract_camera && "Even after loading yaml the camera seem invalid. Something wrong\n"); // dataManager.setAbstractCamera( abstract_camera_1, 1 ); } } // PinholeCamera camera = PinholeCamera( config_file ); // camera.printCameraInfo(2); // Not in use, using camodocal camera instead. //--- DataManager ---// DataManager dataManager = DataManager(nh); // dataManager.setCamera(camera); dataManager.setAbstractCamera( abstract_camera ); //--- Subscribers ---// TODO: move all subscribers this to dataManager class ? // [A] // w_T_c: Pose of camera (all, only a subset are keyframes) in world-cordinate system. string camera_pose_topic = string("/vins_estimator/camera_pose"); ROS_INFO( "Subscribe to camera_pose_topic: %s", camera_pose_topic.c_str() ); ros::Subscriber sub_odometry = nh.subscribe( camera_pose_topic, 1000, &DataManager::camera_pose_callback, &dataManager ); // // [A.1] TODO: not needed. consider not subscribing // // Marker for keyframes. Caution, these poses are of imu (NOT of camera). // string keyframe_pose_topic = string("/vins_estimator/keyframe_pose"); // ROS_INFO( "Subscribe to keyframe_camera_pose_topic: %s", keyframe_pose_topic.c_str() ); // ros::Subscriber sub_kf_odometry = nh.subscribe( keyframe_pose_topic, 1000, &DataManager::keyframe_pose_callback, &dataManager ); // [B] // Raw Image (all). The topic's name is in the config_file string raw_image_topic; if( !fs["image0_topic"].isString() ) { ROS_ERROR_STREAM( "[cerebro_node] cannot find key 'image0_topic' in config_file("<< config_file << ")\n...fatal...quit" ); exit(1); } fs["image0_topic"] >> raw_image_topic; ROS_INFO( "Subscribe to raw_image_topic: %s", raw_image_topic.c_str() ); ros::Subscriber sub_image = nh.subscribe( raw_image_topic, 10, &DataManager::raw_image_callback, &dataManager ); // [B.1] // Additional Image topic (stereo pair) string raw_image_topic_1; ros::Subscriber sub_image_1; if( !fs["image1_topic"].isString() ) { ROS_WARN_STREAM( "[cerebro_node] cannot find key `image1_topic` in config_file=" << config_file << ". This was optional so nothing to worry if you don't need the stereo pair" ); exit(1); } else { fs["image1_topic"] >> raw_image_topic_1; ROS_INFO( "Subscribe to image_topic_1: %s", raw_image_topic_1.c_str() ); sub_image_1 = nh.subscribe( raw_image_topic_1, 10, &DataManager::raw_image_callback_1, &dataManager ); } // [B.2] // Additional Image topic (realsense depth) 16UC1 string depth_image_topic = "/camera/depth/image_rect_raw"; ros::Subscriber sub_depth_image; ROS_INFO( "******\nSubscribe to depth_image_topic: %s\n********", depth_image_topic.c_str() ); sub_depth_image = nh.subscribe( depth_image_topic, 10, &DataManager::depth_image_callback, &dataManager ); // [B.2] // Additional Cameras (yaml) string camera_yaml_1; if( !fs["cam1_calib"].isString() ) { ROS_WARN_STREAM( "[cerebro_node] cannot find key `cam1_calib` in config_file=" << config_file << ". This was optional so nothing to worry if you don't need the 2nd camera" ); exit(1); } else { fs["cam1_calib"] >> camera_yaml_1; ROS_INFO( "cam1_calib : %s", camera_yaml_1.c_str() ); vector ___path = MiscUtils::split( config_file, '/' ); // does::> $(python) '/'.join( ___path[0:-1] ) string ___camera_yaml_1_path = string(""); for( int _i = 0 ; _i<___path.size()-1 ; _i++ ) { ___camera_yaml_1_path += ___path[_i] + "/"; } ___camera_yaml_1_path += "/"+camera_yaml_1; cout << "camera_yaml_1_fullpath=" << ___camera_yaml_1_path << endl; // check if file exists - depends on boost if ( !boost::filesystem::exists( ___camera_yaml_1_path ) ) { std::cout << "[cerebro_node] Can't find my file for additional camera: " << ___camera_yaml_1_path << std::endl; ROS_ERROR_STREAM( "[cerebro_node] Can't find my file for additional camera" << ___camera_yaml_1_path ); exit(1); } // Make Abstract Camera camodocal::CameraPtr abstract_camera_1; cout << TermColor::GREEN() << "Load file : " << ___camera_yaml_1_path << TermColor::RESET() << endl; abstract_camera_1 = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(___camera_yaml_1_path); assert( abstract_camera_1 && "Even after loading yaml the camera_1 seem invalid. Something wrong\n"); dataManager.setAbstractCamera( abstract_camera_1, 1 ); } // [B.3] // Camera baseline. Set stereobaseline transform ie. right_T_left from yaml file if( !fs["extrinsic_1_T_0"].isString() ) { ROS_WARN_STREAM( "[cerebro_node] cannot find key `extrinsic_1_T_0` in config file=" << config_file << " this was not needed for monocular camera, but needed for a stereo camera" ); cout << TermColor::RED() << "[cerebro_node] cannot find key `extrinsic_1_T_0` in config file=" << config_file << " this was not needed for monocular camera, but needed for a stereo camera" << TermColor::RESET() << endl; // exit(1); #if 1 // look for the existence of `body_T_cam0` and `body_T_cam1` cout << TermColor::YELLOW() << "Since, the key `extrinsic_1_T_0`, I am looking for the existence of `body_T_cam0` and `body_T_cam1`" << TermColor::RESET() << endl; if( fs["body_T_cam0"].empty() || fs["body_T_cam1"].empty() ) { cout << TermColor::RED() << "Either of the keys `body_T_cam0` or `body_T_cam1` cannot be found in config file. FATAL ERROR\n" << TermColor::RESET() ; exit(1); } cout << TermColor::iGREEN() << "The keys `body_T_cam0` and `body_T_cam1` exists, compute extrinsic_1_T_0 from these two as ` ee_body_T_cam1.inverse() * ee_body_T_cam0;`\n" << TermColor::RESET() ; cv::Mat body_T_cam0, body_T_cam1; fs["body_T_cam0"] >> body_T_cam0; fs["body_T_cam1"] >> body_T_cam1; Matrix4d ee_body_T_cam0, ee_body_T_cam1; cv::cv2eigen( body_T_cam0, ee_body_T_cam0 ); cv::cv2eigen( body_T_cam1, ee_body_T_cam1 ); Matrix4d cam1_T_cam0 = ee_body_T_cam1.inverse() * ee_body_T_cam0; cout << "Matrix4d cam1_T_cam0 = " << PoseManipUtils::prettyprintMatrix4d(cam1_T_cam0) << endl; { cout << "raw matrix of cam1_T_cam0:\n" << cam1_T_cam0 << "----" << endl; double q_xyzw[5], t_xyz[5]; PoseManipUtils::eigenmat_to_raw_xyzw( cam1_T_cam0, q_xyzw, t_xyz ); cout << "q_xyzw: " << q_xyzw[0] << "," << q_xyzw[1] << "," << q_xyzw[2] << "," << q_xyzw[3] << endl; cout << " t_xyz: " << t_xyz[0] << "," << t_xyz[1] << "," << t_xyz[2] << endl; } dataManager.setCameraRelPose( cam1_T_cam0, std::make_pair(1,0) ); #endif } else { // Extract the fname from the config_file string extrinsic_1_T_0; fs["extrinsic_1_T_0"] >> extrinsic_1_T_0; ROS_INFO( "in config_file=%s; extrinsic_1_T_0 : %s", config_file.c_str(), extrinsic_1_T_0.c_str() ); // Make full path from fname vector ___path = MiscUtils::split( config_file, '/' ); // does::> $(python) '/'.join( ___path[0:-1] ) string ___extrinsic_1_T_0_path = string(""); for( int _i = 0 ; _i<___path.size()-1 ; _i++ ) { ___extrinsic_1_T_0_path += ___path[_i] + "/"; } ___extrinsic_1_T_0_path += "/"+extrinsic_1_T_0; cout << "___extrinsic_1_T_0_fullpath=" << ___extrinsic_1_T_0_path << endl; // Open fullpath of extrinsic.yaml cout << "opencv yaml reading: open file: " << ___extrinsic_1_T_0_path << endl; cv::FileStorage fs(___extrinsic_1_T_0_path, cv::FileStorage::READ); if (!fs.isOpened()) { ROS_ERROR_STREAM( "config_file asked to open extrinsicbasline file but it cannot be opened.\nTHIS IS FATAL, QUITING" ); exit(1); } cout << TermColor::GREEN() << "successfully opened file "<< ___extrinsic_1_T_0_path << TermColor::RESET() << endl; cv::FileNode n = fs["transform"]; if( n.empty() ) { cout << TermColor::RED() << "I was looking for the key `transform` in the file but it doesnt seem to exist. FATAL ERROR" << TermColor::RESET() << endl; exit(1); } Vector4d q_xyzw; q_xyzw << (double)n["q_x"] , (double)n["q_y"] ,(double) n["q_z"] ,(double) n["q_w"]; Vector3d tr_xyz; tr_xyz << (double)n["t_x"] , (double)n["t_y"] ,(double) n["t_z"]; cout << "--values from file--\n" << TermColor::iGREEN(); cout << "q_xyzw:\n" << q_xyzw << endl; cout << "tr_xyz:\n" << tr_xyz << endl; cout << TermColor::RESET() << endl; Matrix4d _1_T_0; PoseManipUtils::raw_xyzw_to_eigenmat( q_xyzw, tr_xyz/1000., _1_T_0 ); cout << "translation divided by 1000 to convert from mm (in file) to meters (as needed)\n"; // cout << TermColor::iBLUE() << "_1_T_0:\n" << _1_T_0 << TermColor::RESET() << endl; cout << TermColor::iBLUE() << "_1_T_0: " << PoseManipUtils::prettyprintMatrix4d( _1_T_0 ) << TermColor::RESET() << endl; cout << "_1_T_0:\n" << _1_T_0 << endl; dataManager.setCameraRelPose( _1_T_0, std::make_pair(1,0) ); #if 0 // verify if it was correctly set --> Works! Verified ! cout << "isCameraRelPoseSet ? " << dataManager.isCameraRelPoseSet( std::make_pair(1,0) ) << endl; cout << "isCameraRelPoseSet ? " << dataManager.isCameraRelPoseSet( std::make_pair(6,10) ) << endl; // cout << "getCameraRelPose:\n" << dataManager.getCameraRelPose( std::make_pair(1,0) ) << endl; cout << "getCameraRelPose:\n" << PoseManipUtils::prettyprintMatrix4d( dataManager.getCameraRelPose( std::make_pair(1,0) ) ) << endl; auto ___all_available_keys = dataManager.getCameraRelPoseKeys(); cout << "# relative poses available = " << ___all_available_keys.size() << endl; #endif } // [C] // imu_T_cam : imu camera extrinsic calib. Will store this just in case there is a need string extrinsic_cam_imu_topic = string("/vins_estimator/extrinsic"); ROS_INFO( "Subscribe to extrinsic_cam_imu_topic: %s", extrinsic_cam_imu_topic.c_str() ); ros::Subscriber sub_extrinsic = nh.subscribe( extrinsic_cam_imu_topic, 1000, &DataManager::extrinsic_cam_imu_callback, &dataManager ); // [D] // PointCloud (all): has 5 channels string ptcld_topic = string("/vins_estimator/keyframe_point"); // string ptcld_topic = string("/feature_tracker/feature"); ROS_INFO( "Subscribe to ptcld_topic: %s", ptcld_topic.c_str() ); ros::Subscriber sub_ptcld = nh.subscribe( ptcld_topic, 1000, &DataManager::ptcld_callback, &dataManager ); // [E] // Tracked features. these contains a) unvn in msg->points b) chnl[0] id of the point c) chnl[1], chnl[2] := (u,v) d) chnl[3], chnl[4] not very sure. // string tracked_feat_topic = string("/feature_tracker/feature"); // ROS_INFO( "Subscribe to tracked_feat_topic: %s", tracked_feat_topic.c_str() ); // ros::Subscriber sub_tracked_feat = nh.subscribe( tracked_feat_topic, 1000, &DataManager::tracked_feat_callback, &dataManager ); // set this to 1 to enable loading state, set this to 0 to not load state // If you dont loadStateFromDisk, make sure you initialize the ImageDataManager. #define __LOAD_STATE__ 0 #if __LOAD_STATE__ //--- Load State From Disk dataManager.loadStateFromDisk( "/Bulk_Data/chkpts_cerebro" ); // cout << "PREMATURE EXIT\n"; // exit(1); #else dataManager.getImageManagerRef()->initStashDir(true); // this will use the default /tmp/cerebro_stash as the stashing directory #endif // If you dont loadStateFromDisk, make sure you initialize the ImageDataManager. if( loadStateFromDisk.compare("") != 0 ) { dataManager.loadStateFromDisk( loadStateFromDisk ); } else { //empty, fresh start dataManager.getImageManagerRef()->initStashDir(true); // this will use the default /tmp/cerebro_stash as the stashing directory } //--- Start Threads ---// // [A] // Data associate thread: looks at the callback buffers and sets the data in the std::map dataManager.data_association_thread_enable(); // dataManager.data_association_thread_disable(); std::thread data_association_th( &DataManager::data_association_thread, &dataManager, 15 ); dataManager.trial_thread_enable(); dataManager.trial_thread_disable(); std::thread dm_trial_th( &DataManager::trial_thread, &dataManager ); //< this threads prints datamanagers status to /dev/pts/20 modify as need be. // [A.1] Another thread in class dataManager which will deallocate images in nonkeyframes. dataManager.clean_up_useless_images_thread_enable(); // dataManager.clean_up_useless_images_thread_disable(); std::thread dm_cleanup_th( &DataManager::clean_up_useless_images_thread, &dataManager ); Cerebro cer( nh ); cer.setDataManager( &dataManager ); cer.setPublishers( "/cerebro" ); // [B.00] // Kidnap Message Publisher. See also comments below for `kidnap message subscriber` // Setup Publisher for sending kidnaped message to // a) vins_estimator and b) pose_graph solver string pub_topic_test = "/feature_tracker/rcvd_flag"; ROS_INFO( "main : Publisher pub_topic_test: %s", pub_topic_test.c_str() ); ros::Publisher rcvd_flag_pub = nh.advertise(pub_topic_test, 1000); // We publish std_msgs::Header to the pose-graph-solver. // The purpose is that, this will serve as carrier of timestamp according to which // we can eliminate the odometry edges from the cost function. string pub_topic_header = "/feature_tracker/rcvd_flag_header"; ROS_INFO( "main : Publisher pub_topic_header: %s", pub_topic_header.c_str() ); ros::Publisher kidnap_indicator_header_pub = nh.advertise(pub_topic_header, 1000); dataManager.setKidnapIndicatorPublishers( rcvd_flag_pub, kidnap_indicator_header_pub ); // [B.01] // Kidnap message subscriber. There are two kinds of kidnap messages // a. Bool b. Header. Look at the documentation of the kidnaped thread to know the details. // This has been done only for compatibility. I (mpkuse) prefer the Header because it // can also contain the timestamp when kidnap started and ended. string kidnap_bool_topic = "/feature_tracker/rcvd_flag"; ROS_INFO( "Subscribe to kidnap_bool_topic: %s", kidnap_bool_topic.c_str() ); ros::Subscriber sub_kidnap_bool = nh.subscribe( kidnap_bool_topic, 1000, &Cerebro::kidnap_bool_callback, &cer ); string kidnap_header_topic = "/feature_tracker/rcvd_flag_header"; ROS_INFO( "Subscribe to kidnap_header_topic: %s", kidnap_header_topic.c_str() ); ros::Subscriber sub_kidnap_header = nh.subscribe( kidnap_header_topic, 1000, &Cerebro::kidnap_header_callback, &cer ); // [B] // Descriptor Computation Thread. // It monitors data_map. If new keyframes are available then // it queries the ros-service with the image to get the whole-image-descriptor. // This whole image descriptor is stored in `node->setWholeImageDescriptor` // and the index of computation stored in `wholeImageComputedList`. cer.descriptor_computer_thread_enable(); // cer.descriptor_computer_thread_disable(); std::thread desc_th( &Cerebro::descriptor_computer_thread, &cer ); //runs @20hz // [C] // loopcandidates producer // This thread looks at `wholeImageComputedList`, if there is something new // in it (new descriptors) it does dot( 0-->T-50, T ), dot( 0-->T-50, T-1 ) // and dot( 0-->T-50, T-2 ). If threshold-test and locality-test both // comeout to be +ve it declares 'loop found' and queues up this pair into the // list `foundLoops` cer.run_thread_enable(); // cer.run_thread_disable(); std::thread dot_product_th( &Cerebro::run, &cer ); //< descrip_N__dot__descrip_0_N. runs @ 10hz // [C.1] // loopcandidates consumer // Monitors the list `foundLoops` aka the putative loop candidates. // If new candidates are present in the list it computes the relative-pose // using GMSMatcher and theia-sfm's pnp. The depth is computed from stereogeom (class StereoGeometry) cer.loopcandidate_consumer_enable(); // cer.loopcandidate_consumer_disable(); std::thread loopcandidate_consumer_th( &Cerebro::loopcandiate_consumer_thread, &cer ); // runs @1hz // [D] // Kidnap Identification Thread cer.kidnaped_thread_enable(); // cer.kidnaped_thread_disable(); std::thread kidnap_th( &Cerebro::kidnaped_thread, &cer, 5 ); // [E] // Visualization - Visualization viz(nh); viz.setDataManager( &dataManager ); viz.setCerebro( &cer ); viz.setVizPublishers( "/cerebro_node/viz/" ); viz.run_thread_enable(); // viz.run_thread_disable(); std::thread viz_th( &Visualization::run, &viz, 10 ); //TODO something wrong with the logic in publish. another solution could be we keep #seq in DataNode. fs.release(); ros::spin(); // seem like user has pressed CTRL+C, so, stop all threads. dataManager.data_association_thread_disable(); dataManager.trial_thread_disable(); dataManager.clean_up_useless_images_thread_disable(); #if 1 cer.run_thread_disable(); cer.descriptor_computer_thread_disable(); cer.loopcandidate_consumer_disable(); cer.kidnaped_thread_disable(); viz.run_thread_disable(); #endif data_association_th.join(); cout << "data_association_th.join()\n"; dm_trial_th.join(); cout << "t1_trial.join()\n"; dm_cleanup_th.join(); cout << "dm_cleanup_th.join()\n"; #if 1 dot_product_th.join(); cout << "dot_product_th.join()\n"; desc_th.join(); cout << "desc_th.join()\n"; loopcandidate_consumer_th.join(); cout << "loopcandidate_consumer_th.join()\n"; kidnap_th.join(); cout << "kidnap_th.join()\n"; viz_th.join(); cout << "viz_th.join()\n"; #endif //make this to 1 to save state to file upon exit #define __SAVE_STATE__ 0 #if __SAVE_STATE__ // Save State (for relocalization) dataManager.saveStateToDisk( "/Bulk_Data/chkpts_cerebro" ); #endif if( saveStateToDisk.compare("") != 0 ) { dataManager.saveStateToDisk( saveStateToDisk ); } /////////////////////////////////////////////////// // A demo of how to look inside dataManager. // /////////////////////////////////////////////////// #if 0 { std::map< ros::Time, DataNode* > data_map = dataManager.getDataMapRef(); // auto descriptor_map = cer.getDescriptorMapRef(); for( auto it = data_map.begin() ; it!= data_map.end() ; it++ ) { cout << TermColor::RED() << "---" << TermColor::RESET() << endl; cout << "Map-Key: " << it->first << "\tseq=" << std::distance( data_map.begin(), it ) << "\t" << it->first - dataManager.getPose0Stamp() << endl; cout << "Map-Value:\n"; it->second->prettyPrint( ); if( false && it->second->isImageAvailable() ) { cv::imshow( "win", it->second->getImage() ); cv::waitKey(30); } } // Printing Global Variables cout << "Pose0 : isAvailable=" << dataManager.isPose0Available() << "\t"; cout << "stamp=" << dataManager.getPose0Stamp() ; cout << endl; cout << "Camera:\n" ; dataManager.getCameraRef().printCameraInfo(2); cout << "IMUCamExtrinsic : isAvailable=" << dataManager.isIMUCamExtrinsicAvailable() << "\t"; cout << "last updated : " << dataManager.getIMUCamExtrinsicLastUpdated() << "\t" << dataManager.getIMUCamExtrinsicLastUpdated() -dataManager.getPose0Stamp() ; cout << "\nimu_T_cam : \n" << PoseManipUtils::prettyprintMatrix4d( dataManager.getIMUCamExtrinsic() ) << endl; } #endif /////////////////////// // Actual Logging. // ////////////////////// #define __LOGGING__ 0 // make this 1 to enable logging. 0 to disable logging. rememeber to catkin_make after this change #if __LOGGING__ // Note: If using roslaunch to launch this node and when LOGGING is enabled, // roslaunch sends a sigterm and kills this thread when ros::ok() returns false ie. // when you press CTRL+C. The timeout is governed by roslaunch. // // If you wish to increase this timeout, you need to edit "/opt/ros/kinetic/lib/python2.7/dist-packages/roslaunch/nodeprocess.py" // then edit these two vars. // _TIMEOUT_SIGINT = 15.0 #seconds // _TIMEOUT_SIGTERM = 2.0 #seconds // ^^^^ Borrowed from : https://answers.ros.org/question/11353/how-to-delay-escalation-to-sig-term-after-sending-sig-int-to-roslaunch/ // Write json log // string save_dir = "/Bulk_Data/_tmp/"; string save_dir = "/tmp/_tmp/"; ROS_INFO( "cerebro_node logging to : %s.\nThis will take upto 2 minutes.", save_dir.c_str()); ROS_ERROR( "cerebro_node logging to : %s.\nThis will take upto 2 minutes.", save_dir.c_str()); ROS_WARN( "cerebro_node logging to : %s.\nThis will take upto 2 minutes.", save_dir.c_str()); // // $ rm -rf ${save_dir} // $ mkdir ${save_dir} #include string system_cmd; system_cmd = string("rm -rf "+save_dir).c_str(); // cout << TermColor::YELLOW() << system_cmd << TermColor::RESET() << endl; // int rm_dir_err = system( system_cmd.c_str() ); const int rm_dir_err = RawFileIO::exec_cmd( system_cmd ); if ( rm_dir_err == -1 ) { cout << TermColor::RED() << "[cerebro_node] Error removing directory!\n" << TermColor::RESET() << endl; exit(1); } system_cmd = string("mkdir -p "+save_dir).c_str(); // cout << TermColor::YELLOW() << system_cmd << TermColor::RESET() << endl; // const int dir_err = system( system_cmd.c_str() ); const int dir_err = RawFileIO::exec_cmd( system_cmd ); if ( dir_err == -1 ) { cout << TermColor::RED() << "[cerebro_node] Error creating directory!\n" << TermColor::RESET() << endl; exit(1); } // done emptying the directory. RawFileIO::write_string( save_dir+"/log.json", dataManager.asJson().dump(4) ); // RawFileIO::write_string( save_dir+"/log.json", dataManager.metaDataAsJson() ); // RawFileIO::write_string( save_dir+"/log.txt", dataManager.metaDataAsFlatFile() ); //TODO remove. Since i can read json in python as well as c++ with ease, these is no point of storing stuff as txt // return 0; #if 1 // std::map< ros::Time, DataNode* > data_map = dataManager.getDataMapRef(); //old - can remove auto data_map = dataManager.getDataMapRef(); auto img_data_mgr = dataManager.getImageManagerRef(); for( auto it = data_map->begin() ; it!= data_map->end() ; it++ ) { int seq_id = std::distance( data_map->begin() , it ); string fname = save_dir+"/"+to_string(seq_id ); #if 0 //old code where we used images inside Nodes. - mark for removal if( it->second->isImageAvailable() ) RawFileIO::write_image( fname+".jpg", it->second->getImage() ); // additional image if( it->second->isImageAvailable(1) ) RawFileIO::write_image( fname+"_1.jpg", it->second->getImage(1) ); #else if( img_data_mgr->isImageRetrivable( "left_image", it->first ) ) { cv::Mat outimg; img_data_mgr->getImage( "left_image", it->first, outimg ); RawFileIO::write_image( fname+".jpg", outimg ); } if( img_data_mgr->isImageRetrivable( "right_image", it->first ) ) { cv::Mat outimg; img_data_mgr->getImage( "right_image", it->first, outimg ); RawFileIO::write_image( fname+"_1.jpg", outimg ); } #endif // Save VINS pose if( it->second->isPoseAvailable() ) { RawFileIO::write_EigenMatrix( fname+".wTc", it->second->getPose() ); } // Save Point Cloud #if 0 if( it->second->isPtCldAvailable() ) { RawFileIO::write_EigenMatrix( fname+".wX.pointcloud", it->second->getPointCloud() ); if( it->second->isPoseAvailable() ) { RawFileIO::write_EigenMatrix( fname+".cX.pointcloud", it->second->getPose().inverse() * it->second->getPointCloud() ); } else ROS_WARN( "ptclod is available but pose is not available at seq_id=%d", seq_id ); } #endif // Save Tracked Points #if 0 if( it->second->isUVAvailable() ) { RawFileIO::write_EigenMatrix( fname+".unvn", it->second->getUnVn() ); RawFileIO::write_EigenMatrix( fname+".uv", it->second->getUV() ); RawFileIO::write_EigenMatrix( fname+".id", it->second->getFeatIds() ); } // Save Whole Image Descriptor if( it->second->isWholeImageDescriptorAvailable() ) { RawFileIO::write_EigenMatrix( fname+".imgdesc", it->second->getWholeImageDescriptor() ); } #endif } #endif // Save Camera Matrix and IMUCamExtrinsic vector cam_keys = dataManager.getAbstractCameraKeys(); for( short _icam=0 ; _icam < cam_keys.size() ; _icam++ ) { short key=cam_keys[_icam]; if( dataManager.isAbstractCameraSet(key) ) { auto abstract_camera = dataManager.getAbstractCameraRef(key); RawFileIO::write_string( save_dir+"/cameraIntrinsic."+std::to_string(key)+".info", abstract_camera->parametersToString() ); abstract_camera->writeParametersToYamlFile( save_dir+"/cameraIntrinsic."+std::to_string(key)+".yaml" ); } } // Save Camera-IMU Extrinsics cout << "IMUCamExtrinsic : isAvailable=" << dataManager.isIMUCamExtrinsicAvailable() << "\t"; cout << "last updated : " << dataManager.getIMUCamExtrinsicLastUpdated() << "\t" << dataManager.getIMUCamExtrinsicLastUpdated() -dataManager.getPose0Stamp() ; cout << "\nimu_T_cam : \n" << PoseManipUtils::prettyprintMatrix4d( dataManager.getIMUCamExtrinsic() ) << endl; if( dataManager.isIMUCamExtrinsicAvailable() ) { RawFileIO::write_EigenMatrix( save_dir+"/IMUCamExtrinsic.imu_T_cam", dataManager.getIMUCamExtrinsic() ); std::stringstream buffer; buffer << "IMUCamExtrinsicLastUpdated: "<< dataManager.getIMUCamExtrinsicLastUpdated() << endl; buffer << "IMUCamExtrinsicLastUpdated Rel: "<< dataManager.getIMUCamExtrinsicLastUpdated() -dataManager.getPose0Stamp() << endl; RawFileIO::write_string( save_dir+"/IMUCamExtrinsic.info", buffer.str() ); } else { ROS_ERROR( "[cerebro_node.cpp] IMUCamExtrinsic appear to be not set...something seem wrong\n" ); } // Save foundLoops from Cerebro class json jsonout_obj = cer.foundLoops_as_JSON(); RawFileIO::write_string( save_dir+"/loopcandidates_liverun.json", jsonout_obj.dump(4) ); // Save kidnap info json json_kidnap_info = cer.kidnap_info_as_json(); RawFileIO::write_string( save_dir+"/kidnap_info.json", json_kidnap_info.dump(4) ); // Save matching images from ProcessedLoopCandidate string cmd_ = string("mkdir -p ")+save_dir+"/matching/"; RawFileIO::exec_cmd( cmd_ ); //****** // If you are looking to ***save*** these matchings image pairs // besure to set the #define just before `Cerebro::loopcandiate_consumer_thread()`. json all_procloops_json_obj; for( int i=0 ; i 0 && __imm.cols > 0 ) { RawFileIO::write_image( save_dir+"/matching/im_pair_"+to_string(i)+"_"+c.debug_images_titles[l]+".jpg", __imm ); } else { cout << "in logging, matching_im_pair of ProcessedLoopCandidate[" << i << "] is not available\n"; } } // Json of the object json procloops_json_obj; if( c.asJson(procloops_json_obj) ) { procloops_json_obj["processedLoops_i"] = i; all_procloops_json_obj.push_back( procloops_json_obj ); } else { cout << "cannot make json for this ProcessedLoopCandidate[" << i << "]\n"; } // Write the final poses as files if( c.isSet_3d2d__2T1 ) RawFileIO::write_EigenMatrix( save_dir+"/matching/im_pair_"+to_string(i)+".3d2d__2T1", c._3d2d__2T1 ); // Write final pose status image. string __final_pose_status_im_string = ""; if( c.isSet_3d2d__2T1 ) { __final_pose_status_im_string = "TRUE (poses were consistent);"; __final_pose_status_im_string += PoseManipUtils::prettyprintMatrix4d( c._3d2d__2T1 ); } else { __final_pose_status_im_string = "FALSE (non consistent poses);"; } cv::Mat __final_pose_status_im = cv::Mat::zeros(cv::Size(400, 20), CV_8UC3); MiscUtils::append_status_image( __final_pose_status_im, __final_pose_status_im_string ); RawFileIO::write_image( save_dir+"/matching/im_pair_"+to_string(i)+"_aaconsistency_status.jpg", __final_pose_status_im ); } RawFileIO::write_string( save_dir+"/matching/ProcessedLoopCandidate.json", all_procloops_json_obj.dump(4) ); #endif return 0 ; } ================================================ FILE: src/unittest/unittest_camera_geom_class_usage.cpp ================================================ #include using namespace std; #include #include #include "camodocal/camera_models/Camera.h" #include "camodocal/camera_models/CameraFactory.h" #include "camodocal/camera_models/CataCamera.h" #include "camodocal/camera_models/EquidistantCamera.h" //opencv #include #include #include #include #include "../utils/MiscUtils.h" #include "../utils/ElapsedTime.h" #include "../utils/PoseManipUtils.h" #include "../utils/TermColor.h" #include "../utils/CameraGeometry.h" #include "../utils/RawFileIO.h" // #include "gms_matcher.h" #include #include using namespace ceres; // Monocular int monocular_demo() { const std::string BASE = "/Bulk_Data/_tmp/"; // Abstract Camera camodocal::CameraPtr m_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.0.yaml"); cout << m_camera->parametersToString() << endl; // Init Monocular Geometry MonoGeometry monogeom( m_camera ); // Raw Image - Image from camera int frame_id = 23; cv::Mat im_raw = cv::imread( BASE+"/"+std::to_string(frame_id)+".jpg" ); cv::Mat im_undistorted; monogeom.do_image_undistortion( im_raw, im_undistorted ); cv::imshow( "im_raw", im_raw ); cv::imshow( "im_undistorted", im_undistorted ); cv::waitKey(0); } // Stereo int stereo_demo() { const std::string BASE = "/Bulk_Data/_tmp/"; //--------- Intrinsics load camodocal::CameraPtr left_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.0.yaml"); camodocal::CameraPtr right_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.1.yaml"); cout << left_camera->parametersToString() << endl; cout << right_camera->parametersToString() << endl; //----------- Stereo Base line load (alsoed called extrinsic calibration) // mynt eye // TODO function to load extrinsic from yaml file. // Vector4d q_xyzw = Vector4d( -1.8252509868889259e-04,-1.6291774489779708e-03,-1.2462127842978489e-03,9.9999787970731446e-01 ); // Vector3d tr_xyz = Vector3d( -1.2075905420832895e+02/1000.,5.4110610639412482e-01/1000.,2.4484815673909591e-01/1000. ); Vector4d q_xyzw = Vector4d( -7.0955103716253032e-04,-1.5775578725333590e-03,-1.2732644461763854e-03,9.9999769332040711e-01 ); Vector3d tr_xyz = Vector3d( -1.2006984141573309e+02/1000.,3.3956264524978619e-01/1000.,-1.6784055634087214e-01/1000. ); Matrix4d right_T_left; PoseManipUtils::raw_xyzw_to_eigenmat( q_xyzw, tr_xyz, right_T_left ); std::shared_ptr stereogeom; stereogeom = std::make_shared( left_camera,right_camera, right_T_left ); stereogeom->set_K( 375.0, 375.0, 376.0, 240.0 ); int frame_id = 1005; cout << "READ IMAGE " << frame_id << endl; cv::Mat imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_id)+".jpg", 0 ); cv::Mat imright_raw = cv::imread( BASE+"/"+std::to_string(frame_id)+"_1.jpg", 0 ); cout << "imleft_raw: " << MiscUtils::cvmat_info( imleft_raw ) << endl; cout << "imright_raw: " << MiscUtils::cvmat_info( imright_raw ) << endl; if( imleft_raw.empty() || imright_raw.empty() ) { cout << "imleft_raw is empty OR imright_raw is empty\n"; return 0; } // will get 3d points, stereo-rectified image, and disparity false colormap MatrixXd _3dpts; //4xN cv::Mat imleft_srectified, imright_srectified; cv::Mat disparity_for_visualization; ElapsedTime timer; timer.tic(); stereogeom->get_srectifiedim_and_3dpoints_and_disparity_from_raw_images(imleft_raw, imright_raw, imleft_srectified, imright_srectified, _3dpts, disparity_for_visualization ); cout << timer.toc_milli() << " (ms)!! get_srectifiedim_and_3dpoints_and_disparity_from_raw_images\n"; cv::imshow( "imleft_srectified", imleft_srectified ); cv::imshow( "imright_srectified", imright_srectified ); cv::imshow( "disparity_for_visualization", disparity_for_visualization ); cv::waitKey(0); return 0; } int main() { cout << "Gello Horld\n"; // monocular_demo(); stereo_demo(); } ================================================ FILE: src/unittest/unittest_camodocal_proj.cpp ================================================ // A usage of my Stereo class. #include using namespace std; /** Experiments to project 3d points on images. */ #include #include "../utils/PoseManipUtils.h" #include "../utils/RawFileIO.h" #include "../utils/MiscUtils.h" // #include "PinholeCamera.h" #include "camodocal/camera_models/Camera.h" #include "camodocal/camera_models/CameraFactory.h" // const string base_path = "/Bulk_Data/_tmp_cerebro/tum_magistrale1/"; const string base_path = "/Bulk_Data/_tmp/"; // TODO Should return read status, ie. false if file-not-found bool load_i( int i, cv::Mat& im, Matrix4d& wTc, MatrixXd& uv, MatrixXd& cX ) { string fname = base_path+"/"+std::to_string( i ); // Image im = cv::imread( fname+".jpg" ); // cv::imshow( "win", im ); // VINS-pose (odometry) RawFileIO::read_eigen_matrix( fname+".wTc", wTc ); // 3D points - in camera cords RawFileIO::read_eigen_matrix( fname+".cX.pointcloud", cX ); // 2D points - tracked points RawFileIO::read_eigen_matrix( fname+".uv", uv ); } // TODO Should return read status, ie. false if file-not-found bool load_i( int i, cv::Mat& im, Matrix4d& wTc, MatrixXd& uv, MatrixXd& cX, VectorXi& uv_ids ) { string fname = base_path+"/"+std::to_string( i ); // Image im = cv::imread( fname+".jpg" ); // cv::imshow( "win", im ); // VINS-pose (odometry) RawFileIO::read_eigen_matrix( fname+".wTc", wTc ); // 3D points - in camera cords RawFileIO::read_eigen_matrix( fname+".cX.pointcloud", cX ); // 2D points - tracked points RawFileIO::read_eigen_matrix( fname+".uv", uv ); // IDs RawFileIO::read_eigen_matrix( fname+".id", uv_ids ); } int main() { // // Load Camera - from yaml std::string calib_file = base_path+"/cameraIntrinsic.0.yaml"; camodocal::CameraPtr m_camera; m_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(calib_file); std::cout << ((m_camera)?"cam is initialized":"cam is not initiazed") << std::endl; //this should print 'initialized' // Load 1st int _idx_1 = 1331; cv::Mat im_1; Matrix4d wTc_1; MatrixXd uv_1, cX_1; VectorXi uv_id_1; load_i( _idx_1, im_1, wTc_1, uv_1, cX_1, uv_id_1 ); // cv::imshow( "533", im_1 ); { cv::Mat dst; MiscUtils::plot_point_sets( im_1, uv_1, dst, cv::Scalar(255,0,0), uv_id_1, "self uv" ); cv::imshow( std::to_string(_idx_1), dst ); } // // Load 2nd int _idx_2 = 839; cv::Mat im_2; Matrix4d wTc_2; MatrixXd uv_2, cX_2; VectorXi uv_id_2; // load_i( 536, im_2, wTc_2, uv_2, cX_2, uv_id_2 ); // load_i( 887, im_2, wTc_2, uv_2, cX_2 ); load_i( _idx_2, im_2, wTc_2, uv_2, cX_2, uv_id_2 ); // cv::imshow( "535", im_2 ); { cv::Mat dst; MiscUtils::plot_point_sets( im_2, uv_2, dst, cv::Scalar(255,255,255), uv_id_2, "self uv" ); cv::imshow( std::to_string(_idx_2), dst ); } // project 1st 3d points using camodocal MatrixXd reprojected_uv_1 = MatrixXd::Zero( 2, cX_1.cols() ); for( int i=0 ; i using namespace std; #include "../utils/Plot2Mat.h" int demo() { cout << "Hello\n"; Plot2Mat han; han.setYminmax( -2, 2 ); for( int i=10; i<50; i++ ) { VectorXd y = VectorXd::Random(10*i); cout << "y=" << y.transpose() << endl; han.mark( i ); han.plot(y); cv::imshow("canvas", han.getCanvasConstPtr() ); cv::waitKey(0); han.resetCanvas(); } cout << "Finished...!\n"; } int demo_easy() { cout <<"demo_easy\n"; Plot2Mat han; han.setYminmax( -2, 2 ); VectorXd y = VectorXd::Random(10); cout << "y=" << y.transpose() << endl; han.mark( 4 ); han.plot(y, cv::Scalar(0,255,0), true, true); // han.plot(y); cv::imshow("canvas", han.getCanvasConstPtr() ); cv::waitKey(0); } int main() { demo_easy(); // demo(); } ================================================ FILE: src/unittest/unittest_pose_tester.cpp ================================================ // This unittest will test and evaluate // ` StaticTheiaPoseCompute::P3P_ICP` and `StaticTheiaPoseCompute::PNP` // from DlsPnpWithRansac.h/cpp // Author : Manohar Kuse // Created : 14th May, 2019 // #include #include #include "camodocal/camera_models/Camera.h" #include "camodocal/camera_models/CameraFactory.h" #include "camodocal/camera_models/CataCamera.h" #include "camodocal/camera_models/EquidistantCamera.h" //opencv #include #include #include #include // Eigen #include #include using namespace Eigen; using namespace std; // custom #include "../utils/CameraGeometry.h" #include "../utils/TermColor.h" #include "../utils/RawFileIO.h" #include "../utils/PointFeatureMatching.h" // #include "../utils/GMSMatcher/gms_matcher.h" #include "../DlsPnpWithRansac.h" const std::string BASE = "/Bulk_Data/_tmp/"; std::shared_ptr make_stereo_geom() { //--------- Intrinsics load camodocal::CameraPtr left_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.0.yaml"); camodocal::CameraPtr right_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.1.yaml"); // camodocal::CameraPtr left_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(string(BASE+"../leveled_cam_pinhole/")+"/camera_left.yaml"); // camodocal::CameraPtr right_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(string(BASE+"../leveled_cam_pinhole/")+"/camera_right.yaml"); cout << left_camera->parametersToString() << endl; cout << right_camera->parametersToString() << endl; if( !left_camera || !right_camera ) { cout << "\nERROR : Abstract stereo Camera is not set. You need to init camera before setting it to geometry clases\n"; exit(10); } //----------- Stereo Base line load (alsoed called extrinsic calibration) // mynt eye // Vector4d q_xyzw = Vector4d( -7.0955103716253032e-04,-1.5775578725333590e-03,-1.2732644461763854e-03,9.9999769332040711e-01 ); // Vector3d tr_xyz = Vector3d( -1.2006984141573309e+02/1000.,3.3956264524978619e-01/1000.,-1.6784055634087214e-01/1000. ); // realsense Vector4d q_xyzw = Vector4d( 0.0, 0.0, 0.0, 1.0 ); Vector3d tr_xyz = Vector3d( -49.8/1000., 0.0/1000., 0.0/1000. ); Matrix4d right_T_left; PoseManipUtils::raw_xyzw_to_eigenmat( q_xyzw, tr_xyz, right_T_left ); cout << "XXright_T_left: " << PoseManipUtils::prettyprintMatrix4d( right_T_left ) << endl; // StereoGeometry* sthm = new StereoGeometry( left_camera, right_camera, right_T_left ); // cout << "sthn->get_K() " << sthm->get_K() << endl; // StereoGeometry NNN = StereoGeometry( left_camera, right_camera, right_T_left ); // cout << "NNN->get_K() " << NNN.get_K() << endl; //-------------------- init stereogeom std::shared_ptr stereogeom; stereogeom = std::make_shared( left_camera,right_camera, right_T_left ); // stereogeom->set_K( 375.0, 375.0, 376.0, 240.0 ); cout << "[in make_stereo_geom]" << stereogeom->get_K() << endl; return stereogeom; } bool compute_rel_pose( std::shared_ptr stereogeom, int frame_a, int frame_b ) { //------------------------------------------------ //------ 3d points from frame_a //------------------------------------------------ cout << TermColor::iGREEN() << "LOAD[A]: BASE/"+std::to_string(frame_a)+".jpg" << TermColor::RESET() << endl; cv::Mat a_imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_a)+".jpg", 0 ); cv::Mat a_imright_raw = cv::imread( BASE+"/"+std::to_string(frame_a)+"_1.jpg", 0 ); Matrix4d wTA; RawFileIO::read_eigen_matrix( BASE+"/"+std::to_string(frame_a)+".wTc", wTA ); cout << "wTA: " << PoseManipUtils::prettyprintMatrix4d( wTA ) << endl; if( a_imright_raw.empty() || a_imright_raw.empty() ) { cout << TermColor::RED() << "Cannot read image with idx=" << frame_a << " perhaps filenotfound" << TermColor::RESET() << endl; return false; } cv::Mat a_imleft_srectified, a_imright_srectified; cv::Mat a_3dImage; MatrixXd a_3dpts; cv::Mat a_disp_viz; stereogeom->get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( a_imleft_raw, a_imright_raw, a_imleft_srectified,a_imright_srectified, a_3dpts, a_3dImage, a_disp_viz ); //----------------------------------------------------- //--------------- 3d points from frame_b //----------------------------------------------------- cout << TermColor::iGREEN() << "LOAD[B]: BASE/"+std::to_string(frame_b)+".jpg" << TermColor::RESET() << endl; cv::Mat b_imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_b)+".jpg", 0 ); cv::Mat b_imright_raw = cv::imread( BASE+"/"+std::to_string(frame_b)+"_1.jpg", 0 ); Matrix4d wTB; RawFileIO::read_eigen_matrix( BASE+"/"+std::to_string(frame_b)+".wTc", wTB ); cout << "wTB: " << PoseManipUtils::prettyprintMatrix4d( wTB ) << endl; if( b_imleft_raw.empty() || b_imright_raw.empty() ) { cout << TermColor::RED() << "Cannot read image with idx=" << frame_b << " perhaps filenotfound" << TermColor::RESET() << endl; return false; } cv::Mat b_imleft_srectified, b_imright_srectified; cv::Mat b_3dImage; MatrixXd b_3dpts; cv::Mat b_disp_viz; stereogeom->get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( b_imleft_raw, b_imright_raw, b_imleft_srectified, b_imright_srectified, b_3dpts, b_3dImage, b_disp_viz ); cout << "ODOM b_T_a: " << PoseManipUtils::prettyprintMatrix4d( wTB.inverse() * wTA ) << endl; //--------------------------------------------------------------------- //------------ point matches between a_left, b_left //--------------------------------------------------------------------- cout << TermColor::iGREEN() << "point matches between a_left, b_left" << TermColor::RESET() << endl; MatrixXd uv, uv_d; // u is from frame_a; ud is from frame_b ElapsedTime timer; timer.tic(); StaticPointFeatureMatching::gms_point_feature_matches(a_imleft_srectified, b_imleft_srectified, uv, uv_d ); string msg_pf_matches = to_string( timer.toc_milli() )+" (ms) elapsed time for point_feature_matches computation"; if( uv.cols() < 30 ) { cout << TermColor::RED() << "too few gms matches between " << frame_a << " and " << frame_b << TermColor::RESET() << endl; return false; } #if 1 // plot ppoint-feature matches cv::Mat dst_feat_matches; MiscUtils::plot_point_pair( a_imleft_srectified, uv, frame_a, b_imleft_srectified, uv_d, frame_b, dst_feat_matches, 3, msg_pf_matches+";#pf-matches: "+to_string( uv.cols() ) ); cv::resize(dst_feat_matches, dst_feat_matches, cv::Size(), 0.5, 0.5 ); cv::imshow( "dst_feat_matches", dst_feat_matches ); #endif #if 0 // disparty maps of both images cv::Mat dst_disp; MiscUtils::side_by_side( a_disp_viz, b_disp_viz, dst_disp ); MiscUtils::append_status_image( dst_disp, "a="+ to_string(frame_a)+" b="+to_string(frame_b), .8 ); cv::resize(dst_disp, dst_disp, cv::Size(), 0.5, 0.5 ); cv::imshow( "dst_disp", dst_disp ); #endif //---------------------------------------------------------------------- //----------- compute pose //---------------------------------------------------------------------- cout << TermColor::iGREEN() << "compute pose" << TermColor::RESET() << endl; #if 0 // pnp( 3d_pts_of_a, 2d_pts_of_b ) cout << TermColor::iBLUE() << " pnp( 3d_pts_of_a, 2d_pts_of_b )" << TermColor::RESET() << endl; //-----------prep data std::vector feature_position_uv; //< these will be normalized image co-ordinates std::vector feature_position_uv_d; //< these will be normalized image co-ordinates std::vector world_point_uv; StaticPointFeatureMatching::make_3d_2d_collection__using__pfmatches_and_disparity( stereogeom, uv, a_3dImage, uv_d, feature_position_uv, feature_position_uv_d, world_point_uv); //----------initial guess Matrix4d out_b_T_a = Matrix4d::Identity(); out_b_T_a = wTB.inverse() * wTA ; cout << TermColor::YELLOW() << "initial_guess___out_b_T_a : " << PoseManipUtils::prettyprintMatrix4d( out_b_T_a ) << TermColor::RESET() << endl; //---------- PNP string ceres_pnp_msh; StaticCeresPoseCompute::PNP( world_point_uv, feature_position_uv_d, out_b_T_a, ceres_pnp_msh ); Matrix4d out_b_T_a111 = Matrix4d::Identity(); string usual_msg; StaticTheiaPoseCompute::PNP( world_point_uv, feature_position_uv_d, out_b_T_a111, usual_msg ); cout << TermColor::YELLOW() << "final_soltion___out_b_T_a(theia) : " << PoseManipUtils::prettyprintMatrix4d( out_b_T_a111 ) << TermColor::RESET() << endl; //--------final solution cout << TermColor::YELLOW() << "final_soltion___out_b_T_a(ceres) : " << PoseManipUtils::prettyprintMatrix4d( out_b_T_a ) << TermColor::RESET() << endl; #if 1 //verification //--------------------------------------------- //--- Use the theia's estimated pose do PI( b_T_a * 3dpts ) and plot these //--- points on B. Also plot detected points of B //--------------------------------------------- #if 1 MatrixXd _3dpts_of_A_projectedonB = MatrixXd::Zero(3, world_point_uv.size() ); MatrixXd _detectedpts_of_B = MatrixXd::Zero(3, world_point_uv.size() ); MatrixXd _detectedpts_of_A = MatrixXd::Zero(3, world_point_uv.size() ); int n_good = 0; for( int i=0 ; iget_K() * _X_i; //< scaling with camera-intrinsic matrix Vector3d _tmp; _tmp << feature_position_uv_d[i], 1.0 ; _detectedpts_of_B.col(i) = stereogeom->get_K() * _tmp; _tmp << feature_position_uv[i], 1.0 ; _detectedpts_of_A.col(i) = stereogeom->get_K() * _tmp; Vector3d delta = _3dpts_of_A_projectedonB.col(i) - _detectedpts_of_B.col(i); // cout << "i=" << i << " "; if( abs(delta(0)) < 2. && abs(delta(1)) < 2. ) { // cout << " delta=" << delta.transpose(); n_good++; } else { // cout << TermColor::RED() << "delta=" << delta.transpose() << TermColor::RESET(); } // cout << endl; } cout << "n_good=" <get_K() * _X_i; //< scaling with camera-intrinsic matrix } #endif // PI( odom_b_T_a * 3dpts ) #if 1 // plot( B, ud ) cv::Mat __dst; MiscUtils::plot_point_sets( b_imleft_srectified, _detectedpts_of_B, __dst, cv::Scalar( 0,0,255), false, "_detectedpts_of_B (red)" ); // cv::imshow( "plot( B, ud )", __dst ); // plot( B, _3dpts_of_A_projectedonB ) MiscUtils::plot_point_sets( __dst, _3dpts_of_A_projectedonB, cv::Scalar( 0,255,255), false, ";_3dpts_of_A_projectedonB, PI( b_T_a * X),(yellow)" ); // MiscUtils::plot_point_sets( b_imleft_srectified, _3dpts_of_A_projectedonB, __dst, cv::Scalar( 0,255,255), false, "_3dpts_of_A_projectedonB" ); MiscUtils::plot_point_sets( __dst, _detectedpts_of_A, cv::Scalar( 255,255,255), false, ";;_detectedpts_of_A(white)" ); MiscUtils::plot_point_sets( __dst, _3dpts_of_A_projectedonB_with_odom_rel_pose, cv::Scalar( 255,0,0), false, ";;;_3dpts_of_A_projectedonB_with_odom_rel_pose, PI( odom_b_T_a * X),(blue)" ); string status_msg = ""; status_msg += "_detectedpts_of_B (red);"; status_msg += "_3dpts_of_A_projectedonB, PI( b_T_a * X),(yellow);"; status_msg += "_detectedpts_of_A(white);"; status_msg += "_3dpts_of_A_projectedonB_with_odom_rel_pose, PI( odom_b_T_a * X),(blue)"; MiscUtils::append_status_image( __dst, status_msg ); cv::imshow( "b_imleft_srectified", __dst ); #endif //plotting #endif //verification #endif //option-1 #if 1 // p3p( 3d_pts_of_a, 3d_pts_of_b ) cout << TermColor::iBLUE() << " p3p( 3d_pts_of_a, 3d_pts_of_b )" << TermColor::RESET() << endl; //---------prep data vector< Vector3d> uv_X; vector< Vector3d> uvd_Y; StaticPointFeatureMatching::make_3d_3d_collection__using__pfmatches_and_disparity( uv, a_3dImage, uv_d, b_3dImage, uv_X, uvd_Y ); //-------------initial guess Matrix4d icp_b_T_a; icp_b_T_a = wTB.inverse() * wTA ; cout << TermColor::YELLOW() << "initial icp_b_T_a(ceres) : " << PoseManipUtils::prettyprintMatrix4d( icp_b_T_a ) << TermColor::RESET() << endl; //------------------p3p (icp) string p3p__msg; // float p3p_goodness = StaticTheiaPoseCompute::P3P_ICP( uv_X, uvd_Y, icp_b_T_a, p3p__msg ); float p3p_goodness = StaticCeresPoseCompute::P3P_ICP( uv_X, uvd_Y, icp_b_T_a, p3p__msg ); //--------final solution cout << TermColor::YELLOW() << "final icp_b_T_a(ceres) : " << PoseManipUtils::prettyprintMatrix4d( icp_b_T_a ) << TermColor::RESET() << endl; #endif //option-3 } int main(int argc, char ** argv ) { //--- cout << TermColor::iGREEN() << "StereoGeometry\n" << TermColor::RESET() ; auto stereogeom = make_stereo_geom(); //--- Cmdline if( argc != 3 ) { cout << "[MAIN::INVALID USAGE] I am expecting you to give two numbers in the command line between which you want to compute the pose\n"; exit(1); } Matrix4d out_b_T_a = Matrix4d::Identity(); int a = stoi( argv[1] ); int b = stoi( argv[2] ); //-- compute_rel_pose( stereogeom, a, b ); cv::waitKey(0); } #include "../utils/nlohmann/json.hpp" using json = nlohmann::json; int main1( int argc, char ** argv ) { std::shared_ptr stereogeom = make_stereo_geom(); // load ProcessedLoopCandidate.json cout << "Open JSON : " << BASE+"/matching/ProcessedLoopCandidate.json" << endl; std::ifstream fp(BASE+"/matching/ProcessedLoopCandidate.json"); json proc_candi_json; fp >> proc_candi_json; for( int i =0 ; i #include #include #include //opencv #include #include #include #include int main( int argc, char ** argv ) { // Ros INIT and Make connection to Server ros::init( argc, argv, "unittest_rosservice_client_cpp" ); ros::NodeHandle n; ros::ServiceClient client = n.serviceClient( "whole_image_descriptor_compute" ); client.waitForExistence(); if( !client.exists() ) { ROS_ERROR( "Connection to server NOT successful" ); return 0; } else std::cout << "Connection to server established\n"; // Make service message cerebro::WholeImageDescriptorCompute srv; //service message cv::Mat im = cv::imread( "/app/lena.jpg") ; cv::Mat im_resized; cv::resize( im, im_resized, cv::Size(640, 512) ); cv::imshow( "lena", im ); cv::waitKey(0); // call sensor_msgs::ImagePtr image_msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", im_resized).toImageMsg(); srv.request.ima = *image_msg; srv.request.a = 678; if( client.call( srv ) ) { ROS_INFO( "Received response from server" ); std::cout << "desc.size=" << srv.response.desc.size() << std::endl; } else { ROS_ERROR( "Failed to call ros service" ); } return 0; } ================================================ FILE: src/unittest/unittest_termcolor.cpp ================================================ #include #include "../utils/TermColor.h" // #include "../utils/PoseManipUtils.h" using namespace std; int main() { cout << "Hello World\n"; cout << TermColor::RED() << "Hello World in red" << TermColor::RESET() << endl; cout << TermColor::iRED() << "Hello World in red" << TermColor::RESET() << endl; cout << TermColor::compose( TermColor::CTRL_BOLD, TermColor::FG_RED, true ) << "Hello" << TermColor::RESET() << endl; cout << TermColor::compose( TermColor::CTRL_BOLD, TermColor::FG_RED, false ) << "Hello" << TermColor::RESET() << endl; cout << TermColor::compose( TermColor::FG_BLUE, true ) << "Hello" << TermColor::RESET() << endl; cout << TermColor::compose( TermColor::FG_BLUE, false ) << "Hello" << TermColor::RESET() << endl; cout << TermColor::compose( TermColor::BG_CYAN ) << "Hello" << TermColor::RESET() << endl; cout << TermColor::compose( TermColor::CTRL_UNDERLINE, TermColor::BG_GREEN ) << "Hello" << TermColor::RESET() << endl; return 0; } ================================================ FILE: src/unittest/unittest_theia.cpp ================================================ // Opens the log.json file : // A) Plots the vio poses // B) plot loop candidates // c) at loop candidate compute relative pose using pnp // d) plot new camera and old camera. #include #include #include "camodocal/camera_models/Camera.h" #include "camodocal/camera_models/CameraFactory.h" #include "camodocal/camera_models/CataCamera.h" #include "camodocal/camera_models/EquidistantCamera.h" //opencv #include #include #include #include #include "../utils/CameraGeometry.h" #include "../utils/MiscUtils.h" #include "../utils/ElapsedTime.h" #include "../utils/PoseManipUtils.h" #include "../utils/TermColor.h" #include "../utils/RawFileIO.h" #include "../utils/RosMarkerUtils.h" #include "../utils/nlohmann/json.hpp" using json = nlohmann::json; #include "../utils/GMSMatcher/gms_matcher.h" #include const std::string BASE = "/Bulk_Data/_tmp/"; /////////// DlsPnp-RANSAC /////////////////// // Data struct CorrespondencePair_3d2d { Vector3d a_X; // 3d point expressed in co-ordinate system of `image-a` Vector2d uv_d; //feature point in normalized-image co-ordinates. of the other view (b) }; // Model struct RelativePose { Matrix4d b_T_a; }; class DlsPnpWithRansac: public theia::Estimator { public: // Number of points needed to estimate a line. double SampleSize() const { return 15; } // Estimate a pose from N points bool EstimateModel( const std::vector& data, std::vector* models) const { // cout << "EstimateModel: data.size="<< data.size() << " " << data[0].uv_d.transpose() << endl; std::vector feature_position; std::vector world_point; for( int i=0 ; i solution_rotations; std::vector solution_translations; theia::DlsPnp( feature_position, world_point, &solution_rotations, &solution_translations ); if( solution_rotations.size() == 1 ) { RelativePose r; r.b_T_a = Matrix4d::Identity(); r.b_T_a.topLeftCorner(3,3) = solution_rotations[0].toRotationMatrix(); r.b_T_a.col(3).topRows(3) = solution_translations[0]; models->push_back( r ); return true; } else { return false; } } double Error( const CorrespondencePair_3d2d& point, const RelativePose& r ) const { Vector3d b_X = r.b_T_a.topLeftCorner(3,3) * point.a_X + r.b_T_a.col(3).topRows(3); double depth = b_X(2); b_X /= depth; double reproj_error = abs(b_X(0) - point.uv_d(0)) + abs(b_X(1) - point.uv_d(1)); // I want nearby points to have smaller Error than far off points. // So artificially increase the error when depth is higher for the point. double f = 1.; #if 0 if( depth < 1. ) f = .5; if( depth >=1 && depth < 3 ) f = 5.0; if( depth >=3 && depth < 8 ) f = 2.; if( depth >= 8. && depth < 15 ) f = 1.7; if( depth >= 15 ) f = 0.1; #endif return reproj_error*f; } }; /////////// END DlsPnp-RANSAC /////////////////// //////////////////// AlignPointCloudsUmeyama with Ransac /////////////////////// // Data struct CorrespondencePair_3d3d { Vector3d a_X; // 3d point expressed in co-ordinate system of `image-a` Vector3d b_X; // 3d point expressed in co-ordinate system of `image-b` }; // Model // struct RelativePose { // Matrix4d b_T_a; // }; class AlignPointCloudsUmeyamaWithRansac: public theia::Estimator { public: // Number of points needed to estimate a line. double SampleSize() const { return 10; } bool EstimateModel( const std::vector& data, std::vector* models) const { //TODO std::vector a_X; std::vector b_X; for( int i=0 ; i 0.9 ) { RelativePose r; r.b_T_a = Matrix4d::Identity(); r.b_T_a.topLeftCorner(3,3) = ____R; r.b_T_a.col(3).topRows(3) = ____t; models->push_back( r ); // cout << "Estimate s=" << ___s << " " << PoseManipUtils::prettyprintMatrix4d(r.b_T_a)<< endl; return true; } else { return false; } } double Error( const CorrespondencePair_3d3d& point, const RelativePose& r ) const { Vector3d b_X_cap = r.b_T_a.topLeftCorner(3,3) * point.a_X + r.b_T_a.col(3).topRows(3); double err = (b_X_cap - point.b_X).norm(); double f=1.0; if( point.a_X(2) < 1. && point.a_X(2) > 8. ) f = 1.2; // cout << "ICP RAnsac error=" << err << endl; return f*err; } }; //////////////////// END AlignPointCloudsUmeyama with Ransac /////////////////////// void point_feature_matches( const cv::Mat& imleft_undistorted, const cv::Mat& imright_undistorted, MatrixXd& u, MatrixXd& ud ) { ElapsedTime timer; // // Point feature and descriptors extract std::vector kp1, kp2; cv::Mat d1, d2; //< descriptors cv::Ptr orb = cv::ORB::create(5000); orb->setFastThreshold(0); timer.tic(); orb->detectAndCompute(imleft_undistorted, cv::Mat(), kp1, d1); orb->detectAndCompute(imright_undistorted, cv::Mat(), kp2, d2); cout << timer.toc_milli() << " (ms) 2X detectAndCompute(ms) : "<< endl; // std::cout << "d1 " << MiscUtils::cvmat_info( d1 ) << std::endl; // std::cout << "d2 " << MiscUtils::cvmat_info( d2 ) << std::endl; //plot // cv::Mat dst_left, dst_right; // MatrixXd e_kp1, e_kp2; // MiscUtils::keypoint_2_eigen( kp1, e_kp1 ); // MiscUtils::keypoint_2_eigen( kp2, e_kp2 ); // MiscUtils::plot_point_sets( imleft_undistorted, e_kp1, dst_left, cv::Scalar(0,0,255), false ); // MiscUtils::plot_point_sets( imright_undistorted, e_kp2, dst_right, cv::Scalar(0,0,255), false ); // cv::imshow( "dst_left", dst_left ); // cv::imshow( "dst_right", dst_right ); // // Point feature matching cv::BFMatcher matcher(cv::NORM_HAMMING); // TODO try FLANN matcher here. vector matches_all, matches_gms; timer.tic(); matcher.match(d1, d2, matches_all); std::cout << timer.toc_milli() << " : (ms) BFMatcher took (ms)\t"; std::cout << "BFMatcher : npts = " << matches_all.size() << std::endl; // gms_matcher timer.tic(); std::vector vbInliers; gms_matcher gms(kp1, imleft_undistorted.size(), kp2, imright_undistorted.size(), matches_all); int num_inliers = gms.GetInlierMask(vbInliers, false, false); cout << timer.toc_milli() << " : (ms) GMSMatcher took.\t" ; cout << "Got total gms matches " << num_inliers << " matches." << endl; // collect matches for (size_t i = 0; i < vbInliers.size(); ++i) { if (vbInliers[i] == true) { matches_gms.push_back(matches_all[i]); } } // MatrixXd M1, M2; MiscUtils::dmatch_2_eigen( kp1, kp2, matches_gms, u, ud, true ); } std::shared_ptr make_stereo_geom() { //--------- Intrinsics load camodocal::CameraPtr left_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.0.yaml"); camodocal::CameraPtr right_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.1.yaml"); // camodocal::CameraPtr left_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(string(BASE+"../leveled_cam_pinhole/")+"/camera_left.yaml"); // camodocal::CameraPtr right_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(string(BASE+"../leveled_cam_pinhole/")+"/camera_right.yaml"); cout << left_camera->parametersToString() << endl; cout << right_camera->parametersToString() << endl; if( !left_camera || !right_camera ) { cout << "\nERROR : Abstract stereo Camera is not set. You need to init camera before setting it to geometry clases\n"; exit(10); } //----------- Stereo Base line load (alsoed called extrinsic calibration) // mynt eye Vector4d q_xyzw = Vector4d( -7.0955103716253032e-04,-1.5775578725333590e-03,-1.2732644461763854e-03,9.9999769332040711e-01 ); Vector3d tr_xyz = Vector3d( -1.2006984141573309e+02/1000.,3.3956264524978619e-01/1000.,-1.6784055634087214e-01/1000. ); Matrix4d right_T_left; PoseManipUtils::raw_xyzw_to_eigenmat( q_xyzw, tr_xyz, right_T_left ); cout << "XXright_T_left: " << PoseManipUtils::prettyprintMatrix4d( right_T_left ) << endl; // StereoGeometry* sthm = new StereoGeometry( left_camera, right_camera, right_T_left ); // cout << "sthn->get_K() " << sthm->get_K() << endl; // StereoGeometry NNN = StereoGeometry( left_camera, right_camera, right_T_left ); // cout << "NNN->get_K() " << NNN.get_K() << endl; //-------------------- init stereogeom std::shared_ptr stereogeom; stereogeom = std::make_shared( left_camera,right_camera, right_T_left ); // stereogeom->set_K( 375.0, 375.0, 376.0, 240.0 ); cout << "[in make_stereo_geom]" << stereogeom->get_K() << endl; return stereogeom; } // will return false if cannot compute pose bool relative_pose_compute_with_theia( std::shared_ptr stereogeom, int frame_a, int frame_b, Matrix4d& out_b_T_a ) { ElapsedTime timer; //------------------------------------------------ //------ 3d points from frame_a //------------------------------------------------ cout << TermColor::iGREEN() << "LOAD: BASE/"+std::to_string(frame_a)+".jpg" << TermColor::RESET() << endl; cv::Mat a_imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_a)+".jpg", 0 ); cv::Mat a_imright_raw = cv::imread( BASE+"/"+std::to_string(frame_a)+"_1.jpg", 0 ); if( a_imright_raw.empty() || a_imright_raw.empty() ) { cout << TermColor::RED() << "Cannot read image with idx=" << frame_a << " perhaps filenotfound" << TermColor::RESET() << endl; return false; } cv::Mat a_imleft_srectified, a_imright_srectified; cv::Mat _3dImage; MatrixXd _3dpts; // stereogeom->get3dpoints_and_3dmap_from_raw_images( a_imleft_raw, a_imright_raw, // _3dpts, _3dImage, // a_imleft_srectified, a_imright_srectified ); cv::Mat disp_viz; timer.tic(); stereogeom->get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( a_imleft_raw, a_imright_raw, a_imleft_srectified,a_imright_srectified, _3dpts, _3dImage, disp_viz ); cout << timer.toc_milli() << " : (ms) get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images\n"; #if 1 cv::imshow( "a_imleft_raw", a_imleft_raw ); cv::imshow( "a_imright_raw", a_imright_raw ); // cv::imshow( "a_imleft_srectified", a_imleft_srectified ); // cv::imshow( "a_imright_srectified", a_imright_srectified ); cv::imshow( "a_disp_viz", disp_viz ); #endif //----------------------------------------------------- //--------------- stereo rectify b //----------------------------------------------------- cout << TermColor::iGREEN() << "LOAD: BASE/"+std::to_string(frame_b)+".jpg" << TermColor::RESET() << endl; cv::Mat b_imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_b)+".jpg", 0 ); cv::Mat b_imright_raw = cv::imread( BASE+"/"+std::to_string(frame_b)+"_1.jpg", 0 ); if( b_imleft_raw.empty() || b_imright_raw.empty() ) { cout << TermColor::RED() << "Cannot read image with idx=" << frame_b << " perhaps filenotfound" << TermColor::RESET() << endl; return false; } cv::Mat b_imleft_srectified, b_imright_srectified; stereogeom->do_stereo_rectification_of_raw_images( b_imleft_raw, b_imright_raw, b_imleft_srectified, b_imright_srectified ); //--------------------------------------------------------------------- //------------ point matches between a_left, b_left //--------------------------------------------------------------------- MatrixXd u, ud; // u is from frame_a; ud is from frame_b point_feature_matches(a_imleft_srectified, b_imleft_srectified, u, ud ); if( u.cols() < 30 ) { cout << TermColor::RED() << "too few gms matches between " << frame_a << " and " << frame_b << TermColor::RESET() << endl; return false; } //---------------- make collection of 3d 2d points int c = 0; MatrixXd u_normalized = stereogeom->get_K().inverse() * u; MatrixXd ud_normalized = stereogeom->get_K().inverse() * ud; std::vector world_point; std::vector feature_position_ud; std::vector feature_position_u; for( int k=0 ; k( (int)u(1,k), (int)u(0,k) ); if( _3dpt[2] < 0.1 || _3dpt[2] > 25. ) continue; // the 3d point should also project to same 2d points. c++; #if 0 cout << TermColor::RED() << "---" << k << "---" << TermColor::RESET() << endl; cout << "ud=" << ud.col(k).transpose() ; cout << " <--> "; cout << "u=" << u.col(k).transpose() ; cout << " 3dpt of u="; cout << TermColor::GREEN() << _3dpt[0] << " " << _3dpt[1] << " " << _3dpt[2] << " " << TermColor::RESET(); // project 3d point with Identity Vector3d X_i = Vector3d( (double) _3dpt[0],(double) _3dpt[1],(double) _3dpt[2] ); X_i(0) /= X_i(2); X_i(1) /= X_i(2); X_i(2) /= X_i(2); Vector3d u_cap = stereogeom->get_K() * X_i; cout << "\nPI(3dpt)=" << u_cap.transpose() ; auto delta = u_cap - u.col(k); if( abs(delta(0)) < 2. && abs(delta(1)) < 2. ) { cout << TermColor::GREEN() << "\tdelta=" << delta.transpose() << TermColor::RESET(); make_n_good++; } else cout << "\tdelta=" << delta.transpose() ; cout << endl; #endif feature_position_ud.push_back( Vector2d( ud_normalized(0,k), ud_normalized(1,k) ) ); feature_position_u.push_back( Vector2d( u_normalized(0,k), u_normalized(1,k) ) ); world_point.push_back( Vector3d( _3dpt[0], _3dpt[1], _3dpt[2] ) ); // world_point.push_back( X_i ); } cout << TermColor::GREEN() << "of the total " << u.cols() << " point feature correspondences " << c << " had valid depths" << TermColor::RESET() << endl; if( c < 30 ) { cout << TermColor::RED() << "too few valid 3d points between " << frame_a << " and " << frame_b << TermColor::RESET() << endl; return false; } #if 1 cv::Mat dst_feat_matches; string msg = string("gms_matcher;")+"of the total "+std::to_string(u.cols())+" point feature correspondences " +std::to_string(c)+ " had valid depths"; MiscUtils::plot_point_pair( a_imleft_srectified, u, frame_a, b_imleft_srectified, ud, frame_b, dst_feat_matches, 3, msg ); cv::Mat dst_feat_matches_resized; cv::resize(dst_feat_matches, dst_feat_matches_resized, cv::Size(), 0.5, 0.5); cv::imshow( "dst_feat_matches", dst_feat_matches_resized); #endif //------------- TODO assess u, ud, world_point for histogram spreads. a more spread in measurements will give more precise more. Also will be helpful to weedout bad poses //--------------------------------------------------------------------- //------------------ theia pnp //--------------------------------------------------------------------- #if 1 // simple DlsPnp() std::vector solution_rotations; std::vector solution_translations; timer.tic(); theia::DlsPnp( feature_position_ud, world_point, &solution_rotations, &solution_translations ); cout << timer.toc_milli() << " : (ms) : theia::DlsPnp done in\n"; cout << "solutions count = " << solution_rotations.size() << " " << solution_translations.size() << endl; if( solution_rotations.size() == 0 ) { cout << TermColor::RED() << " theia::DlsPnp returns no solution" << TermColor::RESET() << endl; return false; } Matrix4d b_T_a = Matrix4d::Identity(); b_T_a.topLeftCorner(3,3) = solution_rotations[0].toRotationMatrix(); b_T_a.col(3).topRows(3) = solution_translations[0]; // cout << "solution_T " << b_T_a << endl; cout << "solution_T (b_T_a): " << PoseManipUtils::prettyprintMatrix4d( b_T_a ) << endl; out_b_T_a = b_T_a; // return true; #endif #if 1 // DlsPnp with ransac timer.tic(); // prep data vector data_r; for( int i=0 ; i ransac_estimator(params, dlspnp_estimator); // theia::LMed ransac_estimator(params, dlspnp_estimator); // Initialize must always be called! ransac_estimator.Initialize(); theia::RansacSummary summary; ransac_estimator.Estimate(data_r, &best_rel_pose, &summary); // cout << "Line y = " << best_line.m << "*x + " << best_line.b; cout << timer.toc_milli() << "(ms)!! DlsPnpWithRansac ElapsedTime includes data prep\n"; cout << "Ransac:"; // for( int i=0; iget_K() * _X_i; //< scaling with camera-intrinsic matrix Vector3d _tmp; _tmp << feature_position_u[i], 1.0 ; _detectedpts_of_A.col(i) = stereogeom->get_K() * _tmp; // cout << "---\n"; cout << i << " ) "; cout << "_3dpts_of_A_projectedonA.col(i)="<< _3dpts_of_A_projectedonA.col(i).transpose() << "\t"; cout << "_detectedpts_of_A.col(i)=" << _detectedpts_of_A.col(i).transpose() ; Vector3d delta = _3dpts_of_A_projectedonA.col(i) - _detectedpts_of_A.col(i); if( abs(delta(0)) < 2. && abs(delta(1)) < 2. ) { cout << " delta=" << delta.transpose(); n_good++; } else { cout << TermColor::RED() << "delta=" << delta.transpose() << TermColor::RESET(); } cout << endl; } cout << "# of points which with ok delta=" << n_good << " out of total" << world_point.size() << endl; // plot( A, u ); cv::Mat __dst; MiscUtils::plot_point_sets( a_imleft_srectified, _detectedpts_of_A, __dst, cv::Scalar( 0,0,255), false ); cv::imshow( "plot( A, u )", __dst ); // plot( A, _3dpts_of_A_projectedonA ) MiscUtils::plot_point_sets( a_imleft_srectified, _3dpts_of_A_projectedonA, __dst, cv::Scalar( 0,255,255), false ); cv::imshow( "plot( A, _3dpts_of_A_projectedonA )", __dst ); #endif //--------------------------------------------- //--- Use the theia's estimated pose do PI( b_T_a * 3dpts ) and plot these //--- points on B. Also plot detected points of B //--------------------------------------------- #if 1 MatrixXd _3dpts_of_A_projectedonB = MatrixXd::Zero(3, world_point.size() ); MatrixXd _detectedpts_of_B = MatrixXd::Zero(3, world_point.size() ); MatrixXd _detectedpts_of_A = MatrixXd::Zero(3, world_point.size() ); int n_good = 0; for( int i=0 ; iget_K() * _X_i; //< scaling with camera-intrinsic matrix Vector3d _tmp; _tmp << feature_position_ud[i], 1.0 ; _detectedpts_of_B.col(i) = stereogeom->get_K() * _tmp; _tmp << feature_position_u[i], 1.0 ; _detectedpts_of_A.col(i) = stereogeom->get_K() * _tmp; Vector3d delta = _3dpts_of_A_projectedonB.col(i) - _detectedpts_of_B.col(i); if( abs(delta(0)) < 2. && abs(delta(1)) < 2. ) { // cout << " delta=" << delta.transpose(); n_good++; } else { // cout << TermColor::RED() << "delta=" << delta.transpose() << TermColor::RESET(); } // cout << endl; } cout << "n_good=" <get_K() * _X_i; //< scaling with camera-intrinsic matrix } #endif // plot( B, ud ) cv::Mat __dst; MiscUtils::plot_point_sets( b_imleft_srectified, _detectedpts_of_B, __dst, cv::Scalar( 0,0,255), false, "_detectedpts_of_B (red)" ); // cv::imshow( "plot( B, ud )", __dst ); // plot( B, _3dpts_of_A_projectedonB ) MiscUtils::plot_point_sets( __dst, _3dpts_of_A_projectedonB, cv::Scalar( 0,255,255), false, ";_3dpts_of_A_projectedonB, PI( b_T_a * X),(yellow)" ); // MiscUtils::plot_point_sets( b_imleft_srectified, _3dpts_of_A_projectedonB, __dst, cv::Scalar( 0,255,255), false, "_3dpts_of_A_projectedonB" ); MiscUtils::plot_point_sets( __dst, _detectedpts_of_A, cv::Scalar( 255,255,255), false, ";;_detectedpts_of_A(white)" ); MiscUtils::plot_point_sets( __dst, _3dpts_of_A_projectedonB_with_odom_rel_pose, cv::Scalar( 255,0,0), false, ";;;_3dpts_of_A_projectedonB_with_odom_rel_pose, PI( odom_b_T_a * X),(blue)" ); string status_msg = ""; status_msg += "_detectedpts_of_B (red);"; status_msg += "_3dpts_of_A_projectedonB, PI( b_T_a * X),(yellow);"; status_msg += "_detectedpts_of_A(white);"; status_msg += "_3dpts_of_A_projectedonB_with_odom_rel_pose, PI( odom_b_T_a * X),(blue)"; MiscUtils::append_status_image( __dst, status_msg ); cv::imshow( "b_imleft_srectified", __dst ); } void plot_vio_poses( json& json_obj, ros::Publisher& pub ) { int n_max = json_obj["DataNodes"].size(); visualization_msgs::Marker cam, cam_text; ros::Rate loop_rate(100); for( int i=0 ; i raa = json_obj["DataNodes"][i]["w_T_c"]["data"]; RawFileIO::read_eigen_matrix( raa, w_T_c );//loads as row-major // cout << "w_T_c "<< w_T_c << endl; cout << "wTc : " << PoseManipUtils::prettyprintMatrix4d( w_T_c ) << endl; RosMarkerUtils::init_text_marker( cam_text ); cam_text.ns = "vio_pose_text"; cam_text.id = i; cam_text.text = std::to_string(i); cam_text.scale.z = 0.08; RosMarkerUtils::setcolor_to_marker( 1.0,1.0,1.0, cam_text ); RosMarkerUtils::setpose_to_marker( w_T_c, cam_text ); pub.publish( cam_text ); RosMarkerUtils::init_camera_marker( cam, 0.7 ); cam.ns = "vio_pose"; cam.id = i; RosMarkerUtils::setpose_to_marker( w_T_c, cam ); RosMarkerUtils::setcolor_to_marker( 0.0, 1.0, 0.0, cam ); pub.publish( cam ); ros::spinOnce(); loop_rate.sleep(); } } } void plot_loop_candidates_as_lines( json& log, json& loopcandidates, ros::Publisher&pub ) { visualization_msgs::Marker loop_line_marker; visualization_msgs::Marker loop_line_marker_new; std::shared_ptr stereogeom = make_stereo_geom(); ros::Rate loop_rate(100); for( int i=0 ; i r_wtb = log["DataNodes"][global_b]["w_T_c"]["data"]; Matrix4d w_T_a, w_T_b; RawFileIO::read_eigen_matrix( r_wta, w_T_a ); RawFileIO::read_eigen_matrix( r_wtb, w_T_b ); RosMarkerUtils::init_line_marker( loop_line_marker, w_T_a.col(3).topRows(3), w_T_b.col(3).topRows(3) ); RosMarkerUtils::setcolor_to_marker( 1.0, 0.0, 0.0, loop_line_marker ); loop_line_marker.ns = "loopcandidates"; loop_line_marker.id = i; pub.publish( loop_line_marker ); // PnP Matrix4d b_T_a; bool status = relative_pose_compute_with_theia( stereogeom, global_a,global_b, b_T_a ); if( status ) { Matrix4d w_Tcap_b = w_T_a * b_T_a.inverse(); //< new pose cout << "w_T_"<< global_a << ": " << PoseManipUtils::prettyprintMatrix4d( w_T_a ) << endl; cout << "w_T_"<< global_b << ": " << PoseManipUtils::prettyprintMatrix4d( w_T_b ) << endl; cout << "w_Tcap_"<< global_b << ": " << PoseManipUtils::prettyprintMatrix4d( w_Tcap_b ) << endl; //new pose of b. RosMarkerUtils::init_line_marker( loop_line_marker_new, w_T_a.col(3).topRows(3), w_Tcap_b.col(3).topRows(3) ); RosMarkerUtils::setcolor_to_marker( 1.0, 1.0, 1.0, loop_line_marker_new ); loop_line_marker_new.ns = "loopcandidates_corrected"; loop_line_marker_new.id = i; pub.publish( loop_line_marker_new ); char key = cv::waitKey(0); if( key == 'q' ) { cout << "q pressed\n"; return; } } else { // blue-out the edge which failed geometric verification RosMarkerUtils::setcolor_to_marker( 0.0, 0.0, 1.0, loop_line_marker ); pub.publish( loop_line_marker ); } ros::spinOnce(); loop_rate.sleep(); } } bool self_projection_test( std::shared_ptr stereogeom, int frame_a, int frame_b ) { // I hacve a doubt that the 3dImage which is created from stereo geometry // gives out the 3d points in co-ordinate system of right camera-center. // I am wanting it in left camera center co-ordinates. Just reproject those // points and ensure my premise. //------------------------------------------------ //------ 3d points from frame_a //------------------------------------------------ cout << TermColor::iGREEN() << "LOAD: BASE/"+std::to_string(frame_a)+".jpg" << TermColor::RESET() << endl; cv::Mat a_imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_a)+".jpg", 0 ); cv::Mat a_imright_raw = cv::imread( BASE+"/"+std::to_string(frame_a)+"_1.jpg", 0 ); Matrix4d wTA; RawFileIO::read_eigen_matrix( BASE+"/"+std::to_string(frame_a)+".wTc", wTA ); if( a_imright_raw.empty() || a_imright_raw.empty() ) { cout << TermColor::RED() << "Cannot read image with idx=" << frame_a << " perhaps filenotfound" << TermColor::RESET() << endl; return false; } cv::Mat a_imleft_srectified, a_imright_srectified; cv::Mat _3dImage; MatrixXd _3dpts; cv::Mat disp_viz; stereogeom->get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( a_imleft_raw, a_imright_raw, a_imleft_srectified,a_imright_srectified, _3dpts, _3dImage, disp_viz ); //----------------------------------------------------- //--------------- stereo rectify b //----------------------------------------------------- cout << TermColor::iGREEN() << "LOAD: BASE/"+std::to_string(frame_b)+".jpg" << TermColor::RESET() << endl; cv::Mat b_imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_b)+".jpg", 0 ); cv::Mat b_imright_raw = cv::imread( BASE+"/"+std::to_string(frame_b)+"_1.jpg", 0 ); Matrix4d wTB; RawFileIO::read_eigen_matrix( BASE+"/"+std::to_string(frame_b)+".wTc", wTB ); if( b_imleft_raw.empty() || b_imright_raw.empty() ) { cout << TermColor::RED() << "Cannot read image with idx=" << frame_b << " perhaps filenotfound" << TermColor::RESET() << endl; return false; } cv::Mat b_imleft_srectified, b_imright_srectified; stereogeom->do_stereo_rectification_of_raw_images( b_imleft_raw, b_imright_raw, b_imleft_srectified, b_imright_srectified ); //--------------------------------------------------------------------- //------------ point matches between a_left, b_left //--------------------------------------------------------------------- MatrixXd u, ud; // u is from frame_a; ud is from frame_b point_feature_matches(a_imleft_srectified, b_imleft_srectified, u, ud ); if( u.cols() < 30 ) { cout << TermColor::RED() << "too few gms matches between " << frame_a << " and " << frame_b << TermColor::RESET() << endl; return false; } //---------------------------------------------------------------------- //---------------- make collection of 3d 2d points //---------------------------------------------------------------------- int c = 0; MatrixXd u_normalized = stereogeom->get_K().inverse() * u; MatrixXd ud_normalized = stereogeom->get_K().inverse() * ud; std::vector world_point; std::vector feature_position_ud; std::vector feature_position_u; for( int k=0 ; k( (int)u(1,k), (int)u(0,k) ); if( _3dpt[2] < 0.1 || _3dpt[2] > 25. ) continue; // the 3d point should also project to same 2d points. c++; #if 0 cout << TermColor::RED() << "---" << k << "---" << TermColor::RESET() << endl; cout << "ud=" << ud.col(k).transpose() ; cout << " <--> "; cout << "u=" << u.col(k).transpose() ; cout << " 3dpt of u="; cout << TermColor::GREEN() << _3dpt[0] << " " << _3dpt[1] << " " << _3dpt[2] << " " << TermColor::RESET(); // project 3d point with Identity Vector3d X_i = Vector3d( (double) _3dpt[0],(double) _3dpt[1],(double) _3dpt[2] ); X_i(0) /= X_i(2); X_i(1) /= X_i(2); X_i(2) /= X_i(2); Vector3d u_cap = stereogeom->get_K() * X_i; cout << "\nPI(3dpt)=" << u_cap.transpose() ; auto delta = u_cap - u.col(k); if( abs(delta(0)) < 2. && abs(delta(1)) < 2. ) { cout << TermColor::GREEN() << "\tdelta=" << delta.transpose() << TermColor::RESET(); make_n_good++; } else cout << "\tdelta=" << delta.transpose() ; cout << endl; #endif feature_position_ud.push_back( Vector2d( ud_normalized(0,k), ud_normalized(1,k) ) ); feature_position_u.push_back( Vector2d( u_normalized(0,k), u_normalized(1,k) ) ); world_point.push_back( Vector3d( _3dpt[0], _3dpt[1], _3dpt[2] ) ); // world_point.push_back( X_i ); } cout << TermColor::GREEN() << "of the total " << u.cols() << " point feature correspondences " << c << " had valid depths" << TermColor::RESET() << endl; #if 0 // PI( _3dpts ) cout << "_3dpts.shape=" << _3dpts.rows() << "," << _3dpts.cols() << endl; MatrixXd reporj = MatrixXd::Zero( 3, _3dpts.cols() ); vector reporj_falsedepthcolors; auto fp_map = FalseColors(); for( int i=0 ; i<_3dpts.cols() ; i++ ) { Vector4d a_X = _3dpts.col(i); //< 3d pt reporj_falsedepthcolors.push_back( fp_map.getFalseColor(a_X(2)/10.) ); reporj.col(i) = stereogeom->get_K() * (a_X.topRows(3) / a_X(2)); // u <== PI( a_X ) } cv::Mat __dst; // MiscUtils::plot_point_sets( a_imleft_raw, reporj, __dst, reporj_falsedepthcolors, 0.5, "PI(_3dpts) on a_imleft_raw" ); MiscUtils::plot_point_sets( a_imright_raw, reporj, __dst, reporj_falsedepthcolors, 0.5, "PI(_3dpts) on a_imright_raw" ); cv::imshow( "__dst", __dst ); cv::imshow( "strip", fp_map.getStrip(30, 300) ); cv::waitKey(0); #endif #if 0 // PI(world_point) - OK MatrixXd reproj_of_worldpts_on_left_of_A = MatrixXd::Zero(3,world_point.size()); for( int i=0 ; iget_K() * (world_point[i] / world_point[i](2) ); } cv::Mat __dst; MiscUtils::plot_point_sets( a_imleft_raw, reproj_of_worldpts_on_left_of_A, __dst, cv::Scalar(0,0,255), false, "reproj_of_worldpts_on_left_of_A(red)" ); MiscUtils::plot_point_sets( __dst, u, cv::Scalar(0,255,0), false, ";u plotted on A (in green)" ); cv::imshow( "__dst", __dst ); cv::waitKey(0); #endif Matrix4d odom_b_T_a = wTB.inverse() * wTA ; Matrix3d rm_R1, rm_R2; cv::cv2eigen( stereogeom->get_rm_R1(), rm_R1 ); cv::cv2eigen( stereogeom->get_rm_R2(), rm_R2 ); Matrix4d rm_T1, rm_T2; rm_T1 = Matrix4d::Identity(); rm_T2 = Matrix4d::Identity(); rm_T1.topLeftCorner<3,3>() = rm_R1; rm_T2.topLeftCorner<3,3>() = rm_R2; cout << "odom_b_T_a" << PoseManipUtils::prettyprintMatrix4d( odom_b_T_a ) << endl; cout << "rm_T1" << PoseManipUtils::prettyprintMatrix4d( rm_T1 ) << endl; cout << "rm_T2" << PoseManipUtils::prettyprintMatrix4d( rm_T2 ) << endl; cout << "get_rm_R1\n" << stereogeom->get_rm_R1() << endl; cout << "get_rm_R1\n" << rm_R1 << endl; cout << "---\n"; cout << "get_rm_R2\n" << stereogeom->get_rm_R2() << endl; cout << "get_rm_R2\n" << rm_R2 << endl; MatrixXd reproj_of_worldpts_on_srectifiedleft_of_B = MatrixXd::Zero(3,world_point.size()); MatrixXd reproj_of_worldpts_on_srectifiedleft_of_B__1 = MatrixXd::Zero(3,world_point.size()); for( int i=0 ; iget_K() * (bd_X.topRows(3) / bd_X(2) ); Vector4d bd_X_1 = (rm_T2.inverse() * (odom_b_T_a * rm_T1)) * ad_X; reproj_of_worldpts_on_srectifiedleft_of_B__1.col(i) = stereogeom->get_K() * (bd_X_1.topRows(3) / bd_X_1(2) ); } cv::Mat __dst; MiscUtils::plot_point_sets( b_imleft_srectified, ud, __dst, cv::Scalar(0,255,0), false, "ud plotted on B srectified_left (in green)" ); MiscUtils::plot_point_sets( __dst, reproj_of_worldpts_on_srectifiedleft_of_B, cv::Scalar(0,0,255), false, ";reproj_of_worldpts_on_srectifiedleft_of_B with odom_b_T_a(red)" ); MiscUtils::plot_point_sets( __dst, reproj_of_worldpts_on_srectifiedleft_of_B__1, cv::Scalar(0,128,255), false, ";;reproj_of_worldpts_on_srectifiedleft_of_B__1 with odom_b_T_a(yellow)" ); cv::imshow( "__dst", __dst ); cv::waitKey(0); } //----------------------------------------------- //-------- NEW ---------------------------------- //----------------------------------------------- // Given the point feature matches and the 3d image (from disparity map) will return // the valid world points and corresponding points. // [Input] // uv: 2xN matrix of point-feature in image-a. In image co-ordinates (not normalized image cords) // _3dImage_uv : 3d image from disparity map of image-a. sizeof( _3dImage_uv) === WxHx3 // uv_d: 2xN matrix of point-feature in image-b. Note that uv<-->uv_d are correspondences so should of equal sizes // [Output] // feature_position_uv : a subset of uv but normalized_image_cordinates // feature_position_uv_d : a subset of uv_d. results in normalized_image_cordinates // world_point : 3d points of uv. // [Note] // feature_position_uv \subset uv. Selects points which have valid depths. // size of output is same for all 3 // world points are of uv and in co-ordinate system of camera center of uv (or image-a). bool make_3d_2d_collection__using__pfmatches_and_disparity( std::shared_ptr stereogeom, const MatrixXd& uv, const cv::Mat& _3dImage_uv, const MatrixXd& uv_d, std::vector& feature_position_uv, std::vector& feature_position_uv_d, std::vector& world_point ) { assert( (uv.cols() == uv_d.cols() ) && "[Cerebro::make_3d_2d_collection__using__pfmatches_and_disparity] pf-matches need to be of same length. You provided of different lengths\n" ); assert( _3dImage_uv.type() == CV_32FC3 ); if( uv.cols() != uv_d.cols() ) { cout << TermColor::RED() << "[Cerebro::make_3d_2d_collection__using__pfmatches_and_disparity] pf-matches need to be of same length. You provided of different lengths\n" << TermColor::RESET(); return false; } if( _3dImage_uv.type() != CV_32FC3 && _3dImage_uv.rows <= 0 && _3dImage_uv.cols <= 0 ) { cout << TermColor::RED() << "[Cerebro::make_3d_2d_collection__using__pfmatches_and_disparity] _3dImage is expected to be of size CV_32FC3\n" << TermColor::RESET(); return false; } int c = 0; MatrixXd ud_normalized = stereogeom->get_K().inverse() * uv_d; MatrixXd u_normalized = stereogeom->get_K().inverse() * uv; feature_position_uv.clear(); feature_position_uv_d.clear(); world_point.clear(); for( int k=0 ; k( (int)uv(1,k), (int)uv(0,k) ); if( _3dpt[2] < 0.1 || _3dpt[2] > 25. ) continue; c++; #if 0 cout << TermColor::RED() << "---" << k << "---" << TermColor::RESET() << endl; cout << "ud=" << ud.col(k).transpose() ; cout << " <--> "; cout << "u=" << u.col(k).transpose() ; cout << " 3dpt of u="; cout << TermColor::GREEN() << _3dpt[0] << " " << _3dpt[1] << " " << _3dpt[2] << " " << TermColor::RESET(); cout << endl; #endif feature_position_uv.push_back( Vector2d( u_normalized(0,k), u_normalized(1,k) ) ); feature_position_uv_d.push_back( Vector2d( ud_normalized(0,k), ud_normalized(1,k) ) ); world_point.push_back( Vector3d( _3dpt[0], _3dpt[1], _3dpt[2] ) ); } cout << "[make_3d_2d_collection__using__pfmatches_and_disparity]of the total " << uv.cols() << " point feature correspondences " << c << " had valid depths\n"; return true; if( c < 30 ) { cout << TermColor::RED() << "too few valid 3d points between frames" << TermColor::RESET() << endl; return false; } } // given pf-matches uv<-->ud_d and their _3dImages. returns the 3d point correspondences at points where it is valid // uv_X: the 3d points are in frame of ref of camera-uv // uvd_Y: these 3d points are in frame of ref of camera-uvd bool make_3d_3d_collection__using__pfmatches_and_disparity( const MatrixXd& uv, const cv::Mat& _3dImage_uv, const MatrixXd& uv_d, const cv::Mat& _3dImage_uv_d, vector& uv_X, vector& uvd_Y ) { assert( uv.cols() > 0 && uv.cols() == uv_d.cols() ); uv_X.clear(); uvd_Y.clear(); // similar to above but should return world_point__uv and world_point__uv_d int c=0; for( int k=0 ; k( (int)uv(1,k), (int)uv(0,k) ); cv::Vec3f uvd_3dpt = _3dImage_uv_d.at( (int)uv_d(1,k), (int)uv_d(0,k) ); if( uv_3dpt[2] < 0.1 || uv_3dpt[2] > 25. || uvd_3dpt[2] < 0.1 || uvd_3dpt[2] > 25. ) continue; uv_X.push_back( Vector3d( uv_3dpt[0], uv_3dpt[1], uv_3dpt[2] ) ); uvd_Y.push_back( Vector3d( uvd_3dpt[0], uvd_3dpt[1], uvd_3dpt[2] ) ); c++; } cout << "[make_3d_3d_collection__using__pfmatches_and_disparity] of the total " << uv.cols() << " point feature correspondences " << c << " had valid depths\n"; } // Theia's ICP // [Input] // uv_X: a 3d point cloud expressed in some frame-of-ref, call it frame-of-ref of `uv` // uvd_Y: a 3d point cloud expressed in another frame-of-ref, call it frame-of-ref of `uvd` // [Output] // uvd_T_uv: Relative pose between the point clouds // [Note] // uv_X <---> uvd_Y #define ____P3P_ICP_( msg ) msg; // #define ____P3P_ICP_( msg ) ; float P3P_ICP( const vector& uv_X, const vector& uvd_Y, Matrix4d& uvd_T_uv, string & p3p__msg ) { // call this theia::AlignPointCloudsUmeyamaWithWeights // void AlignPointCloudsUmeyamaWithWeights( // const std::vector& left, // const std::vector& right, // const std::vector& weights, Eigen::Matrix3d* rotation, // Eigen::Vector3d* translation, double* scale) p3p__msg = ""; Matrix3d ____R; Vector3d ____t; double ___s=1.0; ElapsedTime timer; timer.tic(); #if 0 theia::AlignPointCloudsUmeyama( uv_X, uvd_Y, &____R, &____t, &___s ); // all weights = 1. TODO: ideally weights could be proportion to point's Z. // theia::AlignPointCloudsICP( uv_X, uvd_Y, &____R, &____t ); // all weights = 1. TODO: ideally weights could be proportion to point's Z. double elapsed_time_p3p = timer.toc_milli(); ____P3P_ICP_( cout << "___R:\n" << ____R << endl; cout << "___t: " << ____t.transpose() << endl; cout << "___s: " << ___s << endl; ) uvd_T_uv = Matrix4d::Identity(); uvd_T_uv.topLeftCorner<3,3>() = ____R; uvd_T_uv.col(3).topRows(3) = ____t; ____P3P_ICP_( cout << TermColor::GREEN() << "p3p done in (ms) " << elapsed_time_p3p << " p3p_ICP: {uvd}_T_{uv} : " << PoseManipUtils::prettyprintMatrix4d( uvd_T_uv ) << TermColor::RESET() << endl; ) if( min( ___s, 1.0/___s ) < 0.9 ) { ____P3P_ICP_( cout << TermColor::RED() << "theia::AlignPointCloudsUmeyama scales doesn't look good;this usually implies that estimation is bad. scale= " << ___s << TermColor::RESET() << endl; ) p3p__msg += "p3p_ICP: scale=" +to_string( ___s )+" {uvd}_T_{uv} : " + PoseManipUtils::prettyprintMatrix4d( uvd_T_uv ); p3p__msg += "p3p done in (ms)" + to_string(elapsed_time_p3p)+"; theia::AlignPointCloudsUmeyama scales doesn't look good, this usually implies that estimation is bad. scale= " + to_string(___s); // return -1; } p3p__msg += "p3p done in (ms)" + to_string(elapsed_time_p3p)+"; p3p_ICP: {uvd}_T_{uv} : " + PoseManipUtils::prettyprintMatrix4d( uvd_T_uv ); p3p__msg += ";weight="+to_string( min( ___s, 1.0/___s ) ); // return min( ___s, 1.0/___s ); #endif // with ransac timer.tic(); vector data_r; for( int i=0 ; i ransac_estimator( params, icp_estimator); ransac_estimator.Initialize(); theia::RansacSummary summary; ransac_estimator.Estimate(data_r, &best_rel_pose, &summary); auto elapsed_time_p3p_ransac = timer.toc_milli(); uvd_T_uv = Matrix4d::Identity(); uvd_T_uv = best_rel_pose.b_T_a ; ____P3P_ICP_( cout << TermColor::iGREEN() << "ICP Ransac:"; // for( int i=0; i& w_X, const std::vector& c_uv_normalized, Matrix4d& c_T_w, string& pnp__msg ) { pnp__msg = ""; ElapsedTime timer; #if 0 //--- DlsPnp std::vector solution_rotations; std::vector solution_translations; timer.tic(); theia::DlsPnp( c_uv_normalized, w_X, &solution_rotations, &solution_translations ); auto elapsed_dls_pnp = timer.toc_milli() ; ___P_N_P__( cout << elapsed_dls_pnp << " : (ms) : theia::DlsPnp done in\n"; cout << "solutions count = " << solution_rotations.size() << " " << solution_translations.size() << endl; ) if( solution_rotations.size() == 0 ) { ___P_N_P__( cout << TermColor::RED() << " theia::DlsPnp returns no solution" << TermColor::RESET() << endl; ) pnp__msg = " theia::DlsPnp returns no solution"; return -1.; } if( solution_rotations.size() > 1 ) { ___P_N_P__( cout << TermColor::RED() << " theia::DlsPnp returns multiple solution" << TermColor::RESET() << endl; ) pnp__msg = " theia::DlsPnp returns multiple solution"; return -1.; } // retrive solution c_T_w = Matrix4d::Identity(); c_T_w.topLeftCorner(3,3) = solution_rotations[0].toRotationMatrix(); c_T_w.col(3).topRows(3) = solution_translations[0]; ___P_N_P__( // cout << "solution_T " << b_T_a << endl; cout << TermColor::GREEN() << "DlsPnp (c_T_w): " << PoseManipUtils::prettyprintMatrix4d( c_T_w ) << TermColor::RESET() << endl; // out_b_T_a = b_T_a; ) pnp__msg += "DlsPnp (c_T_w): " + PoseManipUtils::prettyprintMatrix4d( c_T_w ) + ";"; pnp__msg += " elapsed_dls_pnp_ms="+to_string(elapsed_dls_pnp)+";"; // return 1.0; #endif #if 1 //--- DlsPnpWithRansac timer.tic(); // prep data vector data_r; for( int i=0 ; i ransac_estimator(params, dlspnp_estimator); // Initialize must always be called! ransac_estimator.Initialize(); theia::RansacSummary summary; ransac_estimator.Estimate(data_r, &best_rel_pose, &summary); auto elapsed_dls_pnp_ransac=timer.toc_milli(); ___P_N_P__( cout << elapsed_dls_pnp_ransac << "(ms)!! DlsPnpWithRansac ElapsedTime includes data prep\n"; cout << "Ransac:"; // for( int i=0; i stereogeom, int frame_a, int frame_b ) { // I hacve a doubt that the 3dImage which is created from stereo geometry // gives out the 3d points in co-ordinate system of right camera-center. // I am wanting it in left camera center co-ordinates. Just reproject those // points and ensure my premise. //------------------------------------------------ //------ 3d points from frame_a //------------------------------------------------ cout << TermColor::iGREEN() << "LOAD: BASE/"+std::to_string(frame_a)+".jpg" << TermColor::RESET() << endl; cv::Mat a_imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_a)+".jpg", 0 ); cv::Mat a_imright_raw = cv::imread( BASE+"/"+std::to_string(frame_a)+"_1.jpg", 0 ); Matrix4d wTA; RawFileIO::read_eigen_matrix( BASE+"/"+std::to_string(frame_a)+".wTc", wTA ); if( a_imright_raw.empty() || a_imright_raw.empty() ) { cout << TermColor::RED() << "Cannot read image with idx=" << frame_a << " perhaps filenotfound" << TermColor::RESET() << endl; return false; } cv::Mat a_imleft_srectified, a_imright_srectified; cv::Mat a_3dImage; MatrixXd a_3dpts; cv::Mat a_disp_viz; stereogeom->get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( a_imleft_raw, a_imright_raw, a_imleft_srectified,a_imright_srectified, a_3dpts, a_3dImage, a_disp_viz ); //----------------------------------------------------- //--------------- stereo rectify b //----------------------------------------------------- cout << TermColor::iGREEN() << "LOAD: BASE/"+std::to_string(frame_b)+".jpg" << TermColor::RESET() << endl; cv::Mat b_imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_b)+".jpg", 0 ); cv::Mat b_imright_raw = cv::imread( BASE+"/"+std::to_string(frame_b)+"_1.jpg", 0 ); Matrix4d wTB; RawFileIO::read_eigen_matrix( BASE+"/"+std::to_string(frame_b)+".wTc", wTB ); if( b_imleft_raw.empty() || b_imright_raw.empty() ) { cout << TermColor::RED() << "Cannot read image with idx=" << frame_b << " perhaps filenotfound" << TermColor::RESET() << endl; return false; } cv::Mat b_imleft_srectified, b_imright_srectified; cv::Mat b_3dImage; MatrixXd b_3dpts; cv::Mat b_disp_viz; stereogeom->get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( b_imleft_raw, b_imright_raw, b_imleft_srectified, b_imright_srectified, b_3dpts, b_3dImage, b_disp_viz ); //--------------------------------------------------------------------- //------------ point matches between a_left, b_left //--------------------------------------------------------------------- MatrixXd uv, uv_d; // u is from frame_a; ud is from frame_b ElapsedTime timer; timer.tic(); point_feature_matches(a_imleft_srectified, b_imleft_srectified, uv, uv_d ); string msg_pf_matches = to_string( timer.toc_milli() )+" (ms) elapsed time for point_feature_matches computation"; if( uv.cols() < 30 ) { cout << TermColor::RED() << "too few gms matches between " << frame_a << " and " << frame_b << TermColor::RESET() << endl; return false; } #if 1 // plot ppoint-feature matches cv::Mat dst_feat_matches; MiscUtils::plot_point_pair( a_imleft_srectified, uv, frame_a, b_imleft_srectified, uv_d, frame_b, dst_feat_matches, 3, msg_pf_matches+";#pf-matches: "+to_string( uv.cols() ) ); cv::resize(dst_feat_matches, dst_feat_matches, cv::Size(), 0.5, 0.5 ); cv::imshow( "dst_feat_matches", dst_feat_matches ); #endif #if 1 // disparty maps of both images cv::Mat dst_disp; MiscUtils::side_by_side( a_disp_viz, b_disp_viz, dst_disp ); MiscUtils::append_status_image( dst_disp, "a="+ to_string(frame_a)+" b="+to_string(frame_b), .8 ); cv::resize(dst_disp, dst_disp, cv::Size(), 0.5, 0.5 ); #endif // // Collect 3d points // // Option-A: cout << TermColor::BLUE() << "Option-A" << TermColor::RESET() << endl; std::vector feature_position_uv; std::vector feature_position_uv_d; std::vector world_point_uv; make_3d_2d_collection__using__pfmatches_and_disparity( stereogeom, uv, a_3dImage, uv_d, feature_position_uv, feature_position_uv_d, world_point_uv); Matrix4d op1__b_T_a; string pnp__msg; float pnp_goodness = PNP( world_point_uv, feature_position_uv_d, op1__b_T_a, pnp__msg ); cout << TermColor::YELLOW() << pnp_goodness << " op1__b_T_a = " << PoseManipUtils::prettyprintMatrix4d( op1__b_T_a ) << TermColor::RESET() << endl; cout << pnp__msg << endl; // Option-B: cout << TermColor::BLUE() << "Option-B" << TermColor::RESET() << endl; std::vector world_point_uv_d; make_3d_2d_collection__using__pfmatches_and_disparity( stereogeom, uv_d, b_3dImage, uv, feature_position_uv_d, feature_position_uv, world_point_uv_d); Matrix4d op2__a_T_b, op2__b_T_a; string pnp__msg_option_B; float pnp_goodness_optioN_B = PNP( world_point_uv_d, feature_position_uv, op2__a_T_b, pnp__msg_option_B ); op2__b_T_a = op2__a_T_b.inverse(); cout << TermColor::YELLOW() << pnp_goodness_optioN_B << " op2__a_T_b = " << PoseManipUtils::prettyprintMatrix4d( op2__a_T_b ) << TermColor::RESET() << endl; cout << TermColor::YELLOW() << pnp_goodness_optioN_B << " op2__b_T_a = " << PoseManipUtils::prettyprintMatrix4d( op2__b_T_a ) << TermColor::RESET() << endl; cout << pnp__msg_option_B << endl; // Option-C cout << TermColor::BLUE() << "Option-C" << TermColor::RESET() << endl; vector< Vector3d> uv_X; vector< Vector3d> uvd_Y; make_3d_3d_collection__using__pfmatches_and_disparity( uv, a_3dImage, uv_d, b_3dImage, uv_X, uvd_Y ); Matrix4d icp_b_T_a; string p3p__msg; float p3p_goodness = P3P_ICP( uv_X, uvd_Y, icp_b_T_a, p3p__msg ); cout << TermColor::YELLOW() << p3p_goodness << " icp_b_T_a = " << PoseManipUtils::prettyprintMatrix4d( icp_b_T_a ) << TermColor::RESET() << endl; cout << p3p__msg << endl; #if 1 MiscUtils::append_status_image( dst_disp, "[b_T_a <-- PNP( 3d(a), uv(b))];"+pnp__msg+";[a_T_b <-- PNP( 3d(b), uv(a))];"+pnp__msg_option_B+";[icp_b_T_a <-- ICP(3d(a), 3d(b))];"+p3p__msg ); cv::imshow( "dst_disp", dst_disp ); #endif Matrix4d odom_b_T_a = wTB.inverse() * wTA; cout << TermColor::YELLOW() << "odom_b_T_a = " << PoseManipUtils::prettyprintMatrix4d( odom_b_T_a ) << TermColor::RESET() << endl; cout << "|op1 - op2|" << PoseManipUtils::prettyprintMatrix4d( op1__b_T_a.inverse() * op2__b_T_a ) << endl; cout << "|op1 - icp|" << PoseManipUtils::prettyprintMatrix4d( op1__b_T_a.inverse() * icp_b_T_a ) << endl; cout << "|op2 - icp|" << PoseManipUtils::prettyprintMatrix4d( op2__b_T_a.inverse() * icp_b_T_a ) << endl; } int main0( int argc, char ** argv ) { std::shared_ptr stereogeom = make_stereo_geom(); // load ProcessedLoopCandidate.json cout << "Open JSON : " << BASE+"/matching/ProcessedLoopCandidate.json" << endl; std::ifstream fp(BASE+"/matching/ProcessedLoopCandidate.json"); json proc_candi_json; fp >> proc_candi_json; for( int i =0 ; i stereogeom = make_stereo_geom(); // cout << "exit main\n"; // return 0; // will return false if cannot compute pose if( argc != 3 ) { cout << "[MAIN::INVALID USAGE] I am expecting you to give two numbers in the command line between which you want to compute the pose\n"; exit(1); } Matrix4d out_b_T_a = Matrix4d::Identity(); int a = stoi( argv[1] ); int b = stoi( argv[2] ); // bool status = relative_pose_compute_with_theia(stereogeom, a, b, out_b_T_a ); // bool status = self_projection_test( stereogeom, a, b ); bool status = verified_alignment( stereogeom, a, b ); // bool status = relative_pose_compute_with_theia(stereogeom, 195, 3, out_b_T_a ); cv::waitKey(0); cout << "####RESULT####\n"; cout << "status: " << status << endl; cout << PoseManipUtils::prettyprintMatrix4d( out_b_T_a ) << endl; } int main1( int argc, char ** argv ) { ///////// ROS INIT ros::init(argc, argv, "camera_geometry_inspect_ptcld" ); ros::NodeHandle n; ros::Publisher chatter_pub = n.advertise("chatter", 1000); ros::Rate loop_rate(10); //////////// Open `log.json` and `loopcandidates_liverun.json` string json_fname = BASE+"/log.json"; cout << TermColor::GREEN() << "Open file: " << json_fname << TermColor::RESET() << endl; std::ifstream json_fileptr(json_fname); json json_obj; json_fileptr >> json_obj; cout << "Successfully opened file "<< json_fname << endl; string json_loops_fname = BASE+"/loopcandidates_liverun.json"; cout << TermColor::GREEN() << "Open file: " << json_loops_fname << TermColor::RESET() << endl; std::ifstream json_fileptr_loops(json_loops_fname); json json_obj_loopcandidates; json_fileptr_loops >> json_obj_loopcandidates; cout << "Successfully opened file "<< json_loops_fname << endl; ///////////// plot_vio_poses plot_vio_poses( json_obj, chatter_pub ); //////////// plot loop candidates plot_loop_candidates_as_lines( json_obj, json_obj_loopcandidates, chatter_pub ); cout << "ros::spin()\n. Press CTRL+C to quit\n"; ros::spin(); ///// stereo geometry /* std::shared_ptr stereogeom = make_stereo_geom(); int frame_a = 840; int frame_b = 1344; Matrix4d b_T_a; relative_pose_compute_with_theia( stereogeom, frame_a,frame_b, b_T_a ); cv::waitKey(0); */ } ================================================ FILE: src/unittest/unittest_theia_ransac.cpp ================================================ #include #include //opencv #include #include #include #include #include int main() { cout << "Hello\n"; // it was convient to edit unittest_theia instead....TODO: remove this file } ================================================ FILE: src/utils/CameraGeometry.cpp ================================================ #include "CameraGeometry.h" MonoGeometry::MonoGeometry(camodocal::CameraPtr _camera) { assert( _camera && "Abstract Camera is not set. You need to init camera before setting it to geometry clases" ); if( !_camera ) { cout << "Abstract Camera is not set. You need to init camera before setting it to geometry clases\n"; exit(10); } this->camera = _camera; // set K_new as default GeometryUtils::getK( this->camera, this->K_new ); this->new_fx = K_new(0,0); // 375.; this->new_fy = K_new(1,1); //375.; this->new_cx = K_new(0,2); //376.; this->new_cy = K_new(1,2); //240.; // make rectification maps. Remember to remake the maps when set_K is called this->camera->initUndistortRectifyMap( map_x, map_y, new_fx, new_fy, cv::Size(0,0), new_cx, new_cy ); } void MonoGeometry::set_K( Matrix3d K ) { std::lock_guard lk(m); this->K_new = K; this->new_fx = K_new(0,0); // 375.; this->new_fy = K_new(1,1); //375.; this->new_cx = K_new(0,2); //376.; this->new_cy = K_new(1,2); //240.; // once a new K is set will have to recompute maps. this->camera->initUndistortRectifyMap( map_x, map_y, new_fx, new_fy, cv::Size(0,0), new_cx, new_cy ); } void MonoGeometry::do_image_undistortion( const cv::Mat& im_raw, cv::Mat & im_undistorted ) { std::lock_guard lk(m); cv::remap( im_raw, im_undistorted, map_x, map_y, CV_INTER_LINEAR ); } //-------------------------------------------------------------------------------------// StereoGeometry::StereoGeometry( camodocal::CameraPtr _left_camera, camodocal::CameraPtr _right_camera, const Matrix4d& __right_T_left ) { if( !_left_camera || !_right_camera ) { cout << "\nERROR : Abstract stereo Camera is not set. You need to init camera before setting it to geometry clases\n"; exit(10); } assert( _left_camera && _right_camera && "Abstract stereo Camera is not set. You need to init camera before setting it to geometry clases" ); this->camera_left = _left_camera; this->camera_right = _right_camera; // this->right_T_left = Matrix4d( __right_T_left ); //stereo extrinsic. relative pose between two pairs. this->set_stereoextrinsic( __right_T_left ); //stereo extrinsic. relative pose between two pairs. // set K_new from left_camera's intrinsic Matrix3d __K_new; GeometryUtils::getK( this->camera_left, __K_new ); this->set_K( __K_new ); left_geom = std::make_shared( this->camera_left ); right_geom = std::make_shared( this->camera_right ); left_geom->set_K( this->K_new ); right_geom->set_K( this->K_new ); // it is really important that you have same K_new for each of the images in the stereo-pair, this is not a mistake. // stereo rectification maps // theory : http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/FUSIELLO/tutorial.html // make_stereo_rectification_maps() // no need to call this again as it is already called when u either do set_K() or set_stereoextrinsic(). // init SGBM // TODO Can put in options to the BM algorithm, look at opencv docs to set the options // like blocksize, numDisparities etc etc. bm = cv::StereoBM::create(64,21); sgbm = cv::StereoSGBM::create(); } void StereoGeometry::print_blockmatcher_algo_info() { cout << TermColor::RED(); cout << "name = " << bm->getDefaultName() << endl; cout << "getNumDisparities=" << bm->getNumDisparities() << endl; cout << "getMinDisparity=" << bm->getMinDisparity() << endl; cout << "getBlockSize=" << bm->getBlockSize() << endl; cout << TermColor::RESET() << endl; } void StereoGeometry::set_stereoextrinsic( Matrix4d __right_T_left ) { std::lock_guard lk(m_extrinsics); this->right_T_left = Matrix4d( __right_T_left ); make_stereo_rectification_maps(); } void StereoGeometry::set_stereoextrinsic(Vector4d quat_xyzw, Vector3d tr_xyz ) { std::lock_guard lk(m_extrinsics); Matrix4d transform ; //right_T_left PoseManipUtils::raw_xyzw_to_eigenmat( quat_xyzw, tr_xyz, transform ); this->right_T_left = Matrix4d( transform ); make_stereo_rectification_maps(); } const Matrix4d& StereoGeometry::get_stereoextrinsic() { std::lock_guard lk(m_extrinsics); return this->right_T_left; } void StereoGeometry::fundamentalmatrix_from_stereoextrinsic( Matrix3d& F ) { Matrix3d Tx, _Rot; { std::lock_guard lk2(m_extrinsics); PoseManipUtils::vec_to_cross_matrix( right_T_left.col(3).topRows(3), Tx ); _Rot = right_T_left.topLeftCorner(3,3); } // The actual formula is : inverse(K_right)' * Tx * R * inverse(K_left) // but we use the same K_new for both. This is a subtle point here. F = this->get_K().transpose().inverse() * Tx * _Rot * this->get_K().inverse(); //< Fundamental Matrix } void StereoGeometry::draw_epipolarlines( cv::Mat& imleft_undistorted, cv::Mat& imright_undistorted ) { IOFormat numpyFmt(FullPrecision, 0, ", ", ",\n", "[", "]", "[", "]"); // if image is 1 channel make it to 3 channel so that I can plot colored lines and points. cv::Mat imleft_undistorted_3chnl, imright_undistorted_3chnl; if( imleft_undistorted.channels() == 1 ) cv::cvtColor(imleft_undistorted, imleft_undistorted_3chnl, CV_GRAY2BGR ); else imleft_undistorted_3chnl = imleft_undistorted; if( imright_undistorted.channels() == 1 ) cv::cvtColor(imright_undistorted, imright_undistorted_3chnl, CV_GRAY2BGR); else imright_undistorted_3chnl = imright_undistorted; imleft_undistorted = imleft_undistorted_3chnl; imright_undistorted = imright_undistorted_3chnl; // Fundamental Matrix Matrix3d F; this->fundamentalmatrix_from_stereoextrinsic( F ); cout << "[StereoGeometry::draw_epipolarlines]F=" << F.format(numpyFmt) << endl; // tryout multiple points and get their corresponding epipolar line. for( int i=0 ; i<500; i+=20 ) { #if 1 // take a sample point x on left image and find the corresponding line on the right image Vector3d x(1.5*i, i, 1.0); Vector3d ld = F * x; MiscUtils::draw_point( x, imleft_undistorted, cv::Scalar(255,0,0) ); MiscUtils::draw_line( ld, imright_undistorted, cv::Scalar(255,0,0) ); #endif #if 1 // take a sample point on right image and find the corresponding line on the left image Vector3d xd(i, i, 1.0); Vector3d l = F.transpose() * xd; MiscUtils::draw_line( l, imleft_undistorted, cv::Scalar(0,0,255) ); MiscUtils::draw_point( xd, imright_undistorted, cv::Scalar(0,0,255) ); #endif } } void StereoGeometry::draw_srectified_epipolarlines( cv::Mat& imleft_srectified, cv::Mat& imright_srectified ) { IOFormat numpyFmt(FullPrecision, 0, ", ", ",\n", "[", "]", "[", "]"); // if image is 1 channel make it to 3 channel so that I can plot colored lines and points. cv::Mat imleft_srectified_3chnl, imright_srectified_3chnl; if( imleft_srectified.channels() == 1 ) cv::cvtColor(imleft_srectified, imleft_srectified_3chnl, CV_GRAY2BGR ); else imleft_srectified_3chnl = imleft_srectified; if( imright_srectified.channels() == 1 ) cv::cvtColor(imright_srectified, imright_srectified_3chnl, CV_GRAY2BGR); else imright_srectified_3chnl = imright_srectified; imleft_srectified = imleft_srectified_3chnl; imright_srectified = imright_srectified_3chnl; Matrix3d F = rm_fundamental_matrix; //< This was computed by make_stereo_rectification_maps. // this->fundamentalmatrix_from_stereoextrinsic( F ); cout << "[StereoGeometry::draw_srectified_epipolarlines]F=" << F.format(numpyFmt) << endl; // tryout multiple points and get their corresponding epipolar line. for( int i=0 ; i<500; i+=20 ) { #if 1 // take a sample point x on left image and find the corresponding line on the right image Vector3d x(1.5*i, i, 1.0); Vector3d ld = F * x; MiscUtils::draw_point( x, imleft_srectified, cv::Scalar(255,0,0) ); MiscUtils::draw_line( ld, imright_srectified, cv::Scalar(255,0,0) ); #endif #if 1 // take a sample point on right image and find the corresponding line on the left image Vector3d xd(i, i, 1.0); Vector3d l = F.transpose() * xd; MiscUtils::draw_line( l, imleft_srectified, cv::Scalar(0,0,255) ); MiscUtils::draw_point( xd, imright_srectified, cv::Scalar(0,0,255) ); #endif } } void StereoGeometry::set_K( Matrix3d K ) { std::lock_guard lk(m_intrinsics); this->K_new = K; this->new_fx = K_new(0,0); // 375.; this->new_fy = K_new(1,1); //375.; this->new_cx = K_new(0,2); //376.; this->new_cy = K_new(1,2); //240.; make_stereo_rectification_maps(); } void StereoGeometry::set_K( float _fx, float _fy, float _cx, float _cy ) { std::lock_guard lk(m_intrinsics); this->K_new << _fx, 0.0, _cx, 0.0, _fy, _cy, 0.0, 0.0, 1.0; this->new_fx = K_new(0,0); // 375.; this->new_fy = K_new(1,1); //375.; this->new_cx = K_new(0,2); //376.; this->new_cy = K_new(1,2); //240.; make_stereo_rectification_maps(); } const Matrix3d& StereoGeometry::get_K() { std::lock_guard lk(m_intrinsics); return this->K_new; } // #define __StereoGeometry___make_stereo_rectification_maps(msg) msg; #define __StereoGeometry___make_stereo_rectification_maps(msg) ; void StereoGeometry::make_stereo_rectification_maps() { cv::Size imsize = cv::Size( camera_left->imageWidth(), camera_left->imageHeight()); IOFormat numpyFmt(FullPrecision, 0, ", ", ",\n", "[", "]", "[", "]"); // Adopted from : http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/FUSIELLO/node18.html __StereoGeometry___make_stereo_rectification_maps( cout << "[compute_stereo_rectification_transform]" << endl ); Matrix3d right_R_left = this->right_T_left.topLeftCorner(3,3); Vector3d right_t_left = this->right_T_left.col(3).topRows(3); cv::Mat R, T; cv::eigen2cv( right_R_left, R ); cv::eigen2cv( right_t_left, T ); __StereoGeometry___make_stereo_rectification_maps( cout << "R"<< R << endl; cout << "T" << T << endl; cout << "this->K_new: "<< this->K_new << endl; ) // Matrix3d K_new_eigen = get_K(); //< this causes some issues. Better not use it. Matrix3d K_new_eigen = this->K_new;//get_K(); __StereoGeometry___make_stereo_rectification_maps( cout << "K_new_eigen: " << K_new_eigen << endl; ) cv::Mat K_new_cvmat; cv::eigen2cv( K_new_eigen, K_new_cvmat ); // TODO: If need be write getters for these matrices. // cv::Mat R1, R2; // cv::Mat P1, P2; // cv::Mat Q; cv::Mat D; // !! Main Call OpenCV !! // /// The below function does exactly this: http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/FUSIELLO/node18.html // See OpenCVdoc: https://docs.opencv.org/3.4/d9/d0c/group__calib3d.html#ga617b1685d4059c6040827800e72ad2b6 cv::stereoRectify( K_new_cvmat, D, K_new_cvmat, D, imsize, R, T, rm_R1, rm_R2, rm_P1, rm_P2, rm_Q ); __StereoGeometry___make_stereo_rectification_maps( cout << "\tK_new=" << cv::format(K_new_cvmat, cv::Formatter::FMT_NUMPY) << endl; cout << "\tR1=" << cv::format(rm_R1, cv::Formatter::FMT_NUMPY) << endl; cout << "\tR2=" << cv::format(rm_R2, cv::Formatter::FMT_NUMPY) << endl; cout << "\tP1=" << cv::format(rm_P1, cv::Formatter::FMT_NUMPY) << endl; cout << "\tP2=" << cv::format(rm_P2, cv::Formatter::FMT_NUMPY) << endl; cout << "\tQ=" << cv::format(rm_Q, cv::Formatter::FMT_NUMPY) << endl; ) // cout << TermColor::RESET(); // For horizontal stereo : // P2(0,3) := Tx * f, where f = P2(0,0) and Tx is the position of left camera as seen by the right camera. right_T_left. double Tx = rm_P2.at(0,3) / rm_P2.at(0,0); rm_shift = Vector3d( Tx, 0, 0); __StereoGeometry___make_stereo_rectification_maps(cout << "\tTx = " << Tx << " Tx is the position of left camera as seen by the right camera. right_T_left" << endl); Matrix3d TTx; PoseManipUtils::vec_to_cross_matrix( rm_shift, TTx ); rm_fundamental_matrix = K_new.transpose().inverse() * TTx * K_new.inverse(); __StereoGeometry___make_stereo_rectification_maps(cout << "F_rect=" << rm_fundamental_matrix.format(numpyFmt) << endl); // For vertical stereo : TODO // P2(1,3) := Ty * f, where f = P2(0,0) and Tx is the horizontal shift between cameras // double Ty = P2(1,3) / P2(0,0); __StereoGeometry___make_stereo_rectification_maps(cout << "[/compute_stereo_rectification_transform]" << endl); // !! Make Stereo Rectification Maps !! // __StereoGeometry___make_stereo_rectification_maps(cout << "[make_rectification_maps]\n"); cv::initUndistortRectifyMap( K_new_cvmat, D, rm_R1, rm_P1, imsize, CV_32FC1, map1_x, map1_y ); cv::initUndistortRectifyMap( K_new_cvmat, D, rm_R2, rm_P2, imsize, CV_32FC1, map2_x, map2_y ); __StereoGeometry___make_stereo_rectification_maps( cout << "\tmap1_x: " << MiscUtils::cvmat_info( map1_x ) << endl; cout << "\tmap1_y: " << MiscUtils::cvmat_info( map1_y ) << endl; cout << "\tmap2_x: " << MiscUtils::cvmat_info( map2_x ) << endl; cout << "\tmap2_y: " << MiscUtils::cvmat_info( map2_y ) << endl; cout << "[/make_rectification_maps]\n"); } //-------------------------- Undistort Raw Image ----------------------------------------// void StereoGeometry::do_image_undistortion( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& imleft_undistorted, cv::Mat& imright_undistorted ) { std::lock_guard lk(m_intrinsics); // cout << "imleft_raw " << MiscUtils::cvmat_info( imleft_raw ) << endl; // cout << "imright_raw " << MiscUtils::cvmat_info( imright_raw ) << endl; // this doesn't need a lock as this is not dependent on left_geom->do_image_undistortion( imleft_raw, imleft_undistorted ); right_geom->do_image_undistortion( imright_raw, imright_undistorted ); } //--------------------------SRectify (Stereo Rectify)----------------------------------------// void StereoGeometry::do_stereo_rectification_of_undistorted_images( const cv::Mat& imleft_undistorted, const cv::Mat& imright_undistorted, cv::Mat& imleft_srectified, cv::Mat& imright_srectified ) { cv::remap( imleft_undistorted, imleft_srectified, this->map1_x, this->map1_y, CV_INTER_LINEAR ); cv::remap( imright_undistorted, imright_srectified, this->map2_x, this->map2_y, CV_INTER_LINEAR ); } void StereoGeometry::do_stereo_rectification_of_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& imleft_srectified, cv::Mat& imright_srectified ) { cv::Mat imleft_undistorted, imright_undistorted; // raw --> undistorted do_image_undistortion(imleft_raw, imright_raw, imleft_undistorted, imright_undistorted ); // undistorted --> stereo rectify do_stereo_rectification_of_undistorted_images(imleft_undistorted, imright_undistorted, imleft_srectified, imright_srectified ); } //-----------------------------------DISPARITY Computation--------------------------------------// // stereo rectified --> disparity. Uses the stereo-block matching algorithm, // ie. cv::StereoBM // if you use this function, besure to call `do_stereo_rectification_of_undistorted_images` // or `do_stereo_rectification_of_raw_images` on your images before calling this function void StereoGeometry::do_stereoblockmatching_of_srectified_images( const cv::Mat& imleft_srectified, const cv::Mat& imright_srectified, cv::Mat& disparity ) { // sgbm->compute( imleft_srectified, imright_srectified, disparity ); //disparity is CV_16UC1 bm->compute( imleft_srectified, imright_srectified, disparity ); //disparity is CV_16UC1 // cout << "[StereoGeometry::do_stereoblockmatching_of_srectified_images] disparity" << MiscUtils::cvmat_info( disparity ) << endl; } // raw--> disparity // internally does: // step-1: raw-->undistorted // step-2: undistorted--> srectify (stereo rectify) // step-3: srectify --> disparity void StereoGeometry::do_stereoblockmatching_of_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& disparity ) { cv::Mat imleft_srectified, imright_srectified; do_stereo_rectification_of_raw_images( imleft_raw, imright_raw, imleft_srectified, imright_srectified ); do_stereoblockmatching_of_srectified_images( imleft_srectified, imright_srectified, disparity ); } // undistorted --> disparity // step-1: undistorted --> srectified // step-2 : srectified --> disparity void StereoGeometry::do_stereoblockmatching_of_undistorted_images( const cv::Mat& imleft_undistorted, const cv::Mat& imright_undistorted, cv::Mat& disparity ) { cv::Mat imleft_srectified, imright_srectified; do_stereo_rectification_of_undistorted_images( imleft_undistorted, imright_undistorted, imleft_srectified, imright_srectified ); do_stereoblockmatching_of_srectified_images( imleft_srectified, imright_srectified, disparity ); } //----------------------------- DISPARITY and point cloud -------------------------// // # Inputs // disparity_raw : The disparity image // fill_eigen_matrix : _3dpts will be allocated and filled only if this flag is true. If this flag is false then eigen matrix is not filled in this function. // make_homogeneous : true will result in Eigen matrix being 4xN else will be 3xN. fill_eigen_matrix need to be true for this to matter. // # Outputs // out3d : 3 channel image. X,Y,Z --> ch0, ch1, ch2. Will also contain invalid 3d points. // _3dpts : 4xN matrix containing only valid points. N < disparity_raw.shape[0]*disparity_raw.shape[1]. void StereoGeometry::disparity_to_3DPoints(const cv::Mat& disparity_raw, cv::Mat& out3D, MatrixXd& _3dpts, bool fill_eigen_matrix, bool make_homogeneous ) { // Adopted from : https://github.com/bkornel/Reproject-Image-To-3D // OpenCV3.3 version has a bug not sure about other version. It produces inf. // /\/\/\/\/\/\/\/\/\ ATTEMPT-1 /\/\/\/\/\/\/\/\/\ //---------------- assemble all the data needed for computation const cv::Mat Q = this->get_Q(); CV_Assert( !disparity_raw.empty() ); cv::Mat disparity; if( disparity_raw.type() != CV_32FC1 ) disparity_raw.convertTo( disparity, CV_32FC1 ); else disparity = disparity_raw; CV_Assert(disparity.type() == CV_32FC1 ); CV_Assert( Q.cols == 4 && Q.rows == 4); CV_Assert( Q.type() == CV_32F || Q.type() == CV_64F ); // 3-channel matrix for containing the reprojected 3D world coordinates out3D = cv::Mat::zeros(disparity.size(), CV_32FC3); // Getting the interesting parameters from Q, everything else is zero or one float Q03,Q13,Q23,Q32,Q33; if( Q.type() == CV_32F ) { Q03 = Q.at(0, 3); Q13 = Q.at(1, 3); Q23 = Q.at(2, 3); Q32 = Q.at(3, 2); Q33 = Q.at(3, 3); } else { Q03 = (float) Q.at(0, 3); Q13 = (float) Q.at(1, 3); Q23 = (float) Q.at(2, 3); Q32 = (float) Q.at(3, 2); Q33 = (float) Q.at(3, 3); } //--------------------- disparity to 3D points store as 3-channel image // Transforming a single-channel disparity map to a 3-channel image representing a 3D surface for (int i = 0; i < disparity.rows; i++) { const float* disp_ptr = disparity.ptr(i); cv::Vec3f* out3D_ptr = out3D.ptr(i); for (int j = 0; j < disparity.cols; j++) { const float pw = 1.0f / (disp_ptr[j]/16. * Q32 + Q33 + 1E-6); // 1E-6 added to avoid inf. // ---------------------------------^^^^^ // it is crucial that you convert the raw_disparity to // CV_32F and divide by 16. This is because raw_disparity from StereoBM() is in CV_16SC1 // (16bit) and a disparity value := NumDisparities*16. so if you have NumDisparities as 64 // the maximum value in disparity will be 1024. This is a bit weird. So be careful. cv::Vec3f& point = out3D_ptr[j]; point[0] = (static_cast(j)+Q03) * pw; point[1] = (static_cast(i)+Q13) * pw; point[2] = Q23 * pw; } } // /\/\/\/\/\/\/\/\/\ ATTEMPT-2 /\/\/\/\/\/\/\/\/ /* const cv::Mat Q = this->get_Q(); cv::Mat Q_32; // opencv's reprojectImageTo3D() need Q to be in CV_32FC1 Q.convertTo( Q_32, CV_32F ); cv::Mat disparity_32f; disparity_raw.convertTo( disparity_32f, CV_32F ); disparity_32f *= (1/16.); // if using opencv's reprojectImageTo3D() it is crucial that you convert the raw_disparity to // CV_32F and divide by 16. This is because raw_disparity from StereoBM() is in CV_16SC1 // (16bit) and a disparity value := NumDisparities*16. so if you have NumDisparities as 64 // the maximum value in disparity will be 1024. This is a bit weird. So be careful. cout << "Q cvinfo" << MiscUtils::cvmat_info( Q ) << endl; cout << "Q_32 cvinfo" << MiscUtils::cvmat_info( Q_32 ) << endl; cout << "Q" << Q << endl; cout << ( (Q_32.size() == cv::Size(4,4))?"true":"false" )<< endl; cv::reprojectImageTo3D( disparity_32f, out3D, Q_32, true ); cv::Mat disparity = disparity_raw; // END /\/\/\/\/\/\/\/\/\ ATTEMPT-2 /\/\/\/\/\/\/\/\/ */ if( fill_eigen_matrix == false ) return ; //--------------------------- Eigen 3D points as 3xN or 4xN // TODO: Make this more efficient. If I enable 4xN matrix it takes 8ms/image if I disable it takes 3ms/image. // Step-1: loop over out3D to know how how many were valid points. remove inf points and remove behind points (points where disparity was invalid) int N = 0; for (int i = 0; i < disparity.rows; i++) { const float* disp_ptr = disparity.ptr(i); cv::Vec3f* out3D_ptr = out3D.ptr(i); for (int j = 0; j < disparity.cols; j++) { cv::Vec3f& point = out3D_ptr[j]; if( point[2] < 500. && point[2] > 0.0001 ) { N++; } } } // Step-2: Allocate Memory if( make_homogeneous ) { _3dpts = MatrixXd::Zero( 4, N ); // Step-3: Copy values into Eigen::Matrix of shape 4xN. int n=0; for (int i = 0; i < disparity.rows; i++) { const float* disp_ptr = disparity.ptr(i); cv::Vec3f* out3D_ptr = out3D.ptr(i); for (int j = 0; j < disparity.cols; j++) { cv::Vec3f& point = out3D_ptr[j]; if( point[2] < 500. && point[2] > 0.0001 ) { _3dpts(0,n) = (double)point[0]; _3dpts(1,n) = (double)point[1]; _3dpts(2,n) = (double)point[2]; _3dpts(3,n) = (double)1.0; n++; } } } } else { _3dpts = MatrixXd::Zero( 3, N ); // Step-3: Copy values into Eigen::Matrix of shape 4xN. int n=0; for (int i = 0; i < disparity.rows; i++) { const float* disp_ptr = disparity.ptr(i); cv::Vec3f* out3D_ptr = out3D.ptr(i); for (int j = 0; j < disparity.cols; j++) { cv::Vec3f& point = out3D_ptr[j]; if( point[2] < 500. && point[2] > 0.0001 ) { _3dpts(0,n) = (double)point[0]; _3dpts(1,n) = (double)point[1]; _3dpts(2,n) = (double)point[2]; n++; } } } } } // _3dpts : 4xN // 1. raw --> srectified // 2. srectified --> disparity_raw // 3. disparity_raw --> 3d points // return only 3d points void StereoGeometry::get3dpoints_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& _3dpts ) { cv::Mat disp_raw; this->do_stereoblockmatching_of_raw_images( imleft_raw, imright_raw, disp_raw ); const cv::Mat Q = this->get_Q(); cv::Mat _3dImage; this->disparity_to_3DPoints( disp_raw, _3dImage, _3dpts, true, true ); // cout << "notimplemented\n"; // exit(11); } // returns both 3dpoints and 3dimage void StereoGeometry::get3dpoints_and_3dmap_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& _3dpts, cv::Mat& _3dImage ) { cv::Mat disp_raw; this->do_stereoblockmatching_of_raw_images( imleft_raw, imright_raw, disp_raw ); const cv::Mat Q = this->get_Q(); this->disparity_to_3DPoints( disp_raw, _3dImage, _3dpts, true, true ); } // returns both 3dpoints and 3dimage along with srectified image pair void StereoGeometry::get3dpoints_and_3dmap_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& _3dpts, cv::Mat& _3dImage, cv::Mat& imleft_srectified, cv::Mat& imright_srectified ) { // raw --> srectified this->do_stereo_rectification_of_raw_images( imleft_raw, imright_raw, imleft_srectified, imright_srectified ); // srectified --> disp cv::Mat disp_raw; this->do_stereoblockmatching_of_srectified_images( imleft_srectified, imright_srectified, disp_raw ); const cv::Mat Q = this->get_Q(); this->disparity_to_3DPoints( disp_raw, _3dImage, _3dpts, true, true ); } void StereoGeometry::get3dpoints_and_disparity_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& _3dpts, cv::Mat& disparity_for_visualization ) { cv::Mat disp_raw; this->do_stereoblockmatching_of_raw_images( imleft_raw, imright_raw, disp_raw ); cv::Mat _3dImage; this->disparity_to_3DPoints( disp_raw, _3dImage, _3dpts, true, true ); cv::Mat disparity_for_visualization_gray; cv::normalize(disp_raw, disparity_for_visualization_gray, 0, 255, CV_MINMAX, CV_8U); //< disp8 used just for visualization cv::applyColorMap(disparity_for_visualization_gray, disparity_for_visualization, cv::COLORMAP_HOT); // cout << "notimplemented\n"; // exit(11); } void StereoGeometry::get_srectifiedim_and_3dpoints_and_disparity_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& imleft_srectified, cv::Mat& imright_srectified, MatrixXd& _3dpts, cv::Mat& disparity_for_visualization ) { // raw --> srectified this->do_stereo_rectification_of_raw_images( imleft_raw, imright_raw, imleft_srectified, imright_srectified ); // block matching cv::Mat disp_raw; this->do_stereoblockmatching_of_srectified_images( imleft_srectified, imright_srectified, disp_raw ); // disparity to 3D points cv::Mat _3dImage; this->disparity_to_3DPoints( disp_raw, _3dImage, _3dpts, true, true ); cv::Mat disparity_for_visualization_gray; cv::normalize(disp_raw, disparity_for_visualization_gray, 0, 255, CV_MINMAX, CV_8U); //< disp8 used just for visualization cv::applyColorMap(disparity_for_visualization_gray, disparity_for_visualization, cv::COLORMAP_HOT); } void StereoGeometry::get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& imleft_srectified, cv::Mat& imright_srectified, MatrixXd& _3dpts, cv::Mat& _3dImage, cv::Mat& disparity_for_visualization ) { // raw --> srectified this->do_stereo_rectification_of_raw_images( imleft_raw, imright_raw, imleft_srectified, imright_srectified ); // block matching cv::Mat disp_raw; this->do_stereoblockmatching_of_srectified_images( imleft_srectified, imright_srectified, disp_raw ); // disparity to 3D points this->disparity_to_3DPoints( disp_raw, _3dImage, _3dpts, true, true ); cv::Mat disparity_for_visualization_gray; cv::normalize(disp_raw, disparity_for_visualization_gray, 0, 255, CV_MINMAX, CV_8U); //< disp8 used just for visualization cv::applyColorMap(disparity_for_visualization_gray, disparity_for_visualization, cv::COLORMAP_HOT); } // _3dImage: 3d points as 3 channel image. 1st channel is X, 2nd channel is Y and 3rd channel is Z. // Also note that these co-ordinates and imleft_raw co-ordinate do not correspond. They correspond to // the srectified images. Incase you want to use the color info with these 3d points, this // will lead to wrong. You should compute the srectified images for that. // 1. raw --> srectified // 2. srectified --> disparity_raw // 3. disparity_raw --> 3d points void StereoGeometry::get3dmap_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& _3dImage ) { cv::Mat disp_raw; this->do_stereoblockmatching_of_raw_images( imleft_raw, imright_raw, disp_raw ); const cv::Mat Q = this->get_Q(); MatrixXd _3dpts; this->disparity_to_3DPoints( disp_raw, _3dImage, _3dpts, false, true ); // cout << "notimplemented\n"; // exit(11); } // e_3dImageX, e_3dImageY, e_3dImageZ: cv::split( _3dImage ). same size as imleft_raw.shape. // Also note that these co-ordinates and imleft_raw co-ordinate do not correspond. They correspond to // the srectified images. Incase you want to use the color info with these 3d points, this // will lead to wrong. You should compute the srectified images for that. // 1. raw --> srectified // 2. srectified --> disparity_raw // 3. disparity_raw --> 3d points void StereoGeometry::get3dmap_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& e_3dImageX, MatrixXd& e_3dImageY, MatrixXd& e_3dImageZ ) { // TODO : perhaps return vector cout << "this is somewhat fragile. I am making a 3 array of matrix and returning it. This is bad. this need to be tested for correctness after this can be used safely."; exit(11); cv::Mat disp_raw; this->do_stereoblockmatching_of_raw_images( imleft_raw, imright_raw, disp_raw ); const cv::Mat Q = this->get_Q(); cv::Mat _3dImage; MatrixXd _3dpts; this->disparity_to_3DPoints( disp_raw, _3dImage, _3dpts, false, true ); cv::Mat _3dImage_XYZ[3]; //destination array cv::split(_3dImage,_3dImage_XYZ);//split source cv::cv2eigen( _3dImage_XYZ[0], e_3dImageX ); cv::cv2eigen( _3dImage_XYZ[1], e_3dImageY ); cv::cv2eigen( _3dImage_XYZ[2], e_3dImageZ ); } //-------------------------------------------------------------------------------------// void GeometryUtils::make_K( float new_fx, float new_fy, float new_cx, float new_cy, Matrix3d& K ) { K << new_fx, 0, new_cx, 0, new_fy, new_cy, 0, 0, 1; } void GeometryUtils::getK( camodocal::CameraPtr m_cam, Matrix3d& K ) { Matrix3d K_rect; vector m_camera_params; m_cam->writeParameters( m_camera_params ); // retrive camera params from Abstract Camera. // camodocal::CataCamera::Parameters p(); // cout << "size=" << m_camera_params.size() << " ::>\n" ; // for( int i=0 ; imodelType() ) { case camodocal::Camera::ModelType::MEI: K_rect << m_camera_params[5], 0, m_camera_params[7], 0, m_camera_params[6], m_camera_params[8], 0, 0, 1; break; case camodocal::Camera::ModelType::PINHOLE: K_rect << m_camera_params[4], 0, m_camera_params[6], 0, m_camera_params[5], m_camera_params[7], 0, 0, 1; break; case camodocal::Camera::ModelType::KANNALA_BRANDT: K_rect << m_camera_params[4], 0, m_camera_params[6], 0, m_camera_params[5], m_camera_params[7], 0, 0, 1; break; default: // TODO: Implement for other models. Look at initUndistortRectifyMap for each of the abstract class. cout << "[getK] Wrong\nQuit...."; cout << "Implement for other models. Look at initUndistortRectifyMap for each of the abstract class.\n"; exit(10); } K = Matrix3d(K_rect); } // given a point cloud as 3xN or 4xN matrix, gets colors for each based on the depth void GeometryUtils::depthColors( const MatrixXd& ptcld, vector& out_colors, double min, double max ) { assert( ( ptcld.rows() == 3 || ptcld.rows() == 4 ) && "ptcld need to be either a 3xN or a 4xN matrix" ); assert( ptcld.cols() > 0 ); cv::Mat colormap_gray = cv::Mat::zeros( 1, 256, CV_8UC1 ); for( int i=0 ; i<256; i++ ) colormap_gray.at(0,i) = i; cv::Mat colormap_color; cv::applyColorMap(colormap_gray, colormap_color, cv::COLORMAP_HOT); if( min < 0 ) min = ptcld.row(2).minCoeff(); if( max < 0 ) max = ptcld.row(2).maxCoeff(); double mean = ptcld.row(2).mean(); assert( max > min ); out_colors.clear(); for( int k=0 ; k 255 ) ixx = 255; cv::Vec3b f = colormap_color.at(0, (int)ixx ); out_colors.push_back( cv::Scalar(f[0], f[1], f[2]) ); } // cout << "min = " << min << " max=" << max << " mean=" << mean << endl; } // given a point cloud as 3xN or 4xN matrix, gets colors for each based on the depth. // return in out_colors as 3xN void GeometryUtils::depthColors( const MatrixXd& ptcld, MatrixXd& out_colors, double min, double max ) { assert( ( ptcld.rows() == 3 || ptcld.rows() == 4 ) && "ptcld need to be either a 3xN or a 4xN matrix" ); assert( ptcld.cols() > 0 ); cv::Mat colormap_gray = cv::Mat::zeros( 1, 256, CV_8UC1 ); for( int i=0 ; i<256; i++ ) colormap_gray.at(0,i) = i; cv::Mat colormap_color; cv::applyColorMap(colormap_gray, colormap_color, cv::COLORMAP_HOT); if( min < 0 ) min = ptcld.row(2).minCoeff(); if( max < 0 ) max = ptcld.row(2).maxCoeff(); double mean = ptcld.row(2).mean(); assert( max > min ); out_colors = MatrixXd( 3,ptcld.cols() ); for( int k=0 ; k 255 ) ixx = 255; cv::Vec3b f = colormap_color.at(0, (int)ixx ); // out_colors.push_back( cv::Scalar(f[0], f[1], f[2]) ); out_colors( 0, k ) = float(f[2]) / 255.; out_colors( 1, k ) = float(f[1]) / 255.; out_colors( 2, k ) = float(f[0]) / 255.; } // cout << "min = " << min << " max=" << max << " mean=" << mean << endl; } void GeometryUtils::idealProjection( const Matrix3d& K, const MatrixXd& c_X, MatrixXd& uv ) { assert( c_X.rows() == 4 && "c_X need to be expressed in homogeneous co-ordinates\n" ); assert( c_X.cols() > 0 ); // a) c_X = c_X / c_X.row(2). ==> Z divide // b) perspective_proj = c_X.topRows(3) MatrixXd uv_normalized = MatrixXd::Constant( 3, c_X.cols(), 1.0 ); uv_normalized.row(0) = c_X.row(0).array() / c_X.row(2).array(); uv_normalized.row(1) = c_X.row(1).array() / c_X.row(2).array(); // c) uv = K * c_X uv = K * uv_normalized; } void GeometryUtils::idealProjection( const Matrix3d& K, const vector& c_X, MatrixXd& uv ) { assert( c_X.size() > 0 ); // a) c_X = c_X / c_X.row(2). ==> Z divide // b) perspective_proj = c_X.topRows(3) MatrixXd uv_normalized = MatrixXd::Constant( 3, c_X.size(), 1.0 ); // uv_normalized.row(0) = c_X.row(0).array() / c_X.row(2).array(); // uv_normalized.row(1) = c_X.row(1).array() / c_X.row(2).array(); for( int i=0 ; i& c_X, MatrixXd& uv ) { assert( c_X.size() > 0 ); assert( abs(w_T_c(3,3)-1.) < 1.0E-5 ); // a) c_X = c_X / c_X.row(2). ==> Z divide // b) perspective_proj = c_X.topRows(3) MatrixXd uv_normalized = MatrixXd::Constant( 3, c_X.size(), 1.0 ); // uv_normalized.row(0) = c_X.row(0).array() / c_X.row(2).array(); // uv_normalized.row(1) = c_X.row(1).array() / c_X.row(2).array(); for( int i=0 ; i #include #include #include #include #include #include "camodocal/camera_models/Camera.h" #include "camodocal/camera_models/CameraFactory.h" #include "camodocal/camera_models/CataCamera.h" #include "camodocal/camera_models/EquidistantCamera.h" #include "MiscUtils.h" #include "ElapsedTime.h" #include "PoseManipUtils.h" #include "TermColor.h" // Threads and threadsafety #include #include #include class MonoGeometry { public: MonoGeometry() { } MonoGeometry(camodocal::CameraPtr _camera ); // Set the new intrinsic matrix (optional). By default will use the same as // the abstract camera. but the K matrix is essentially just scaling pixel sizes. // this comes to play in stereopairs and/or motion stereo. void set_K( Matrix3d K ); // this is just a call to cv::remap with the maps already computed. void do_image_undistortion( const cv::Mat& im_raw, cv::Mat & im_undistorted ); private: camodocal::CameraPtr camera; Matrix3d K_new; float new_fx, new_fy, new_cx, new_cy; cv::Mat map_x, map_y; std::mutex m; }; // There are 3 types of images // im*_raw // im*_undistorted // im*_stereo_rectified class StereoGeometry { public: // right_T_left: extrinsic calibration for stereo pair. The position of left camera as viewed from right camera. StereoGeometry( camodocal::CameraPtr _left_camera, camodocal::CameraPtr _right_camera, const Matrix4d& right_T_left ); // intrinsic cam matrix for both cameras. Here it is important to set this // else the block matching computation won't be valid. If this is not set // we will use the matrix for left camera as K_new. // Everytime a new K is set, we have to recompute the maps. void set_K( Matrix3d K ); void set_K( float _fx, float _fy, float _cx, float _cy ); const Matrix3d& get_K(); // set extrinsic. THis is thread safe // Everytime a new extrinsic is set, we have to recompute the maps. void set_stereoextrinsic( Matrix4d __right_T_left ); void set_stereoextrinsic( Vector4d quat_xyzw, Vector3d tr_xyz ); const Matrix4d& get_stereoextrinsic(); void fundamentalmatrix_from_stereoextrinsic( Matrix3d& F ); //TODO write another function to return fundamental matrix of rectified stereo pair. // Draw epipolar lines on both views. These images will be overwritten. You need // to give me undistorted image-pairs. You can undistory image pair with // StereoGeometry::do_image_undistortion() void draw_epipolarlines( cv::Mat& imleft_undistorted, cv::Mat& imright_undistorted ); void draw_srectified_epipolarlines( cv::Mat& imleft_srectified, cv::Mat& imright_srectified ); void do_image_undistortion( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& imleft_undistorted, cv::Mat& imright_undistorted ); // given the undistorted images outputs the stereo-rectified pairs // This is essentially just cv::remap using the stereo-rectification maps void do_stereo_rectification_of_undistorted_images( const cv::Mat& imleft_undistorted, const cv::Mat& imright_undistorted, cv::Mat& imleft_srectified, cv::Mat& imright_srectified ); // Given raw image pair return stereo-rectified pair. This is a 2 step process // raw --> undistorted --> stereo rectified void do_stereo_rectification_of_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& imleft_srectified, cv::Mat& imright_srectified ); // stereo rectified --> disparity. Uses the stereo-block matching algorithm, // ie. cv::StereoBM // if you use this function, besure to call `do_stereo_rectification_of_undistorted_images` // or `do_stereo_rectification_of_raw_images` on your images before calling this function. // returned disparity is CV_16SC1. Be cautioned. void do_stereoblockmatching_of_srectified_images( const cv::Mat& imleft_srectified, const cv::Mat& imright_srectified, cv::Mat& disparity ); // raw--> disparity // internally does: // step-1: raw-->undistorted // step-2: undistorted--> srectify (stereo rectify) // step-3: srectify --> disparity void do_stereoblockmatching_of_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& disparity ); // undistorted --> disparity // step-1: undistorted --> srectified // step-2 : srectified --> disparity void do_stereoblockmatching_of_undistorted_images( const cv::Mat& imleft_undistorted, const cv::Mat& imright_undistorted, cv::Mat& disparity ); // # Inputs // disparity_raw : The disparity image // fill_eigen_matrix : _3dpts will be allocated and filled only if this flag is true. If this flag is false then eigen matrix is not filled in this function. // make_homogeneous : true will result in Eigen matrix being 4xN else will be 3xN. fill_eigen_matrix need to be true for this to matter. // # Outputs // out3d : 3 channel image. X,Y,Z --> ch0, ch1, ch2. Will also contain invalid 3d points. // _3dpts : 4xN matrix containing only valid points. N < disparity_raw.shape[0]*disparity_raw.shape[1]. void disparity_to_3DPoints(const cv::Mat& disparity_raw, cv::Mat& out3D, MatrixXd& _3dpts, bool fill_eigen_matrix=true, bool make_homogeneous=true ); // #Input // imleft_raw, imright_raw : Raw images. As is from the camera. // # Output // _3dpts : 4xN // 1. raw --> srectified // 2. srectified --> disparity_raw // 3. disparity_raw --> 3d points void get3dpoints_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& _3dpts ); // #Input // imleft_raw, imright_raw : Raw images. As is from the camera. // # Output // _3dpts : 4xN // 1. raw --> srectified // 2. srectified --> disparity_raw // 3. disparity_raw --> 3d points // _3dImage: 3d points as 3 channel image. 1st channel is X, 2nd channel is Y and 3rd channel is Z. void get3dpoints_and_3dmap_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& _3dpts, cv::Mat& _3dImage ); // returns both 3dpoints and 3dimage along with srectified image pair void get3dpoints_and_3dmap_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& _3dpts, cv::Mat& _3dImage, cv::Mat& imleft_srectified, cv::Mat& imright_srectified ); // #Input // imleft_raw, imright_raw : Raw images. As is from the camera. // # Output // _3dImage: 3d points as 3 channel image. 1st channel is X, 2nd channel is Y and 3rd channel is Z. // Also note that these co-ordinates and imleft_raw co-ordinate do not correspond. They correspond to // the srectified images. Incase you want to use the color info with these 3d points, this // will lead to wrong. You should compute the srectified images for that. // 1. raw --> srectified // 2. srectified --> disparity_raw // 3. disparity_raw --> 3d points void get3dmap_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& _3dImage ); // #Input // imleft_raw, imright_raw : Raw images. As is from the camera. // # Output // e_3dImageX, e_3dImageY, e_3dImageZ: cv::split( _3dImage ). same size as imleft_raw.shape. // Also note that these co-ordinates and imleft_raw co-ordinate do not correspond. They correspond to // the srectified images. Incase you want to use the color info with these 3d points, this // will lead to wrong. You should compute the srectified images for that. // 1. raw --> srectified // 2. srectified --> disparity_raw // 3. disparity_raw --> 3d points void get3dmap_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& e_3dImageX, MatrixXd& e_3dImageY, MatrixXd& e_3dImageZ ); // Given raw image pair returns valid 3d points and disparity false color map. // #Input // imleft_raw, imright_raw : Raw images. As is from the camera. // #Output // _3dpts : 4xN matrix. // disparity_for_visualization : false color mapped for visualization only. Don't do any processing with this one. void get3dpoints_and_disparity_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, MatrixXd& _3dpts, cv::Mat& disparity_for_visualization ); // Given raw image pair return a) stereo-rectified image pair b) valid 3d points c) disparity false color map void get_srectifiedim_and_3dpoints_and_disparity_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& imleft_srectified, cv::Mat& imright_srectified, MatrixXd& _3dpts, cv::Mat& disparity_for_visualization ); void get_srectifiedim_and_3dpoints_and_3dmap_and_disparity_from_raw_images( const cv::Mat& imleft_raw, const cv::Mat& imright_raw, cv::Mat& imleft_srectified, cv::Mat& imright_srectified, MatrixXd& _3dpts, cv::Mat& _3dImage, cv::Mat& disparity_for_visualization ); // Some semi private getters of internal variables public: const cv::Mat& get_Q() const { return rm_Q; } // TODO: (if need be) also have getters from rm_R1, rm_R2, rm_P1, rm_P2, rm_shift, rm_fundamental_matrix. const cv::Mat& get_rm_R1( ) const { return rm_R1; } const cv::Mat& get_rm_R2( ) const { return rm_R2; } const cv::Mat& get_rm_P1( ) const { return rm_P1; } const cv::Mat& get_rm_P2( ) const { return rm_P2; } void print_blockmatcher_algo_info(); private: camodocal::CameraPtr camera_left, camera_right; Matrix4d right_T_left; ///< extrinsic calibration of the stereopair. Matrix3d K_new; float new_fx, new_fy, new_cx, new_cy; std::shared_ptr left_geom, right_geom; // stereo rectify maps // theory : http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/FUSIELLO/tutorial.html mutable std::mutex m_extrinsics; mutable std::mutex m_intrinsics; // This function is called everytime you change the intrinsics and extrinsics for // the stereo pair. This outputs the stereo-rectification maps, ie. map1_x, map1_y, map2_x, map2_y // This is not intended to be a public call. // TODO: thread safety void make_stereo_rectification_maps(); cv::Mat rm_R1, rm_R2, rm_P1, rm_P2, rm_Q; Vector3d rm_shift; // right_T_left Matrix3d rm_fundamental_matrix; // fundamental matrix after rectification cv::Mat map1_x, map1_y, map2_x, map2_y; // Stereo Block Matching cv::Ptr bm; cv::Ptr sgbm; }; class GeometryUtils { public: // Given the focal length make a K out of it static void make_K( float new_fx, float new_fy, float new_cx, float new_cy, Matrix3d& K ); // For a camera gets a K static void getK( camodocal::CameraPtr m_cam, Matrix3d& K ); // Ideal Projection: // a) c_X = c_X / c_X.row(2). ==> Z divide // b) perspective_proj = c_X.topRows(3) // c) uv = K * c_X // # Input // K : Camera params. K_new // c_X : 3D points expressed in frame of the camera. // Note that if you have world 3d points you will need to transform // it to camera co-ordinates before you pass it to this function. // uv : image co-ordinates (xy) static void idealProjection( const Matrix3d& K, const MatrixXd& c_X, MatrixXd& uv ); static void idealProjection( const Matrix3d& K, const vector& c_X, MatrixXd& uv ); static void idealProjection( const Matrix3d& K, const Matrix4d& w_T_c, const vector& c_X, MatrixXd& uv ); // given a point cloud as 3xN or 4xN matrix, gets colors for each based on the depth // min==-1 then we will compute the min from data, else will use whatever was supplied // max==-1 then we will compute the max from data, else will use whatever. static void depthColors( const MatrixXd& ptcld, vector& out_colors, double min=-1, double max=-1 ); static void depthColors( const MatrixXd& ptcld, MatrixXd& out_colors, double min=-1, double max=-1 ); // out_colors : 3xN rgb \in [0,1] }; ================================================ FILE: src/utils/ElapsedTime.h ================================================ #pragma once #include #include #include #include #include class ElapsedTime { public: ElapsedTime() { // start timer this->begin = std::chrono::steady_clock::now(); } void tic() { // start timer this->begin = std::chrono::steady_clock::now(); } int toc_milli() { std::chrono::steady_clock::time_point end= std::chrono::steady_clock::now(); return (int) std::chrono::duration_cast(end - begin).count(); } int toc_micro() { std::chrono::steady_clock::time_point end= std::chrono::steady_clock::now(); return (int) std::chrono::duration_cast(end - begin).count(); } int toc( ) { std::chrono::steady_clock::time_point end= std::chrono::steady_clock::now(); return (int) std::chrono::duration_cast(end - begin).count(); } private: std::chrono::steady_clock::time_point begin; }; class DateAndTime { public: static std::string current_date_and_time() { auto now = std::chrono::system_clock::now(); auto in_time_t = std::chrono::system_clock::to_time_t(now); std::stringstream ss; ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %X"); return ss.str(); } // TODO Can make more functions later to get hr, min, date etc. Not priority. }; ================================================ FILE: src/utils/GMSMatcher/gms_matcher.cpp ================================================ #include "gms_matcher.h" int gms_matcher::GetInlierMask(vector &vbInliers, bool WithScale, bool WithRotation) { int max_inlier = 0; if (!WithScale && !WithRotation) { SetScale(0); max_inlier = run(1); vbInliers = mvbInlierMask; return max_inlier; } if (WithRotation && WithScale) { for (int Scale = 0; Scale < 5; Scale++) { SetScale(Scale); for (int RotationType = 1; RotationType <= 8; RotationType++) { int num_inlier = run(RotationType); if (num_inlier > max_inlier) { vbInliers = mvbInlierMask; max_inlier = num_inlier; } } } return max_inlier; } if (WithRotation && !WithScale) { SetScale(0); for (int RotationType = 1; RotationType <= 8; RotationType++) { int num_inlier = run(RotationType); if (num_inlier > max_inlier) { vbInliers = mvbInlierMask; max_inlier = num_inlier; } } return max_inlier; } if (!WithRotation && WithScale) { for (int Scale = 0; Scale < 5; Scale++) { SetScale(Scale); int num_inlier = run(1); if (num_inlier > max_inlier) { vbInliers = mvbInlierMask; max_inlier = num_inlier; } } return max_inlier; } return max_inlier; } void gms_matcher::AssignMatchPairs(int GridType) { for (size_t i = 0; i < mNumberMatches; i++) { Point2f &lp = mvP1[mvMatches[i].first]; Point2f &rp = mvP2[mvMatches[i].second]; int lgidx = mvMatchPairs[i].first = GetGridIndexLeft(lp, GridType); int rgidx = -1; if (GridType == 1) { rgidx = mvMatchPairs[i].second = GetGridIndexRight(rp); } else { rgidx = mvMatchPairs[i].second; } if (lgidx < 0 || rgidx < 0) continue; mMotionStatistics.at(lgidx, rgidx)++; mNumberPointsInPerCellLeft[lgidx]++; } } void gms_matcher::VerifyCellPairs(int RotationType) { const int *CurrentRP = mRotationPatterns[RotationType - 1]; for (int i = 0; i < mGridNumberLeft; i++) { if (sum(mMotionStatistics.row(i))[0] == 0) { mCellPairs[i] = -1; continue; } int max_number = 0; for (int j = 0; j < mGridNumberRight; j++) { int *value = mMotionStatistics.ptr(i); if (value[j] > max_number) { mCellPairs[i] = j; max_number = value[j]; } } int idx_grid_rt = mCellPairs[i]; const int *NB9_lt = mGridNeighborLeft.ptr(i); const int *NB9_rt = mGridNeighborRight.ptr(idx_grid_rt); int score = 0; double thresh = 0; int numpair = 0; for (size_t j = 0; j < 9; j++) { int ll = NB9_lt[j]; int rr = NB9_rt[CurrentRP[j] - 1]; if (ll == -1 || rr == -1) continue; score += mMotionStatistics.at(ll, rr); thresh += mNumberPointsInPerCellLeft[ll]; numpair++; } thresh = THRESH_FACTOR * sqrt(thresh / numpair); if (score < thresh) mCellPairs[i] = -2; } } int gms_matcher::run(int RotationType) { mvbInlierMask.assign(mNumberMatches, false); // Initialize Motion Statisctics mMotionStatistics = Mat::zeros(mGridNumberLeft, mGridNumberRight, CV_32SC1); mvMatchPairs.assign(mNumberMatches, pair(0, 0)); for (int GridType = 1; GridType <= 4; GridType++) { // initialize mMotionStatistics.setTo(0); mCellPairs.assign(mGridNumberLeft, -1); mNumberPointsInPerCellLeft.assign(mGridNumberLeft, 0); AssignMatchPairs(GridType); VerifyCellPairs(RotationType); // Mark inliers for (size_t i = 0; i < mNumberMatches; i++) { if (mvMatchPairs[i].first >= 0) { if (mCellPairs[mvMatchPairs[i].first] == mvMatchPairs[i].second) { mvbInlierMask[i] = true; } } } } int num_inlier = sum(mvbInlierMask)[0]; return num_inlier; } ================================================ FILE: src/utils/GMSMatcher/gms_matcher.h ================================================ #pragma once #include #include #include #include using namespace std; using namespace cv; #define THRESH_FACTOR 6 // 8 possible rotation and each one is 3 X 3 const int mRotationPatterns[8][9] = { 1,2,3, 4,5,6, 7,8,9, 4,1,2, 7,5,3, 8,9,6, 7,4,1, 8,5,2, 9,6,3, 8,7,4, 9,5,1, 6,3,2, 9,8,7, 6,5,4, 3,2,1, 6,9,8, 3,5,7, 2,1,4, 3,6,9, 2,5,8, 1,4,7, 2,3,6, 1,5,9, 4,7,8 }; // 5 level scales const double mScaleRatios[5] = { 1.0, 1.0 / 2, 1.0 / sqrt(2.0), sqrt(2.0), 2.0 }; class gms_matcher { public: // OpenCV Keypoints & Correspond Image Size & Nearest Neighbor Matches gms_matcher(const vector &vkp1, const Size size1, const vector &vkp2, const Size size2, const vector &vDMatches) { // Input initialize NormalizePoints(vkp1, size1, mvP1); NormalizePoints(vkp2, size2, mvP2); mNumberMatches = vDMatches.size(); ConvertMatches(vDMatches, mvMatches); // Grid initialize mGridSizeLeft = Size(20, 20); mGridNumberLeft = mGridSizeLeft.width * mGridSizeLeft.height; // Initialize the neihbor of left grid mGridNeighborLeft = Mat::zeros(mGridNumberLeft, 9, CV_32SC1); InitalizeNiehbors(mGridNeighborLeft, mGridSizeLeft); }; ~gms_matcher() {}; private: // Normalized Points vector mvP1, mvP2; // Matches vector > mvMatches; // Number of Matches size_t mNumberMatches; // Grid Size Size mGridSizeLeft, mGridSizeRight; int mGridNumberLeft; int mGridNumberRight; // x : left grid idx // y : right grid idx // value : how many matches from idx_left to idx_right Mat mMotionStatistics; // vector mNumberPointsInPerCellLeft; // Inldex : grid_idx_left // Value : grid_idx_right vector mCellPairs; // Every Matches has a cell-pair // first : grid_idx_left // second : grid_idx_right vector > mvMatchPairs; // Inlier Mask for output vector mvbInlierMask; // Mat mGridNeighborLeft; Mat mGridNeighborRight; public: // Get Inlier Mask // Return number of inliers int GetInlierMask(vector &vbInliers, bool WithScale = false, bool WithRotation = false); private: // Normalize Key Points to Range(0 - 1) void NormalizePoints(const vector &kp, const Size &size, vector &npts) { const size_t numP = kp.size(); const int width = size.width; const int height = size.height; npts.resize(numP); for (size_t i = 0; i < numP; i++) { npts[i].x = kp[i].pt.x / width; npts[i].y = kp[i].pt.y / height; } } // Convert OpenCV DMatch to Match (pair) void ConvertMatches(const vector &vDMatches, vector > &vMatches) { vMatches.resize(mNumberMatches); for (size_t i = 0; i < mNumberMatches; i++) { vMatches[i] = pair(vDMatches[i].queryIdx, vDMatches[i].trainIdx); } } int GetGridIndexLeft(const Point2f &pt, int type) { int x = 0, y = 0; if (type == 1) { x = floor(pt.x * mGridSizeLeft.width); y = floor(pt.y * mGridSizeLeft.height); if (y >= mGridSizeLeft.height || x >= mGridSizeLeft.width){ return -1; } } if (type == 2) { x = floor(pt.x * mGridSizeLeft.width + 0.5); y = floor(pt.y * mGridSizeLeft.height); if (x >= mGridSizeLeft.width || x < 1) { return -1; } } if (type == 3) { x = floor(pt.x * mGridSizeLeft.width); y = floor(pt.y * mGridSizeLeft.height + 0.5); if (y >= mGridSizeLeft.height || y < 1) { return -1; } } if (type == 4) { x = floor(pt.x * mGridSizeLeft.width + 0.5); y = floor(pt.y * mGridSizeLeft.height + 0.5); if (y >= mGridSizeLeft.height || y < 1 || x >= mGridSizeLeft.width || x < 1) { return -1; } } return x + y * mGridSizeLeft.width; } int GetGridIndexRight(const Point2f &pt) { int x = floor(pt.x * mGridSizeRight.width); int y = floor(pt.y * mGridSizeRight.height); return x + y * mGridSizeRight.width; } // Assign Matches to Cell Pairs void AssignMatchPairs(int GridType); // Verify Cell Pairs void VerifyCellPairs(int RotationType); // Get Neighbor 9 vector GetNB9(const int idx, const Size& GridSize) { vector NB9(9, -1); int idx_x = idx % GridSize.width; int idx_y = idx / GridSize.width; for (int yi = -1; yi <= 1; yi++) { for (int xi = -1; xi <= 1; xi++) { int idx_xx = idx_x + xi; int idx_yy = idx_y + yi; if (idx_xx < 0 || idx_xx >= GridSize.width || idx_yy < 0 || idx_yy >= GridSize.height) continue; NB9[xi + 4 + yi * 3] = idx_xx + idx_yy * GridSize.width; } } return NB9; } void InitalizeNiehbors(Mat &neighbor, const Size& GridSize) { for (int i = 0; i < neighbor.rows; i++) { vector NB9 = GetNB9(i, GridSize); int *data = neighbor.ptr(i); memcpy(data, &NB9[0], sizeof(int) * 9); } } void SetScale(int Scale) { // Set Scale mGridSizeRight.width = mGridSizeLeft.width * mScaleRatios[Scale]; mGridSizeRight.height = mGridSizeLeft.height * mScaleRatios[Scale]; mGridNumberRight = mGridSizeRight.width * mGridSizeRight.height; // Initialize the neihbor of right grid mGridNeighborRight = Mat::zeros(mGridNumberRight, 9, CV_32SC1); InitalizeNiehbors(mGridNeighborRight, mGridSizeRight); } // Run int run(int RotationType); }; ================================================ FILE: src/utils/MiscUtils.cpp ================================================ #include "MiscUtils.h" string MiscUtils::type2str(int type) { string r; uchar depth = type & CV_MAT_DEPTH_MASK; uchar chans = 1 + (type >> CV_CN_SHIFT); switch ( depth ) { case CV_8U: r = "8U"; break; case CV_8S: r = "8S"; break; case CV_16U: r = "16U"; break; case CV_16S: r = "16S"; break; case CV_32S: r = "32S"; break; case CV_32F: r = "32F"; break; case CV_64F: r = "64F"; break; default: r = "User"; break; } r += "C"; r += (chans+'0'); return r; } string MiscUtils::cvmat_info( const cv::Mat& mat ) { std::stringstream buffer; buffer << "shape=" << mat.rows << "," << mat.cols << "," << mat.channels() ; buffer << "\t" << "dtype=" << MiscUtils::type2str( mat.type() ); return buffer.str(); } string MiscUtils::imgmsg_info(const sensor_msgs::ImageConstPtr &img_msg) { std::stringstream buffer; buffer << "---\n"; buffer << "\theader:\n"; buffer << "\t\tseq: " << img_msg->header.seq << endl; buffer << "\t\tstamp: " << img_msg->header.stamp << endl; buffer << "\t\tframe_id: " << img_msg->header.frame_id << endl; buffer << "\twidth:" << img_msg->width << endl; buffer << "\theight:" << img_msg->height << endl; buffer << "\tencoding:" << img_msg->encoding << endl; buffer << "\tis_bigendian:" << (int) img_msg->is_bigendian << endl; buffer << "\tstep:" << img_msg->step << endl; buffer << "\tdata:" << "[..." << img_msg->step * img_msg->height << " sized array...]" << endl; return buffer.str(); } string MiscUtils::imgmsg_info(const sensor_msgs::Image& img_msg) { std::stringstream buffer; buffer << "---\n"; buffer << "\theader:\n"; buffer << "\t\tseq: " << img_msg.header.seq << endl; buffer << "\t\tstamp: " << img_msg.header.stamp << endl; buffer << "\t\tframe_id: " << img_msg.header.frame_id << endl; buffer << "\twidth:" << img_msg.width << endl; buffer << "\theight:" << img_msg.height << endl; buffer << "\tencoding:" << img_msg.encoding << endl; buffer << "\tis_bigendian:" << (int) img_msg.is_bigendian << endl; buffer << "\tstep:" << img_msg.step << endl; buffer << "\tdata:" << "[..." << img_msg.step * img_msg.height << " sized array...]" << endl; return buffer.str(); } cv::Mat MiscUtils::getImageFromMsg(const sensor_msgs::ImageConstPtr &img_msg) { cv_bridge::CvImageConstPtr ptr; if (img_msg->encoding == "8UC1") { sensor_msgs::Image img; img.header = img_msg->header; img.height = img_msg->height; img.width = img_msg->width; img.is_bigendian = img_msg->is_bigendian; img.step = img_msg->step; img.data = img_msg->data; img.encoding = "mono8"; ptr = cv_bridge::toCvCopy(img, sensor_msgs::image_encodings::MONO8); } else ptr = cv_bridge::toCvCopy(img_msg, sensor_msgs::image_encodings::MONO8); cv::Mat img = ptr->image.clone(); return img; } std::vector MiscUtils::split( std::string const& original, char separator ) { std::vector results; std::string::const_iterator start = original.begin(); std::string::const_iterator end = original.end(); std::string::const_iterator next = std::find( start, end, separator ); while ( next != end ) { results.push_back( std::string( start, next ) ); start = next + 1; next = std::find( start, end, separator ); } results.push_back( std::string( start, next ) ); return results; } void MiscUtils::keypoint_2_eigen( const std::vector& kp, MatrixXd& uv, bool make_homogeneous ) { assert( kp.size() > 0 && "MiscUtils::keypoint_2_eigen empty kp."); uv = MatrixXd::Constant( (make_homogeneous?3:2), kp.size(), 1.0 ); for( int i=0; i& kp1, const std::vector& kp2, const std::vector matches, MatrixXd& M1, MatrixXd& M2, bool make_homogeneous ) { assert( matches.size() > 0 && "MiscUtils::dmatch_2_eigen DMatch cannot be empty" ); assert( kp1.size() > 0 && kp2.size() > 0 && "MiscUtils::dmatch_2_eigen keypoints cannot be empty" ); M1 = MatrixXd::Constant( (make_homogeneous?3:2), matches.size(), 1.0 ); M2 = MatrixXd::Constant( (make_homogeneous?3:2), matches.size(), 1.0 ); for( int i=0 ; i=0 && queryIdx < kp1.size() ); assert( trainIdx >=0 && trainIdx < kp2.size() ); M1(0,i) = kp1[queryIdx].pt.x; M1(1,i) = kp1[queryIdx].pt.y; M2(0,i) = kp2[trainIdx].pt.x; M2(1,i) = kp2[trainIdx].pt.y; } } void MiscUtils::plot_point_sets( const cv::Mat& im, const MatrixXd& pts_set, cv::Mat& dst, const cv::Scalar& color, bool enable_keypoint_annotation, const string& msg ) { MatrixXf pts_set_float; pts_set_float = pts_set.cast(); cv::Mat pts_set_mat; cv::eigen2cv( pts_set_float, pts_set_mat ); MiscUtils::plot_point_sets( im, pts_set_mat, dst, color, enable_keypoint_annotation, msg ); } // in place manip void MiscUtils::plot_point_sets( cv::Mat& im, const MatrixXd& pts_set, const cv::Scalar& color, bool enable_keypoint_annotation, const string& msg ) { MatrixXf pts_set_float; pts_set_float = pts_set.cast(); cv::Mat pts_set_mat; cv::eigen2cv( pts_set_float, pts_set_mat ); MiscUtils::plot_point_sets( im, pts_set_mat, im, color, enable_keypoint_annotation, msg ); } // TODO: call the next function instead of actually doing the work. void MiscUtils::plot_point_sets( const cv::Mat& im, const cv::Mat& pts_set, cv::Mat& dst, const cv::Scalar& color, bool enable_keypoint_annotation, const string& msg ) { if( im.data == dst.data ) { // this is pointer comparison to know if this operation is inplace // cout << "src and dst are same\n"; // src and dst images are same, so dont copy. just ensure it is a 3 channel image. assert( im.rows > 0 && im.cols > 0 && "\n[MiscUtils::plot_point_sets]Image appears to be emoty. cannot plot.\n"); assert( im.channels() == 3 && dst.channels() == 3 && "[MiscUtils::plot_point_sets]src and dst image are same physical image in memory. They need to be 3 channel." ); } else { // dst = cv::Mat( im.rows, im.cols, CV_8UC3 ); if( im.channels() == 1 ) cv::cvtColor( im, dst, cv::COLOR_GRAY2BGR ); else im.copyTo(dst); } // cv::putText( dst, to_string(msg.length()), cv::Point(5,5), cv::FONT_HERSHEY_COMPLEX_SMALL, .95, cv::Scalar(0,255,255) ); if( msg.length() > 0 ) { vector msg_split; msg_split = MiscUtils::split( msg, ';' ); for( int q=0 ; q(0,i),pts_set.at(1,i) ); cv::circle( dst, pt, 2, color, -1 ); char to_s[20]; sprintf( to_s, "%d", i); if( enable_keypoint_annotation ) { // cv::putText( dst, to_s, pt, cv::FONT_HERSHEY_COMPLEX_SMALL, 0.5, cv::Scalar(255,255,255) - color ); cv::putText( dst, to_s, pt, cv::FONT_HERSHEY_COMPLEX_SMALL, 0.5, color ); } } } void MiscUtils::plot_point_sets( cv::Mat& im, const MatrixXd& pts_set, const cv::Scalar& color, const VectorXi& annotations, const string& msg ) { plot_point_sets( im, pts_set, im, color, annotations, msg ); } void MiscUtils::plot_point_sets( cv::Mat& im, const MatrixXd& pts_set, cv::Mat& dst, const cv::Scalar& color, const VectorXi& annotations, const string& msg ) { // TODO consider addressof(a) == addressof(b) // dst = im.clone(); assert( im.rows > 0 && im.cols > 0 && "\n[MiscUtils::plot_point_sets]Image appears to be emoty. cannot plot.\n"); assert( pts_set.cols() > 0 && pts_set.cols() == annotations.rows() && "[MiscUtils::plot_point_sets] VectorXi annotation size must be equal to number of points. If you wish to use the default annotation ie. 0,1,...n use `true` instead. If you do not want annotation use `false`." ); if( im.data == dst.data ) { // cout << "src and dst are same\n"; // src and dst images are same, so dont copy. just ensure it is a 3 channel image. assert( im.channels() == 3 && dst.channels() == 3 && "[MiscUtils::plot_point_sets]src and dst image are same physical image in memory. They need to be 3 channel." ); } else { // dst = cv::Mat( im.rows, im.cols, CV_8UC3 ); if( im.channels() == 1 ) cv::cvtColor( im, dst, cv::COLOR_GRAY2BGR ); else im.copyTo(dst); } // cv::putText( dst, to_string(msg.length()), cv::Point(5,5), cv::FONT_HERSHEY_COMPLEX_SMALL, .95, cv::Scalar(0,255,255) ); if( msg.length() > 0 ) { vector msg_split; msg_split = MiscUtils::split( msg, ';' ); for( int q=0 ; q(0,i),pts_set.at(1,i) ); pt = cv::Point2d( (float)pts_set(0,i), (float)pts_set(1,i) ); cv::circle( dst, pt, 2, color, -1 ); char to_s[20]; sprintf( to_s, "%d", annotations(i) ); cv::putText( dst, to_s, pt, cv::FONT_HERSHEY_COMPLEX_SMALL, 0.5, color ); } } void MiscUtils::plot_point_sets( const cv::Mat& im, const MatrixXd& pts_set, cv::Mat& dst, vector& color_annotations, float alpha, const string& msg ) { assert( im.rows > 0 && im.cols > 0 && "\n[MiscUtils::plot_point_sets]Image appears to be emoty. cannot plot.\n"); assert( pts_set.cols() > 0 && pts_set.cols() == color_annotations.size() && "[MiscUtils::plot_point_sets] len of color vector must be equal to number of pts.\n" ); if( im.data == dst.data ) { // cout << "src and dst are same\n"; // src and dst images are same, so dont copy. just ensure it is a 3 channel image. assert( im.channels() == 3 && dst.channels() == 3 && "[MiscUtils::plot_point_sets]src and dst image are same physical image in memory. They need to be 3 channel." ); } else { // dst = cv::Mat( im.rows, im.cols, CV_8UC3 ); if( im.channels() == 1 ) cv::cvtColor( im, dst, cv::COLOR_GRAY2BGR ); else im.copyTo(dst); } // cv::putText( dst, to_string(msg.length()), cv::Point(5,5), cv::FONT_HERSHEY_COMPLEX_SMALL, .95, cv::Scalar(0,255,255) ); if( msg.length() > 0 ) { vector msg_split; msg_split = MiscUtils::split( msg, ';' ); for( int q=0 ; q im.cols || pts_set(1,i) < 0 || pts_set(1,i) > im.rows ) { // avoid points which are outside n_outside_image_domain++; continue; } pt = cv::Point( (int)pts_set(0,i), (int)pts_set(1,i) ); cv::Scalar _color = color_annotations[i]; dst.at< cv::Vec3b >( pt )[0] = (uchar) ( (1.0-alpha)*(float)dst.at< cv::Vec3b >( pt )[0] + (alpha)*(float)_color[0] ); dst.at< cv::Vec3b >( pt )[1] = (uchar) ( (1.0-alpha)*(float)dst.at< cv::Vec3b >( pt )[1] + (alpha)*(float)_color[1] ); dst.at< cv::Vec3b >( pt )[2] = (uchar) ( (1.0-alpha)*(float)dst.at< cv::Vec3b >( pt )[2] + (alpha)*(float)_color[2] ); // cv::Vec3d new_color( .5*_orgcolor[0]+.5*_color[0] + .5*_orgcolor[1]+.5*_color[1] + .5*_orgcolor[2]+.5*_color[2] ) } if( float(n_outside_image_domain)/ float(pts_set.cols() ) > 0.2 ) // print only if u see too many outside the image cout << "[MiscUtils::plot_point_sets] with color at every point, found " << n_outside_image_domain << " outside the image of total points to plot=" << pts_set.cols() << "\n"; } void MiscUtils::plot_point_pair( const cv::Mat& imA, const MatrixXd& ptsA, int idxA, const cv::Mat& imB, const MatrixXd& ptsB, int idxB, // const VectorXd& mask, cv::Mat& dst, const cv::Scalar& color_marker, const cv::Scalar& color_line, bool annotate_pts, /*const vector& msg,*/ const string& msg ) { // ptsA : ptsB : 2xN or 3xN assert( imA.rows == imB.rows && imA.rows > 0 ); assert( imA.cols == imB.cols && imA.cols > 0 ); assert( ptsA.cols() == ptsB.cols() && ptsA.cols() > 0 ); // assert( mask.size() == ptsA.cols() ); cv::Mat outImg_; cv::hconcat(imA, imB, outImg_); cv::Mat outImg; if( outImg_.channels() == 3 ) outImg = outImg_; else cv::cvtColor( outImg_, outImg, CV_GRAY2BGR ); // loop over all points int count = 0; for( int kl=0 ; kl 0 ) { // ':' separated. Each will go in new line std::vector msg_tokens = split(msg, ';'); for( int h=0 ; h(0,i) = i; cv::applyColorMap(colormap_gray, colormap_color, cv::COLORMAP_JET ); if( f<0 ) { cv::Vec3b f_ = colormap_color.at(0, (int)0 ); cv::Scalar color_marker = cv::Scalar(f_[0],f_[1],f_[2]); return color_marker; } if( f>1. ) { cv::Vec3b f_ = colormap_color.at(0, (int)255 ); cv::Scalar color_marker = cv::Scalar(f_[0],f_[1],f_[2]); return color_marker; } int idx = (int) (f*255.); cv::Vec3b f_ = colormap_color.at(0, (int)idx ); cv::Scalar color_marker = cv::Scalar(f_[0],f_[1],f_[2]); return color_marker; } void MiscUtils::plot_point_pair( const cv::Mat& imA, const MatrixXd& ptsA, int idxA, const cv::Mat& imB, const MatrixXd& ptsB, int idxB, cv::Mat& dst, short color_map_direction, const string& msg ) { // ptsA : ptsB : 2xN or 3xN assert( color_map_direction >= 0 && color_map_direction<=3 ); assert( imA.rows == imB.rows && imA.rows > 0 ); assert( imA.cols == imB.cols && imA.cols > 0 ); assert( ptsA.cols() == ptsB.cols() && ptsA.cols() > 0 ); // assert( mask.size() == ptsA.cols() ); // make colormap cv::Mat colormap_gray = cv::Mat::zeros( 1, 256, CV_8UC1 ); for( int i=0 ; i<256; i++ ) colormap_gray.at(0,i) = i; cv::Mat colormap_color; cv::applyColorMap(colormap_gray, colormap_color, cv::COLORMAP_HSV); cv::Mat outImg_; cv::hconcat(imA, imB, outImg_); cv::Mat outImg; if( outImg_.channels() == 3 ) outImg = outImg_; else cv::cvtColor( outImg_, outImg, CV_GRAY2BGR ); // loop over all points int count = 0; for( int kl=0 ; kl255 ) coloridx=0; cv::Vec3b f = colormap_color.at(0, (int)coloridx ); cv::Scalar color_marker = cv::Scalar(f[0],f[1],f[2]); cv::circle( outImg, A, 2,color_marker, -1 ); cv::circle( outImg, B+cv::Point2d(imA.cols,0), 2,color_marker, -1 ); /* cv::line( outImg, A, B+cv::Point2d(imA.cols,0), color_line ); if( annotate_pts ) { cv::putText( outImg, to_string(kl), A, cv::FONT_HERSHEY_SIMPLEX, 0.3, color_marker, 1 ); cv::putText( outImg, to_string(kl), B+cv::Point2d(imA.cols,0), cv::FONT_HERSHEY_SIMPLEX, 0.3, color_marker, 1 ); } */ } cv::Mat status = cv::Mat(150, outImg.cols, CV_8UC3, cv::Scalar(0,0,0) ); cv::putText( status, to_string(idxA).c_str(), cv::Point(10,30), cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(255,255,255), 2 ); cv::putText( status, to_string(idxB).c_str(), cv::Point(imA.cols+10,30), cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(255,255,255), 2 ); cv::putText( status, "marked # pts: "+to_string(count), cv::Point(10,60), cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(255,255,255), 1.5 ); // put msg in status image if( msg.length() > 0 ) { // ':' separated. Each will go in new line std::vector msg_tokens = split(msg, ';'); for( int h=0 ; h2)?0.4:txt_size; std::vector msg_tokens = split(msg, ';'); int status_im_height = 50+20*msg_tokens.size(); cv::Mat status; if( is_single_channel ) status = cv::Mat(status_im_height, im.cols, CV_8UC1, cv::Scalar(0,0,0) ); else status = cv::Mat(status_im_height, im.cols, CV_8UC3, bg_color ); for( int h=0 ; h" << b << endl; // cv::line( im, a, b, cv::Scalar(255,255,255) ); MiscUtils::draw_fullLine( im, a, b, color ); } // mark point on the image, pt is in homogeneous co-ordinate. void MiscUtils::draw_point( const Vector3d pt, cv::Mat& im, cv::Scalar color ) { // C++: void circle(Mat& img, Point center, int radius, const Scalar& color, int thickness=1, int lineType=8, int shift=0) cv::circle( im, cv::Point2f( pt(0)/pt(2), pt(1)/pt(2) ), 2, color, -1 ); } // mark point on image void MiscUtils::draw_point( const Vector2d pt, cv::Mat& im, cv::Scalar color ) { cv::circle( im, cv::Point2f( pt(0), pt(1) ), 2, color, -1 ); } ================================================ FILE: src/utils/MiscUtils.h ================================================ #pragma once #include #include #include #include #include #include //opencv #include #include #include #include #include #include #include using namespace Eigen; #include using namespace std; #include "GMSMatcher/gms_matcher.h" #include "ElapsedTime.h" #include #include class MiscUtils { public: static string type2str(int type); static string cvmat_info( const cv::Mat& mat ); static string imgmsg_info(const sensor_msgs::ImageConstPtr &img_msg); static string imgmsg_info(const sensor_msgs::Image& img_msg); static cv::Mat getImageFromMsg(const sensor_msgs::ImageConstPtr &img_msg); static std::vector split( std::string const& original, char separator ); //---------------------------- Conversions ---------------------------------// // convert from opencv format of keypoints to Eigen static void keypoint_2_eigen( const std::vector& kp, MatrixXd& uv, bool make_homogeneous=true ); // given opencv keypoints and DMatch will produce M1, and M2 the co-ordinates static void dmatch_2_eigen( const std::vector& kp1, const std::vector& kp2, const std::vector matches, MatrixXd& M1, MatrixXd& M2, bool make_homogeneous=true ); //---------------------------- Conversions ---------------------------------// //--------------------- Plot Keypoints on Image ----------------------------// // Eigen Interace: PLotting functions with Eigen Interfaces static void plot_point_sets( const cv::Mat& im, const MatrixXd& pts_set, cv::Mat& dst, const cv::Scalar& color, bool enable_keypoint_annotation=true,const string& msg=string("") ); // cv::Mat Interfaces: Plotting Functions with cv::Mat Interfaces. static void plot_point_sets( const cv::Mat& im, const cv::Mat& pts_set, cv::Mat& dst, const cv::Scalar& color, bool enable_keypoint_annotation=true, const string& msg=string("") ); // Inplace plotting. Here dont need to specify a separate destination. src is modified. static void plot_point_sets( cv::Mat& im, const MatrixXd& pts_set, const cv::Scalar& color, bool enable_keypoint_annotation=true, const string& msg=string("") ); // Plotting with annotations specified by VectorXi static void plot_point_sets( cv::Mat& im, const MatrixXd& pts_set, cv::Mat& dst, const cv::Scalar& color, const VectorXi& annotations, const string& msg ); // Plotting with annotations specified by VectorXi inplace static void plot_point_sets( cv::Mat& im, const MatrixXd& pts_set, const cv::Scalar& color, const VectorXi& annotations, const string& msg ); // plot point with colors specified at every point. pts_set : 3xN or 2xN, len(color_annotations) == pts_set.cols() static void plot_point_sets( const cv::Mat& im, const MatrixXd& pts_set, cv::Mat& dst, vector& color_annotations, float alpha=0.8, const string& msg=string("N/A") ); // END--------------------- Plot Keypoints on Image ----------------------------// //------------------------------- Plot Matchings on image pair -------------------------// // Plots [ imA | imaB ] with points correspondences // [Input] // imA, imB : Images // ptsA, ptsB : 2xN or 3xN // idxA, idxB : Index of each of the image. This will appear in status part. No other imppact of these. // color_marker : color of the point marker // color_line : color of the line // annotate_pts : true with putText for each point. False will not putText. // [Output] // outImg : Output image static void plot_point_pair( const cv::Mat& imA, const MatrixXd& ptsA, int idxA, const cv::Mat& imB, const MatrixXd& ptsB, int idxB, cv::Mat& dst, const cv::Scalar& color_marker, const cv::Scalar& color_line=cv::Scalar(0,255,0), bool annotate_pts=false, const string& msg=string("N.A") ); // nearly same as the above, but will color every co-ordinate with different color // color_map_direction : 0 ==> // horizontal-gradiant // 1 ==> // vertical-gradiant // 2 ==> // manhattan-gradiant // 3 ==> // image centered manhattan-gradiant static void plot_point_pair( const cv::Mat& imA, const MatrixXd& ptsA, int idxA, const cv::Mat& imB, const MatrixXd& ptsB, int idxB, cv::Mat& dst, short color_map_direction, const string& msg=string("N.A") ); //------------------------------- Plot Matchings on image pair -------------------------// //------------------------- Points and Lines on Images --------------------------------// // Given two image-points draw line between them, extend both ways. Infinite line-segments static void draw_fullLine(cv::Mat& img, cv::Point2f a, cv::Point2f b, cv::Scalar color); // draw line on the image, given a line equation in homogeneous co-ordinates. l = (a,b,c) for ax+by+c = 0 static void draw_line( const Vector3d l, cv::Mat& im, cv::Scalar color ); // mark point on the image, pt is in homogeneous co-ordinate. static void draw_point( const Vector3d pt, cv::Mat& im, cv::Scalar color ); // mark point on image static void draw_point( const Vector2d pt, cv::Mat& im, cv::Scalar color ); // append a status image . ';' separated static void append_status_image( cv::Mat& im, const string& msg, float txt_size=0.4, cv::Scalar bg_color=cv::Scalar(0,0,0), cv::Scalar txt_color=cv::Scalar(255,255,255), float line_thinkness=1.5 ); static bool side_by_side( const cv::Mat& A, const cv::Mat& B, cv::Mat& dst ); static bool vertical_side_by_side( const cv::Mat& A, const cv::Mat& B, cv::Mat& dst ); // END ------------------------- Points and Lines on Images --------------------------------// // [Input] : f a float between 0 and 1. // [Output]: cv::Scalar gives out a bgr color pallet. // Note: This is inefficient, don't use it too often. If you are going to do lot of quering for colors use `class FalseColors` static cv::Scalar getFalseColor( float f ); private: static double Slope(int x0, int y0, int x1, int y1); }; class FalseColors { public: FalseColors() { cv::Mat colormap_gray = cv::Mat::zeros( 1, 256, CV_8UC1 ); for( int i=0 ; i<256; i++ ) colormap_gray.at(0,i) = i; cv::applyColorMap(colormap_gray, colormap_color, cv::COLORMAP_JET ); } cv::Scalar getFalseColor( float f ) { int idx = (int) (f*255.); if( f<0 ) { idx=0; } if( f>255 ) { idx=255; } cv::Vec3b f_ = colormap_color.at(0, (int)idx ); cv::Scalar color_marker = cv::Scalar(f_[0],f_[1],f_[2]); return color_marker; } cv::Mat getStrip( int nrows, int ncols ) { cv::Mat colormap_gray = cv::Mat::zeros( nrows, ncols, CV_8UC1 ); for( int r=0; r(r,c) = (uchar) ( (float(c)/ncols)*256 ); } cv::Mat __dst; cv::applyColorMap(colormap_gray, __dst, cv::COLORMAP_JET ); return __dst; } private: cv::Mat colormap_color; }; ================================================ FILE: src/utils/Plot2Mat.cpp ================================================ #include "Plot2Mat.h" Plot2Mat::Plot2Mat() { im_width = 640; im_height = 480; bg_color = cv::Scalar(80,80,80); create( bg_color ); } Plot2Mat::Plot2Mat(int _width, int _height, cv::Scalar _bg_color) { im_width = _width; im_height = _height; bg_color = _bg_color; create( bg_color ); } void Plot2Mat::resetCanvas() { create( bg_color ); } void Plot2Mat::setYminmax( float _ymin, float _ymax ) { assert( _ymin<_ymax ); ymin=_ymin; ymax=_ymax; setYminmax_dynamically = false; } void Plot2Mat::setYminmaxDynamic() { setYminmax_dynamically = true; } const cv::Mat& Plot2Mat::getCanvasConstPtr() { return (const cv::Mat&) im; } void Plot2Mat::create( const cv::Scalar bg_color ) { this->im = cv::Mat( int(im_height*1.2), int(im_width*1.2), CV_8UC3, bg_color ); } bool Plot2Mat::plot( const VectorXd& y, const cv::Scalar line_color, bool mark_plottedpts, bool label_plottedpts ) { // cout << "y.rows = " << y.rows() << endl; L = y.rows(); assert( y.rows() > 0 ); auto x = VectorXd( L ); for( int i=0; i pt[i] for( int i=1 ; i0 && "you called mark before plotting. the intended use is to call plot and then optionally call mark" ); float xd = float(x_) / float(L) * im_width; auto c = cv::Point2f( xd, im_height ); cv::circle( im, c, 7, color, -1 ); if( enable_text_label ) cv::putText(im, to_string(x_), c, cv::FONT_HERSHEY_COMPLEX_SMALL, 0.8, color, 1, CV_AA); } ================================================ FILE: src/utils/Plot2Mat.h ================================================ #pragma once /** A OpenCV based plotting. The idea is, given a vector (of floats) to plot them and make a cv::Mat out of it. Author : Manohar Kuse Created : 9th Apr, 2019 **/ /**** import cv2 import numpy as np import matplotlib import code class Plot2Mat: def __init__(self): self.width = 640 self.height = 480 self.im = None self.create() def xprint( self, msg ): print '[Plot2Mat]', msg def create( self ): self.im = np.zeros( ( int(self.height*1.2), int(self.width*1.2), 3) ) + 80 def mark( self, xm_array, color=(0,0,255) ): """ Give 1 iteration number (x-axis) to mark. This can be called after plot """ # self.xprint( 'mark called'+str(xm_array.shape) ) if self.im is None: return self.im # self.xprint( str(xm_array.shape) ) for xm in xm_array: # self.xprint( str(xm) ) xd = xm / self.L * self.width c = ( int(xd), int(self.height) ) self.im = cv2.circle( self.im, c, 7, color, -1 ) cv2.putText(self.im, 'loop_items = %d' %(len(xm_array)), ( 10,40 ), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255,255,255), 3) return self.im def plot( self, y, line_color=(0,255,0) ): """ y is a 1-d array. Plot with indices. """ im = self.im x = np.array( range( len(y) ) ) # [0 to N] ---> [ 0 to self.width ] L = float( len(x) ) self.L = L # y [ min(y) to max(y) ] ---> [ 0 to self.height ] # code.interact( local=locals() ) xd = x / ( L ) * (self.width) ymin = 0.0#y.min() ymax = 1.0#y.max() yd = (y - ymin) / (ymax - ymin) * (self.height) # Plot each point as circle for i in range( int(L) ): # print 'draw circle at ', xd[i], yd[i] c = ( int(xd[i]), int(-yd[i] + self.height) ) # circle center # cv2.circle(im, c, 2, (0,0,255) ) c_xlabel = ( int(xd[i]), int(0.0 + self.height) ) if i % int(L/5) == 0 : cv2.circle(im, c_xlabel, 4, (255,255,255), -1 ) cv2.putText(im,str(i), (c_xlabel[0],c_xlabel[1]+50), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (255,255,255), 3) cv2.line( im, c_xlabel, (c_xlabel[0],0), (255,255,255), 2 ) for i in range( 1, int(L) ): p1 = ( int(xd[i]), int(-yd[i] + self.height) ) # circle center p2 = ( int(xd[i-1]), int(-yd[i-1] + self.height) ) # circle center cv2.line( im, p1, p2, line_color, 3 ) # cv2.line( ) for _yd in np.linspace( 0, 1, 11 ): #line (in image cord space): (0, _y) --- (self.width, _y) _y = (_yd) / (ymax - ymin) * self.height + ymin _y = int(-_y+self.height) # print '(%d,%d) <---> (%d,%d)' %(0,_y, self.width, _y) cv2.line( im, (0,_y), (self.width, _y), (255,255,255),3) cv2.putText(im,str(_yd),(self.width+40, _y+10), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255,255,255), 3) self.im = im return im ****/ #include #include #include #include #include #include //opencv #include #include #include #include #include #include #include using namespace Eigen; #include using namespace std; class Plot2Mat { public: Plot2Mat(); Plot2Mat(int _width, int _height, cv::Scalar _bg_color); // Canvas controllers void resetCanvas(); void setYminmax( float _ymin, float _ymax ); void setYminmaxDynamic(); bool mark( const int x_, const cv::Scalar color=cv::Scalar(0,0,255), bool enable_text_label=true ); // y is a 1-d array. Plot with indices. // mark_plottedpts : This will draw circles for each pt plotted. // label_plottedpts : This will write in text (using cv::putText) the co-ordinate bool plot( const VectorXd& y, const cv::Scalar line_color=cv::Scalar(0,255,0), bool mark_plottedpts=false, bool label_plottedpts=false ); const cv::Mat& getCanvasConstPtr(); private: void create(const cv::Scalar bg_color=cv::Scalar(80,80,80)); int im_width; int im_height; cv::Mat im; cv::Scalar bg_color = cv::Scalar(80,80,80); int L=-1;//length of y-plot double ymin, ymax; bool setYminmax_dynamically=true; }; ================================================ FILE: src/utils/PointFeatureMatching.cpp ================================================ #include "PointFeatureMatching.h" // #define ___StaticPointFeatureMatching__gms_point_feature_matches( msg ) msg; #define ___StaticPointFeatureMatching__gms_point_feature_matches( msg ) ; void StaticPointFeatureMatching::gms_point_feature_matches( const cv::Mat& imleft_undistorted, const cv::Mat& imright_undistorted, MatrixXd& u, MatrixXd& ud, int n_orb_feat ) { ElapsedTime timer; // // Point feature and descriptors extract std::vector kp1, kp2; cv::Mat d1, d2; //< descriptors // cv::Ptr orb = cv::ORB::create(5000); cv::Ptr orb = cv::ORB::create(n_orb_feat); orb->setFastThreshold(0); ___StaticPointFeatureMatching__gms_point_feature_matches(timer.tic();) orb->detectAndCompute(imleft_undistorted, cv::Mat(), kp1, d1); orb->detectAndCompute(imright_undistorted, cv::Mat(), kp2, d2); ___StaticPointFeatureMatching__gms_point_feature_matches(cout << timer.toc_milli() << " (ms) 2X detectAndCompute(ms) : "<< endl;) // std::cout << "d1 " << MiscUtils::cvmat_info( d1 ) << std::endl; // std::cout << "d2 " << MiscUtils::cvmat_info( d2 ) << std::endl; //plot // cv::Mat dst_left, dst_right; // MatrixXd e_kp1, e_kp2; // MiscUtils::keypoint_2_eigen( kp1, e_kp1 ); // MiscUtils::keypoint_2_eigen( kp2, e_kp2 ); // MiscUtils::plot_point_sets( imleft_undistorted, e_kp1, dst_left, cv::Scalar(0,0,255), false ); // MiscUtils::plot_point_sets( imright_undistorted, e_kp2, dst_right, cv::Scalar(0,0,255), false ); // cv::imshow( "dst_left", dst_left ); // cv::imshow( "dst_right", dst_right ); // // Point feature matching cv::BFMatcher matcher(cv::NORM_HAMMING); // TODO try FLANN matcher here. vector matches_all, matches_gms; ___StaticPointFeatureMatching__gms_point_feature_matches(timer.tic();) matcher.match(d1, d2, matches_all); ___StaticPointFeatureMatching__gms_point_feature_matches( std::cout << timer.toc_milli() << " : (ms) BFMatcher took (ms)\t"; std::cout << "BFMatcher : npts = " << matches_all.size() << std::endl; ) // gms_matcher ___StaticPointFeatureMatching__gms_point_feature_matches(timer.tic();) std::vector vbInliers; gms_matcher gms(kp1, imleft_undistorted.size(), kp2, imright_undistorted.size(), matches_all); int num_inliers = gms.GetInlierMask(vbInliers, false, false); ___StaticPointFeatureMatching__gms_point_feature_matches( cout << timer.toc_milli() << " : (ms) GMSMatcher took.\t" ; cout << "Got total gms matches " << num_inliers << " matches." << endl; ) // collect matches for (size_t i = 0; i < vbInliers.size(); ++i) { if (vbInliers[i] == true) { matches_gms.push_back(matches_all[i]); } } // MatrixXd M1, M2; if( matches_gms.size() > 0 ) MiscUtils::dmatch_2_eigen( kp1, kp2, matches_gms, u, ud, true ); } //----------------------------------------------- //-------- NEW ---------------------------------- //----------------------------------------------- // Given the point feature matches and the 3d image (from disparity map) will return // the valid world points and corresponding points. // [Input] // uv: 2xN matrix of point-feature in image-a. In image co-ordinates (not normalized image cords) // _3dImage_uv : 3d image from disparity map of image-a. sizeof( _3dImage_uv) === WxHx3 // uv_d: 2xN matrix of point-feature in image-b. Note that uv<-->uv_d are correspondences so should of equal sizes // [Output] // feature_position_uv : a subset of uv but normalized_image_cordinates // feature_position_uv_d : a subset of uv_d. results in normalized_image_cordinates // world_point : 3d points of uv. // [Note] // feature_position_uv \subset uv. Selects points which have valid depths. // size of output is same for all 3 // world points are of uv and in co-ordinate system of camera center of uv (or image-a). bool StaticPointFeatureMatching::make_3d_2d_collection__using__pfmatches_and_disparity( std::shared_ptr stereogeom, const MatrixXd& uv, const cv::Mat& _3dImage_uv, const MatrixXd& uv_d, std::vector& feature_position_uv, std::vector& feature_position_uv_d, std::vector& world_point ) { assert( (uv.cols() == uv_d.cols() ) && "[StaticPointFeatureMatching::make_3d_2d_collection__using__pfmatches_and_disparity] pf-matches need to be of same length. You provided of different lengths\n" ); assert( _3dImage_uv.type() == CV_32FC3 ); if( uv.cols() != uv_d.cols() ) { cout << TermColor::RED() << "[StaticPointFeatureMatching::make_3d_2d_collection__using__pfmatches_and_disparity] pf-matches need to be of same length. You provided of different lengths\n" << TermColor::RESET(); return false; } if( _3dImage_uv.type() != CV_32FC3 && _3dImage_uv.rows <= 0 && _3dImage_uv.cols <= 0 ) { cout << TermColor::RED() << "[StaticPointFeatureMatching::make_3d_2d_collection__using__pfmatches_and_disparity] _3dImage is expected to be of size CV_32FC3\n" << TermColor::RESET(); return false; } int c = 0; MatrixXd ud_normalized = stereogeom->get_K().inverse() * uv_d; MatrixXd u_normalized = stereogeom->get_K().inverse() * uv; feature_position_uv.clear(); feature_position_uv_d.clear(); world_point.clear(); for( int k=0 ; k( (int)uv(1,k), (int)uv(0,k) ); if( _3dpt[2] < 0.1 || _3dpt[2] > 25. ) continue; c++; #if 0 cout << TermColor::RED() << "---" << k << "---" << TermColor::RESET() << endl; cout << "ud=" << ud.col(k).transpose() ; cout << " <--> "; cout << "u=" << u.col(k).transpose() ; cout << " 3dpt of u="; cout << TermColor::GREEN() << _3dpt[0] << " " << _3dpt[1] << " " << _3dpt[2] << " " << TermColor::RESET(); cout << endl; #endif feature_position_uv.push_back( Vector2d( u_normalized(0,k), u_normalized(1,k) ) ); feature_position_uv_d.push_back( Vector2d( ud_normalized(0,k), ud_normalized(1,k) ) ); world_point.push_back( Vector3d( _3dpt[0], _3dpt[1], _3dpt[2] ) ); } // cout << "[make_3d_2d_collection__using__pfmatches_and_disparity]of the total " << uv.cols() << " point feature correspondences " << c << " had valid depths\n"; return true; if( c < 30 ) { cout << TermColor::RED() << "too few valid 3d points between frames" << TermColor::RESET() << endl; return false; } } // given pf-matches uv<-->ud_d and their _3dImages. returns the 3d point correspondences at points where it is valid // uv_X: the 3d points are in frame of ref of camera-uv // uvd_Y: these 3d points are in frame of ref of camera-uvd bool StaticPointFeatureMatching::make_3d_3d_collection__using__pfmatches_and_disparity( const MatrixXd& uv, const cv::Mat& _3dImage_uv, const MatrixXd& uv_d, const cv::Mat& _3dImage_uv_d, vector& uv_X, vector& uvd_Y ) { assert( uv.cols() > 0 && uv.cols() == uv_d.cols() ); uv_X.clear(); uvd_Y.clear(); if( uv.cols() != uv_d.cols() ) { cout << TermColor::RED() << "[StaticPointFeatureMatching::make_3d_3d_collection__using__pfmatches_and_disparity] pf-matches need to be of same length. You provided of different lengths\n" << TermColor::RESET(); return false; } if( _3dImage_uv.type() != CV_32FC3 && _3dImage_uv.rows <= 0 && _3dImage_uv.cols <= 0 ) { cout << TermColor::RED() << "[StaticPointFeatureMatching::make_3d_3d_collection__using__pfmatches_and_disparity] _3dImage is expected to be of size CV_32FC3\n" << TermColor::RESET(); return false; } // similar to above but should return world_point__uv and world_point__uv_d int c=0; for( int k=0 ; k( (int)uv(1,k), (int)uv(0,k) ); cv::Vec3f uvd_3dpt = _3dImage_uv_d.at( (int)uv_d(1,k), (int)uv_d(0,k) ); if( uv_3dpt[2] < 0.1 || uv_3dpt[2] > 25. || uvd_3dpt[2] < 0.1 || uvd_3dpt[2] > 25. ) continue; uv_X.push_back( Vector3d( uv_3dpt[0], uv_3dpt[1], uv_3dpt[2] ) ); uvd_Y.push_back( Vector3d( uvd_3dpt[0], uvd_3dpt[1], uvd_3dpt[2] ) ); c++; } // cout << "[make_3d_3d_collection__using__pfmatches_and_disparity] of the total " << uv.cols() << " point feature correspondences " << c << " had valid depths\n"; return true; } ================================================ FILE: src/utils/PointFeatureMatching.h ================================================ #pragma once #include #include #include #include #include #include #include //for std::shared_ptr //opencv #include #include #include #include #include #include #include using namespace Eigen; #include using namespace std; #include "GMSMatcher/gms_matcher.h" #include "ElapsedTime.h" #include "MiscUtils.h" #include "TermColor.h" #include "CameraGeometry.h" class StaticPointFeatureMatching { public: static void gms_point_feature_matches( const cv::Mat& imleft_undistorted, const cv::Mat& imright_undistorted, MatrixXd& u, MatrixXd& ud, int n_orb_feat=5000 ); //< n_orb_feat has to be a few thousands atleast for spatial consistency checks. // Given the point feature matches and the 3d image (from disparity map) will return // the valid world points and corresponding points. // [Input] // uv: 2xN matrix of point-feature in image-a. In image co-ordinates (not normalized image cords) // _3dImage_uv : 3d image from disparity map of image-a. sizeof( _3dImage_uv) === WxHx3 // uv_d: 2xN matrix of point-feature in image-b. Note that uv<-->uv_d are correspondences so should of equal sizes // [Output] // feature_position_uv : a subset of uv but normalized_image_cordinates // feature_position_uv_d : a subset of uv_d. results in normalized_image_cordinates // world_point : 3d points of uv. // [Note] // feature_position_uv \subset uv. Selects points which have valid depths. // size of output is same for all 3 // world points are of uv and in co-ordinate system of camera center of uv (or image-a). static bool make_3d_2d_collection__using__pfmatches_and_disparity( std::shared_ptr stereogeom, const MatrixXd& uv, const cv::Mat& _3dImage_uv, const MatrixXd& uv_d, std::vector& feature_position_uv, std::vector& feature_position_uv_d, std::vector& world_point ); // given pf-matches uv<-->ud_d and their _3dImages. returns the 3d point correspondences at points where it is valid // uv_X: the 3d points are in frame of ref of camera-uv // uvd_Y: these 3d points are in frame of ref of camera-uvd static bool make_3d_3d_collection__using__pfmatches_and_disparity( const MatrixXd& uv, const cv::Mat& _3dImage_uv, const MatrixXd& uv_d, const cv::Mat& _3dImage_uv_d, vector& uv_X, vector& uvd_Y ); }; ================================================ FILE: src/utils/PoseManipUtils.cpp ================================================ #include "PoseManipUtils.h" void PoseManipUtils::raw_to_eigenmat( const double * quat, const double * t, Matrix4d& dstT ) { Quaterniond q = Quaterniond( quat[0], quat[1], quat[2], quat[3] ); dstT = Matrix4d::Zero(); dstT.topLeftCorner<3,3>() = q.toRotationMatrix(); dstT(0,3) = t[0]; dstT(1,3) = t[1]; dstT(2,3) = t[2]; dstT(3,3) = 1.0; } #ifdef __PoseManipUtils__with_ROS void PoseManipUtils::geometry_msgs_Pose_to_eigenmat( const geometry_msgs::Pose& pose, Matrix4d& dstT ) { Quaterniond q = Quaterniond( pose.orientation.w, pose.orientation.x, pose.orientation.y, pose.orientation.z ); dstT = Matrix4d::Zero(); dstT.topLeftCorner<3,3>() = q.toRotationMatrix(); dstT(0,3) = pose.position.x; dstT(1,3) = pose.position.y; dstT(2,3) = pose.position.z; dstT(3,3) = 1.0; } void PoseManipUtils::eigenmat_to_geometry_msgs_Pose( const Matrix4d& T, geometry_msgs::Pose& pose ) { assert( T(3,3) == 1 ); Quaterniond q( T.topLeftCorner<3,3>() ); pose.position.x = T(0,3); pose.position.y = T(1,3); pose.position.z = T(2,3); pose.orientation.x = q.x(); pose.orientation.y = q.y(); pose.orientation.z = q.z(); pose.orientation.w = q.w(); } #endif void PoseManipUtils::eigenmat_to_raw( const Matrix4d& T, double * quat, double * t) { assert( T(3,3) == 1 ); Quaterniond q( T.topLeftCorner<3,3>() ); quat[0] = q.w(); quat[1] = q.x(); quat[2] = q.y(); quat[3] = q.z(); t[0] = T(0,3); t[1] = T(1,3); t[2] = T(2,3); } void PoseManipUtils::raw_xyzw_to_eigenmat( const double * quat, const double * t, Matrix4d& dstT ) { Quaterniond q = Quaterniond( quat[3], quat[0], quat[1], quat[2] ); dstT = Matrix4d::Zero(); dstT.topLeftCorner<3,3>() = q.toRotationMatrix(); dstT(0,3) = t[0]; dstT(1,3) = t[1]; dstT(2,3) = t[2]; dstT(3,3) = 1.0; } void PoseManipUtils::raw_xyzw_to_eigenmat( const Vector4d& quat, const Vector3d& t, Matrix4d& dstT ) { Quaterniond q = Quaterniond( quat(3), quat(0), quat(1), quat(2) ); dstT = Matrix4d::Zero(); dstT.topLeftCorner<3,3>() = q.toRotationMatrix(); dstT(0,3) = t(0); dstT(1,3) = t(1); dstT(2,3) = t(2); dstT(3,3) = 1.0; } void PoseManipUtils::eigenmat_to_raw_xyzw( const Matrix4d& T, double * quat, double * t) { assert( T(3,3) == 1 ); Quaterniond q( T.topLeftCorner<3,3>() ); quat[3] = q.w(); quat[0] = q.x(); quat[1] = q.y(); quat[2] = q.z(); t[0] = T(0,3); t[1] = T(1,3); t[2] = T(2,3); } void PoseManipUtils::rawyprt_to_eigenmat( const double * ypr, const double * t, Matrix4d& dstT ) { dstT = Matrix4d::Identity(); Vector3d eigen_ypr; eigen_ypr << ypr[0], ypr[1], ypr[2]; dstT.topLeftCorner<3,3>() = ypr2R( eigen_ypr ); dstT(0,3) = t[0]; dstT(1,3) = t[1]; dstT(2,3) = t[2]; } // input ypr must be in degrees. void PoseManipUtils::rawyprt_to_eigenmat( const Vector3d& eigen_ypr_degrees, const Vector3d& t, Matrix4d& dstT ) { dstT = Matrix4d::Identity(); dstT.topLeftCorner<3,3>() = ypr2R( eigen_ypr_degrees ); dstT(0,3) = t(0); dstT(1,3) = t(1); dstT(2,3) = t(2); } void PoseManipUtils::eigenmat_to_rawyprt( const Matrix4d& T, double * ypr, double * t) { assert( T(3,3) == 1 ); Vector3d T_cap_ypr = R2ypr( T.topLeftCorner<3,3>() ); ypr[0] = T_cap_ypr(0); ypr[1] = T_cap_ypr(1); ypr[2] = T_cap_ypr(2); t[0] = T(0,3); t[1] = T(1,3); t[2] = T(2,3); } void PoseManipUtils::eigenmat_to_rawyprt( const Matrix4d& T, Vector3d& ypr, Vector3d& t) { assert( T(3,3) == 1 ); Vector3d T_cap_ypr = R2ypr( T.topLeftCorner<3,3>() ); ypr(0) = T_cap_ypr(0); ypr(1) = T_cap_ypr(1); ypr(2) = T_cap_ypr(2); t(0) = T(0,3); t(1) = T(1,3); t(2) = T(2,3); } Vector3d PoseManipUtils::R2ypr( const Matrix3d& R) { Eigen::Vector3d n = R.col(0); Eigen::Vector3d o = R.col(1); Eigen::Vector3d a = R.col(2); Eigen::Vector3d ypr(3); double y = atan2(n(1), n(0)); double p = atan2(-n(2), n(0) * cos(y) + n(1) * sin(y)); double r = atan2(a(0) * sin(y) - a(1) * cos(y), -o(0) * sin(y) + o(1) * cos(y)); ypr(0) = y; ypr(1) = p; ypr(2) = r; return ypr / M_PI * 180.0; } Matrix3d PoseManipUtils::ypr2R( const Vector3d& ypr) { double y = ypr(0) / 180.0 * M_PI; double p = ypr(1) / 180.0 * M_PI; double r = ypr(2) / 180.0 * M_PI; // Eigen::Matrix Rz; Matrix3d Rz; Rz << cos(y), -sin(y), 0, sin(y), cos(y), 0, 0, 0, 1; // Eigen::Matrix Ry; Matrix3d Ry; Ry << cos(p), 0., sin(p), 0., 1., 0., -sin(p), 0., cos(p); // Eigen::Matrix Rx; Matrix3d Rx; Rx << 1., 0., 0., 0., cos(r), -sin(r), 0., sin(r), cos(r); return Rz * Ry * Rx; } void PoseManipUtils::prettyprintPoseMatrix( const Matrix4d& M ) { cout << "YPR : " << R2ypr( M.topLeftCorner<3,3>() ).transpose() << "; "; cout << "Tx,Ty,Tz : " << M(0,3) << ", " << M(1,3) << ", " << M(2,3) << endl; } void PoseManipUtils::prettyprintPoseMatrix( const Matrix4d& M, string& return_string ) { Vector3d ypr; ypr = R2ypr( M.topLeftCorner<3,3>() ); char __tmp[200]; snprintf( __tmp, 200, ":YPR(deg)=(%4.2f,%4.2f,%4.2f) :TxTyTz=(%4.2f,%4.2f,%4.2f)", ypr(0), ypr(1), ypr(2), M(0,3), M(1,3), M(2,3) ); return_string = string( __tmp ); } string PoseManipUtils::prettyprintMatrix4d( const Matrix4d& M ) { Vector3d ypr; ypr = R2ypr( M.topLeftCorner<3,3>() ); char __tmp[200]; snprintf( __tmp, 200, ":YPR(deg)=(%4.3f,%4.3f,%4.3f) :TxTyTz=(%4.3f,%4.3f,%4.3f)", ypr(0), ypr(1), ypr(2), M(0,3), M(1,3), M(2,3) ); string return_string = string( __tmp ); return return_string; } string PoseManipUtils::prettyprintMatrix4d_YPR( const Matrix4d& M ) { Vector3d ypr; ypr = R2ypr( M.topLeftCorner<3,3>() ); char __tmp[200]; snprintf( __tmp, 200, " YPR(deg)=(%4.2f,%4.2f,%4.2f) ", ypr(0), ypr(1), ypr(2) ); string return_string = string( __tmp ); return return_string; } string PoseManipUtils::prettyprintMatrix4d_t( const Matrix4d& M ) { Vector3d ypr; ypr = R2ypr( M.topLeftCorner<3,3>() ); char __tmp[200]; snprintf( __tmp, 200, " TxTyTz=(%4.2f,%4.2f,%4.2f) ", M(0,3), M(1,3), M(2,3) ); string return_string = string( __tmp ); return return_string; } void PoseManipUtils::vec_to_cross_matrix( const Vector3d& t, Matrix3d& A_x ) { // Tx = Matrix3d::Zero(); A_x << 0, -t(2) , t(1) , t(2), 0, -t(0), -t(1), t(0), 0; } void PoseManipUtils::vec_to_cross_matrix( double a, double b, double c, Matrix3d& A_x ) { Vector3d t(a,b,c); vec_to_cross_matrix( t, A_x ); } ================================================ FILE: src/utils/PoseManipUtils.h ================================================ #pragma once // // This provides utilities for pose manipulation. // // Author : Manohar Kuse // #include #include #include #include // uncomment this to compile without ros dependency for this file #define __PoseManipUtils__with_ROS 1 #ifdef __PoseManipUtils__with_ROS #include #endif using namespace std; using namespace Eigen; class PoseManipUtils { public: static void raw_to_eigenmat( const double * quat, const double * t, Matrix4d& dstT ); static void eigenmat_to_raw( const Matrix4d& T, double * quat, double * t); static void rawyprt_to_eigenmat( const double * ypr, const double * t, Matrix4d& dstT ); static void rawyprt_to_eigenmat( const Vector3d& eigen_ypr_degrees, const Vector3d& t, Matrix4d& dstT ); static void eigenmat_to_rawyprt( const Matrix4d& T, double * ypr, double * t); static void eigenmat_to_rawyprt( const Matrix4d& T, Vector3d& ypr, Vector3d& t); static Vector3d R2ypr( const Matrix3d& R); static Matrix3d ypr2R( const Vector3d& ypr); // input ypr must be in degrees. static void prettyprintPoseMatrix( const Matrix4d& M ); static void prettyprintPoseMatrix( const Matrix4d& M, string& return_string ); static string prettyprintMatrix4d( const Matrix4d& M ); static string prettyprintMatrix4d_YPR( const Matrix4d& M ); static string prettyprintMatrix4d_t( const Matrix4d& M ); static void raw_xyzw_to_eigenmat( const double * quat, const double * t, Matrix4d& dstT ); static void raw_xyzw_to_eigenmat( const Vector4d& quat, const Vector3d& t, Matrix4d& dstT ); static void eigenmat_to_raw_xyzw( const Matrix4d& T, double * quat, double * t); #ifdef __PoseManipUtils__with_ROS static void geometry_msgs_Pose_to_eigenmat( const geometry_msgs::Pose& pose, Matrix4d& dstT ); static void eigenmat_to_geometry_msgs_Pose( const Matrix4d& T, geometry_msgs::Pose& pose ); #endif // Given a point convert it to cross-product matrix. A_x = [ [ 0, -c, -b ], [c,0,-a], [-b,-a,0] ] static void vec_to_cross_matrix( const Vector3d& a, Matrix3d& A_x ); static void vec_to_cross_matrix( double a, double b, double c, Matrix3d& A_x ); }; ================================================ FILE: src/utils/RawFileIO.cpp ================================================ #include "RawFileIO.h" void RawFileIO::write_image( string fname, const cv::Mat& img) { __RawFileIO__write_image_debug_dm( std::cout << "write_image: "<< fname << endl ); cv::imwrite( (fname).c_str(), img ); } void RawFileIO::write_string( string fname, const string& my_string) { __RawFileIO__write_image_debug_dm( std::cout << "write_string: "<< fname << endl ); std::ofstream outfile( fname ); outfile << my_string << endl; outfile.close(); } // templated static function canot only exist in header files. // so this was moved to the header file // template // void RawFileIO::write_EigenMatrix(const string& filename, const MatrixBase& a) // { // // string base = string("/home/mpkuse/Desktop/bundle_adj/dump/datamgr_mateigen_"); // std::ofstream file(filename); // if( file.is_open() ) // { // // file << a.format(CSVFormat) << endl; // file << a << endl; // write_image_debug_dm(std::cout << "\033[1;32m" <<"Written to file: "<< filename << "\033[0m\n" ); // } // else // { // cout << "\033[1;31m" << "FAIL TO OPEN FILE for writing: "<< filename << "\033[0m\n"; // // } // } void RawFileIO::write_Matrix2d( const string& filename, const double * D, int nRows, int nCols ) { // string base = string("/home/mpkuse/Desktop/bundle_adj/dump/datamgr_mat2d_"); std::ofstream file(filename); if( file.is_open() ) { int c = 0 ; for( int i=0; i> buff[cols*rows+temp_cols++]; if (temp_cols == 0) continue; if (cols == 0) cols = temp_cols; rows++; } infile.close(); rows--; // Populate matrix with numbers. result = MatrixXd::Zero(rows,cols); for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) result(i,j) = buff[ cols*i+j ]; // return result; __RawFileIO__write_image_debug_dm( cout << "\tshape=" << result.rows() << "x" << result.cols() << endl; ) return true; }; bool RawFileIO::read_eigen_matrix( string filename, Matrix4d& result ) { int cols = 0, rows = 0; const int MAXBUFSIZE = ((int) 2000); double buff[MAXBUFSIZE]; // Read numbers from file into buffer. __RawFileIO__write_image_debug_dm( std::cout << "\033[1;32m" << "read_eigen_matrix: "<< filename << "\033[0m" ); ifstream infile; infile.open(filename); if( !infile ) { cout << "\n\033[1;31m" << "failed to open file " << filename << "\033[0m" << endl; return false; } while (! infile.eof()) { string line; getline(infile, line); int temp_cols = 0; stringstream stream(line); while(! stream.eof()) stream >> buff[cols*rows+temp_cols++]; if (temp_cols == 0) continue; if (cols == 0) cols = temp_cols; rows++; } infile.close(); rows--; assert( rows==4 && cols==4 && "\n[RawFileIO::read_eigen_matrix( string filename, Matrix4d& result )] The input file is not 4x4" ); // Populate matrix with numbers. result = Matrix4d::Zero(); for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) result(i,j) = buff[ cols*i+j ]; // return result; __RawFileIO__write_image_debug_dm( cout << "\tshape=" << result.rows() << "x" << result.cols() << endl; ) return true; }; bool RawFileIO::read_eigen_matrix( string filename, Matrix3d& result ) { int cols = 0, rows = 0; const int MAXBUFSIZE = ((int) 2000); double buff[MAXBUFSIZE]; // Read numbers from file into buffer. __RawFileIO__write_image_debug_dm( std::cout << "\033[1;32m" << "read_eigen_matrix: "<< filename << "\033[0m" ); ifstream infile; infile.open(filename); if( !infile ) { cout << "\n\033[1;31m" << "failed to open file " << filename << "\033[0m" << endl; return false; } while (! infile.eof()) { string line; getline(infile, line); int temp_cols = 0; stringstream stream(line); while(! stream.eof()) stream >> buff[cols*rows+temp_cols++]; if (temp_cols == 0) continue; if (cols == 0) cols = temp_cols; rows++; } infile.close(); rows--; assert( rows==3 && cols==3 && "\n[RawFileIO::read_eigen_matrix( string filename, Matrix3d& result )] The input file is not 3x3" ); // Populate matrix with numbers. result = Matrix3d::Zero(); for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) result(i,j) = buff[ cols*i+j ]; // return result; __RawFileIO__write_image_debug_dm( cout << "\tshape=" << result.rows() << "x" << result.cols() << endl; ) return true; } bool RawFileIO::read_eigen_matrix( string filename, VectorXi& result ) { int cols = 0, rows = 0; const int MAXBUFSIZE = ((int) 2000); double buff[MAXBUFSIZE]; // Read numbers from file into buffer. __RawFileIO__write_image_debug_dm( std::cout << "\033[1;32m" << "read_eigen_matrix: "<< filename << "\033[0m" ); ifstream infile; infile.open(filename); if( !infile ) { cout << "\n\033[1;31m" << "failed to open file " << filename << "\033[0m" << endl; return false; } while (! infile.eof()) { string line; getline(infile, line); int temp_cols = 0; stringstream stream(line); while(! stream.eof()) stream >> buff[cols*rows+temp_cols++]; if (temp_cols == 0) continue; if (cols == 0) cols = temp_cols; rows++; } infile.close(); rows--; assert( cols==1 && "\n[RawFileIO::read_eigen_matrix( string filename, VectorXi& result )] The input file is not row-vector" ); // Populate matrix with numbers. result = VectorXi::Zero(rows); for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) result(i,j) = (int) buff[ cols*i+j ]; // return result; __RawFileIO__write_image_debug_dm( cout << "\tshape=" << result.rows() << "x" << result.cols() << endl; ) return true; }; bool RawFileIO::read_eigen_matrix( const std::vector& ary, Matrix4d& result ) { assert( ary.size() == result.rows() * result.cols() && "[RawFileIO::read_eigen_matrix] size of vector need to be equal to the size of Eigen::Matrix"); for( int i=0 ; i 0 && ncols > 0 ) output = MatrixXd::Zero(nrows, ncols ); else { cout << "[RawFileIO::read_eigen_matrix_fromjson] ERROR, nrows and cols should be positive\n"; return false; } // split( data, '\n') vector all_rows = MiscUtils::split( data, '\n' ); if( nrows != all_rows.size() ) { cout << "[RawFileIO::read_eigen_matrix_fromjson] ERROR, requested " << nrows << " but actually are " << all_rows.size() << endl; return false; } for( int r=0 ; r all_cols_for_this_row = MiscUtils::split( all_rows[r], ',' ); if( ncols != all_cols_for_this_row.size() ) { cout << "[RawFileIO::read_eigen_matrix_fromjson] ERROR, requested " << ncols << " but actually are " << all_cols_for_this_row.size() << " for row=" << r << endl; return false; } for( int c=0 ; c all_rows = MiscUtils::split( data, '\n' ); if( nrows != all_rows.size() ) { cout << "[RawFileIO::read_eigen_matrix4d_fromjson] ERROR, requested " << nrows << " but actually are " << all_rows.size() << endl; return false; } for( int r=0 ; r all_cols_for_this_row = MiscUtils::split( all_rows[r], ',' ); if( ncols != all_cols_for_this_row.size() ) { cout << "[RawFileIO::read_eigen_matrix4d_fromjson] ERROR, requested " << ncols << " but actually are " << all_cols_for_this_row.size() << " for row=" << r << endl; return false; } for( int c=0 ; c 0 ) { output = VectorXd::Zero( nrows ); } else { cout << "[RawFileIO::read_eigen_vector_fromjson] ERROR, nrows should be positive\n"; return false; } if( ncols != 1 ) { cout << "[RawFileIO::read_eigen_vector_fromjson] ERROR, you request to convert to VectorXd however the input json cols != 1\n"; return false; } vector all_rows = MiscUtils::split( data, '\n' ); if( nrows != all_rows.size() ) { cout << "[RawFileIO::read_eigen_vector_fromjson] ERROR, requested " << nrows << " but actually are " << all_rows.size() << endl; return false; } for( int r=0 ; r all_cols_for_this_row = MiscUtils::split( all_rows[r], ',' ); if( all_cols_for_this_row.size() != 1 ) { cout << "[RawFileIO::read_eigen_vector_fromjson] ERROR, requested " << ncols << " but actually are " << all_cols_for_this_row.size() << " for row=" << r << endl; return false; } output( r ) = std::stod( all_cols_for_this_row[0] ); } return true; } #endif //WITH_NLOHMANN_JSON ================================================ FILE: src/utils/RawFileIO.h ================================================ #pragma once #include #include #include #include #include #include #include //opencv #include #include #include #include #include #include #include using namespace Eigen; #include using namespace std; #define WITH_NLOHMANN_JSON #ifdef WITH_NLOHMANN_JSON #include "nlohmann/json.hpp" using json = nlohmann::json; #include "MiscUtils.h" #endif #include // for is_directory #define __RawFileIO__write_image_debug_dm( msg ) msg; class RawFileIO { public: static void write_image( string fname, const cv::Mat& img); static void write_string( string fname, const string& my_string); // templated static function canot only exist in header files. template static void write_EigenMatrix(const string& filename, const MatrixBase& a) { // string base = string("/home/mpkuse/Desktop/bundle_adj/dump/datamgr_mateigen_"); std::ofstream file(filename); if( file.is_open() ) { // file << a.format(CSVFormat) << endl; file << a << endl; __RawFileIO__write_image_debug_dm(std::cout << "\033[1;32m" <<"write_EigenMatrix: "<< filename << " size=" << a.rows() << "x" << a.cols() << "\033[0m\n";) } else { cout << "\033[1;31m" << "FAIL TO OPEN FILE for writing: "<< filename << "\033[0m\n"; } } static void write_Matrix2d( const string& filename, const double * D, int nRows, int nCols ); static void write_Matrix1d( const string& filename, const double * D, int n ); static bool read_eigen_matrix( string filename, MatrixXd& result ); static bool read_eigen_matrix( string filename, Matrix4d& result ); static bool read_eigen_matrix( string filename, Matrix3d& result ); static bool read_eigen_matrix( string filename, VectorXi& result ); ///< read the flat vector ary as a rowmajor matrix. /// [ 1, 2, 3, 4,5,6...,16 ] ==> [ [1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16] ] /// TODO: Have a flag to read interpret the 1d array as a colmajor. static bool read_eigen_matrix( const std::vector& ary, Matrix4d& result ); #ifdef WITH_NLOHMANN_JSON // The input json need to be something like: // A new row is denoted with \n, and a comma separates elements. //{ // "cols": 4, // "rows": 4, // "data": "0.2857131543876468, -0.2530077727001951, 0.9243132912401226, -0.02953755668229465\n-0.9566719337894203, -0.01884313243445668, 0.2905576491157474, 0.2114882406102183,\n-0.05609638588601445, -0.9672807262182707, -0.2474291659792429, 0.04534835279466286\n0, 0, 0, 1" // } static bool read_eigen_matrix_fromjson( const json str, MatrixXd& output ); static bool read_eigen_matrix4d_fromjson( const json str, Matrix4d& output ); static bool read_eigen_vector_fromjson( const json str, VectorXd& output ); #endif static bool if_file_exist( char * fname ); static bool if_file_exist( string fname ); static bool is_path_a_directory(const char* path); static bool is_path_a_directory(const string path); static int exec_cmd( const string& cmd ); //< Executes a unix command. }; ================================================ FILE: src/utils/RosMarkerUtils.cpp ================================================ #include "RosMarkerUtils.h" // cam_size = 1: means basic size. 1.5 will make it 50% bigger. void RosMarkerUtils::init_camera_marker( visualization_msgs::Marker& marker, float cam_size ) { marker.header.frame_id = "world"; marker.header.stamp = ros::Time::now(); marker.action = visualization_msgs::Marker::ADD; marker.color.a = .7; // Don't forget to set the alpha! marker.type = visualization_msgs::Marker::LINE_LIST; // marker.id = i; // marker.ns = "camerapose_visual"; marker.scale.x = 0.003; //width of line-segments float __vcam_width = 0.07*cam_size; float __vcam_height = 0.04*cam_size; float __z = 0.1*cam_size; marker.points.clear(); geometry_msgs::Point pt; pt.x = 0; pt.y=0; pt.z=0; marker.points.push_back( pt ); pt.x = __vcam_width; pt.y=__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = 0; pt.y=0; pt.z=0; marker.points.push_back( pt ); pt.x = -__vcam_width; pt.y=__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = 0; pt.y=0; pt.z=0; marker.points.push_back( pt ); pt.x = __vcam_width; pt.y=-__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = 0; pt.y=0; pt.z=0; marker.points.push_back( pt ); pt.x = -__vcam_width; pt.y=-__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = __vcam_width; pt.y=__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = -__vcam_width; pt.y=__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = -__vcam_width; pt.y=__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = -__vcam_width; pt.y=-__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = -__vcam_width; pt.y=-__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = __vcam_width; pt.y=-__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = __vcam_width; pt.y=-__vcam_height; pt.z=__z; marker.points.push_back( pt ); pt.x = __vcam_width; pt.y=__vcam_height; pt.z=__z; marker.points.push_back( pt ); // TOSET marker.pose.position.x = 0.; marker.pose.position.y = 0.; marker.pose.position.z = 0.; marker.pose.orientation.x = 0.; marker.pose.orientation.y = 0.; marker.pose.orientation.z = 0.; marker.pose.orientation.w = 1.; // marker.id = i; // marker.ns = "camerapose_visual"; marker.color.r = 0.2;marker.color.b = 0.;marker.color.g = 0.; } void RosMarkerUtils::setpose_to_marker( const Matrix4d& w_T_c, visualization_msgs::Marker& marker ) { Quaterniond quat( w_T_c.topLeftCorner<3,3>() ); marker.pose.position.x = w_T_c(0,3); marker.pose.position.y = w_T_c(1,3); marker.pose.position.z = w_T_c(2,3); marker.pose.orientation.x = quat.x(); marker.pose.orientation.y = quat.y(); marker.pose.orientation.z = quat.z(); marker.pose.orientation.w = quat.w(); } void RosMarkerUtils::setposition_to_marker( const Vector3d& w_t_c, visualization_msgs::Marker& marker ) { marker.pose.position.x = w_t_c(0); marker.pose.position.y = w_t_c(1); marker.pose.position.z = w_t_c(2); } void RosMarkerUtils::setposition_to_marker( const Vector4d& w_t_c, visualization_msgs::Marker& marker ) { marker.pose.position.x = w_t_c(0) / w_t_c(3); ; marker.pose.position.y = w_t_c(1) / w_t_c(3); ; marker.pose.position.z = w_t_c(2) / w_t_c(3); ; } void RosMarkerUtils::setposition_to_marker( float x, float y, float z, visualization_msgs::Marker& marker ) { marker.pose.position.x = x; marker.pose.position.y = y; marker.pose.position.z = z; } void RosMarkerUtils::setcolor_to_marker( float r, float g, float b, visualization_msgs::Marker& marker ) { assert( r>=0. && r<=1.0 && g>=0. && g<=1.0 && b>=0 && b<=1.0 ); marker.color.a = 1.0; marker.color.r = r; marker.color.g = g; marker.color.b = b; } void RosMarkerUtils::setcolor_to_marker( float r, float g, float b, float a, visualization_msgs::Marker& marker ) { assert( r>=0. && r<=1.0 && g>=0. && g<=1.0 && b>=0 && b<=1.0 && a>0. && a<=1.0); marker.color.a = a; marker.color.r = r; marker.color.g = g; marker.color.b = b; } void RosMarkerUtils::init_text_marker( visualization_msgs::Marker &marker ) { marker.header.frame_id = "world"; marker.header.stamp = ros::Time::now(); marker.action = visualization_msgs::Marker::ADD; marker.color.a = .8; // Don't forget to set the alpha! marker.type = visualization_msgs::Marker::TEXT_VIEW_FACING; marker.scale.x = 1.; //not in use marker.scale.y = 1.; //not in use marker.scale.z = 1.; //// Done . no need to edit firther marker.pose.position.x = 0.; marker.pose.position.y = 0.; marker.pose.position.z = 0.; marker.pose.orientation.x = 0.; marker.pose.orientation.y = 0.; marker.pose.orientation.z = 0.; marker.pose.orientation.w = 1.; // marker.id = i; // marker.ns = "camerapose_visual"; marker.color.r = 0.2;marker.color.b = 0.;marker.color.g = 0.; } void RosMarkerUtils::init_line_strip_marker( visualization_msgs::Marker &marker ) { marker.header.frame_id = "world"; marker.header.stamp = ros::Time::now(); marker.action = visualization_msgs::Marker::ADD; marker.color.a = .8; // Don't forget to set the alpha! marker.type = visualization_msgs::Marker::LINE_STRIP; marker.scale.x = 0.02; marker.points.clear(); //// Done . no need to edit firther marker.pose.position.x = 0.; marker.pose.position.y = 0.; marker.pose.position.z = 0.; marker.pose.orientation.x = 0.; marker.pose.orientation.y = 0.; marker.pose.orientation.z = 0.; marker.pose.orientation.w = 1.; // marker.id = i; // marker.ns = "camerapose_visual"; marker.color.r = 0.2;marker.color.b = 0.;marker.color.g = 0.; } void RosMarkerUtils::init_line_marker( visualization_msgs::Marker &marker ) { marker.header.frame_id = "world"; marker.header.stamp = ros::Time::now(); marker.action = visualization_msgs::Marker::ADD; marker.color.a = .8; // Don't forget to set the alpha! marker.type = visualization_msgs::Marker::LINE_LIST; marker.scale.x = 0.02; marker.points.clear(); //// Done . no need to edit firther marker.pose.position.x = 0.; marker.pose.position.y = 0.; marker.pose.position.z = 0.; marker.pose.orientation.x = 0.; marker.pose.orientation.y = 0.; marker.pose.orientation.z = 0.; marker.pose.orientation.w = 1.; // marker.id = i; // marker.ns = "camerapose_visual"; marker.color.r = 0.2;marker.color.b = 0.;marker.color.g = 0.; } void RosMarkerUtils::init_line_marker( visualization_msgs::Marker &marker, const Vector3d& p1, const Vector3d& p2 ) { marker.header.frame_id = "world"; marker.header.stamp = ros::Time::now(); marker.action = visualization_msgs::Marker::ADD; marker.color.a = .8; // Don't forget to set the alpha! marker.type = visualization_msgs::Marker::LINE_LIST; marker.scale.x = 0.02; marker.points.clear(); geometry_msgs::Point pt; pt.x = p1(0); pt.y = p1(1); pt.z = p1(2); marker.points.push_back( pt ); pt.x = p2(0); pt.y = p2(1); pt.z = p2(2); marker.points.push_back( pt ); //// Done . no need to edit firther marker.pose.position.x = 0.; marker.pose.position.y = 0.; marker.pose.position.z = 0.; marker.pose.orientation.x = 0.; marker.pose.orientation.y = 0.; marker.pose.orientation.z = 0.; marker.pose.orientation.w = 1.; // marker.id = i; // marker.ns = "camerapose_visual"; marker.color.r = 0.2;marker.color.b = 0.;marker.color.g = 0.; } void RosMarkerUtils::init_points_marker( visualization_msgs::Marker &marker ) { marker.header.frame_id = "world"; marker.header.stamp = ros::Time::now(); marker.action = visualization_msgs::Marker::ADD; marker.color.a = .8; // Don't forget to set the alpha! marker.type = visualization_msgs::Marker::POINTS; marker.scale.x = 0.04; marker.scale.y = 0.04; marker.points.clear(); //// Done . no need to edit firther marker.pose.position.x = 0.; marker.pose.position.y = 0.; marker.pose.position.z = 0.; marker.pose.orientation.x = 0.; marker.pose.orientation.y = 0.; marker.pose.orientation.z = 0.; marker.pose.orientation.w = 1.; // marker.id = i; // marker.ns = "camerapose_visual"; marker.color.r = 0.2;marker.color.b = 0.;marker.color.g = 0.; } void RosMarkerUtils::add_point_to_marker( float x, float y, float z, visualization_msgs::Marker& marker, bool clear_prev_points ) { if( clear_prev_points ) marker.points.clear(); geometry_msgs::Point pt; pt.x = x; pt.y = y; pt.z = z; marker.points.push_back( pt ); } void RosMarkerUtils::add_point_to_marker( const Vector3d& X, visualization_msgs::Marker& marker, bool clear_prev_points ) { if( clear_prev_points ) marker.points.clear(); geometry_msgs::Point pt; pt.x = X(0); pt.y = X(1); pt.z = X(2); marker.points.push_back( pt ); } void RosMarkerUtils::add_point_to_marker( const Vector4d& X, visualization_msgs::Marker& marker, bool clear_prev_points ) { if( clear_prev_points ) marker.points.clear(); geometry_msgs::Point pt; assert( abs(X(3)) > 1E-5 ); pt.x = X(0)/X(3); pt.y = X(1)/X(3); pt.z = X(2)/X(3); marker.points.push_back( pt ); } void RosMarkerUtils::add_points_to_marker( const MatrixXd& X, visualization_msgs::Marker& marker, bool clear_prev_points ) //X : 3xN or 4xN. { assert( (X.rows() == 3 || X.rows() == 4) && "[RosMarkerUtils::add_points_to_marker] X need to of size 3xN or 4xN\n" ); geometry_msgs::Point pt; if( clear_prev_points ) marker.points.clear(); for( int i=0 ; i // Notes: http://wiki.ros.org/rviz/DisplayTypes/Marker #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace Eigen; class RosMarkerUtils { public: ////////////////// INIT ///////////////////////// /// Initialize a camera with a few lines. You need to set the `ns` and `id` before publishing. static void init_camera_marker( visualization_msgs::Marker& marker, float cam_size ); static void init_text_marker( visualization_msgs::Marker &marker ); static void init_line_strip_marker( visualization_msgs::Marker &marker ); static void init_line_marker( visualization_msgs::Marker &marker ); static void init_line_marker( visualization_msgs::Marker &marker, const Vector3d& p1, const Vector3d& p2 ); static void init_points_marker( visualization_msgs::Marker &marker ); //////////////// SET ////////////////////////// static void setpose_to_marker( const Matrix4d& w_T_c, visualization_msgs::Marker& marker ); static void setposition_to_marker( const Vector3d& w_t_c, visualization_msgs::Marker& marker ); static void setposition_to_marker( const Vector4d& w_t_c, visualization_msgs::Marker& marker ); static void setposition_to_marker( float x, float y, float z, visualization_msgs::Marker& marker ); static void setcolor_to_marker( float r, float g, float b, visualization_msgs::Marker& marker ); static void setcolor_to_marker( float r, float g, float b, float a, visualization_msgs::Marker& marker ); //////////////// Add Points //////////////////// static void add_point_to_marker( float x, float y, float z, visualization_msgs::Marker& marker, bool clear_prev_points=true ); static void add_point_to_marker( const Vector3d& X, visualization_msgs::Marker& marker, bool clear_prev_points=true ); static void add_point_to_marker( const Vector4d& X, visualization_msgs::Marker& marker, bool clear_prev_points=true ); static void add_points_to_marker( const MatrixXd& X, visualization_msgs::Marker& marker, bool clear_prev_points=true ); //X : 3xN or 4xN. }; ================================================ FILE: src/utils/SafeQueue.h ================================================ #pragma once #include #include #include // A threadsafe-queue. - https://stackoverflow.com/questions/15278343/c11-thread-safe-queue template class SafeQueue { public: SafeQueue(void) : q() , m() , c() {} ~SafeQueue(void) {} // Add an element to the queue. void enqueue(T t) { std::lock_guard lock(m); q.push(t); c.notify_one(); } // Get the "front"-element. // If the queue is empty, wait till a element is avaiable. T dequeue(void) { std::unique_lock lock(m); while(q.empty()) { // release lock as long as the wait and reaquire it afterwards. c.wait(lock); } T val = q.front(); q.pop(); return val; } ////// added my mpkuse // return size int size() { std::lock_guard lock(m); int n = q.size(); c.notify_one(); return n; } // Add an element to the queue. void push(T t) { std::lock_guard lock(m); q.push(t); c.notify_one(); } // Get the "front"-element. // If the queue is empty, wait till a element is avaiable. T pop(void) { std::unique_lock lock(m); while(q.empty()) { // release lock as long as the wait and reaquire it afterwards. c.wait(lock); } T val = q.front(); q.pop(); return val; } // most recently added element T back() { assert( size() > 0 && "SafeQueue: You tried to SafeQueue::front(), but the queue was empty. This operation is only allowed when there are elements in the queue. "); std::lock_guard lock(m); T val = q.back(); c.notify_one(); return val; } // 1st element in queue T front() { assert( size() > 0 && "SafeQueue: You tried to SafeQueue::front(), but the queue was empty. This operation is only allowed when there are elements in the queue. "); std::lock_guard lock(m); T val = q.front(); c.notify_one(); return val; } private: std::queue q; mutable std::mutex m; std::condition_variable c; }; ================================================ FILE: src/utils/TermColor.h ================================================ #pragma once /** // TODO Work in progress // https://stackoverflow.com/questions/2616906/how-do-i-output-coloured-text-to-a-linux-terminal black 30 40 red 31 41 green 32 42 yellow 33 43 blue 34 44 magenta 35 45 cyan 36 46 white 37 47 reset 0 (everything back to normal) bold/bright 1 (often a brighter shade of the same colour) underline 4 inverse 7 (swap foreground and background colours) bold/bright off 21 underline off 24 inverse off 27 eg: cout << "\033[1;31mbold red text\033[0m\n"; */ #include #include #include class TermColor { public: static std::string RED() { return compose( TermColor::BG_RED ); } static std::string GREEN() { return compose( TermColor::BG_GREEN ); } static std::string YELLOW() { return compose( TermColor::BG_YELLOW ); } static std::string BLUE() { return compose( TermColor::BG_BLUE ); } static std::string MAGENTA() { return compose( TermColor::BG_MAGENTA ); } static std::string CYAN() { return compose( TermColor::BG_CYAN ); } static std::string WHITE() { return compose( TermColor::BG_WHITE ); } static std::string iRED() { return compose( TermColor::BG_RED, true ); } static std::string iGREEN() { return compose( TermColor::BG_GREEN, true ); } static std::string iYELLOW() { return compose( TermColor::BG_YELLOW, true ); } static std::string iBLUE() { return compose( TermColor::BG_BLUE, true ); } static std::string iMAGENTA() { return compose( TermColor::BG_MAGENTA, true ); } static std::string iCYAN() { return compose( TermColor::BG_CYAN, true ); } static std::string iWHITE() { return compose( TermColor::BG_WHITE, true ); } static std::string RESET() { std::stringstream buffer; buffer << "\033[" << CTRL_RESET << "m"; return buffer.str(); } // modifier : bold, underline etc. // color : FG_RED, ..., BG_RED etc static std::string compose( const int modifier, const int color, bool invert=false ) { std::stringstream buffer; if( !invert ) buffer << "\033[" << modifier << ";" << color << "m"; else buffer << "\033[" << CTRL_INVERSE << ";" << modifier << ";" << color << "m"; return buffer.str(); } static std::string compose( const int color, bool invert=false ) { std::stringstream buffer; if( !invert ) buffer << "\033[" << color << "m"; else buffer << "\033[" << CTRL_INVERSE << ";" << color << "m"; return buffer.str(); } static const int BG_BLACK = 30; static const int BG_RED = 31; static const int BG_GREEN = 32; static const int BG_YELLOW = 33; static const int BG_BLUE = 34; static const int BG_MAGENTA = 35; static const int BG_CYAN = 36; static const int BG_WHITE = 37; static const int FG_BLACK = 40; static const int FG_RED = 41; static const int FG_GREEN = 42; static const int FG_YELLOW = 43; static const int FG_BLUE = 44; static const int FG_MAGENTA = 45; static const int FG_CYAN = 46; static const int FG_WHITE = 47; static const int CTRL_RESET = 0; static const int CTRL_BOLD = 1; static const int CTRL_UNDERLINE = 4; static const int CTRL_INVERSE = 7; //(swap foreground and background colours) static const int CTRL_BOLD_OFF = 21; static const int CTRL_UNDERLINE_OFF = 24; static const int CTRL_INVERSE_OFF = 27; //(swap foreground and background colours) }; ================================================ FILE: src/utils/camera_geometry_usage.cpp ================================================ // Sample usage for class CameraGeometry.h/MonoGeometry and CameraGeometry.h/StereoGeometry // These classes abstractout the Stereo Geometry, Undistort etc. They can be used // with any of the Camodocal cameras ie. Mei, Kannala-brandt, pinhole (ofcourse). // The whole idea of the abstract Camera clases and Geometry class is to make the // codebase truely object oriented and the core geometry abstracted. Hopefully // all this will help to develop more higher level applications faster. // YONGYEN'S METHOD TO CORRECT THE STEREO-EXTRINSIC WHOLLY CONTAINED IN THIS FILE // implements Yonggen Ling's method // Y. Ling and S. Shen, "High-precision online markerless stereo extrinsic calibration," 2016 IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS), Daejeon, 2016, pp. 1771-1778. // minimize_{R,t} \sum_i || (f'_i)^T E f_i || , where R,t == right_T_left. // a) E is the Essential matrix E := [t]_x R // b) f and f' are point feature matches (f from left f from right) in normalized image co-ordinates #include #include #include "camodocal/camera_models/Camera.h" #include "camodocal/camera_models/CameraFactory.h" #include "camodocal/camera_models/CataCamera.h" #include "camodocal/camera_models/EquidistantCamera.h" //opencv #include #include #include #include #include "../utils/MiscUtils.h" #include "../utils/ElapsedTime.h" #include "../utils/PoseManipUtils.h" #include "../utils/TermColor.h" #include "../utils/CameraGeometry.h" #include "../utils/RawFileIO.h" #include "gms_matcher.h" #include #include using namespace ceres; void point_feature_matches( const cv::Mat& imleft_undistorted, const cv::Mat& imright_undistorted, MatrixXd& u, MatrixXd& ud ) { ElapsedTime timer; // // Point feature and descriptors extract std::vector kp1, kp2; cv::Mat d1, d2; //< descriptors cv::Ptr orb = cv::ORB::create(10000); orb->setFastThreshold(0); timer.tic(); orb->detectAndCompute(imleft_undistorted, cv::Mat(), kp1, d1); orb->detectAndCompute(imright_undistorted, cv::Mat(), kp2, d2); cout << "2X detectAndCompute(ms) : " << timer.toc_milli() << endl; // std::cout << "d1 " << MiscUtils::cvmat_info( d1 ) << std::endl; // std::cout << "d2 " << MiscUtils::cvmat_info( d2 ) << std::endl; //plot // cv::Mat dst_left, dst_right; // MatrixXd e_kp1, e_kp2; // MiscUtils::keypoint_2_eigen( kp1, e_kp1 ); // MiscUtils::keypoint_2_eigen( kp2, e_kp2 ); // MiscUtils::plot_point_sets( imleft_undistorted, e_kp1, dst_left, cv::Scalar(0,0,255), false ); // MiscUtils::plot_point_sets( imright_undistorted, e_kp2, dst_right, cv::Scalar(0,0,255), false ); // cv::imshow( "dst_left", dst_left ); // cv::imshow( "dst_right", dst_right ); // // Point feature matching cv::BFMatcher matcher(cv::NORM_HAMMING); // TODO try FLANN matcher here. vector matches_all, matches_gms; timer.tic(); matcher.match(d1, d2, matches_all); std::cout << "BFMatcher : npts = " << matches_all.size() << std::endl; std::cout << "BFMatcher took (ms) : "<< timer.toc_milli() << std::endl; // gms_matcher timer.tic(); std::vector vbInliers; gms_matcher gms(kp1, imleft_undistorted.size(), kp2, imright_undistorted.size(), matches_all); int num_inliers = gms.GetInlierMask(vbInliers, false, false); cout << "Got total gms matches " << num_inliers << " matches." << endl; cout << "GMSMatcher took (ms) " << timer.toc_milli() << std::endl; // collect matches for (size_t i = 0; i < vbInliers.size(); ++i) { if (vbInliers[i] == true) { matches_gms.push_back(matches_all[i]); } } // MatrixXd M1, M2; MiscUtils::dmatch_2_eigen( kp1, kp2, matches_gms, u, ud, true ); } // Orthonormalization is needed because the optimization variable translation is unit normalized // and to preserve this property we need to define the '+' operation for it. Note that it can // only more in tangent space of the sphere. So graph schmit-orthonormalization gives out a basis // vector at this point. Ofcourse this is not a unique solution. See Fig. 3 in yonggen's iros2016 paper. class UnitVectorParameterization : public ceres::LocalParameterization { public: UnitVectorParameterization() {} virtual ~UnitVectorParameterization() {} virtual bool Plus(const double* x, const double* delta, double* x_plus_delta) const { // Compute T (tangent space) // tangent space is a function of x double m[5], n[5]; bool statis = gram_schmidt_orthonormalization( x, m, n ); if( !statis ) return false; // x_new := x + T*[ delta[0]; delta[1] ] x_plus_delta[0] = x[0] + m[0]*delta[0] + n[0]*delta[1]; x_plus_delta[1] = x[1] + m[1]*delta[0] + n[1]*delta[1]; x_plus_delta[2] = x[2] + m[2]*delta[0] + n[2]*delta[1]; return true; } virtual bool ComputeJacobian(const double* x, double* jacobian) const { // Compute T (tangent space) // tangent space is a function of x double m[5], n[5]; bool statis = gram_schmidt_orthonormalization( x, m, n ); if( !statis ) return false; // \del(x') / \del(a) = [ T_{0,0}; T_{1,0} ] // \del(x') / \del(b) = [ T_{0,1}; T_{1,1} ] // jacobian = [ concate above 2 ] jacobian[0] = m[0]; jacobian[1] = n[0]; jacobian[2] = m[1]; jacobian[3] = n[1]; jacobian[4] = m[2]; jacobian[5] = n[2]; return true; } virtual int GlobalSize() const { return 3; } // TODO : Generalize. virtual int LocalSize() const { return 2; } // This function return 2 vectors m, n each of dimension equal to x (ie. 3) such that x,m,n are orthogonal to each other. const bool gram_schmidt_orthonormalization( const double * _x , double * _m, double * _n, int len=3 ) const { VectorXd xp = VectorXd::Zero( len ); for( int i=0 ; iXi = Xi; // this->Xid = Xid; } template bool operator()( const T* const q, const T* const t , T* residual ) const { // Optimization variables Quaternion eigen_q( q[0], q[1], q[2], q[3] ); Eigen::Matrix eigen_tx; eigen_tx << T(0.0), -t[2] , t[1] , t[2], T(0.0), -t[0], -t[1], t[0], T(0.0); Eigen::Matrix essential_matrix; essential_matrix = eigen_tx * eigen_q.toRotationMatrix() ; // Known Constant Eigen::Matrix eigen_Xi; Eigen::Matrix eigen_Xid; eigen_Xi << T(Xi(0)), T(Xi(1)), T(Xi(2)); eigen_Xid << T(Xid(0)), T(Xid(1)), T(Xid(2)); // Error term Eigen::Matrix e; // e = eigen_Xi - ( eigen_q.toRotationMatrix() * eigen_Xid + eigen_t ); e = eigen_Xid.transpose() * essential_matrix * eigen_Xi; // e = eigen_Xi.transpose() * (essential_matrix * eigen_Xid); residual[0] = e(0); // residual[1] = e(1); // residual[2] = e(2); return true; } static ceres::CostFunction* Create(const Vector3d& _Xi, const Vector3d& _Xid) { return ( new ceres::AutoDiffCostFunction ( new YonggenResidue(_Xi,_Xid) ) ); } private: Vector3d Xi, Xid; }; void nudge_extrinsics( const cv::Mat& imleft_undistorted, const cv::Mat& imright_undistorted, const Matrix3d& K_new, const Matrix4d& right_T_left, Matrix4d& optimized_right_T_left ) { // implements Yonggen Ling's method // Y. Ling and S. Shen, "High-precision online markerless stereo extrinsic calibration," 2016 IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS), Daejeon, 2016, pp. 1771-1778. // minimize_{R,t} \sum_i || (f'_i)^T E f_i || , where R,t == right_T_left. // a) E is the Essential matrix E := [t]_x R // b) f and f' are point feature matches (f from left f from right) in normalized image co-ordinates // // Step-1 : Match point features from `imleft_undistorted, imright_undistorted` MatrixXd u, ud; //< 3xN, ie. image co-ordinates represented in homogeneous cord system. point_feature_matches( imleft_undistorted, imright_undistorted, u, ud ); cv::Mat dst; MiscUtils::plot_point_pair( imleft_undistorted, u, -1, imright_undistorted, ud, -1, dst, 0 ); cv::imshow( "gms_matches", dst ); // // Step-2 : In normalized co-ordinates : inv(K_new) * f' and inv(K_inv) * f MatrixXd f = K_new.inverse() * u; MatrixXd fd = K_new.inverse() * ud; // // Step-3 : Setup CERES problem // 3.1 : Initial Guess double T_cap_q[10], T_cap_t[10]; PoseManipUtils::eigenmat_to_raw( right_T_left, T_cap_q, T_cap_t ); double n_norm = sqrt( T_cap_t[0]*T_cap_t[0] + T_cap_t[1]*T_cap_t[1] + T_cap_t[2]*T_cap_t[2] ); T_cap_t[0] /= n_norm; T_cap_t[1] /= n_norm; T_cap_t[2] /= n_norm; cout << "CERES Inital Guess : " << PoseManipUtils::prettyprintMatrix4d( right_T_left ) << endl; cout << "CERES Initial Guess n_norm: " << n_norm << endl; cout << "CERES Initial Guess T_cap_t: " << T_cap_t[0] << " " << T_cap_t[1] << " " << T_cap_t[2] << endl; // 3.2 : Error Terms ceres::Problem problem; cout << "CERES #residues : " << f.cols() << endl; for( int i=0 ; igenerateCameraFromYamlFile(BASE+"/cameraIntrinsic.0.yaml"); camodocal::CameraPtr right_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.1.yaml"); // camodocal::CameraPtr left_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(string(BASE+"../leveled_cam_pinhole/")+"/camera_left.yaml"); // camodocal::CameraPtr right_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(string(BASE+"../leveled_cam_pinhole/")+"/camera_right.yaml"); cout << left_camera->parametersToString() << endl; cout << right_camera->parametersToString() << endl; // Extrinsics - quat is wxyz . translation in meters. // mynt eye Vector4d q_wxyz = Vector4d( -1.8252509868889259e-04,-1.6291774489779708e-03,-1.2462127842978489e-03,9.9999787970731446e-01 ); Vector3d tr_xyz = Vector3d( -1.2075905420832895e+02/1000.,5.4110610639412482e-01/1000.,2.4484815673909591e-01/1000. ); // bluefox stereo leveled // Vector4d q_wxyz = Vector4d( -1.7809713490350254e-03, 4.2143149583451564e-04,4.1936662160154632e-02, 9.9911859501433165e-01 ); // Vector3d tr_xyz = Vector3d( -1.4031938291177164e+02/1000.,-6.6214729932530441e+00/1000.,1.4808567571722902e+00/1000. ); Matrix4d right_T_left; PoseManipUtils::raw_xyzw_to_eigenmat( q_wxyz, tr_xyz, right_T_left ); #if 0 cout << "right_T_left(before applying delta): " << PoseManipUtils::prettyprintMatrix4d( right_T_left ) << endl; Matrix4d delta; PoseManipUtils::rawyprt_to_eigenmat( Vector3d(4.0,4.0,4.0), Vector3d(0,0,0), delta ); right_T_left = delta * right_T_left; cout << "right_T_left(after applying delta): " << PoseManipUtils::prettyprintMatrix4d( right_T_left ) << endl; #endif cout << "right_T_left: " << PoseManipUtils::prettyprintMatrix4d( right_T_left ) << endl; cout << "right_T_left=" << right_T_left.format( numpyFmt ); // StereoGeometry stereogeom( left_camera,right_camera, right_T_left ); std::shared_ptr stereogeom; stereogeom = std::make_shared( left_camera,right_camera, right_T_left ); stereogeom->set_K( 375.0, 375.0, 376.0, 240.0 ); // Raw Image - Image from camera int frame_id = 1005; // for( int frame_id=100 ; frame_id < 1000 ; frame_id++ ) { cv::Mat imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_id)+".jpg", 0 ); cv::Mat imright_raw = cv::imread( BASE+"/"+std::to_string(frame_id)+"_1.jpg", 0 ); // cv::Mat imleft_raw = cv::imread( BASE+"/cam0_"+std::to_string(frame_id)+".png",0 ); // cv::Mat imright_raw = cv::imread( BASE+"/cam1_"+ std::to_string(frame_id)+".png",0 ); // Undistort Only cv::Mat imleft_undistorted, imright_undistorted; timer.tic(); stereogeom->do_image_undistortion( imleft_raw, imright_raw, imleft_undistorted, imright_undistorted ); cout << timer.toc_milli() << " :(ms) stereogeom->do_image_undistortion()\n"; cv::imshow( "imleft_raw", imleft_raw ); cv::imshow( "imright_raw", imright_raw ); cv::imshow( "imleft_undistorted", imleft_undistorted ); cv::imshow( "imright_undistorted", imright_undistorted ); // #define YONGGEN_MARKERLESS_ONLINE_STEREOCALIB #ifdef YONGGEN_MARKERLESS_ONLINE_STEREOCALIB Matrix4d optimized_right_T_left; nudge_extrinsics(imleft_undistorted, imright_undistorted, stereogeom->get_K(), right_T_left, optimized_right_T_left); cout << "optimized_right_T_left: " << PoseManipUtils::prettyprintMatrix4d( optimized_right_T_left) << endl; stereogeom->set_stereoextrinsic( optimized_right_T_left ); #endif // Draw Epipolar Lines cv::Mat dst_imleft_undistorted = imleft_undistorted.clone(); cv::Mat dst_imright_undistorted = imright_undistorted.clone(); stereogeom->draw_epipolarlines(dst_imleft_undistorted, dst_imright_undistorted); cv::imshow( "imleft_undistorted", dst_imleft_undistorted ); cv::imshow( "imright_undistorted", dst_imright_undistorted ); // Stereo Rectify. cv::Mat imleft_srectified, imright_srectified; timer.tic(); stereogeom->do_stereo_rectification_of_undistorted_images( imleft_undistorted, imright_undistorted, imleft_srectified, imright_srectified ); cout << timer.toc_milli() << " :(ms) stereogeom->do_stereo_rectification_of_undistorted_images\n"; cv::imshow( "imleft_srectified", imleft_srectified ); cv::imshow( "imright_srectified", imright_srectified ); // Draw Epipolar lines on stereo rectified images cv::Mat dst_imleft_srectified = imleft_srectified.clone(); cv::Mat dst_imright_srectified = imright_srectified.clone(); stereogeom->draw_srectified_epipolarlines( dst_imleft_srectified, dst_imright_srectified ); cv::imshow( "dst_imleft_srectified", dst_imleft_srectified ); cv::imshow( "dst_imright_srectified", dst_imright_srectified ); // Stereo Block Matching to compute disparity cv::Mat disp_raw, disp8; timer.tic(); stereogeom->do_stereoblockmatching_of_srectified_images( imleft_srectified, imright_srectified, disp_raw ); // stereogeom->do_stereoblockmatching_of_raw_images( imleft_raw, imright_raw, disp_raw ); cout << timer.toc_milli() << " : (ms)do_stereoblockmatching_of_srectified_images done in\n"; cout << "disp_raw info " << MiscUtils::cvmat_info( disp_raw ) << endl; stereogeom->print_blockmatcher_algo_info(); cv::normalize(disp_raw, disp8, 0, 255, CV_MINMAX, CV_8U); //< disp8 used just for visualization cv::imshow( "disp8", disp8 ); #if 0 { cv::FileStorage file("disp_raw.opencv.txt", cv::FileStorage::WRITE); file << "disp_raw" << disp_raw; file.release(); // save disp_raw MatrixXd e_disp_raw; cv::cv2eigen(disp_raw, e_disp_raw ); RawFileIO::write_EigenMatrix( "./disp_raw.txt", e_disp_raw ); } #endif // Disparity to pointcloud const cv::Mat Q = stereogeom->get_Q(); cv::Mat _3dImage; MatrixXd _3dpts; timer.tic(); stereogeom->disparity_to_3DPoints( disp_raw, _3dImage, _3dpts, true, true ); cout << timer.toc_milli() << " : (ms) disparity_to_3DPoints computed in \n"; #if 0 // split channels cout << "_3dImage : " << MiscUtils::cvmat_info( _3dImage ) << endl; Mat _3dImage_XYZ[3]; //destination array cv::split(_3dImage,_3dImage_XYZ);//split source cout << "_3dImageX : " << MiscUtils::cvmat_info( _3dImage_XYZ[0] ) << endl; cout << "_3dImageY : " << MiscUtils::cvmat_info( _3dImage_XYZ[1] ) << endl; cout << "_3dImageZ : " << MiscUtils::cvmat_info( _3dImage_XYZ[2] ) << endl; MatrixXf e_3dImage[3]; cv::cv2eigen( _3dImage_XYZ[0], e_3dImage[0] ); cv::cv2eigen( _3dImage_XYZ[1], e_3dImage[1] ); cv::cv2eigen( _3dImage_XYZ[2], e_3dImage[2] ); RawFileIO::write_EigenMatrix( "./e_3dImageX.txt", e_3dImage[0] ); RawFileIO::write_EigenMatrix( "./e_3dImageY.txt", e_3dImage[1] ); RawFileIO::write_EigenMatrix( "./e_3dImageZ.txt", e_3dImage[2] ); RawFileIO::write_EigenMatrix( "./_3dpts.txt", _3dpts ); #endif // Visualize 3d point cloud from stereo // a) as reprojections vector pt_colors; GeometryUtils::depthColors( _3dpts, pt_colors, .5, 4.5 ); MatrixXd perspective_proj = MatrixXd::Zero( 3, _3dpts.cols() ); for( int k=0 ; k<_3dpts.cols() ; k++ ) { perspective_proj(0,k) = _3dpts( 0, k ) / _3dpts( 2, k) ; perspective_proj(1,k) = _3dpts( 1, k ) / _3dpts( 2, k) ; perspective_proj(2,k) = 1.0; } MatrixXd reproj_uv = stereogeom->get_K() * perspective_proj; #if 0 cout << "_3dpts(sample)\n" << _3dpts.leftCols(10) << endl; cout << "perspective_proj(sample)\n" << perspective_proj.leftCols(10) << endl; cout << "reproj_uv(sample)\n" << reproj_uv.leftCols(10) << endl; #endif cv::Mat dst_reproj_uv; MiscUtils::plot_point_sets( imleft_srectified, reproj_uv, dst_reproj_uv, pt_colors, 0.6, "colored by depth" ); cv::imshow( "dst_reproj_uv", dst_reproj_uv ); cv::waitKey(0); } } int stereo_demo_easy() { ElapsedTime timer; // const std::string BASE = "/Bulk_Data/_tmp_cerebro/mynt_multi_loops_in_lab/"; // const std::string BASE = "/Bulk_Data/ros_bags/bluefox_stereo/calib/leveled_cam_sampled/"; const std::string BASE = "/Bulk_Data/_tmp_cerebro/mynt_seng3/"; //--------- Intrinsics load camodocal::CameraPtr left_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.0.yaml"); camodocal::CameraPtr right_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.1.yaml"); // camodocal::CameraPtr left_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(string(BASE+"../leveled_cam_pinhole/")+"/camera_left.yaml"); // camodocal::CameraPtr right_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(string(BASE+"../leveled_cam_pinhole/")+"/camera_right.yaml"); cout << left_camera->parametersToString() << endl; cout << right_camera->parametersToString() << endl; //----------- Stereo Base line load (alsoed called extrinsic calibration) // mynt eye Vector4d q_wxyz = Vector4d( -1.8252509868889259e-04,-1.6291774489779708e-03,-1.2462127842978489e-03,9.9999787970731446e-01 ); Vector3d tr_xyz = Vector3d( -1.2075905420832895e+02/1000.,5.4110610639412482e-01/1000.,2.4484815673909591e-01/1000. ); // bluefox stereo leveled // Vector4d q_wxyz = Vector4d( -1.7809713490350254e-03, 4.2143149583451564e-04,4.1936662160154632e-02, 9.9911859501433165e-01 ); // Vector3d tr_xyz = Vector3d( -1.4031938291177164e+02/1000.,-6.6214729932530441e+00/1000.,1.4808567571722902e+00/1000. ); Matrix4d right_T_left; PoseManipUtils::raw_xyzw_to_eigenmat( q_wxyz, tr_xyz, right_T_left ); // cout << "right_T_left: " << PoseManipUtils::prettyprintMatrix4d( right_T_left ) << endl; //-------------------- init stereogeom std::shared_ptr stereogeom; stereogeom = std::make_shared( left_camera,right_camera, right_T_left ); stereogeom->set_K( 375.0, 375.0, 376.0, 240.0 ); int frame_id = 1005; //----------------- load images_raw for left and right for( frame_id=0; frame_id < 2500 ; frame_id++ ) { cout << "READ IMAGE " << frame_id << endl; cv::Mat imleft_raw = cv::imread( BASE+"/"+std::to_string(frame_id)+".jpg", 0 ); cv::Mat imright_raw = cv::imread( BASE+"/"+std::to_string(frame_id)+"_1.jpg", 0 ); // cv::Mat imleft_raw = cv::imread( BASE+"/cam0_"+std::to_string(frame_id)+".png",0 ); // cv::Mat imright_raw = cv::imread( BASE+"/cam1_"+ std::to_string(frame_id)+".png",0 ); if( imleft_raw.empty() || imright_raw.empty() ) continue; //------------------- stereogeom->get3dpoints_from_raw_images() // can use one of the options depending on the need. #if 0 // (A) fastest - if you are just looking for valid 3d points - look at the CameraGeometry.h header to see various options to call. timer.tic(); MatrixXd _3dpts; //4xN stereogeom->get3dpoints_from_raw_images(imleft_raw, imright_raw, _3dpts ); cout << timer.toc_milli() << " (ms)!! get3dpoints_from_raw_images\n"; cout << "_3dpts.shape= " << _3dpts.rows() << " " << _3dpts.cols() << endl; #endif #if 0 // will get the 3d points and disparity. Takes about 2-4ms more than (A) MatrixXd _3dpts; //4xN cv::Mat disparity_for_visualization; cout << "_3dpts.shape= " << _3dpts.rows() << " " << _3dpts.cols() << endl; timer.tic(); stereogeom->get3dpoints_and_disparity_from_raw_images(imleft_raw, imright_raw, _3dpts, disparity_for_visualization ); cout << timer.toc_milli() << " (ms)!! get3dpoints_and_disparity_from_raw_images\n"; cv::imshow( "disparity_for_visualization", disparity_for_visualization ); #endif #if 1 // will get 3d points, stereo-rectified image, and disparity false colormap MatrixXd _3dpts; //4xN cv::Mat imleft_srectified, imright_srectified; cv::Mat disparity_for_visualization; timer.tic(); stereogeom->get_srectifiedim_and_3dpoints_and_disparity_from_raw_images(imleft_raw, imright_raw, imleft_srectified, imright_srectified, _3dpts, disparity_for_visualization ); cout << timer.toc_milli() << " (ms)!! get_srectifiedim_and_3dpoints_and_disparity_from_raw_images\n"; cv::imshow( "imleft_srectified", imleft_srectified ); cv::imshow( "imright_srectified", imright_srectified ); cv::imshow( "disparity_for_visualization", disparity_for_visualization ); #endif //-------------------- reproject the 3d points. // note: that these 3d points after reprojections will be correct as plotted to imleft_srectified #if 0 vector pt_colors; GeometryUtils::depthColors( _3dpts, pt_colors, .5, 4.5 ); MatrixXd perspective_proj = MatrixXd::Zero( 3, _3dpts.cols() ); // perspective project _3dpts. for( int k=0 ; k<_3dpts.cols() ; k++ ) { perspective_proj(0,k) = _3dpts( 0, k ) / _3dpts( 2, k) ; perspective_proj(1,k) = _3dpts( 1, k ) / _3dpts( 2, k) ; perspective_proj(2,k) = 1.0; } MatrixXd reproj_uv = stereogeom->get_K() * perspective_proj; //< scaling with camera intrinsic for visualization cout << "_3dpts(sample)\n" << _3dpts.leftCols(10) << endl; cout << "perspective_proj(sample)\n" << perspective_proj.leftCols(10) << endl; cout << "reproj_uv(sample)\n" << reproj_uv.leftCols(10) << endl; cv::Mat dst_reproj_uv; MiscUtils::plot_point_sets( imleft_srectified, reproj_uv, dst_reproj_uv, pt_colors, 0.6, "plot of reprojected points;colored by depth" ); cv::imshow( "dst_reproj_uv", dst_reproj_uv ); #endif //-------------------- visualize 3d points with rviz cv::waitKey(0); } } // Monocular int monocular_demo() { const std::string BASE = "/Bulk_Data/_tmp_cerebro/mynt_multi_loops_in_lab/"; // Abstract Camera camodocal::CameraPtr m_camera = camodocal::CameraFactory::instance()->generateCameraFromYamlFile(BASE+"/cameraIntrinsic.0.yaml"); cout << m_camera->parametersToString() << endl; // Init Monocular Geometry MonoGeometry monogeom( m_camera ); // Raw Image - Image from camera int frame_id = 23; cv::Mat im_raw = cv::imread( BASE+"/"+std::to_string(frame_id)+".jpg" ); cv::Mat im_undistorted; monogeom.do_image_undistortion( im_raw, im_undistorted ); cv::imshow( "im_raw", im_raw ); cv::imshow( "im_undistorted", im_undistorted ); cv::waitKey(0); } int main() { // monocular_demo(); // stereo_demo(); stereo_demo_easy(); /* double _x[5]; _x[0] = 0.545435; _x[1] = .8; _x[2] = .25; double _m[5], _n[5]; UnitVectorParameterization parm = UnitVectorParameterization(); parm.gram_schmidt_orthonormalization( _x, _m, _n ); */ } ================================================ FILE: src/utils/camodocal/README.md ================================================ # Camodocal Original Implementation : https://github.com/hengli/camodocal My (Manohar Kuse, mpkuse@connect.ust.hk) Adaptation : https://github.com/mpkuse/camera_model ## CMakefile.txt If you put this folder under `src/utils/` and CMakeLists.txt is in `./` you will need to add the following to you CMake files ``` include_directories(src/utils/camodocal/include) FILE(GLOB CamodocalCameraModelSources src/utils/camodocal/src/chessboard/Chessboard.cc src/utils/camodocal/src/calib/CameraCalibration.cc src/utils/camodocal/src/camera_models/Camera.cc src/utils/camodocal/src/camera_models/CameraFactory.cc src/utils/camodocal/src/camera_models/CostFunctionFactory.cc src/utils/camodocal/src/camera_models/PinholeCamera.cc src/utils/camodocal/src/camera_models/CataCamera.cc src/utils/camodocal/src/camera_models/EquidistantCamera.cc src/utils/camodocal/src/camera_models/ScaramuzzaCamera.cc src/utils/camodocal/src/sparse_graph/Transform.cc src/utils/camodocal/src/gpl/gpl.cc src/utils/camodocal/src/gpl/EigenQuaternionParameterization.cc ) ``` Dependencies if not already added: ``` find_package(Eigen3 REQUIRED) find_package(Ceres REQUIRED) find_package(OpenCV 3 REQUIRED) find_package(Boost REQUIRED COMPONENTS filesystem program_options system) ``` ================================================ FILE: src/utils/camodocal/include/camodocal/calib/CameraCalibration.h ================================================ #ifndef CAMERACALIBRATION_H #define CAMERACALIBRATION_H #include #include "camodocal/camera_models/Camera.h" namespace camodocal { class CameraCalibration { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW CameraCalibration(); CameraCalibration(Camera::ModelType modelType, const std::string& cameraName, const cv::Size& imageSize, const cv::Size& boardSize, float squareSize); void clear(void); void addChessboardData(const std::vector& corners); bool calibrate(void); int sampleCount(void) const; std::vector >& imagePoints(void); const std::vector >& imagePoints(void) const; std::vector >& scenePoints(void); const std::vector >& scenePoints(void) const; CameraPtr& camera(void); const CameraConstPtr camera(void) const; Eigen::Matrix2d& measurementCovariance(void); const Eigen::Matrix2d& measurementCovariance(void) const; cv::Mat& cameraPoses(void); const cv::Mat& cameraPoses(void) const; void drawResults(std::vector& images) const; void writeParams(const std::string& filename) const; bool writeChessboardData(const std::string& filename) const; bool readChessboardData(const std::string& filename); void setVerbose(bool verbose); private: bool calibrateHelper(CameraPtr& camera, std::vector& rvecs, std::vector& tvecs) const; void optimize(CameraPtr& camera, std::vector& rvecs, std::vector& tvecs) const; template void readData(std::ifstream& ifs, T& data) const; template void writeData(std::ofstream& ofs, T data) const; cv::Size m_boardSize; float m_squareSize; CameraPtr m_camera; cv::Mat m_cameraPoses; std::vector > m_imagePoints; std::vector > m_scenePoints; Eigen::Matrix2d m_measurementCovariance; bool m_verbose; }; } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/camera_models/Camera.h ================================================ #ifndef CAMERA_H #define CAMERA_H #include #include #include #include namespace camodocal { class Camera { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW enum ModelType { KANNALA_BRANDT, MEI, PINHOLE, SCARAMUZZA }; class Parameters { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW Parameters(ModelType modelType); Parameters(ModelType modelType, const std::string& cameraName, int w, int h); ModelType& modelType(void); std::string& cameraName(void); int& imageWidth(void); int& imageHeight(void); ModelType modelType(void) const; const std::string& cameraName(void) const; int imageWidth(void) const; int imageHeight(void) const; int nIntrinsics(void) const; virtual bool readFromYamlFile(const std::string& filename) = 0; virtual void writeToYamlFile(const std::string& filename) const = 0; protected: ModelType m_modelType; int m_nIntrinsics; std::string m_cameraName; int m_imageWidth; int m_imageHeight; }; virtual ModelType modelType(void) const = 0; virtual const std::string& cameraName(void) const = 0; virtual int imageWidth(void) const = 0; virtual int imageHeight(void) const = 0; virtual cv::Mat& mask(void); virtual const cv::Mat& mask(void) const; virtual void estimateIntrinsics(const cv::Size& boardSize, const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints) = 0; virtual void estimateExtrinsics(const std::vector& objectPoints, const std::vector& imagePoints, cv::Mat& rvec, cv::Mat& tvec) const; // Lift points from the image plane to the sphere virtual void liftSphere(const Eigen::Vector2d& p, Eigen::Vector3d& P) const = 0; //%output P // Lift points from the image plane to the projective space virtual void liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const = 0; //%output P // Projects 3D points to the image plane (Pi function) virtual void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p) const = 0; //%output p // Projects 3D points to the image plane (Pi function) // and calculates jacobian //virtual void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p, // Eigen::Matrix& J) const = 0; //%output p //%output J virtual void undistToPlane(const Eigen::Vector2d& p_u, Eigen::Vector2d& p) const = 0; //%output p //virtual void initUndistortMap(cv::Mat& map1, cv::Mat& map2, double fScale = 1.0) const = 0; virtual cv::Mat initUndistortRectifyMap(cv::Mat& map1, cv::Mat& map2, float fx = -1.0f, float fy = -1.0f, cv::Size imageSize = cv::Size(0, 0), float cx = -1.0f, float cy = -1.0f, cv::Mat rmat = cv::Mat::eye(3, 3, CV_32F)) const = 0; virtual int parameterCount(void) const = 0; virtual void readParameters(const std::vector& parameters) = 0; virtual void writeParameters(std::vector& parameters) const = 0; virtual void writeParametersToYamlFile(const std::string& filename) const = 0; virtual std::string parametersToString(void) const = 0; /** * \brief Calculates the reprojection distance between points * * \param P1 first 3D point coordinates * \param P2 second 3D point coordinates * \return euclidean distance in the plane */ double reprojectionDist(const Eigen::Vector3d& P1, const Eigen::Vector3d& P2) const; double reprojectionError(const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints, const std::vector& rvecs, const std::vector& tvecs, cv::OutputArray perViewErrors = cv::noArray()) const; double reprojectionError(const Eigen::Vector3d& P, const Eigen::Quaterniond& camera_q, const Eigen::Vector3d& camera_t, const Eigen::Vector2d& observed_p) const; void projectPoints(const std::vector& objectPoints, const cv::Mat& rvec, const cv::Mat& tvec, std::vector& imagePoints) const; protected: cv::Mat m_mask; }; typedef boost::shared_ptr CameraPtr; typedef boost::shared_ptr CameraConstPtr; } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/camera_models/CameraFactory.h ================================================ #ifndef CAMERAFACTORY_H #define CAMERAFACTORY_H #include #include #include "camodocal/camera_models/Camera.h" namespace camodocal { class CameraFactory { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW CameraFactory(); static boost::shared_ptr instance(void); CameraPtr generateCamera(Camera::ModelType modelType, const std::string& cameraName, cv::Size imageSize) const; CameraPtr generateCameraFromYamlFile(const std::string& filename); private: static boost::shared_ptr m_instance; }; } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/camera_models/CataCamera.h ================================================ #ifndef CATACAMERA_H #define CATACAMERA_H #include #include #include "ceres/rotation.h" #include "Camera.h" namespace camodocal { /** * C. Mei, and P. Rives, Single View Point Omnidirectional Camera Calibration * from Planar Grids, ICRA 2007 */ class CataCamera: public Camera { public: class Parameters: public Camera::Parameters { public: Parameters(); Parameters(const std::string& cameraName, int w, int h, double xi, double k1, double k2, double p1, double p2, double gamma1, double gamma2, double u0, double v0); double& xi(void); double& k1(void); double& k2(void); double& p1(void); double& p2(void); double& gamma1(void); double& gamma2(void); double& u0(void); double& v0(void); double xi(void) const; double k1(void) const; double k2(void) const; double p1(void) const; double p2(void) const; double gamma1(void) const; double gamma2(void) const; double u0(void) const; double v0(void) const; bool readFromYamlFile(const std::string& filename); void writeToYamlFile(const std::string& filename) const; Parameters& operator=(const Parameters& other); friend std::ostream& operator<< (std::ostream& out, const Parameters& params); private: double m_xi; double m_k1; double m_k2; double m_p1; double m_p2; double m_gamma1; double m_gamma2; double m_u0; double m_v0; }; CataCamera(); /** * \brief Constructor from the projection model parameters */ CataCamera(const std::string& cameraName, int imageWidth, int imageHeight, double xi, double k1, double k2, double p1, double p2, double gamma1, double gamma2, double u0, double v0); /** * \brief Constructor from the projection model parameters */ CataCamera(const Parameters& params); Camera::ModelType modelType(void) const; const std::string& cameraName(void) const; int imageWidth(void) const; int imageHeight(void) const; void estimateIntrinsics(const cv::Size& boardSize, const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints); // Lift points from the image plane to the sphere void liftSphere(const Eigen::Vector2d& p, Eigen::Vector3d& P) const; //%output P // Lift points from the image plane to the projective space void liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const; //%output P // Projects 3D points to the image plane (Pi function) void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p) const; //%output p // Projects 3D points to the image plane (Pi function) // and calculates jacobian void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p, Eigen::Matrix& J) const; //%output p //%output J void undistToPlane(const Eigen::Vector2d& p_u, Eigen::Vector2d& p) const; //%output p template static void spaceToPlane(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& p); void distortion(const Eigen::Vector2d& p_u, Eigen::Vector2d& d_u) const; void distortion(const Eigen::Vector2d& p_u, Eigen::Vector2d& d_u, Eigen::Matrix2d& J) const; void initUndistortMap(cv::Mat& map1, cv::Mat& map2, double fScale = 1.0) const; cv::Mat initUndistortRectifyMap(cv::Mat& map1, cv::Mat& map2, float fx = -1.0f, float fy = -1.0f, cv::Size imageSize = cv::Size(0, 0), float cx = -1.0f, float cy = -1.0f, cv::Mat rmat = cv::Mat::eye(3, 3, CV_32F)) const; int parameterCount(void) const; const Parameters& getParameters(void) const; void setParameters(const Parameters& parameters); void readParameters(const std::vector& parameterVec); void writeParameters(std::vector& parameterVec) const; void writeParametersToYamlFile(const std::string& filename) const; std::string parametersToString(void) const; private: Parameters mParameters; double m_inv_K11, m_inv_K13, m_inv_K22, m_inv_K23; bool m_noDistortion; }; typedef boost::shared_ptr CataCameraPtr; typedef boost::shared_ptr CataCameraConstPtr; template void CataCamera::spaceToPlane(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& p) { T P_w[3]; P_w[0] = T(P(0)); P_w[1] = T(P(1)); P_w[2] = T(P(2)); // Convert quaternion from Eigen convention (x, y, z, w) // to Ceres convention (w, x, y, z) T q_ceres[4] = {q[3], q[0], q[1], q[2]}; T P_c[3]; ceres::QuaternionRotatePoint(q_ceres, P_w, P_c); P_c[0] += t[0]; P_c[1] += t[1]; P_c[2] += t[2]; // project 3D object point to the image plane T xi = params[0]; T k1 = params[1]; T k2 = params[2]; T p1 = params[3]; T p2 = params[4]; T gamma1 = params[5]; T gamma2 = params[6]; T alpha = T(0); //cameraParams.alpha(); T u0 = params[7]; T v0 = params[8]; // Transform to model plane T len = sqrt(P_c[0] * P_c[0] + P_c[1] * P_c[1] + P_c[2] * P_c[2]); P_c[0] /= len; P_c[1] /= len; P_c[2] /= len; T u = P_c[0] / (P_c[2] + xi); T v = P_c[1] / (P_c[2] + xi); T rho_sqr = u * u + v * v; T L = T(1.0) + k1 * rho_sqr + k2 * rho_sqr * rho_sqr; T du = T(2.0) * p1 * u * v + p2 * (rho_sqr + T(2.0) * u * u); T dv = p1 * (rho_sqr + T(2.0) * v * v) + T(2.0) * p2 * u * v; u = L * u + du; v = L * v + dv; p(0) = gamma1 * (u + alpha * v) + u0; p(1) = gamma2 * v + v0; } } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/camera_models/CostFunctionFactory.h ================================================ #ifndef COSTFUNCTIONFACTORY_H #define COSTFUNCTIONFACTORY_H #include #include #include "camodocal/camera_models/Camera.h" namespace ceres { class CostFunction; } namespace camodocal { enum { CAMERA_INTRINSICS = 1 << 0, CAMERA_POSE = 1 << 1, POINT_3D = 1 << 2, ODOMETRY_INTRINSICS = 1 << 3, ODOMETRY_3D_POSE = 1 << 4, ODOMETRY_6D_POSE = 1 << 5, CAMERA_ODOMETRY_TRANSFORM = 1 << 6 }; class CostFunctionFactory { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW CostFunctionFactory(); static boost::shared_ptr instance(void); ceres::CostFunction* generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p, int flags) const; ceres::CostFunction* generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p, const Eigen::Matrix2d& sqrtPrecisionMat, int flags) const; ceres::CostFunction* generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector2d& observed_p, int flags, bool optimize_cam_odo_z = true) const; ceres::CostFunction* generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector2d& observed_p, const Eigen::Matrix2d& sqrtPrecisionMat, int flags, bool optimize_cam_odo_z = true) const; ceres::CostFunction* generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector3d& odo_pos, const Eigen::Vector3d& odo_att, const Eigen::Vector2d& observed_p, int flags, bool optimize_cam_odo_z = true) const; ceres::CostFunction* generateCostFunction(const CameraConstPtr& camera, const Eigen::Quaterniond& cam_odo_q, const Eigen::Vector3d& cam_odo_t, const Eigen::Vector3d& odo_pos, const Eigen::Vector3d& odo_att, const Eigen::Vector2d& observed_p, int flags) const; ceres::CostFunction* generateCostFunction(const CameraConstPtr& cameraLeft, const CameraConstPtr& cameraRight, const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p_left, const Eigen::Vector2d& observed_p_right) const; private: static boost::shared_ptr m_instance; }; } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/camera_models/EquidistantCamera.h ================================================ #ifndef EQUIDISTANTCAMERA_H #define EQUIDISTANTCAMERA_H #include #include #include "ceres/rotation.h" #include "Camera.h" namespace camodocal { /** * J. Kannala, and S. Brandt, A Generic Camera Model and Calibration Method * for Conventional, Wide-Angle, and Fish-Eye Lenses, PAMI 2006 */ class EquidistantCamera: public Camera { public: class Parameters: public Camera::Parameters { public: Parameters(); Parameters(const std::string& cameraName, int w, int h, double k2, double k3, double k4, double k5, double mu, double mv, double u0, double v0); double& k2(void); double& k3(void); double& k4(void); double& k5(void); double& mu(void); double& mv(void); double& u0(void); double& v0(void); double k2(void) const; double k3(void) const; double k4(void) const; double k5(void) const; double mu(void) const; double mv(void) const; double u0(void) const; double v0(void) const; bool readFromYamlFile(const std::string& filename); void writeToYamlFile(const std::string& filename) const; Parameters& operator=(const Parameters& other); friend std::ostream& operator<< (std::ostream& out, const Parameters& params); private: // projection double m_k2; double m_k3; double m_k4; double m_k5; double m_mu; double m_mv; double m_u0; double m_v0; }; EquidistantCamera(); /** * \brief Constructor from the projection model parameters */ EquidistantCamera(const std::string& cameraName, int imageWidth, int imageHeight, double k2, double k3, double k4, double k5, double mu, double mv, double u0, double v0); /** * \brief Constructor from the projection model parameters */ EquidistantCamera(const Parameters& params); Camera::ModelType modelType(void) const; const std::string& cameraName(void) const; int imageWidth(void) const; int imageHeight(void) const; void estimateIntrinsics(const cv::Size& boardSize, const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints); // Lift points from the image plane to the sphere virtual void liftSphere(const Eigen::Vector2d& p, Eigen::Vector3d& P) const; //%output P // Lift points from the image plane to the projective space void liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const; //%output P // Projects 3D points to the image plane (Pi function) void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p) const; //%output p // Projects 3D points to the image plane (Pi function) // and calculates jacobian void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p, Eigen::Matrix& J) const; //%output p //%output J void undistToPlane(const Eigen::Vector2d& p_u, Eigen::Vector2d& p) const; //%output p template static void spaceToPlane(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& p); void initUndistortMap(cv::Mat& map1, cv::Mat& map2, double fScale = 1.0) const; cv::Mat initUndistortRectifyMap(cv::Mat& map1, cv::Mat& map2, float fx = -1.0f, float fy = -1.0f, cv::Size imageSize = cv::Size(0, 0), float cx = -1.0f, float cy = -1.0f, cv::Mat rmat = cv::Mat::eye(3, 3, CV_32F)) const; int parameterCount(void) const; const Parameters& getParameters(void) const; void setParameters(const Parameters& parameters); void readParameters(const std::vector& parameterVec); void writeParameters(std::vector& parameterVec) const; void writeParametersToYamlFile(const std::string& filename) const; std::string parametersToString(void) const; private: template static T r(T k2, T k3, T k4, T k5, T theta); void fitOddPoly(const std::vector& x, const std::vector& y, int n, std::vector& coeffs) const; void backprojectSymmetric(const Eigen::Vector2d& p_u, double& theta, double& phi) const; Parameters mParameters; double m_inv_K11, m_inv_K13, m_inv_K22, m_inv_K23; }; typedef boost::shared_ptr EquidistantCameraPtr; typedef boost::shared_ptr EquidistantCameraConstPtr; template T EquidistantCamera::r(T k2, T k3, T k4, T k5, T theta) { // k1 = 1 return theta + k2 * theta * theta * theta + k3 * theta * theta * theta * theta * theta + k4 * theta * theta * theta * theta * theta * theta * theta + k5 * theta * theta * theta * theta * theta * theta * theta * theta * theta; } template void EquidistantCamera::spaceToPlane(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& p) { T P_w[3]; P_w[0] = T(P(0)); P_w[1] = T(P(1)); P_w[2] = T(P(2)); // Convert quaternion from Eigen convention (x, y, z, w) // to Ceres convention (w, x, y, z) T q_ceres[4] = {q[3], q[0], q[1], q[2]}; T P_c[3]; ceres::QuaternionRotatePoint(q_ceres, P_w, P_c); P_c[0] += t[0]; P_c[1] += t[1]; P_c[2] += t[2]; // project 3D object point to the image plane; T k2 = params[0]; T k3 = params[1]; T k4 = params[2]; T k5 = params[3]; T mu = params[4]; T mv = params[5]; T u0 = params[6]; T v0 = params[7]; T len = sqrt(P_c[0] * P_c[0] + P_c[1] * P_c[1] + P_c[2] * P_c[2]); T theta = acos(P_c[2] / len); T phi = atan2(P_c[1], P_c[0]); Eigen::Matrix p_u = r(k2, k3, k4, k5, theta) * Eigen::Matrix(cos(phi), sin(phi)); p(0) = mu * p_u(0) + u0; p(1) = mv * p_u(1) + v0; } } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/camera_models/PinholeCamera.h ================================================ #ifndef PINHOLECAMERA_H #define PINHOLECAMERA_H #include #include #include "ceres/rotation.h" #include "Camera.h" namespace camodocal { class PinholeCamera: public Camera { public: class Parameters: public Camera::Parameters { public: Parameters(); Parameters(const std::string& cameraName, int w, int h, double k1, double k2, double p1, double p2, double fx, double fy, double cx, double cy); double& k1(void); double& k2(void); double& p1(void); double& p2(void); double& fx(void); double& fy(void); double& cx(void); double& cy(void); double xi(void) const; double k1(void) const; double k2(void) const; double p1(void) const; double p2(void) const; double fx(void) const; double fy(void) const; double cx(void) const; double cy(void) const; bool readFromYamlFile(const std::string& filename); void writeToYamlFile(const std::string& filename) const; Parameters& operator=(const Parameters& other); friend std::ostream& operator<< (std::ostream& out, const Parameters& params); private: double m_k1; double m_k2; double m_p1; double m_p2; double m_fx; double m_fy; double m_cx; double m_cy; }; PinholeCamera(); /** * \brief Constructor from the projection model parameters */ PinholeCamera(const std::string& cameraName, int imageWidth, int imageHeight, double k1, double k2, double p1, double p2, double fx, double fy, double cx, double cy); /** * \brief Constructor from the projection model parameters */ PinholeCamera(const Parameters& params); Camera::ModelType modelType(void) const; const std::string& cameraName(void) const; int imageWidth(void) const; int imageHeight(void) const; void estimateIntrinsics(const cv::Size& boardSize, const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints); // Lift points from the image plane to the sphere virtual void liftSphere(const Eigen::Vector2d& p, Eigen::Vector3d& P) const; //%output P // Lift points from the image plane to the projective space void liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const; //%output P // Projects 3D points to the image plane (Pi function) void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p) const; //%output p // Projects 3D points to the image plane (Pi function) // and calculates jacobian void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p, Eigen::Matrix& J) const; //%output p //%output J void undistToPlane(const Eigen::Vector2d& p_u, Eigen::Vector2d& p) const; //%output p template static void spaceToPlane(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& p); void distortion(const Eigen::Vector2d& p_u, Eigen::Vector2d& d_u) const; void distortion(const Eigen::Vector2d& p_u, Eigen::Vector2d& d_u, Eigen::Matrix2d& J) const; void initUndistortMap(cv::Mat& map1, cv::Mat& map2, double fScale = 1.0) const; cv::Mat initUndistortRectifyMap(cv::Mat& map1, cv::Mat& map2, float fx = -1.0f, float fy = -1.0f, cv::Size imageSize = cv::Size(0, 0), float cx = -1.0f, float cy = -1.0f, cv::Mat rmat = cv::Mat::eye(3, 3, CV_32F)) const; int parameterCount(void) const; const Parameters& getParameters(void) const; void setParameters(const Parameters& parameters); void readParameters(const std::vector& parameterVec); void writeParameters(std::vector& parameterVec) const; void writeParametersToYamlFile(const std::string& filename) const; std::string parametersToString(void) const; private: Parameters mParameters; double m_inv_K11, m_inv_K13, m_inv_K22, m_inv_K23; bool m_noDistortion; }; typedef boost::shared_ptr PinholeCameraPtr; typedef boost::shared_ptr PinholeCameraConstPtr; template void PinholeCamera::spaceToPlane(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& p) { T P_w[3]; P_w[0] = T(P(0)); P_w[1] = T(P(1)); P_w[2] = T(P(2)); // Convert quaternion from Eigen convention (x, y, z, w) // to Ceres convention (w, x, y, z) T q_ceres[4] = {q[3], q[0], q[1], q[2]}; T P_c[3]; ceres::QuaternionRotatePoint(q_ceres, P_w, P_c); P_c[0] += t[0]; P_c[1] += t[1]; P_c[2] += t[2]; // project 3D object point to the image plane T k1 = params[0]; T k2 = params[1]; T p1 = params[2]; T p2 = params[3]; T fx = params[4]; T fy = params[5]; T alpha = T(0); //cameraParams.alpha(); T cx = params[6]; T cy = params[7]; // Transform to model plane T u = P_c[0] / P_c[2]; T v = P_c[1] / P_c[2]; T rho_sqr = u * u + v * v; T L = T(1.0) + k1 * rho_sqr + k2 * rho_sqr * rho_sqr; T du = T(2.0) * p1 * u * v + p2 * (rho_sqr + T(2.0) * u * u); T dv = p1 * (rho_sqr + T(2.0) * v * v) + T(2.0) * p2 * u * v; u = L * u + du; v = L * v + dv; p(0) = fx * (u + alpha * v) + cx; p(1) = fy * v + cy; } } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/camera_models/ScaramuzzaCamera.h ================================================ #ifndef SCARAMUZZACAMERA_H #define SCARAMUZZACAMERA_H #include #include #include "ceres/rotation.h" #include "Camera.h" namespace camodocal { #define SCARAMUZZA_POLY_SIZE 5 #define SCARAMUZZA_INV_POLY_SIZE 20 #define SCARAMUZZA_CAMERA_NUM_PARAMS (SCARAMUZZA_POLY_SIZE + SCARAMUZZA_INV_POLY_SIZE + 2 /*center*/ + 3 /*affine*/) /** * Scaramuzza Camera (Omnidirectional) * https://sites.google.com/site/scarabotix/ocamcalib-toolbox */ class OCAMCamera: public Camera { public: class Parameters: public Camera::Parameters { public: Parameters(); double& C(void) { return m_C; } double& D(void) { return m_D; } double& E(void) { return m_E; } double& center_x(void) { return m_center_x; } double& center_y(void) { return m_center_y; } double& poly(int idx) { return m_poly[idx]; } double& inv_poly(int idx) { return m_inv_poly[idx]; } double C(void) const { return m_C; } double D(void) const { return m_D; } double E(void) const { return m_E; } double center_x(void) const { return m_center_x; } double center_y(void) const { return m_center_y; } double poly(int idx) const { return m_poly[idx]; } double inv_poly(int idx) const { return m_inv_poly[idx]; } bool readFromYamlFile(const std::string& filename); void writeToYamlFile(const std::string& filename) const; Parameters& operator=(const Parameters& other); friend std::ostream& operator<< (std::ostream& out, const Parameters& params); private: double m_poly[SCARAMUZZA_POLY_SIZE]; double m_inv_poly[SCARAMUZZA_INV_POLY_SIZE]; double m_C; double m_D; double m_E; double m_center_x; double m_center_y; }; OCAMCamera(); /** * \brief Constructor from the projection model parameters */ OCAMCamera(const Parameters& params); Camera::ModelType modelType(void) const; const std::string& cameraName(void) const; int imageWidth(void) const; int imageHeight(void) const; void estimateIntrinsics(const cv::Size& boardSize, const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints); // Lift points from the image plane to the sphere void liftSphere(const Eigen::Vector2d& p, Eigen::Vector3d& P) const; //%output P // Lift points from the image plane to the projective space void liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const; //%output P // Projects 3D points to the image plane (Pi function) void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p) const; //%output p // Projects 3D points to the image plane (Pi function) // and calculates jacobian //void spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p, // Eigen::Matrix& J) const; //%output p //%output J void undistToPlane(const Eigen::Vector2d& p_u, Eigen::Vector2d& p) const; //%output p template static void spaceToPlane(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& p); template static void spaceToSphere(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& P_s); template static void LiftToSphere(const T* const params, const Eigen::Matrix& p, Eigen::Matrix& P); template static void SphereToPlane(const T* const params, const Eigen::Matrix& P, Eigen::Matrix& p); void initUndistortMap(cv::Mat& map1, cv::Mat& map2, double fScale = 1.0) const; cv::Mat initUndistortRectifyMap(cv::Mat& map1, cv::Mat& map2, float fx = -1.0f, float fy = -1.0f, cv::Size imageSize = cv::Size(0, 0), float cx = -1.0f, float cy = -1.0f, cv::Mat rmat = cv::Mat::eye(3, 3, CV_32F)) const; int parameterCount(void) const; const Parameters& getParameters(void) const; void setParameters(const Parameters& parameters); void readParameters(const std::vector& parameterVec); void writeParameters(std::vector& parameterVec) const; void writeParametersToYamlFile(const std::string& filename) const; std::string parametersToString(void) const; private: Parameters mParameters; double m_inv_scale; }; typedef boost::shared_ptr OCAMCameraPtr; typedef boost::shared_ptr OCAMCameraConstPtr; template void OCAMCamera::spaceToPlane(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& p) { T P_c[3]; { T P_w[3]; P_w[0] = T(P(0)); P_w[1] = T(P(1)); P_w[2] = T(P(2)); // Convert quaternion from Eigen convention (x, y, z, w) // to Ceres convention (w, x, y, z) T q_ceres[4] = {q[3], q[0], q[1], q[2]}; ceres::QuaternionRotatePoint(q_ceres, P_w, P_c); P_c[0] += t[0]; P_c[1] += t[1]; P_c[2] += t[2]; } T c = params[0]; T d = params[1]; T e = params[2]; T xc[2] = { params[3], params[4] }; //T poly[SCARAMUZZA_POLY_SIZE]; //for (int i=0; i < SCARAMUZZA_POLY_SIZE; i++) // poly[i] = params[5+i]; T inv_poly[SCARAMUZZA_INV_POLY_SIZE]; for (int i=0; i < SCARAMUZZA_INV_POLY_SIZE; i++) inv_poly[i] = params[5 + SCARAMUZZA_POLY_SIZE + i]; T norm_sqr = P_c[0] * P_c[0] + P_c[1] * P_c[1]; T norm = T(0.0); if (norm_sqr > T(0.0)) norm = sqrt(norm_sqr); T theta = atan2(-P_c[2], norm); T rho = T(0.0); T theta_i = T(1.0); for (int i = 0; i < SCARAMUZZA_INV_POLY_SIZE; i++) { rho += theta_i * inv_poly[i]; theta_i *= theta; } T invNorm = T(1.0) / norm; T xn[2] = { P_c[0] * invNorm * rho, P_c[1] * invNorm * rho }; p(0) = xn[0] * c + xn[1] * d + xc[0]; p(1) = xn[0] * e + xn[1] + xc[1]; } template void OCAMCamera::spaceToSphere(const T* const params, const T* const q, const T* const t, const Eigen::Matrix& P, Eigen::Matrix& P_s) { T P_c[3]; { T P_w[3]; P_w[0] = T(P(0)); P_w[1] = T(P(1)); P_w[2] = T(P(2)); // Convert quaternion from Eigen convention (x, y, z, w) // to Ceres convention (w, x, y, z) T q_ceres[4] = {q[3], q[0], q[1], q[2]}; ceres::QuaternionRotatePoint(q_ceres, P_w, P_c); P_c[0] += t[0]; P_c[1] += t[1]; P_c[2] += t[2]; } //T poly[SCARAMUZZA_POLY_SIZE]; //for (int i=0; i < SCARAMUZZA_POLY_SIZE; i++) // poly[i] = params[5+i]; T norm_sqr = P_c[0] * P_c[0] + P_c[1] * P_c[1] + P_c[2] * P_c[2]; T norm = T(0.0); if (norm_sqr > T(0.0)) norm = sqrt(norm_sqr); P_s(0) = P_c[0] / norm; P_s(1) = P_c[1] / norm; P_s(2) = P_c[2] / norm; } template void OCAMCamera::LiftToSphere(const T* const params, const Eigen::Matrix& p, Eigen::Matrix& P) { T c = params[0]; T d = params[1]; T e = params[2]; T cc[2] = { params[3], params[4] }; T poly[SCARAMUZZA_POLY_SIZE]; for (int i=0; i < SCARAMUZZA_POLY_SIZE; i++) poly[i] = params[5+i]; // Relative to Center T p_2d[2]; p_2d[0] = T(p(0)); p_2d[1] = T(p(1)); T xc[2] = { p_2d[0] - cc[0], p_2d[1] - cc[1]}; T inv_scale = T(1.0) / (c - d * e); // Affine Transformation T xc_a[2]; xc_a[0] = inv_scale * (xc[0] - d * xc[1]); xc_a[1] = inv_scale * (-e * xc[0] + c * xc[1]); T norm_sqr = xc_a[0] * xc_a[0] + xc_a[1] * xc_a[1]; T phi = sqrt(norm_sqr); T phi_i = T(1.0); T z = T(0.0); for (int i = 0; i < SCARAMUZZA_POLY_SIZE; i++) { if (i!=1) { z += phi_i * poly[i]; } phi_i *= phi; } T p_3d[3]; p_3d[0] = xc[0]; p_3d[1] = xc[1]; p_3d[2] = -z; T p_3d_norm_sqr = p_3d[0] * p_3d[0] + p_3d[1] * p_3d[1] + p_3d[2] * p_3d[2]; T p_3d_norm = sqrt(p_3d_norm_sqr); P << p_3d[0] / p_3d_norm, p_3d[1] / p_3d_norm, p_3d[2] / p_3d_norm; } template void OCAMCamera::SphereToPlane(const T* const params, const Eigen::Matrix& P, Eigen::Matrix& p) { T P_c[3]; { P_c[0] = T(P(0)); P_c[1] = T(P(1)); P_c[2] = T(P(2)); } T c = params[0]; T d = params[1]; T e = params[2]; T xc[2] = {params[3], params[4]}; T inv_poly[SCARAMUZZA_INV_POLY_SIZE]; for (int i = 0; i < SCARAMUZZA_INV_POLY_SIZE; i++) inv_poly[i] = params[5 + SCARAMUZZA_POLY_SIZE + i]; T norm_sqr = P_c[0] * P_c[0] + P_c[1] * P_c[1]; T norm = T(0.0); if (norm_sqr > T(0.0)) norm = sqrt(norm_sqr); T theta = atan2(-P_c[2], norm); T rho = T(0.0); T theta_i = T(1.0); for (int i = 0; i < SCARAMUZZA_INV_POLY_SIZE; i++) { rho += theta_i * inv_poly[i]; theta_i *= theta; } T invNorm = T(1.0) / norm; T xn[2] = {P_c[0] * invNorm * rho, P_c[1] * invNorm * rho}; p(0) = xn[0] * c + xn[1] * d + xc[0]; p(1) = xn[0] * e + xn[1] + xc[1]; } } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/chessboard/Chessboard.h ================================================ #ifndef CHESSBOARD_H #define CHESSBOARD_H #include #include namespace camodocal { // forward declarations class ChessboardCorner; typedef boost::shared_ptr ChessboardCornerPtr; class ChessboardQuad; typedef boost::shared_ptr ChessboardQuadPtr; class Chessboard { public: Chessboard(cv::Size boardSize, cv::Mat& image); void findCorners(bool useOpenCV = false); const std::vector& getCorners(void) const; bool cornersFound(void) const; const cv::Mat& getImage(void) const; const cv::Mat& getSketch(void) const; private: bool findChessboardCorners(const cv::Mat& image, const cv::Size& patternSize, std::vector& corners, int flags, bool useOpenCV); bool findChessboardCornersImproved(const cv::Mat& image, const cv::Size& patternSize, std::vector& corners, int flags); void cleanFoundConnectedQuads(std::vector& quadGroup, cv::Size patternSize); void findConnectedQuads(std::vector& quads, std::vector& group, int group_idx, int dilation); // int checkQuadGroup(std::vector& quadGroup, // std::vector& outCorners, // cv::Size patternSize); void labelQuadGroup(std::vector& quad_group, cv::Size patternSize, bool firstRun); void findQuadNeighbors(std::vector& quads, int dilation); int augmentBestRun(std::vector& candidateQuads, int candidateDilation, std::vector& existingQuads, int existingDilation); void generateQuads(std::vector& quads, cv::Mat& image, int flags, int dilation, bool firstRun); bool checkQuadGroup(std::vector& quads, std::vector& corners, cv::Size patternSize); void getQuadrangleHypotheses(const std::vector< std::vector >& contours, std::vector< std::pair >& quads, int classId) const; bool checkChessboard(const cv::Mat& image, cv::Size patternSize) const; bool checkBoardMonotony(std::vector& corners, cv::Size patternSize); bool matchCorners(ChessboardQuadPtr& quad1, int corner1, ChessboardQuadPtr& quad2, int corner2) const; cv::Mat mImage; cv::Mat mSketch; std::vector mCorners; cv::Size mBoardSize; bool mCornersFound; }; } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/chessboard/ChessboardCorner.h ================================================ #ifndef CHESSBOARDCORNER_H #define CHESSBOARDCORNER_H #include #include namespace camodocal { class ChessboardCorner; typedef boost::shared_ptr ChessboardCornerPtr; class ChessboardCorner { public: ChessboardCorner() : row(0), column(0), needsNeighbor(true), count(0) {} float meanDist(int &n) const { float sum = 0; n = 0; for (int i = 0; i < 4; ++i) { if (neighbors[i].get()) { float dx = neighbors[i]->pt.x - pt.x; float dy = neighbors[i]->pt.y - pt.y; sum += sqrt(dx*dx + dy*dy); n++; } } return sum / std::max(n, 1); } cv::Point2f pt; // X and y coordinates int row; // Row and column of the corner int column; // in the found pattern bool needsNeighbor; // Does the corner require a neighbor? int count; // number of corner neighbors ChessboardCornerPtr neighbors[4]; // pointer to all corner neighbors }; } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/chessboard/ChessboardQuad.h ================================================ #ifndef CHESSBOARDQUAD_H #define CHESSBOARDQUAD_H #include #include "camodocal/chessboard/ChessboardCorner.h" namespace camodocal { class ChessboardQuad; typedef boost::shared_ptr ChessboardQuadPtr; class ChessboardQuad { public: ChessboardQuad() : count(0), group_idx(-1), edge_len(FLT_MAX), labeled(false) {} int count; // Number of quad neighbors int group_idx; // Quad group ID float edge_len; // Smallest side length^2 ChessboardCornerPtr corners[4]; // Coordinates of quad corners ChessboardQuadPtr neighbors[4]; // Pointers of quad neighbors bool labeled; // Has this corner been labeled? }; } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/chessboard/Spline.h ================================================ /* dynamo:- Event driven molecular dynamics simulator http://www.marcusbannerman.co.uk/dynamo Copyright (C) 2011 Marcus N Campbell Bannerman This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #pragma once #include #include #include #include #include #include namespace ublas = boost::numeric::ublas; class Spline : private std::vector > { public: //The boundary conditions available enum BC_type { FIXED_1ST_DERIV_BC, FIXED_2ND_DERIV_BC, PARABOLIC_RUNOUT_BC }; enum Spline_type { LINEAR, CUBIC }; //Constructor takes the boundary conditions as arguments, this //sets the first derivative (gradient) at the lower and upper //end points Spline(): _valid(false), _BCLow(FIXED_2ND_DERIV_BC), _BCHigh(FIXED_2ND_DERIV_BC), _BCLowVal(0), _BCHighVal(0), _type(CUBIC) {} typedef std::vector > base; typedef base::const_iterator const_iterator; //Standard STL read-only container stuff const_iterator begin() const { return base::begin(); } const_iterator end() const { return base::end(); } void clear() { _valid = false; base::clear(); _data.clear(); } size_t size() const { return base::size(); } size_t max_size() const { return base::max_size(); } size_t capacity() const { return base::capacity(); } bool empty() const { return base::empty(); } //Add a point to the spline, and invalidate it so its //recalculated on the next access inline void addPoint(double x, double y) { _valid = false; base::push_back(std::pair(x,y)); } //Reset the boundary conditions inline void setLowBC(BC_type BC, double val = 0) { _BCLow = BC; _BCLowVal = val; _valid = false; } inline void setHighBC(BC_type BC, double val = 0) { _BCHigh = BC; _BCHighVal = val; _valid = false; } void setType(Spline_type type) { _type = type; _valid = false; } //Check if the spline has been calculated, then generate the //spline interpolated value double operator()(double xval) { if (!_valid) generate(); //Special cases when we're outside the range of the spline points if (xval <= x(0)) return lowCalc(xval); if (xval >= x(size()-1)) return highCalc(xval); //Check all intervals except the last one for (std::vector::const_iterator iPtr = _data.begin(); iPtr != _data.end()-1; ++iPtr) if ((xval >= iPtr->x) && (xval <= (iPtr+1)->x)) return splineCalc(iPtr, xval); return splineCalc(_data.end() - 1, xval); } private: ///////PRIVATE DATA MEMBERS struct SplineData { double x,a,b,c,d; }; //vector of calculated spline data std::vector _data; //Second derivative at each point ublas::vector _ddy; //Tracks whether the spline parameters have been calculated for //the current set of points bool _valid; //The boundary conditions BC_type _BCLow, _BCHigh; //The values of the boundary conditions double _BCLowVal, _BCHighVal; Spline_type _type; ///////PRIVATE FUNCTIONS //Function to calculate the value of a given spline at a point xval inline double splineCalc(std::vector::const_iterator i, double xval) { const double lx = xval - i->x; return ((i->a * lx + i->b) * lx + i->c) * lx + i->d; } inline double lowCalc(double xval) { const double lx = xval - x(0); if (_type == LINEAR) return lx * _BCHighVal + y(0); const double firstDeriv = (y(1) - y(0)) / h(0) - 2 * h(0) * (_data[0].b + 2 * _data[1].b) / 6; switch(_BCLow) { case FIXED_1ST_DERIV_BC: return lx * _BCLowVal + y(0); case FIXED_2ND_DERIV_BC: return lx * lx * _BCLowVal + firstDeriv * lx + y(0); case PARABOLIC_RUNOUT_BC: return lx * lx * _ddy[0] + lx * firstDeriv + y(0); } throw std::runtime_error("Unknown BC"); } inline double highCalc(double xval) { const double lx = xval - x(size() - 1); if (_type == LINEAR) return lx * _BCHighVal + y(size() - 1); const double firstDeriv = 2 * h(size() - 2) * (_ddy[size() - 2] + 2 * _ddy[size() - 1]) / 6 + (y(size() - 1) - y(size() - 2)) / h(size() - 2); switch(_BCHigh) { case FIXED_1ST_DERIV_BC: return lx * _BCHighVal + y(size() - 1); case FIXED_2ND_DERIV_BC: return lx * lx * _BCHighVal + firstDeriv * lx + y(size() - 1); case PARABOLIC_RUNOUT_BC: return lx * lx * _ddy[size()-1] + lx * firstDeriv + y(size() - 1); } throw std::runtime_error("Unknown BC"); } //These just provide access to the point data in a clean way inline double x(size_t i) const { return operator[](i).first; } inline double y(size_t i) const { return operator[](i).second; } inline double h(size_t i) const { return x(i+1) - x(i); } //Invert a arbitrary matrix using the boost ublas library template bool InvertMatrix(ublas::matrix A, ublas::matrix& inverse) { using namespace ublas; // create a permutation matrix for the LU-factorization permutation_matrix pm(A.size1()); // perform LU-factorization int res = lu_factorize(A,pm); if( res != 0 ) return false; // create identity matrix of "inverse" inverse.assign(ublas::identity_matrix(A.size1())); // backsubstitute to get the inverse lu_substitute(A, pm, inverse); return true; } //This function will recalculate the spline parameters and store //them in _data, ready for spline interpolation void generate() { if (size() < 2) throw std::runtime_error("Spline requires at least 2 points"); //If any spline points are at the same x location, we have to //just slightly seperate them { bool testPassed(false); while (!testPassed) { testPassed = true; std::sort(base::begin(), base::end()); for (base::iterator iPtr = base::begin(); iPtr != base::end() - 1; ++iPtr) if (iPtr->first == (iPtr+1)->first) { if ((iPtr+1)->first != 0) (iPtr+1)->first += (iPtr+1)->first * std::numeric_limits::epsilon() * 10; else (iPtr+1)->first = std::numeric_limits::epsilon() * 10; testPassed = false; break; } } } const size_t e = size() - 1; switch (_type) { case LINEAR: { _data.resize(e); for (size_t i(0); i < e; ++i) { _data[i].x = x(i); _data[i].a = 0; _data[i].b = 0; _data[i].c = (y(i+1) - y(i)) / (x(i+1) - x(i)); _data[i].d = y(i); } break; } case CUBIC: { ublas::matrix A(size(), size()); for (size_t yv(0); yv <= e; ++yv) for (size_t xv(0); xv <= e; ++xv) A(xv,yv) = 0; for (size_t i(1); i < e; ++i) { A(i-1,i) = h(i-1); A(i,i) = 2 * (h(i-1) + h(i)); A(i+1,i) = h(i); } ublas::vector C(size()); for (size_t xv(0); xv <= e; ++xv) C(xv) = 0; for (size_t i(1); i < e; ++i) C(i) = 6 * ((y(i+1) - y(i)) / h(i) - (y(i) - y(i-1)) / h(i-1)); //Boundary conditions switch(_BCLow) { case FIXED_1ST_DERIV_BC: C(0) = 6 * ((y(1) - y(0)) / h(0) - _BCLowVal); A(0,0) = 2 * h(0); A(1,0) = h(0); break; case FIXED_2ND_DERIV_BC: C(0) = _BCLowVal; A(0,0) = 1; break; case PARABOLIC_RUNOUT_BC: C(0) = 0; A(0,0) = 1; A(1,0) = -1; break; } switch(_BCHigh) { case FIXED_1ST_DERIV_BC: C(e) = 6 * (_BCHighVal - (y(e) - y(e-1)) / h(e-1)); A(e,e) = 2 * h(e - 1); A(e-1,e) = h(e - 1); break; case FIXED_2ND_DERIV_BC: C(e) = _BCHighVal; A(e,e) = 1; break; case PARABOLIC_RUNOUT_BC: C(e) = 0; A(e,e) = 1; A(e-1,e) = -1; break; } ublas::matrix AInv(size(), size()); InvertMatrix(A,AInv); _ddy = ublas::prod(C, AInv); _data.resize(size()-1); for (size_t i(0); i < e; ++i) { _data[i].x = x(i); _data[i].a = (_ddy(i+1) - _ddy(i)) / (6 * h(i)); _data[i].b = _ddy(i) / 2; _data[i].c = (y(i+1) - y(i)) / h(i) - _ddy(i+1) * h(i) / 6 - _ddy(i) * h(i) / 3; _data[i].d = y(i); } } } _valid = true; } }; ================================================ FILE: src/utils/camodocal/include/camodocal/gpl/EigenQuaternionParameterization.h ================================================ #ifndef EIGENQUATERNIONPARAMETERIZATION_H #define EIGENQUATERNIONPARAMETERIZATION_H #include "ceres/local_parameterization.h" namespace camodocal { class EigenQuaternionParameterization : public ceres::LocalParameterization { public: virtual ~EigenQuaternionParameterization() {} virtual bool Plus(const double* x, const double* delta, double* x_plus_delta) const; virtual bool ComputeJacobian(const double* x, double* jacobian) const; virtual int GlobalSize() const { return 4; } virtual int LocalSize() const { return 3; } private: template void EigenQuaternionProduct(const T z[4], const T w[4], T zw[4]) const; }; template void EigenQuaternionParameterization::EigenQuaternionProduct(const T z[4], const T w[4], T zw[4]) const { zw[0] = z[3] * w[0] + z[0] * w[3] + z[1] * w[2] - z[2] * w[1]; zw[1] = z[3] * w[1] - z[0] * w[2] + z[1] * w[3] + z[2] * w[0]; zw[2] = z[3] * w[2] + z[0] * w[1] - z[1] * w[0] + z[2] * w[3]; zw[3] = z[3] * w[3] - z[0] * w[0] - z[1] * w[1] - z[2] * w[2]; } } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/gpl/EigenUtils.h ================================================ #ifndef EIGENUTILS_H #define EIGENUTILS_H #include #include "ceres/rotation.h" #include "camodocal/gpl/gpl.h" namespace camodocal { // Returns the 3D cross product skew symmetric matrix of a given 3D vector template Eigen::Matrix skew(const Eigen::Matrix& vec) { return (Eigen::Matrix() << T(0), -vec(2), vec(1), vec(2), T(0), -vec(0), -vec(1), vec(0), T(0)).finished(); } template typename Eigen::MatrixBase::PlainObject sqrtm(const Eigen::MatrixBase& A) { Eigen::SelfAdjointEigenSolver es(A); return es.operatorSqrt(); } template Eigen::Matrix AngleAxisToRotationMatrix(const Eigen::Matrix& rvec) { T angle = rvec.norm(); if (angle == T(0)) { return Eigen::Matrix::Identity(); } Eigen::Matrix axis; axis = rvec.normalized(); Eigen::Matrix rmat; rmat = Eigen::AngleAxis(angle, axis); return rmat; } template Eigen::Quaternion AngleAxisToQuaternion(const Eigen::Matrix& rvec) { Eigen::Matrix rmat = AngleAxisToRotationMatrix(rvec); return Eigen::Quaternion(rmat); } template void AngleAxisToQuaternion(const Eigen::Matrix& rvec, T* q) { Eigen::Quaternion quat = AngleAxisToQuaternion(rvec); q[0] = quat.x(); q[1] = quat.y(); q[2] = quat.z(); q[3] = quat.w(); } template Eigen::Matrix RotationToAngleAxis(const Eigen::Matrix & rmat) { Eigen::AngleAxis angleaxis; angleaxis.fromRotationMatrix(rmat); return angleaxis.angle() * angleaxis.axis(); } template void QuaternionToAngleAxis(const T* const q, Eigen::Matrix& rvec) { Eigen::Quaternion quat(q[3], q[0], q[1], q[2]); Eigen::Matrix rmat = quat.toRotationMatrix(); Eigen::AngleAxis angleaxis; angleaxis.fromRotationMatrix(rmat); rvec = angleaxis.angle() * angleaxis.axis(); } template Eigen::Matrix QuaternionToRotation(const T* const q) { T R[9]; ceres::QuaternionToRotation(q, R); Eigen::Matrix rmat; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { rmat(i,j) = R[i * 3 + j]; } } return rmat; } template void QuaternionToRotation(const T* const q, T* rot) { ceres::QuaternionToRotation(q, rot); } template Eigen::Matrix QuaternionMultMatLeft(const Eigen::Quaternion& q) { return (Eigen::Matrix() << q.w(), -q.z(), q.y(), q.x(), q.z(), q.w(), -q.x(), q.y(), -q.y(), q.x(), q.w(), q.z(), -q.x(), -q.y(), -q.z(), q.w()).finished(); } template Eigen::Matrix QuaternionMultMatRight(const Eigen::Quaternion& q) { return (Eigen::Matrix() << q.w(), q.z(), -q.y(), q.x(), -q.z(), q.w(), q.x(), q.y(), q.y(), -q.x(), q.w(), q.z(), -q.x(), -q.y(), -q.z(), q.w()).finished(); } /// @param theta - rotation about screw axis /// @param d - projection of tvec on the rotation axis /// @param l - screw axis direction /// @param m - screw axis moment template void AngleAxisAndTranslationToScrew(const Eigen::Matrix& rvec, const Eigen::Matrix& tvec, T& theta, T& d, Eigen::Matrix& l, Eigen::Matrix& m) { theta = rvec.norm(); if (theta == 0) { l.setZero(); m.setZero(); std::cout << "Warning: Undefined screw! Returned 0. " << std::endl; } l = rvec.normalized(); Eigen::Matrix t = tvec; d = t.transpose() * l; // point on screw axis - projection of origin on screw axis Eigen::Matrix c; c = 0.5 * (t - d * l + (1.0 / tan(theta / 2.0) * l).cross(t)); // c and hence the screw axis is not defined if theta is either 0 or M_PI m = c.cross(l); } template Eigen::Matrix RPY2mat(T roll, T pitch, T yaw) { Eigen::Matrix m; T cr = cos(roll); T sr = sin(roll); T cp = cos(pitch); T sp = sin(pitch); T cy = cos(yaw); T sy = sin(yaw); m(0,0) = cy * cp; m(0,1) = cy * sp * sr - sy * cr; m(0,2) = cy * sp * cr + sy * sr; m(1,0) = sy * cp; m(1,1) = sy * sp * sr + cy * cr; m(1,2) = sy * sp * cr - cy * sr; m(2,0) = - sp; m(2,1) = cp * sr; m(2,2) = cp * cr; return m; } template void mat2RPY(const Eigen::Matrix& m, T& roll, T& pitch, T& yaw) { roll = atan2(m(2,1), m(2,2)); pitch = atan2(-m(2,0), sqrt(m(2,1) * m(2,1) + m(2,2) * m(2,2))); yaw = atan2(m(1,0), m(0,0)); } template Eigen::Matrix homogeneousTransform(const Eigen::Matrix& R, const Eigen::Matrix& t) { Eigen::Matrix H; H.setIdentity(); H.block(0,0,3,3) = R; H.block(0,3,3,1) = t; return H; } template Eigen::Matrix poseWithCartesianTranslation(const T* const q, const T* const p) { Eigen::Matrix pose = Eigen::Matrix::Identity(); T rotation[9]; ceres::QuaternionToRotation(q, rotation); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { pose(i,j) = rotation[i * 3 + j]; } } pose(0,3) = p[0]; pose(1,3) = p[1]; pose(2,3) = p[2]; return pose; } template Eigen::Matrix poseWithSphericalTranslation(const T* const q, const T* const p, const T scale = T(1.0)) { Eigen::Matrix pose = Eigen::Matrix::Identity(); T rotation[9]; ceres::QuaternionToRotation(q, rotation); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { pose(i,j) = rotation[i * 3 + j]; } } T theta = p[0]; T phi = p[1]; pose(0,3) = sin(theta) * cos(phi) * scale; pose(1,3) = sin(theta) * sin(phi) * scale; pose(2,3) = cos(theta) * scale; return pose; } // Returns the Sampson error of a given essential matrix and 2 image points template T sampsonError(const Eigen::Matrix& E, const Eigen::Matrix& p1, const Eigen::Matrix& p2) { Eigen::Matrix Ex1 = E * p1; Eigen::Matrix Etx2 = E.transpose() * p2; T x2tEx1 = p2.dot(Ex1); // compute Sampson error T err = square(x2tEx1) / (square(Ex1(0,0)) + square(Ex1(1,0)) + square(Etx2(0,0)) + square(Etx2(1,0))); return err; } // Returns the Sampson error of a given rotation/translation and 2 image points template T sampsonError(const Eigen::Matrix& R, const Eigen::Matrix& t, const Eigen::Matrix& p1, const Eigen::Matrix& p2) { // construct essential matrix Eigen::Matrix E = skew(t) * R; Eigen::Matrix Ex1 = E * p1; Eigen::Matrix Etx2 = E.transpose() * p2; T x2tEx1 = p2.dot(Ex1); // compute Sampson error T err = square(x2tEx1) / (square(Ex1(0,0)) + square(Ex1(1,0)) + square(Etx2(0,0)) + square(Etx2(1,0))); return err; } // Returns the Sampson error of a given rotation/translation and 2 image points template T sampsonError(const Eigen::Matrix& H, const Eigen::Matrix& p1, const Eigen::Matrix& p2) { Eigen::Matrix R = H.block(0, 0, 3, 3); Eigen::Matrix t = H.block(0, 3, 3, 1); return sampsonError(R, t, p1, p2); } template Eigen::Matrix transformPoint(const Eigen::Matrix& H, const Eigen::Matrix& P) { Eigen::Matrix P_trans = H.block(0, 0, 3, 3) * P + H.block(0, 3, 3, 1); return P_trans; } template Eigen::Matrix estimate3DRigidTransform(const std::vector, Eigen::aligned_allocator > >& points1, const std::vector, Eigen::aligned_allocator > >& points2) { // compute centroids Eigen::Matrix c1, c2; c1.setZero(); c2.setZero(); for (size_t i = 0; i < points1.size(); ++i) { c1 += points1.at(i); c2 += points2.at(i); } c1 /= points1.size(); c2 /= points1.size(); Eigen::Matrix X(3, points1.size()); Eigen::Matrix Y(3, points1.size()); for (size_t i = 0; i < points1.size(); ++i) { X.col(i) = points1.at(i) - c1; Y.col(i) = points2.at(i) - c2; } Eigen::Matrix H = X * Y.transpose(); Eigen::JacobiSVD< Eigen::Matrix > svd(H, Eigen::ComputeFullU | Eigen::ComputeFullV); Eigen::Matrix U = svd.matrixU(); Eigen::Matrix V = svd.matrixV(); if (U.determinant() * V.determinant() < 0.0) { V.col(2) *= -1.0; } Eigen::Matrix R = V * U.transpose(); Eigen::Matrix t = c2 - R * c1; return homogeneousTransform(R, t); } template Eigen::Matrix estimate3DRigidSimilarityTransform(const std::vector, Eigen::aligned_allocator > >& points1, const std::vector, Eigen::aligned_allocator > >& points2) { // compute centroids Eigen::Matrix c1, c2; c1.setZero(); c2.setZero(); for (size_t i = 0; i < points1.size(); ++i) { c1 += points1.at(i); c2 += points2.at(i); } c1 /= points1.size(); c2 /= points1.size(); Eigen::Matrix X(3, points1.size()); Eigen::Matrix Y(3, points1.size()); for (size_t i = 0; i < points1.size(); ++i) { X.col(i) = points1.at(i) - c1; Y.col(i) = points2.at(i) - c2; } Eigen::Matrix H = X * Y.transpose(); Eigen::JacobiSVD< Eigen::Matrix > svd(H, Eigen::ComputeFullU | Eigen::ComputeFullV); Eigen::Matrix U = svd.matrixU(); Eigen::Matrix V = svd.matrixV(); if (U.determinant() * V.determinant() < 0.0) { V.col(2) *= -1.0; } Eigen::Matrix R = V * U.transpose(); std::vector, Eigen::aligned_allocator > > rotatedPoints1(points1.size()); for (size_t i = 0; i < points1.size(); ++i) { rotatedPoints1.at(i) = R * (points1.at(i) - c1); } double sum_ss = 0.0, sum_tt = 0.0; for (size_t i = 0; i < points1.size(); ++i) { sum_ss += (points1.at(i) - c1).squaredNorm(); sum_tt += (points2.at(i) - c2).dot(rotatedPoints1.at(i)); } double scale = sum_tt / sum_ss; Eigen::Matrix sR = scale * R; Eigen::Matrix t = c2 - sR * c1; return homogeneousTransform(sR, t); } } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/gpl/gpl.h ================================================ #ifndef GPL_H #define GPL_H #include #include #include namespace camodocal { template const T clamp(const T& v, const T& a, const T& b) { return std::min(b, std::max(a, v)); } double hypot3(double x, double y, double z); float hypot3f(float x, float y, float z); template const T normalizeTheta(const T& theta) { T normTheta = theta; while (normTheta < - M_PI) { normTheta += 2.0 * M_PI; } while (normTheta > M_PI) { normTheta -= 2.0 * M_PI; } return normTheta; } double d2r(double deg); float d2r(float deg); double r2d(double rad); float r2d(float rad); double sinc(double theta); template const T square(const T& x) { return x * x; } template const T cube(const T& x) { return x * x * x; } template const T random(const T& a, const T& b) { return static_cast(rand()) / RAND_MAX * (b - a) + a; } template const T randomNormal(const T& sigma) { T x1, x2, w; do { x1 = 2.0 * random(0.0, 1.0) - 1.0; x2 = 2.0 * random(0.0, 1.0) - 1.0; w = x1 * x1 + x2 * x2; } while (w >= 1.0 || w == 0.0); w = sqrt((-2.0 * log(w)) / w); return x1 * w * sigma; } unsigned long long timeInMicroseconds(void); double timeInSeconds(void); void colorDepthImage(cv::Mat& imgDepth, cv::Mat& imgColoredDepth, float minRange, float maxRange); bool colormap(const std::string& name, unsigned char idx, float& r, float& g, float& b); std::vector bresLine(int x0, int y0, int x1, int y1); std::vector bresCircle(int x0, int y0, int r); void fitCircle(const std::vector& points, double& centerX, double& centerY, double& radius); std::vector intersectCircles(double x1, double y1, double r1, double x2, double y2, double r2); void LLtoUTM(double latitude, double longitude, double& utmNorthing, double& utmEasting, std::string& utmZone); void UTMtoLL(double utmNorthing, double utmEasting, const std::string& utmZone, double& latitude, double& longitude); long int timestampDiff(uint64_t t1, uint64_t t2); } #endif ================================================ FILE: src/utils/camodocal/include/camodocal/sparse_graph/Transform.h ================================================ #ifndef TRANSFORM_H #define TRANSFORM_H #include #include #include namespace camodocal { class Transform { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW Transform(); Transform(const Eigen::Matrix4d& H); Eigen::Quaterniond& rotation(void); const Eigen::Quaterniond& rotation(void) const; double* rotationData(void); const double* const rotationData(void) const; Eigen::Vector3d& translation(void); const Eigen::Vector3d& translation(void) const; double* translationData(void); const double* const translationData(void) const; Eigen::Matrix4d toMatrix(void) const; private: Eigen::Quaterniond m_q; Eigen::Vector3d m_t; }; } #endif ================================================ FILE: src/utils/camodocal/src/calib/CameraCalibration.cc ================================================ #include "camodocal/calib/CameraCalibration.h" #include #include #include #include #include #include #include #include #include #include #include "camodocal/camera_models/CameraFactory.h" #include "camodocal/sparse_graph/Transform.h" #include "camodocal/gpl/EigenQuaternionParameterization.h" #include "camodocal/gpl/EigenUtils.h" #include "camodocal/camera_models/CostFunctionFactory.h" #include "ceres/ceres.h" namespace camodocal { CameraCalibration::CameraCalibration() : m_boardSize(cv::Size(0,0)) , m_squareSize(0.0f) , m_verbose(false) { } CameraCalibration::CameraCalibration(const Camera::ModelType modelType, const std::string& cameraName, const cv::Size& imageSize, const cv::Size& boardSize, float squareSize) : m_boardSize(boardSize) , m_squareSize(squareSize) , m_verbose(false) { m_camera = CameraFactory::instance()->generateCamera(modelType, cameraName, imageSize); } void CameraCalibration::clear(void) { m_imagePoints.clear(); m_scenePoints.clear(); } void CameraCalibration::addChessboardData(const std::vector& corners) { m_imagePoints.push_back(corners); std::vector scenePointsInView; for (int i = 0; i < m_boardSize.height; ++i) { for (int j = 0; j < m_boardSize.width; ++j) { scenePointsInView.push_back(cv::Point3f(i * m_squareSize, j * m_squareSize, 0.0)); } } m_scenePoints.push_back(scenePointsInView); } bool CameraCalibration::calibrate(void) { int imageCount = m_imagePoints.size(); // compute intrinsic camera parameters and extrinsic parameters for each of the views std::vector rvecs; std::vector tvecs; bool ret = calibrateHelper(m_camera, rvecs, tvecs); m_cameraPoses = cv::Mat(imageCount, 6, CV_64F); for (int i = 0; i < imageCount; ++i) { m_cameraPoses.at(i,0) = rvecs.at(i).at(0); m_cameraPoses.at(i,1) = rvecs.at(i).at(1); m_cameraPoses.at(i,2) = rvecs.at(i).at(2); m_cameraPoses.at(i,3) = tvecs.at(i).at(0); m_cameraPoses.at(i,4) = tvecs.at(i).at(1); m_cameraPoses.at(i,5) = tvecs.at(i).at(2); } // Compute measurement covariance. std::vector > errVec(m_imagePoints.size()); Eigen::Vector2d errSum = Eigen::Vector2d::Zero(); size_t errCount = 0; for (size_t i = 0; i < m_imagePoints.size(); ++i) { std::vector estImagePoints; m_camera->projectPoints(m_scenePoints.at(i), rvecs.at(i), tvecs.at(i), estImagePoints); for (size_t j = 0; j < m_imagePoints.at(i).size(); ++j) { cv::Point2f pObs = m_imagePoints.at(i).at(j); cv::Point2f pEst = estImagePoints.at(j); cv::Point2f err = pObs - pEst; errVec.at(i).push_back(err); errSum += Eigen::Vector2d(err.x, err.y); } errCount += m_imagePoints.at(i).size(); } Eigen::Vector2d errMean = errSum / static_cast(errCount); Eigen::Matrix2d measurementCovariance = Eigen::Matrix2d::Zero(); for (size_t i = 0; i < errVec.size(); ++i) { for (size_t j = 0; j < errVec.at(i).size(); ++j) { cv::Point2f err = errVec.at(i).at(j); double d0 = err.x - errMean(0); double d1 = err.y - errMean(1); measurementCovariance(0,0) += d0 * d0; measurementCovariance(0,1) += d0 * d1; measurementCovariance(1,1) += d1 * d1; } } measurementCovariance /= static_cast(errCount); measurementCovariance(1,0) = measurementCovariance(0,1); m_measurementCovariance = measurementCovariance; return ret; } int CameraCalibration::sampleCount(void) const { return m_imagePoints.size(); } std::vector >& CameraCalibration::imagePoints(void) { return m_imagePoints; } const std::vector >& CameraCalibration::imagePoints(void) const { return m_imagePoints; } std::vector >& CameraCalibration::scenePoints(void) { return m_scenePoints; } const std::vector >& CameraCalibration::scenePoints(void) const { return m_scenePoints; } CameraPtr& CameraCalibration::camera(void) { return m_camera; } const CameraConstPtr CameraCalibration::camera(void) const { return m_camera; } Eigen::Matrix2d& CameraCalibration::measurementCovariance(void) { return m_measurementCovariance; } const Eigen::Matrix2d& CameraCalibration::measurementCovariance(void) const { return m_measurementCovariance; } cv::Mat& CameraCalibration::cameraPoses(void) { return m_cameraPoses; } const cv::Mat& CameraCalibration::cameraPoses(void) const { return m_cameraPoses; } void CameraCalibration::drawResults(std::vector& images) const { std::vector rvecs, tvecs; for (size_t i = 0; i < images.size(); ++i) { cv::Mat rvec(3, 1, CV_64F); rvec.at(0) = m_cameraPoses.at(i,0); rvec.at(1) = m_cameraPoses.at(i,1); rvec.at(2) = m_cameraPoses.at(i,2); cv::Mat tvec(3, 1, CV_64F); tvec.at(0) = m_cameraPoses.at(i,3); tvec.at(1) = m_cameraPoses.at(i,4); tvec.at(2) = m_cameraPoses.at(i,5); rvecs.push_back(rvec); tvecs.push_back(tvec); } int drawShiftBits = 4; int drawMultiplier = 1 << drawShiftBits; cv::Scalar green(0, 255, 0); cv::Scalar red(0, 0, 255); for (size_t i = 0; i < images.size(); ++i) { cv::Mat& image = images.at(i); if (image.channels() == 1) { cv::cvtColor(image, image, CV_GRAY2RGB); } std::vector estImagePoints; m_camera->projectPoints(m_scenePoints.at(i), rvecs.at(i), tvecs.at(i), estImagePoints); float errorSum = 0.0f; float errorMax = std::numeric_limits::min(); for (size_t j = 0; j < m_imagePoints.at(i).size(); ++j) { cv::Point2f pObs = m_imagePoints.at(i).at(j); cv::Point2f pEst = estImagePoints.at(j); cv::circle(image, cv::Point(cvRound(pObs.x * drawMultiplier), cvRound(pObs.y * drawMultiplier)), 5, green, 2, CV_AA, drawShiftBits); cv::circle(image, cv::Point(cvRound(pEst.x * drawMultiplier), cvRound(pEst.y * drawMultiplier)), 5, red, 2, CV_AA, drawShiftBits); float error = cv::norm(pObs - pEst); errorSum += error; if (error > errorMax) { errorMax = error; } } std::ostringstream oss; oss << "Reprojection error: avg = " << errorSum / m_imagePoints.at(i).size() << " max = " << errorMax; cv::putText(image, oss.str(), cv::Point(10, image.rows - 10), cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(255, 255, 255), 1, CV_AA); } } void CameraCalibration::writeParams(const std::string& filename) const { m_camera->writeParametersToYamlFile(filename); } bool CameraCalibration::writeChessboardData(const std::string& filename) const { std::ofstream ofs(filename.c_str(), std::ios::out | std::ios::binary); if (!ofs.is_open()) { return false; } writeData(ofs, m_boardSize.width); writeData(ofs, m_boardSize.height); writeData(ofs, m_squareSize); writeData(ofs, m_measurementCovariance(0,0)); writeData(ofs, m_measurementCovariance(0,1)); writeData(ofs, m_measurementCovariance(1,0)); writeData(ofs, m_measurementCovariance(1,1)); writeData(ofs, m_cameraPoses.rows); writeData(ofs, m_cameraPoses.cols); writeData(ofs, m_cameraPoses.type()); for (int i = 0; i < m_cameraPoses.rows; ++i) { for (int j = 0; j < m_cameraPoses.cols; ++j) { writeData(ofs, m_cameraPoses.at(i,j)); } } writeData(ofs, m_imagePoints.size()); for (size_t i = 0; i < m_imagePoints.size(); ++i) { writeData(ofs, m_imagePoints.at(i).size()); for (size_t j = 0; j < m_imagePoints.at(i).size(); ++j) { const cv::Point2f& ipt = m_imagePoints.at(i).at(j); writeData(ofs, ipt.x); writeData(ofs, ipt.y); } } writeData(ofs, m_scenePoints.size()); for (size_t i = 0; i < m_scenePoints.size(); ++i) { writeData(ofs, m_scenePoints.at(i).size()); for (size_t j = 0; j < m_scenePoints.at(i).size(); ++j) { const cv::Point3f& spt = m_scenePoints.at(i).at(j); writeData(ofs, spt.x); writeData(ofs, spt.y); writeData(ofs, spt.z); } } return true; } bool CameraCalibration::readChessboardData(const std::string& filename) { std::ifstream ifs(filename.c_str(), std::ios::in | std::ios::binary); if (!ifs.is_open()) { return false; } readData(ifs, m_boardSize.width); readData(ifs, m_boardSize.height); readData(ifs, m_squareSize); readData(ifs, m_measurementCovariance(0,0)); readData(ifs, m_measurementCovariance(0,1)); readData(ifs, m_measurementCovariance(1,0)); readData(ifs, m_measurementCovariance(1,1)); int rows, cols, type; readData(ifs, rows); readData(ifs, cols); readData(ifs, type); m_cameraPoses = cv::Mat(rows, cols, type); for (int i = 0; i < m_cameraPoses.rows; ++i) { for (int j = 0; j < m_cameraPoses.cols; ++j) { readData(ifs, m_cameraPoses.at(i,j)); } } size_t nImagePointSets; readData(ifs, nImagePointSets); m_imagePoints.clear(); m_imagePoints.resize(nImagePointSets); for (size_t i = 0; i < m_imagePoints.size(); ++i) { size_t nImagePoints; readData(ifs, nImagePoints); m_imagePoints.at(i).resize(nImagePoints); for (size_t j = 0; j < m_imagePoints.at(i).size(); ++j) { cv::Point2f& ipt = m_imagePoints.at(i).at(j); readData(ifs, ipt.x); readData(ifs, ipt.y); } } size_t nScenePointSets; readData(ifs, nScenePointSets); m_scenePoints.clear(); m_scenePoints.resize(nScenePointSets); for (size_t i = 0; i < m_scenePoints.size(); ++i) { size_t nScenePoints; readData(ifs, nScenePoints); m_scenePoints.at(i).resize(nScenePoints); for (size_t j = 0; j < m_scenePoints.at(i).size(); ++j) { cv::Point3f& spt = m_scenePoints.at(i).at(j); readData(ifs, spt.x); readData(ifs, spt.y); readData(ifs, spt.z); } } return true; } void CameraCalibration::setVerbose(bool verbose) { m_verbose = verbose; } bool CameraCalibration::calibrateHelper(CameraPtr& camera, std::vector& rvecs, std::vector& tvecs) const { rvecs.assign(m_scenePoints.size(), cv::Mat()); tvecs.assign(m_scenePoints.size(), cv::Mat()); // STEP 1: Estimate intrinsics camera->estimateIntrinsics(m_boardSize, m_scenePoints, m_imagePoints); // STEP 2: Estimate extrinsics for (size_t i = 0; i < m_scenePoints.size(); ++i) { camera->estimateExtrinsics(m_scenePoints.at(i), m_imagePoints.at(i), rvecs.at(i), tvecs.at(i)); } if (m_verbose) { std::cout << "[" << camera->cameraName() << "] " << "# INFO: " << "Initial reprojection error: " << std::fixed << std::setprecision(3) << camera->reprojectionError(m_scenePoints, m_imagePoints, rvecs, tvecs) << " pixels" << std::endl; } // STEP 3: optimization using ceres optimize(camera, rvecs, tvecs); if (m_verbose) { double err = camera->reprojectionError(m_scenePoints, m_imagePoints, rvecs, tvecs); std::cout << "[" << camera->cameraName() << "] " << "# INFO: Final reprojection error: " << err << " pixels" << std::endl; std::cout << "[" << camera->cameraName() << "] " << "# INFO: " << camera->parametersToString() << std::endl; } return true; } void CameraCalibration::optimize(CameraPtr& camera, std::vector& rvecs, std::vector& tvecs) const { // Use ceres to do optimization ceres::Problem problem; std::vector > transformVec(rvecs.size()); for (size_t i = 0; i < rvecs.size(); ++i) { Eigen::Vector3d rvec; cv::cv2eigen(rvecs.at(i), rvec); transformVec.at(i).rotation() = Eigen::AngleAxisd(rvec.norm(), rvec.normalized()); transformVec.at(i).translation() << tvecs[i].at(0), tvecs[i].at(1), tvecs[i].at(2); } std::vector intrinsicCameraParams; m_camera->writeParameters(intrinsicCameraParams); // create residuals for each observation for (size_t i = 0; i < m_imagePoints.size(); ++i) { for (size_t j = 0; j < m_imagePoints.at(i).size(); ++j) { const cv::Point3f& spt = m_scenePoints.at(i).at(j); const cv::Point2f& ipt = m_imagePoints.at(i).at(j); ceres::CostFunction* costFunction = CostFunctionFactory::instance()->generateCostFunction(camera, Eigen::Vector3d(spt.x, spt.y, spt.z), Eigen::Vector2d(ipt.x, ipt.y), CAMERA_INTRINSICS | CAMERA_POSE); ceres::LossFunction* lossFunction = new ceres::CauchyLoss(1.0); problem.AddResidualBlock(costFunction, lossFunction, intrinsicCameraParams.data(), transformVec.at(i).rotationData(), transformVec.at(i).translationData()); } ceres::LocalParameterization* quaternionParameterization = new EigenQuaternionParameterization; problem.SetParameterization(transformVec.at(i).rotationData(), quaternionParameterization); } std::cout << "begin ceres" << std::endl; ceres::Solver::Options options; options.max_num_iterations = 1000; options.num_threads = 1; if (m_verbose) { options.minimizer_progress_to_stdout = true; } ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); std::cout << "end ceres" << std::endl; if (m_verbose) { std::cout << summary.FullReport() << std::endl; } camera->readParameters(intrinsicCameraParams); for (size_t i = 0; i < rvecs.size(); ++i) { Eigen::AngleAxisd aa(transformVec.at(i).rotation()); Eigen::Vector3d rvec = aa.angle() * aa.axis(); cv::eigen2cv(rvec, rvecs.at(i)); cv::Mat& tvec = tvecs.at(i); tvec.at(0) = transformVec.at(i).translation()(0); tvec.at(1) = transformVec.at(i).translation()(1); tvec.at(2) = transformVec.at(i).translation()(2); } } template void CameraCalibration::readData(std::ifstream& ifs, T& data) const { char* buffer = new char[sizeof(T)]; ifs.read(buffer, sizeof(T)); data = *(reinterpret_cast(buffer)); delete[] buffer; } template void CameraCalibration::writeData(std::ofstream& ofs, T data) const { char* pData = reinterpret_cast(&data); ofs.write(pData, sizeof(T)); } } ================================================ FILE: src/utils/camodocal/src/camera_models/Camera.cc ================================================ #include "camodocal/camera_models/Camera.h" #include "camodocal/camera_models/ScaramuzzaCamera.h" #include namespace camodocal { Camera::Parameters::Parameters(ModelType modelType) : m_modelType(modelType) , m_imageWidth(0) , m_imageHeight(0) { switch (modelType) { case KANNALA_BRANDT: m_nIntrinsics = 8; break; case PINHOLE: m_nIntrinsics = 8; break; case SCARAMUZZA: m_nIntrinsics = SCARAMUZZA_CAMERA_NUM_PARAMS; break; case MEI: default: m_nIntrinsics = 9; } } Camera::Parameters::Parameters(ModelType modelType, const std::string& cameraName, int w, int h) : m_modelType(modelType) , m_cameraName(cameraName) , m_imageWidth(w) , m_imageHeight(h) { switch (modelType) { case KANNALA_BRANDT: m_nIntrinsics = 8; break; case PINHOLE: m_nIntrinsics = 8; break; case SCARAMUZZA: m_nIntrinsics = SCARAMUZZA_CAMERA_NUM_PARAMS; break; case MEI: default: m_nIntrinsics = 9; } } Camera::ModelType& Camera::Parameters::modelType(void) { return m_modelType; } std::string& Camera::Parameters::cameraName(void) { return m_cameraName; } int& Camera::Parameters::imageWidth(void) { return m_imageWidth; } int& Camera::Parameters::imageHeight(void) { return m_imageHeight; } Camera::ModelType Camera::Parameters::modelType(void) const { return m_modelType; } const std::string& Camera::Parameters::cameraName(void) const { return m_cameraName; } int Camera::Parameters::imageWidth(void) const { return m_imageWidth; } int Camera::Parameters::imageHeight(void) const { return m_imageHeight; } int Camera::Parameters::nIntrinsics(void) const { return m_nIntrinsics; } cv::Mat& Camera::mask(void) { return m_mask; } const cv::Mat& Camera::mask(void) const { return m_mask; } void Camera::estimateExtrinsics(const std::vector& objectPoints, const std::vector& imagePoints, cv::Mat& rvec, cv::Mat& tvec) const { std::vector Ms(imagePoints.size()); for (size_t i = 0; i < Ms.size(); ++i) { Eigen::Vector3d P; liftProjective(Eigen::Vector2d(imagePoints.at(i).x, imagePoints.at(i).y), P); P /= P(2); Ms.at(i).x = P(0); Ms.at(i).y = P(1); } // assume unit focal length, zero principal point, and zero distortion cv::solvePnP(objectPoints, Ms, cv::Mat::eye(3, 3, CV_64F), cv::noArray(), rvec, tvec); } double Camera::reprojectionDist(const Eigen::Vector3d& P1, const Eigen::Vector3d& P2) const { Eigen::Vector2d p1, p2; spaceToPlane(P1, p1); spaceToPlane(P2, p2); return (p1 - p2).norm(); } double Camera::reprojectionError(const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints, const std::vector& rvecs, const std::vector& tvecs, cv::OutputArray _perViewErrors) const { int imageCount = objectPoints.size(); size_t pointsSoFar = 0; double totalErr = 0.0; bool computePerViewErrors = _perViewErrors.needed(); cv::Mat perViewErrors; if (computePerViewErrors) { _perViewErrors.create(imageCount, 1, CV_64F); perViewErrors = _perViewErrors.getMat(); } for (int i = 0; i < imageCount; ++i) { size_t pointCount = imagePoints.at(i).size(); pointsSoFar += pointCount; std::vector estImagePoints; projectPoints(objectPoints.at(i), rvecs.at(i), tvecs.at(i), estImagePoints); double err = 0.0; for (size_t j = 0; j < imagePoints.at(i).size(); ++j) { err += cv::norm(imagePoints.at(i).at(j) - estImagePoints.at(j)); } if (computePerViewErrors) { perViewErrors.at(i) = err / pointCount; } totalErr += err; } return totalErr / pointsSoFar; } double Camera::reprojectionError(const Eigen::Vector3d& P, const Eigen::Quaterniond& camera_q, const Eigen::Vector3d& camera_t, const Eigen::Vector2d& observed_p) const { Eigen::Vector3d P_cam = camera_q.toRotationMatrix() * P + camera_t; Eigen::Vector2d p; spaceToPlane(P_cam, p); return (p - observed_p).norm(); } void Camera::projectPoints(const std::vector& objectPoints, const cv::Mat& rvec, const cv::Mat& tvec, std::vector& imagePoints) const { // project 3D object points to the image plane imagePoints.reserve(objectPoints.size()); //double cv::Mat R0; cv::Rodrigues(rvec, R0); Eigen::MatrixXd R(3,3); R << R0.at(0,0), R0.at(0,1), R0.at(0,2), R0.at(1,0), R0.at(1,1), R0.at(1,2), R0.at(2,0), R0.at(2,1), R0.at(2,2); Eigen::Vector3d t; t << tvec.at(0), tvec.at(1), tvec.at(2); for (size_t i = 0; i < objectPoints.size(); ++i) { const cv::Point3f& objectPoint = objectPoints.at(i); // Rotate and translate Eigen::Vector3d P; P << objectPoint.x, objectPoint.y, objectPoint.z; P = R * P + t; Eigen::Vector2d p; spaceToPlane(P, p); imagePoints.push_back(cv::Point2f(p(0), p(1))); } } } ================================================ FILE: src/utils/camodocal/src/camera_models/CameraFactory.cc ================================================ #include "camodocal/camera_models/CameraFactory.h" #include #include "camodocal/camera_models/CataCamera.h" #include "camodocal/camera_models/EquidistantCamera.h" #include "camodocal/camera_models/PinholeCamera.h" #include "camodocal/camera_models/ScaramuzzaCamera.h" #include "ceres/ceres.h" namespace camodocal { boost::shared_ptr CameraFactory::m_instance; CameraFactory::CameraFactory() { } boost::shared_ptr CameraFactory::instance(void) { if (m_instance.get() == 0) { m_instance.reset(new CameraFactory); } return m_instance; } CameraPtr CameraFactory::generateCamera(Camera::ModelType modelType, const std::string& cameraName, cv::Size imageSize) const { switch (modelType) { case Camera::KANNALA_BRANDT: { EquidistantCameraPtr camera(new EquidistantCamera); EquidistantCamera::Parameters params = camera->getParameters(); params.cameraName() = cameraName; params.imageWidth() = imageSize.width; params.imageHeight() = imageSize.height; camera->setParameters(params); return camera; } case Camera::PINHOLE: { PinholeCameraPtr camera(new PinholeCamera); PinholeCamera::Parameters params = camera->getParameters(); params.cameraName() = cameraName; params.imageWidth() = imageSize.width; params.imageHeight() = imageSize.height; camera->setParameters(params); return camera; } case Camera::SCARAMUZZA: { OCAMCameraPtr camera(new OCAMCamera); OCAMCamera::Parameters params = camera->getParameters(); params.cameraName() = cameraName; params.imageWidth() = imageSize.width; params.imageHeight() = imageSize.height; camera->setParameters(params); return camera; } case Camera::MEI: default: { CataCameraPtr camera(new CataCamera); CataCamera::Parameters params = camera->getParameters(); params.cameraName() = cameraName; params.imageWidth() = imageSize.width; params.imageHeight() = imageSize.height; camera->setParameters(params); return camera; } } } CameraPtr CameraFactory::generateCameraFromYamlFile(const std::string& filename) { cv::FileStorage fs(filename, cv::FileStorage::READ); if (!fs.isOpened()) { return CameraPtr(); } Camera::ModelType modelType = Camera::MEI; if (!fs["model_type"].isNone()) { std::string sModelType; fs["model_type"] >> sModelType; if (boost::iequals(sModelType, "kannala_brandt")) { modelType = Camera::KANNALA_BRANDT; } else if (boost::iequals(sModelType, "mei")) { modelType = Camera::MEI; } else if (boost::iequals(sModelType, "scaramuzza")) { modelType = Camera::SCARAMUZZA; } else if (boost::iequals(sModelType, "pinhole")) { modelType = Camera::PINHOLE; } else { std::cerr << "# ERROR: Unknown camera model: " << sModelType << std::endl; return CameraPtr(); } } switch (modelType) { case Camera::KANNALA_BRANDT: { EquidistantCameraPtr camera(new EquidistantCamera); EquidistantCamera::Parameters params = camera->getParameters(); params.readFromYamlFile(filename); camera->setParameters(params); return camera; } case Camera::PINHOLE: { PinholeCameraPtr camera(new PinholeCamera); PinholeCamera::Parameters params = camera->getParameters(); params.readFromYamlFile(filename); camera->setParameters(params); return camera; } case Camera::SCARAMUZZA: { OCAMCameraPtr camera(new OCAMCamera); OCAMCamera::Parameters params = camera->getParameters(); params.readFromYamlFile(filename); camera->setParameters(params); return camera; } case Camera::MEI: default: { CataCameraPtr camera(new CataCamera); CataCamera::Parameters params = camera->getParameters(); params.readFromYamlFile(filename); camera->setParameters(params); return camera; } } return CameraPtr(); } } ================================================ FILE: src/utils/camodocal/src/camera_models/CataCamera.cc ================================================ #include "camodocal/camera_models/CataCamera.h" #include #include #include #include #include #include #include #include #include "camodocal/gpl/gpl.h" namespace camodocal { CataCamera::Parameters::Parameters() : Camera::Parameters(MEI) , m_xi(0.0) , m_k1(0.0) , m_k2(0.0) , m_p1(0.0) , m_p2(0.0) , m_gamma1(0.0) , m_gamma2(0.0) , m_u0(0.0) , m_v0(0.0) { } CataCamera::Parameters::Parameters(const std::string& cameraName, int w, int h, double xi, double k1, double k2, double p1, double p2, double gamma1, double gamma2, double u0, double v0) : Camera::Parameters(MEI, cameraName, w, h) , m_xi(xi) , m_k1(k1) , m_k2(k2) , m_p1(p1) , m_p2(p2) , m_gamma1(gamma1) , m_gamma2(gamma2) , m_u0(u0) , m_v0(v0) { } double& CataCamera::Parameters::xi(void) { return m_xi; } double& CataCamera::Parameters::k1(void) { return m_k1; } double& CataCamera::Parameters::k2(void) { return m_k2; } double& CataCamera::Parameters::p1(void) { return m_p1; } double& CataCamera::Parameters::p2(void) { return m_p2; } double& CataCamera::Parameters::gamma1(void) { return m_gamma1; } double& CataCamera::Parameters::gamma2(void) { return m_gamma2; } double& CataCamera::Parameters::u0(void) { return m_u0; } double& CataCamera::Parameters::v0(void) { return m_v0; } double CataCamera::Parameters::xi(void) const { return m_xi; } double CataCamera::Parameters::k1(void) const { return m_k1; } double CataCamera::Parameters::k2(void) const { return m_k2; } double CataCamera::Parameters::p1(void) const { return m_p1; } double CataCamera::Parameters::p2(void) const { return m_p2; } double CataCamera::Parameters::gamma1(void) const { return m_gamma1; } double CataCamera::Parameters::gamma2(void) const { return m_gamma2; } double CataCamera::Parameters::u0(void) const { return m_u0; } double CataCamera::Parameters::v0(void) const { return m_v0; } bool CataCamera::Parameters::readFromYamlFile(const std::string& filename) { cv::FileStorage fs(filename, cv::FileStorage::READ); if (!fs.isOpened()) { return false; } if (!fs["model_type"].isNone()) { std::string sModelType; fs["model_type"] >> sModelType; if (sModelType.compare("MEI") != 0) { return false; } } m_modelType = MEI; fs["camera_name"] >> m_cameraName; m_imageWidth = static_cast(fs["image_width"]); m_imageHeight = static_cast(fs["image_height"]); cv::FileNode n = fs["mirror_parameters"]; m_xi = static_cast(n["xi"]); n = fs["distortion_parameters"]; m_k1 = static_cast(n["k1"]); m_k2 = static_cast(n["k2"]); m_p1 = static_cast(n["p1"]); m_p2 = static_cast(n["p2"]); n = fs["projection_parameters"]; m_gamma1 = static_cast(n["gamma1"]); m_gamma2 = static_cast(n["gamma2"]); m_u0 = static_cast(n["u0"]); m_v0 = static_cast(n["v0"]); return true; } void CataCamera::Parameters::writeToYamlFile(const std::string& filename) const { cv::FileStorage fs(filename, cv::FileStorage::WRITE); fs << "model_type" << "MEI"; fs << "camera_name" << m_cameraName; fs << "image_width" << m_imageWidth; fs << "image_height" << m_imageHeight; // mirror: xi fs << "mirror_parameters"; fs << "{" << "xi" << m_xi << "}"; // radial distortion: k1, k2 // tangential distortion: p1, p2 fs << "distortion_parameters"; fs << "{" << "k1" << m_k1 << "k2" << m_k2 << "p1" << m_p1 << "p2" << m_p2 << "}"; // projection: gamma1, gamma2, u0, v0 fs << "projection_parameters"; fs << "{" << "gamma1" << m_gamma1 << "gamma2" << m_gamma2 << "u0" << m_u0 << "v0" << m_v0 << "}"; fs.release(); } CataCamera::Parameters& CataCamera::Parameters::operator=(const CataCamera::Parameters& other) { if (this != &other) { m_modelType = other.m_modelType; m_cameraName = other.m_cameraName; m_imageWidth = other.m_imageWidth; m_imageHeight = other.m_imageHeight; m_xi = other.m_xi; m_k1 = other.m_k1; m_k2 = other.m_k2; m_p1 = other.m_p1; m_p2 = other.m_p2; m_gamma1 = other.m_gamma1; m_gamma2 = other.m_gamma2; m_u0 = other.m_u0; m_v0 = other.m_v0; } return *this; } std::ostream& operator<< (std::ostream& out, const CataCamera::Parameters& params) { out << "Camera Parameters:" << std::endl; out << " model_type " << "MEI" << std::endl; out << " camera_name " << params.m_cameraName << std::endl; out << " image_width " << params.m_imageWidth << std::endl; out << " image_height " << params.m_imageHeight << std::endl; out << "Mirror Parameters" << std::endl; out << std::fixed << std::setprecision(10); out << " xi " << params.m_xi << std::endl; // radial distortion: k1, k2 // tangential distortion: p1, p2 out << "Distortion Parameters" << std::endl; out << " k1 " << params.m_k1 << std::endl << " k2 " << params.m_k2 << std::endl << " p1 " << params.m_p1 << std::endl << " p2 " << params.m_p2 << std::endl; // projection: gamma1, gamma2, u0, v0 out << "Projection Parameters" << std::endl; out << " gamma1 " << params.m_gamma1 << std::endl << " gamma2 " << params.m_gamma2 << std::endl << " u0 " << params.m_u0 << std::endl << " v0 " << params.m_v0 << std::endl; return out; } CataCamera::CataCamera() : m_inv_K11(1.0) , m_inv_K13(0.0) , m_inv_K22(1.0) , m_inv_K23(0.0) , m_noDistortion(true) { } CataCamera::CataCamera(const std::string& cameraName, int imageWidth, int imageHeight, double xi, double k1, double k2, double p1, double p2, double gamma1, double gamma2, double u0, double v0) : mParameters(cameraName, imageWidth, imageHeight, xi, k1, k2, p1, p2, gamma1, gamma2, u0, v0) { if ((mParameters.k1() == 0.0) && (mParameters.k2() == 0.0) && (mParameters.p1() == 0.0) && (mParameters.p2() == 0.0)) { m_noDistortion = true; } else { m_noDistortion = false; } // Inverse camera projection matrix parameters m_inv_K11 = 1.0 / mParameters.gamma1(); m_inv_K13 = -mParameters.u0() / mParameters.gamma1(); m_inv_K22 = 1.0 / mParameters.gamma2(); m_inv_K23 = -mParameters.v0() / mParameters.gamma2(); } CataCamera::CataCamera(const CataCamera::Parameters& params) : mParameters(params) { if ((mParameters.k1() == 0.0) && (mParameters.k2() == 0.0) && (mParameters.p1() == 0.0) && (mParameters.p2() == 0.0)) { m_noDistortion = true; } else { m_noDistortion = false; } // Inverse camera projection matrix parameters m_inv_K11 = 1.0 / mParameters.gamma1(); m_inv_K13 = -mParameters.u0() / mParameters.gamma1(); m_inv_K22 = 1.0 / mParameters.gamma2(); m_inv_K23 = -mParameters.v0() / mParameters.gamma2(); } Camera::ModelType CataCamera::modelType(void) const { return mParameters.modelType(); } const std::string& CataCamera::cameraName(void) const { return mParameters.cameraName(); } int CataCamera::imageWidth(void) const { return mParameters.imageWidth(); } int CataCamera::imageHeight(void) const { return mParameters.imageHeight(); } void CataCamera::estimateIntrinsics(const cv::Size& boardSize, const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints) { Parameters params = getParameters(); double u0 = params.imageWidth() / 2.0; double v0 = params.imageHeight() / 2.0; double gamma0 = 0.0; double minReprojErr = std::numeric_limits::max(); std::vector rvecs, tvecs; rvecs.assign(objectPoints.size(), cv::Mat()); tvecs.assign(objectPoints.size(), cv::Mat()); params.xi() = 1.0; params.k1() = 0.0; params.k2() = 0.0; params.p1() = 0.0; params.p2() = 0.0; params.u0() = u0; params.v0() = v0; // Initialize gamma (focal length) // Use non-radial line image and xi = 1 for (size_t i = 0; i < imagePoints.size(); ++i) { for (int r = 0; r < boardSize.height; ++r) { cv::Mat P(boardSize.width, 4, CV_64F); for (int c = 0; c < boardSize.width; ++c) { const cv::Point2f& imagePoint = imagePoints.at(i).at(r * boardSize.width + c); double u = imagePoint.x - u0; double v = imagePoint.y - v0; P.at(c, 0) = u; P.at(c, 1) = v; P.at(c, 2) = 0.5; P.at(c, 3) = -0.5 * (square(u) + square(v)); } cv::Mat C; cv::SVD::solveZ(P, C); double t = square(C.at(0)) + square(C.at(1)) + C.at(2) * C.at(3); if (t < 0.0) { continue; } // check that line image is not radial double d = sqrt(1.0 / t); double nx = C.at(0) * d; double ny = C.at(1) * d; if (hypot(nx, ny) > 0.95) { continue; } double gamma = sqrt(C.at(2) / C.at(3)); params.gamma1() = gamma; params.gamma2() = gamma; setParameters(params); for (size_t j = 0; j < objectPoints.size(); ++j) { estimateExtrinsics(objectPoints.at(j), imagePoints.at(j), rvecs.at(j), tvecs.at(j)); } double reprojErr = reprojectionError(objectPoints, imagePoints, rvecs, tvecs, cv::noArray()); if (reprojErr < minReprojErr) { minReprojErr = reprojErr; gamma0 = gamma; } } } if (gamma0 <= 0.0 && minReprojErr >= std::numeric_limits::max()) { std::cout << "[" << params.cameraName() << "] " << "# INFO: CataCamera model fails with given data. " << std::endl; return; } params.gamma1() = gamma0; params.gamma2() = gamma0; setParameters(params); } /** * \brief Lifts a point from the image plane to the unit sphere * * \param p image coordinates * \param P coordinates of the point on the sphere */ void CataCamera::liftSphere(const Eigen::Vector2d& p, Eigen::Vector3d& P) const { double mx_d, my_d,mx2_d, mxy_d, my2_d, mx_u, my_u; double rho2_d, rho4_d, radDist_d, Dx_d, Dy_d, inv_denom_d; double lambda; // Lift points to normalised plane mx_d = m_inv_K11 * p(0) + m_inv_K13; my_d = m_inv_K22 * p(1) + m_inv_K23; if (m_noDistortion) { mx_u = mx_d; my_u = my_d; } else { // Apply inverse distortion model if (0) { double k1 = mParameters.k1(); double k2 = mParameters.k2(); double p1 = mParameters.p1(); double p2 = mParameters.p2(); // Inverse distortion model // proposed by Heikkila mx2_d = mx_d*mx_d; my2_d = my_d*my_d; mxy_d = mx_d*my_d; rho2_d = mx2_d+my2_d; rho4_d = rho2_d*rho2_d; radDist_d = k1*rho2_d+k2*rho4_d; Dx_d = mx_d*radDist_d + p2*(rho2_d+2*mx2_d) + 2*p1*mxy_d; Dy_d = my_d*radDist_d + p1*(rho2_d+2*my2_d) + 2*p2*mxy_d; inv_denom_d = 1/(1+4*k1*rho2_d+6*k2*rho4_d+8*p1*my_d+8*p2*mx_d); mx_u = mx_d - inv_denom_d*Dx_d; my_u = my_d - inv_denom_d*Dy_d; } else { // Recursive distortion model int n = 6; Eigen::Vector2d d_u; distortion(Eigen::Vector2d(mx_d, my_d), d_u); // Approximate value mx_u = mx_d - d_u(0); my_u = my_d - d_u(1); for (int i = 1; i < n; ++i) { distortion(Eigen::Vector2d(mx_u, my_u), d_u); mx_u = mx_d - d_u(0); my_u = my_d - d_u(1); } } } // Lift normalised points to the sphere (inv_hslash) double xi = mParameters.xi(); if (xi == 1.0) { lambda = 2.0 / (mx_u * mx_u + my_u * my_u + 1.0); P << lambda * mx_u, lambda * my_u, lambda - 1.0; } else { lambda = (xi + sqrt(1.0 + (1.0 - xi * xi) * (mx_u * mx_u + my_u * my_u))) / (1.0 + mx_u * mx_u + my_u * my_u); P << lambda * mx_u, lambda * my_u, lambda - xi; } } /** * \brief Lifts a point from the image plane to its projective ray * * \param p image coordinates * \param P coordinates of the projective ray */ void CataCamera::liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const { double mx_d, my_d,mx2_d, mxy_d, my2_d, mx_u, my_u; double rho2_d, rho4_d, radDist_d, Dx_d, Dy_d, inv_denom_d; //double lambda; // Lift points to normalised plane mx_d = m_inv_K11 * p(0) + m_inv_K13; my_d = m_inv_K22 * p(1) + m_inv_K23; if (m_noDistortion) { mx_u = mx_d; my_u = my_d; } else { if (0) { double k1 = mParameters.k1(); double k2 = mParameters.k2(); double p1 = mParameters.p1(); double p2 = mParameters.p2(); // Apply inverse distortion model // proposed by Heikkila mx2_d = mx_d*mx_d; my2_d = my_d*my_d; mxy_d = mx_d*my_d; rho2_d = mx2_d+my2_d; rho4_d = rho2_d*rho2_d; radDist_d = k1*rho2_d+k2*rho4_d; Dx_d = mx_d*radDist_d + p2*(rho2_d+2*mx2_d) + 2*p1*mxy_d; Dy_d = my_d*radDist_d + p1*(rho2_d+2*my2_d) + 2*p2*mxy_d; inv_denom_d = 1/(1+4*k1*rho2_d+6*k2*rho4_d+8*p1*my_d+8*p2*mx_d); mx_u = mx_d - inv_denom_d*Dx_d; my_u = my_d - inv_denom_d*Dy_d; } else { // Recursive distortion model int n = 8; Eigen::Vector2d d_u; distortion(Eigen::Vector2d(mx_d, my_d), d_u); // Approximate value mx_u = mx_d - d_u(0); my_u = my_d - d_u(1); for (int i = 1; i < n; ++i) { distortion(Eigen::Vector2d(mx_u, my_u), d_u); mx_u = mx_d - d_u(0); my_u = my_d - d_u(1); } } } // Obtain a projective ray double xi = mParameters.xi(); if (xi == 1.0) { P << mx_u, my_u, (1.0 - mx_u * mx_u - my_u * my_u) / 2.0; } else { // Reuse variable rho2_d = mx_u * mx_u + my_u * my_u; P << mx_u, my_u, 1.0 - xi * (rho2_d + 1.0) / (xi + sqrt(1.0 + (1.0 - xi * xi) * rho2_d)); } } /** * \brief Project a 3D point (\a x,\a y,\a z) to the image plane in (\a u,\a v) * * \param P 3D point coordinates * \param p return value, contains the image point coordinates */ void CataCamera::spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p) const { Eigen::Vector2d p_u, p_d; // Project points to the normalised plane double z = P(2) + mParameters.xi() * P.norm(); p_u << P(0) / z, P(1) / z; if (m_noDistortion) { p_d = p_u; } else { // Apply distortion Eigen::Vector2d d_u; distortion(p_u, d_u); p_d = p_u + d_u; } // Apply generalised projection matrix p << mParameters.gamma1() * p_d(0) + mParameters.u0(), mParameters.gamma2() * p_d(1) + mParameters.v0(); } #if 0 /** * \brief Project a 3D point to the image plane and calculate Jacobian * * \param P 3D point coordinates * \param p return value, contains the image point coordinates */ void CataCamera::spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p, Eigen::Matrix& J) const { double xi = mParameters.xi(); Eigen::Vector2d p_u, p_d; double norm, inv_denom; double dxdmx, dydmx, dxdmy, dydmy; norm = P.norm(); // Project points to the normalised plane inv_denom = 1.0 / (P(2) + xi * norm); p_u << inv_denom * P(0), inv_denom * P(1); // Calculate jacobian inv_denom = inv_denom * inv_denom / norm; double dudx = inv_denom * (norm * P(2) + xi * (P(1) * P(1) + P(2) * P(2))); double dvdx = -inv_denom * xi * P(0) * P(1); double dudy = dvdx; double dvdy = inv_denom * (norm * P(2) + xi * (P(0) * P(0) + P(2) * P(2))); inv_denom = inv_denom * (-xi * P(2) - norm); // reuse variable double dudz = P(0) * inv_denom; double dvdz = P(1) * inv_denom; if (m_noDistortion) { p_d = p_u; } else { // Apply distortion Eigen::Vector2d d_u; distortion(p_u, d_u); p_d = p_u + d_u; } double gamma1 = mParameters.gamma1(); double gamma2 = mParameters.gamma2(); // Make the product of the jacobians // and add projection matrix jacobian inv_denom = gamma1 * (dudx * dxdmx + dvdx * dxdmy); // reuse dvdx = gamma2 * (dudx * dydmx + dvdx * dydmy); dudx = inv_denom; inv_denom = gamma1 * (dudy * dxdmx + dvdy * dxdmy); // reuse dvdy = gamma2 * (dudy * dydmx + dvdy * dydmy); dudy = inv_denom; inv_denom = gamma1 * (dudz * dxdmx + dvdz * dxdmy); // reuse dvdz = gamma2 * (dudz * dydmx + dvdz * dydmy); dudz = inv_denom; // Apply generalised projection matrix p << gamma1 * p_d(0) + mParameters.u0(), gamma2 * p_d(1) + mParameters.v0(); J << dudx, dudy, dudz, dvdx, dvdy, dvdz; } #endif /** * \brief Projects an undistorted 2D point p_u to the image plane * * \param p_u 2D point coordinates * \return image point coordinates */ void CataCamera::undistToPlane(const Eigen::Vector2d& p_u, Eigen::Vector2d& p) const { Eigen::Vector2d p_d; if (m_noDistortion) { p_d = p_u; } else { // Apply distortion Eigen::Vector2d d_u; distortion(p_u, d_u); p_d = p_u + d_u; } // Apply generalised projection matrix p << mParameters.gamma1() * p_d(0) + mParameters.u0(), mParameters.gamma2() * p_d(1) + mParameters.v0(); } /** * \brief Apply distortion to input point (from the normalised plane) * * \param p_u undistorted coordinates of point on the normalised plane * \return to obtain the distorted point: p_d = p_u + d_u */ void CataCamera::distortion(const Eigen::Vector2d& p_u, Eigen::Vector2d& d_u) const { double k1 = mParameters.k1(); double k2 = mParameters.k2(); double p1 = mParameters.p1(); double p2 = mParameters.p2(); double mx2_u, my2_u, mxy_u, rho2_u, rad_dist_u; mx2_u = p_u(0) * p_u(0); my2_u = p_u(1) * p_u(1); mxy_u = p_u(0) * p_u(1); rho2_u = mx2_u + my2_u; rad_dist_u = k1 * rho2_u + k2 * rho2_u * rho2_u; d_u << p_u(0) * rad_dist_u + 2.0 * p1 * mxy_u + p2 * (rho2_u + 2.0 * mx2_u), p_u(1) * rad_dist_u + 2.0 * p2 * mxy_u + p1 * (rho2_u + 2.0 * my2_u); } /** * \brief Apply distortion to input point (from the normalised plane) * and calculate Jacobian * * \param p_u undistorted coordinates of point on the normalised plane * \return to obtain the distorted point: p_d = p_u + d_u */ void CataCamera::distortion(const Eigen::Vector2d& p_u, Eigen::Vector2d& d_u, Eigen::Matrix2d& J) const { double k1 = mParameters.k1(); double k2 = mParameters.k2(); double p1 = mParameters.p1(); double p2 = mParameters.p2(); double mx2_u, my2_u, mxy_u, rho2_u, rad_dist_u; mx2_u = p_u(0) * p_u(0); my2_u = p_u(1) * p_u(1); mxy_u = p_u(0) * p_u(1); rho2_u = mx2_u + my2_u; rad_dist_u = k1 * rho2_u + k2 * rho2_u * rho2_u; d_u << p_u(0) * rad_dist_u + 2.0 * p1 * mxy_u + p2 * (rho2_u + 2.0 * mx2_u), p_u(1) * rad_dist_u + 2.0 * p2 * mxy_u + p1 * (rho2_u + 2.0 * my2_u); double dxdmx = 1.0 + rad_dist_u + k1 * 2.0 * mx2_u + k2 * rho2_u * 4.0 * mx2_u + 2.0 * p1 * p_u(1) + 6.0 * p2 * p_u(0); double dydmx = k1 * 2.0 * p_u(0) * p_u(1) + k2 * 4.0 * rho2_u * p_u(0) * p_u(1) + p1 * 2.0 * p_u(0) + 2.0 * p2 * p_u(1); double dxdmy = dydmx; double dydmy = 1.0 + rad_dist_u + k1 * 2.0 * my2_u + k2 * rho2_u * 4.0 * my2_u + 6.0 * p1 * p_u(1) + 2.0 * p2 * p_u(0); J << dxdmx, dxdmy, dydmx, dydmy; } void CataCamera::initUndistortMap(cv::Mat& map1, cv::Mat& map2, double fScale) const { cv::Size imageSize(mParameters.imageWidth(), mParameters.imageHeight()); cv::Mat mapX = cv::Mat::zeros(imageSize, CV_32F); cv::Mat mapY = cv::Mat::zeros(imageSize, CV_32F); for (int v = 0; v < imageSize.height; ++v) { for (int u = 0; u < imageSize.width; ++u) { double mx_u = m_inv_K11 / fScale * u + m_inv_K13 / fScale; double my_u = m_inv_K22 / fScale * v + m_inv_K23 / fScale; double xi = mParameters.xi(); double d2 = mx_u * mx_u + my_u * my_u; Eigen::Vector3d P; P << mx_u, my_u, 1.0 - xi * (d2 + 1.0) / (xi + sqrt(1.0 + (1.0 - xi * xi) * d2)); Eigen::Vector2d p; spaceToPlane(P, p); mapX.at(v,u) = p(0); mapY.at(v,u) = p(1); } } cv::convertMaps(mapX, mapY, map1, map2, CV_32FC1, false); } cv::Mat CataCamera::initUndistortRectifyMap(cv::Mat& map1, cv::Mat& map2, float fx, float fy, cv::Size imageSize, float cx, float cy, cv::Mat rmat) const { if (imageSize == cv::Size(0, 0)) { imageSize = cv::Size(mParameters.imageWidth(), mParameters.imageHeight()); } cv::Mat mapX = cv::Mat::zeros(imageSize.height, imageSize.width, CV_32F); cv::Mat mapY = cv::Mat::zeros(imageSize.height, imageSize.width, CV_32F); Eigen::Matrix3f K_rect; if (cx == -1.0f && cy == -1.0f) { K_rect << fx, 0, imageSize.width / 2, 0, fy, imageSize.height / 2, 0, 0, 1; } else { K_rect << fx, 0, cx, 0, fy, cy, 0, 0, 1; } if (fx == -1.0f || fy == -1.0f) { K_rect(0,0) = mParameters.gamma1(); K_rect(1,1) = mParameters.gamma2(); } Eigen::Matrix3f K_rect_inv = K_rect.inverse(); Eigen::Matrix3f R, R_inv; cv::cv2eigen(rmat, R); R_inv = R.inverse(); for (int v = 0; v < imageSize.height; ++v) { for (int u = 0; u < imageSize.width; ++u) { Eigen::Vector3f xo; xo << u, v, 1; Eigen::Vector3f uo = R_inv * K_rect_inv * xo; Eigen::Vector2d p; spaceToPlane(uo.cast(), p); mapX.at(v,u) = p(0); mapY.at(v,u) = p(1); } } cv::convertMaps(mapX, mapY, map1, map2, CV_32FC1, false); cv::Mat K_rect_cv; cv::eigen2cv(K_rect, K_rect_cv); return K_rect_cv; } int CataCamera::parameterCount(void) const { return 9; } const CataCamera::Parameters& CataCamera::getParameters(void) const { return mParameters; } void CataCamera::setParameters(const CataCamera::Parameters& parameters) { mParameters = parameters; if ((mParameters.k1() == 0.0) && (mParameters.k2() == 0.0) && (mParameters.p1() == 0.0) && (mParameters.p2() == 0.0)) { m_noDistortion = true; } else { m_noDistortion = false; } m_inv_K11 = 1.0 / mParameters.gamma1(); m_inv_K13 = -mParameters.u0() / mParameters.gamma1(); m_inv_K22 = 1.0 / mParameters.gamma2(); m_inv_K23 = -mParameters.v0() / mParameters.gamma2(); } void CataCamera::readParameters(const std::vector& parameterVec) { if ((int)parameterVec.size() != parameterCount()) { return; } Parameters params = getParameters(); params.xi() = parameterVec.at(0); params.k1() = parameterVec.at(1); params.k2() = parameterVec.at(2); params.p1() = parameterVec.at(3); params.p2() = parameterVec.at(4); params.gamma1() = parameterVec.at(5); params.gamma2() = parameterVec.at(6); params.u0() = parameterVec.at(7); params.v0() = parameterVec.at(8); setParameters(params); } void CataCamera::writeParameters(std::vector& parameterVec) const { parameterVec.resize(parameterCount()); parameterVec.at(0) = mParameters.xi(); parameterVec.at(1) = mParameters.k1(); parameterVec.at(2) = mParameters.k2(); parameterVec.at(3) = mParameters.p1(); parameterVec.at(4) = mParameters.p2(); parameterVec.at(5) = mParameters.gamma1(); parameterVec.at(6) = mParameters.gamma2(); parameterVec.at(7) = mParameters.u0(); parameterVec.at(8) = mParameters.v0(); } void CataCamera::writeParametersToYamlFile(const std::string& filename) const { mParameters.writeToYamlFile(filename); } std::string CataCamera::parametersToString(void) const { std::ostringstream oss; oss << mParameters; return oss.str(); } } ================================================ FILE: src/utils/camodocal/src/camera_models/CostFunctionFactory.cc ================================================ #include "camodocal/camera_models/CostFunctionFactory.h" #include "ceres/ceres.h" #include "camodocal/camera_models/CataCamera.h" #include "camodocal/camera_models/EquidistantCamera.h" #include "camodocal/camera_models/PinholeCamera.h" #include "camodocal/camera_models/ScaramuzzaCamera.h" namespace camodocal { template void worldToCameraTransform(const T* const q_cam_odo, const T* const t_cam_odo, const T* const p_odo, const T* const att_odo, T* q, T* t, bool optimize_cam_odo_z = true) { Eigen::Quaternion q_z_inv(cos(att_odo[0] / T(2)), T(0), T(0), -sin(att_odo[0] / T(2))); Eigen::Quaternion q_y_inv(cos(att_odo[1] / T(2)), T(0), -sin(att_odo[1] / T(2)), T(0)); Eigen::Quaternion q_x_inv(cos(att_odo[2] / T(2)), -sin(att_odo[2] / T(2)), T(0), T(0)); Eigen::Quaternion q_zyx_inv = q_x_inv * q_y_inv * q_z_inv; T q_odo[4] = {q_zyx_inv.w(), q_zyx_inv.x(), q_zyx_inv.y(), q_zyx_inv.z()}; T q_odo_cam[4] = {q_cam_odo[3], -q_cam_odo[0], -q_cam_odo[1], -q_cam_odo[2]}; T q0[4]; ceres::QuaternionProduct(q_odo_cam, q_odo, q0); T t0[3]; T t_odo[3] = {p_odo[0], p_odo[1], p_odo[2]}; ceres::QuaternionRotatePoint(q_odo, t_odo, t0); t0[0] += t_cam_odo[0]; t0[1] += t_cam_odo[1]; if (optimize_cam_odo_z) { t0[2] += t_cam_odo[2]; } ceres::QuaternionRotatePoint(q_odo_cam, t0, t); t[0] = -t[0]; t[1] = -t[1]; t[2] = -t[2]; // Convert quaternion from Ceres convention (w, x, y, z) // to Eigen convention (x, y, z, w) q[0] = q0[1]; q[1] = q0[2]; q[2] = q0[3]; q[3] = q0[0]; } template class ReprojectionError1 { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW ReprojectionError1(const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p) : m_observed_P(observed_P), m_observed_p(observed_p) , m_sqrtPrecisionMat(Eigen::Matrix2d::Identity()) {} ReprojectionError1(const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p, const Eigen::Matrix2d& sqrtPrecisionMat) : m_observed_P(observed_P), m_observed_p(observed_p) , m_sqrtPrecisionMat(sqrtPrecisionMat) {} ReprojectionError1(const std::vector& intrinsic_params, const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p) : m_intrinsic_params(intrinsic_params) , m_observed_P(observed_P), m_observed_p(observed_p) {} // variables: camera intrinsics and camera extrinsics template bool operator()(const T* const intrinsic_params, const T* const q, const T* const t, T* residuals) const { Eigen::Matrix P = m_observed_P.cast(); Eigen::Matrix predicted_p; CameraT::spaceToPlane(intrinsic_params, q, t, P, predicted_p); Eigen::Matrix e = predicted_p - m_observed_p.cast(); Eigen::Matrix e_weighted = m_sqrtPrecisionMat.cast() * e; residuals[0] = e_weighted(0); residuals[1] = e_weighted(1); return true; } // variables: camera-odometry transforms and odometry poses template bool operator()(const T* const q_cam_odo, const T* const t_cam_odo, const T* const p_odo, const T* const att_odo, T* residuals) const { T q[4], t[3]; worldToCameraTransform(q_cam_odo, t_cam_odo, p_odo, att_odo, q, t); Eigen::Matrix P = m_observed_P.cast(); std::vector intrinsic_params(m_intrinsic_params.begin(), m_intrinsic_params.end()); // project 3D object point to the image plane Eigen::Matrix predicted_p; CameraT::spaceToPlane(intrinsic_params.data(), q, t, P, predicted_p); residuals[0] = predicted_p(0) - T(m_observed_p(0)); residuals[1] = predicted_p(1) - T(m_observed_p(1)); return true; } //private: // camera intrinsics std::vector m_intrinsic_params; // observed 3D point Eigen::Vector3d m_observed_P; // observed 2D point Eigen::Vector2d m_observed_p; // square root of precision matrix Eigen::Matrix2d m_sqrtPrecisionMat; }; // variables: camera extrinsics, 3D point template class ReprojectionError2 { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW ReprojectionError2(const std::vector& intrinsic_params, const Eigen::Vector2d& observed_p) : m_intrinsic_params(intrinsic_params), m_observed_p(observed_p) {} template bool operator()(const T* const q, const T* const t, const T* const point, T* residuals) const { Eigen::Matrix P; P(0) = T(point[0]); P(1) = T(point[1]); P(2) = T(point[2]); std::vector intrinsic_params(m_intrinsic_params.begin(), m_intrinsic_params.end()); // project 3D object point to the image plane Eigen::Matrix predicted_p; CameraT::spaceToPlane(intrinsic_params.data(), q, t, P, predicted_p); residuals[0] = predicted_p(0) - T(m_observed_p(0)); residuals[1] = predicted_p(1) - T(m_observed_p(1)); return true; } private: // camera intrinsics std::vector m_intrinsic_params; // observed 2D point Eigen::Vector2d m_observed_p; }; template class ReprojectionError3 { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW ReprojectionError3(const Eigen::Vector2d& observed_p) : m_observed_p(observed_p) , m_sqrtPrecisionMat(Eigen::Matrix2d::Identity()) , m_optimize_cam_odo_z(true) {} ReprojectionError3(const Eigen::Vector2d& observed_p, const Eigen::Matrix2d& sqrtPrecisionMat) : m_observed_p(observed_p) , m_sqrtPrecisionMat(sqrtPrecisionMat) , m_optimize_cam_odo_z(true) {} ReprojectionError3(const std::vector& intrinsic_params, const Eigen::Vector2d& observed_p) : m_intrinsic_params(intrinsic_params) , m_observed_p(observed_p) , m_sqrtPrecisionMat(Eigen::Matrix2d::Identity()) , m_optimize_cam_odo_z(true) {} ReprojectionError3(const std::vector& intrinsic_params, const Eigen::Vector2d& observed_p, const Eigen::Matrix2d& sqrtPrecisionMat) : m_intrinsic_params(intrinsic_params) , m_observed_p(observed_p) , m_sqrtPrecisionMat(sqrtPrecisionMat) , m_optimize_cam_odo_z(true) {} ReprojectionError3(const std::vector& intrinsic_params, const Eigen::Vector3d& odo_pos, const Eigen::Vector3d& odo_att, const Eigen::Vector2d& observed_p, bool optimize_cam_odo_z) : m_intrinsic_params(intrinsic_params) , m_odo_pos(odo_pos), m_odo_att(odo_att) , m_observed_p(observed_p) , m_optimize_cam_odo_z(optimize_cam_odo_z) {} ReprojectionError3(const std::vector& intrinsic_params, const Eigen::Quaterniond& cam_odo_q, const Eigen::Vector3d& cam_odo_t, const Eigen::Vector3d& odo_pos, const Eigen::Vector3d& odo_att, const Eigen::Vector2d& observed_p) : m_intrinsic_params(intrinsic_params) , m_cam_odo_q(cam_odo_q), m_cam_odo_t(cam_odo_t) , m_odo_pos(odo_pos), m_odo_att(odo_att) , m_observed_p(observed_p) , m_optimize_cam_odo_z(true) {} // variables: camera intrinsics, camera-to-odometry transform, // odometry extrinsics, 3D point template bool operator()(const T* const intrinsic_params, const T* const q_cam_odo, const T* const t_cam_odo, const T* const p_odo, const T* const att_odo, const T* const point, T* residuals) const { T q[4], t[3]; worldToCameraTransform(q_cam_odo, t_cam_odo, p_odo, att_odo, q, t, m_optimize_cam_odo_z); Eigen::Matrix P(point[0], point[1], point[2]); // project 3D object point to the image plane Eigen::Matrix predicted_p; CameraT::spaceToPlane(intrinsic_params, q, t, P, predicted_p); Eigen::Matrix err = predicted_p - m_observed_p.cast(); Eigen::Matrix err_weighted = m_sqrtPrecisionMat.cast() * err; residuals[0] = err_weighted(0); residuals[1] = err_weighted(1); return true; } // variables: camera-to-odometry transform, 3D point template bool operator()(const T* const q_cam_odo, const T* const t_cam_odo, const T* const point, T* residuals) const { T p_odo[3] = {T(m_odo_pos(0)), T(m_odo_pos(1)), T(m_odo_pos(2))}; T att_odo[3] = {T(m_odo_att(0)), T(m_odo_att(1)), T(m_odo_att(2))}; T q[4], t[3]; worldToCameraTransform(q_cam_odo, t_cam_odo, p_odo, att_odo, q, t, m_optimize_cam_odo_z); std::vector intrinsic_params(m_intrinsic_params.begin(), m_intrinsic_params.end()); Eigen::Matrix P(point[0], point[1], point[2]); // project 3D object point to the image plane Eigen::Matrix predicted_p; CameraT::spaceToPlane(intrinsic_params.data(), q, t, P, predicted_p); residuals[0] = predicted_p(0) - T(m_observed_p(0)); residuals[1] = predicted_p(1) - T(m_observed_p(1)); return true; } // variables: camera-to-odometry transform, odometry extrinsics, 3D point template bool operator()(const T* const q_cam_odo, const T* const t_cam_odo, const T* const p_odo, const T* const att_odo, const T* const point, T* residuals) const { T q[4], t[3]; worldToCameraTransform(q_cam_odo, t_cam_odo, p_odo, att_odo, q, t, m_optimize_cam_odo_z); std::vector intrinsic_params(m_intrinsic_params.begin(), m_intrinsic_params.end()); Eigen::Matrix P(point[0], point[1], point[2]); // project 3D object point to the image plane Eigen::Matrix predicted_p; CameraT::spaceToPlane(intrinsic_params.data(), q, t, P, predicted_p); Eigen::Matrix err = predicted_p - m_observed_p.cast(); Eigen::Matrix err_weighted = m_sqrtPrecisionMat.cast() * err; residuals[0] = err_weighted(0); residuals[1] = err_weighted(1); return true; } // variables: 3D point template bool operator()(const T* const point, T* residuals) const { T q_cam_odo[4] = {T(m_cam_odo_q.coeffs()(0)), T(m_cam_odo_q.coeffs()(1)), T(m_cam_odo_q.coeffs()(2)), T(m_cam_odo_q.coeffs()(3))}; T t_cam_odo[3] = {T(m_cam_odo_t(0)), T(m_cam_odo_t(1)), T(m_cam_odo_t(2))}; T p_odo[3] = {T(m_odo_pos(0)), T(m_odo_pos(1)), T(m_odo_pos(2))}; T att_odo[3] = {T(m_odo_att(0)), T(m_odo_att(1)), T(m_odo_att(2))}; T q[4], t[3]; worldToCameraTransform(q_cam_odo, t_cam_odo, p_odo, att_odo, q, t, m_optimize_cam_odo_z); std::vector intrinsic_params(m_intrinsic_params.begin(), m_intrinsic_params.end()); Eigen::Matrix P(point[0], point[1], point[2]); // project 3D object point to the image plane Eigen::Matrix predicted_p; CameraT::spaceToPlane(intrinsic_params.data(), q, t, P, predicted_p); residuals[0] = predicted_p(0) - T(m_observed_p(0)); residuals[1] = predicted_p(1) - T(m_observed_p(1)); return true; } private: // camera intrinsics std::vector m_intrinsic_params; // observed camera-odometry transform Eigen::Quaterniond m_cam_odo_q; Eigen::Vector3d m_cam_odo_t; // observed odometry Eigen::Vector3d m_odo_pos; Eigen::Vector3d m_odo_att; // observed 2D point Eigen::Vector2d m_observed_p; Eigen::Matrix2d m_sqrtPrecisionMat; bool m_optimize_cam_odo_z; }; // variables: camera intrinsics and camera extrinsics template class StereoReprojectionError { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW StereoReprojectionError(const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p_l, const Eigen::Vector2d& observed_p_r) : m_observed_P(observed_P) , m_observed_p_l(observed_p_l) , m_observed_p_r(observed_p_r) { } template bool operator()(const T* const intrinsic_params_l, const T* const intrinsic_params_r, const T* const q_l, const T* const t_l, const T* const q_l_r, const T* const t_l_r, T* residuals) const { Eigen::Matrix P; P(0) = T(m_observed_P(0)); P(1) = T(m_observed_P(1)); P(2) = T(m_observed_P(2)); Eigen::Matrix predicted_p_l; CameraT::spaceToPlane(intrinsic_params_l, q_l, t_l, P, predicted_p_l); Eigen::Quaternion q_r = Eigen::Quaternion(q_l_r) * Eigen::Quaternion(q_l); Eigen::Matrix t_r; t_r(0) = t_l[0]; t_r(1) = t_l[1]; t_r(2) = t_l[2]; t_r = Eigen::Quaternion(q_l_r) * t_r; t_r(0) += t_l_r[0]; t_r(1) += t_l_r[1]; t_r(2) += t_l_r[2]; Eigen::Matrix predicted_p_r; CameraT::spaceToPlane(intrinsic_params_r, q_r.coeffs().data(), t_r.data(), P, predicted_p_r); residuals[0] = predicted_p_l(0) - T(m_observed_p_l(0)); residuals[1] = predicted_p_l(1) - T(m_observed_p_l(1)); residuals[2] = predicted_p_r(0) - T(m_observed_p_r(0)); residuals[3] = predicted_p_r(1) - T(m_observed_p_r(1)); return true; } private: // observed 3D point Eigen::Vector3d m_observed_P; // observed 2D point Eigen::Vector2d m_observed_p_l; Eigen::Vector2d m_observed_p_r; }; template class ComprehensionError { public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW ComprehensionError(const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p) : m_observed_P(observed_P), m_observed_p(observed_p), m_sqrtPrecisionMat(Eigen::Matrix2d::Identity()) { } template bool operator()(const T* const intrinsic_params, const T* const q, const T* const t, T* residuals) const { { Eigen::Matrix p = m_observed_p.cast(); Eigen::Matrix predicted_img_P; CameraT::LiftToSphere(intrinsic_params, p, predicted_img_P); Eigen::Matrix predicted_p; CameraT::SphereToPlane(intrinsic_params, predicted_img_P, predicted_p); Eigen::Matrix e = predicted_p - m_observed_p.cast(); Eigen::Matrix e_weighted = m_sqrtPrecisionMat.cast() * e; residuals[0] = e_weighted(0); residuals[1] = e_weighted(1); } { Eigen::Matrix P = m_observed_P.cast(); Eigen::Matrix predicted_p; CameraT::spaceToPlane(intrinsic_params, q, t, P, predicted_p); Eigen::Matrix e = predicted_p - m_observed_p.cast(); Eigen::Matrix e_weighted = m_sqrtPrecisionMat.cast() * e; residuals[2] = e_weighted(0); residuals[3] = e_weighted(1); } return true; } // private: // camera intrinsics // std::vector m_intrinsic_params; // observed 3D point Eigen::Vector3d m_observed_P; // observed 2D point Eigen::Vector2d m_observed_p; // square root of precision matrix Eigen::Matrix2d m_sqrtPrecisionMat; }; boost::shared_ptr CostFunctionFactory::m_instance; CostFunctionFactory::CostFunctionFactory() { } boost::shared_ptr CostFunctionFactory::instance(void) { if (m_instance.get() == 0) { m_instance.reset(new CostFunctionFactory); } return m_instance; } ceres::CostFunction* CostFunctionFactory::generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p, int flags) const { ceres::CostFunction* costFunction = 0; std::vector intrinsic_params; camera->writeParameters(intrinsic_params); switch (flags) { case CAMERA_INTRINSICS | CAMERA_POSE: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3>( new ReprojectionError1(observed_P, observed_p)); break; case Camera::PINHOLE: costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3>( new ReprojectionError1(observed_P, observed_p)); break; case Camera::MEI: costFunction = new ceres::AutoDiffCostFunction, 2, 9, 4, 3>( new ReprojectionError1(observed_P, observed_p)); break; case Camera::SCARAMUZZA: costFunction = new ceres::AutoDiffCostFunction, 4, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 3>( new ComprehensionError(observed_P, observed_p)); // new ceres::AutoDiffCostFunction, 2, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 3>( // new ReprojectionError1(observed_P, observed_p)); break; } break; case CAMERA_ODOMETRY_TRANSFORM | ODOMETRY_6D_POSE: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3>( new ReprojectionError1(intrinsic_params, observed_P, observed_p)); break; case Camera::PINHOLE: costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3>( new ReprojectionError1(intrinsic_params, observed_P, observed_p)); break; case Camera::MEI: costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3>( new ReprojectionError1(intrinsic_params, observed_P, observed_p)); break; case Camera::SCARAMUZZA: costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3>( new ReprojectionError1(intrinsic_params, observed_P, observed_p)); break; } break; } return costFunction; } ceres::CostFunction* CostFunctionFactory::generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p, const Eigen::Matrix2d& sqrtPrecisionMat, int flags) const { ceres::CostFunction* costFunction = 0; std::vector intrinsic_params; camera->writeParameters(intrinsic_params); switch (flags) { case CAMERA_INTRINSICS | CAMERA_POSE: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3>( new ReprojectionError1(observed_P, observed_p, sqrtPrecisionMat)); break; case Camera::PINHOLE: costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3>( new ReprojectionError1(observed_P, observed_p, sqrtPrecisionMat)); break; case Camera::MEI: costFunction = new ceres::AutoDiffCostFunction, 2, 9, 4, 3>( new ReprojectionError1(observed_P, observed_p, sqrtPrecisionMat)); break; case Camera::SCARAMUZZA: costFunction = new ceres::AutoDiffCostFunction, 2, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 3>( new ReprojectionError1(observed_P, observed_p, sqrtPrecisionMat)); break; } break; } return costFunction; } ceres::CostFunction* CostFunctionFactory::generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector2d& observed_p, int flags, bool optimize_cam_odo_z) const { ceres::CostFunction* costFunction = 0; std::vector intrinsic_params; camera->writeParameters(intrinsic_params); switch (flags) { case CAMERA_POSE | POINT_3D: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3>( new ReprojectionError2(intrinsic_params, observed_p)); break; case Camera::PINHOLE: costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3>( new ReprojectionError2(intrinsic_params, observed_p)); break; case Camera::MEI: costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3>( new ReprojectionError2(intrinsic_params, observed_p)); break; case Camera::SCARAMUZZA: costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3>( new ReprojectionError2(intrinsic_params, observed_p)); break; } break; case CAMERA_ODOMETRY_TRANSFORM | ODOMETRY_3D_POSE | POINT_3D: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 2, 1, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 2, 1, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } break; case Camera::PINHOLE: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 2, 1, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 2, 1, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } break; case Camera::MEI: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 2, 1, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 2, 1, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } break; case Camera::SCARAMUZZA: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 2, 1, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 2, 1, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } break; } break; case CAMERA_ODOMETRY_TRANSFORM | ODOMETRY_6D_POSE | POINT_3D: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } break; case Camera::PINHOLE: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } break; case Camera::MEI: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } break; case Camera::SCARAMUZZA: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p)); } break; } break; case CAMERA_INTRINSICS | CAMERA_ODOMETRY_TRANSFORM | ODOMETRY_3D_POSE | POINT_3D: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3, 2, 1, 3>( new ReprojectionError3(observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 2, 2, 1, 3>( new ReprojectionError3(observed_p)); } break; case Camera::PINHOLE: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3, 2, 1, 3>( new ReprojectionError3(observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 2, 2, 1, 3>( new ReprojectionError3(observed_p)); } break; case Camera::MEI: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 9, 4, 3, 2, 1, 3>( new ReprojectionError3(observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 9, 4, 2, 2, 1, 3>( new ReprojectionError3(observed_p)); } break; case Camera::SCARAMUZZA: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 3, 2, 1, 3>( new ReprojectionError3(observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 2, 2, 1, 3>( new ReprojectionError3(observed_p)); } break; } break; case CAMERA_INTRINSICS | CAMERA_ODOMETRY_TRANSFORM | ODOMETRY_6D_POSE | POINT_3D: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3, 3, 3, 3>( new ReprojectionError3(observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 2, 3, 3, 3>( new ReprojectionError3(observed_p)); } break; case Camera::PINHOLE: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3, 3, 3, 3>( new ReprojectionError3(observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 2, 3, 3, 3>( new ReprojectionError3(observed_p)); } break; case Camera::MEI: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 9, 4, 3, 3, 3, 3>( new ReprojectionError3(observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 9, 4, 2, 3, 3, 3>( new ReprojectionError3(observed_p)); } break; case Camera::SCARAMUZZA: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 3, 3, 3, 3>( new ReprojectionError3(observed_p)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 2, 3, 3, 3>( new ReprojectionError3(observed_p)); } break; } break; } return costFunction; } ceres::CostFunction* CostFunctionFactory::generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector2d& observed_p, const Eigen::Matrix2d& sqrtPrecisionMat, int flags, bool optimize_cam_odo_z) const { ceres::CostFunction* costFunction = 0; std::vector intrinsic_params; camera->writeParameters(intrinsic_params); switch (flags) { case CAMERA_ODOMETRY_TRANSFORM | ODOMETRY_6D_POSE | POINT_3D: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p, sqrtPrecisionMat)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p, sqrtPrecisionMat)); } break; case Camera::PINHOLE: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p, sqrtPrecisionMat)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p, sqrtPrecisionMat)); } break; case Camera::MEI: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p, sqrtPrecisionMat)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p, sqrtPrecisionMat)); } break; case Camera::SCARAMUZZA: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p, sqrtPrecisionMat)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3, 3, 3>( new ReprojectionError3(intrinsic_params, observed_p, sqrtPrecisionMat)); } break; } break; case CAMERA_INTRINSICS | CAMERA_ODOMETRY_TRANSFORM | ODOMETRY_6D_POSE | POINT_3D: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3, 3, 3, 3>( new ReprojectionError3(observed_p, sqrtPrecisionMat)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 2, 3, 3, 3>( new ReprojectionError3(observed_p, sqrtPrecisionMat)); } break; case Camera::PINHOLE: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 3, 3, 3, 3>( new ReprojectionError3(observed_p, sqrtPrecisionMat)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 8, 4, 2, 3, 3, 3>( new ReprojectionError3(observed_p, sqrtPrecisionMat)); } break; case Camera::MEI: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 9, 4, 3, 3, 3, 3>( new ReprojectionError3(observed_p, sqrtPrecisionMat)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 9, 4, 2, 3, 3, 3>( new ReprojectionError3(observed_p, sqrtPrecisionMat)); } break; case Camera::SCARAMUZZA: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 3, 3, 3, 3>( new ReprojectionError3(observed_p, sqrtPrecisionMat)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 2, 3, 3, 3>( new ReprojectionError3(observed_p, sqrtPrecisionMat)); } break; } break; } return costFunction; } ceres::CostFunction* CostFunctionFactory::generateCostFunction(const CameraConstPtr& camera, const Eigen::Vector3d& odo_pos, const Eigen::Vector3d& odo_att, const Eigen::Vector2d& observed_p, int flags, bool optimize_cam_odo_z) const { ceres::CostFunction* costFunction = 0; std::vector intrinsic_params; camera->writeParameters(intrinsic_params); switch (flags) { case CAMERA_ODOMETRY_TRANSFORM | POINT_3D: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3>( new ReprojectionError3(intrinsic_params, odo_pos, odo_att, observed_p, optimize_cam_odo_z)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3>( new ReprojectionError3(intrinsic_params, odo_pos, odo_att, observed_p, optimize_cam_odo_z)); } break; case Camera::PINHOLE: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3>( new ReprojectionError3(intrinsic_params, odo_pos, odo_att, observed_p, optimize_cam_odo_z)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3>( new ReprojectionError3(intrinsic_params, odo_pos, odo_att, observed_p, optimize_cam_odo_z)); } break; case Camera::MEI: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3>( new ReprojectionError3(intrinsic_params, odo_pos, odo_att, observed_p, optimize_cam_odo_z)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3>( new ReprojectionError3(intrinsic_params, odo_pos, odo_att, observed_p, optimize_cam_odo_z)); } break; case Camera::SCARAMUZZA: if (optimize_cam_odo_z) { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 3, 3>( new ReprojectionError3(intrinsic_params, odo_pos, odo_att, observed_p, optimize_cam_odo_z)); } else { costFunction = new ceres::AutoDiffCostFunction, 2, 4, 2, 3>( new ReprojectionError3(intrinsic_params, odo_pos, odo_att, observed_p, optimize_cam_odo_z)); } break; } break; } return costFunction; } ceres::CostFunction* CostFunctionFactory::generateCostFunction(const CameraConstPtr& camera, const Eigen::Quaterniond& cam_odo_q, const Eigen::Vector3d& cam_odo_t, const Eigen::Vector3d& odo_pos, const Eigen::Vector3d& odo_att, const Eigen::Vector2d& observed_p, int flags) const { ceres::CostFunction* costFunction = 0; std::vector intrinsic_params; camera->writeParameters(intrinsic_params); switch (flags) { case POINT_3D: switch (camera->modelType()) { case Camera::KANNALA_BRANDT: costFunction = new ceres::AutoDiffCostFunction, 2, 3>( new ReprojectionError3(intrinsic_params, cam_odo_q, cam_odo_t, odo_pos, odo_att, observed_p)); break; case Camera::PINHOLE: costFunction = new ceres::AutoDiffCostFunction, 2, 3>( new ReprojectionError3(intrinsic_params, cam_odo_q, cam_odo_t, odo_pos, odo_att, observed_p)); break; case Camera::MEI: costFunction = new ceres::AutoDiffCostFunction, 2, 3>( new ReprojectionError3(intrinsic_params, cam_odo_q, cam_odo_t, odo_pos, odo_att, observed_p)); break; case Camera::SCARAMUZZA: costFunction = new ceres::AutoDiffCostFunction, 2, 3>( new ReprojectionError3(intrinsic_params, cam_odo_q, cam_odo_t, odo_pos, odo_att, observed_p)); break; } break; } return costFunction; } ceres::CostFunction* CostFunctionFactory::generateCostFunction(const CameraConstPtr& cameraL, const CameraConstPtr& cameraR, const Eigen::Vector3d& observed_P, const Eigen::Vector2d& observed_p_l, const Eigen::Vector2d& observed_p_r) const { ceres::CostFunction* costFunction = 0; if (cameraL->modelType() != cameraR->modelType()) { return costFunction; } switch (cameraL->modelType()) { case Camera::KANNALA_BRANDT: costFunction = new ceres::AutoDiffCostFunction, 4, 8, 8, 4, 3, 4, 3>( new StereoReprojectionError(observed_P, observed_p_l, observed_p_r)); break; case Camera::PINHOLE: costFunction = new ceres::AutoDiffCostFunction, 4, 8, 8, 4, 3, 4, 3>( new StereoReprojectionError(observed_P, observed_p_l, observed_p_r)); break; case Camera::MEI: costFunction = new ceres::AutoDiffCostFunction, 4, 9, 9, 4, 3, 4, 3>( new StereoReprojectionError(observed_P, observed_p_l, observed_p_r)); break; case Camera::SCARAMUZZA: costFunction = new ceres::AutoDiffCostFunction, 4, SCARAMUZZA_CAMERA_NUM_PARAMS, SCARAMUZZA_CAMERA_NUM_PARAMS, 4, 3, 4, 3>( new StereoReprojectionError(observed_P, observed_p_l, observed_p_r)); break; } return costFunction; } } ================================================ FILE: src/utils/camodocal/src/camera_models/EquidistantCamera.cc ================================================ #include "camodocal/camera_models/EquidistantCamera.h" #include #include #include #include #include #include #include #include #include "camodocal/gpl/gpl.h" namespace camodocal { EquidistantCamera::Parameters::Parameters() : Camera::Parameters(KANNALA_BRANDT) , m_k2(0.0) , m_k3(0.0) , m_k4(0.0) , m_k5(0.0) , m_mu(0.0) , m_mv(0.0) , m_u0(0.0) , m_v0(0.0) { } EquidistantCamera::Parameters::Parameters(const std::string& cameraName, int w, int h, double k2, double k3, double k4, double k5, double mu, double mv, double u0, double v0) : Camera::Parameters(KANNALA_BRANDT, cameraName, w, h) , m_k2(k2) , m_k3(k3) , m_k4(k4) , m_k5(k5) , m_mu(mu) , m_mv(mv) , m_u0(u0) , m_v0(v0) { } double& EquidistantCamera::Parameters::k2(void) { return m_k2; } double& EquidistantCamera::Parameters::k3(void) { return m_k3; } double& EquidistantCamera::Parameters::k4(void) { return m_k4; } double& EquidistantCamera::Parameters::k5(void) { return m_k5; } double& EquidistantCamera::Parameters::mu(void) { return m_mu; } double& EquidistantCamera::Parameters::mv(void) { return m_mv; } double& EquidistantCamera::Parameters::u0(void) { return m_u0; } double& EquidistantCamera::Parameters::v0(void) { return m_v0; } double EquidistantCamera::Parameters::k2(void) const { return m_k2; } double EquidistantCamera::Parameters::k3(void) const { return m_k3; } double EquidistantCamera::Parameters::k4(void) const { return m_k4; } double EquidistantCamera::Parameters::k5(void) const { return m_k5; } double EquidistantCamera::Parameters::mu(void) const { return m_mu; } double EquidistantCamera::Parameters::mv(void) const { return m_mv; } double EquidistantCamera::Parameters::u0(void) const { return m_u0; } double EquidistantCamera::Parameters::v0(void) const { return m_v0; } bool EquidistantCamera::Parameters::readFromYamlFile(const std::string& filename) { cv::FileStorage fs(filename, cv::FileStorage::READ); if (!fs.isOpened()) { return false; } if (!fs["model_type"].isNone()) { std::string sModelType; fs["model_type"] >> sModelType; if (sModelType.compare("KANNALA_BRANDT") != 0) { return false; } } m_modelType = KANNALA_BRANDT; fs["camera_name"] >> m_cameraName; m_imageWidth = static_cast(fs["image_width"]); m_imageHeight = static_cast(fs["image_height"]); cv::FileNode n = fs["projection_parameters"]; m_k2 = static_cast(n["k2"]); m_k3 = static_cast(n["k3"]); m_k4 = static_cast(n["k4"]); m_k5 = static_cast(n["k5"]); m_mu = static_cast(n["mu"]); m_mv = static_cast(n["mv"]); m_u0 = static_cast(n["u0"]); m_v0 = static_cast(n["v0"]); return true; } void EquidistantCamera::Parameters::writeToYamlFile(const std::string& filename) const { cv::FileStorage fs(filename, cv::FileStorage::WRITE); fs << "model_type" << "KANNALA_BRANDT"; fs << "camera_name" << m_cameraName; fs << "image_width" << m_imageWidth; fs << "image_height" << m_imageHeight; // projection: k2, k3, k4, k5, mu, mv, u0, v0 fs << "projection_parameters"; fs << "{" << "k2" << m_k2 << "k3" << m_k3 << "k4" << m_k4 << "k5" << m_k5 << "mu" << m_mu << "mv" << m_mv << "u0" << m_u0 << "v0" << m_v0 << "}"; fs.release(); } EquidistantCamera::Parameters& EquidistantCamera::Parameters::operator=(const EquidistantCamera::Parameters& other) { if (this != &other) { m_modelType = other.m_modelType; m_cameraName = other.m_cameraName; m_imageWidth = other.m_imageWidth; m_imageHeight = other.m_imageHeight; m_k2 = other.m_k2; m_k3 = other.m_k3; m_k4 = other.m_k4; m_k5 = other.m_k5; m_mu = other.m_mu; m_mv = other.m_mv; m_u0 = other.m_u0; m_v0 = other.m_v0; } return *this; } std::ostream& operator<< (std::ostream& out, const EquidistantCamera::Parameters& params) { out << "Camera Parameters:" << std::endl; out << " model_type " << "KANNALA_BRANDT" << std::endl; out << " camera_name " << params.m_cameraName << std::endl; out << " image_width " << params.m_imageWidth << std::endl; out << " image_height " << params.m_imageHeight << std::endl; // projection: k2, k3, k4, k5, mu, mv, u0, v0 out << "Projection Parameters" << std::endl; out << " k2 " << params.m_k2 << std::endl << " k3 " << params.m_k3 << std::endl << " k4 " << params.m_k4 << std::endl << " k5 " << params.m_k5 << std::endl << " mu " << params.m_mu << std::endl << " mv " << params.m_mv << std::endl << " u0 " << params.m_u0 << std::endl << " v0 " << params.m_v0 << std::endl; return out; } EquidistantCamera::EquidistantCamera() : m_inv_K11(1.0) , m_inv_K13(0.0) , m_inv_K22(1.0) , m_inv_K23(0.0) { } EquidistantCamera::EquidistantCamera(const std::string& cameraName, int imageWidth, int imageHeight, double k2, double k3, double k4, double k5, double mu, double mv, double u0, double v0) : mParameters(cameraName, imageWidth, imageHeight, k2, k3, k4, k5, mu, mv, u0, v0) { // Inverse camera projection matrix parameters m_inv_K11 = 1.0 / mParameters.mu(); m_inv_K13 = -mParameters.u0() / mParameters.mu(); m_inv_K22 = 1.0 / mParameters.mv(); m_inv_K23 = -mParameters.v0() / mParameters.mv(); } EquidistantCamera::EquidistantCamera(const EquidistantCamera::Parameters& params) : mParameters(params) { // Inverse camera projection matrix parameters m_inv_K11 = 1.0 / mParameters.mu(); m_inv_K13 = -mParameters.u0() / mParameters.mu(); m_inv_K22 = 1.0 / mParameters.mv(); m_inv_K23 = -mParameters.v0() / mParameters.mv(); } Camera::ModelType EquidistantCamera::modelType(void) const { return mParameters.modelType(); } const std::string& EquidistantCamera::cameraName(void) const { return mParameters.cameraName(); } int EquidistantCamera::imageWidth(void) const { return mParameters.imageWidth(); } int EquidistantCamera::imageHeight(void) const { return mParameters.imageHeight(); } void EquidistantCamera::estimateIntrinsics(const cv::Size& boardSize, const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints) { Parameters params = getParameters(); double u0 = params.imageWidth() / 2.0; double v0 = params.imageHeight() / 2.0; double minReprojErr = std::numeric_limits::max(); std::vector rvecs, tvecs; rvecs.assign(objectPoints.size(), cv::Mat()); tvecs.assign(objectPoints.size(), cv::Mat()); params.k2() = 0.0; params.k3() = 0.0; params.k4() = 0.0; params.k5() = 0.0; params.u0() = u0; params.v0() = v0; // Initialize focal length // C. Hughes, P. Denny, M. Glavin, and E. Jones, // Equidistant Fish-Eye Calibration and Rectification by Vanishing Point // Extraction, PAMI 2010 // Find circles from rows of chessboard corners, and for each pair // of circles, find vanishing points: v1 and v2. // f = ||v1 - v2|| / PI; double f0 = 0.0; for (size_t i = 0; i < imagePoints.size(); ++i) { std::vector center(boardSize.height); double radius[boardSize.height]; for (int r = 0; r < boardSize.height; ++r) { std::vector circle; for (int c = 0; c < boardSize.width; ++c) { circle.push_back(imagePoints.at(i).at(r * boardSize.width + c)); } fitCircle(circle, center[r](0), center[r](1), radius[r]); } for (int j = 0; j < boardSize.height; ++j) { for (int k = j + 1; k < boardSize.height; ++k) { // find distance between pair of vanishing points which // correspond to intersection points of 2 circles std::vector ipts; ipts = intersectCircles(center[j](0), center[j](1), radius[j], center[k](0), center[k](1), radius[k]); if (ipts.size() < 2) { continue; } double f = cv::norm(ipts.at(0) - ipts.at(1)) / M_PI; params.mu() = f; params.mv() = f; setParameters(params); for (size_t l = 0; l < objectPoints.size(); ++l) { estimateExtrinsics(objectPoints.at(l), imagePoints.at(l), rvecs.at(l), tvecs.at(l)); } double reprojErr = reprojectionError(objectPoints, imagePoints, rvecs, tvecs, cv::noArray()); if (reprojErr < minReprojErr) { minReprojErr = reprojErr; f0 = f; } } } } if (f0 <= 0.0 && minReprojErr >= std::numeric_limits::max()) { std::cout << "[" << params.cameraName() << "] " << "# INFO: kannala-Brandt model fails with given data. " << std::endl; return; } params.mu() = f0; params.mv() = f0; setParameters(params); } /** * \brief Lifts a point from the image plane to the unit sphere * * \param p image coordinates * \param P coordinates of the point on the sphere */ void EquidistantCamera::liftSphere(const Eigen::Vector2d& p, Eigen::Vector3d& P) const { liftProjective(p, P); } /** * \brief Lifts a point from the image plane to its projective ray * * \param p image coordinates * \param P coordinates of the projective ray */ void EquidistantCamera::liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const { // Lift points to normalised plane Eigen::Vector2d p_u; p_u << m_inv_K11 * p(0) + m_inv_K13, m_inv_K22 * p(1) + m_inv_K23; // Obtain a projective ray double theta, phi; backprojectSymmetric(p_u, theta, phi); P(0) = sin(theta) * cos(phi); P(1) = sin(theta) * sin(phi); P(2) = cos(theta); } /** * \brief Project a 3D point (\a x,\a y,\a z) to the image plane in (\a u,\a v) * * \param P 3D point coordinates * \param p return value, contains the image point coordinates */ void EquidistantCamera::spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p) const { double theta = acos(P(2) / P.norm()); double phi = atan2(P(1), P(0)); Eigen::Vector2d p_u = r(mParameters.k2(), mParameters.k3(), mParameters.k4(), mParameters.k5(), theta) * Eigen::Vector2d(cos(phi), sin(phi)); // Apply generalised projection matrix p << mParameters.mu() * p_u(0) + mParameters.u0(), mParameters.mv() * p_u(1) + mParameters.v0(); } /** * \brief Project a 3D point to the image plane and calculate Jacobian * * \param P 3D point coordinates * \param p return value, contains the image point coordinates */ void EquidistantCamera::spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p, Eigen::Matrix& J) const { double theta = acos(P(2) / P.norm()); double phi = atan2(P(1), P(0)); Eigen::Vector2d p_u = r(mParameters.k2(), mParameters.k3(), mParameters.k4(), mParameters.k5(), theta) * Eigen::Vector2d(cos(phi), sin(phi)); // Apply generalised projection matrix p << mParameters.mu() * p_u(0) + mParameters.u0(), mParameters.mv() * p_u(1) + mParameters.v0(); } /** * \brief Projects an undistorted 2D point p_u to the image plane * * \param p_u 2D point coordinates * \return image point coordinates */ void EquidistantCamera::undistToPlane(const Eigen::Vector2d& p_u, Eigen::Vector2d& p) const { // Eigen::Vector2d p_d; // // if (m_noDistortion) // { // p_d = p_u; // } // else // { // // Apply distortion // Eigen::Vector2d d_u; // distortion(p_u, d_u); // p_d = p_u + d_u; // } // // // Apply generalised projection matrix // p << mParameters.gamma1() * p_d(0) + mParameters.u0(), // mParameters.gamma2() * p_d(1) + mParameters.v0(); } void EquidistantCamera::initUndistortMap(cv::Mat& map1, cv::Mat& map2, double fScale) const { cv::Size imageSize(mParameters.imageWidth(), mParameters.imageHeight()); cv::Mat mapX = cv::Mat::zeros(imageSize, CV_32F); cv::Mat mapY = cv::Mat::zeros(imageSize, CV_32F); for (int v = 0; v < imageSize.height; ++v) { for (int u = 0; u < imageSize.width; ++u) { double mx_u = m_inv_K11 / fScale * u + m_inv_K13 / fScale; double my_u = m_inv_K22 / fScale * v + m_inv_K23 / fScale; double theta, phi; backprojectSymmetric(Eigen::Vector2d(mx_u, my_u), theta, phi); Eigen::Vector3d P; P << sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta); Eigen::Vector2d p; spaceToPlane(P, p); mapX.at(v,u) = p(0); mapY.at(v,u) = p(1); } } cv::convertMaps(mapX, mapY, map1, map2, CV_32FC1, false); } cv::Mat EquidistantCamera::initUndistortRectifyMap(cv::Mat& map1, cv::Mat& map2, float fx, float fy, cv::Size imageSize, float cx, float cy, cv::Mat rmat) const { if (imageSize == cv::Size(0, 0)) { imageSize = cv::Size(mParameters.imageWidth(), mParameters.imageHeight()); } cv::Mat mapX = cv::Mat::zeros(imageSize.height, imageSize.width, CV_32F); cv::Mat mapY = cv::Mat::zeros(imageSize.height, imageSize.width, CV_32F); Eigen::Matrix3f K_rect; if (cx == -1.0f && cy == -1.0f) { K_rect << fx, 0, imageSize.width / 2, 0, fy, imageSize.height / 2, 0, 0, 1; } else { K_rect << fx, 0, cx, 0, fy, cy, 0, 0, 1; } if (fx == -1.0f || fy == -1.0f) { K_rect(0,0) = mParameters.mu(); K_rect(1,1) = mParameters.mv(); } Eigen::Matrix3f K_rect_inv = K_rect.inverse(); Eigen::Matrix3f R, R_inv; cv::cv2eigen(rmat, R); R_inv = R.inverse(); for (int v = 0; v < imageSize.height; ++v) { for (int u = 0; u < imageSize.width; ++u) { Eigen::Vector3f xo; xo << u, v, 1; Eigen::Vector3f uo = R_inv * K_rect_inv * xo; Eigen::Vector2d p; spaceToPlane(uo.cast(), p); mapX.at(v,u) = p(0); mapY.at(v,u) = p(1); } } cv::convertMaps(mapX, mapY, map1, map2, CV_32FC1, false); cv::Mat K_rect_cv; cv::eigen2cv(K_rect, K_rect_cv); return K_rect_cv; } int EquidistantCamera::parameterCount(void) const { return 8; } const EquidistantCamera::Parameters& EquidistantCamera::getParameters(void) const { return mParameters; } void EquidistantCamera::setParameters(const EquidistantCamera::Parameters& parameters) { mParameters = parameters; // Inverse camera projection matrix parameters m_inv_K11 = 1.0 / mParameters.mu(); m_inv_K13 = -mParameters.u0() / mParameters.mu(); m_inv_K22 = 1.0 / mParameters.mv(); m_inv_K23 = -mParameters.v0() / mParameters.mv(); } void EquidistantCamera::readParameters(const std::vector& parameterVec) { if (parameterVec.size() != parameterCount()) { return; } Parameters params = getParameters(); params.k2() = parameterVec.at(0); params.k3() = parameterVec.at(1); params.k4() = parameterVec.at(2); params.k5() = parameterVec.at(3); params.mu() = parameterVec.at(4); params.mv() = parameterVec.at(5); params.u0() = parameterVec.at(6); params.v0() = parameterVec.at(7); setParameters(params); } void EquidistantCamera::writeParameters(std::vector& parameterVec) const { parameterVec.resize(parameterCount()); parameterVec.at(0) = mParameters.k2(); parameterVec.at(1) = mParameters.k3(); parameterVec.at(2) = mParameters.k4(); parameterVec.at(3) = mParameters.k5(); parameterVec.at(4) = mParameters.mu(); parameterVec.at(5) = mParameters.mv(); parameterVec.at(6) = mParameters.u0(); parameterVec.at(7) = mParameters.v0(); } void EquidistantCamera::writeParametersToYamlFile(const std::string& filename) const { mParameters.writeToYamlFile(filename); } std::string EquidistantCamera::parametersToString(void) const { std::ostringstream oss; oss << mParameters; return oss.str(); } void EquidistantCamera::fitOddPoly(const std::vector& x, const std::vector& y, int n, std::vector& coeffs) const { std::vector pows; for (int i = 1; i <= n; i += 2) { pows.push_back(i); } Eigen::MatrixXd X(x.size(), pows.size()); Eigen::MatrixXd Y(y.size(), 1); for (size_t i = 0; i < x.size(); ++i) { for (size_t j = 0; j < pows.size(); ++j) { X(i,j) = pow(x.at(i), pows.at(j)); } Y(i,0) = y.at(i); } Eigen::MatrixXd A = (X.transpose() * X).inverse() * X.transpose() * Y; coeffs.resize(A.rows()); for (int i = 0; i < A.rows(); ++i) { coeffs.at(i) = A(i,0); } } void EquidistantCamera::backprojectSymmetric(const Eigen::Vector2d& p_u, double& theta, double& phi) const { double tol = 1e-10; double p_u_norm = p_u.norm(); if (p_u_norm < 1e-10) { phi = 0.0; } else { phi = atan2(p_u(1), p_u(0)); } int npow = 9; if (mParameters.k5() == 0.0) { npow -= 2; } if (mParameters.k4() == 0.0) { npow -= 2; } if (mParameters.k3() == 0.0) { npow -= 2; } if (mParameters.k2() == 0.0) { npow -= 2; } Eigen::MatrixXd coeffs(npow + 1, 1); coeffs.setZero(); coeffs(0) = -p_u_norm; coeffs(1) = 1.0; if (npow >= 3) { coeffs(3) = mParameters.k2(); } if (npow >= 5) { coeffs(5) = mParameters.k3(); } if (npow >= 7) { coeffs(7) = mParameters.k4(); } if (npow >= 9) { coeffs(9) = mParameters.k5(); } if (npow == 1) { theta = p_u_norm; } else { // Get eigenvalues of companion matrix corresponding to polynomial. // Eigenvalues correspond to roots of polynomial. Eigen::MatrixXd A(npow, npow); A.setZero(); A.block(1, 0, npow - 1, npow - 1).setIdentity(); A.col(npow - 1) = - coeffs.block(0, 0, npow, 1) / coeffs(npow); Eigen::EigenSolver es(A); Eigen::MatrixXcd eigval = es.eigenvalues(); std::vector thetas; for (int i = 0; i < eigval.rows(); ++i) { if (fabs(eigval(i).imag()) > tol) { continue; } double t = eigval(i).real(); if (t < -tol) { continue; } else if (t < 0.0) { t = 0.0; } thetas.push_back(t); } if (thetas.empty()) { theta = p_u_norm; } else { theta = *std::min_element(thetas.begin(), thetas.end()); } } } } ================================================ FILE: src/utils/camodocal/src/camera_models/PinholeCamera.cc ================================================ #include "camodocal/camera_models/PinholeCamera.h" #include #include #include #include #include #include #include #include "camodocal/gpl/gpl.h" namespace camodocal { PinholeCamera::Parameters::Parameters() : Camera::Parameters(PINHOLE) , m_k1(0.0) , m_k2(0.0) , m_p1(0.0) , m_p2(0.0) , m_fx(0.0) , m_fy(0.0) , m_cx(0.0) , m_cy(0.0) { } PinholeCamera::Parameters::Parameters(const std::string& cameraName, int w, int h, double k1, double k2, double p1, double p2, double fx, double fy, double cx, double cy) : Camera::Parameters(PINHOLE, cameraName, w, h) , m_k1(k1) , m_k2(k2) , m_p1(p1) , m_p2(p2) , m_fx(fx) , m_fy(fy) , m_cx(cx) , m_cy(cy) { } double& PinholeCamera::Parameters::k1(void) { return m_k1; } double& PinholeCamera::Parameters::k2(void) { return m_k2; } double& PinholeCamera::Parameters::p1(void) { return m_p1; } double& PinholeCamera::Parameters::p2(void) { return m_p2; } double& PinholeCamera::Parameters::fx(void) { return m_fx; } double& PinholeCamera::Parameters::fy(void) { return m_fy; } double& PinholeCamera::Parameters::cx(void) { return m_cx; } double& PinholeCamera::Parameters::cy(void) { return m_cy; } double PinholeCamera::Parameters::k1(void) const { return m_k1; } double PinholeCamera::Parameters::k2(void) const { return m_k2; } double PinholeCamera::Parameters::p1(void) const { return m_p1; } double PinholeCamera::Parameters::p2(void) const { return m_p2; } double PinholeCamera::Parameters::fx(void) const { return m_fx; } double PinholeCamera::Parameters::fy(void) const { return m_fy; } double PinholeCamera::Parameters::cx(void) const { return m_cx; } double PinholeCamera::Parameters::cy(void) const { return m_cy; } bool PinholeCamera::Parameters::readFromYamlFile(const std::string& filename) { cv::FileStorage fs(filename, cv::FileStorage::READ); if (!fs.isOpened()) { return false; } if (!fs["model_type"].isNone()) { std::string sModelType; fs["model_type"] >> sModelType; if (sModelType.compare("PINHOLE") != 0) { return false; } } m_modelType = PINHOLE; fs["camera_name"] >> m_cameraName; m_imageWidth = static_cast(fs["image_width"]); m_imageHeight = static_cast(fs["image_height"]); cv::FileNode n = fs["distortion_parameters"]; m_k1 = static_cast(n["k1"]); m_k2 = static_cast(n["k2"]); m_p1 = static_cast(n["p1"]); m_p2 = static_cast(n["p2"]); n = fs["projection_parameters"]; m_fx = static_cast(n["fx"]); m_fy = static_cast(n["fy"]); m_cx = static_cast(n["cx"]); m_cy = static_cast(n["cy"]); return true; } void PinholeCamera::Parameters::writeToYamlFile(const std::string& filename) const { cv::FileStorage fs(filename, cv::FileStorage::WRITE); fs << "model_type" << "PINHOLE"; fs << "camera_name" << m_cameraName; fs << "image_width" << m_imageWidth; fs << "image_height" << m_imageHeight; // radial distortion: k1, k2 // tangential distortion: p1, p2 fs << "distortion_parameters"; fs << "{" << "k1" << m_k1 << "k2" << m_k2 << "p1" << m_p1 << "p2" << m_p2 << "}"; // projection: fx, fy, cx, cy fs << "projection_parameters"; fs << "{" << "fx" << m_fx << "fy" << m_fy << "cx" << m_cx << "cy" << m_cy << "}"; fs.release(); } PinholeCamera::Parameters& PinholeCamera::Parameters::operator=(const PinholeCamera::Parameters& other) { if (this != &other) { m_modelType = other.m_modelType; m_cameraName = other.m_cameraName; m_imageWidth = other.m_imageWidth; m_imageHeight = other.m_imageHeight; m_k1 = other.m_k1; m_k2 = other.m_k2; m_p1 = other.m_p1; m_p2 = other.m_p2; m_fx = other.m_fx; m_fy = other.m_fy; m_cx = other.m_cx; m_cy = other.m_cy; } return *this; } std::ostream& operator<< (std::ostream& out, const PinholeCamera::Parameters& params) { out << "Camera Parameters:" << std::endl; out << " model_type " << "PINHOLE" << std::endl; out << " camera_name " << params.m_cameraName << std::endl; out << " image_width " << params.m_imageWidth << std::endl; out << " image_height " << params.m_imageHeight << std::endl; // radial distortion: k1, k2 // tangential distortion: p1, p2 out << "Distortion Parameters" << std::endl; out << " k1 " << params.m_k1 << std::endl << " k2 " << params.m_k2 << std::endl << " p1 " << params.m_p1 << std::endl << " p2 " << params.m_p2 << std::endl; // projection: fx, fy, cx, cy out << "Projection Parameters" << std::endl; out << " fx " << params.m_fx << std::endl << " fy " << params.m_fy << std::endl << " cx " << params.m_cx << std::endl << " cy " << params.m_cy << std::endl; return out; } PinholeCamera::PinholeCamera() : m_inv_K11(1.0) , m_inv_K13(0.0) , m_inv_K22(1.0) , m_inv_K23(0.0) , m_noDistortion(true) { } PinholeCamera::PinholeCamera(const std::string& cameraName, int imageWidth, int imageHeight, double k1, double k2, double p1, double p2, double fx, double fy, double cx, double cy) : mParameters(cameraName, imageWidth, imageHeight, k1, k2, p1, p2, fx, fy, cx, cy) { if ((mParameters.k1() == 0.0) && (mParameters.k2() == 0.0) && (mParameters.p1() == 0.0) && (mParameters.p2() == 0.0)) { m_noDistortion = true; } else { m_noDistortion = false; } // Inverse camera projection matrix parameters m_inv_K11 = 1.0 / mParameters.fx(); m_inv_K13 = -mParameters.cx() / mParameters.fx(); m_inv_K22 = 1.0 / mParameters.fy(); m_inv_K23 = -mParameters.cy() / mParameters.fy(); } PinholeCamera::PinholeCamera(const PinholeCamera::Parameters& params) : mParameters(params) { if ((mParameters.k1() == 0.0) && (mParameters.k2() == 0.0) && (mParameters.p1() == 0.0) && (mParameters.p2() == 0.0)) { m_noDistortion = true; } else { m_noDistortion = false; } // Inverse camera projection matrix parameters m_inv_K11 = 1.0 / mParameters.fx(); m_inv_K13 = -mParameters.cx() / mParameters.fx(); m_inv_K22 = 1.0 / mParameters.fy(); m_inv_K23 = -mParameters.cy() / mParameters.fy(); } Camera::ModelType PinholeCamera::modelType(void) const { return mParameters.modelType(); } const std::string& PinholeCamera::cameraName(void) const { return mParameters.cameraName(); } int PinholeCamera::imageWidth(void) const { return mParameters.imageWidth(); } int PinholeCamera::imageHeight(void) const { return mParameters.imageHeight(); } void PinholeCamera::estimateIntrinsics(const cv::Size& boardSize, const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints) { // Z. Zhang, A Flexible New Technique for Camera Calibration, PAMI 2000 Parameters params = getParameters(); params.k1() = 0.0; params.k2() = 0.0; params.p1() = 0.0; params.p2() = 0.0; double cx = params.imageWidth() / 2.0; double cy = params.imageHeight() / 2.0; params.cx() = cx; params.cy() = cy; size_t nImages = imagePoints.size(); cv::Mat A(nImages * 2, 2, CV_64F); cv::Mat b(nImages * 2, 1, CV_64F); for (size_t i = 0; i < nImages; ++i) { const std::vector& oPoints = objectPoints.at(i); std::vector M(oPoints.size()); for (size_t j = 0; j < M.size(); ++j) { M.at(j) = cv::Point2f(oPoints.at(j).x, oPoints.at(j).y); } cv::Mat H = cv::findHomography(M, imagePoints.at(i)); H.at(0,0) -= H.at(2,0) * cx; H.at(0,1) -= H.at(2,1) * cx; H.at(0,2) -= H.at(2,2) * cx; H.at(1,0) -= H.at(2,0) * cy; H.at(1,1) -= H.at(2,1) * cy; H.at(1,2) -= H.at(2,2) * cy; double h[3], v[3], d1[3], d2[3]; double n[4] = {0,0,0,0}; for (int j = 0; j < 3; ++j) { double t0 = H.at(j,0); double t1 = H.at(j,1); h[j] = t0; v[j] = t1; d1[j] = (t0 + t1) * 0.5; d2[j] = (t0 - t1) * 0.5; n[0] += t0 * t0; n[1] += t1 * t1; n[2] += d1[j] * d1[j]; n[3] += d2[j] * d2[j]; } for (int j = 0; j < 4; ++j) { n[j] = 1.0 / sqrt(n[j]); } for (int j = 0; j < 3; ++j) { h[j] *= n[0]; v[j] *= n[1]; d1[j] *= n[2]; d2[j] *= n[3]; } A.at(i * 2, 0) = h[0] * v[0]; A.at(i * 2, 1) = h[1] * v[1]; A.at(i * 2 + 1, 0) = d1[0] * d2[0]; A.at(i * 2 + 1, 1) = d1[1] * d2[1]; b.at(i * 2, 0) = -h[2] * v[2]; b.at(i * 2 + 1, 0) = -d1[2] * d2[2]; } cv::Mat f(2, 1, CV_64F); cv::solve(A, b, f, cv::DECOMP_NORMAL | cv::DECOMP_LU); params.fx() = sqrt(fabs(1.0 / f.at(0))); params.fy() = sqrt(fabs(1.0 / f.at(1))); setParameters(params); } /** * \brief Lifts a point from the image plane to the unit sphere * * \param p image coordinates * \param P coordinates of the point on the sphere */ void PinholeCamera::liftSphere(const Eigen::Vector2d& p, Eigen::Vector3d& P) const { liftProjective(p, P); P.normalize(); } /** * \brief Lifts a point from the image plane to its projective ray * * \param p image coordinates * \param P coordinates of the projective ray */ void PinholeCamera::liftProjective(const Eigen::Vector2d& p, Eigen::Vector3d& P) const { double mx_d, my_d,mx2_d, mxy_d, my2_d, mx_u, my_u; double rho2_d, rho4_d, radDist_d, Dx_d, Dy_d, inv_denom_d; //double lambda; // Lift points to normalised plane mx_d = m_inv_K11 * p(0) + m_inv_K13; my_d = m_inv_K22 * p(1) + m_inv_K23; if (m_noDistortion) { mx_u = mx_d; my_u = my_d; } else { if (0) { double k1 = mParameters.k1(); double k2 = mParameters.k2(); double p1 = mParameters.p1(); double p2 = mParameters.p2(); // Apply inverse distortion model // proposed by Heikkila mx2_d = mx_d*mx_d; my2_d = my_d*my_d; mxy_d = mx_d*my_d; rho2_d = mx2_d+my2_d; rho4_d = rho2_d*rho2_d; radDist_d = k1*rho2_d+k2*rho4_d; Dx_d = mx_d*radDist_d + p2*(rho2_d+2*mx2_d) + 2*p1*mxy_d; Dy_d = my_d*radDist_d + p1*(rho2_d+2*my2_d) + 2*p2*mxy_d; inv_denom_d = 1/(1+4*k1*rho2_d+6*k2*rho4_d+8*p1*my_d+8*p2*mx_d); mx_u = mx_d - inv_denom_d*Dx_d; my_u = my_d - inv_denom_d*Dy_d; } else { // Recursive distortion model int n = 8; Eigen::Vector2d d_u; distortion(Eigen::Vector2d(mx_d, my_d), d_u); // Approximate value mx_u = mx_d - d_u(0); my_u = my_d - d_u(1); for (int i = 1; i < n; ++i) { distortion(Eigen::Vector2d(mx_u, my_u), d_u); mx_u = mx_d - d_u(0); my_u = my_d - d_u(1); } } } // Obtain a projective ray P << mx_u, my_u, 1.0; } /** * \brief Project a 3D point (\a x,\a y,\a z) to the image plane in (\a u,\a v) * * \param P 3D point coordinates * \param p return value, contains the image point coordinates */ void PinholeCamera::spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p) const { Eigen::Vector2d p_u, p_d; // Project points to the normalised plane p_u << P(0) / P(2), P(1) / P(2); if (m_noDistortion) { p_d = p_u; } else { // Apply distortion Eigen::Vector2d d_u; distortion(p_u, d_u); p_d = p_u + d_u; } // Apply generalised projection matrix p << mParameters.fx() * p_d(0) + mParameters.cx(), mParameters.fy() * p_d(1) + mParameters.cy(); } #if 0 /** * \brief Project a 3D point to the image plane and calculate Jacobian * * \param P 3D point coordinates * \param p return value, contains the image point coordinates */ void PinholeCamera::spaceToPlane(const Eigen::Vector3d& P, Eigen::Vector2d& p, Eigen::Matrix& J) const { Eigen::Vector2d p_u, p_d; double norm, inv_denom; double dxdmx, dydmx, dxdmy, dydmy; norm = P.norm(); // Project points to the normalised plane inv_denom = 1.0 / P(2); p_u << inv_denom * P(0), inv_denom * P(1); // Calculate jacobian double dudx = inv_denom; double dvdx = 0.0; double dudy = 0.0; double dvdy = inv_denom; inv_denom = - inv_denom * inv_denom; double dudz = P(0) * inv_denom; double dvdz = P(1) * inv_denom; if (m_noDistortion) { p_d = p_u; } else { // Apply distortion Eigen::Vector2d d_u; distortion(p_u, d_u); p_d = p_u + d_u; } double fx = mParameters.fx(); double fy = mParameters.fy(); // Make the product of the jacobians // and add projection matrix jacobian inv_denom = fx * (dudx * dxdmx + dvdx * dxdmy); // reuse dvdx = fy * (dudx * dydmx + dvdx * dydmy); dudx = inv_denom; inv_denom = fx * (dudy * dxdmx + dvdy * dxdmy); // reuse dvdy = fy * (dudy * dydmx + dvdy * dydmy); dudy = inv_denom; inv_denom = fx * (dudz * dxdmx + dvdz * dxdmy); // reuse dvdz = fy * (dudz * dydmx + dvdz * dydmy); dudz = inv_denom; // Apply generalised projection matrix p << fx * p_d(0) + mParameters.cx(), fy * p_d(1) + mParameters.cy(); J << dudx, dudy, dudz, dvdx, dvdy, dvdz; } #endif /** * \brief Projects an undistorted 2D point p_u to the image plane * * \param p_u 2D point coordinates * \return image point coordinates */ void PinholeCamera::undistToPlane(const Eigen::Vector2d& p_u, Eigen::Vector2d& p) const { Eigen::Vector2d p_d; if (m_noDistortion) { p_d = p_u; } else { // Apply distortion Eigen::Vector2d d_u; distortion(p_u, d_u); p_d = p_u + d_u; } // Apply generalised projection matrix p << mParameters.fx() * p_d(0) + mParameters.cx(), mParameters.fy() * p_d(1) + mParameters.cy(); } /** * \brief Apply distortion to input point (from the normalised plane) * * \param p_u undistorted coordinates of point on the normalised plane * \return to obtain the distorted point: p_d = p_u + d_u */ void PinholeCamera::distortion(const Eigen::Vector2d& p_u, Eigen::Vector2d& d_u) const { double k1 = mParameters.k1(); double k2 = mParameters.k2(); double p1 = mParameters.p1(); double p2 = mParameters.p2(); double mx2_u, my2_u, mxy_u, rho2_u, rad_dist_u; mx2_u = p_u(0) * p_u(0); my2_u = p_u(1) * p_u(1); mxy_u = p_u(0) * p_u(1); rho2_u = mx2_u + my2_u; rad_dist_u = k1 * rho2_u + k2 * rho2_u * rho2_u; d_u << p_u(0) * rad_dist_u + 2.0 * p1 * mxy_u + p2 * (rho2_u + 2.0 * mx2_u), p_u(1) * rad_dist_u + 2.0 * p2 * mxy_u + p1 * (rho2_u + 2.0 * my2_u); } /** * \brief Apply distortion to input point (from the normalised plane) * and calculate Jacobian * * \param p_u undistorted coordinates of point on the normalised plane * \return to obtain the distorted point: p_d = p_u + d_u */ void PinholeCamera::distortion(const Eigen::Vector2d& p_u, Eigen::Vector2d& d_u, Eigen::Matrix2d& J) const { double k1 = mParameters.k1(); double k2 = mParameters.k2(); double p1 = mParameters.p1(); double p2 = mParameters.p2(); double mx2_u, my2_u, mxy_u, rho2_u, rad_dist_u; mx2_u = p_u(0) * p_u(0); my2_u = p_u(1) * p_u(1); mxy_u = p_u(0) * p_u(1); rho2_u = mx2_u + my2_u; rad_dist_u = k1 * rho2_u + k2 * rho2_u * rho2_u; d_u << p_u(0) * rad_dist_u + 2.0 * p1 * mxy_u + p2 * (rho2_u + 2.0 * mx2_u), p_u(1) * rad_dist_u + 2.0 * p2 * mxy_u + p1 * (rho2_u + 2.0 * my2_u); double dxdmx = 1.0 + rad_dist_u + k1 * 2.0 * mx2_u + k2 * rho2_u * 4.0 * mx2_u + 2.0 * p1 * p_u(1) + 6.0 * p2 * p_u(0); double dydmx = k1 * 2.0 * p_u(0) * p_u(1) + k2 * 4.0 * rho2_u * p_u(0) * p_u(1) + p1 * 2.0 * p_u(0) + 2.0 * p2 * p_u(1); double dxdmy = dydmx; double dydmy = 1.0 + rad_dist_u + k1 * 2.0 * my2_u + k2 * rho2_u * 4.0 * my2_u + 6.0 * p1 * p_u(1) + 2.0 * p2 * p_u(0); J << dxdmx, dxdmy, dydmx, dydmy; } void PinholeCamera::initUndistortMap(cv::Mat& map1, cv::Mat& map2, double fScale) const { cv::Size imageSize(mParameters.imageWidth(), mParameters.imageHeight()); cv::Mat mapX = cv::Mat::zeros(imageSize, CV_32F); cv::Mat mapY = cv::Mat::zeros(imageSize, CV_32F); for (int v = 0; v < imageSize.height; ++v) { for (int u = 0; u < imageSize.width; ++u) { double mx_u = m_inv_K11 / fScale * u + m_inv_K13 / fScale; double my_u = m_inv_K22 / fScale * v + m_inv_K23 / fScale; Eigen::Vector3d P; P << mx_u, my_u, 1.0; Eigen::Vector2d p; spaceToPlane(P, p); mapX.at(v,u) = p(0); mapY.at(v,u) = p(1); } } cv::convertMaps(mapX, mapY, map1, map2, CV_32FC1, false); } cv::Mat PinholeCamera::initUndistortRectifyMap(cv::Mat& map1, cv::Mat& map2, float fx, float fy, cv::Size imageSize, float cx, float cy, cv::Mat rmat) const { if (imageSize == cv::Size(0, 0)) { imageSize = cv::Size(mParameters.imageWidth(), mParameters.imageHeight()); } cv::Mat mapX = cv::Mat::zeros(imageSize.height, imageSize.width, CV_32F); cv::Mat mapY = cv::Mat::zeros(imageSize.height, imageSize.width, CV_32F); Eigen::Matrix3f R, R_inv; cv::cv2eigen(rmat, R); R_inv = R.inverse(); // assume no skew Eigen::Matrix3f K_rect; if (cx == -1.0f || cy == -1.0f) { K_rect << fx, 0, imageSize.width / 2, 0, fy, imageSize.height / 2, 0, 0, 1; } else { K_rect << fx, 0, cx, 0, fy, cy, 0, 0, 1; } if (fx == -1.0f || fy == -1.0f) { K_rect(0,0) = mParameters.fx(); K_rect(1,1) = mParameters.fy(); } Eigen::Matrix3f K_rect_inv = K_rect.inverse(); for (int v = 0; v < imageSize.height; ++v) { for (int u = 0; u < imageSize.width; ++u) { Eigen::Vector3f xo; xo << u, v, 1; Eigen::Vector3f uo = R_inv * K_rect_inv * xo; Eigen::Vector2d p; spaceToPlane(uo.cast(), p); mapX.at(v,u) = p(0); mapY.at(v,u) = p(1); } } cv::convertMaps(mapX, mapY, map1, map2, CV_32FC1, false); cv::Mat K_rect_cv; cv::eigen2cv(K_rect, K_rect_cv); return K_rect_cv; } int PinholeCamera::parameterCount(void) const { return 8; } const PinholeCamera::Parameters& PinholeCamera::getParameters(void) const { return mParameters; } void PinholeCamera::setParameters(const PinholeCamera::Parameters& parameters) { mParameters = parameters; if ((mParameters.k1() == 0.0) && (mParameters.k2() == 0.0) && (mParameters.p1() == 0.0) && (mParameters.p2() == 0.0)) { m_noDistortion = true; } else { m_noDistortion = false; } m_inv_K11 = 1.0 / mParameters.fx(); m_inv_K13 = -mParameters.cx() / mParameters.fx(); m_inv_K22 = 1.0 / mParameters.fy(); m_inv_K23 = -mParameters.cy() / mParameters.fy(); } void PinholeCamera::readParameters(const std::vector& parameterVec) { if ((int)parameterVec.size() != parameterCount()) { return; } Parameters params = getParameters(); params.k1() = parameterVec.at(0); params.k2() = parameterVec.at(1); params.p1() = parameterVec.at(2); params.p2() = parameterVec.at(3); params.fx() = parameterVec.at(4); params.fy() = parameterVec.at(5); params.cx() = parameterVec.at(6); params.cy() = parameterVec.at(7); setParameters(params); } void PinholeCamera::writeParameters(std::vector& parameterVec) const { parameterVec.resize(parameterCount()); parameterVec.at(0) = mParameters.k1(); parameterVec.at(1) = mParameters.k2(); parameterVec.at(2) = mParameters.p1(); parameterVec.at(3) = mParameters.p2(); parameterVec.at(4) = mParameters.fx(); parameterVec.at(5) = mParameters.fy(); parameterVec.at(6) = mParameters.cx(); parameterVec.at(7) = mParameters.cy(); } void PinholeCamera::writeParametersToYamlFile(const std::string& filename) const { mParameters.writeToYamlFile(filename); } std::string PinholeCamera::parametersToString(void) const { std::ostringstream oss; oss << mParameters; return oss.str(); } } ================================================ FILE: src/utils/camodocal/src/camera_models/ScaramuzzaCamera.cc ================================================ #include "camodocal/camera_models/ScaramuzzaCamera.h" #include #include #include #include #include #include #include #include #include #include #include #include "camodocal/gpl/gpl.h" Eigen::VectorXd polyfit(Eigen::VectorXd& xVec, Eigen::VectorXd& yVec, int poly_order) { assert(poly_order > 0); assert(xVec.size() > poly_order); assert(xVec.size() == yVec.size()); Eigen::MatrixXd A(xVec.size(), poly_order+1); Eigen::VectorXd B(xVec.size()); for(int i = 0; i < xVec.size(); ++i) { const double x = xVec(i); const double y = yVec(i); double x_pow_k = 1.0; for(int k=0; k<=poly_order; ++k) { A(i,k) = x_pow_k; x_pow_k *= x; } B(i) = y; } Eigen::JacobiSVD svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV); Eigen::VectorXd x = svd.solve(B); return x; } namespace camodocal { OCAMCamera::Parameters::Parameters() : Camera::Parameters(SCARAMUZZA) , m_C(0.0) , m_D(0.0) , m_E(0.0) , m_center_x(0.0) , m_center_y(0.0) { memset(m_poly, 0, sizeof(double) * SCARAMUZZA_POLY_SIZE); memset(m_inv_poly, 0, sizeof(double) * SCARAMUZZA_INV_POLY_SIZE); } bool OCAMCamera::Parameters::readFromYamlFile(const std::string& filename) { cv::FileStorage fs(filename, cv::FileStorage::READ); if (!fs.isOpened()) { return false; } if (!fs["model_type"].isNone()) { std::string sModelType; fs["model_type"] >> sModelType; if (!boost::iequals(sModelType, "scaramuzza")) { return false; } } m_modelType = SCARAMUZZA; fs["camera_name"] >> m_cameraName; m_imageWidth = static_cast(fs["image_width"]); m_imageHeight = static_cast(fs["image_height"]); cv::FileNode n = fs["poly_parameters"]; for(int i=0; i < SCARAMUZZA_POLY_SIZE; i++) m_poly[i] = static_cast(n[std::string("p") + boost::lexical_cast(i)]); n = fs["inv_poly_parameters"]; for(int i=0; i < SCARAMUZZA_INV_POLY_SIZE; i++) m_inv_poly[i] = static_cast(n[std::string("p") + boost::lexical_cast(i)]); n = fs["affine_parameters"]; m_C = static_cast(n["ac"]); m_D = static_cast(n["ad"]); m_E = static_cast(n["ae"]); m_center_x = static_cast(n["cx"]); m_center_y = static_cast(n["cy"]); return true; } void OCAMCamera::Parameters::writeToYamlFile(const std::string& filename) const { cv::FileStorage fs(filename, cv::FileStorage::WRITE); fs << "model_type" << "scaramuzza"; fs << "camera_name" << m_cameraName; fs << "image_width" << m_imageWidth; fs << "image_height" << m_imageHeight; fs << "poly_parameters"; fs << "{"; for(int i=0; i < SCARAMUZZA_POLY_SIZE; i++) fs << std::string("p") + boost::lexical_cast(i) << m_poly[i]; fs << "}"; fs << "inv_poly_parameters"; fs << "{"; for(int i=0; i < SCARAMUZZA_INV_POLY_SIZE; i++) fs << std::string("p") + boost::lexical_cast(i) << m_inv_poly[i]; fs << "}"; fs << "affine_parameters"; fs << "{" << "ac" << m_C << "ad" << m_D << "ae" << m_E << "cx" << m_center_x << "cy" << m_center_y << "}"; fs.release(); } OCAMCamera::Parameters& OCAMCamera::Parameters::operator=(const OCAMCamera::Parameters& other) { if (this != &other) { m_modelType = other.m_modelType; m_cameraName = other.m_cameraName; m_imageWidth = other.m_imageWidth; m_imageHeight = other.m_imageHeight; m_C = other.m_C; m_D = other.m_D; m_E = other.m_E; m_center_x = other.m_center_x; m_center_y = other.m_center_y; memcpy(m_poly, other.m_poly, sizeof(double) * SCARAMUZZA_POLY_SIZE); memcpy(m_inv_poly, other.m_inv_poly, sizeof(double) * SCARAMUZZA_INV_POLY_SIZE); } return *this; } std::ostream& operator<< (std::ostream& out, const OCAMCamera::Parameters& params) { out << "Camera Parameters:" << std::endl; out << " model_type " << "scaramuzza" << std::endl; out << " camera_name " << params.m_cameraName << std::endl; out << " image_width " << params.m_imageWidth << std::endl; out << " image_height " << params.m_imageHeight << std::endl; out << std::fixed << std::setprecision(10); out << "Poly Parameters" << std::endl; for(int i=0; i < SCARAMUZZA_POLY_SIZE; i++) out << std::string("p") + boost::lexical_cast(i) << ": " << params.m_poly[i] << std::endl; out << "Inverse Poly Parameters" << std::endl; for(int i=0; i < SCARAMUZZA_INV_POLY_SIZE; i++) out << std::string("p") + boost::lexical_cast(i) << ": " << params.m_inv_poly[i] << std::endl; out << "Affine Parameters" << std::endl; out << " ac " << params.m_C << std::endl << " ad " << params.m_D << std::endl << " ae " << params.m_E << std::endl; out << " cx " << params.m_center_x << std::endl << " cy " << params.m_center_y << std::endl; return out; } OCAMCamera::OCAMCamera() : m_inv_scale(0.0) { } OCAMCamera::OCAMCamera(const OCAMCamera::Parameters& params) : mParameters(params) { m_inv_scale = 1.0 / (params.C() - params.D() * params.E()); } Camera::ModelType OCAMCamera::modelType(void) const { return mParameters.modelType(); } const std::string& OCAMCamera::cameraName(void) const { return mParameters.cameraName(); } int OCAMCamera::imageWidth(void) const { return mParameters.imageWidth(); } int OCAMCamera::imageHeight(void) const { return mParameters.imageHeight(); } void OCAMCamera::estimateIntrinsics(const cv::Size& boardSize, const std::vector< std::vector >& objectPoints, const std::vector< std::vector >& imagePoints) { // std::cout << "OCAMCamera::estimateIntrinsics - NOT IMPLEMENTED" << std::endl; // throw std::string("OCAMCamera::estimateIntrinsics - NOT IMPLEMENTED"); // Reference: Page 30 of // " Scaramuzza, D. Omnidirectional Vision: from Calibration to Robot Motion Estimation, ETH Zurich. Thesis no. 17635." // http://e-collection.library.ethz.ch/eserv/eth:30301/eth-30301-02.pdf // Matlab code: calibrate.m // First, estimate every image's extrinsics parameters std::vector< Eigen::Matrix3d > RList; std::vector< Eigen::Vector3d > TList; RList.reserve(imagePoints.size()); TList.reserve(imagePoints.size()); // i-th image for (size_t image_index = 0; image_index < imagePoints.size(); ++image_index) { const std::vector& objPts = objectPoints.at(image_index); const std::vector& imgPts = imagePoints.at(image_index); assert(objPts.size() == imgPts.size()); assert(objPts.size() == static_cast(boardSize.width * boardSize.height)); Eigen::MatrixXd M(objPts.size(), 6); for(size_t corner_index = 0; corner_index < objPts.size(); ++corner_index) { double X = objPts.at(corner_index).x; double Y = objPts.at(corner_index).y; assert(objPts.at(corner_index).z==0.0); double u = imgPts.at(corner_index).x; double v = imgPts.at(corner_index).y; M(corner_index, 0) = -v * X; M(corner_index, 1) = -v * Y; M(corner_index, 2) = u * X; M(corner_index, 3) = u * Y; M(corner_index, 4) = -v; M(corner_index, 5) = u; } Eigen::JacobiSVD svd(M, Eigen::ComputeFullU | Eigen::ComputeFullV); assert(svd.matrixV().cols() == 6); Eigen::VectorXd h = -svd.matrixV().col(5); // scaled version of R and T const double sr11 = h(0); const double sr12 = h(1); const double sr21 = h(2); const double sr22 = h(3); const double st1 = h(4); const double st2 = h(5); const double AA = square(sr11*sr12 + sr21*sr22); const double BB = square(sr11) + square(sr21); const double CC = square(sr12) + square(sr22); const double sr32_squared_1 = (- (CC-BB) + sqrt(square(CC-BB) + 4.0 * AA)) / 2.0; const double sr32_squared_2 = (- (CC-BB) - sqrt(square(CC-BB) + 4.0 * AA)) / 2.0; // printf("rst = %.12f\n", sr32_squared_1*sr32_squared_1 + (CC-BB)*sr32_squared_1 - AA); std::vector sr32_squared_values; if (sr32_squared_1 > 0) sr32_squared_values.push_back(sr32_squared_1); if (sr32_squared_2 > 0) sr32_squared_values.push_back(sr32_squared_2); assert(!sr32_squared_values.empty()); std::vector sr32_values; std::vector sr31_values; for (auto sr32_squared : sr32_squared_values) { for(int sign = -1; sign <= 1; sign += 2) { const double sr32 = static_cast(sign) * std::sqrt(sr32_squared); sr32_values.push_back( sr32 ); if (sr32_squared == 0.0) { // sr31 can be calculated through norm equality, // but it has positive and negative posibilities // positive one sr31_values.push_back(std::sqrt(CC-BB)); // negative one sr32_values.push_back( sr32 ); sr31_values.push_back(-std::sqrt(CC-BB)); break; // skip the same situation } else { // sr31 can be calculated throught dot product == 0 sr31_values.push_back(- (sr11*sr12 + sr21*sr22) / sr32); } } } // std::cout << "h= " << std::setprecision(12) << h.transpose() << std::endl; // std::cout << "length: " << sr32_values.size() << " & " << sr31_values.size() << std::endl; assert(!sr31_values.empty()); assert(sr31_values.size() == sr32_values.size()); std::vector H_values; for(size_t i=0;i(v,u) = p(0); mapY.at(v,u) = p(1); } } cv::convertMaps(mapX, mapY, map1, map2, CV_32FC1, false); } #endif cv::Mat OCAMCamera::initUndistortRectifyMap(cv::Mat& map1, cv::Mat& map2, float fx, float fy, cv::Size imageSize, float cx, float cy, cv::Mat rmat) const { if (imageSize == cv::Size(0, 0)) { imageSize = cv::Size(mParameters.imageWidth(), mParameters.imageHeight()); } cv::Mat mapX = cv::Mat::zeros(imageSize.height, imageSize.width, CV_32F); cv::Mat mapY = cv::Mat::zeros(imageSize.height, imageSize.width, CV_32F); Eigen::Matrix3f K_rect; K_rect << fx, 0, cx < 0 ? imageSize.width / 2 : cx, 0, fy, cy < 0 ? imageSize.height / 2 : cy, 0, 0, 1; if (fx < 0 || fy < 0) { throw std::string(std::string(__FUNCTION__) + ": Focal length must be specified"); } Eigen::Matrix3f K_rect_inv = K_rect.inverse(); Eigen::Matrix3f R, R_inv; cv::cv2eigen(rmat, R); R_inv = R.inverse(); for (int v = 0; v < imageSize.height; ++v) { for (int u = 0; u < imageSize.width; ++u) { Eigen::Vector3f xo; xo << u, v, 1; Eigen::Vector3f uo = R_inv * K_rect_inv * xo; Eigen::Vector2d p; spaceToPlane(uo.cast(), p); mapX.at(v,u) = p(0); mapY.at(v,u) = p(1); } } cv::convertMaps(mapX, mapY, map1, map2, CV_32FC1, false); cv::Mat K_rect_cv; cv::eigen2cv(K_rect, K_rect_cv); return K_rect_cv; } int OCAMCamera::parameterCount(void) const { return SCARAMUZZA_CAMERA_NUM_PARAMS; } const OCAMCamera::Parameters& OCAMCamera::getParameters(void) const { return mParameters; } void OCAMCamera::setParameters(const OCAMCamera::Parameters& parameters) { mParameters = parameters; m_inv_scale = 1.0 / (parameters.C() - parameters.D() * parameters.E()); } void OCAMCamera::readParameters(const std::vector& parameterVec) { if ((int)parameterVec.size() != parameterCount()) { return; } Parameters params = getParameters(); params.C() = parameterVec.at(0); params.D() = parameterVec.at(1); params.E() = parameterVec.at(2); params.center_x() = parameterVec.at(3); params.center_y() = parameterVec.at(4); for (int i=0; i < SCARAMUZZA_POLY_SIZE; i++) params.poly(i) = parameterVec.at(5+i); for (int i=0; i < SCARAMUZZA_INV_POLY_SIZE; i++) params.inv_poly(i) = parameterVec.at(5 + SCARAMUZZA_POLY_SIZE + i); setParameters(params); } void OCAMCamera::writeParameters(std::vector& parameterVec) const { parameterVec.resize(parameterCount()); parameterVec.at(0) = mParameters.C(); parameterVec.at(1) = mParameters.D(); parameterVec.at(2) = mParameters.E(); parameterVec.at(3) = mParameters.center_x(); parameterVec.at(4) = mParameters.center_y(); for (int i=0; i < SCARAMUZZA_POLY_SIZE; i++) parameterVec.at(5+i) = mParameters.poly(i); for (int i=0; i < SCARAMUZZA_INV_POLY_SIZE; i++) parameterVec.at(5 + SCARAMUZZA_POLY_SIZE + i) = mParameters.inv_poly(i); } void OCAMCamera::writeParametersToYamlFile(const std::string& filename) const { mParameters.writeToYamlFile(filename); } std::string OCAMCamera::parametersToString(void) const { std::ostringstream oss; oss << mParameters; return oss.str(); } } ================================================ FILE: src/utils/camodocal/src/chessboard/Chessboard.cc ================================================ #include "camodocal/chessboard/Chessboard.h" #include #include #include "camodocal/chessboard/ChessboardQuad.h" #include "camodocal/chessboard/Spline.h" #define MAX_CONTOUR_APPROX 7 namespace camodocal { Chessboard::Chessboard(cv::Size boardSize, cv::Mat& image) : mBoardSize(boardSize) , mCornersFound(false) { if (image.channels() == 1) { cv::cvtColor(image, mSketch, CV_GRAY2BGR); image.copyTo(mImage); } else { image.copyTo(mSketch); cv::cvtColor(image, mImage, CV_BGR2GRAY); } } void Chessboard::findCorners(bool useOpenCV) { mCornersFound = findChessboardCorners(mImage, mBoardSize, mCorners, CV_CALIB_CB_ADAPTIVE_THRESH + CV_CALIB_CB_NORMALIZE_IMAGE + CV_CALIB_CB_FILTER_QUADS + CV_CALIB_CB_FAST_CHECK, useOpenCV); if (mCornersFound) { // draw chessboard corners cv::drawChessboardCorners(mSketch, mBoardSize, mCorners, mCornersFound); } } const std::vector& Chessboard::getCorners(void) const { return mCorners; } bool Chessboard::cornersFound(void) const { return mCornersFound; } const cv::Mat& Chessboard::getImage(void) const { return mImage; } const cv::Mat& Chessboard::getSketch(void) const { return mSketch; } bool Chessboard::findChessboardCorners(const cv::Mat& image, const cv::Size& patternSize, std::vector& corners, int flags, bool useOpenCV) { if (useOpenCV) { return cv::findChessboardCorners(image, patternSize, corners, flags); } else { return findChessboardCornersImproved(image, patternSize, corners, flags); } } bool Chessboard::findChessboardCornersImproved(const cv::Mat& image, const cv::Size& patternSize, std::vector& corners, int flags) { /************************************************************************************\ This is improved variant of chessboard corner detection algorithm that uses a graph of connected quads. It is based on the code contributed by Vladimir Vezhnevets and Philip Gruebele. Here is the copyright notice from the original Vladimir's code: =============================================================== The algorithms developed and implemented by Vezhnevets Vldimir aka Dead Moroz (vvp@graphics.cs.msu.ru) See http://graphics.cs.msu.su/en/research/calibration/opencv.html for detailed information. Reliability additions and modifications made by Philip Gruebele. pgruebele@cox.net Some improvements were made by: 1) Martin Rufli - increased chance of correct corner matching Rufli, M., Scaramuzza, D., and Siegwart, R. (2008), Automatic Detection of Checkerboards on Blurred and Distorted Images, Proceedings of the IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS 2008), Nice, France, September 2008. 2) Lionel Heng - post-detection checks and better thresholding \************************************************************************************/ //int bestDilation = -1; const int minDilations = 0; const int maxDilations = 7; std::vector outputQuadGroup; if (image.depth() != CV_8U || image.channels() == 2) { return false; } if (patternSize.width < 2 || patternSize.height < 2) { return false; } if (patternSize.width > 127 || patternSize.height > 127) { return false; } cv::Mat img = image; // Image histogram normalization and // BGR to Grayscale image conversion (if applicable) // MARTIN: Set to "false" if (image.channels() != 1 || (flags & CV_CALIB_CB_NORMALIZE_IMAGE)) { cv::Mat norm_img(image.rows, image.cols, CV_8UC1); if (image.channels() != 1) { cv::cvtColor(image, norm_img, CV_BGR2GRAY); img = norm_img; } if (flags & CV_CALIB_CB_NORMALIZE_IMAGE) { cv::equalizeHist(image, norm_img); img = norm_img; } } if (flags & CV_CALIB_CB_FAST_CHECK) { if (!checkChessboard(img, patternSize)) { return false; } } // PART 1: FIND LARGEST PATTERN //----------------------------------------------------------------------- // Checker patterns are tried to be found by dilating the background and // then applying a canny edge finder on the closed contours (checkers). // Try one dilation run, but if the pattern is not found, repeat until // max_dilations is reached. int prevSqrSize = 0; bool found = false; std::vector outputCorners; for (int k = 0; k < 6; ++k) { for (int dilations = minDilations; dilations <= maxDilations; ++dilations) { if (found) { break; } cv::Mat thresh_img; // convert the input grayscale image to binary (black-n-white) if (flags & CV_CALIB_CB_ADAPTIVE_THRESH) { int blockSize = lround(prevSqrSize == 0 ? std::min(img.cols,img.rows)*(k%2 == 0 ? 0.2 : 0.1): prevSqrSize*2)|1; // convert to binary cv::adaptiveThreshold(img, thresh_img, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, blockSize, (k/2)*5); } else { // empiric threshold level double mean = (cv::mean(img))[0]; int thresh_level = lround(mean - 10); thresh_level = std::max(thresh_level, 10); cv::threshold(img, thresh_img, thresh_level, 255, CV_THRESH_BINARY); } // MARTIN's Code // Use both a rectangular and a cross kernel. In this way, a more // homogeneous dilation is performed, which is crucial for small, // distorted checkers. Use the CROSS kernel first, since its action // on the image is more subtle cv::Mat kernel1 = cv::getStructuringElement(CV_SHAPE_CROSS, cv::Size(3,3), cv::Point(1,1)); cv::Mat kernel2 = cv::getStructuringElement(CV_SHAPE_RECT, cv::Size(3,3), cv::Point(1,1)); if (dilations >= 1) cv::dilate(thresh_img, thresh_img, kernel1); if (dilations >= 2) cv::dilate(thresh_img, thresh_img, kernel2); if (dilations >= 3) cv::dilate(thresh_img, thresh_img, kernel1); if (dilations >= 4) cv::dilate(thresh_img, thresh_img, kernel2); if (dilations >= 5) cv::dilate(thresh_img, thresh_img, kernel1); if (dilations >= 6) cv::dilate(thresh_img, thresh_img, kernel2); // In order to find rectangles that go to the edge, we draw a white // line around the image edge. Otherwise FindContours will miss those // clipped rectangle contours. The border color will be the image mean, // because otherwise we risk screwing up filters like cvSmooth() cv::rectangle(thresh_img, cv::Point(0,0), cv::Point(thresh_img.cols - 1, thresh_img.rows - 1), CV_RGB(255,255,255), 3, 8); // Generate quadrangles in the following function std::vector quads; generateQuads(quads, thresh_img, flags, dilations, true); if (quads.empty()) { continue; } // The following function finds and assigns neighbor quads to every // quadrangle in the immediate vicinity fulfilling certain // prerequisites findQuadNeighbors(quads, dilations); // The connected quads will be organized in groups. The following loop // increases a "group_idx" identifier. // The function "findConnectedQuads assigns all connected quads // a unique group ID. // If more quadrangles were assigned to a given group (i.e. connected) // than are expected by the input variable "patternSize", the // function "cleanFoundConnectedQuads" erases the surplus // quadrangles by minimizing the convex hull of the remaining pattern. for (int group_idx = 0; ; ++group_idx) { std::vector quadGroup; findConnectedQuads(quads, quadGroup, group_idx, dilations); if (quadGroup.empty()) { break; } cleanFoundConnectedQuads(quadGroup, patternSize); // The following function labels all corners of every quad // with a row and column entry. // "count" specifies the number of found quads in "quad_group" // with group identifier "group_idx" // The last parameter is set to "true", because this is the // first function call and some initializations need to be // made. labelQuadGroup(quadGroup, patternSize, true); found = checkQuadGroup(quadGroup, outputCorners, patternSize); float sumDist = 0; int total = 0; for (int i = 0; i < (int)outputCorners.size(); ++i) { int ni = 0; float avgi = outputCorners.at(i)->meanDist(ni); sumDist += avgi * ni; total += ni; } prevSqrSize = lround(sumDist / std::max(total, 1)); if (found && !checkBoardMonotony(outputCorners, patternSize)) { found = false; } } } } if (!found) { return false; } else { corners.clear(); corners.reserve(outputCorners.size()); for (size_t i = 0; i < outputCorners.size(); ++i) { corners.push_back(outputCorners.at(i)->pt); } cv::cornerSubPix(image, corners, cv::Size(11, 11), cv::Size(-1,-1), cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1)); return true; } } //=========================================================================== // ERASE OVERHEAD //=========================================================================== // If we found too many connected quads, remove those which probably do not // belong. void Chessboard::cleanFoundConnectedQuads(std::vector& quadGroup, cv::Size patternSize) { cv::Point2f center(0.0f, 0.0f); // Number of quads this pattern should contain int count = ((patternSize.width + 1)*(patternSize.height + 1) + 1)/2; // If we have more quadrangles than we should, try to eliminate duplicates // or ones which don't belong to the pattern rectangle. Else go to the end // of the function if ((int)quadGroup.size() <= count) { return; } // Create an array of quadrangle centers std::vector centers; centers.resize(quadGroup.size()); for (size_t i = 0; i < quadGroup.size(); ++i) { cv::Point2f ci(0.0f, 0.0f); ChessboardQuadPtr& q = quadGroup[i]; for (int j = 0; j < 4; ++j) { ci += q->corners[j]->pt; } ci *= 0.25f; // Centers(i), is the geometric center of quad(i) // Center, is the center of all found quads centers[i] = ci; center += ci; } center *= 1.0f / quadGroup.size(); // If we have more quadrangles than we should, we try to eliminate bad // ones based on minimizing the bounding box. We iteratively remove the // point which reduces the size of the bounding box of the blobs the most // (since we want the rectangle to be as small as possible) remove the // quadrange that causes the biggest reduction in pattern size until we // have the correct number while ((int)quadGroup.size() > count) { double minBoxArea = DBL_MAX; int minBoxAreaIndex = -1; // For each point, calculate box area without that point for (size_t skip = 0; skip < quadGroup.size(); ++skip) { // get bounding rectangle cv::Point2f temp = centers[skip]; centers[skip] = center; std::vector hull; cv::convexHull(centers, hull, true, true); centers[skip] = temp; double hull_area = fabs(cv::contourArea(hull)); // remember smallest box area if (hull_area < minBoxArea) { minBoxArea = hull_area; minBoxAreaIndex = skip; } } ChessboardQuadPtr& q0 = quadGroup[minBoxAreaIndex]; // remove any references to this quad as a neighbor for (size_t i = 0; i < quadGroup.size(); ++i) { ChessboardQuadPtr& q = quadGroup.at(i); for (int j = 0; j < 4; ++j) { if (q->neighbors[j] == q0) { q->neighbors[j].reset(); q->count--; for (int k = 0; k < 4; ++k) { if (q0->neighbors[k] == q) { q0->neighbors[k].reset(); q0->count--; break; } } break; } } } // remove the quad quadGroup.at(minBoxAreaIndex) = quadGroup.back(); centers.at(minBoxAreaIndex) = centers.back(); quadGroup.pop_back(); centers.pop_back(); } } //=========================================================================== // FIND COONECTED QUADS //=========================================================================== void Chessboard::findConnectedQuads(std::vector& quads, std::vector& group, int group_idx, int dilation) { ChessboardQuadPtr q; // Scan the array for a first unlabeled quad for (size_t i = 0; i < quads.size(); ++i) { ChessboardQuadPtr& quad = quads.at(i); if (quad->count > 0 && quad->group_idx < 0) { q = quad; break; } } if (q.get() == 0) { return; } // Recursively find a group of connected quads starting from the seed quad std::vector stack; stack.push_back(q); group.push_back(q); q->group_idx = group_idx; while (!stack.empty()) { q = stack.back(); stack.pop_back(); for (int i = 0; i < 4; ++i) { ChessboardQuadPtr& neighbor = q->neighbors[i]; // If he neighbor exists and the neighbor has more than 0 // neighbors and the neighbor has not been classified yet. if (neighbor.get() && neighbor->count > 0 && neighbor->group_idx < 0) { stack.push_back(neighbor); group.push_back(neighbor); neighbor->group_idx = group_idx; } } } } void Chessboard::labelQuadGroup(std::vector& quadGroup, cv::Size patternSize, bool firstRun) { // If this is the first function call, a seed quad needs to be selected if (firstRun) { // Search for the (first) quad with the maximum number of neighbors // (usually 4). This will be our starting point. int mark = -1; int maxNeighborCount = 0; for (size_t i = 0; i < quadGroup.size(); ++i) { ChessboardQuadPtr& q = quadGroup.at(i); if (q->count > maxNeighborCount) { mark = i; maxNeighborCount = q->count; if (maxNeighborCount == 4) { break; } } } // Mark the starting quad's (per definition) upper left corner with //(0,0) and then proceed clockwise // The following labeling sequence enshures a "right coordinate system" ChessboardQuadPtr& q = quadGroup.at(mark); q->labeled = true; q->corners[0]->row = 0; q->corners[0]->column = 0; q->corners[1]->row = 0; q->corners[1]->column = 1; q->corners[2]->row = 1; q->corners[2]->column = 1; q->corners[3]->row = 1; q->corners[3]->column = 0; } // Mark all other corners with their respective row and column bool flagChanged = true; while (flagChanged) { // First reset the flag to "false" flagChanged = false; // Go through all quads top down is faster, since unlabeled quads will // be inserted at the end of the list for (int i = quadGroup.size() - 1; i >= 0; --i) { ChessboardQuadPtr& quad = quadGroup.at(i); // Check whether quad "i" has been labeled already if (!quad->labeled) { // Check its neighbors, whether some of them have been labeled // already for (int j = 0; j < 4; j++) { // Check whether the neighbor exists (i.e. is not the NULL // pointer) if (quad->neighbors[j]) { ChessboardQuadPtr& quadNeighbor = quad->neighbors[j]; // Only proceed, if neighbor "j" was labeled if (quadNeighbor->labeled) { // For every quad it could happen to pass here // multiple times. We therefore "break" later. // Check whitch of the neighbors corners is // connected to the current quad int connectedNeighborCornerId = -1; for (int k = 0; k < 4; ++k) { if (quadNeighbor->neighbors[k] == quad) { connectedNeighborCornerId = k; // there is only one, therefore break; } } // For the following calculations we need the row // and column of the connected neighbor corner and // all other corners of the connected quad "j", // clockwise (CW) ChessboardCornerPtr& conCorner = quadNeighbor->corners[connectedNeighborCornerId]; ChessboardCornerPtr& conCornerCW1 = quadNeighbor->corners[(connectedNeighborCornerId+1)%4]; ChessboardCornerPtr& conCornerCW2 = quadNeighbor->corners[(connectedNeighborCornerId+2)%4]; ChessboardCornerPtr& conCornerCW3 = quadNeighbor->corners[(connectedNeighborCornerId+3)%4]; quad->corners[j]->row = conCorner->row; quad->corners[j]->column = conCorner->column; quad->corners[(j+1)%4]->row = conCorner->row - conCornerCW2->row + conCornerCW3->row; quad->corners[(j+1)%4]->column = conCorner->column - conCornerCW2->column + conCornerCW3->column; quad->corners[(j+2)%4]->row = conCorner->row + conCorner->row - conCornerCW2->row; quad->corners[(j+2)%4]->column = conCorner->column + conCorner->column - conCornerCW2->column; quad->corners[(j+3)%4]->row = conCorner->row - conCornerCW2->row + conCornerCW1->row; quad->corners[(j+3)%4]->column = conCorner->column - conCornerCW2->column + conCornerCW1->column; // Mark this quad as labeled quad->labeled = true; // Changes have taken place, set the flag flagChanged = true; // once is enough! break; } } } } } } // All corners are marked with row and column // Record the minimal and maximal row and column indices // It is unlikely that more than 8bit checkers are used per dimension, if there are // an error would have been thrown at the beginning of "cvFindChessboardCorners2" int min_row = 127; int max_row = -127; int min_column = 127; int max_column = -127; for (int i = 0; i < (int)quadGroup.size(); ++i) { ChessboardQuadPtr& q = quadGroup.at(i); for (int j = 0; j < 4; ++j) { ChessboardCornerPtr& c = q->corners[j]; if (c->row > max_row) { max_row = c->row; } if (c->row < min_row) { min_row = c->row; } if (c->column > max_column) { max_column = c->column; } if (c->column < min_column) { min_column = c->column; } } } // Label all internal corners with "needsNeighbor" = false // Label all external corners with "needsNeighbor" = true, // except if in a given dimension the pattern size is reached for (int i = min_row; i <= max_row; ++i) { for (int j = min_column; j <= max_column; ++j) { // A flag that indicates, whether a row/column combination is // executed multiple times bool flag = false; // Remember corner and quad int cornerID; int quadID; for (int k = 0; k < (int)quadGroup.size(); ++k) { ChessboardQuadPtr& q = quadGroup.at(k); for (int l = 0; l < 4; ++l) { if ((q->corners[l]->row == i) && (q->corners[l]->column == j)) { if (flag) { // Passed at least twice through here q->corners[l]->needsNeighbor = false; quadGroup[quadID]->corners[cornerID]->needsNeighbor = false; } else { // Mark with needs a neighbor, but note the // address q->corners[l]->needsNeighbor = true; cornerID = l; quadID = k; } // set the flag to true flag = true; } } } } } // Complete Linking: // sometimes not all corners were properly linked in "findQuadNeighbors", // but after labeling each corner with its respective row and column, it is // possible to match them anyway. for (int i = min_row; i <= max_row; ++i) { for (int j = min_column; j <= max_column; ++j) { // the following "number" indicates the number of corners which // correspond to the given (i,j) value // 1 is a border corner or a conrer which still needs a neighbor // 2 is a fully connected internal corner // >2 something went wrong during labeling, report a warning int number = 1; // remember corner and quad int cornerID; int quadID; for (int k = 0; k < (int)quadGroup.size(); ++k) { ChessboardQuadPtr& q = quadGroup.at(k); for (int l = 0; l < 4; ++l) { if ((q->corners[l]->row == i) && (q->corners[l]->column == j)) { if (number == 1) { // First corner, note its ID cornerID = l; quadID = k; } else if (number == 2) { // Second corner, check wheter this and the // first one have equal coordinates, else // interpolate cv::Point2f delta = q->corners[l]->pt - quadGroup[quadID]->corners[cornerID]->pt; if (delta.x != 0.0f || delta.y != 0.0f) { // Interpolate q->corners[l]->pt -= delta * 0.5f; quadGroup[quadID]->corners[cornerID]->pt += delta * 0.5f; } } else if (number > 2) { // Something went wrong during row/column labeling // Report a Warning // ->Implemented in the function "mrWriteCorners" } // increase the number by one ++number; } } } } } // Bordercorners don't need any neighbors, if the pattern size in the // respective direction is reached // The only time we can make shure that the target pattern size is reached in a given // dimension, is when the larger side has reached the target size in the maximal // direction, or if the larger side is larger than the smaller target size and the // smaller side equals the smaller target size int largerDimPattern = std::max(patternSize.height,patternSize.width); int smallerDimPattern = std::min(patternSize.height,patternSize.width); bool flagSmallerDim1 = false; bool flagSmallerDim2 = false; if ((largerDimPattern + 1) == max_column - min_column) { flagSmallerDim1 = true; // We found out that in the column direction the target pattern size is reached // Therefore border column corners do not need a neighbor anymore // Go through all corners for (int k = 0; k < (int)quadGroup.size(); ++k) { ChessboardQuadPtr& q = quadGroup.at(k); for (int l = 0; l < 4; ++l) { ChessboardCornerPtr& c = q->corners[l]; if (c->column == min_column || c->column == max_column) { // Needs no neighbor anymore c->needsNeighbor = false; } } } } if ((largerDimPattern + 1) == max_row - min_row) { flagSmallerDim2 = true; // We found out that in the column direction the target pattern size is reached // Therefore border column corners do not need a neighbor anymore // Go through all corners for (int k = 0; k < (int)quadGroup.size(); ++k) { ChessboardQuadPtr& q = quadGroup.at(k); for (int l = 0; l < 4; ++l) { ChessboardCornerPtr& c = q->corners[l]; if (c->row == min_row || c->row == max_row) { // Needs no neighbor anymore c->needsNeighbor = false; } } } } // Check the two flags: // - If one is true and the other false, then the pattern target // size was reached in in one direction -> We can check, whether the target // pattern size is also reached in the other direction // - If both are set to true, then we deal with a square board -> do nothing // - If both are set to false -> There is a possibility that the larger side is // larger than the smaller target size -> Check and if true, then check whether // the other side has the same size as the smaller target size if ((flagSmallerDim1 == false && flagSmallerDim2 == true)) { // Larger target pattern size is in row direction, check wheter smaller target // pattern size is reached in column direction if ((smallerDimPattern + 1) == max_column - min_column) { for (int k = 0; k < (int)quadGroup.size(); ++k) { ChessboardQuadPtr& q = quadGroup.at(k); for (int l = 0; l < 4; ++l) { ChessboardCornerPtr& c = q->corners[l]; if (c->column == min_column || c->column == max_column) { // Needs no neighbor anymore c->needsNeighbor = false; } } } } } if ((flagSmallerDim1 == true && flagSmallerDim2 == false)) { // Larger target pattern size is in column direction, check wheter smaller target // pattern size is reached in row direction if ((smallerDimPattern + 1) == max_row - min_row) { for (int k = 0; k < (int)quadGroup.size(); ++k) { ChessboardQuadPtr& q = quadGroup.at(k); for (int l = 0; l < 4; ++l) { ChessboardCornerPtr& c = q->corners[l]; if (c->row == min_row || c->row == max_row) { // Needs no neighbor anymore c->needsNeighbor = false; } } } } } if ((flagSmallerDim1 == false && flagSmallerDim2 == false) && smallerDimPattern + 1 < max_column - min_column) { // Larger target pattern size is in column direction, check wheter smaller target // pattern size is reached in row direction if ((smallerDimPattern + 1) == max_row - min_row) { for (int k = 0; k < (int)quadGroup.size(); ++k) { ChessboardQuadPtr& q = quadGroup.at(k); for (int l = 0; l < 4; ++l) { ChessboardCornerPtr& c = q->corners[l]; if (c->row == min_row || c->row == max_row) { // Needs no neighbor anymore c->needsNeighbor = false; } } } } } if ((flagSmallerDim1 == false && flagSmallerDim2 == false) && smallerDimPattern + 1 < max_row - min_row) { // Larger target pattern size is in row direction, check wheter smaller target // pattern size is reached in column direction if ((smallerDimPattern + 1) == max_column - min_column) { for (int k = 0; k < (int)quadGroup.size(); ++k) { ChessboardQuadPtr& q = quadGroup.at(k); for (int l = 0; l < 4; ++l) { ChessboardCornerPtr& c = q->corners[l]; if (c->column == min_column || c->column == max_column) { // Needs no neighbor anymore c->needsNeighbor = false; } } } } } } //=========================================================================== // GIVE A GROUP IDX //=========================================================================== void Chessboard::findQuadNeighbors(std::vector& quads, int dilation) { // Thresh dilation is used to counter the effect of dilation on the // distance between 2 neighboring corners. Since the distance below is // computed as its square, we do here the same. Additionally, we take the // conservative assumption that dilation was performed using the 3x3 CROSS // kernel, which coresponds to the 4-neighborhood. const float thresh_dilation = (float)(2*dilation+3)*(2*dilation+3)*2; // the "*2" is for the x and y component // the "3" is for initial corner mismatch // Find quad neighbors for (size_t idx = 0; idx < quads.size(); ++idx) { ChessboardQuadPtr& curQuad = quads.at(idx); // Go through all quadrangles and label them in groups // For each corner of this quadrangle for (int i = 0; i < 4; ++i) { float minDist = FLT_MAX; int closestCornerIdx = -1; ChessboardQuadPtr closestQuad; if (curQuad->neighbors[i]) { continue; } cv::Point2f pt = curQuad->corners[i]->pt; // Find the closest corner in all other quadrangles for (size_t k = 0; k < quads.size(); ++k) { if (k == idx) { continue; } ChessboardQuadPtr& quad = quads.at(k); for (int j = 0; j < 4; ++j) { // If it already has a neighbor if (quad->neighbors[j]) { continue; } cv::Point2f dp = pt - quad->corners[j]->pt; float dist = dp.dot(dp); // The following "if" checks, whether "dist" is the // shortest so far and smaller than the smallest // edge length of the current and target quads if (dist < minDist && dist <= (curQuad->edge_len + thresh_dilation) && dist <= (quad->edge_len + thresh_dilation) ) { // Check whether conditions are fulfilled if (matchCorners(curQuad, i, quad, j)) { closestCornerIdx = j; closestQuad = quad; minDist = dist; } } } } // Have we found a matching corner point? if (closestCornerIdx >= 0 && minDist < FLT_MAX) { ChessboardCornerPtr closestCorner = closestQuad->corners[closestCornerIdx]; // Make sure that the closest quad does not have the current // quad as neighbor already bool valid = true; for (int j = 0; j < 4; ++j) { if (closestQuad->neighbors[j] == curQuad) { valid = false; break; } } if (!valid) { continue; } // We've found one more corner - remember it closestCorner->pt = (pt + closestCorner->pt) * 0.5f; curQuad->count++; curQuad->neighbors[i] = closestQuad; curQuad->corners[i] = closestCorner; closestQuad->count++; closestQuad->neighbors[closestCornerIdx] = curQuad; closestQuad->corners[closestCornerIdx] = closestCorner; } } } } //=========================================================================== // AUGMENT PATTERN WITH ADDITIONAL QUADS //=========================================================================== // The first part of the function is basically a copy of // "findQuadNeighbors" // The comparisons between two points and two lines could be computed in their // own function int Chessboard::augmentBestRun(std::vector& candidateQuads, int candidateDilation, std::vector& existingQuads, int existingDilation) { // thresh dilation is used to counter the effect of dilation on the // distance between 2 neighboring corners. Since the distance below is // computed as its square, we do here the same. Additionally, we take the // conservative assumption that dilation was performed using the 3x3 CROSS // kernel, which coresponds to the 4-neighborhood. const float thresh_dilation = (2*candidateDilation+3)*(2*existingDilation+3)*2; // the "*2" is for the x and y component // Search all old quads which have a neighbor that needs to be linked for (size_t idx = 0; idx < existingQuads.size(); ++idx) { ChessboardQuadPtr& curQuad = existingQuads.at(idx); // For each corner of this quadrangle for (int i = 0; i < 4; ++i) { float minDist = FLT_MAX; int closestCornerIdx = -1; ChessboardQuadPtr closestQuad; // If curQuad corner[i] is already linked, continue if (!curQuad->corners[i]->needsNeighbor) { continue; } cv::Point2f pt = curQuad->corners[i]->pt; // Look for a match in all candidateQuads' corners for (size_t k = 0; k < candidateQuads.size(); ++k) { ChessboardQuadPtr& candidateQuad = candidateQuads.at(k); // Only look at unlabeled new quads if (candidateQuad->labeled) { continue; } for (int j = 0; j < 4; ++j) { // Only proceed if they are less than dist away from each // other cv::Point2f dp = pt - candidateQuad->corners[j]->pt; float dist = dp.dot(dp); if ((dist < minDist) && dist <= (curQuad->edge_len + thresh_dilation) && dist <= (candidateQuad->edge_len + thresh_dilation)) { if (matchCorners(curQuad, i, candidateQuad, j)) { closestCornerIdx = j; closestQuad = candidateQuad; minDist = dist; } } } } // Have we found a matching corner point? if (closestCornerIdx >= 0 && minDist < FLT_MAX) { ChessboardCornerPtr closestCorner = closestQuad->corners[closestCornerIdx]; closestCorner->pt = (pt + closestCorner->pt) * 0.5f; // We've found one more corner - remember it // ATTENTION: write the corner x and y coordinates separately, // else the crucial row/column entries will be overwritten !!! curQuad->corners[i]->pt = closestCorner->pt; curQuad->neighbors[i] = closestQuad; closestQuad->corners[closestCornerIdx]->pt = closestCorner->pt; // Label closest quad as labeled. In this way we exclude it // being considered again during the next loop iteration closestQuad->labeled = true; // We have a new member of the final pattern, copy it over ChessboardQuadPtr newQuad(new ChessboardQuad); newQuad->count = 1; newQuad->edge_len = closestQuad->edge_len; newQuad->group_idx = curQuad->group_idx; //the same as the current quad newQuad->labeled = false; //do it right afterwards curQuad->neighbors[i] = newQuad; // We only know one neighbor for sure newQuad->neighbors[closestCornerIdx] = curQuad; for (int j = 0; j < 4; j++) { newQuad->corners[j].reset(new ChessboardCorner); newQuad->corners[j]->pt = closestQuad->corners[j]->pt; } existingQuads.push_back(newQuad); // Start the function again return -1; } } } // Finished, don't start the function again return 1; } //=========================================================================== // GENERATE QUADRANGLES //=========================================================================== void Chessboard::generateQuads(std::vector& quads, cv::Mat& image, int flags, int dilation, bool firstRun) { // Empirical lower bound for the size of allowable quadrangles. // MARTIN, modified: Added "*0.1" in order to find smaller quads. int minSize = lround(image.cols * image.rows * .03 * 0.01 * 0.92 * 0.1); std::vector< std::vector > contours; std::vector hierarchy; // Initialize contour retrieving routine cv::findContours(image, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE); std::vector< std::vector > quadContours; for (size_t i = 0; i < contours.size(); ++i) { // Reject contours with a too small perimeter and contours which are // completely surrounded by another contour // MARTIN: If this function is called during PART 1, then the parameter "first run" // is set to "true". This guarantees, that only "nice" squares are detected. // During PART 2, we allow the polygonal approximation function below to // approximate more freely, which can result in recognized "squares" that are in // reality multiple blurred and sticked together squares. std::vector& contour = contours.at(i); if (hierarchy[i][3] == -1 || cv::contourArea(contour) < minSize) { continue; } int min_approx_level = 1, max_approx_level; if (firstRun) { max_approx_level = 3; } else { max_approx_level = MAX_CONTOUR_APPROX; } std::vector approxContour; for (int approx_level = min_approx_level; approx_level <= max_approx_level; approx_level++) { cv::approxPolyDP(contour, approxContour, approx_level, true); if (approxContour.size() == 4) { break; } } // Reject non-quadrangles if (approxContour.size() == 4 && cv::isContourConvex(approxContour)) { double p = cv::arcLength(approxContour, true); double area = cv::contourArea(approxContour); cv::Point pt[4]; for (int i = 0; i < 4; i++) { pt[i] = approxContour[i]; } cv::Point dp = pt[0] - pt[2]; double d1 = sqrt(dp.dot(dp)); dp = pt[1] - pt[3]; double d2 = sqrt(dp.dot(dp)); // PHILIPG: Only accept those quadrangles which are more // square than rectangular and which are big enough dp = pt[0] - pt[1]; double d3 = sqrt(dp.dot(dp)); dp = pt[1] - pt[2]; double d4 = sqrt(dp.dot(dp)); if (!(flags & CV_CALIB_CB_FILTER_QUADS) || (d3*4 > d4 && d4*4 > d3 && d3*d4 < area*1.5 && area > minSize && d1 >= 0.15 * p && d2 >= 0.15 * p)) { quadContours.push_back(approxContour); } } } // Allocate quad & corner buffers quads.resize(quadContours.size()); // Create array of quads structures for (size_t idx = 0; idx < quadContours.size(); ++idx) { ChessboardQuadPtr& q = quads.at(idx); std::vector& contour = quadContours.at(idx); // Reset group ID q.reset(new ChessboardQuad); assert(contour.size() == 4); for (int i = 0; i < 4; ++i) { cv::Point2f pt = contour.at(i); ChessboardCornerPtr corner(new ChessboardCorner); corner->pt = pt; q->corners[i] = corner; } for (int i = 0; i < 4; ++i) { cv::Point2f dp = q->corners[i]->pt - q->corners[(i+1)&3]->pt; float d = dp.dot(dp); if (q->edge_len > d) { q->edge_len = d; } } } } bool Chessboard::checkQuadGroup(std::vector& quads, std::vector& corners, cv::Size patternSize) { // Initialize bool flagRow = false; bool flagColumn = false; int height = -1; int width = -1; // Compute the minimum and maximum row / column ID // (it is unlikely that more than 8bit checkers are used per dimension) int min_row = 127; int max_row = -127; int min_col = 127; int max_col = -127; for (size_t i = 0; i < quads.size(); ++i) { ChessboardQuadPtr& q = quads.at(i); for (int j = 0; j < 4; ++j) { ChessboardCornerPtr& c = q->corners[j]; if (c->row > max_row) { max_row = c->row; } if (c->row < min_row) { min_row = c->row; } if (c->column > max_col) { max_col = c->column; } if (c->column < min_col) { min_col = c->column; } } } // If in a given direction the target pattern size is reached, we know exactly how // the checkerboard is oriented. // Else we need to prepare enough "dummy" corners for the worst case. for (size_t i = 0; i < quads.size(); ++i) { ChessboardQuadPtr& q = quads.at(i); for (int j = 0; j < 4; ++j) { ChessboardCornerPtr& c = q->corners[j]; if (c->column == max_col && c->row != min_row && c->row != max_row && !c->needsNeighbor) { flagColumn = true; } if (c->row == max_row && c->column != min_col && c->column != max_col && !c->needsNeighbor) { flagRow = true; } } } if (flagColumn) { if (max_col - min_col == patternSize.width + 1) { width = patternSize.width; height = patternSize.height; } else { width = patternSize.height; height = patternSize.width; } } else if (flagRow) { if (max_row - min_row == patternSize.width + 1) { height = patternSize.width; width = patternSize.height; } else { height = patternSize.height; width = patternSize.width; } } else { // If target pattern size is not reached in at least one of the two // directions, then we do not know where the remaining corners are // located. Account for this. width = std::max(patternSize.width, patternSize.height); height = std::max(patternSize.width, patternSize.height); } ++min_row; ++min_col; max_row = min_row + height - 1; max_col = min_col + width - 1; corners.clear(); int linkedBorderCorners = 0; // Write the corners in increasing order to the output file for (int i = min_row; i <= max_row; ++i) { for (int j = min_col; j <= max_col; ++j) { // Reset the iterator int iter = 1; for (int k = 0; k < (int)quads.size(); ++k) { ChessboardQuadPtr& quad = quads.at(k); for (int l = 0; l < 4; ++l) { ChessboardCornerPtr& c = quad->corners[l]; if (c->row == i && c->column == j) { bool boardEdge = false; if (i == min_row || i == max_row || j == min_col || j == max_col) { boardEdge = true; } if ((iter == 1 && boardEdge) || (iter == 2 && !boardEdge)) { // The respective row and column have been found corners.push_back(quad->corners[l]); } if (iter == 2 && boardEdge) { ++linkedBorderCorners; } // If the iterator is larger than two, this means that more than // two corners have the same row / column entries. Then some // linking errors must have occured and we should not use the found // pattern if (iter > 2) { return false; } iter++; } } } } } if ((int)corners.size() != patternSize.width * patternSize.height || linkedBorderCorners < (patternSize.width * 2 + patternSize.height * 2 - 2) * 0.75f) { return false; } // check that no corners lie at image boundary float border = 5.0f; for (int i = 0; i < (int)corners.size(); ++i) { ChessboardCornerPtr& c = corners.at(i); if (c->pt.x < border || c->pt.x > mImage.cols - border || c->pt.y < border || c->pt.y > mImage.rows - border) { return false; } } // check if we need to transpose the board if (width != patternSize.width) { std::swap(width, height); std::vector outputCorners; outputCorners.resize(corners.size()); for (int i = 0; i < height; ++i) { for (int j = 0; j < width; ++j) { outputCorners.at(i * width + j) = corners.at(j * height + i); } } corners = outputCorners; } // check if we need to revert the order in each row cv::Point2f p0 = corners.at(0)->pt; cv::Point2f p1 = corners.at(width-1)->pt; cv::Point2f p2 = corners.at(width)->pt; if ((p1 - p0).cross(p2 - p0) < 0.0f) { for (int i = 0; i < height; ++i) { for (int j = 0; j < width / 2; ++j) { std::swap(corners.at(i * width + j), corners.at(i * width + width - j - 1)); } } } p0 = corners.at(0)->pt; p2 = corners.at(width)->pt; // check if we need to rotate the board if (p2.y < p0.y) { std::vector outputCorners; outputCorners.resize(corners.size()); for (int i = 0; i < height; ++i) { for (int j = 0; j < width; ++j) { outputCorners.at(i * width + j) = corners.at((height - i - 1) * width + width - j - 1); } } corners = outputCorners; } return true; } void Chessboard::getQuadrangleHypotheses(const std::vector< std::vector >& contours, std::vector< std::pair >& quads, int classId) const { const float minAspectRatio = 0.2f; const float maxAspectRatio = 5.0f; const float minBoxSize = 10.0f; for (size_t i = 0; i < contours.size(); ++i) { cv::RotatedRect box = cv::minAreaRect(contours.at(i)); float boxSize = std::max(box.size.width, box.size.height); if (boxSize < minBoxSize) { continue; } float aspectRatio = box.size.width / std::max(box.size.height, 1.0f); if (aspectRatio < minAspectRatio || aspectRatio > maxAspectRatio) { continue; } quads.push_back(std::pair(boxSize, classId)); } } bool less_pred(const std::pair& p1, const std::pair& p2) { return p1.first < p2.first; } void countClasses(const std::vector >& pairs, size_t idx1, size_t idx2, std::vector& counts) { counts.assign(2, 0); for (size_t i = idx1; i != idx2; ++i) { counts[pairs[i].second]++; } } bool Chessboard::checkChessboard(const cv::Mat& image, cv::Size patternSize) const { const int erosionCount = 1; const float blackLevel = 20.f; const float whiteLevel = 130.f; const float blackWhiteGap = 70.f; cv::Mat white = image.clone(); cv::Mat black = image.clone(); cv::erode(white, white, cv::Mat(), cv::Point(-1,-1), erosionCount); cv::dilate(black, black, cv::Mat(), cv::Point(-1,-1), erosionCount); cv::Mat thresh(image.rows, image.cols, CV_8UC1); bool result = false; for (float threshLevel = blackLevel; threshLevel < whiteLevel && !result; threshLevel += 20.0f) { cv::threshold(white, thresh, threshLevel + blackWhiteGap, 255, CV_THRESH_BINARY); std::vector< std::vector > contours; std::vector hierarchy; // Initialize contour retrieving routine cv::findContours(thresh, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE); std::vector > quads; getQuadrangleHypotheses(contours, quads, 1); cv::threshold(black, thresh, threshLevel, 255, CV_THRESH_BINARY_INV); cv::findContours(thresh, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE); getQuadrangleHypotheses(contours, quads, 0); const size_t min_quads_count = patternSize.width * patternSize.height / 2; std::sort(quads.begin(), quads.end(), less_pred); // now check if there are many hypotheses with similar sizes // do this by floodfill-style algorithm const float sizeRelDev = 0.4f; for (size_t i = 0; i < quads.size(); ++i) { size_t j = i + 1; for (; j < quads.size(); ++j) { if (quads[j].first / quads[i].first > 1.0f + sizeRelDev) { break; } } if (j + 1 > min_quads_count + i) { // check the number of black and white squares std::vector counts; countClasses(quads, i, j, counts); const int blackCount = lroundf(ceilf(patternSize.width / 2.0f) * ceilf(patternSize.height / 2.0f)); const int whiteCount = lroundf(floorf(patternSize.width / 2.0f) * floorf(patternSize.height / 2.0f)); if (counts[0] < blackCount * 0.75f || counts[1] < whiteCount * 0.75f) { continue; } result = true; break; } } } return result; } bool Chessboard::checkBoardMonotony(std::vector& corners, cv::Size patternSize) { const float threshFactor = 0.2f; Spline splineXY, splineYX; splineXY.setLowBC(Spline::PARABOLIC_RUNOUT_BC); splineXY.setHighBC(Spline::PARABOLIC_RUNOUT_BC); splineYX.setLowBC(Spline::PARABOLIC_RUNOUT_BC); splineYX.setHighBC(Spline::PARABOLIC_RUNOUT_BC); // check if each row of corners approximates a cubic spline for (int i = 0; i < patternSize.height; ++i) { splineXY.clear(); splineYX.clear(); cv::Point2f p[3]; p[0] = corners.at(i * patternSize.width)->pt; p[1] = corners.at(i * patternSize.width + patternSize.width / 2)->pt; p[2] = corners.at(i * patternSize.width + patternSize.width - 1)->pt; for (int j = 0; j < 3; ++j) { splineXY.addPoint(p[j].x, p[j].y); splineYX.addPoint(p[j].y, p[j].x); } for (int j = 1; j < patternSize.width - 1; ++j) { cv::Point2f& p_j = corners.at(i * patternSize.width + j)->pt; float thresh = std::numeric_limits::max(); // up-neighbor if (i > 0) { cv::Point2f& neighbor = corners.at((i - 1) * patternSize.width + j)->pt; thresh = fminf(thresh, cv::norm(neighbor - p_j)); } // down-neighbor if (i < patternSize.height - 1) { cv::Point2f& neighbor = corners.at((i + 1) * patternSize.width + j)->pt; thresh = fminf(thresh, cv::norm(neighbor - p_j)); } // left-neighbor { cv::Point2f& neighbor = corners.at(i * patternSize.width + j - 1)->pt; thresh = fminf(thresh, cv::norm(neighbor - p_j)); } // right-neighbor { cv::Point2f& neighbor = corners.at(i * patternSize.width + j + 1)->pt; thresh = fminf(thresh, cv::norm(neighbor - p_j)); } thresh *= threshFactor; if (fminf(fabsf(splineXY(p_j.x) - p_j.y), fabsf(splineYX(p_j.y) - p_j.x)) > thresh) { return false; } } } // check if each column of corners approximates a cubic spline for (int j = 0; j < patternSize.width; ++j) { splineXY.clear(); splineYX.clear(); cv::Point2f p[3]; p[0] = corners.at(j)->pt; p[1] = corners.at(patternSize.height / 2 * patternSize.width + j)->pt; p[2] = corners.at((patternSize.height - 1) * patternSize.width + j)->pt; for (int i = 0; i < 3; ++i) { splineXY.addPoint(p[i].x, p[i].y); splineYX.addPoint(p[i].y, p[i].x); } for (int i = 1; i < patternSize.height - 1; ++i) { cv::Point2f& p_i = corners.at(i * patternSize.width + j)->pt; float thresh = std::numeric_limits::max(); // up-neighbor { cv::Point2f& neighbor = corners.at((i - 1) * patternSize.width + j)->pt; thresh = fminf(thresh, cv::norm(neighbor - p_i)); } // down-neighbor { cv::Point2f& neighbor = corners.at((i + 1) * patternSize.width + j)->pt; thresh = fminf(thresh, cv::norm(neighbor - p_i)); } // left-neighbor if (j > 0) { cv::Point2f& neighbor = corners.at(i * patternSize.width + j - 1)->pt; thresh = fminf(thresh, cv::norm(neighbor - p_i)); } // right-neighbor if (j < patternSize.width - 1) { cv::Point2f& neighbor = corners.at(i * patternSize.width + j + 1)->pt; thresh = fminf(thresh, cv::norm(neighbor - p_i)); } thresh *= threshFactor; if (fminf(fabsf(splineXY(p_i.x) - p_i.y), fabsf(splineYX(p_i.y) - p_i.x)) > thresh) { return false; } } } return true; } bool Chessboard::matchCorners(ChessboardQuadPtr& quad1, int corner1, ChessboardQuadPtr& quad2, int corner2) const { // First Check everything from the viewpoint of the // current quad compute midpoints of "parallel" quad // sides 1 float x1 = (quad1->corners[corner1]->pt.x + quad1->corners[(corner1+1)%4]->pt.x)/2; float y1 = (quad1->corners[corner1]->pt.y + quad1->corners[(corner1+1)%4]->pt.y)/2; float x2 = (quad1->corners[(corner1+2)%4]->pt.x + quad1->corners[(corner1+3)%4]->pt.x)/2; float y2 = (quad1->corners[(corner1+2)%4]->pt.y + quad1->corners[(corner1+3)%4]->pt.y)/2; // compute midpoints of "parallel" quad sides 2 float x3 = (quad1->corners[corner1]->pt.x + quad1->corners[(corner1+3)%4]->pt.x)/2; float y3 = (quad1->corners[corner1]->pt.y + quad1->corners[(corner1+3)%4]->pt.y)/2; float x4 = (quad1->corners[(corner1+1)%4]->pt.x + quad1->corners[(corner1+2)%4]->pt.x)/2; float y4 = (quad1->corners[(corner1+1)%4]->pt.y + quad1->corners[(corner1+2)%4]->pt.y)/2; // MARTIN: Heuristic // For corner2 of quad2 to be considered, // it needs to be on the same side of the two lines as // corner1. This is given, if the cross product has // the same sign for both computations below: float a1 = x1 - x2; float b1 = y1 - y2; // the current corner float c11 = quad1->corners[corner1]->pt.x - x2; float d11 = quad1->corners[corner1]->pt.y - y2; // the candidate corner float c12 = quad2->corners[corner2]->pt.x - x2; float d12 = quad2->corners[corner2]->pt.y - y2; float sign11 = a1*d11 - c11*b1; float sign12 = a1*d12 - c12*b1; float a2 = x3 - x4; float b2 = y3 - y4; // the current corner float c21 = quad1->corners[corner1]->pt.x - x4; float d21 = quad1->corners[corner1]->pt.y - y4; // the candidate corner float c22 = quad2->corners[corner2]->pt.x - x4; float d22 = quad2->corners[corner2]->pt.y - y4; float sign21 = a2*d21 - c21*b2; float sign22 = a2*d22 - c22*b2; // Also make shure that two border quads of the same row or // column don't link. Check from the current corner's view, // whether the corner diagonal from the candidate corner // is also on the same side of the two lines as the current // corner and the candidate corner. float c13 = quad2->corners[(corner2+2)%4]->pt.x - x2; float d13 = quad2->corners[(corner2+2)%4]->pt.y - y2; float c23 = quad2->corners[(corner2+2)%4]->pt.x - x4; float d23 = quad2->corners[(corner2+2)%4]->pt.y - y4; float sign13 = a1*d13 - c13*b1; float sign23 = a2*d23 - c23*b2; // Second: Then check everything from the viewpoint of // the candidate quad. Compute midpoints of "parallel" // quad sides 1 float u1 = (quad2->corners[corner2]->pt.x + quad2->corners[(corner2+1)%4]->pt.x)/2; float v1 = (quad2->corners[corner2]->pt.y + quad2->corners[(corner2+1)%4]->pt.y)/2; float u2 = (quad2->corners[(corner2+2)%4]->pt.x + quad2->corners[(corner2+3)%4]->pt.x)/2; float v2 = (quad2->corners[(corner2+2)%4]->pt.y + quad2->corners[(corner2+3)%4]->pt.y)/2; // compute midpoints of "parallel" quad sides 2 float u3 = (quad2->corners[corner2]->pt.x + quad2->corners[(corner2+3)%4]->pt.x)/2; float v3 = (quad2->corners[corner2]->pt.y + quad2->corners[(corner2+3)%4]->pt.y)/2; float u4 = (quad2->corners[(corner2+1)%4]->pt.x + quad2->corners[(corner2+2)%4]->pt.x)/2; float v4 = (quad2->corners[(corner2+1)%4]->pt.y + quad2->corners[(corner2+2)%4]->pt.y)/2; // MARTIN: Heuristic // For corner2 of quad2 to be considered, // it needs to be on the same side of the two lines as // corner1. This is given, if the cross product has // the same sign for both computations below: float a3 = u1 - u2; float b3 = v1 - v2; // the current corner float c31 = quad1->corners[corner1]->pt.x - u2; float d31 = quad1->corners[corner1]->pt.y - v2; // the candidate corner float c32 = quad2->corners[corner2]->pt.x - u2; float d32 = quad2->corners[corner2]->pt.y - v2; float sign31 = a3*d31-c31*b3; float sign32 = a3*d32-c32*b3; float a4 = u3 - u4; float b4 = v3 - v4; // the current corner float c41 = quad1->corners[corner1]->pt.x - u4; float d41 = quad1->corners[corner1]->pt.y - v4; // the candidate corner float c42 = quad2->corners[corner2]->pt.x - u4; float d42 = quad2->corners[corner2]->pt.y - v4; float sign41 = a4*d41-c41*b4; float sign42 = a4*d42-c42*b4; // Also make sure that two border quads of the same row or // column don't link. Check from the candidate corner's view, // whether the corner diagonal from the current corner // is also on the same side of the two lines as the current // corner and the candidate corner. float c33 = quad1->corners[(corner1+2)%4]->pt.x - u2; float d33 = quad1->corners[(corner1+2)%4]->pt.y - v2; float c43 = quad1->corners[(corner1+2)%4]->pt.x - u4; float d43 = quad1->corners[(corner1+2)%4]->pt.y - v4; float sign33 = a3*d33-c33*b3; float sign43 = a4*d43-c43*b4; // This time we also need to make shure, that no quad // is linked to a quad of another dilation run which // may lie INSIDE it!!! // Third: Therefore check everything from the viewpoint // of the current quad compute midpoints of "parallel" // quad sides 1 float x5 = quad1->corners[corner1]->pt.x; float y5 = quad1->corners[corner1]->pt.y; float x6 = quad1->corners[(corner1+1)%4]->pt.x; float y6 = quad1->corners[(corner1+1)%4]->pt.y; // compute midpoints of "parallel" quad sides 2 float x7 = x5; float y7 = y5; float x8 = quad1->corners[(corner1+3)%4]->pt.x; float y8 = quad1->corners[(corner1+3)%4]->pt.y; // MARTIN: Heuristic // For corner2 of quad2 to be considered, // it needs to be on the other side of the two lines than // corner1. This is given, if the cross product has // a different sign for both computations below: float a5 = x6 - x5; float b5 = y6 - y5; // the current corner float c51 = quad1->corners[(corner1+2)%4]->pt.x - x5; float d51 = quad1->corners[(corner1+2)%4]->pt.y - y5; // the candidate corner float c52 = quad2->corners[corner2]->pt.x - x5; float d52 = quad2->corners[corner2]->pt.y - y5; float sign51 = a5*d51 - c51*b5; float sign52 = a5*d52 - c52*b5; float a6 = x8 - x7; float b6 = y8 - y7; // the current corner float c61 = quad1->corners[(corner1+2)%4]->pt.x - x7; float d61 = quad1->corners[(corner1+2)%4]->pt.y - y7; // the candidate corner float c62 = quad2->corners[corner2]->pt.x - x7; float d62 = quad2->corners[corner2]->pt.y - y7; float sign61 = a6*d61 - c61*b6; float sign62 = a6*d62 - c62*b6; // Fourth: Then check everything from the viewpoint of // the candidate quad compute midpoints of "parallel" // quad sides 1 float u5 = quad2->corners[corner2]->pt.x; float v5 = quad2->corners[corner2]->pt.y; float u6 = quad2->corners[(corner2+1)%4]->pt.x; float v6 = quad2->corners[(corner2+1)%4]->pt.y; // compute midpoints of "parallel" quad sides 2 float u7 = u5; float v7 = v5; float u8 = quad2->corners[(corner2+3)%4]->pt.x; float v8 = quad2->corners[(corner2+3)%4]->pt.y; // MARTIN: Heuristic // For corner2 of quad2 to be considered, // it needs to be on the other side of the two lines than // corner1. This is given, if the cross product has // a different sign for both computations below: float a7 = u6 - u5; float b7 = v6 - v5; // the current corner float c71 = quad1->corners[corner1]->pt.x - u5; float d71 = quad1->corners[corner1]->pt.y - v5; // the candidate corner float c72 = quad2->corners[(corner2+2)%4]->pt.x - u5; float d72 = quad2->corners[(corner2+2)%4]->pt.y - v5; float sign71 = a7*d71-c71*b7; float sign72 = a7*d72-c72*b7; float a8 = u8 - u7; float b8 = v8 - v7; // the current corner float c81 = quad1->corners[corner1]->pt.x - u7; float d81 = quad1->corners[corner1]->pt.y - v7; // the candidate corner float c82 = quad2->corners[(corner2+2)%4]->pt.x - u7; float d82 = quad2->corners[(corner2+2)%4]->pt.y - v7; float sign81 = a8*d81-c81*b8; float sign82 = a8*d82-c82*b8; // Check whether conditions are fulfilled if (((sign11 < 0 && sign12 < 0) || (sign11 > 0 && sign12 > 0)) && ((sign21 < 0 && sign22 < 0) || (sign21 > 0 && sign22 > 0)) && ((sign31 < 0 && sign32 < 0) || (sign31 > 0 && sign32 > 0)) && ((sign41 < 0 && sign42 < 0) || (sign41 > 0 && sign42 > 0)) && ((sign11 < 0 && sign13 < 0) || (sign11 > 0 && sign13 > 0)) && ((sign21 < 0 && sign23 < 0) || (sign21 > 0 && sign23 > 0)) && ((sign31 < 0 && sign33 < 0) || (sign31 > 0 && sign33 > 0)) && ((sign41 < 0 && sign43 < 0) || (sign41 > 0 && sign43 > 0)) && ((sign51 < 0 && sign52 > 0) || (sign51 > 0 && sign52 < 0)) && ((sign61 < 0 && sign62 > 0) || (sign61 > 0 && sign62 < 0)) && ((sign71 < 0 && sign72 > 0) || (sign71 > 0 && sign72 < 0)) && ((sign81 < 0 && sign82 > 0) || (sign81 > 0 && sign82 < 0))) { return true; } else { return false; } } } ================================================ FILE: src/utils/camodocal/src/gpl/EigenQuaternionParameterization.cc ================================================ #include "camodocal/gpl/EigenQuaternionParameterization.h" #include namespace camodocal { bool EigenQuaternionParameterization::Plus(const double* x, const double* delta, double* x_plus_delta) const { const double norm_delta = sqrt(delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]); if (norm_delta > 0.0) { const double sin_delta_by_delta = (sin(norm_delta) / norm_delta); double q_delta[4]; q_delta[0] = sin_delta_by_delta * delta[0]; q_delta[1] = sin_delta_by_delta * delta[1]; q_delta[2] = sin_delta_by_delta * delta[2]; q_delta[3] = cos(norm_delta); EigenQuaternionProduct(q_delta, x, x_plus_delta); } else { for (int i = 0; i < 4; ++i) { x_plus_delta[i] = x[i]; } } return true; } bool EigenQuaternionParameterization::ComputeJacobian(const double* x, double* jacobian) const { jacobian[0] = x[3]; jacobian[1] = x[2]; jacobian[2] = -x[1]; // NOLINT jacobian[3] = -x[2]; jacobian[4] = x[3]; jacobian[5] = x[0]; // NOLINT jacobian[6] = x[1]; jacobian[7] = -x[0]; jacobian[8] = x[3]; // NOLINT jacobian[9] = -x[0]; jacobian[10] = -x[1]; jacobian[11] = -x[2]; // NOLINT return true; } } ================================================ FILE: src/utils/camodocal/src/gpl/gpl.cc ================================================ #include "camodocal/gpl/gpl.h" #include #ifdef _WIN32 #include #else #include #endif // source: https://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x #ifdef __APPLE__ #include #define ORWL_NANO (+1.0E-9) #define ORWL_GIGA UINT64_C(1000000000) static double orwl_timebase = 0.0; static uint64_t orwl_timestart = 0; struct timespec orwl_gettime(void) { // be more careful in a multithreaded environement if (!orwl_timestart) { mach_timebase_info_data_t tb = { 0 }; mach_timebase_info(&tb); orwl_timebase = tb.numer; orwl_timebase /= tb.denom; orwl_timestart = mach_absolute_time(); } struct timespec t; double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase; t.tv_sec = diff * ORWL_NANO; t.tv_nsec = diff - (t.tv_sec * ORWL_GIGA); return t; } #endif // __APPLE__ const double WGS84_A = 6378137.0; const double WGS84_ECCSQ = 0.00669437999013; // Windows lacks fminf #ifndef fminf #define fminf(x, y) (((x) < (y)) ? (x) : (y)) #endif namespace camodocal { double hypot3(double x, double y, double z) { return sqrt(square(x) + square(y) + square(z)); } float hypot3f(float x, float y, float z) { return sqrtf(square(x) + square(y) + square(z)); } double d2r(double deg) { return deg / 180.0 * M_PI; } float d2r(float deg) { return deg / 180.0f * M_PI; } double r2d(double rad) { return rad / M_PI * 180.0; } float r2d(float rad) { return rad / M_PI * 180.0f; } double sinc(double theta) { return sin(theta) / theta; } #ifdef _WIN32 #include #include #include LARGE_INTEGER getFILETIMEoffset() { SYSTEMTIME s; FILETIME f; LARGE_INTEGER t; s.wYear = 1970; s.wMonth = 1; s.wDay = 1; s.wHour = 0; s.wMinute = 0; s.wSecond = 0; s.wMilliseconds = 0; SystemTimeToFileTime(&s, &f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; return (t); } int clock_gettime(int X, struct timespec *tp) { LARGE_INTEGER t; FILETIME f; double microseconds; static LARGE_INTEGER offset; static double frequencyToMicroseconds; static int initialized = 0; static BOOL usePerformanceCounter = 0; if (!initialized) { LARGE_INTEGER performanceFrequency; initialized = 1; usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); if (usePerformanceCounter) { QueryPerformanceCounter(&offset); frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; } else { offset = getFILETIMEoffset(); frequencyToMicroseconds = 10.; } } if (usePerformanceCounter) QueryPerformanceCounter(&t); else { GetSystemTimeAsFileTime(&f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; } t.QuadPart -= offset.QuadPart; microseconds = (double)t.QuadPart / frequencyToMicroseconds; t.QuadPart = microseconds; tp->tv_sec = t.QuadPart / 1000000; tp->tv_nsec = (t.QuadPart % 1000000) * 1000; return (0); } #endif unsigned long long timeInMicroseconds(void) { struct timespec tp; #ifdef __APPLE__ tp = orwl_gettime(); #else clock_gettime(CLOCK_REALTIME, &tp); #endif return tp.tv_sec * 1000000 + tp.tv_nsec / 1000; } double timeInSeconds(void) { struct timespec tp; #ifdef __APPLE__ tp = orwl_gettime(); #else clock_gettime(CLOCK_REALTIME, &tp); #endif return static_cast(tp.tv_sec) + static_cast(tp.tv_nsec) / 1000000000.0; } float colormapAutumn[128][3] = { {1.0f,0.f,0.f}, {1.0f,0.007874f,0.f}, {1.0f,0.015748f,0.f}, {1.0f,0.023622f,0.f}, {1.0f,0.031496f,0.f}, {1.0f,0.03937f,0.f}, {1.0f,0.047244f,0.f}, {1.0f,0.055118f,0.f}, {1.0f,0.062992f,0.f}, {1.0f,0.070866f,0.f}, {1.0f,0.07874f,0.f}, {1.0f,0.086614f,0.f}, {1.0f,0.094488f,0.f}, {1.0f,0.10236f,0.f}, {1.0f,0.11024f,0.f}, {1.0f,0.11811f,0.f}, {1.0f,0.12598f,0.f}, {1.0f,0.13386f,0.f}, {1.0f,0.14173f,0.f}, {1.0f,0.14961f,0.f}, {1.0f,0.15748f,0.f}, {1.0f,0.16535f,0.f}, {1.0f,0.17323f,0.f}, {1.0f,0.1811f,0.f}, {1.0f,0.18898f,0.f}, {1.0f,0.19685f,0.f}, {1.0f,0.20472f,0.f}, {1.0f,0.2126f,0.f}, {1.0f,0.22047f,0.f}, {1.0f,0.22835f,0.f}, {1.0f,0.23622f,0.f}, {1.0f,0.24409f,0.f}, {1.0f,0.25197f,0.f}, {1.0f,0.25984f,0.f}, {1.0f,0.26772f,0.f}, {1.0f,0.27559f,0.f}, {1.0f,0.28346f,0.f}, {1.0f,0.29134f,0.f}, {1.0f,0.29921f,0.f}, {1.0f,0.30709f,0.f}, {1.0f,0.31496f,0.f}, {1.0f,0.32283f,0.f}, {1.0f,0.33071f,0.f}, {1.0f,0.33858f,0.f}, {1.0f,0.34646f,0.f}, {1.0f,0.35433f,0.f}, {1.0f,0.3622f,0.f}, {1.0f,0.37008f,0.f}, {1.0f,0.37795f,0.f}, {1.0f,0.38583f,0.f}, {1.0f,0.3937f,0.f}, {1.0f,0.40157f,0.f}, {1.0f,0.40945f,0.f}, {1.0f,0.41732f,0.f}, {1.0f,0.4252f,0.f}, {1.0f,0.43307f,0.f}, {1.0f,0.44094f,0.f}, {1.0f,0.44882f,0.f}, {1.0f,0.45669f,0.f}, {1.0f,0.46457f,0.f}, {1.0f,0.47244f,0.f}, {1.0f,0.48031f,0.f}, {1.0f,0.48819f,0.f}, {1.0f,0.49606f,0.f}, {1.0f,0.50394f,0.f}, {1.0f,0.51181f,0.f}, {1.0f,0.51969f,0.f}, {1.0f,0.52756f,0.f}, {1.0f,0.53543f,0.f}, {1.0f,0.54331f,0.f}, {1.0f,0.55118f,0.f}, {1.0f,0.55906f,0.f}, {1.0f,0.56693f,0.f}, {1.0f,0.5748f,0.f}, {1.0f,0.58268f,0.f}, {1.0f,0.59055f,0.f}, {1.0f,0.59843f,0.f}, {1.0f,0.6063f,0.f}, {1.0f,0.61417f,0.f}, {1.0f,0.62205f,0.f}, {1.0f,0.62992f,0.f}, {1.0f,0.6378f,0.f}, {1.0f,0.64567f,0.f}, {1.0f,0.65354f,0.f}, {1.0f,0.66142f,0.f}, {1.0f,0.66929f,0.f}, {1.0f,0.67717f,0.f}, {1.0f,0.68504f,0.f}, {1.0f,0.69291f,0.f}, {1.0f,0.70079f,0.f}, {1.0f,0.70866f,0.f}, {1.0f,0.71654f,0.f}, {1.0f,0.72441f,0.f}, {1.0f,0.73228f,0.f}, {1.0f,0.74016f,0.f}, {1.0f,0.74803f,0.f}, {1.0f,0.75591f,0.f}, {1.0f,0.76378f,0.f}, {1.0f,0.77165f,0.f}, {1.0f,0.77953f,0.f}, {1.0f,0.7874f,0.f}, {1.0f,0.79528f,0.f}, {1.0f,0.80315f,0.f}, {1.0f,0.81102f,0.f}, {1.0f,0.8189f,0.f}, {1.0f,0.82677f,0.f}, {1.0f,0.83465f,0.f}, {1.0f,0.84252f,0.f}, {1.0f,0.85039f,0.f}, {1.0f,0.85827f,0.f}, {1.0f,0.86614f,0.f}, {1.0f,0.87402f,0.f}, {1.0f,0.88189f,0.f}, {1.0f,0.88976f,0.f}, {1.0f,0.89764f,0.f}, {1.0f,0.90551f,0.f}, {1.0f,0.91339f,0.f}, {1.0f,0.92126f,0.f}, {1.0f,0.92913f,0.f}, {1.0f,0.93701f,0.f}, {1.0f,0.94488f,0.f}, {1.0f,0.95276f,0.f}, {1.0f,0.96063f,0.f}, {1.0f,0.9685f,0.f}, {1.0f,0.97638f,0.f}, {1.0f,0.98425f,0.f}, {1.0f,0.99213f,0.f}, {1.0f,1.0f,0.0f} }; float colormapJet[128][3] = { {0.0f,0.0f,0.53125f}, {0.0f,0.0f,0.5625f}, {0.0f,0.0f,0.59375f}, {0.0f,0.0f,0.625f}, {0.0f,0.0f,0.65625f}, {0.0f,0.0f,0.6875f}, {0.0f,0.0f,0.71875f}, {0.0f,0.0f,0.75f}, {0.0f,0.0f,0.78125f}, {0.0f,0.0f,0.8125f}, {0.0f,0.0f,0.84375f}, {0.0f,0.0f,0.875f}, {0.0f,0.0f,0.90625f}, {0.0f,0.0f,0.9375f}, {0.0f,0.0f,0.96875f}, {0.0f,0.0f,1.0f}, {0.0f,0.03125f,1.0f}, {0.0f,0.0625f,1.0f}, {0.0f,0.09375f,1.0f}, {0.0f,0.125f,1.0f}, {0.0f,0.15625f,1.0f}, {0.0f,0.1875f,1.0f}, {0.0f,0.21875f,1.0f}, {0.0f,0.25f,1.0f}, {0.0f,0.28125f,1.0f}, {0.0f,0.3125f,1.0f}, {0.0f,0.34375f,1.0f}, {0.0f,0.375f,1.0f}, {0.0f,0.40625f,1.0f}, {0.0f,0.4375f,1.0f}, {0.0f,0.46875f,1.0f}, {0.0f,0.5f,1.0f}, {0.0f,0.53125f,1.0f}, {0.0f,0.5625f,1.0f}, {0.0f,0.59375f,1.0f}, {0.0f,0.625f,1.0f}, {0.0f,0.65625f,1.0f}, {0.0f,0.6875f,1.0f}, {0.0f,0.71875f,1.0f}, {0.0f,0.75f,1.0f}, {0.0f,0.78125f,1.0f}, {0.0f,0.8125f,1.0f}, {0.0f,0.84375f,1.0f}, {0.0f,0.875f,1.0f}, {0.0f,0.90625f,1.0f}, {0.0f,0.9375f,1.0f}, {0.0f,0.96875f,1.0f}, {0.0f,1.0f,1.0f}, {0.03125f,1.0f,0.96875f}, {0.0625f,1.0f,0.9375f}, {0.09375f,1.0f,0.90625f}, {0.125f,1.0f,0.875f}, {0.15625f,1.0f,0.84375f}, {0.1875f,1.0f,0.8125f}, {0.21875f,1.0f,0.78125f}, {0.25f,1.0f,0.75f}, {0.28125f,1.0f,0.71875f}, {0.3125f,1.0f,0.6875f}, {0.34375f,1.0f,0.65625f}, {0.375f,1.0f,0.625f}, {0.40625f,1.0f,0.59375f}, {0.4375f,1.0f,0.5625f}, {0.46875f,1.0f,0.53125f}, {0.5f,1.0f,0.5f}, {0.53125f,1.0f,0.46875f}, {0.5625f,1.0f,0.4375f}, {0.59375f,1.0f,0.40625f}, {0.625f,1.0f,0.375f}, {0.65625f,1.0f,0.34375f}, {0.6875f,1.0f,0.3125f}, {0.71875f,1.0f,0.28125f}, {0.75f,1.0f,0.25f}, {0.78125f,1.0f,0.21875f}, {0.8125f,1.0f,0.1875f}, {0.84375f,1.0f,0.15625f}, {0.875f,1.0f,0.125f}, {0.90625f,1.0f,0.09375f}, {0.9375f,1.0f,0.0625f}, {0.96875f,1.0f,0.03125f}, {1.0f,1.0f,0.0f}, {1.0f,0.96875f,0.0f}, {1.0f,0.9375f,0.0f}, {1.0f,0.90625f,0.0f}, {1.0f,0.875f,0.0f}, {1.0f,0.84375f,0.0f}, {1.0f,0.8125f,0.0f}, {1.0f,0.78125f,0.0f}, {1.0f,0.75f,0.0f}, {1.0f,0.71875f,0.0f}, {1.0f,0.6875f,0.0f}, {1.0f,0.65625f,0.0f}, {1.0f,0.625f,0.0f}, {1.0f,0.59375f,0.0f}, {1.0f,0.5625f,0.0f}, {1.0f,0.53125f,0.0f}, {1.0f,0.5f,0.0f}, {1.0f,0.46875f,0.0f}, {1.0f,0.4375f,0.0f}, {1.0f,0.40625f,0.0f}, {1.0f,0.375f,0.0f}, {1.0f,0.34375f,0.0f}, {1.0f,0.3125f,0.0f}, {1.0f,0.28125f,0.0f}, {1.0f,0.25f,0.0f}, {1.0f,0.21875f,0.0f}, {1.0f,0.1875f,0.0f}, {1.0f,0.15625f,0.0f}, {1.0f,0.125f,0.0f}, {1.0f,0.09375f,0.0f}, {1.0f,0.0625f,0.0f}, {1.0f,0.03125f,0.0f}, {1.0f,0.0f,0.0f}, {0.96875f,0.0f,0.0f}, {0.9375f,0.0f,0.0f}, {0.90625f,0.0f,0.0f}, {0.875f,0.0f,0.0f}, {0.84375f,0.0f,0.0f}, {0.8125f,0.0f,0.0f}, {0.78125f,0.0f,0.0f}, {0.75f,0.0f,0.0f}, {0.71875f,0.0f,0.0f}, {0.6875f,0.0f,0.0f}, {0.65625f,0.0f,0.0f}, {0.625f,0.0f,0.0f}, {0.59375f,0.0f,0.0f}, {0.5625f,0.0f,0.0f}, {0.53125f,0.0f,0.0f}, {0.5f,0.0f,0.0f} }; void colorDepthImage(cv::Mat& imgDepth, cv::Mat& imgColoredDepth, float minRange, float maxRange) { imgColoredDepth = cv::Mat::zeros(imgDepth.size(), CV_8UC3); for (int i = 0; i < imgColoredDepth.rows; ++i) { const float* depth = imgDepth.ptr(i); unsigned char* pixel = imgColoredDepth.ptr(i); for (int j = 0; j < imgColoredDepth.cols; ++j) { if (depth[j] != 0) { int idx = fminf(depth[j] - minRange, maxRange - minRange) / (maxRange - minRange) * 127.0f; idx = 127 - idx; pixel[0] = colormapJet[idx][2] * 255.0f; pixel[1] = colormapJet[idx][1] * 255.0f; pixel[2] = colormapJet[idx][0] * 255.0f; } pixel += 3; } } } bool colormap(const std::string& name, unsigned char idx, float& r, float& g, float& b) { if (name.compare("jet") == 0) { float* color = colormapJet[idx]; r = color[0]; g = color[1]; b = color[2]; return true; } else if (name.compare("autumn") == 0) { float* color = colormapAutumn[idx]; r = color[0]; g = color[1]; b = color[2]; return true; } return false; } std::vector bresLine(int x0, int y0, int x1, int y1) { // Bresenham's line algorithm // Find cells intersected by line between (x0,y0) and (x1,y1) std::vector cells; int dx = std::abs(x1 - x0); int dy = std::abs(y1 - y0); int sx = (x0 < x1) ? 1 : -1; int sy = (y0 < y1) ? 1 : -1; int err = dx - dy; while (1) { cells.push_back(cv::Point2i(x0, y0)); if (x0 == x1 && y0 == y1) { break; } int e2 = 2 * err; if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; } } return cells; } std::vector bresCircle(int x0, int y0, int r) { // Bresenham's circle algorithm // Find cells intersected by circle with center (x0,y0) and radius r std::vector< std::vector > mask(2 * r + 1); for (int i = 0; i < 2 * r + 1; ++i) { mask[i].resize(2 * r + 1); for (int j = 0; j < 2 * r + 1; ++j) { mask[i][j] = false; } } int f = 1 - r; int ddF_x = 1; int ddF_y = -2 * r; int x = 0; int y = r; std::vector line; line = bresLine(x0, y0 - r, x0, y0 + r); for (std::vector::iterator it = line.begin(); it != line.end(); ++it) { mask[it->x - x0 + r][it->y - y0 + r] = true; } line = bresLine(x0 - r, y0, x0 + r, y0); for (std::vector::iterator it = line.begin(); it != line.end(); ++it) { mask[it->x - x0 + r][it->y - y0 + r] = true; } while (x < y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; line = bresLine(x0 - x, y0 + y, x0 + x, y0 + y); for (std::vector::iterator it = line.begin(); it != line.end(); ++it) { mask[it->x - x0 + r][it->y - y0 + r] = true; } line = bresLine(x0 - x, y0 - y, x0 + x, y0 - y); for (std::vector::iterator it = line.begin(); it != line.end(); ++it) { mask[it->x - x0 + r][it->y - y0 + r] = true; } line = bresLine(x0 - y, y0 + x, x0 + y, y0 + x); for (std::vector::iterator it = line.begin(); it != line.end(); ++it) { mask[it->x - x0 + r][it->y - y0 + r] = true; } line = bresLine(x0 - y, y0 - x, x0 + y, y0 - x); for (std::vector::iterator it = line.begin(); it != line.end(); ++it) { mask[it->x - x0 + r][it->y - y0 + r] = true; } } std::vector cells; for (int i = 0; i < 2 * r + 1; ++i) { for (int j = 0; j < 2 * r + 1; ++j) { if (mask[i][j]) { cells.push_back(cv::Point2i(i - r + x0, j - r + y0)); } } } return cells; } void fitCircle(const std::vector& points, double& centerX, double& centerY, double& radius) { // D. Umbach, and K. Jones, A Few Methods for Fitting Circles to Data, // IEEE Transactions on Instrumentation and Measurement, 2000 // We use the modified least squares method. double sum_x = 0.0; double sum_y = 0.0; double sum_xx = 0.0; double sum_xy = 0.0; double sum_yy = 0.0; double sum_xxx = 0.0; double sum_xxy = 0.0; double sum_xyy = 0.0; double sum_yyy = 0.0; int n = points.size(); for (int i = 0; i < n; ++i) { double x = points.at(i).x; double y = points.at(i).y; sum_x += x; sum_y += y; sum_xx += x * x; sum_xy += x * y; sum_yy += y * y; sum_xxx += x * x * x; sum_xxy += x * x * y; sum_xyy += x * y * y; sum_yyy += y * y * y; } double A = n * sum_xx - square(sum_x); double B = n * sum_xy - sum_x * sum_y; double C = n * sum_yy - square(sum_y); double D = 0.5 * (n * sum_xyy - sum_x * sum_yy + n * sum_xxx - sum_x * sum_xx); double E = 0.5 * (n * sum_xxy - sum_y * sum_xx + n * sum_yyy - sum_y * sum_yy); centerX = (D * C - B * E) / (A * C - square(B)); centerY = (A * E - B * D) / (A * C - square(B)); double sum_r = 0.0; for (int i = 0; i < n; ++i) { double x = points.at(i).x; double y = points.at(i).y; sum_r += hypot(x - centerX, y - centerY); } radius = sum_r / n; } std::vector intersectCircles(double x1, double y1, double r1, double x2, double y2, double r2) { std::vector ipts; double d = hypot(x1 - x2, y1 - y2); if (d > r1 + r2) { // circles are separate return ipts; } if (d < fabs(r1 - r2)) { // one circle is contained within the other return ipts; } double a = (square(r1) - square(r2) + square(d)) / (2.0 * d); double h = sqrt(square(r1) - square(a)); double x3 = x1 + a * (x2 - x1) / d; double y3 = y1 + a * (y2 - y1) / d; if (h < 1e-10) { // two circles touch at one point ipts.push_back(cv::Point2d(x3, y3)); return ipts; } ipts.push_back(cv::Point2d(x3 + h * (y2 - y1) / d, y3 - h * (x2 - x1) / d)); ipts.push_back(cv::Point2d(x3 - h * (y2 - y1) / d, y3 + h * (x2 - x1) / d)); return ipts; } char UTMLetterDesignator(double latitude) { // This routine determines the correct UTM letter designator for the given latitude // returns 'Z' if latitude is outside the UTM limits of 84N to 80S // Written by Chuck Gantz- chuck.gantz@globalstar.com char letterDesignator; if ((84.0 >= latitude) && (latitude >= 72.0)) letterDesignator = 'X'; else if ((72.0 > latitude) && (latitude >= 64.0)) letterDesignator = 'W'; else if ((64.0 > latitude) && (latitude >= 56.0)) letterDesignator = 'V'; else if ((56.0 > latitude) && (latitude >= 48.0)) letterDesignator = 'U'; else if ((48.0 > latitude) && (latitude >= 40.0)) letterDesignator = 'T'; else if ((40.0 > latitude) && (latitude >= 32.0)) letterDesignator = 'S'; else if ((32.0 > latitude) && (latitude >= 24.0)) letterDesignator = 'R'; else if ((24.0 > latitude) && (latitude >= 16.0)) letterDesignator = 'Q'; else if ((16.0 > latitude) && (latitude >= 8.0)) letterDesignator = 'P'; else if (( 8.0 > latitude) && (latitude >= 0.0)) letterDesignator = 'N'; else if (( 0.0 > latitude) && (latitude >= -8.0)) letterDesignator = 'M'; else if ((-8.0 > latitude) && (latitude >= -16.0)) letterDesignator = 'L'; else if ((-16.0 > latitude) && (latitude >= -24.0)) letterDesignator = 'K'; else if ((-24.0 > latitude) && (latitude >= -32.0)) letterDesignator = 'J'; else if ((-32.0 > latitude) && (latitude >= -40.0)) letterDesignator = 'H'; else if ((-40.0 > latitude) && (latitude >= -48.0)) letterDesignator = 'G'; else if ((-48.0 > latitude) && (latitude >= -56.0)) letterDesignator = 'F'; else if ((-56.0 > latitude) && (latitude >= -64.0)) letterDesignator = 'E'; else if ((-64.0 > latitude) && (latitude >= -72.0)) letterDesignator = 'D'; else if ((-72.0 > latitude) && (latitude >= -80.0)) letterDesignator = 'C'; else letterDesignator = 'Z'; //This is here as an error flag to show that the Latitude is outside the UTM limits return letterDesignator; } void LLtoUTM(double latitude, double longitude, double& utmNorthing, double& utmEasting, std::string& utmZone) { // converts lat/long to UTM coords. Equations from USGS Bulletin 1532 // East Longitudes are positive, West longitudes are negative. // North latitudes are positive, South latitudes are negative // Lat and Long are in decimal degrees // Written by Chuck Gantz- chuck.gantz@globalstar.com double k0 = 0.9996; double LongOrigin; double eccPrimeSquared; double N, T, C, A, M; double LatRad = latitude * M_PI / 180.0; double LongRad = longitude * M_PI / 180.0; double LongOriginRad; int ZoneNumber = static_cast((longitude + 180.0) / 6.0) + 1; if (latitude >= 56.0 && latitude < 64.0 && longitude >= 3.0 && longitude < 12.0) { ZoneNumber = 32; } // Special zones for Svalbard if (latitude >= 72.0 && latitude < 84.0) { if ( longitude >= 0.0 && longitude < 9.0) ZoneNumber = 31; else if (longitude >= 9.0 && longitude < 21.0) ZoneNumber = 33; else if (longitude >= 21.0 && longitude < 33.0) ZoneNumber = 35; else if (longitude >= 33.0 && longitude < 42.0) ZoneNumber = 37; } LongOrigin = static_cast((ZoneNumber - 1) * 6 - 180 + 3); //+3 puts origin in middle of zone LongOriginRad = LongOrigin * M_PI / 180.0; // compute the UTM Zone from the latitude and longitude std::ostringstream oss; oss << ZoneNumber << UTMLetterDesignator(latitude); utmZone = oss.str(); eccPrimeSquared = WGS84_ECCSQ / (1.0 - WGS84_ECCSQ); N = WGS84_A / sqrt(1.0 - WGS84_ECCSQ * sin(LatRad) * sin(LatRad)); T = tan(LatRad) * tan(LatRad); C = eccPrimeSquared * cos(LatRad) * cos(LatRad); A = cos(LatRad) * (LongRad - LongOriginRad); M = WGS84_A * ((1.0 - WGS84_ECCSQ / 4.0 - 3.0 * WGS84_ECCSQ * WGS84_ECCSQ / 64.0 - 5.0 * WGS84_ECCSQ * WGS84_ECCSQ * WGS84_ECCSQ / 256.0) * LatRad - (3.0 * WGS84_ECCSQ / 8.0 + 3.0 * WGS84_ECCSQ * WGS84_ECCSQ / 32.0 + 45.0 * WGS84_ECCSQ * WGS84_ECCSQ * WGS84_ECCSQ / 1024.0) * sin(2.0 * LatRad) + (15.0 * WGS84_ECCSQ * WGS84_ECCSQ / 256.0 + 45.0 * WGS84_ECCSQ * WGS84_ECCSQ * WGS84_ECCSQ / 1024.0) * sin(4.0 * LatRad) - (35.0 * WGS84_ECCSQ * WGS84_ECCSQ * WGS84_ECCSQ / 3072.0) * sin(6.0 * LatRad)); utmEasting = k0 * N * (A + (1.0 - T + C) * A * A * A / 6.0 + (5.0 - 18.0 * T + T * T + 72.0 * C - 58.0 * eccPrimeSquared) * A * A * A * A * A / 120.0) + 500000.0; utmNorthing = k0 * (M + N * tan(LatRad) * (A * A / 2.0 + (5.0 - T + 9.0 * C + 4.0 * C * C) * A * A * A * A / 24.0 + (61.0 - 58.0 * T + T * T + 600.0 * C - 330.0 * eccPrimeSquared) * A * A * A * A * A * A / 720.0)); if (latitude < 0.0) { utmNorthing += 10000000.0; //10000000 meter offset for southern hemisphere } } void UTMtoLL(double utmNorthing, double utmEasting, const std::string& utmZone, double& latitude, double& longitude) { // converts UTM coords to lat/long. Equations from USGS Bulletin 1532 // East Longitudes are positive, West longitudes are negative. // North latitudes are positive, South latitudes are negative // Lat and Long are in decimal degrees. // Written by Chuck Gantz- chuck.gantz@globalstar.com double k0 = 0.9996; double eccPrimeSquared; double e1 = (1.0 - sqrt(1.0 - WGS84_ECCSQ)) / (1.0 + sqrt(1.0 - WGS84_ECCSQ)); double N1, T1, C1, R1, D, M; double LongOrigin; double mu, phi1, phi1Rad; double x, y; int ZoneNumber; char ZoneLetter; bool NorthernHemisphere; x = utmEasting - 500000.0; //remove 500,000 meter offset for longitude y = utmNorthing; std::istringstream iss(utmZone); iss >> ZoneNumber >> ZoneLetter; if ((static_cast(ZoneLetter) - static_cast('N')) >= 0) { NorthernHemisphere = true;//point is in northern hemisphere } else { NorthernHemisphere = false;//point is in southern hemisphere y -= 10000000.0;//remove 10,000,000 meter offset used for southern hemisphere } LongOrigin = (ZoneNumber - 1.0) * 6.0 - 180.0 + 3.0; //+3 puts origin in middle of zone eccPrimeSquared = WGS84_ECCSQ / (1.0 - WGS84_ECCSQ); M = y / k0; mu = M / (WGS84_A * (1.0 - WGS84_ECCSQ / 4.0 - 3.0 * WGS84_ECCSQ * WGS84_ECCSQ / 64.0 - 5.0 * WGS84_ECCSQ * WGS84_ECCSQ * WGS84_ECCSQ / 256.0)); phi1Rad = mu + (3.0 * e1 / 2.0 - 27.0 * e1 * e1 * e1 / 32.0) * sin(2.0 * mu) + (21.0 * e1 * e1 / 16.0 - 55.0 * e1 * e1 * e1 * e1 / 32.0) * sin(4.0 * mu) + (151.0 * e1 * e1 * e1 / 96.0) * sin(6.0 * mu); phi1 = phi1Rad / M_PI * 180.0; N1 = WGS84_A / sqrt(1.0 - WGS84_ECCSQ * sin(phi1Rad) * sin(phi1Rad)); T1 = tan(phi1Rad) * tan(phi1Rad); C1 = eccPrimeSquared * cos(phi1Rad) * cos(phi1Rad); R1 = WGS84_A * (1.0 - WGS84_ECCSQ) / pow(1.0 - WGS84_ECCSQ * sin(phi1Rad) * sin(phi1Rad), 1.5); D = x / (N1 * k0); latitude = phi1Rad - (N1 * tan(phi1Rad) / R1) * (D * D / 2.0 - (5.0 + 3.0 * T1 + 10.0 * C1 - 4.0 * C1 * C1 - 9.0 * eccPrimeSquared) * D * D * D * D / 24.0 + (61.0 + 90.0 * T1 + 298.0 * C1 + 45.0 * T1 * T1 - 252.0 * eccPrimeSquared - 3.0 * C1 * C1) * D * D * D * D * D * D / 720.0); latitude *= 180.0 / M_PI; longitude = (D - (1.0 + 2.0 * T1 + C1) * D * D * D / 6.0 + (5.0 - 2.0 * C1 + 28.0 * T1 - 3.0 * C1 * C1 + 8.0 * eccPrimeSquared + 24.0 * T1 * T1) * D * D * D * D * D / 120.0) / cos(phi1Rad); longitude = LongOrigin + longitude / M_PI * 180.0; } long int timestampDiff(uint64_t t1, uint64_t t2) { if (t2 > t1) { uint64_t d = t2 - t1; if (d > std::numeric_limits::max()) { return std::numeric_limits::max(); } else { return d; } } else { uint64_t d = t1 - t2; if (d > std::numeric_limits::max()) { return std::numeric_limits::min(); } else { return - static_cast(d); } } } } ================================================ FILE: src/utils/camodocal/src/intrinsic_calib.cc ================================================ #include #include #include #include #include #include #include #include #include #include "camodocal/chessboard/Chessboard.h" #include "camodocal/calib/CameraCalibration.h" #include "camodocal/gpl/gpl.h" int main(int argc, char** argv) { cv::Size boardSize; float squareSize; std::string inputDir; std::string cameraModel; std::string cameraName; std::string prefix; std::string fileExtension; bool useOpenCV; bool viewResults; bool verbose; //========= Handling Program options ========= boost::program_options::options_description desc("Allowed options"); desc.add_options() ("help", "produce help message") ("width,w", boost::program_options::value(&boardSize.width)->default_value(8), "Number of inner corners on the chessboard pattern in x direction") ("height,h", boost::program_options::value(&boardSize.height)->default_value(12), "Number of inner corners on the chessboard pattern in y direction") ("size,s", boost::program_options::value(&squareSize)->default_value(7.f), "Size of one square in mm") ("input,i", boost::program_options::value(&inputDir)->default_value("calibrationdata"), "Input directory containing chessboard images") ("prefix,p", boost::program_options::value(&prefix)->default_value("left-"), "Prefix of images") ("file-extension,e", boost::program_options::value(&fileExtension)->default_value(".png"), "File extension of images") ("camera-model", boost::program_options::value(&cameraModel)->default_value("mei"), "Camera model: kannala-brandt | mei | pinhole") ("camera-name", boost::program_options::value(&cameraName)->default_value("camera"), "Name of camera") ("opencv", boost::program_options::bool_switch(&useOpenCV)->default_value(true), "Use OpenCV to detect corners") ("view-results", boost::program_options::bool_switch(&viewResults)->default_value(false), "View results") ("verbose,v", boost::program_options::bool_switch(&verbose)->default_value(true), "Verbose output") ; boost::program_options::positional_options_description pdesc; pdesc.add("input", 1); boost::program_options::variables_map vm; boost::program_options::store(boost::program_options::command_line_parser(argc, argv).options(desc).positional(pdesc).run(), vm); boost::program_options::notify(vm); if (vm.count("help")) { std::cout << desc << std::endl; return 1; } if (!boost::filesystem::exists(inputDir) && !boost::filesystem::is_directory(inputDir)) { std::cerr << "# ERROR: Cannot find input directory " << inputDir << "." << std::endl; return 1; } camodocal::Camera::ModelType modelType; if (boost::iequals(cameraModel, "kannala-brandt")) { modelType = camodocal::Camera::KANNALA_BRANDT; } else if (boost::iequals(cameraModel, "mei")) { modelType = camodocal::Camera::MEI; } else if (boost::iequals(cameraModel, "pinhole")) { modelType = camodocal::Camera::PINHOLE; } else if (boost::iequals(cameraModel, "scaramuzza")) { modelType = camodocal::Camera::SCARAMUZZA; } else { std::cerr << "# ERROR: Unknown camera model: " << cameraModel << std::endl; return 1; } switch (modelType) { case camodocal::Camera::KANNALA_BRANDT: std::cout << "# INFO: Camera model: Kannala-Brandt" << std::endl; break; case camodocal::Camera::MEI: std::cout << "# INFO: Camera model: Mei" << std::endl; break; case camodocal::Camera::PINHOLE: std::cout << "# INFO: Camera model: Pinhole" << std::endl; break; case camodocal::Camera::SCARAMUZZA: std::cout << "# INFO: Camera model: Scaramuzza-Omnidirect" << std::endl; break; } // look for images in input directory std::vector imageFilenames; boost::filesystem::directory_iterator itr; for (boost::filesystem::directory_iterator itr(inputDir); itr != boost::filesystem::directory_iterator(); ++itr) { if (!boost::filesystem::is_regular_file(itr->status())) { continue; } std::string filename = itr->path().filename().string(); // check if prefix matches if (!prefix.empty()) { if (filename.compare(0, prefix.length(), prefix) != 0) { continue; } } // check if file extension matches if (filename.compare(filename.length() - fileExtension.length(), fileExtension.length(), fileExtension) != 0) { continue; } imageFilenames.push_back(itr->path().string()); if (verbose) { std::cerr << "# INFO: Adding " << imageFilenames.back() << std::endl; } } if (imageFilenames.empty()) { std::cerr << "# ERROR: No chessboard images found." << std::endl; return 1; } if (verbose) { std::cerr << "# INFO: # images: " << imageFilenames.size() << std::endl; } std::sort(imageFilenames.begin(), imageFilenames.end()); cv::Mat image = cv::imread(imageFilenames.front(), -1); const cv::Size frameSize = image.size(); camodocal::CameraCalibration calibration(modelType, cameraName, frameSize, boardSize, squareSize); calibration.setVerbose(verbose); std::vector chessboardFound(imageFilenames.size(), false); for (size_t i = 0; i < imageFilenames.size(); ++i) { // image = cv::imread(imageFilenames.at(i), -1); image = cv::imread(imageFilenames.at(i), 0); camodocal::Chessboard chessboard(boardSize, image); chessboard.findCorners(useOpenCV); if (chessboard.cornersFound()) { if (verbose) { std::cerr << "# INFO: Detected chessboard in image " << i + 1 << ", " << imageFilenames.at(i) << std::endl; } calibration.addChessboardData(chessboard.getCorners()); cv::Mat sketch; chessboard.getSketch().copyTo(sketch); cv::imshow("Image", sketch); cv::waitKey(50); } else if (verbose) { std::cerr << "# INFO: Did not detect chessboard in image " << i + 1 << std::endl; } chessboardFound.at(i) = chessboard.cornersFound(); } cv::destroyWindow("Image"); if (calibration.sampleCount() < 10) { std::cerr << "# ERROR: Insufficient number of detected chessboards." << std::endl; return 1; } if (verbose) { std::cerr << "# INFO: Calibrating..." << std::endl; } double startTime = camodocal::timeInSeconds(); calibration.calibrate(); calibration.writeParams(cameraName + "_camera_calib.yaml"); calibration.writeChessboardData(cameraName + "_chessboard_data.dat"); if (verbose) { std::cout << "# INFO: Calibration took a total time of " << std::fixed << std::setprecision(3) << camodocal::timeInSeconds() - startTime << " sec.\n"; } if (verbose) { std::cerr << "# INFO: Wrote calibration file to " << cameraName + "_camera_calib.yaml" << std::endl; } if (viewResults) { std::vector cbImages; std::vector cbImageFilenames; for (size_t i = 0; i < imageFilenames.size(); ++i) { if (!chessboardFound.at(i)) { continue; } cbImages.push_back(cv::imread(imageFilenames.at(i), -1)); cbImageFilenames.push_back(imageFilenames.at(i)); } // visualize observed and reprojected points calibration.drawResults(cbImages); for (size_t i = 0; i < cbImages.size(); ++i) { cv::putText(cbImages.at(i), cbImageFilenames.at(i), cv::Point(10,20), cv::FONT_HERSHEY_COMPLEX, 0.5, cv::Scalar(255, 255, 255), 1, CV_AA); cv::imshow("Image", cbImages.at(i)); cv::waitKey(0); } } return 0; } ================================================ FILE: src/utils/camodocal/src/sparse_graph/Transform.cc ================================================ #include namespace camodocal { Transform::Transform() { m_q.setIdentity(); m_t.setZero(); } Transform::Transform(const Eigen::Matrix4d& H) { m_q = Eigen::Quaterniond(H.block<3,3>(0,0)); m_t = H.block<3,1>(0,3); } Eigen::Quaterniond& Transform::rotation(void) { return m_q; } const Eigen::Quaterniond& Transform::rotation(void) const { return m_q; } double* Transform::rotationData(void) { return m_q.coeffs().data(); } const double* const Transform::rotationData(void) const { return m_q.coeffs().data(); } Eigen::Vector3d& Transform::translation(void) { return m_t; } const Eigen::Vector3d& Transform::translation(void) const { return m_t; } double* Transform::translationData(void) { return m_t.data(); } const double* const Transform::translationData(void) const { return m_t.data(); } Eigen::Matrix4d Transform::toMatrix(void) const { Eigen::Matrix4d H; H.setIdentity(); H.block<3,3>(0,0) = m_q.toRotationMatrix(); H.block<3,1>(0,3) = m_t; return H; } } ================================================ FILE: src/utils/nlohmann/json.hpp ================================================ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ | | |__ | | | | | | version 3.4.0 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . SPDX-License-Identifier: MIT Copyright (c) 2013-2018 Niels Lohmann . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef NLOHMANN_JSON_HPP #define NLOHMANN_JSON_HPP #define NLOHMANN_JSON_VERSION_MAJOR 3 #define NLOHMANN_JSON_VERSION_MINOR 4 #define NLOHMANN_JSON_VERSION_PATCH 0 #include // all_of, find, for_each #include // assert #include // and, not, or #include // nullptr_t, ptrdiff_t, size_t #include // hash, less #include // initializer_list #include // istream, ostream #include // iterator_traits, random_access_iterator_tag #include // accumulate #include // string, stoi, to_string #include // declval, forward, move, pair, swap // #include #ifndef NLOHMANN_JSON_FWD_HPP #define NLOHMANN_JSON_FWD_HPP #include // int64_t, uint64_t #include // map #include // allocator #include // string #include // vector /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann @since version 1.0.0 */ namespace nlohmann { /*! @brief default JSONSerializer template argument This serializer ignores the template arguments and uses ADL ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) for serialization. */ template struct adl_serializer; template class ObjectType = std::map, template class ArrayType = std::vector, class StringType = std::string, class BooleanType = bool, class NumberIntegerType = std::int64_t, class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, template class AllocatorType = std::allocator, template class JSONSerializer = adl_serializer> class basic_json; /*! @brief JSON Pointer A JSON pointer defines a string syntax for identifying a specific value within a JSON document. It can be used with functions `at` and `operator[]`. Furthermore, JSON pointers are the base for JSON patches. @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) @since version 2.0.0 */ template class json_pointer; /*! @brief default JSON class This type is the default specialization of the @ref basic_json class which uses the standard template types. @since version 1.0.0 */ using json = basic_json<>; } // namespace nlohmann #endif // #include // This file contains all internal macro definitions // You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them // exclude unsupported compilers #if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) #if defined(__clang__) #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" #endif #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" #endif #endif #endif // disable float-equal warnings on GCC/clang #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif // disable documentation warnings on clang #if defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdocumentation" #endif // allow for portable deprecation warnings #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #define JSON_DEPRECATED __attribute__((deprecated)) #elif defined(_MSC_VER) #define JSON_DEPRECATED __declspec(deprecated) #else #define JSON_DEPRECATED #endif // allow to disable exceptions #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try #define JSON_CATCH(exception) catch(exception) #define JSON_INTERNAL_CATCH(exception) catch(exception) #else #define JSON_THROW(exception) std::abort() #define JSON_TRY if(true) #define JSON_CATCH(exception) if(false) #define JSON_INTERNAL_CATCH(exception) if(false) #endif // override exception macros #if defined(JSON_THROW_USER) #undef JSON_THROW #define JSON_THROW JSON_THROW_USER #endif #if defined(JSON_TRY_USER) #undef JSON_TRY #define JSON_TRY JSON_TRY_USER #endif #if defined(JSON_CATCH_USER) #undef JSON_CATCH #define JSON_CATCH JSON_CATCH_USER #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_CATCH_USER #endif #if defined(JSON_INTERNAL_CATCH_USER) #undef JSON_INTERNAL_CATCH #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER #endif // manual branch prediction #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) #else #define JSON_LIKELY(x) x #define JSON_UNLIKELY(x) x #endif // C++ language standard detection #if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 #define JSON_HAS_CPP_17 #define JSON_HAS_CPP_14 #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) #define JSON_HAS_CPP_14 #endif /*! @brief macro to briefly define a mapping between an enum and JSON @def NLOHMANN_JSON_SERIALIZE_ENUM @since version 3.4.0 */ #define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ template \ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [e](const std::pair& ej_pair) -> bool \ { \ return ej_pair.first == e; \ }); \ j = ((it != std::end(m)) ? it : std::begin(m))->second; \ } \ template \ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ { \ static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ static const std::pair m[] = __VA_ARGS__; \ auto it = std::find_if(std::begin(m), std::end(m), \ [j](const std::pair& ej_pair) -> bool \ { \ return ej_pair.second == j; \ }); \ e = ((it != std::end(m)) ? it : std::begin(m))->first; \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They // may be removed in the future once the class is split. #define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ template class ObjectType, \ template class ArrayType, \ class StringType, class BooleanType, class NumberIntegerType, \ class NumberUnsignedType, class NumberFloatType, \ template class AllocatorType, \ template class JSONSerializer> #define NLOHMANN_BASIC_JSON_TPL \ basic_json // #include #include // not #include // size_t #include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type namespace nlohmann { namespace detail { // alias templates to reduce boilerplate template using enable_if_t = typename std::enable_if::type; template using uncvref_t = typename std::remove_cv::type>::type; // implementation of C++14 index_sequence and affiliates // source: https://stackoverflow.com/a/32223343 template struct index_sequence { using type = index_sequence; using value_type = std::size_t; static constexpr std::size_t size() noexcept { return sizeof...(Ints); } }; template struct merge_and_renumber; template struct merge_and_renumber, index_sequence> : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; template struct make_index_sequence : merge_and_renumber < typename make_index_sequence < N / 2 >::type, typename make_index_sequence < N - N / 2 >::type > {}; template<> struct make_index_sequence<0> : index_sequence<> {}; template<> struct make_index_sequence<1> : index_sequence<0> {}; template using index_sequence_for = make_index_sequence; // dispatch utility (taken from ranges-v3) template struct priority_tag : priority_tag < N - 1 > {}; template<> struct priority_tag<0> {}; // taken from ranges-v3 template struct static_const { static constexpr T value{}; }; template constexpr T static_const::value; } // namespace detail } // namespace nlohmann // #include #include // not #include // numeric_limits #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval // #include // #include // #include #include // #include namespace nlohmann { namespace detail { template struct make_void { using type = void; }; template using void_t = typename make_void::type; } // namespace detail } // namespace nlohmann // http://en.cppreference.com/w/cpp/experimental/is_detected namespace nlohmann { namespace detail { struct nonesuch { nonesuch() = delete; ~nonesuch() = delete; nonesuch(nonesuch const&) = delete; void operator=(nonesuch const&) = delete; }; template class Op, class... Args> struct detector { using value_t = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { using value_t = std::true_type; using type = Op; }; template