diff --git a/.gitignore b/.gitignore
index eff634ad2..b2f6230d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,3 +51,6 @@
*docs/_build
*docs/doxyoutput
*docs/api
+
+# test
+test*
diff --git a/README.md b/README.md
index b2b84c112..d80e99024 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,87 @@
+# Nvblox-modify
+
+#### Code Pipeline of NVBlox
+
+1. Data loader
+
+2. Frame integration
+
+ 1. After loading data (the code API): Fuser::integrateFrame(const int frame_number)
+
+ 2. RgbdMapper::integrateOSLidarDepth -> ProjectiveTsdfIntegrator::integrateFrame -> ProjectiveTsdfIntegrator::integrateFrameTemplate
+
+ > * set voxel_size and truncation_distance
+ > * set truncation_distance_m = truncation_distance_vox * voxel_size
+ > * Identify blocks given the camera view: view_calculator_.getBlocksInImageViewRaycast
+ > * getBlocksByRaycastingPixels: Raycasts through (possibly subsampled) pixels in the image, use the kernal function
+ > * *void* combinedBlockIndicesInImageKernel: retrieve visiable block by raycasting voxels, done in GPU
+ >
+ >
+ > * TSDF integration given block indices: integrateBlocksTemplate
+ >
+ > * ProjectiveTsdfIntegrator::integrateBlocks: block integration for the OSLidar, use the kernal function
+ > * integrateBlocksKernel: TSDF integration for each block, done in GPU
+ > * projectThreadVoxel: convert blocks' indices into coordinates, retrieve voxels from the block, and project them onto the image to check whether they are visible or not
+ > * interpolateOSLidarImage: linear interpolation of depth images given float coordinates
+ > * ```const Index2D u_M_rounded = u_px.array().round().cast();```
+ > * ```u_M_rounded.x() < 0 || u_M_rounded.y() < 0 || u_M_rounded.x() >= cols || u_M_rounded.y() >= rows)```: check bounds
+ > * updateVoxel: update the TSDF values of all visible voxels.
+
+3. Weight averaging methods
+ ```
+ Projective distance:
+ 1: constant weight, truncate the fused_distance
+ 2: constant weight, truncate the voxel_distance_measured
+ 3: linear weight, truncate the voxel_distance_measured
+ 4: exponential weight, truncate the voxel_distance_measured
+ Non-Projective distance:
+ 5: weight and distance derived from VoxField
+ 6: linear weight, distance derived from VoxField
+ ```
+
+3. Output data
+ 1. Mesh map
+ 2. ESDF map
+ 3. Obstacle map: points from the ESDF map whose distance is smaller than a threshold
+
+4. Global planning test
+
+--------------------------
+### Demo with [KITTI](https://www.cvlibs.net/datasets/kitti) dataset
+
+1. Prepare data:
+* Download test data
+
+ * [2011_09_30_drive_0027_sync](http://gofile.me/72EEc/NGdCJrzA5)
+
+2. Run the NVBlox
+
+ ```../script/run_fuse_kitti.sh```
+
+* [Experiments on NVBlox with the KITTI dataset](docs/experiments_kitti.md)
+
+--------------------------
+### Demo with the [FusionPortable](https://ram-lab.com/file/site/multi-sensor-dataset) dataset
+
+##### Demo
+
+1. Download test data
+
+ * [20220226_campus_road_day](http://gofile.me/72EEc/MDghPwECu)
+
+3. Run the NVBlox:
+
+ ```../script/run_fuse_fusionportable.sh```
+
+4. We can view the output mesh using the Open3D viewer.
+
+ ```python3 ../../visualization/visualize_mesh.py 20220216_garden_day_mesh.ply```
+
+* [Tricks to preprocess OSLiDAR points](docs/preprocess_OSLiDAR.md)
+* [Experiments on NVBlox and VDBMapping](docs/experiments_fusionportable.md)
+
+--------------------------
+--------------------------
# nvblox
Signed Distance Functions (SDFs) on NVIDIA GPUs.
@@ -49,6 +133,7 @@ cmake .. && make && cd tests && ctest
```
## Run an example
+
In this example we fuse data from the [3DMatch dataset](https://3dmatch.cs.princeton.edu/). First let's grab the dataset. Here I'm downloading it to my dataset folder `~/dataset/3dmatch`.
```
wget http://vision.princeton.edu/projects/2016/3DMatch/downloads/rgbd-datasets/sun3d-mit_76_studyroom-76-1studyroom2.zip -P ~/datasets/3dmatch
@@ -158,3 +243,7 @@ export OPENBLAS_CORETYPE=ARMV8
# License
This code is under an [open-source license](LICENSE) (Apache 2.0). :)
+
+# Reference
+[1] Parallel Banding Algorithm to Compute Exact Distance Transform with the GPU
+> compute EDT with the GPU
\ No newline at end of file
diff --git a/docs/.~experiments.md b/docs/.~experiments.md
new file mode 100644
index 000000000..5d5b8c025
--- /dev/null
+++ b/docs/.~experiments.md
@@ -0,0 +1,84 @@
+### Experimental Results
+
+##### Reconstruction results (voxel_size = 0.1)
+
+* Sequence: 20220216_garden_day, *2000* frames
+ * Projective distance
+ * NVBlox (constant weight, truncate fused_distance):
+ Point cloud distance [m]: 0.0713986; Coverage [%]: 0.648376
+ * NVBlox (constant weight, truncate measured_distance):
+ Point cloud distance [m]: 0.070609; Coverage [%]: 0.716344
+ * NVBlox (linear weight, truncate fused_distance):
+ Point cloud distance [m]: 0.0687769; Coverage [%]: 0.73421
+ * NVBlox (exp weight, truncate fused_distance):
+ Point cloud distance [m]: 0.0690283; Coverage [%]: 0.714388
+
+ * Non-Projective distance
+ * NVBlox (non-projective distance, truncate fused_distance): (distance_th = 10.0m)
+ Point cloud distance [m]: 0.0480275; Coverage [%]: 0.388869
+ * NVBlox (non-projective distance, truncate fused_distance): (distance_th = 30.0m)
+ Point cloud distance [m]: 0.058146; Coverage [%]: 0.631758
+ * NVBlox (non-projective distance, truncate fused_distance): (distance_th = 50.0m)
+ Point cloud distance [m]: 0.057562; Coverage [%]: 0.651013
+
+ * VDBMapping:
+ Point cloud distance [m]: 0.074576; Coverage [%]: 0.724301
+
+* Sequence: 20220216_canteen_day, *2600* frames
+ * Non-Projective distance
+ * NVBlox (non-projective distance, truncate fused_distance): (distance_th = 70.0m)
+ Point cloud distance [m]: 0.102588; Coverage [%]: 0.602803
+
+* Sequence: 20220225_building_day, *2300* frames
+ * Non-Projective distance
+ * NVBlox (non-projective distance, truncate fused_distance): (distance_th = 70.0m)
+ Point cloud distance [m]: 0.0704632; Coverage [%]: 0.574235
+
+* Sequence: 20220216_escalator_day, *3200* frames
+ * Projective distance
+ * NVBlox (linear weight, truncate fused_distance):
+ Point cloud distance [m]: 0.615201; Coverage [%]: 0.649969
+
+ * Non-Projective distance
+ * NVBlox (non-projective distance, truncate fused_distance): (distance_th = 70.0m)
+ Point cloud distance [m]: 0.0526527; Coverage [%]: 0.629851
+ * NVBlox (non-projective distance, linear weight, truncate fused_distance): (distance_th = 70.0m)
+ Point cloud distance [m]: 0.0602039; Coverage [%]: 0.641247
+
+##### Computation time (voxel_size = 0.1)
+* Sequence: 20220216_garden_day
+ * Normal computation with a GPU: 0.7ms per frame
+ * NVBLox: 14.2ms per frame (total 2000)
+ * VDBMapping: 384.062 ms per frame (total 2500)
+
+* Sequence: 20220216_canteen_day
+ * Normal computation with a GPU: 0.01ms per frame
+ * NVBLox: 3ms per frame (total 2600)
+
+* Sequence: 20220226_campus_road_day
+ * NVBLox: 5ms per frame (total 2000)
+
+#### Appendix
+
+**Reconstruction results of NVBLox on 20220216_garden_day (voxel_size = 0.1)**
+
+
+
+
+
+
+**Reconstruction results of NVBLox on 20220225_building_day (voxel_size = 0.1)**
+
+
+
+
+
+
+**Reconstruction results of NVBLox on other sequences (voxel_size = 0.1)**
+
+
+
+
+
+
+
diff --git a/docs/.~experiments_kitti.md b/docs/.~experiments_kitti.md
new file mode 100644
index 000000000..88d268711
--- /dev/null
+++ b/docs/.~experiments_kitti.md
@@ -0,0 +1,12 @@
+### Experimental Results
+
+#### Appendix
+
+**Reconstruction results of NVBLox on 2011_09_30_drive_0027_sync (voxel_size = 0.1)**
+
+
+
+
+
+
+
diff --git a/docs/code_review_voxfield_panmap.md b/docs/code_review_voxfield_panmap.md
new file mode 100755
index 000000000..fd1d4a705
--- /dev/null
+++ b/docs/code_review_voxfield_panmap.md
@@ -0,0 +1,14 @@
+## VoxField
+
+
+
+## Panmap
+
+Process semantic point cloud
+
+```c++
+conversions.h - inline void convertPointcloud
+1. define Label
+2. give label specific definitions
+```
+
diff --git a/docs/experiments_fusionportable.md b/docs/experiments_fusionportable.md
new file mode 100755
index 000000000..65ab7b679
--- /dev/null
+++ b/docs/experiments_fusionportable.md
@@ -0,0 +1,89 @@
+## Experiments
+
+### Reconstruction results
+
+###### Weight averaging methods (TSDF integration):
+```
+Projective distance:
+ 1: constant weight, truncate the fused_distance
+ 2: constant weight, truncate the voxel_distance_measured
+ 3: linear weight, truncate the voxel_distance_measured
+ 4: exponential weight, truncate the voxel_distance_measured
+Non-Projective distance:
+ 5: weight and distance derived from VoxField
+ 6: linear weight, distance derived from VoxField
+```
+
+###### Weight averaging methods (Color integration):
+```
+ 1: constant weight, truncate the voxel_distance_measured
+ 2: linear weight, truncate the voxel_distance_measured
+ 3: exponential weight, truncate the voxel_distance_measured
+ 4: sensor distance weight
+ 5: linear weight * sensor distance weight
+```
+
+###### Scene reconstruction results (voxel_size = 0.1)
+| Sequence | Algorithm | Method | Point cloud distance | Coverage [%] |
+| :------- | :-------- | :----- | :------------------- | :------------|
+| 20220216_garden_day (2000) | NVBlox | 1 | 0.0713986 | 0.648376 |
+| 20220216_garden_day (2000) | NVBlox | 2 | 0.070609 | 0.716344 |
+| 20220216_garden_day (2000) | NVBlox | 3 | 0.0687769 | 0.73421 |
+| 20220216_garden_day (2000) | NVBlox | 4 | 0.0690283 | 0.714388 |
+| 20220216_garden_day (2000) | NVBlox | 5 | 0.0480275 | 0.388869 |
+| 20220216_garden_day (2000) | NVBlox | 5 | 0.058146 | 0.631758 |
+| 20220216_garden_day (2000) | NVBlox | 5 | 0.057562 | 0.651013 |
+| 20220216_garden_day (2000) | NVBlox | 6 | 0.0607844 | 0.689476 |
+| 20220216_garden_day (2000) | VDBMapping | 3 | 0.074576 | 0.724301 |
+| 20220216_canteen_day (2600) | NVBlox | 5 (70m) | 0.102588 | 0.602803 |
+| 20220225_building_day (2300) | NVBlox | 5 (70m) | 0.0704632 | 0.574235 |
+| 20220216_escalator_day (3200) | NVBlox | 3 | 0.615201 | 0.649969 |
+| 20220216_escalator_day (3200) | NVBlox | 5 (70m) | 0.0526527 | 0.629851 |
+| 20220216_escalator_day (3200) | NVBlox | 6 (50m) | 0.0585957 | 0.641295 |
+
+###### Computation time (voxel_size = 0.1)
+| Sequence | Algorithm/ Module | Time per frame |
+| :------- | :-------- | :----- |
+| 20220216_garden_day | Normal computation | 0.7ms |
+| 20220216_garden_day | NVBLox | 14.2ms (2000) |
+| 20220216_garden_day | VDBMapping | 384.062ms (2500) |
+| 20220216_canteen_day | Normal computation | 0.01ms |
+| 20220216_canteen_day | NVBLox | 3ms (2600) |
+| 20220226_campus_road_day | NVBLox | 5ms (2000) |
+
+###### Figures
+
+Reconstruction of NVBLox on 20220216_garden_day (voxel_size = 0.1)
+
+
+
+
+
+
+Reconstruction of NVBLox on 20220225_building_day (voxel_size = 0.1)
+
+
+
+
+
+
+Reconstruction of NVBLox on other sequences (voxel_size = 0.1)
+
+
+
+
+
+
+Obstacle information (voxel_size = 0.1)
+
+
+
+
+Reconstruction of NVBLox on 20221126_lab_static (voxel_size = 0.05) with dynamic objcts
+
+
100 frames
+
+
150 frames
+
+
600 frames
+
\ No newline at end of file
diff --git a/docs/experiments_kitti.md b/docs/experiments_kitti.md
new file mode 100755
index 000000000..c0a2c7488
--- /dev/null
+++ b/docs/experiments_kitti.md
@@ -0,0 +1,15 @@
+### Experimental Results
+
+#### Appendix
+
+**Reconstruction results of NVBLox on 2011_09_30_drive_0027_sync (voxel_size = 0.1)**
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/images/2011_09_30_drive_0027_sync_mesh.png b/docs/images/2011_09_30_drive_0027_sync_mesh.png
new file mode 100644
index 000000000..7b53831da
Binary files /dev/null and b/docs/images/2011_09_30_drive_0027_sync_mesh.png differ
diff --git a/docs/images/2011_09_30_drive_0027_sync_mesh_closeview.png b/docs/images/2011_09_30_drive_0027_sync_mesh_closeview.png
new file mode 100644
index 000000000..da841b0c7
Binary files /dev/null and b/docs/images/2011_09_30_drive_0027_sync_mesh_closeview.png differ
diff --git a/docs/images/20220216_escalator_day_mesh.png b/docs/images/20220216_escalator_day_mesh.png
new file mode 100644
index 000000000..5a9345902
Binary files /dev/null and b/docs/images/20220216_escalator_day_mesh.png differ
diff --git a/docs/images/20220216_garden_day_mesh_img.png b/docs/images/20220216_garden_day_mesh_img.png
new file mode 100644
index 000000000..6012319b3
Binary files /dev/null and b/docs/images/20220216_garden_day_mesh_img.png differ
diff --git a/docs/images/20220216_garden_day_mesh_img_eval_error.png b/docs/images/20220216_garden_day_mesh_img_eval_error.png
new file mode 100644
index 000000000..f847e892f
Binary files /dev/null and b/docs/images/20220216_garden_day_mesh_img_eval_error.png differ
diff --git a/docs/images/20220225_building_day_mesh_img.png b/docs/images/20220225_building_day_mesh_img.png
new file mode 100644
index 000000000..d47d7a702
Binary files /dev/null and b/docs/images/20220225_building_day_mesh_img.png differ
diff --git a/docs/images/20220225_building_day_mesh_img_eval_error.png b/docs/images/20220225_building_day_mesh_img_eval_error.png
new file mode 100644
index 000000000..2d3b198e8
Binary files /dev/null and b/docs/images/20220225_building_day_mesh_img_eval_error.png differ
diff --git a/docs/images/20220226_campus_road_day_mesh.png b/docs/images/20220226_campus_road_day_mesh.png
new file mode 100644
index 000000000..372cfbf83
Binary files /dev/null and b/docs/images/20220226_campus_road_day_mesh.png differ
diff --git a/docs/images/20220226_campus_road_day_obs.png b/docs/images/20220226_campus_road_day_obs.png
new file mode 100644
index 000000000..1bae49049
Binary files /dev/null and b/docs/images/20220226_campus_road_day_obs.png differ
diff --git a/docs/images/20221126_lab_static_1.png b/docs/images/20221126_lab_static_1.png
new file mode 100644
index 000000000..b7569bc2f
Binary files /dev/null and b/docs/images/20221126_lab_static_1.png differ
diff --git a/docs/images/20221126_lab_static_2.png b/docs/images/20221126_lab_static_2.png
new file mode 100644
index 000000000..92775c984
Binary files /dev/null and b/docs/images/20221126_lab_static_2.png differ
diff --git a/docs/images/20221126_lab_static_3.png b/docs/images/20221126_lab_static_3.png
new file mode 100644
index 000000000..0d5973b57
Binary files /dev/null and b/docs/images/20221126_lab_static_3.png differ
diff --git a/docs/images/kitti_color_mesh.png b/docs/images/kitti_color_mesh.png
new file mode 100644
index 000000000..440a990c4
Binary files /dev/null and b/docs/images/kitti_color_mesh.png differ
diff --git a/docs/images/kitti_global_path.png b/docs/images/kitti_global_path.png
new file mode 100644
index 000000000..6e11fcc33
Binary files /dev/null and b/docs/images/kitti_global_path.png differ
diff --git a/docs/images/ouster_angle_after_filter.jpg b/docs/images/ouster_angle_after_filter.jpg
new file mode 100644
index 000000000..0cacd3b3c
Binary files /dev/null and b/docs/images/ouster_angle_after_filter.jpg differ
diff --git a/docs/images/ouster_angle_before_filter.jpg b/docs/images/ouster_angle_before_filter.jpg
new file mode 100644
index 000000000..d0fafe92d
Binary files /dev/null and b/docs/images/ouster_angle_before_filter.jpg differ
diff --git a/docs/images/voxfield_result.png b/docs/images/voxfield_result.png
new file mode 100644
index 000000000..1847ea20f
Binary files /dev/null and b/docs/images/voxfield_result.png differ
diff --git a/docs/preprocess_OSLiDAR.md b/docs/preprocess_OSLiDAR.md
new file mode 100755
index 000000000..05c6e11f1
--- /dev/null
+++ b/docs/preprocess_OSLiDAR.md
@@ -0,0 +1,67 @@
+### Tricks to preprocessing OSLiDAR points
+
+### Explanation
+
+The perfect intrinsic model of a LiDAR should be (like VLP, Hesai):
+
+* The elevation angle (angle between ray and +z) of each line increases evenly
+* The azimuth angle of each point at one line (2pi - angle between ray and +x) increases evenly
+
+But the intrinsics of the OSLiDAR are unstable. This means that the conversion between a point cloud and a range image is not lossless. Especially, near points may have large noise. We analyze the angle characters of OSLiDAR:
+
+
+

+
+
+
+We need to preprocess the OSLiDAR points. For each line, we only keep points if their length is within [scan_min, scan_max]. And then we compute the mean elevation angle of the first line and last line as the starting and ending elevation angle. We analyze the angle characters of OSLiDAR with filtered points:
+
+
+
+
+### Code to generate range images from point clouds of OSLiDAR
+
+```c++
+cv::Mat depth_img(num_elevation_divisions, num_azimuth_divisions,
+ CV_16UC1, cv::Scalar(0));
+for (const auto &pt : input_cloud) {
+ float r = sqrt(pt.x * pt.x + pt.y * pt.y + pt.z * pt.z);
+ if (r <= SCAN_MIN || r >= SCAN_MAX) continue;
+ float elevation_angle_rad = acos(pt.z / r);
+ float azimuth_angle_rad = M_PI - atan2(pt.y, pt.x);
+ int row_id = round((elevation_angle_rad - start_elevation_rad) /
+ rads_per_pixel_elevation);
+ if (row_id < 0 || row_id > num_azimuth_divisions - 1) continue;
+ int col_id = round(azimuth_angle_rad / rads_per_pixel_azimuth);
+ if (col_id >= 2048) col_id -= 2048;
+ float dep = r * default_scale_factor;
+ if (dep > std::numeric_limits::max()) continue;
+ if (dep < 0.0f) continue;
+ depth_img.at(row_id, col_id) = uint16_t(dep);
+}
+```
+
+### Code to generate point clouds from range images for OSLiDAR
+
+```c++
+pcl::PointCloud output_cloud;
+for (size_t row_id = 0; row_id < depth_img.rows; row_id++) {
+ for (size_t col_id = 0; col_id < height_img.cols; col_id++) {
+ float dep = depth_img.at(row_id, col_id);
+ float elevation_angle_rad =
+ row_id * rads_per_pixel_elevation + start_elevation_rad;
+ float z = height_img.at(row_id, col_id);
+ float r = sqrt(dep * dep - z * z);
+ float azimuth_angle_rad = M_PI - float(col_id) * rads_per_pixel_azimuth;
+ float x = r * cos(azimuth_angle_rad);
+ float y = r * sin(azimuth_angle_rad);
+ pcl::PointXYZ pt;
+ pt.x = x;
+ pt.y = y;
+ pt.z = z;
+ output_cloud.push_back(pt);
+ }
+}
+
+```
+
diff --git a/nvblox/CMakeLists.txt b/nvblox/CMakeLists.txt
index fab37aa34..db587344c 100644
--- a/nvblox/CMakeLists.txt
+++ b/nvblox/CMakeLists.txt
@@ -100,10 +100,13 @@ add_library(nvblox_lib SHARED
src/core/bounding_boxes.cpp
src/core/bounding_spheres.cpp
src/core/camera.cpp
+ src/core/camera_pinhole.cpp
+ src/core/frustum.cpp
src/core/color.cpp
src/core/cuda/blox.cu
src/core/cuda/image_cuda.cu
src/core/cuda/warmup.cu
+ src/core/cuda/image_operation.cu
src/core/image.cpp
src/core/interpolation_3d.cpp
src/core/mapper.cpp
@@ -201,6 +204,7 @@ target_link_libraries(nvblox_interface INTERFACE
nvblox_lib
nvblox_gpu_hash
nvblox_cuda_check
+ nvblox_datasets
${GLOG_LIBRARIES}
${CUDA_LIBRARIES}
Eigen3::Eigen
diff --git a/nvblox/executables/CMakeLists.txt b/nvblox/executables/CMakeLists.txt
index 23cf07a9d..ccdeba211 100644
--- a/nvblox/executables/CMakeLists.txt
+++ b/nvblox/executables/CMakeLists.txt
@@ -1,33 +1,68 @@
+include_directories(include)
+
# Datasets library
add_library(nvblox_datasets SHARED
- src/datasets/3dmatch.cpp
- src/datasets/image_loader.cpp
- src/datasets/replica.cpp
- src/fuser.cpp
+ src/datasets/image_loader.cpp
)
target_include_directories(nvblox_datasets PUBLIC
- $
- $
+ $
+ $
)
target_link_libraries(nvblox_datasets nvblox_lib)
set_target_properties(nvblox_datasets PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
+####################################
+#### NOTE(gogojjh): compile libraries for datasets
+add_library(nvblox_datasets_3dmatch SHARED
+ src/datasets/3dmatch.cpp
+ src/fuser_rgbd.cpp
+)
+target_link_libraries(nvblox_datasets_3dmatch nvblox_lib nvblox_datasets)
-# 3Dmatch executable
-add_executable(fuse_3dmatch
- src/fuse_3dmatch.cpp
+add_library(nvblox_datasets_replica SHARED
+ src/datasets/replica.cpp
+ src/fuser_rgbd.cpp
+)
+target_link_libraries(nvblox_datasets_replica nvblox_lib nvblox_datasets)
+
+add_library(nvblox_datasets_fusionportable SHARED
+ src/datasets/fusionportable.cpp
+ src/fuser_lidar.cpp
+)
+target_link_libraries(nvblox_datasets_fusionportable nvblox_lib nvblox_datasets)
+
+add_library(nvblox_datasets_kitti SHARED
+ src/datasets/kitti.cpp
+ src/fuser_lidar.cpp
)
+target_link_libraries(nvblox_datasets_kitti nvblox_lib nvblox_datasets)
+
+####################################
+# 3Dmatch executable
+add_executable(fuse_3dmatch src/fuse_3dmatch.cpp)
target_link_libraries(fuse_3dmatch
- nvblox_lib nvblox_datasets
+ nvblox_lib nvblox_datasets nvblox_datasets_3dmatch
)
set_target_properties(fuse_3dmatch PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
# Replica executable
-add_executable(fuse_replica
- src/fuse_replica.cpp
-)
+add_executable(fuse_replica src/fuse_replica.cpp)
target_link_libraries(fuse_replica
- nvblox_lib nvblox_datasets
+ nvblox_lib nvblox_datasets nvblox_datasets_replica
)
set_target_properties(fuse_replica PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
+
+# FusionPortable executable
+add_executable(fuse_fusionportable src/fuse_fusionportable.cpp)
+target_link_libraries(fuse_fusionportable
+ nvblox_lib nvblox_datasets nvblox_datasets_fusionportable
+)
+set_target_properties(fuse_fusionportable PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
+
+# KITTI executable
+add_executable(fuse_kitti src/fuse_kitti.cpp)
+target_link_libraries(fuse_kitti
+ nvblox_lib nvblox_datasets nvblox_datasets_kitti
+)
+set_target_properties(fuse_kitti PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
diff --git a/nvblox/executables/include/nvblox/datasets/3dmatch.h b/nvblox/executables/include/nvblox/datasets/3dmatch.h
index dd68918d5..2b36966e7 100644
--- a/nvblox/executables/include/nvblox/datasets/3dmatch.h
+++ b/nvblox/executables/include/nvblox/datasets/3dmatch.h
@@ -21,21 +21,21 @@ limitations under the License.
#include "nvblox/core/types.h"
#include "nvblox/datasets/data_loader.h"
#include "nvblox/datasets/image_loader.h"
-#include "nvblox/executables/fuser.h"
+#include "nvblox/executables/fuser_rgbd.h"
namespace nvblox {
namespace datasets {
namespace threedmatch {
// Build a Fuser for the 3DMatch dataset
-std::unique_ptr createFuser(const std::string base_path,
- const int seq_id);
+std::unique_ptr createFuser(const std::string base_path,
+ const int seq_id);
///@brief A class for loading 3DMatch data
class DataLoader : public RgbdDataLoaderInterface {
public:
DataLoader(const std::string& base_path, const int seq_id,
- bool multithreaded = true);
+ bool multithreaded = true);
/// Interface for a function that loads the next frames in a dataset
///@param[out] depth_frame_ptr The loaded depth frame.
@@ -48,6 +48,21 @@ class DataLoader : public RgbdDataLoaderInterface {
Camera* camera_ptr, // NOLINT
ColorImage* color_frame_ptr = nullptr) override;
+ /// Interface for a function that loads the next frames in a dataset
+ ///@param[out] depth_frame_ptr The loaded depth frame.
+ ///@param[out] T_L_C_ptr Transform from Camera to the Layer frame.
+ ///@param[out] camera_ptr The intrinsic camera model.
+ ///@param[out] lidar_ptr The intrinsic oslidar model.
+ ///@param[out] height_frame_ptr The loaded z frame.
+ ///@param[out] color_frame_ptr Optional, load color frame.
+ ///@return Whether loading succeeded.
+ DataLoadResult loadNext(DepthImage* depth_frame_ptr, // NOLINT
+ Transform* T_L_C_ptr, // NOLINT
+ CameraPinhole* camera_ptr, // NOLINT
+ OSLidar* lidar_ptr, // NOLINT
+ DepthImage* height_frame_ptr, // NOLINT
+ ColorImage* color_frame_ptr = nullptr) override;
+
protected:
const std::string base_path_;
const int seq_id_;
diff --git a/nvblox/executables/include/nvblox/datasets/data_loader.h b/nvblox/executables/include/nvblox/datasets/data_loader.h
index ce3492d70..3b747eba6 100644
--- a/nvblox/executables/include/nvblox/datasets/data_loader.h
+++ b/nvblox/executables/include/nvblox/datasets/data_loader.h
@@ -16,7 +16,10 @@ limitations under the License.
#pragma once
#include "nvblox/core/camera.h"
+#include "nvblox/core/camera_pinhole.h"
#include "nvblox/core/image.h"
+#include "nvblox/core/lidar.h"
+#include "nvblox/core/oslidar.h"
#include "nvblox/core/types.h"
#include "nvblox/datasets/image_loader.h"
@@ -27,10 +30,12 @@ enum class DataLoadResult { kSuccess, kBadFrame, kNoMoreData };
class RgbdDataLoaderInterface {
public:
- RgbdDataLoaderInterface(std::unique_ptr>&& depth_image_loader,
- std::unique_ptr>&& color_image_loader)
+ RgbdDataLoaderInterface(
+ std::unique_ptr>&& depth_image_loader,
+ std::unique_ptr>&& color_image_loader)
: depth_image_loader_(std::move(depth_image_loader)),
color_image_loader_(std::move(color_image_loader)) {}
+
virtual ~RgbdDataLoaderInterface() = default;
/// Interface for a function that loads the next frames in a dataset
@@ -44,6 +49,22 @@ class RgbdDataLoaderInterface {
Camera* camera_ptr, // NOLINT
ColorImage* color_frame_ptr = nullptr) = 0;
+ /// Interface for a function that loads the next frames in a dataset
+ ///@param[out] depth_frame_ptr The loaded depth frame.
+ ///@param[out] T_L_C_ptr Transform from Camera to the Layer frame.
+ ///@param[out] camera_ptr The intrinsic camera model.
+ ///@param[out] lidar_ptr The intrinsic Ouster lidar model.
+ ///@param[out] height_frame_ptr The loaded z frame.
+ ///@param[out] color_frame_ptr Optional, load color frame.
+ ///@return Whether loading succeeded.
+ virtual DataLoadResult loadNext(
+ DepthImage* depth_frame_ptr, // NOLINT
+ Transform* T_L_C_ptr, // NOLINT
+ CameraPinhole* camera_ptr, // NOLINT
+ OSLidar* lidar_ptr, // NOLINT
+ DepthImage* height_frame_ptr = nullptr, // NOLINT
+ ColorImage* color_frame_ptr = nullptr) = 0;
+
protected:
// Objects which do (multithreaded) image loading.
std::unique_ptr> depth_image_loader_;
diff --git a/nvblox/executables/include/nvblox/datasets/fusionportable.h b/nvblox/executables/include/nvblox/datasets/fusionportable.h
new file mode 100644
index 000000000..83fc428f3
--- /dev/null
+++ b/nvblox/executables/include/nvblox/datasets/fusionportable.h
@@ -0,0 +1,108 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#pragma once
+
+#include
+#include
+
+#include "nvblox/core/types.h"
+#include "nvblox/datasets/data_loader.h"
+#include "nvblox/datasets/image_loader.h"
+#include "nvblox/executables/fuser_lidar.h"
+
+namespace nvblox {
+namespace datasets {
+namespace fusionportable {
+
+// NOTE(gogojjh): the default settings of preprocess height_image
+// offset
+constexpr float kDefaultUintDepthScaleFactor = 1.0f / 1000.0f;
+constexpr float kDefaultUintDepthScaleOffset = 10.0f;
+
+// Build a FuserLidar for the FusionPortable dataset
+std::unique_ptr createFuser(const std::string base_path,
+ const int seq_id);
+
+///@brief A class for loading FusionPortable data
+class DataLoader : public RgbdDataLoaderInterface {
+ public:
+ DataLoader(const std::string& base_path, const int seq_id,
+ bool multithreaded = true);
+
+ DataLoadResult loadNext(DepthImage* depth_frame_ptr, // NOLINT
+ Transform* T_L_C_ptr, // NOLINT
+ Camera* camera_ptr, // NOLINT
+ ColorImage* color_frame_ptr = nullptr) override;
+
+ /// Interface for a function that loads the next frames in a dataset
+ ///@param[out] depth_frame_ptr The loaded depth frame.
+ ///@param[out] T_L_C_ptr Transform from Camera to the Layer frame.
+ ///@param[out] camera_ptr The intrinsic camera model.
+ ///@param[out] lidar_ptr The intrinsic oslidar model.
+ ///@param[out] height_frame_ptr The loaded z frame.
+ ///@param[out] color_frame_ptr Optional, load color frame.
+ ///@return Whether loading succeeded.
+ DataLoadResult loadNext(DepthImage* depth_frame_ptr, // NOLINT
+ Transform* T_L_C_ptr, // NOLINT
+ CameraPinhole* camera_ptr, // NOLINT
+ OSLidar* lidar_ptr, // NOLINT
+ DepthImage* height_frame_ptr = nullptr, // NOLINT
+ ColorImage* color_frame_ptr = nullptr) override;
+
+ protected:
+ std::unique_ptr> height_image_loader_;
+
+ const std::string base_path_;
+ const int seq_id_;
+
+ // The next frame to be loaded
+ int frame_number_ = 0;
+};
+
+namespace internal {
+
+bool parsePoseFromFile(const std::string& filename, Transform* transform);
+bool parseCameraFromFile(const std::string& filename,
+ Eigen::Matrix3f* intrinsics);
+bool parseLidarFromFile(const std::string& filename,
+ Eigen::Matrix* intrinsics);
+std::string getPathForCameraIntrinsics(const std::string& base_path);
+std::string getPathForLidarIntrinsics(const std::string& base_path,
+ const int seq_id, const int frame_id);
+std::string getPathForFramePose(const std::string& base_path, const int seq_id,
+ const int frame_id);
+
+std::string getPathForDepthImage(const std::string& base_path, const int seq_id,
+ const int frame_id);
+std::string getPathForHeightImage(const std::string& base_path,
+ const int seq_id, const int frame_id);
+std::string getPathForColorImage(const std::string& base_path, const int seq_id,
+ const int frame_id);
+
+std::unique_ptr> createDepthImageLoader(
+ const std::string& base_path, const int seq_id,
+ const bool multithreaded = true);
+std::unique_ptr> createHeightImageLoader(
+ const std::string& base_path, const int seq_id,
+ const bool multithreaded = true);
+std::unique_ptr> createColorImageLoader(
+ const std::string& base_path, const int seq_id,
+ const bool multithreaded = true);
+
+} // namespace internal
+} // namespace fusionportable
+} // namespace datasets
+} // namespace nvblox
diff --git a/nvblox/executables/include/nvblox/datasets/image_loader.h b/nvblox/executables/include/nvblox/datasets/image_loader.h
index d90b625d7..fde4f0aa0 100644
--- a/nvblox/executables/include/nvblox/datasets/image_loader.h
+++ b/nvblox/executables/include/nvblox/datasets/image_loader.h
@@ -29,10 +29,14 @@ namespace datasets {
// depth is expressed in mm, and we converts it to a float with meters (through
// a multiplication with 1.0/1000.0).
constexpr float kDefaultUintDepthScaleFactor = 1.0f / 1000.0f;
+constexpr float kDefaultUintDepthScaleOffset = 0.0f;
+
bool load16BitDepthImage(
const std::string& filename, DepthImage* depth_frame_ptr,
MemoryType memory_type = kDefaultImageMemoryType,
- const float scaling_factor = kDefaultUintDepthScaleFactor);
+ const float scaling_factor = kDefaultUintDepthScaleFactor,
+ const float scaling_offset = 0.0f);
+
bool load8BitColorImage(const std::string& filename,
ColorImage* color_image_ptr,
MemoryType memory_type = kDefaultImageMemoryType);
@@ -45,10 +49,12 @@ class ImageLoader {
public:
ImageLoader(IndexToFilepathFunction index_to_filepath,
MemoryType memory_type = kDefaultImageMemoryType,
- float depth_image_scaling_factor = kDefaultUintDepthScaleFactor)
+ float depth_image_scaling_factor = kDefaultUintDepthScaleFactor,
+ float depth_image_scaling_offset = kDefaultUintDepthScaleOffset)
: index_to_filepath_(index_to_filepath),
memory_type_(memory_type),
- depth_image_scaling_factor_(depth_image_scaling_factor) {}
+ depth_image_scaling_factor_(depth_image_scaling_factor),
+ depth_image_scaling_offset_(depth_image_scaling_offset) {}
virtual ~ImageLoader() {}
virtual bool getNextImage(ImageType* image_ptr);
@@ -62,6 +68,7 @@ class ImageLoader {
// Note(alexmillane): Only used for depth image loading. Ignored for color;
const float depth_image_scaling_factor_;
+ const float depth_image_scaling_offset_;
};
// Multi-threaded image loader
@@ -72,10 +79,11 @@ using ImageOptional = std::pair;
template
class MultiThreadedImageLoader : public ImageLoader {
public:
- MultiThreadedImageLoader(IndexToFilepathFunction index_to_filepath,
- int num_threads,
- MemoryType memory_type = kDefaultImageMemoryType,
- float depth_image_scaling_factor = kDefaultUintDepthScaleFactor);
+ MultiThreadedImageLoader(
+ IndexToFilepathFunction index_to_filepath, int num_threads,
+ MemoryType memory_type = kDefaultImageMemoryType,
+ float depth_image_scaling_factor = kDefaultUintDepthScaleFactor,
+ float depth_image_scaling_offset = kDefaultUintDepthScaleOffset);
~MultiThreadedImageLoader();
bool getNextImage(ImageType* image_ptr) override;
@@ -95,7 +103,8 @@ template
std::unique_ptr> createImageLoader(
IndexToFilepathFunction index_to_path_function,
const bool multithreaded = true,
- const float depth_image_scaling_factor = kDefaultUintDepthScaleFactor);
+ const float depth_image_scaling_factor = kDefaultUintDepthScaleFactor,
+ const float depth_image_scaling_offset = kDefaultUintDepthScaleOffset);
} // namespace datasets
} // namespace nvblox
diff --git a/nvblox/executables/include/nvblox/datasets/impl/image_loader_impl.h b/nvblox/executables/include/nvblox/datasets/impl/image_loader_impl.h
index d8eb427f4..ec448a4d2 100644
--- a/nvblox/executables/include/nvblox/datasets/impl/image_loader_impl.h
+++ b/nvblox/executables/include/nvblox/datasets/impl/image_loader_impl.h
@@ -28,9 +28,11 @@ bool ImageLoader::getNextImage(ImageType* image_ptr) {
template
MultiThreadedImageLoader::MultiThreadedImageLoader(
IndexToFilepathFunction index_to_filepath, int num_threads,
- MemoryType memory_type, float depth_image_scaling_factor)
+ MemoryType memory_type, float depth_image_scaling_factor,
+ float depth_image_scaling_offset)
: ImageLoader(index_to_filepath, memory_type,
- depth_image_scaling_factor),
+ depth_image_scaling_factor,
+ depth_image_scaling_offset),
num_threads_(num_threads) {
initLoadQueue();
}
@@ -89,7 +91,8 @@ MultiThreadedImageLoader::getImageAsOptional(int image_idx) {
template
std::unique_ptr> createImageLoader(
IndexToFilepathFunction index_to_path_function, const bool multithreaded,
- const float depth_image_scaling_factor) {
+ const float depth_image_scaling_factor,
+ const float depth_image_scaling_offset) {
if (multithreaded) {
// NOTE(alexmillane): On my desktop the performance of threaded image
// loading seems to saturate at around 6 threads. On machines with less
@@ -103,10 +106,11 @@ std::unique_ptr> createImageLoader(
<< " threads for loading images.";
return std::make_unique>(
index_to_path_function, kMaxLoadingThreads, MemoryType::kDevice,
- depth_image_scaling_factor);
+ depth_image_scaling_factor, depth_image_scaling_offset);
}
return std::make_unique>(
- index_to_path_function, MemoryType::kDevice, depth_image_scaling_factor);
+ index_to_path_function, MemoryType::kDevice, depth_image_scaling_factor,
+ depth_image_scaling_offset);
}
} // namespace datasets
diff --git a/nvblox/executables/include/nvblox/datasets/kitti.h b/nvblox/executables/include/nvblox/datasets/kitti.h
new file mode 100644
index 000000000..5375496e5
--- /dev/null
+++ b/nvblox/executables/include/nvblox/datasets/kitti.h
@@ -0,0 +1,107 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#pragma once
+
+#include
+#include
+
+#include "nvblox/core/types.h"
+#include "nvblox/datasets/data_loader.h"
+#include "nvblox/datasets/image_loader.h"
+#include "nvblox/executables/fuser_lidar.h"
+
+namespace nvblox {
+namespace datasets {
+namespace kitti {
+
+// NOTE(gogojjh): the default settings of preprocess height_image offset
+constexpr float kDefaultUintDepthScaleFactor = 1.0f / 1000.0f;
+constexpr float kDefaultUintDepthScaleOffset = 10.0f;
+
+// Build a FuserLidar for the FusionPortable dataset
+std::unique_ptr createFuser(const std::string base_path,
+ const int seq_id);
+
+///@brief A class for loading FusionPortable data
+class DataLoader : public RgbdDataLoaderInterface {
+ public:
+ DataLoader(const std::string& base_path, const int seq_id,
+ bool multithreaded = true);
+
+ DataLoadResult loadNext(DepthImage* depth_frame_ptr, // NOLINT
+ Transform* T_L_C_ptr, // NOLINT
+ Camera* camera_ptr, // NOLINT
+ ColorImage* color_frame_ptr = nullptr) override;
+
+ /// Interface for a function that loads the next frames in a dataset
+ ///@param[out] depth_frame_ptr The loaded depth frame.
+ ///@param[out] T_L_C_ptr Transform from Camera to the Layer frame.
+ ///@param[out] camera_ptr The intrinsic camera model.
+ ///@param[out] lidar_ptr The intrinsic oslidar model.
+ ///@param[out] height_frame_ptr The loaded z frame.
+ ///@param[out] color_frame_ptr Optional, load color frame.
+ ///@return Whether loading succeeded.
+ DataLoadResult loadNext(DepthImage* depth_frame_ptr, // NOLINT
+ Transform* T_L_C_ptr, // NOLINT
+ CameraPinhole* camera_ptr, // NOLINT
+ OSLidar* lidar_ptr, // NOLINT
+ DepthImage* height_frame_ptr = nullptr, // NOLINT
+ ColorImage* color_frame_ptr = nullptr) override;
+
+ protected:
+ std::unique_ptr> height_image_loader_;
+
+ const std::string base_path_;
+ const int seq_id_;
+
+ // The next frame to be loaded
+ int frame_number_ = 0;
+};
+
+namespace internal {
+
+bool parsePoseFromFile(const std::string& filename, Transform* transform);
+bool parseCameraFromFile(const std::string& filename,
+ Eigen::Matrix3f* intrinsics);
+bool parseLidarFromFile(const std::string& filename,
+ Eigen::Matrix* intrinsics);
+std::string getPathForCameraIntrinsics(const std::string& base_path);
+std::string getPathForLidarIntrinsics(const std::string& base_path,
+ const int seq_id, const int frame_id);
+std::string getPathForFramePose(const std::string& base_path, const int seq_id,
+ const int frame_id);
+
+std::string getPathForDepthImage(const std::string& base_path, const int seq_id,
+ const int frame_id);
+std::string getPathForHeightImage(const std::string& base_path,
+ const int seq_id, const int frame_id);
+std::string getPathForColorImage(const std::string& base_path, const int seq_id,
+ const int frame_id);
+
+std::unique_ptr> createDepthImageLoader(
+ const std::string& base_path, const int seq_id,
+ const bool multithreaded = true);
+std::unique_ptr> createHeightImageLoader(
+ const std::string& base_path, const int seq_id,
+ const bool multithreaded = true);
+std::unique_ptr> createColorImageLoader(
+ const std::string& base_path, const int seq_id,
+ const bool multithreaded = true);
+
+} // namespace internal
+} // namespace kitti
+} // namespace datasets
+} // namespace nvblox
diff --git a/nvblox/executables/include/nvblox/datasets/replica.h b/nvblox/executables/include/nvblox/datasets/replica.h
index 7cc3f4be4..3d1c1adb0 100644
--- a/nvblox/executables/include/nvblox/datasets/replica.h
+++ b/nvblox/executables/include/nvblox/datasets/replica.h
@@ -21,14 +21,14 @@ limitations under the License.
#include "nvblox/core/types.h"
#include "nvblox/datasets/data_loader.h"
#include "nvblox/datasets/image_loader.h"
-#include "nvblox/executables/fuser.h"
+#include "nvblox/executables/fuser_rgbd.h"
namespace nvblox {
namespace datasets {
namespace replica {
-// Build a Fuser for the Replica dataset
-std::unique_ptr createFuser(const std::string base_path);
+// Build a FuserRGBD for the Replica dataset
+std::unique_ptr createFuser(const std::string base_path);
///@brief A class for loading Replica data
class DataLoader : public RgbdDataLoaderInterface {
@@ -46,6 +46,13 @@ class DataLoader : public RgbdDataLoaderInterface {
Camera* camera_ptr, // NOLINT
ColorImage* color_frame_ptr = nullptr) override;
+ DataLoadResult loadNext(DepthImage* depth_frame_ptr, // NOLINT
+ Transform* T_L_C_ptr, // NOLINT
+ CameraPinhole* camera_ptr, // NOLINT
+ OSLidar* lidar_ptr, // NOLINT
+ DepthImage* height_frame_ptr = nullptr, // NOLINT
+ ColorImage* color_frame_ptr = nullptr) override;
+
protected:
const std::string base_path_;
diff --git a/nvblox/executables/include/nvblox/executables/fuser_lidar.h b/nvblox/executables/include/nvblox/executables/fuser_lidar.h
new file mode 100644
index 000000000..e8b472c5e
--- /dev/null
+++ b/nvblox/executables/include/nvblox/executables/fuser_lidar.h
@@ -0,0 +1,112 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#pragma once
+
+#include
+#include
+
+#include
+
+#include "nvblox/core/blox.h"
+#include "nvblox/core/layer.h"
+#include "nvblox/core/layer_cake.h"
+#include "nvblox/core/mapper.h"
+#include "nvblox/core/voxels.h"
+#include "nvblox/datasets/data_loader.h"
+#include "nvblox/gpu_hash/gpu_layer_view.h"
+#include "nvblox/integrators/esdf_integrator.h"
+#include "nvblox/integrators/projective_color_integrator.h"
+#include "nvblox/integrators/projective_tsdf_integrator.h"
+#include "nvblox/mesh/mesh_block.h"
+#include "nvblox/mesh/mesh_integrator.h"
+#include "nvblox/rays/sphere_tracer.h"
+
+namespace nvblox {
+
+class FuserLidar {
+ public:
+ FuserLidar() = default;
+ FuserLidar(std::unique_ptr&& data_loader);
+
+ // Loads parameters from command line flags
+ void readCommandLineFlags();
+
+ // Runs an experiment
+ int run();
+
+ // Set various settings.
+ void setVoxelSize(float voxel_size);
+ void setTsdfFrameSubsampling(int subsample);
+ void setColorFrameSubsampling(int subsample);
+ void setMeshFrameSubsampling(int subsample);
+ void setEsdfFrameSubsampling(int subsample);
+ void setEsdfMode(RgbdMapper::EsdfMode esdf_mode);
+
+ // Integrate certain layers.
+ bool integrateFrame(const int frame_number);
+ bool integrateFrames();
+ void updateEsdf();
+
+ // Output a pointcloud ESDF as PLY file.
+ bool outputPointcloudPly();
+ // Output a file with the mesh.
+ bool outputMeshPly();
+ // Output timings to a file
+ bool outputTimingsToFile();
+ // Output the serialized map to a file
+ bool outputMapToFile();
+ // Output an obstacle pointcloud based on the ESDF map as PLY file.
+ bool outputObstaclePointcloudPly();
+
+ // Get the mapper (useful for experiments where we modify mapper settings)
+ RgbdMapper& mapper();
+
+ // Dataset settings.
+ int num_frames_to_integrate_ = std::numeric_limits::max();
+ std::unique_ptr data_loader_;
+
+ // Params
+ float voxel_size_m_ = 0.05;
+ int tsdf_frame_subsampling_ = 1;
+ int color_frame_subsampling_ = 1;
+ // By default we just do the mesh and esdf once at the end (if output paths
+ // exist)
+ int mesh_frame_subsampling_ = -1;
+ int esdf_frame_subsampling_ = -1;
+
+ // ESDF slice params
+ float z_min_ = 0.5f;
+ float z_max_ = 1.0f;
+ float z_slice_ = 0.75f;
+
+ // ESDF mode
+ RgbdMapper::EsdfMode esdf_mode_ = RgbdMapper::EsdfMode::k3D;
+
+ // Mapper - Contains map layers and integrators
+ std::unique_ptr mapper_;
+
+ // Output paths
+ std::string timing_output_path_;
+ std::string esdf_output_path_;
+ std::string mesh_output_path_;
+ std::string map_output_path_;
+ std::string obs_output_path_;
+
+ // NOTE(gogojjh): setting parameters for different datasets
+ Transform T_B_C_;
+};
+
+} // namespace nvblox
diff --git a/nvblox/executables/include/nvblox/executables/fuser.h b/nvblox/executables/include/nvblox/executables/fuser_rgbd.h
similarity index 95%
rename from nvblox/executables/include/nvblox/executables/fuser.h
rename to nvblox/executables/include/nvblox/executables/fuser_rgbd.h
index 7ac2a6f2f..c5e27356b 100644
--- a/nvblox/executables/include/nvblox/executables/fuser.h
+++ b/nvblox/executables/include/nvblox/executables/fuser_rgbd.h
@@ -36,10 +36,10 @@ limitations under the License.
namespace nvblox {
-class Fuser {
+class FuserRGBD {
public:
- Fuser() = default;
- Fuser(std::unique_ptr&& data_loader);
+ FuserRGBD() = default;
+ FuserRGBD(std::unique_ptr&& data_loader);
// Loads parameters from command line flags
void readCommandLineFlags();
@@ -80,7 +80,8 @@ class Fuser {
float voxel_size_m_ = 0.05;
int tsdf_frame_subsampling_ = 1;
int color_frame_subsampling_ = 1;
- // By default we just do the mesh and esdf once at the end (if output paths exist)
+ // By default we just do the mesh and esdf once at the end (if output paths
+ // exist)
int mesh_frame_subsampling_ = -1;
int esdf_frame_subsampling_ = -1;
diff --git a/nvblox/executables/src/datasets/3dmatch.cpp b/nvblox/executables/src/datasets/3dmatch.cpp
index e48db361d..2ad098012 100644
--- a/nvblox/executables/src/datasets/3dmatch.cpp
+++ b/nvblox/executables/src/datasets/3dmatch.cpp
@@ -114,12 +114,12 @@ std::unique_ptr> createColorImageLoader(
} // namespace internal
-std::unique_ptr createFuser(const std::string base_path,
- const int seq_id) {
+std::unique_ptr createFuser(const std::string base_path,
+ const int seq_id) {
// Object to load 3DMatch data
auto data_loader = std::make_unique(base_path, seq_id);
- // Fuser
- return std::make_unique(std::move(data_loader));
+ // FuserRGBD
+ return std::make_unique(std::move(data_loader));
}
DataLoader::DataLoader(const std::string& base_path, const int seq_id,
@@ -217,6 +217,15 @@ DataLoadResult DataLoader::loadNext(DepthImage* depth_frame_ptr,
return DataLoadResult::kSuccess;
}
+DataLoadResult DataLoader::loadNext(DepthImage* depth_frame_ptr,
+ Transform* T_L_C_ptr,
+ CameraPinhole* camera_ptr,
+ OSLidar* lidar_ptr,
+ DepthImage* height_frame_ptr,
+ ColorImage* color_frame_ptr) {
+ return DataLoadResult::kNoMoreData;
+}
+
} // namespace threedmatch
} // namespace datasets
} // namespace nvblox
diff --git a/nvblox/executables/src/datasets/fusionportable.cpp b/nvblox/executables/src/datasets/fusionportable.cpp
new file mode 100644
index 000000000..d4b419512
--- /dev/null
+++ b/nvblox/executables/src/datasets/fusionportable.cpp
@@ -0,0 +1,333 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#include "nvblox/datasets/fusionportable.h"
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include "nvblox/utils/timing.h"
+
+namespace nvblox {
+namespace datasets {
+namespace fusionportable {
+namespace internal {
+
+bool parsePoseFromFile(const std::string& filename, Transform* transform) {
+ CHECK_NOTNULL(transform);
+ constexpr int kDimension = 4;
+
+ std::ifstream fin(filename);
+ if (fin.is_open()) {
+ for (int row = 0; row < kDimension; row++)
+ for (int col = 0; col < kDimension; col++) {
+ float item = 0.0;
+ fin >> item;
+ (*transform)(row, col) = item;
+ }
+ fin.close();
+ return true;
+ }
+ return false;
+}
+
+bool parseCameraFromFile(const std::string& filename,
+ Eigen::Matrix3f* intrinsics) {
+ CHECK_NOTNULL(intrinsics);
+ constexpr int kDimension = 3;
+
+ std::ifstream fin(filename);
+ if (fin.is_open()) {
+ for (int row = 0; row < kDimension; row++)
+ for (int col = 0; col < kDimension; col++) {
+ float item = 0.0;
+ fin >> item;
+ (*intrinsics)(row, col) = item;
+ }
+ fin.close();
+
+ return true;
+ }
+ return false;
+} // namespace internal
+
+bool parseLidarFromFile(const std::string& filename,
+ Eigen::Matrix* intrinsics) {
+ CHECK_NOTNULL(intrinsics);
+ const int kDimension = intrinsics->rows();
+ std::ifstream fin(filename);
+ if (fin.is_open()) {
+ for (int col = 0; col < kDimension; col++) {
+ float item = 0.0;
+ fin >> item;
+ (*intrinsics)(col) = item;
+ }
+ fin.close();
+ return true;
+ }
+ return false;
+} // namespace internal
+
+// *********************
+// *********************
+std::string getPathForCameraIntrinsics(const std::string& base_path) {
+ return base_path + "/camera-intrinsics.txt";
+}
+
+std::string getPathForLidarIntrinsics(const std::string& base_path,
+ const int seq_id, const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".lidar-intrinsics.txt";
+ return ss.str();
+}
+
+std::string getPathForFramePose(const std::string& base_path, const int seq_id,
+ const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".pose.txt";
+ return ss.str();
+}
+
+std::string getPathForDepthImage(const std::string& base_path, const int seq_id,
+ const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".depth.png";
+ return ss.str();
+}
+
+std::string getPathForColorImage(const std::string& base_path, const int seq_id,
+ const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".color.png";
+ return ss.str();
+}
+
+std::string getPathForHeightImage(const std::string& base_path,
+ const int seq_id, const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".height.png";
+ return ss.str();
+}
+
+std::unique_ptr> createDepthImageLoader(
+ const std::string& base_path, const int seq_id, const bool multithreaded) {
+ return createImageLoader(
+ std::bind(getPathForDepthImage, base_path, seq_id, std::placeholders::_1),
+ multithreaded, kDefaultUintDepthScaleFactor, 0.0f);
+}
+
+std::unique_ptr> createColorImageLoader(
+ const std::string& base_path, const int seq_id, const bool multithreaded) {
+ return createImageLoader(
+ std::bind(getPathForColorImage, base_path, seq_id, std::placeholders::_1),
+ multithreaded);
+}
+
+// TODO: we need a more proper way to set kDefaultUintDepthScaleOffset
+std::unique_ptr> createHeightImageLoader(
+ const std::string& base_path, const int seq_id, const bool multithreaded) {
+ return createImageLoader(
+ std::bind(getPathForHeightImage, base_path, seq_id,
+ std::placeholders::_1),
+ multithreaded, kDefaultUintDepthScaleFactor,
+ kDefaultUintDepthScaleOffset);
+}
+
+} // namespace internal
+
+std::unique_ptr createFuser(const std::string base_path,
+ const int seq_id) {
+ bool multithreaded = false;
+ // Object to load FusionPortable data
+ auto data_loader =
+ std::make_unique(base_path, seq_id, multithreaded);
+ // FuserLidar
+ return std::make_unique(std::move(data_loader));
+}
+
+DataLoader::DataLoader(const std::string& base_path, const int seq_id,
+ bool multithreaded)
+ : RgbdDataLoaderInterface(fusionportable::internal::createDepthImageLoader(
+ base_path, seq_id, multithreaded),
+ fusionportable::internal::createColorImageLoader(
+ base_path, seq_id, multithreaded)),
+ height_image_loader_(
+ std::move(fusionportable::internal::createHeightImageLoader(
+ base_path, seq_id, multithreaded))),
+ base_path_(base_path),
+ seq_id_(seq_id) {
+ //
+}
+
+/// Interface for a function that loads the next frames in a dataset
+///@param[out] depth_frame_ptr The loaded depth frame.
+///@param[out] T_L_C_ptr Transform from Camera to the Layer frame.
+///@param[out] camera_ptr The intrinsic camera model.
+///@param[out] height_frame_ptr The loaded z frame.
+///@param[out] color_frame_ptr Optional, load color frame.
+///@return Whether loading succeeded.
+DataLoadResult DataLoader::loadNext(DepthImage* depth_frame_ptr,
+ Transform* T_L_C_ptr,
+ CameraPinhole* camera_ptr,
+ OSLidar* lidar_ptr,
+ DepthImage* height_frame_ptr,
+ ColorImage* color_frame_ptr) {
+ CHECK_NOTNULL(depth_frame_ptr);
+ CHECK_NOTNULL(T_L_C_ptr);
+ CHECK_NOTNULL(camera_ptr);
+ CHECK_NOTNULL(lidar_ptr);
+ CHECK_NOTNULL(height_frame_ptr);
+ // CHECK_NOTNULL(color_frame_ptr); // can be null
+
+ // Because we might fail along the way, increment the frame number before we
+ // start.
+ const int frame_number = frame_number_;
+ ++frame_number_;
+
+ // *********************************************
+ // *********************************************
+ // *********************************************
+ // Load the image into a Depth Frame.
+ CHECK(depth_image_loader_);
+ timing::Timer timer_file_depth("file_loading/depth_image");
+ if (!depth_image_loader_->getNextImage(depth_frame_ptr)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ timer_file_depth.Stop();
+
+ // Load the image into a Height Frame.
+ CHECK(height_image_loader_);
+ timing::Timer timer_file_coord("file_loading/height_image");
+ if (!height_image_loader_->getNextImage(height_frame_ptr)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ timer_file_coord.Stop();
+
+ // NOTE(gogojjh): Load lidar intrinsics:
+ // num_azimuth_divisions
+ // num_elevation_divisions
+ // horizontal_fov_rad
+ // vertical_fov_rad
+ // start_azimuth_angle_rad
+ // end_azimuth_angle_rad
+ // start_elevation_angle_rad
+ // end_elevation_angle_rad
+ timing::Timer timer_file_camera("file_loading/lidar");
+ Eigen::Matrix lidar_intrinsics;
+ if (!fusionportable::internal::parseLidarFromFile(
+ fusionportable::internal::getPathForLidarIntrinsics(
+ base_path_, seq_id_, frame_number),
+ &lidar_intrinsics)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ *lidar_ptr =
+ OSLidar(lidar_intrinsics(0), lidar_intrinsics(1), lidar_intrinsics(2),
+ lidar_intrinsics(3), lidar_intrinsics(4), lidar_intrinsics(5),
+ lidar_intrinsics(6), lidar_intrinsics(7));
+ CHECK(depth_frame_ptr->rows() == lidar_ptr->num_elevation_divisions());
+ CHECK(depth_frame_ptr->cols() == lidar_ptr->num_azimuth_divisions());
+ CHECK(height_frame_ptr->rows() == lidar_ptr->num_elevation_divisions());
+ CHECK(height_frame_ptr->cols() == lidar_ptr->num_azimuth_divisions());
+
+ // *********************************************
+ // *********************************************
+ // *********************************************
+ // Load the color image into a ColorImage
+ if (color_frame_ptr) {
+ CHECK(color_image_loader_);
+ timing::Timer timer_file_color("file_loading/color_image");
+ if (!color_image_loader_->getNextImage(color_frame_ptr)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ timer_file_color.Stop();
+ }
+
+ // Get the camera for this frame.
+ if (color_frame_ptr) {
+ timing::Timer timer_file_camera("file_loading/camera");
+ Eigen::Matrix3f K;
+ if (!fusionportable::internal::parseCameraFromFile(
+ fusionportable::internal::getPathForCameraIntrinsics(base_path_),
+ &K)) {
+ return DataLoadResult::kNoMoreData;
+ }
+
+ // Create a camera object.
+ const int image_width = color_frame_ptr->cols();
+ const int image_height = color_frame_ptr->rows();
+ *camera_ptr =
+ CameraPinhole::fromIntrinsicsMatrix(K, image_width, image_height);
+ timer_file_camera.Stop();
+
+ if (!K.allFinite()) {
+ LOG(WARNING) << "Bad CSV data.";
+ return DataLoadResult::kBadFrame; // Bad data, but keep going.
+ }
+ }
+
+ // *********************************************
+ // *********************************************
+ // *********************************************
+ // Get the transform.
+ timing::Timer timer_file_pose("file_loading/pose");
+ Transform T_O_C;
+ if (!fusionportable::internal::parsePoseFromFile(
+ fusionportable::internal::getPathForFramePose(base_path_, seq_id_,
+ frame_number),
+ &T_O_C)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ *T_L_C_ptr = T_O_C;
+
+ // Check that the loaded data doesn't contain NaNs or a faulty rotation
+ // matrix. This does occur. If we find one, skip that frame and move to the
+ // next.
+ constexpr float kRotationMatrixDetEpsilon = 1e-4;
+ if (!T_L_C_ptr->matrix().allFinite() ||
+ std::abs(T_L_C_ptr->matrix().block<3, 3>(0, 0).determinant() - 1.0f) >
+ kRotationMatrixDetEpsilon) {
+ LOG(WARNING) << "Bad CSV data.";
+ return DataLoadResult::kBadFrame; // Bad data, but keep going.
+ }
+ timer_file_pose.Stop();
+
+ return DataLoadResult::kSuccess;
+}
+
+// NOTE(gogojjh): need to define the virutal function (not used) here
+/// Interface for a function that loads the next frames in a dataset
+///@param[out] depth_frame_ptr The loaded depth frame.
+///@param[out] T_L_C_ptr Transform from Camera to the Layer frame.
+///@param[out] camera_ptr The intrinsic camera model.
+///@param[out] color_frame_ptr Optional, load color frame.
+///@return Whether loading succeeded.
+DataLoadResult DataLoader::loadNext(DepthImage* depth_frame_ptr,
+ Transform* T_L_C_ptr, Camera* camera_ptr,
+ ColorImage* color_frame_ptr) {
+ return DataLoadResult::kNoMoreData;
+}
+
+} // namespace fusionportable
+} // namespace datasets
+} // namespace nvblox
diff --git a/nvblox/executables/src/datasets/image_loader.cpp b/nvblox/executables/src/datasets/image_loader.cpp
index b77e5559c..94a530de5 100644
--- a/nvblox/executables/src/datasets/image_loader.cpp
+++ b/nvblox/executables/src/datasets/image_loader.cpp
@@ -27,9 +27,16 @@ namespace datasets {
bool load16BitDepthImage(const std::string& filename,
DepthImage* depth_frame_ptr, MemoryType memory_type,
- const float scale_factor) {
+ const float scale_factor, const float scale_offset) {
CHECK_NOTNULL(depth_frame_ptr);
- timing::Timer stbi_timer("file_loading/depth_image/stbi");
+ std::string timer_name;
+ if (scale_offset == 0.0f) {
+ timer_name = "file_loading/depth_image/stbi";
+ } else {
+ timer_name = "file_loading/height_image/stbi";
+ }
+ timing::Timer stbi_timer(timer_name);
+
int width, height, num_channels;
uint16_t* image_data =
stbi_load_16(filename.c_str(), &width, &height, &num_channels, 0);
@@ -47,8 +54,18 @@ bool load16BitDepthImage(const std::string& filename,
// ~1ms. So only do this when 1ms is relevant.
std::vector float_image_data(height * width);
for (int lin_idx = 0; lin_idx < float_image_data.size(); lin_idx++) {
- float_image_data[lin_idx] =
- static_cast(image_data[lin_idx]) * scale_factor;
+ if (scale_offset == 0.0f) {
+ float_image_data[lin_idx] =
+ static_cast(image_data[lin_idx]) * scale_factor;
+ } else {
+ if (image_data[lin_idx] == 0) {
+ float_image_data[lin_idx] = static_cast(image_data[lin_idx]);
+ } else {
+ float_image_data[lin_idx] =
+ static_cast(image_data[lin_idx]) * scale_factor -
+ scale_offset;
+ }
+ }
}
*depth_frame_ptr = DepthImage::fromBuffer(
@@ -86,7 +103,8 @@ template <>
bool ImageLoader::getImage(int image_idx, DepthImage* image_ptr) {
CHECK_NOTNULL(image_ptr);
bool res = load16BitDepthImage(index_to_filepath_(image_idx), image_ptr,
- memory_type_, depth_image_scaling_factor_);
+ memory_type_, depth_image_scaling_factor_,
+ depth_image_scaling_offset_);
return res;
}
diff --git a/nvblox/executables/src/datasets/kitti.cpp b/nvblox/executables/src/datasets/kitti.cpp
new file mode 100644
index 000000000..46e666b5a
--- /dev/null
+++ b/nvblox/executables/src/datasets/kitti.cpp
@@ -0,0 +1,352 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#include "nvblox/datasets/kitti.h"
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include "nvblox/utils/timing.h"
+
+namespace nvblox {
+namespace datasets {
+namespace kitti {
+namespace internal {
+
+bool parsePoseFromFile(const std::string& filename, Transform* transform) {
+ CHECK_NOTNULL(transform);
+ constexpr int kDimension = 4;
+
+ std::ifstream fin(filename);
+ if (fin.is_open()) {
+ for (int row = 0; row < kDimension; row++)
+ for (int col = 0; col < kDimension; col++) {
+ float item = 0.0;
+ fin >> item;
+ (*transform)(row, col) = item;
+ }
+ fin.close();
+ return true;
+ }
+ return false;
+}
+
+bool parseCameraFromFile(const std::string& filename, Matrix3x4f* P_rect,
+ Matrix3f* R_rect, int* height, int* width) {
+ CHECK_NOTNULL(P_rect);
+ CHECK_NOTNULL(R_rect);
+ CHECK_NOTNULL(height);
+ CHECK_NOTNULL(width);
+
+ std::ifstream fin(filename);
+ if (fin.is_open()) {
+ fin >> *height >> *width;
+
+ for (int row = 0; row < 3; row++) {
+ for (int col = 0; col < 4; col++) {
+ float item = 0.0;
+ fin >> item;
+ (*P_rect)(row, col) = item;
+ }
+ }
+
+ for (int row = 0; row < 3; row++) {
+ for (int col = 0; col < 3; col++) {
+ float item = 0.0;
+ fin >> item;
+ (*R_rect)(row, col) = item;
+ }
+ }
+
+ fin.close();
+ return true;
+ }
+ return false;
+} // namespace internal
+
+bool parseLidarFromFile(const std::string& filename,
+ Eigen::Matrix* intrinsics) {
+ CHECK_NOTNULL(intrinsics);
+ const int kDimension = intrinsics->rows();
+ std::ifstream fin(filename);
+ if (fin.is_open()) {
+ for (int col = 0; col < kDimension; col++) {
+ float item = 0.0;
+ fin >> item;
+ (*intrinsics)(col) = item;
+ }
+ fin.close();
+ return true;
+ }
+ return false;
+} // namespace internal
+
+// *********************
+// *********************
+std::string getPathForCameraIntrinsics(const std::string& base_path) {
+ return base_path + "/camera-intrinsics.txt";
+}
+
+std::string getPathForLidarIntrinsics(const std::string& base_path,
+ const int seq_id, const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".lidar-intrinsics.txt";
+ return ss.str();
+}
+
+std::string getPathForFramePose(const std::string& base_path, const int seq_id,
+ const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".pose.txt";
+ return ss.str();
+}
+
+std::string getPathForDepthImage(const std::string& base_path, const int seq_id,
+ const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".depth.png";
+ return ss.str();
+}
+
+std::string getPathForColorImage(const std::string& base_path, const int seq_id,
+ const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".color.png";
+ return ss.str();
+}
+
+std::string getPathForHeightImage(const std::string& base_path,
+ const int seq_id, const int frame_id) {
+ std::stringstream ss;
+ ss << base_path << "/seq-" << std::setfill('0') << std::setw(2) << seq_id
+ << "/frame-" << std::setw(6) << frame_id << ".height.png";
+ return ss.str();
+}
+
+std::unique_ptr> createDepthImageLoader(
+ const std::string& base_path, const int seq_id, const bool multithreaded) {
+ return createImageLoader(
+ std::bind(getPathForDepthImage, base_path, seq_id, std::placeholders::_1),
+ multithreaded, kDefaultUintDepthScaleFactor, 0.0f);
+}
+
+std::unique_ptr> createColorImageLoader(
+ const std::string& base_path, const int seq_id, const bool multithreaded) {
+ return createImageLoader(
+ std::bind(getPathForColorImage, base_path, seq_id, std::placeholders::_1),
+ multithreaded);
+}
+
+std::unique_ptr> createHeightImageLoader(
+ const std::string& base_path, const int seq_id, const bool multithreaded) {
+ return createImageLoader(
+ std::bind(getPathForHeightImage, base_path, seq_id,
+ std::placeholders::_1),
+ multithreaded, kDefaultUintDepthScaleFactor,
+ kDefaultUintDepthScaleOffset);
+}
+
+} // namespace internal
+
+std::unique_ptr createFuser(const std::string base_path,
+ const int seq_id) {
+ bool multithreaded = false;
+ // Object to load FusionPortable data
+ auto data_loader =
+ std::make_unique(base_path, seq_id, multithreaded);
+ // FuserLidar
+ return std::make_unique(std::move(data_loader));
+}
+
+DataLoader::DataLoader(const std::string& base_path, const int seq_id,
+ bool multithreaded)
+ : RgbdDataLoaderInterface(kitti::internal::createDepthImageLoader(
+ base_path, seq_id, multithreaded),
+ kitti::internal::createColorImageLoader(
+ base_path, seq_id, multithreaded)),
+ height_image_loader_(std::move(kitti::internal::createHeightImageLoader(
+ base_path, seq_id, multithreaded))),
+ base_path_(base_path),
+ seq_id_(seq_id) {
+ //
+}
+
+/// Interface for a function that loads the next frames in a dataset
+///@param[out] depth_frame_ptr The loaded depth frame.
+///@param[out] T_L_C_ptr Transform from the Base camera to the Layer frame.
+///@param[out] camera_ptr The intrinsic camera model.
+///@param[out] height_frame_ptr The loaded z frame.
+///@param[out] color_frame_ptr Optional, load color frame.
+///@return Whether loading succeeded.
+DataLoadResult DataLoader::loadNext(DepthImage* depth_frame_ptr,
+ Transform* T_L_C_ptr,
+ CameraPinhole* camera_ptr,
+ OSLidar* lidar_ptr,
+ DepthImage* height_frame_ptr,
+ ColorImage* color_frame_ptr) {
+ CHECK_NOTNULL(depth_frame_ptr);
+ CHECK_NOTNULL(T_L_C_ptr);
+ CHECK_NOTNULL(camera_ptr);
+ CHECK_NOTNULL(lidar_ptr);
+ CHECK_NOTNULL(height_frame_ptr);
+ // CHECK_NOTNULL(color_frame_ptr); // can be null
+
+ // Because we might fail along the way, increment the frame number before we
+ // start.
+ const int frame_number = frame_number_;
+ ++frame_number_;
+
+ // *********************************************
+ // *********************************************
+ // *********************************************
+ // Load the image into a Depth Frame.
+ CHECK(depth_image_loader_);
+ timing::Timer timer_file_depth("file_loading/depth_image");
+ if (!depth_image_loader_->getNextImage(depth_frame_ptr)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ timer_file_depth.Stop();
+
+ // Load the image into a Height Frame.
+ CHECK(height_image_loader_);
+ timing::Timer timer_file_coord("file_loading/height_image");
+ if (!height_image_loader_->getNextImage(height_frame_ptr)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ timer_file_coord.Stop();
+
+ // NOTE(gogojjh): Load lidar intrinsics:
+ // num_azimuth_divisions
+ // num_elevation_divisions
+ // horizontal_fov_rad
+ // vertical_fov_rad
+ // start_azimuth_angle_rad
+ // end_azimuth_angle_rad
+ // start_elevation_angle_rad
+ // end_elevation_angle_rad
+ timing::Timer timer_file_camera("file_loading/lidar");
+ Eigen::Matrix lidar_intrinsics;
+ if (!kitti::internal::parseLidarFromFile(
+ kitti::internal::getPathForLidarIntrinsics(base_path_, seq_id_,
+ frame_number),
+ &lidar_intrinsics)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ *lidar_ptr =
+ OSLidar(lidar_intrinsics(0), lidar_intrinsics(1), lidar_intrinsics(2),
+ lidar_intrinsics(3), lidar_intrinsics(4), lidar_intrinsics(5),
+ lidar_intrinsics(6), lidar_intrinsics(7));
+ CHECK(depth_frame_ptr->rows() == lidar_ptr->num_elevation_divisions());
+ CHECK(depth_frame_ptr->cols() == lidar_ptr->num_azimuth_divisions());
+ CHECK(height_frame_ptr->rows() == lidar_ptr->num_elevation_divisions());
+ CHECK(height_frame_ptr->cols() == lidar_ptr->num_azimuth_divisions());
+
+ // *********************************************
+ // *********************************************
+ // *********************************************
+ // Load the color image into a ColorImage
+ if (color_frame_ptr) {
+ CHECK(color_image_loader_);
+ timing::Timer timer_file_color("file_loading/color_image");
+ if (!color_image_loader_->getNextImage(color_frame_ptr)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ timer_file_color.Stop();
+ }
+
+ // Get the camera for this frame.
+ if (color_frame_ptr) {
+ timing::Timer timer_file_camera("file_loading/camera");
+ Matrix3f R_rect;
+ Matrix3x4f P_rect;
+ int image_width;
+ int image_height;
+ if (!kitti::internal::parseCameraFromFile(
+ kitti::internal::getPathForCameraIntrinsics(base_path_), &P_rect,
+ &R_rect, &image_height, &image_width)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ // std::cout << image_height << " " << image_width << std::endl
+ // << P_rect << std::endl
+ // << R_rect << std::endl;
+
+ // Create a camera object.
+ *camera_ptr = CameraPinhole::fromIntrinsicsMatrix(
+ P_rect, R_rect, image_width, image_height);
+ timer_file_camera.Stop();
+
+ if (!P_rect.allFinite()) {
+ LOG(WARNING) << "[P_rect] Bad CSV data.";
+ return DataLoadResult::kBadFrame; // Bad data, but keep going.
+ }
+ if (!R_rect.allFinite()) {
+ LOG(WARNING) << "[R_rect] Bad CSV data.";
+ return DataLoadResult::kBadFrame; // Bad data, but keep going.
+ }
+ }
+
+ // *********************************************
+ // *********************************************
+ // *********************************************
+ // Get the transform.
+ timing::Timer timer_file_pose("file_loading/pose");
+ Transform T_O_C;
+ if (!kitti::internal::parsePoseFromFile(
+ kitti::internal::getPathForFramePose(base_path_, seq_id_,
+ frame_number),
+ &T_O_C)) {
+ return DataLoadResult::kNoMoreData;
+ }
+ *T_L_C_ptr = T_O_C;
+
+ // Check that the loaded data doesn't contain NaNs or a faulty rotation
+ // matrix. This does occur. If we find one, skip that frame and move to the
+ // next.
+ constexpr float kRotationMatrixDetEpsilon = 1e-4;
+ if (!T_L_C_ptr->matrix().allFinite() ||
+ std::abs(T_L_C_ptr->matrix().block<3, 3>(0, 0).determinant() - 1.0f) >
+ kRotationMatrixDetEpsilon) {
+ LOG(WARNING) << "Bad CSV data.";
+ return DataLoadResult::kBadFrame; // Bad data, but keep going.
+ }
+ timer_file_pose.Stop();
+
+ return DataLoadResult::kSuccess;
+}
+
+// NOTE(gogojjh): need to define the virutal function (not used) here
+/// Interface for a function that loads the next frames in a dataset
+///@param[out] depth_frame_ptr The loaded depth frame.
+///@param[out] T_L_C_ptr Transform from Camera to the Layer frame.
+///@param[out] camera_ptr The intrinsic camera model.
+///@param[out] color_frame_ptr Optional, load color frame.
+///@return Whether loading succeeded.
+DataLoadResult DataLoader::loadNext(DepthImage* depth_frame_ptr,
+ Transform* T_L_C_ptr, Camera* camera_ptr,
+ ColorImage* color_frame_ptr) {
+ return DataLoadResult::kNoMoreData;
+}
+
+} // namespace kitti
+} // namespace datasets
+} // namespace nvblox
diff --git a/nvblox/executables/src/datasets/replica.cpp b/nvblox/executables/src/datasets/replica.cpp
index 07f2c3e09..4f7a9109b 100644
--- a/nvblox/executables/src/datasets/replica.cpp
+++ b/nvblox/executables/src/datasets/replica.cpp
@@ -141,11 +141,11 @@ std::unique_ptr> createColorImageLoader(
} // namespace internal
-std::unique_ptr createFuser(const std::string base_path) {
+std::unique_ptr createFuser(const std::string base_path) {
// Object to load 3DMatch data
auto data_loader = std::make_unique(base_path);
- // Fuser
- return std::make_unique(std::move(data_loader));
+ // FuserRGBD
+ return std::make_unique(std::move(data_loader));
}
DataLoader::DataLoader(const std::string& base_path, bool multithreaded)
@@ -214,7 +214,7 @@ DataLoadResult DataLoader::loadNext(DepthImage* depth_frame_ptr,
} else {
if (!replica::internal::parseCameraFromFile(
replica::internal::getPathForCameraIntrinsics(base_path_), &camera_,
- &scale)) {
+ &scale)) {
LOG(INFO) << "Couldn't find camera params file";
return DataLoadResult::kNoMoreData;
}
@@ -251,6 +251,15 @@ DataLoadResult DataLoader::loadNext(DepthImage* depth_frame_ptr,
return DataLoadResult::kSuccess;
}
+DataLoadResult DataLoader::loadNext(DepthImage* depth_frame_ptr,
+ Transform* T_L_C_ptr,
+ CameraPinhole* camera_ptr,
+ OSLidar* lidar_ptr,
+ DepthImage* height_frame_ptr,
+ ColorImage* color_frame_ptr) {
+ return DataLoadResult::kNoMoreData;
+}
+
} // namespace replica
} // namespace datasets
} // namespace nvblox
diff --git a/nvblox/executables/src/fuse_3dmatch.cpp b/nvblox/executables/src/fuse_3dmatch.cpp
index b5edc03af..83a3f770e 100644
--- a/nvblox/executables/src/fuse_3dmatch.cpp
+++ b/nvblox/executables/src/fuse_3dmatch.cpp
@@ -17,7 +17,7 @@ limitations under the License.
#include
#include "nvblox/datasets/3dmatch.h"
-#include "nvblox/executables/fuser.h"
+#include "nvblox/executables/fuser_rgbd.h"
DECLARE_bool(alsologtostderr);
@@ -44,7 +44,7 @@ int main(int argc, char* argv[]) {
// Fuser
// NOTE(alexmillane): Hardcode the sequence ID.
constexpr int seq_id = 1;
- std::unique_ptr fuser =
+ std::unique_ptr fuser =
datasets::threedmatch::createFuser(base_path, seq_id);
// Mesh location (optional)
diff --git a/nvblox/executables/src/fuse_fusionportable.cpp b/nvblox/executables/src/fuse_fusionportable.cpp
new file mode 100644
index 000000000..70d0bc438
--- /dev/null
+++ b/nvblox/executables/src/fuse_fusionportable.cpp
@@ -0,0 +1,74 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#include
+#include
+
+#include "nvblox/datasets/fusionportable.h"
+#include "nvblox/executables/fuser_lidar.h"
+
+DECLARE_bool(alsologtostderr);
+
+using namespace nvblox;
+
+int main(int argc, char* argv[]) {
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+ google::InitGoogleLogging(argv[0]);
+ FLAGS_alsologtostderr = true;
+ google::InstallFailureSignalHandler();
+
+ // Get the dataset
+ std::string base_path;
+ if (argc < 2) {
+ // Try out running on the test datasets.
+ base_path = "../tests/data/fusionportable";
+ LOG(WARNING)
+ << "No FusionProtable file path specified; defaulting to the test "
+ "directory.";
+ } else {
+ base_path = argv[1];
+ LOG(INFO) << "Loading FusionPortable files from " << base_path;
+ }
+
+ // Fuser
+ // NOTE(alexmillane): Hardcode the sequence ID.
+ constexpr int seq_id = 1;
+ std::unique_ptr fuser_lidar =
+ datasets::fusionportable::createFuser(base_path, seq_id);
+
+ // Mesh location (optional)
+ if (argc >= 3) {
+ fuser_lidar->mesh_output_path_ = argv[2];
+ LOG(INFO) << "Mesh location:" << fuser_lidar->mesh_output_path_;
+ }
+
+ // NOTE(gogojjh): set extrinsics from the base_link to the camera
+ Eigen::Quaternionf Qcb(0.500292, 0.490181, -0.508467, 0.500889);
+ Eigen::Vector3f tcb(0.067436, -0.022029, -0.078333);
+ Eigen::Quaternionf Qbc = Qcb.inverse();
+ Eigen::Vector3f tbc = -(Qbc * tcb);
+
+ Eigen::Matrix4f Tbc = Eigen::Matrix4f::Identity();
+ Tbc.block<3, 3>(0, 0) = Qbc.toRotationMatrix();
+ Tbc.block<3, 1>(0, 3) = tbc;
+ fuser_lidar->T_B_C_ = Transform(Tbc);
+
+ // std::cout << fuser_lidar->T_B_C_.matrix() << std::endl;
+ // std::cout << std::endl;
+ // std::cout << Tbc << std::endl;
+
+ // Make sure the layers are the correct resolution.
+ return fuser_lidar->run();
+}
diff --git a/nvblox/executables/src/fuse_kitti.cpp b/nvblox/executables/src/fuse_kitti.cpp
new file mode 100644
index 000000000..d5efdd7e9
--- /dev/null
+++ b/nvblox/executables/src/fuse_kitti.cpp
@@ -0,0 +1,74 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#include
+#include
+
+#include "nvblox/datasets/kitti.h"
+#include "nvblox/executables/fuser_lidar.h"
+
+DECLARE_bool(alsologtostderr);
+
+using namespace nvblox;
+
+int main(int argc, char* argv[]) {
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+ google::InitGoogleLogging(argv[0]);
+ FLAGS_alsologtostderr = true;
+ google::InstallFailureSignalHandler();
+
+ // Get the dataset
+ std::string base_path;
+ if (argc < 2) {
+ // Try out running on the test datasets.
+ base_path = "../tests/data/kitti";
+ LOG(WARNING)
+ << "No FusionProtable file path specified; defaulting to the test "
+ "directory.";
+ } else {
+ base_path = argv[1];
+ LOG(INFO) << "Loading FusionPortable files from " << base_path;
+ }
+
+ // Fuser
+ // NOTE(alexmillane): Hardcode the sequence ID.
+ constexpr int seq_id = 1;
+ std::unique_ptr fuser_lidar =
+ datasets::kitti::createFuser(base_path, seq_id);
+
+ // Mesh location (optional)
+ if (argc >= 3) {
+ fuser_lidar->mesh_output_path_ = argv[2];
+ LOG(INFO) << "Mesh location:" << fuser_lidar->mesh_output_path_;
+ }
+
+ // NOTE(gogojjh): set extrinsics from the base_link to the camera
+ Eigen::Quaternionf Qcb(0.500292, 0.490181, -0.508467, 0.500889);
+ Eigen::Vector3f tcb(0.067436, -0.022029, -0.078333);
+ Eigen::Quaternionf Qbc = Qcb.inverse();
+ Eigen::Vector3f tbc = -(Qbc * tcb);
+
+ Eigen::Matrix4f Tbc = Eigen::Matrix4f::Identity();
+ Tbc.block<3, 3>(0, 0) = Qbc.toRotationMatrix();
+ Tbc.block<3, 1>(0, 3) = tbc;
+ fuser_lidar->T_B_C_ = Transform(Tbc);
+
+ // std::cout << fuser_lidar->T_B_C_.matrix() << std::endl;
+ // std::cout << std::endl;
+ // std::cout << Tbc << std::endl;
+
+ // Make sure the layers are the correct resolution.
+ return fuser_lidar->run();
+}
diff --git a/nvblox/executables/src/fuse_replica.cpp b/nvblox/executables/src/fuse_replica.cpp
index 2a9029aea..90491b89c 100644
--- a/nvblox/executables/src/fuse_replica.cpp
+++ b/nvblox/executables/src/fuse_replica.cpp
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-
#include
#include
#include
@@ -29,7 +28,7 @@ limitations under the License.
#include "nvblox/core/types.h"
#include "nvblox/datasets/image_loader.h"
#include "nvblox/datasets/replica.h"
-#include "nvblox/executables/fuser.h"
+#include "nvblox/executables/fuser_rgbd.h"
using namespace nvblox;
@@ -48,7 +47,7 @@ int main(int argc, char* argv[]) {
LOG(INFO) << "Loading Replica files from " << base_path;
// Fuser
- std::unique_ptr fuser = datasets::replica::createFuser(base_path);
+ std::unique_ptr fuser = datasets::replica::createFuser(base_path);
// Mesh location (optional)
if (argc >= 3) {
diff --git a/nvblox/executables/src/fuser_lidar.cpp b/nvblox/executables/src/fuser_lidar.cpp
new file mode 100644
index 000000000..fbe96e350
--- /dev/null
+++ b/nvblox/executables/src/fuser_lidar.cpp
@@ -0,0 +1,471 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+#include
+#include
+
+#include "nvblox/executables/fuser_lidar.h"
+#include "nvblox/io/mesh_io.h"
+#include "nvblox/io/ply_writer.h"
+#include "nvblox/io/pointcloud_io.h"
+#include "nvblox/utils/timing.h"
+
+#include "nvblox/core/cuda/image_operation.h"
+
+// Layer params
+DEFINE_double(voxel_size, 0.0f, "Voxel resolution in meters.");
+
+// Dataset flags
+DEFINE_int32(num_frames, -1,
+ "Number of frames to process. Empty means process all.");
+
+// The output paths
+DEFINE_string(timing_output_path, "",
+ "File in which to save the timing results.");
+DEFINE_string(esdf_output_path, "",
+ "File in which to save the ESDF pointcloud.");
+DEFINE_string(mesh_output_path, "", "File in which to save the surface mesh.");
+DEFINE_string(map_output_path, "", "File in which to save the serialize map.");
+DEFINE_string(obstacle_output_path, "",
+ "File in which to save the obstacle pointcloud map.");
+
+// Subsampling
+DEFINE_int32(tsdf_frame_subsampling, 0,
+ "By what amount to subsample the TSDF frames. A subsample of 3 "
+ "means only every 3rd frame is taken.");
+DEFINE_int32(color_frame_subsampling, 0,
+ "How much to subsample the color integration by.");
+DEFINE_int32(mesh_frame_subsampling, 0,
+ "How much to subsample the meshing by.");
+DEFINE_int32(esdf_frame_subsampling, 0,
+ "How much to subsample the ESDF integration by.");
+
+// TSDF Integrator settings
+DEFINE_double(tsdf_integrator_max_integration_distance_m, -1.0,
+ "Maximum distance (in meters) from the camera at which to "
+ "integrate data into the TSDF.");
+DEFINE_double(tsdf_integrator_truncation_distance_vox, -1.0,
+ "Truncation band (in voxels).");
+DEFINE_double(
+ tsdf_integrator_max_weight, -1.0,
+ "The maximum weight that a tsdf voxel can accumulate through integration.");
+
+// Mesh integrator settings
+DEFINE_double(mesh_integrator_min_weight, -1.0,
+ "The minimum weight a tsdf voxel must have before it is meshed.");
+DEFINE_bool(mesh_integrator_weld_vertices, true,
+ "Whether or not to weld duplicate vertices in the mesh.");
+
+// Color integrator settings
+DEFINE_double(color_integrator_max_integration_distance_m, -1.0,
+ "Maximum distance (in meters) from the camera at which to "
+ "integrate color into the voxel grid.");
+
+// ESDF Integrator settings
+DEFINE_double(esdf_integrator_min_weight, -1.0,
+ "The minimum weight at which to consider a voxel a site.");
+DEFINE_double(esdf_integrator_max_site_distance_vox, -1.0,
+ "The maximum distance at which we consider a TSDF voxel a site.");
+DEFINE_double(esdf_integrator_max_distance_m, -1.0,
+ "The maximum distance which we integrate ESDF distances out to.");
+DEFINE_int32(esdf_mode, 0, "The ESDF mode. 0: k3D, 1: k2D, 2: kUnset");
+DEFINE_double(esdf_zmin, 0.5, "zmin of the 2D ESDF map");
+DEFINE_double(esdf_zmax, 1.0, "zmax of the 2D ESDF map");
+DEFINE_double(esdf_z_slice, 0.75, "z_slice of the 2D ESDF map");
+
+namespace nvblox {
+FuserLidar::FuserLidar(
+ std::unique_ptr&& data_loader)
+ : data_loader_(std::move(data_loader)) {
+ // NOTE(alexmillane): We require the voxel size before we construct the
+ // mapper, so we grab this parameter first and separately.
+ if (!gflags::GetCommandLineFlagInfoOrDie("voxel_size").is_default) {
+ LOG(INFO) << "Command line parameter found: voxel_size = "
+ << FLAGS_voxel_size;
+ setVoxelSize(static_cast(FLAGS_voxel_size));
+ }
+
+ // Initialize the mapper
+ mapper_ = std::make_unique(voxel_size_m_);
+
+ // Default parameters
+ mapper_->mesh_integrator().min_weight(2.0f);
+ mapper_->color_integrator().max_integration_distance_m(5.0f);
+ mapper_->lidar_tsdf_integrator().max_integration_distance_m(1.0f);
+ mapper_->lidar_tsdf_integrator().view_calculator().raycast_subsampling_factor(
+ 4);
+ mapper_->esdf_integrator().max_distance_m(4.0f);
+ mapper_->esdf_integrator().min_weight(2.0f);
+
+ // Pick commands off the command line
+ readCommandLineFlags();
+};
+
+void FuserLidar::readCommandLineFlags() {
+ // Dataset flags
+ if (!gflags::GetCommandLineFlagInfoOrDie("num_frames").is_default) {
+ LOG(INFO) << "Command line parameter found: num_frames = "
+ << FLAGS_num_frames;
+ num_frames_to_integrate_ = FLAGS_num_frames;
+ }
+ // Output paths
+ if (!gflags::GetCommandLineFlagInfoOrDie("timing_output_path").is_default) {
+ LOG(INFO) << "Command line parameter found: timing_output_path = "
+ << FLAGS_timing_output_path;
+ timing_output_path_ = FLAGS_timing_output_path;
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie("esdf_output_path").is_default) {
+ LOG(INFO) << "Command line parameter found: esdf_output_path = "
+ << FLAGS_esdf_output_path;
+ esdf_output_path_ = FLAGS_esdf_output_path;
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie("mesh_output_path").is_default) {
+ LOG(INFO) << "Command line parameter found: mesh_output_path = "
+ << FLAGS_mesh_output_path;
+ mesh_output_path_ = FLAGS_mesh_output_path;
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie("map_output_path").is_default) {
+ LOG(INFO) << "Command line parameter found: map_output_path = "
+ << FLAGS_map_output_path;
+ map_output_path_ = FLAGS_map_output_path;
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie("obstacle_output_path").is_default) {
+ LOG(INFO) << "Command line parameter found: obstacle_output_path = "
+ << FLAGS_obstacle_output_path;
+ obs_output_path_ = FLAGS_obstacle_output_path;
+ }
+
+ // Subsampling flags
+ if (!gflags::GetCommandLineFlagInfoOrDie("tsdf_frame_subsampling")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: tsdf_frame_subsampling = "
+ << FLAGS_tsdf_frame_subsampling;
+ setTsdfFrameSubsampling(FLAGS_tsdf_frame_subsampling);
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie("color_frame_subsampling")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: color_frame_subsampling = "
+ << FLAGS_color_frame_subsampling;
+ setColorFrameSubsampling(FLAGS_color_frame_subsampling);
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie("mesh_frame_subsampling")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: mesh_frame_subsampling = "
+ << FLAGS_mesh_frame_subsampling;
+ setMeshFrameSubsampling(FLAGS_mesh_frame_subsampling);
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie("esdf_frame_subsampling")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: esdf_frame_subsampling = "
+ << FLAGS_esdf_frame_subsampling;
+ setEsdfFrameSubsampling(FLAGS_esdf_frame_subsampling);
+ }
+
+ // TSDF integrator
+ if (!gflags::GetCommandLineFlagInfoOrDie(
+ "tsdf_integrator_max_integration_distance_m")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: "
+ "tsdf_integrator_max_integration_distance_m= "
+ << FLAGS_tsdf_integrator_max_integration_distance_m;
+ mapper_->lidar_tsdf_integrator().max_integration_distance_m(
+ FLAGS_tsdf_integrator_max_integration_distance_m);
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie(
+ "tsdf_integrator_truncation_distance_vox")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: "
+ "tsdf_integrator_truncation_distance_vox = "
+ << FLAGS_tsdf_integrator_truncation_distance_vox;
+ mapper_->lidar_tsdf_integrator().truncation_distance_vox(
+ FLAGS_tsdf_integrator_truncation_distance_vox);
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie("tsdf_integrator_max_weight")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: tsdf_integrator_max_weight = "
+ << FLAGS_tsdf_integrator_max_weight;
+ mapper_->lidar_tsdf_integrator().max_weight(
+ FLAGS_tsdf_integrator_max_weight);
+ }
+
+ // Mesh integrator
+ if (!gflags::GetCommandLineFlagInfoOrDie("mesh_integrator_min_weight")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: mesh_integrator_min_weight = "
+ << FLAGS_mesh_integrator_min_weight;
+ mapper_->mesh_integrator().min_weight(FLAGS_mesh_integrator_min_weight);
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie("mesh_integrator_weld_vertices")
+ .is_default) {
+ LOG(INFO)
+ << "Command line parameter found: mesh_integrator_weld_vertices = "
+ << FLAGS_mesh_integrator_weld_vertices;
+ mapper_->mesh_integrator().weld_vertices(
+ FLAGS_mesh_integrator_weld_vertices);
+ }
+
+ // Color integrator
+ if (!gflags::GetCommandLineFlagInfoOrDie(
+ "color_integrator_max_integration_distance_m")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: "
+ "color_integrator_max_integration_distance_m = "
+ << FLAGS_color_integrator_max_integration_distance_m;
+ mapper_->color_integrator().max_integration_distance_m(
+ FLAGS_color_integrator_max_integration_distance_m);
+ }
+
+ // ESDF integrator
+ if (!gflags::GetCommandLineFlagInfoOrDie("esdf_integrator_min_weight")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: esdf_integrator_min_weight = "
+ << FLAGS_esdf_integrator_min_weight;
+ mapper_->esdf_integrator().min_weight(FLAGS_esdf_integrator_min_weight);
+ }
+ if (!gflags::GetCommandLineFlagInfoOrDie(
+ "esdf_integrator_max_site_distance_vox")
+ .is_default) {
+ LOG(INFO) << "Command line parameter found: "
+ "esdf_integrator_max_site_distance_vox = "
+ << FLAGS_esdf_integrator_max_site_distance_vox;
+ mapper_->esdf_integrator().max_site_distance_vox(
+ FLAGS_esdf_integrator_max_site_distance_vox);
+ }
+
+ if (!gflags::GetCommandLineFlagInfoOrDie("esdf_integrator_max_distance_m")
+ .is_default) {
+ LOG(INFO)
+ << "Command line parameter found: esdf_integrator_max_distance_m = "
+ << FLAGS_esdf_integrator_max_distance_m;
+ mapper_->esdf_integrator().max_distance_m(
+ FLAGS_esdf_integrator_max_distance_m);
+ }
+
+ if (!gflags::GetCommandLineFlagInfoOrDie("esdf_mode").is_default) {
+ LOG(INFO) << "Command line parameter found: esdf_mode = "
+ << FLAGS_esdf_mode;
+ if (FLAGS_esdf_mode == 0) {
+ setEsdfMode(RgbdMapper::EsdfMode::k3D);
+ } else if (FLAGS_esdf_mode == 1) {
+ setEsdfMode(RgbdMapper::EsdfMode::k2D);
+ z_min_ = FLAGS_esdf_zmin;
+ z_max_ = FLAGS_esdf_zmax;
+ z_slice_ = FLAGS_esdf_z_slice;
+ } else if (FLAGS_esdf_mode == 2) {
+ setEsdfMode(RgbdMapper::EsdfMode::kUnset);
+ }
+ }
+}
+
+// NOTE(gogojjh): the overall procedures running function
+int FuserLidar::run() {
+ LOG(INFO) << "Trying to integrate the first frame: ";
+ if (!integrateFrames()) {
+ LOG(FATAL)
+ << "Failed to integrate first frame. Please check the file path.";
+ return 1;
+ }
+
+ if (!mesh_output_path_.empty()) {
+ LOG(INFO) << "Generating the mesh.";
+ mapper_->updateMesh();
+ LOG(INFO) << "Outputting mesh ply file to " << mesh_output_path_;
+ outputMeshPly();
+ }
+
+ if (!esdf_output_path_.empty()) {
+ LOG(INFO) << "Generating the ESDF.";
+ updateEsdf();
+ LOG(INFO) << "Outputting ESDF pointcloud ply file to " << esdf_output_path_;
+ outputPointcloudPly();
+ }
+
+ if (!map_output_path_.empty()) {
+ LOG(INFO) << "Outputting the serialized map to " << map_output_path_;
+ outputMapToFile();
+ }
+
+ if (!obs_output_path_.empty()) {
+ LOG(INFO) << "Outputting Obstacle based on the ESDF map ply file to "
+ << obs_output_path_;
+ outputObstaclePointcloudPly();
+ }
+
+ // std::vector keywords = {
+ // std::string("integrate"), std::string("normal"), std::string("write")};
+ std::vector keywords = {std::string("fuser"),
+ std::string("write")};
+ LOG(INFO) << nvblox::timing::Timing::Print(keywords);
+
+ LOG(INFO) << "Writing timings to file.";
+ outputTimingsToFile();
+
+ return 0;
+}
+
+RgbdMapper& FuserLidar::mapper() { return *mapper_; }
+
+void FuserLidar::setVoxelSize(float voxel_size) { voxel_size_m_ = voxel_size; }
+
+void FuserLidar::setTsdfFrameSubsampling(int subsample) {
+ tsdf_frame_subsampling_ = subsample;
+}
+
+void FuserLidar::setColorFrameSubsampling(int subsample) {
+ color_frame_subsampling_ = subsample;
+}
+
+void FuserLidar::setMeshFrameSubsampling(int subsample) {
+ mesh_frame_subsampling_ = subsample;
+}
+
+void FuserLidar::setEsdfFrameSubsampling(int subsample) {
+ esdf_frame_subsampling_ = subsample;
+}
+
+void FuserLidar::setEsdfMode(RgbdMapper::EsdfMode esdf_mode) {
+ if (esdf_mode_ != RgbdMapper::EsdfMode::kUnset) {
+ LOG(WARNING) << "EsdfMode already set. Cannot change once set once. Not "
+ "doing anything.";
+ }
+ esdf_mode_ = esdf_mode;
+}
+
+// NOTE(gogojjh): this function will run the tsdf, mesh, and esdf integration
+// for each incoming frame
+bool FuserLidar::integrateFrame(const int frame_number) {
+ timing::Timer timer_file("fuser/file_loading");
+ DepthImage depth_frame;
+ DepthImage height_frame;
+ ColorImage color_frame;
+ Transform T_W_B;
+ CameraPinhole camera;
+ OSLidar oslidar;
+ const datasets::DataLoadResult load_result = data_loader_->loadNext(
+ &depth_frame, &T_W_B, &camera, &oslidar, &height_frame, &color_frame);
+ timer_file.Stop();
+
+ if (load_result == datasets::DataLoadResult::kBadFrame) {
+ LOG(INFO) << "Bad frame: wrong parameters of intrinsics or extrinsics";
+ return true; // Bad data but keep going
+ }
+ if (load_result == datasets::DataLoadResult::kNoMoreData) {
+ LOG(INFO) << "No more data: lack of depth_frame, height_frame, "
+ "or color_frame";
+ return false; // Shows over folks
+ }
+
+ timing::Timer per_frame_timer("fuser/time_per_frame");
+
+ if ((frame_number + 1) % tsdf_frame_subsampling_ == 0) {
+ oslidar.setDepthFrameCUDA(depth_frame.dataPtr());
+ oslidar.setHeightFrameCUDA(height_frame.dataPtr());
+
+ timing::Timer timer_normal("fuser/compute_normal_image"); // 0.7ms
+ nvblox::cuda::getNormalImageOSLidar(oslidar);
+ timer_normal.Stop();
+
+ timing::Timer timer_integrate("fuser/integrate_tsdf");
+ mapper_->integrateOSLidarDepth(depth_frame, T_W_B, oslidar);
+ timer_integrate.Stop();
+
+ nvblox::cuda::freeNormalImageOSLidar(oslidar);
+ }
+
+ Transform T_W_C = T_W_B * T_B_C_;
+ if (color_frame_subsampling_ > 0) {
+ if ((frame_number + 1) % color_frame_subsampling_ == 0) {
+ timing::Timer timer_integrate_color("fuser/integrate_color");
+ mapper_->integrateColor(color_frame, T_W_C, camera);
+ timer_integrate_color.Stop();
+ }
+ }
+
+ if (mesh_frame_subsampling_ > 0) {
+ if ((frame_number + 1) % mesh_frame_subsampling_ == 0) {
+ timing::Timer timer_mesh("fuser/mesh");
+ mapper_->updateMesh();
+ }
+ }
+
+ if (esdf_frame_subsampling_ > 0) {
+ if ((frame_number + 1) % esdf_frame_subsampling_ == 0) {
+ timing::Timer timer_integrate_esdf("fuser/integrate_esdf");
+ updateEsdf();
+ timer_integrate_esdf.Stop();
+ }
+ }
+
+ per_frame_timer.Stop();
+ return true;
+}
+
+// NOTE(gogojjh): Running all TSDF integrations
+bool FuserLidar::integrateFrames() {
+ int frame_number = 0;
+ while (frame_number < num_frames_to_integrate_ &&
+ integrateFrame(frame_number++)) {
+ timing::mark("Frame " + std::to_string(frame_number - 1), Color::Red());
+ LOG(INFO) << "Integrating frame " << frame_number - 1;
+ }
+ LOG(INFO) << "Ran out of data at frame: " << frame_number - 1;
+ return true;
+}
+
+void FuserLidar::updateEsdf() {
+ switch (esdf_mode_) {
+ case RgbdMapper::EsdfMode::kUnset:
+ break;
+ case RgbdMapper::EsdfMode::k3D:
+ mapper_->updateEsdf();
+ break;
+ case RgbdMapper::EsdfMode::k2D:
+ mapper_->updateEsdfSlice(z_min_, z_max_, z_slice_);
+ break;
+ }
+}
+
+bool FuserLidar::outputPointcloudPly() {
+ timing::Timer timer_write("fuser/esdf/write");
+ return io::outputVoxelLayerToPly(mapper_->esdf_layer(), esdf_output_path_);
+}
+
+bool FuserLidar::outputMeshPly() {
+ timing::Timer timer_write("fuser/mesh/write");
+ return io::outputMeshLayerToPly(mapper_->mesh_layer(), mesh_output_path_);
+}
+
+bool FuserLidar::outputObstaclePointcloudPly() {
+ timing::Timer timer_write("fuser/obstacle/write");
+ LOG(INFO)
+ << "[NOTE] The output of the obstacle point cloud is under construction";
+ return io::outputObstacleToPly(mapper_->esdf_layer(), obs_output_path_);
+}
+
+bool FuserLidar::outputTimingsToFile() {
+ LOG(INFO) << "Writing timing to: " << timing_output_path_;
+ std::ofstream timing_file(timing_output_path_);
+ timing_file << nvblox::timing::Timing::Print();
+ timing_file.close();
+ return true;
+}
+
+bool FuserLidar::outputMapToFile() {
+ timing::Timer timer_serialize("fuser/map/write");
+ return mapper_->saveMap(map_output_path_);
+}
+
+} // namespace nvblox
diff --git a/nvblox/executables/src/fuser.cpp b/nvblox/executables/src/fuser_rgbd.cpp
similarity index 90%
rename from nvblox/executables/src/fuser.cpp
rename to nvblox/executables/src/fuser_rgbd.cpp
index 0d2f16de9..9a80a3640 100644
--- a/nvblox/executables/src/fuser.cpp
+++ b/nvblox/executables/src/fuser_rgbd.cpp
@@ -13,12 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
-#include "nvblox/executables/fuser.h"
#include
#include
-#include "nvblox/executables/fuser.h"
+#include "nvblox/executables/fuser_rgbd.h"
#include "nvblox/io/mesh_io.h"
#include "nvblox/io/ply_writer.h"
#include "nvblox/io/pointcloud_io.h"
@@ -81,7 +80,8 @@ DEFINE_double(esdf_integrator_max_distance_m, -1.0,
namespace nvblox {
-Fuser::Fuser(std::unique_ptr&& data_loader)
+FuserRGBD::FuserRGBD(
+ std::unique_ptr&& data_loader)
: data_loader_(std::move(data_loader)) {
// NOTE(alexmillane): We require the voxel size before we construct the
// mapper, so we grab this parameter first and separately.
@@ -106,7 +106,7 @@ Fuser::Fuser(std::unique_ptr&& data_loader)
readCommandLineFlags();
};
-void Fuser::readCommandLineFlags() {
+void FuserRGBD::readCommandLineFlags() {
// Dataset flags
if (!gflags::GetCommandLineFlagInfoOrDie("num_frames").is_default) {
LOG(INFO) << "Command line parameter found: num_frames = "
@@ -160,6 +160,7 @@ void Fuser::readCommandLineFlags() {
<< FLAGS_esdf_frame_subsampling;
setEsdfFrameSubsampling(FLAGS_esdf_frame_subsampling);
}
+
// TSDF integrator
if (!gflags::GetCommandLineFlagInfoOrDie(
"tsdf_integrator_max_integration_distance_m")
@@ -185,6 +186,7 @@ void Fuser::readCommandLineFlags() {
<< FLAGS_tsdf_integrator_max_weight;
mapper_->tsdf_integrator().max_weight(FLAGS_tsdf_integrator_max_weight);
}
+
// Mesh integrator
if (!gflags::GetCommandLineFlagInfoOrDie("mesh_integrator_min_weight")
.is_default) {
@@ -200,6 +202,7 @@ void Fuser::readCommandLineFlags() {
mapper_->mesh_integrator().weld_vertices(
FLAGS_mesh_integrator_weld_vertices);
}
+
// Color integrator
if (!gflags::GetCommandLineFlagInfoOrDie(
"color_integrator_max_integration_distance_m")
@@ -210,6 +213,7 @@ void Fuser::readCommandLineFlags() {
mapper_->color_integrator().max_integration_distance_m(
FLAGS_color_integrator_max_integration_distance_m);
}
+
// ESDF integrator
if (!gflags::GetCommandLineFlagInfoOrDie("esdf_integrator_min_weight")
.is_default) {
@@ -226,6 +230,7 @@ void Fuser::readCommandLineFlags() {
mapper_->esdf_integrator().max_site_distance_vox(
FLAGS_esdf_integrator_max_site_distance_vox);
}
+
if (!gflags::GetCommandLineFlagInfoOrDie("esdf_integrator_max_distance_m")
.is_default) {
LOG(INFO)
@@ -236,7 +241,7 @@ void Fuser::readCommandLineFlags() {
}
}
-int Fuser::run() {
+int FuserRGBD::run() {
LOG(INFO) << "Trying to integrate the first frame: ";
if (!integrateFrames()) {
LOG(FATAL)
@@ -271,27 +276,27 @@ int Fuser::run() {
return 0;
}
-RgbdMapper& Fuser::mapper() { return *mapper_; }
+RgbdMapper& FuserRGBD::mapper() { return *mapper_; }
-void Fuser::setVoxelSize(float voxel_size) { voxel_size_m_ = voxel_size; }
+void FuserRGBD::setVoxelSize(float voxel_size) { voxel_size_m_ = voxel_size; }
-void Fuser::setTsdfFrameSubsampling(int subsample) {
+void FuserRGBD::setTsdfFrameSubsampling(int subsample) {
tsdf_frame_subsampling_ = subsample;
}
-void Fuser::setColorFrameSubsampling(int subsample) {
+void FuserRGBD::setColorFrameSubsampling(int subsample) {
color_frame_subsampling_ = subsample;
}
-void Fuser::setMeshFrameSubsampling(int subsample) {
+void FuserRGBD::setMeshFrameSubsampling(int subsample) {
mesh_frame_subsampling_ = subsample;
}
-void Fuser::setEsdfFrameSubsampling(int subsample) {
+void FuserRGBD::setEsdfFrameSubsampling(int subsample) {
esdf_frame_subsampling_ = subsample;
}
-void Fuser::setEsdfMode(RgbdMapper::EsdfMode esdf_mode) {
+void FuserRGBD::setEsdfMode(RgbdMapper::EsdfMode esdf_mode) {
if (esdf_mode_ != RgbdMapper::EsdfMode::kUnset) {
LOG(WARNING) << "EsdfMode already set. Cannot change once set once. Not "
"doing anything.";
@@ -299,8 +304,8 @@ void Fuser::setEsdfMode(RgbdMapper::EsdfMode esdf_mode) {
esdf_mode_ = esdf_mode;
}
-bool Fuser::integrateFrame(const int frame_number) {
- timing::Timer timer_file("fuser/file_loading");
+bool FuserRGBD::integrateFrame(const int frame_number) {
+ timing::Timer timer_file("FuserRGBD/file_loading");
DepthImage depth_frame;
ColorImage color_frame;
Transform T_L_C;
@@ -316,51 +321,50 @@ bool Fuser::integrateFrame(const int frame_number) {
return false; // Shows over folks
}
- timing::Timer per_frame_timer("fuser/time_per_frame");
+ timing::Timer per_frame_timer("FuserRGBD/time_per_frame");
if ((frame_number + 1) % tsdf_frame_subsampling_ == 0) {
- timing::Timer timer_integrate("fuser/integrate_tsdf");
+ timing::Timer timer_integrate("FuserRGBD/integrate_tsdf");
mapper_->integrateDepth(depth_frame, T_L_C, camera);
timer_integrate.Stop();
}
if ((frame_number + 1) % color_frame_subsampling_ == 0) {
- timing::Timer timer_integrate_color("fuser/integrate_color");
+ timing::Timer timer_integrate_color("FuserRGBD/integrate_color");
mapper_->integrateColor(color_frame, T_L_C, camera);
timer_integrate_color.Stop();
}
if (mesh_frame_subsampling_ > 0) {
if ((frame_number + 1) % mesh_frame_subsampling_ == 0) {
- timing::Timer timer_mesh("fuser/mesh");
+ timing::Timer timer_mesh("FuserRGBD/mesh");
mapper_->updateMesh();
}
}
if (esdf_frame_subsampling_ > 0) {
if ((frame_number + 1) % esdf_frame_subsampling_ == 0) {
- timing::Timer timer_integrate_esdf("fuser/integrate_esdf");
+ timing::Timer timer_integrate_esdf("FuserRGBD/integrate_esdf");
updateEsdf();
timer_integrate_esdf.Stop();
}
}
-
per_frame_timer.Stop();
-
return true;
-}
+} // namespace nvblox
-bool Fuser::integrateFrames() {
+bool FuserRGBD::integrateFrames() {
int frame_number = 0;
while (frame_number < num_frames_to_integrate_ &&
integrateFrame(frame_number++)) {
timing::mark("Frame " + std::to_string(frame_number - 1), Color::Red());
LOG(INFO) << "Integrating frame " << frame_number - 1;
+ std::cout << std::endl;
}
LOG(INFO) << "Ran out of data at frame: " << frame_number - 1;
return true;
}
-void Fuser::updateEsdf() {
+void FuserRGBD::updateEsdf() {
switch (esdf_mode_) {
case RgbdMapper::EsdfMode::kUnset:
break;
@@ -373,17 +377,17 @@ void Fuser::updateEsdf() {
}
}
-bool Fuser::outputPointcloudPly() {
- timing::Timer timer_write("fuser/esdf/write");
+bool FuserRGBD::outputPointcloudPly() {
+ timing::Timer timer_write("FuserRGBD/esdf/write");
return io::outputVoxelLayerToPly(mapper_->esdf_layer(), esdf_output_path_);
}
-bool Fuser::outputMeshPly() {
- timing::Timer timer_write("fuser/mesh/write");
+bool FuserRGBD::outputMeshPly() {
+ timing::Timer timer_write("FuserRGBD/mesh/write");
return io::outputMeshLayerToPly(mapper_->mesh_layer(), mesh_output_path_);
}
-bool Fuser::outputTimingsToFile() {
+bool FuserRGBD::outputTimingsToFile() {
LOG(INFO) << "Writing timing to: " << timing_output_path_;
std::ofstream timing_file(timing_output_path_);
timing_file << nvblox::timing::Timing::Print();
@@ -391,8 +395,8 @@ bool Fuser::outputTimingsToFile() {
return true;
}
-bool Fuser::outputMapToFile() {
- timing::Timer timer_serialize("fuser/map/write");
+bool FuserRGBD::outputMapToFile() {
+ timing::Timer timer_serialize("FuserRGBD/map/write");
return mapper_->saveMap(map_output_path_);
}
diff --git a/nvblox/experiments/experiments/threaded_image_loading/main.cpp b/nvblox/experiments/experiments/threaded_image_loading/main.cpp
index b68ce2276..015eeb7ef 100644
--- a/nvblox/experiments/experiments/threaded_image_loading/main.cpp
+++ b/nvblox/experiments/experiments/threaded_image_loading/main.cpp
@@ -25,7 +25,7 @@ limitations under the License.
#include "nvblox/datasets/3dmatch.h"
#include "nvblox/datasets/3dmatch.h"
-#include "nvblox/executables/fuser.h"
+#include "nvblox/executables/fuser_rgbd.h"
DEFINE_bool(single_thread, false, "Load images using a single thread.");
DEFINE_bool(multi_thread, false, "Load images using multiple threads");
@@ -58,7 +58,7 @@ int main(int argc, char* argv[]) {
}
constexpr int seq_id = 1;
- std::unique_ptr fuser =
+ std::unique_ptr fuser =
datasets::threedmatch::createFuser(base_path, seq_id);
// Replacing the image loader with the one we are asked to test.
diff --git a/nvblox/include/nvblox/core/camera.h b/nvblox/include/nvblox/core/camera.h
index 01dc72144..3a1a89068 100644
--- a/nvblox/include/nvblox/core/camera.h
+++ b/nvblox/include/nvblox/core/camera.h
@@ -15,6 +15,7 @@ limitations under the License.
*/
#pragma once
+#include "nvblox/core/frustum.h"
#include "nvblox/core/types.h"
namespace nvblox {
@@ -90,53 +91,6 @@ class Camera {
// Stream Camera as text
std::ostream& operator<<(std::ostream& os, const Camera& camera);
-/// A bounding plane which has one "inside" direction and the other direction is
-/// "outside." Quick tests for which side of the plane you are on.
-class BoundingPlane {
- public:
- BoundingPlane() : normal_(Vector3f::Identity()), distance_(0.0f) {}
-
- void setFromPoints(const Vector3f& p1, const Vector3f& p2,
- const Vector3f& p3);
- void setFromDistanceNormal(const Vector3f& normal, float distance);
-
- /// Is the point on the correct side of the bounding plane?
- bool isPointInside(const Vector3f& point) const;
-
- const Vector3f& normal() const { return normal_; }
- float distance() const { return distance_; }
-
- private:
- Vector3f normal_;
- float distance_;
-};
-
-/// Class that allows checking for whether objects are within the field of view
-/// of a camera or not.
-class Frustum {
- public:
- // Frustum must be initialized with a camera and min and max depth and pose.
- Frustum(const Camera& camera, const Transform& T_L_C, float min_depth,
- float max_depth);
-
- AxisAlignedBoundingBox getAABB() const { return aabb_; }
-
- bool isPointInView(const Vector3f& point) const;
- bool isAABBInView(const AxisAlignedBoundingBox& aabb) const;
-
- private:
- // Helper functions to do the actual computations.
- void computeBoundingPlanes(const Eigen::Matrix& corners_C,
- const Transform& T_L_C);
-
- /// Bounding planes containing around the frustum. Expressed in the layer
- /// coordinate frame.
- std::array bounding_planes_L_;
-
- /// Cached AABB of the
- AxisAlignedBoundingBox aabb_;
-};
-
} // namespace nvblox
#include "nvblox/core/impl/camera_impl.h"
diff --git a/nvblox/include/nvblox/core/camera_pinhole.h b/nvblox/include/nvblox/core/camera_pinhole.h
new file mode 100644
index 000000000..b0106d79d
--- /dev/null
+++ b/nvblox/include/nvblox/core/camera_pinhole.h
@@ -0,0 +1,103 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#pragma once
+
+#include "nvblox/core/frustum.h"
+#include "nvblox/core/types.h"
+
+namespace nvblox {
+
+/// Class that describes the parameters and FoV of a camera.
+class CameraPinhole {
+ public:
+ __host__ __device__ inline CameraPinhole() = default;
+ __host__ __device__ inline CameraPinhole(const Matrix3f& K, const int width,
+ const int height);
+ __host__ __device__ inline CameraPinhole(const Matrix3x4f& P,
+ const Matrix3f& rect,
+ const int width, const int height);
+
+ __host__ __device__ inline bool project(const Vector3f& p_C,
+ Vector2f* u_C) const;
+
+ __host__ __device__ inline float getDepth(const Vector3f& p_C) const;
+
+ // Back projection (image plane point to 3D point)
+ __host__ __device__ inline Vector3f unprojectFromImagePlaneCoordinates(
+ const Vector2f& u_C, const float depth) const;
+ __host__ __device__ inline Vector3f unprojectFromPixelIndices(
+ const Index2D& u_C, const float depth) const;
+
+ /// Get the axis aligned bounding box of the view in the LAYER coordinate
+ /// frame.
+ __host__ AxisAlignedBoundingBox getViewAABB(const Transform& T_L_C,
+ const float min_depth,
+ const float max_depth) const;
+
+ __host__ Frustum getViewFrustum(const Transform& T_L_C, const float min_depth,
+ const float max_depth) const;
+
+ /// Gets the view corners in the CAMERA coordinate frame.
+ __host__ Eigen::Matrix getViewCorners(
+ const float min_depth, const float max_depth) const;
+
+ // Returns an unnormalized ray direction in the camera frame corresponding to
+ // the passed pixel.
+ // Two functions, one for (floating point) image-plane coordinates, another
+ // function for pixel indices.
+ __host__ __device__ inline Vector3f vectorFromImagePlaneCoordinates(
+ const Vector2f& u_C) const;
+ __host__ __device__ inline Vector3f vectorFromPixelIndices(
+ const Index2D& u_C) const;
+
+ // Accessors
+ __host__ __device__ inline Matrix3f K() const { return K_; }
+ __host__ __device__ inline Matrix3x4f P() const { return P_; }
+ __host__ __device__ inline Matrix3f Rect() const { return R_rect_; }
+ __host__ __device__ inline int width() const { return width_; }
+ __host__ __device__ inline int height() const { return height_; }
+ __host__ __device__ inline int cols() const { return width_; }
+ __host__ __device__ inline int rows() const { return height_; }
+ __host__ __device__ inline bool isRectified() const { return rectified_; }
+
+ // Factories
+ inline static CameraPinhole fromIntrinsicsMatrix(const Matrix3f& mat,
+ int width, int height);
+
+ inline static CameraPinhole fromIntrinsicsMatrix(const Matrix3x4f& P,
+ const Matrix3f& rect,
+ int width, int height);
+
+ private:
+ Matrix3f K_;
+ /// NOTE(gogojjh):
+ /// KITTI dataset: P_ = P_rect_00, we first transform the point into a camera
+ /// P_(0:2, 3) = 0, P_(3, 3) = 1
+ Matrix3x4f P_;
+ /// KITTI dataset: R_rect_ = R_rect_0x
+ Matrix3f R_rect_;
+
+ int width_;
+ int height_;
+ bool rectified_;
+};
+
+// Stream Camera as text
+std::ostream& operator<<(std::ostream& os, const CameraPinhole& camera);
+
+} // namespace nvblox
+
+#include "nvblox/core/impl/camera_pinhole_impl.h"
diff --git a/nvblox/include/nvblox/core/cuda/image_operation.h b/nvblox/include/nvblox/core/cuda/image_operation.h
new file mode 100644
index 000000000..c8df43019
--- /dev/null
+++ b/nvblox/include/nvblox/core/cuda/image_operation.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "nvblox/core/oslidar.h"
+
+namespace nvblox {
+namespace cuda {
+
+__host__ __device__ inline int idivup(int a, int b) {
+ return ((a % b) != 0) ? (a / b + 1) : (a / b);
+}
+
+// NOTE(gogojjh): we cannot directly give the value from CPU and avoid
+// dangerous operations, like bug1: int w_ = lidar.num_azimuth_divisions();
+// bug2: int h_ = lidar.num_elevation_divisions();
+// bug3: cannot print in the loop
+// we cannot correctly use values of w_ and h_
+__global__ void computeNormalImageOSLidar(const float* depth_image,
+ const float* height_image,
+ float* normal_image, const int w,
+ const int h,
+ const float rads_per_pixel_azimuth,
+ const float rads_per_pixel_elevation);
+/**
+ * Get and compute the normal image with the CUDA
+ * @param {OSLidar} lidar :
+ */
+void getNormalImageOSLidar(OSLidar& lidar);
+
+/**
+ * Free the CUDA memory allocated
+ * @param {OSLidar} lidar :
+ */
+void freeNormalImageOSLidar(OSLidar& lidar);
+
+} // namespace cuda
+} // namespace nvblox
diff --git a/nvblox/include/nvblox/core/frustum.h b/nvblox/include/nvblox/core/frustum.h
new file mode 100644
index 000000000..2e14ef311
--- /dev/null
+++ b/nvblox/include/nvblox/core/frustum.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "nvblox/core/types.h"
+
+namespace nvblox {
+
+/// A bounding plane which has one "inside" direction and the other direction is
+/// "outside." Quick tests for which side of the plane you are on.
+class BoundingPlane {
+ public:
+ BoundingPlane() : normal_(Vector3f::Identity()), distance_(0.0f) {}
+
+ void setFromPoints(const Vector3f& p1, const Vector3f& p2,
+ const Vector3f& p3);
+ void setFromDistanceNormal(const Vector3f& normal, float distance);
+
+ /// Is the point on the correct side of the bounding plane?
+ bool isPointInside(const Vector3f& point) const;
+
+ const Vector3f& normal() const { return normal_; }
+ float distance() const { return distance_; }
+
+ private:
+ Vector3f normal_;
+ float distance_;
+};
+
+/// Class that allows checking for whether objects are within the field of view
+/// of a camera or not.
+class Frustum {
+ public:
+ // Frustum must be initialized with a camera and min and max depth and pose.
+ template
+ Frustum(const CameraType& camera, const Transform& T_L_C, float min_depth,
+ float max_depth);
+
+ AxisAlignedBoundingBox getAABB() const { return aabb_; }
+
+ bool isPointInView(const Vector3f& point) const;
+ bool isAABBInView(const AxisAlignedBoundingBox& aabb) const;
+
+ private:
+ // Helper functions to do the actual computations.
+ void computeBoundingPlanes(const Eigen::Matrix& corners_C,
+ const Transform& T_L_C);
+
+ /// Bounding planes containing around the frustum. Expressed in the layer
+ /// coordinate frame.
+ std::array bounding_planes_L_;
+
+ /// Cached AABB of the
+ AxisAlignedBoundingBox aabb_;
+};
+
+} // namespace nvblox
\ No newline at end of file
diff --git a/nvblox/include/nvblox/core/image.h b/nvblox/include/nvblox/core/image.h
index 453c1f47e..cec0297d3 100644
--- a/nvblox/include/nvblox/core/image.h
+++ b/nvblox/include/nvblox/core/image.h
@@ -17,6 +17,7 @@ limitations under the License.
#include
+#include
#include "nvblox/core/color.h"
#include "nvblox/core/types.h"
#include "nvblox/core/unified_ptr.h"
@@ -133,7 +134,9 @@ class Image {
};
using DepthImage = Image;
+using NormalImage = Image;
using ColorImage = Image;
+using CoorImage = Image;
// Image Reductions
namespace image {
diff --git a/nvblox/include/nvblox/core/impl/camera_pinhole_impl.h b/nvblox/include/nvblox/core/impl/camera_pinhole_impl.h
new file mode 100644
index 000000000..bed66ee53
--- /dev/null
+++ b/nvblox/include/nvblox/core/impl/camera_pinhole_impl.h
@@ -0,0 +1,110 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#pragma once
+
+#include
+
+namespace nvblox {
+
+CameraPinhole::CameraPinhole(const Matrix3f& K, const int width,
+ const int height)
+ : K_(K), width_(width), height_(height), rectified_(false) {
+ P_.setIdentity();
+ R_rect_.setIdentity();
+}
+
+CameraPinhole::CameraPinhole(const Matrix3x4f& P, const Matrix3f& rect,
+ const int width, const int height)
+ : P_(P), R_rect_(rect), width_(width), height_(height), rectified_(true) {
+ K_.setIdentity();
+}
+
+bool CameraPinhole::project(const Eigen::Vector3f& p_C,
+ Eigen::Vector2f* u_C) const {
+ // Point is behind the camera.
+ if (p_C.z() <= 0.0f) {
+ return false;
+ }
+ if (rectified_) {
+ Vector3f p = P_.block<3, 3>(0, 0) * (R_rect_ * p_C) + P_.block<3, 1>(0, 3);
+ *u_C = p.head<2>() / p(2);
+ if (u_C->x() > width_ || u_C->y() > height_ || u_C->x() < 0 ||
+ u_C->y() < 0) {
+ return false;
+ }
+ } else {
+ Vector3f p = K_ * p_C;
+ *u_C = p.head<2>() / p(2);
+ if (u_C->x() > width_ || u_C->y() > height_ || u_C->x() < 0 ||
+ u_C->y() < 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+float CameraPinhole::getDepth(const Vector3f& p_C) const { return p_C.z(); }
+
+Vector3f CameraPinhole::unprojectFromImagePlaneCoordinates(
+ const Vector2f& u_C, const float depth) const {
+ return depth * vectorFromImagePlaneCoordinates(u_C);
+}
+
+Vector3f CameraPinhole::unprojectFromPixelIndices(const Index2D& u_C,
+ const float depth) const {
+ return depth * vectorFromPixelIndices(u_C);
+}
+
+Vector3f CameraPinhole::vectorFromImagePlaneCoordinates(
+ const Vector2f& u_C) const {
+ // NOTE(alexmillane): We allow u_C values up to the outer edges of pixels,
+ // such that:
+ // 0.0f < u_C[0] <= width
+ // 0.0f < u_C[1] <= height
+ if (rectified_) {
+ Matrix3f P_tmp = P_.block<3, 3>(0, 0);
+ Vector3f x(u_C[0], u_C[1], 1.0f);
+ Vector3f p = P_tmp.inverse() * x;
+ p /= p(2);
+ return p;
+ } else {
+ Vector3f x(u_C[0], u_C[1], 1.0f);
+ Vector3f p = K_.inverse() * x;
+ p /= p(2);
+ return p;
+ }
+}
+
+Vector3f CameraPinhole::vectorFromPixelIndices(const Index2D& u_C) const {
+ // NOTE(alexmillane): The +0.5 here takes us from image plane indices, which
+ // are equal to the coordinates of the lower pixel corner, to the pixel
+ // center.
+ return vectorFromImagePlaneCoordinates(u_C.cast() +
+ Vector2f(0.5, 0.5));
+}
+
+CameraPinhole CameraPinhole::fromIntrinsicsMatrix(const Eigen::Matrix3f& mat,
+ int width, int height) {
+ return CameraPinhole(mat, width, height);
+}
+
+CameraPinhole CameraPinhole::fromIntrinsicsMatrix(const Matrix3x4f& P,
+ const Matrix3f& rect,
+ int width, int height) {
+ return CameraPinhole(P, rect, width, height);
+}
+
+} // namespace nvblox
diff --git a/nvblox/include/nvblox/core/impl/interpolation_2d_impl.h b/nvblox/include/nvblox/core/impl/interpolation_2d_impl.h
index 705438eb2..4e6acde31 100644
--- a/nvblox/include/nvblox/core/impl/interpolation_2d_impl.h
+++ b/nvblox/include/nvblox/core/impl/interpolation_2d_impl.h
@@ -119,7 +119,8 @@ bool interpolate2DLinear(const Image& frame, const Vector2f& u_px,
template
bool interpolate2DClosest(const ElementType* frame, const Vector2f& u_px,
const int rows, const int cols,
- ElementType* value_interpolated_ptr, Index2D* u_px_closest_ptr) {
+ ElementType* value_interpolated_ptr,
+ Index2D* u_px_closest_ptr) {
// Closest pixel
const Index2D u_M_rounded = u_px.array().round().cast();
// Check bounds:
@@ -135,17 +136,18 @@ bool interpolate2DClosest(const ElementType* frame, const Vector2f& u_px,
return false;
}
*value_interpolated_ptr = pixel_value;
- if(u_px_closest_ptr) {
+ if (u_px_closest_ptr) {
*u_px_closest_ptr = u_M_rounded;
}
return true;
}
+/// @brief interpolate the pixel value given float coordinates
template
-bool interpolate2DLinear(const ElementType* frame, const Vector2f& u_px,
- const int rows, const int cols,
- ElementType* value_interpolated_ptr,
- Interpolation2DNeighbours* neighbours_ptr) {
+bool interpolate2DLinear(
+ const ElementType* frame, const Vector2f& u_px, const int rows,
+ const int cols, ElementType* value_interpolated_ptr,
+ Interpolation2DNeighbours* neighbours_ptr) {
// Subtraction of Vector2f(0.5, 0.5) takes our coordinates from
// corner-referenced to center-referenced.
const Vector2f u_center_referenced_px = u_px - Vector2f(0.5, 0.5);
diff --git a/nvblox/include/nvblox/core/impl/lidar_impl.h b/nvblox/include/nvblox/core/impl/lidar_impl.h
index 4e0b9ddf3..51d155eeb 100644
--- a/nvblox/include/nvblox/core/impl/lidar_impl.h
+++ b/nvblox/include/nvblox/core/impl/lidar_impl.h
@@ -14,6 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+/*
+NOTE(gogojjh):
+This implements the operation that project lidar points onto a depth image
+This implementation is only valid for lidars that have average elevation angle
+ i.e., VLP16, otherwise, the lidar_impl.h should be rewritten for a specific
+ lidar type
+*/
+
#pragma once
#include "math.h"
@@ -23,9 +31,10 @@ limitations under the License.
namespace nvblox {
Lidar::Lidar(int num_azimuth_divisions, int num_elevation_divisions,
- float vertical_fov_rad)
+ float horizontal_fov_rad, float vertical_fov_rad)
: num_azimuth_divisions_(num_azimuth_divisions),
num_elevation_divisions_(num_elevation_divisions),
+ horizontal_fov_rad_(horizontal_fov_rad),
vertical_fov_rad_(vertical_fov_rad) {
// Even numbers of beams allowed
CHECK(num_azimuth_divisions_ % 2 == 0);
@@ -35,9 +44,9 @@ Lidar::Lidar(int num_azimuth_divisions, int num_elevation_divisions,
// This is because in the azimuth direction there's a wrapping around. The
// point at pi/-pi is not double sampled, generating this difference.
rads_per_pixel_elevation_ =
- vertical_fov_rad / static_cast(num_elevation_divisions_ - 1);
+ vertical_fov_rad_ / static_cast(num_elevation_divisions_ - 1);
rads_per_pixel_azimuth_ =
- 2.0f * M_PI / static_cast(num_azimuth_divisions_);
+ horizontal_fov_rad_ / static_cast(num_azimuth_divisions_);
// Inverse of the above
elevation_pixels_per_rad_ = 1.0f / rads_per_pixel_elevation_;
@@ -49,7 +58,13 @@ Lidar::Lidar(int num_azimuth_divisions, int num_elevation_divisions,
// below this.
// Note(alexmillane): Note that we use polar angle here, not elevation.
// Polar is from the top of the sphere down, elevation, the middle up.
- start_polar_angle_rad_ = M_PI / 2.0f - (vertical_fov_rad / 2.0f +
+ // ********************* polar_angle
+ // ****** the start polar_angle indicate the direction: x=0, +z
+ // ****** the end polar_angle indicate the direction: x=0, -z
+ // ********************* azimuth_angle
+ // ****** the start and end azimuth_angle: counterclockwise
+ // -x, y=0 -> +x, y=0
+ start_polar_angle_rad_ = M_PI / 2.0f - (vertical_fov_rad_ / 2.0f +
rads_per_pixel_elevation_ / 2.0f);
start_azimuth_angle_rad_ = -M_PI - rads_per_pixel_azimuth_ / 2.0f;
}
diff --git a/nvblox/include/nvblox/core/impl/oslidar_impl.h b/nvblox/include/nvblox/core/impl/oslidar_impl.h
new file mode 100644
index 000000000..b0173d15a
--- /dev/null
+++ b/nvblox/include/nvblox/core/impl/oslidar_impl.h
@@ -0,0 +1,257 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/*
+NOTE(gogojjh):
+This implements the operation that project OSLidar points onto a depth image
+*/
+
+#pragma once
+
+#include "math.h"
+
+#include
+
+namespace nvblox {
+
+// given initial intrinsics from the intrisics file, but need to be refined for
+// each new scan
+OSLidar::OSLidar(int num_azimuth_divisions, int num_elevation_divisions,
+ float horizontal_fov_rad, float vertical_fov_rad,
+ float start_azimuth_angle_rad, float end_azimuth_angle_rad,
+ float start_elevation_angle_rad, float end_elevation_angle_rad)
+ : num_azimuth_divisions_(num_azimuth_divisions),
+ num_elevation_divisions_(num_elevation_divisions),
+ horizontal_fov_rad_(horizontal_fov_rad),
+ vertical_fov_rad_(vertical_fov_rad),
+ start_azimuth_angle_rad_(start_azimuth_angle_rad),
+ end_azimuth_angle_rad_(end_azimuth_angle_rad),
+ start_elevation_angle_rad_(start_elevation_angle_rad),
+ end_elevation_angle_rad_(end_elevation_angle_rad) {
+ // Even numbers of beams allowed
+ CHECK(num_azimuth_divisions_ % 2 == 0);
+
+ rads_per_pixel_elevation_ =
+ vertical_fov_rad_ / static_cast(num_elevation_divisions_ - 1);
+ rads_per_pixel_azimuth_ =
+ horizontal_fov_rad_ / static_cast(num_azimuth_divisions_ - 1);
+
+ // Inverse of the above
+ elevation_pixels_per_rad_ = 1.0f / rads_per_pixel_elevation_;
+ azimuth_pixels_per_rad_ = 1.0f / rads_per_pixel_azimuth_;
+
+ // printIntrinsics();
+
+ depth_image_ptr_cuda_ = nullptr;
+ height_image_ptr_cuda_ = nullptr;
+ normal_image_ptr_cuda_ = nullptr;
+}
+
+OSLidar::~OSLidar() {}
+
+void OSLidar::printIntrinsics() const {
+ printf("OSLidar intrinsics--------------------\n");
+ printf("horizontal_fov_rad: %f\n", horizontal_fov_rad_);
+ printf("vertical_fov_rad: %f\n", vertical_fov_rad_);
+ printf("start_elevation: %f\n", start_elevation_angle_rad_);
+ printf("end_elevation: %f\n", end_elevation_angle_rad_);
+ printf("rads_per_pixel_elevation: %f\n", rads_per_pixel_elevation_);
+ printf("rads_per_pixel_azimuth: %f\n", rads_per_pixel_azimuth_);
+}
+
+/**********************************************
+ * get the parameters of OSLidar
+ **********************************************/
+int OSLidar::num_azimuth_divisions() const { return num_azimuth_divisions_; }
+
+int OSLidar::num_elevation_divisions() const {
+ return num_elevation_divisions_;
+}
+
+float OSLidar::vertical_fov_rad() const { return vertical_fov_rad_; }
+
+float OSLidar::horizontal_fov_rad() const { return horizontal_fov_rad_; }
+
+float OSLidar::rads_per_pixel_elevation() const {
+ return rads_per_pixel_elevation_;
+}
+
+float OSLidar::rads_per_pixel_azimuth() const {
+ return rads_per_pixel_azimuth_;
+}
+
+int OSLidar::numel() const {
+ return num_azimuth_divisions_ * num_elevation_divisions_;
+}
+
+int OSLidar::cols() const { return num_azimuth_divisions_; }
+
+int OSLidar::rows() const { return num_elevation_divisions_; }
+
+float OSLidar::getDepth(const Vector3f& p_C) const { return p_C.norm(); }
+
+// NOTE(gogojjh): this function is added by gogojjh
+Vector3f OSLidar::getNormalVector(const Index2D& u_C) const {
+ if (normal_image_ptr_cuda_) {
+ float x = normal_image_ptr_cuda_[3 * (u_C.y() * num_azimuth_divisions_ +
+ u_C.x())];
+ float y = normal_image_ptr_cuda_[3 * (u_C.y() * num_azimuth_divisions_ +
+ u_C.x()) +
+ 1];
+ float z = normal_image_ptr_cuda_[3 * (u_C.y() * num_azimuth_divisions_ +
+ u_C.x()) +
+ 2];
+ return Vector3f(x, y, z);
+ } else {
+ return Vector3f(0.0f, 0.0f, 0.0f);
+ }
+}
+
+/**********************************************
+ * Project a 3D point p_C to get the image coordinates u_C
+ **********************************************/
+bool OSLidar::project(const Vector3f& p_C, Vector2f* u_C) const {
+ const float r = p_C.norm();
+ constexpr float kMinProjectionEps = 0.01;
+ if (r < kMinProjectionEps) return false;
+
+ const float elevation_angle_rad = acos(p_C.z() / r);
+ const float azimuth_angle_rad = M_PI - atan2(p_C.y(), p_C.x());
+ float v_float = (elevation_angle_rad - start_elevation_angle_rad_) /
+ rads_per_pixel_elevation_;
+ float u_float =
+ (azimuth_angle_rad - start_azimuth_angle_rad_) / rads_per_pixel_azimuth_;
+
+ // Catch wrap around issues.
+ if (u_float >= num_azimuth_divisions_) {
+ u_float -= num_azimuth_divisions_;
+ }
+
+ // Points out of FOV
+ // NOTE(alexmillane): It should be impossible to escape the -pi-to-pi range in
+ // azimuth due to wrap around this. Therefore we don't check.
+ if ((round(v_float) < 0) || (round(v_float) > num_elevation_divisions_ - 1)) {
+ return false;
+ }
+
+ // Write output
+ *u_C = Vector2f(u_float, v_float);
+ return true;
+}
+
+bool OSLidar::project(const Vector3f& p_C, Index2D* u_C) const {
+ Vector2f u_C_float;
+ bool res = project(p_C, &u_C_float);
+ *u_C = u_C_float.array().round().matrix().cast();
+ return res;
+}
+
+/**********************************************
+ * transformation between the pixel index (int) and image coordinates (float)
+ **********************************************/
+Vector2f OSLidar::pixelIndexToImagePlaneCoordsOfCenter(
+ const Index2D& u_C) const {
+ // The index cast to a float is the coordinates of the lower corner of the
+ // pixel.
+ return u_C.cast();
+}
+
+Index2D OSLidar::imagePlaneCoordsToPixelIndex(const Vector2f& u_C) const {
+ // NOTE(alexmillane): We do round rather than a straight truncation such that
+ // we handle negative image plane coordinates.
+ return u_C.array().round().cast();
+}
+
+/**********************************************
+ * Unproject a 2D image coordinates u_C to a 3D point p_C
+ **********************************************/
+Vector3f OSLidar::unprojectFromImagePlaneCoordinates(const Vector2f& u_C,
+ const float depth) const {
+ return depth * vectorFromImagePlaneCoordinates(u_C);
+}
+
+Vector3f OSLidar::unprojectFromPixelIndices(const Index2D& u_C,
+ const float depth) const {
+ return depth * vectorFromPixelIndices(u_C);
+}
+
+Vector3f OSLidar::unprojectFromImageIndex(const Index2D& u_C) const {
+ float height = image::access(u_C.y(), u_C.x(), num_azimuth_divisions_,
+ height_image_ptr_cuda_);
+ float depth = image::access(u_C.y(), u_C.x(), num_azimuth_divisions_,
+ depth_image_ptr_cuda_);
+ float r = sqrt(depth * depth - height * height);
+ float azimuth_angle_rad = M_PI - u_C.x() * rads_per_pixel_azimuth_;
+ Vector3f p(r * cos(azimuth_angle_rad), r * sin(azimuth_angle_rad), height);
+ return p;
+}
+
+Vector3f OSLidar::vectorFromImagePlaneCoordinates(const Vector2f& u_C) const {
+ float height =
+ image::access(round(u_C.y()), round(u_C.x()),
+ num_azimuth_divisions_, height_image_ptr_cuda_);
+ float depth =
+ image::access(round(u_C.y()), round(u_C.x()),
+ num_azimuth_divisions_, depth_image_ptr_cuda_);
+ float r = sqrt(depth * depth - height * height);
+ float azimuth_angle_rad = M_PI - u_C.x() * rads_per_pixel_azimuth_;
+ Vector3f p(r * cos(azimuth_angle_rad), r * sin(azimuth_angle_rad), height);
+ return p / depth;
+}
+
+Vector3f OSLidar::vectorFromPixelIndices(const Index2D& u_C) const {
+ return vectorFromImagePlaneCoordinates(
+ pixelIndexToImagePlaneCoordsOfCenter(u_C));
+}
+
+/**********************************************
+ * Project a 3D point p_C to get the image coordinates u_C
+ **********************************************/
+AxisAlignedBoundingBox OSLidar::getViewAABB(const Transform& T_L_C,
+ const float min_depth,
+ const float max_depth) const {
+ // The AABB is a square centered at the OSLidars location where the height is
+ // determined by the OSLidar FoV.
+ // NOTE(alexmillane): The min depth is ignored in this function, it is a
+ // parameter so it matches with camera's getViewAABB()
+ AxisAlignedBoundingBox box(
+ Vector3f(-max_depth, -max_depth,
+ -max_depth * sin(vertical_fov_rad_ / 2.0f)),
+ Vector3f(max_depth, max_depth,
+ max_depth * sin(vertical_fov_rad_ / 2.0f)));
+
+ // Translate the box to the sensor's location (note that orientation doesn't
+ // matter as the OSLidar sees in the circle)
+ box.translate(T_L_C.translation());
+ return box;
+}
+
+size_t OSLidar::Hash::operator()(const OSLidar& OSLidar) const {
+ // Taken from:
+ // https://stackoverflow.com/questions/17016175/c-unordered-map-using-a-custom-class-type-as-the-key
+ size_t az_hash = std::hash()(OSLidar.num_azimuth_divisions_);
+ size_t el_hash = std::hash()(OSLidar.num_elevation_divisions_);
+ size_t fov_hash = std::hash()(OSLidar.vertical_fov_rad_);
+ return ((az_hash ^ (el_hash << 1)) >> 1) ^ (fov_hash << 1);
+}
+
+bool operator==(const OSLidar& lhs, const OSLidar& rhs) {
+ return (lhs.num_azimuth_divisions_ == rhs.num_azimuth_divisions_) &&
+ (lhs.num_elevation_divisions_ == rhs.num_elevation_divisions_) &&
+ (std::fabs(lhs.vertical_fov_rad_ - rhs.vertical_fov_rad_) <
+ std::numeric_limits::epsilon());
+}
+} // namespace nvblox
diff --git a/nvblox/include/nvblox/core/lidar.h b/nvblox/include/nvblox/core/lidar.h
index 7c07e0c3d..ddb00604f 100644
--- a/nvblox/include/nvblox/core/lidar.h
+++ b/nvblox/include/nvblox/core/lidar.h
@@ -24,6 +24,7 @@ class Lidar {
Lidar() = delete;
__host__ __device__ inline Lidar(int num_azimuth_divisions,
int num_elevation_divisions,
+ float horizontal_fov_rad,
float vertical_fov_rad);
__host__ __device__ inline ~Lidar() = default;
@@ -81,6 +82,7 @@ class Lidar {
// Core parameters
int num_azimuth_divisions_;
int num_elevation_divisions_;
+ float horizontal_fov_rad_;
float vertical_fov_rad_;
// Dependent parameters
diff --git a/nvblox/include/nvblox/core/mapper.h b/nvblox/include/nvblox/core/mapper.h
index ab4e4cc21..ee4ea08d3 100644
--- a/nvblox/include/nvblox/core/mapper.h
+++ b/nvblox/include/nvblox/core/mapper.h
@@ -19,11 +19,13 @@ limitations under the License.
#include "nvblox/core/blox.h"
#include "nvblox/core/camera.h"
+#include "nvblox/core/camera_pinhole.h"
#include "nvblox/core/common_names.h"
#include "nvblox/core/hash.h"
#include "nvblox/core/layer.h"
#include "nvblox/core/layer_cake.h"
#include "nvblox/core/lidar.h"
+#include "nvblox/core/oslidar.h"
#include "nvblox/core/voxels.h"
#include "nvblox/integrators/esdf_integrator.h"
#include "nvblox/integrators/projective_color_integrator.h"
@@ -94,8 +96,9 @@ class RgbdMapper : public MapperBase {
///@param T_L_C Pose of the camera, specified as a transform from Camera-frame
/// to Layer-frame transform.
///@param camera Intrinsics model of the camera.
+ template
void integrateColor(const ColorImage& color_frame, const Transform& T_L_C,
- const Camera& camera);
+ const CameraType& camera);
/// Integrates a 3D LiDAR scan into the reconstruction.
///@param depth_frame Depth image representing the LiDAR scan. To convert a
@@ -106,6 +109,15 @@ class RgbdMapper : public MapperBase {
void integrateLidarDepth(const DepthImage& depth_frame,
const Transform& T_L_C, const Lidar& lidar);
+ /// Integrates a 3D LiDAR scan into the reconstruction.
+ ///@param depth_frame Depth image representing the LiDAR scan. To convert a
+ /// lidar scan to a DepthImage see TODOOO.
+ ///@param T_L_C Pose of the LiDAR, specified as a transform from LiDAR-frame
+ /// to Layer-frame transform.
+ ///@param lidar Intrinsics model of the Ouster LiDAR.
+ void integrateOSLidarDepth(DepthImage& depth_frame, const Transform& T_L_C,
+ OSLidar& oslidar);
+
/// Updates the mesh blocks which require an update
/// @return The indices of the blocks that were updated in this call.
std::vector updateMesh();
@@ -251,6 +263,7 @@ class RgbdMapper : public MapperBase {
/// updateEsdfSlice(). This member tracks which mode we're in.
EsdfMode esdf_mode_ = EsdfMode::kUnset;
+ /// TODO(gogojjh): how to modify these integrators for large-scale mapping?
/// Integrators
ProjectiveTsdfIntegrator tsdf_integrator_;
ProjectiveTsdfIntegrator lidar_tsdf_integrator_;
diff --git a/nvblox/include/nvblox/core/oslidar.h b/nvblox/include/nvblox/core/oslidar.h
new file mode 100644
index 000000000..a7b95b1f7
--- /dev/null
+++ b/nvblox/include/nvblox/core/oslidar.h
@@ -0,0 +1,166 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#pragma once
+
+#include "nvblox/core/image.h"
+#include "nvblox/core/types.h"
+
+namespace nvblox {
+// NOTE(gogojjh):
+// image coordinates to pixel indices
+// return u_C.array().floor().cast();
+// image angle order:
+// from left to right: 0 -> 2pi
+// fron bottom to top: phi_min -> phi_max
+class OSLidar {
+ public:
+ __host__ __device__ inline OSLidar() = default;
+ __host__ __device__ inline OSLidar(
+ int num_azimuth_divisions, int num_elevation_divisions,
+ float horizontal_fov_rad, float vertical_fov_rad,
+ float start_azimuth_angle_rad, float end_azimuth_angle_rad,
+ float start_elevation_angle_rad, float end_elevation_angle_rad);
+ __host__ __device__ inline ~OSLidar();
+
+ // __host__ __device__ inline void setIntrinsics();
+ __host__ __device__ inline void printIntrinsics() const;
+
+ // NOTE(gogojjh): This function is used to check whether p_C (in the camera
+ // coordinate) is projected on the the image plane, or outside the
+ // OSLidar's FOV Projects a 3D point to the (floating-point) image plane
+ __host__ __device__ inline bool project(const Vector3f& p_C,
+ Vector2f* u_C) const;
+
+ // Projects a 3D point to the (index-based) image plane
+ __host__ __device__ inline bool project(const Vector3f& p_C,
+ Index2D* u_C) const;
+
+ // Gets the depth of a point
+ __host__ __device__ inline float getDepth(const Vector3f& p_C) const;
+
+ // Gets the normal vector of a point
+ __host__ __device__ inline Vector3f getNormalVector(const Index2D& u_C) const;
+
+ // NOTE(gogojjh): This function is used to unproject a pixel to a 3D point
+ // given the 2D coordinate of an image Back projection (image plane point to
+ // 3D point) represented in the lidar coordinate system
+ __host__ __device__ inline Vector3f unprojectFromImagePlaneCoordinates(
+ const Vector2f& u_C, const float depth) const;
+ __host__ __device__ inline Vector3f unprojectFromPixelIndices(
+ const Index2D& u_C, const float depth) const;
+ __host__ __device__ inline Vector3f unprojectFromImageIndex(
+ const Index2D& u_C) const;
+
+ // Back projection (image plane point to ray)
+ __host__ __device__ inline Vector3f vectorFromImagePlaneCoordinates(
+ const Vector2f& u_C) const;
+ __host__ __device__ inline Vector3f vectorFromPixelIndices(
+ const Index2D& u_C) const;
+
+ // Conversions between pixel indices and image plane coordinates
+ __host__ __device__ inline Vector2f pixelIndexToImagePlaneCoordsOfCenter(
+ const Index2D& u_C) const;
+ __host__ __device__ inline Index2D imagePlaneCoordsToPixelIndex(
+ const Vector2f& u_C) const;
+
+ __host__ __device__ void setDepthFrameCUDA(float* depth_image_ptr_cuda) {
+ depth_image_ptr_cuda_ = depth_image_ptr_cuda;
+ }
+
+ __host__ __device__ void setHeightFrameCUDA(float* height_image_ptr_cuda) {
+ height_image_ptr_cuda_ = height_image_ptr_cuda;
+ }
+
+ __host__ __device__ void setNormalFrameCUDA(float* normal_image_ptr_cuda) {
+ normal_image_ptr_cuda_ = normal_image_ptr_cuda;
+ }
+
+ __host__ __device__ inline float* getDepthFrameCUDA() const {
+ return depth_image_ptr_cuda_;
+ }
+
+ __host__ __device__ inline float* getHeightFrameCUDA() const {
+ return height_image_ptr_cuda_;
+ }
+
+ __host__ __device__ inline float* getNormalFrameCUDA() const {
+ return normal_image_ptr_cuda_;
+ }
+
+ // View
+ __host__ inline AxisAlignedBoundingBox getViewAABB(
+ const Transform& T_L_C, const float min_depth,
+ const float max_depth) const;
+
+ __host__ __device__ inline int num_azimuth_divisions() const;
+ __host__ __device__ inline int num_elevation_divisions() const;
+ __host__ __device__ inline float horizontal_fov_rad() const;
+ __host__ __device__ inline float vertical_fov_rad() const;
+ __host__ __device__ inline float rads_per_pixel_elevation() const;
+ __host__ __device__ inline float rads_per_pixel_azimuth() const;
+ __host__ __device__ inline int numel() const;
+ __host__ __device__ inline int rows() const;
+ __host__ __device__ inline int cols() const;
+
+ // Equality
+ __host__ inline friend bool operator==(const OSLidar& lhs,
+ const OSLidar& rhs);
+
+ // Hash
+ struct Hash {
+ __host__ inline size_t operator()(const OSLidar& OSLidar) const;
+ };
+
+ private:
+ float* depth_image_ptr_cuda_;
+ float* height_image_ptr_cuda_;
+ float* normal_image_ptr_cuda_;
+
+ // Core parameters
+ int num_azimuth_divisions_;
+ int num_elevation_divisions_;
+ float horizontal_fov_rad_;
+ float vertical_fov_rad_;
+
+ // Angular distance between pixels
+ // Note(alexmillane): Note the difference in division by N vs. (N-1) below.
+ // This is because in the azimuth direction there's a wrapping around. The
+ // point at pi/-pi is not double sampled, generating this difference.
+ float start_azimuth_angle_rad_;
+ float end_azimuth_angle_rad_;
+
+ // ********************* elevation_angle
+ // ****** the start elevation_angle indicate the direction: x=0, +z
+ // ****** the end elevation_angle indicate the direction: x=0, -z
+ // ********************* azimuth_angle
+ // ****** the start and end azimuth_angle: clockwise
+ // -x, y=0 -> +x, y=0
+ float start_elevation_angle_rad_;
+ float end_elevation_angle_rad_;
+
+ // Dependent parameters
+ float elevation_pixels_per_rad_;
+ float azimuth_pixels_per_rad_;
+ float rads_per_pixel_elevation_;
+ float rads_per_pixel_azimuth_;
+};
+
+// Equality
+__host__ inline bool operator==(const OSLidar& lhs, const OSLidar& rhs);
+
+} // namespace nvblox
+
+#include "nvblox/core/impl/oslidar_impl.h"
diff --git a/nvblox/include/nvblox/core/types.h b/nvblox/include/nvblox/core/types.h
index 2ac3d1037..295e087f4 100644
--- a/nvblox/include/nvblox/core/types.h
+++ b/nvblox/include/nvblox/core/types.h
@@ -34,9 +34,15 @@ enum class DeviceType { kCPU, kGPU };
enum class MemoryType { kDevice, kUnified, kHost };
inline std::string toString(MemoryType memory_type) {
switch (memory_type) {
- case MemoryType::kDevice: return "kDevice"; break;
- case MemoryType::kUnified: return "kUnified"; break;
- default: return "kHost"; break;
+ case MemoryType::kDevice:
+ return "kDevice";
+ break;
+ case MemoryType::kUnified:
+ return "kUnified";
+ break;
+ default:
+ return "kHost";
+ break;
}
}
@@ -50,14 +56,20 @@ typedef Eigen::AlignedBox3f AxisAlignedBoundingBox;
typedef Eigen::Isometry3f Transform;
+// NOTE(gogojjh): add other types of vectors/matrices
+typedef Eigen::Matrix Vector4f;
+typedef Eigen::Matrix3f Matrix3f;
+typedef Eigen::Matrix4f Matrix4f;
+typedef Eigen::Matrix Matrix3x4f;
+
// This can be replaced with std::byte once we go to C++17.
-typedef uint8_t Byte;
+typedef uint8_t Byte;
// Aligned Eigen containers
template
using AlignedVector = std::vector>;
-enum class InterpolationType { kNearestNeighbor, kLinear};
+enum class InterpolationType { kNearestNeighbor, kLinear };
typedef Eigen::ParametrizedLine Ray;
diff --git a/nvblox/include/nvblox/core/voxels.h b/nvblox/include/nvblox/core/voxels.h
index 02fc8b3a3..007a45ebb 100644
--- a/nvblox/include/nvblox/core/voxels.h
+++ b/nvblox/include/nvblox/core/voxels.h
@@ -26,6 +26,9 @@ struct TsdfVoxel {
float distance = 0.0f;
// How many observations/how confident we are in this observation.
float weight = 0.0f;
+ // ADD(gogojjh): for the implementation of signed distance gradient, its
+ // direction is from the surface toward the sensor // NOLINT
+ Eigen::Vector3f gradient = Eigen::Vector3f::Zero();
};
struct EsdfVoxel {
diff --git a/nvblox/include/nvblox/integrators/internal/cuda/impl/projective_integrators_common_impl.cuh b/nvblox/include/nvblox/integrators/internal/cuda/impl/projective_integrators_common_impl.cuh
index 5352d111e..d825f2243 100644
--- a/nvblox/include/nvblox/integrators/internal/cuda/impl/projective_integrators_common_impl.cuh
+++ b/nvblox/include/nvblox/integrators/internal/cuda/impl/projective_integrators_common_impl.cuh
@@ -18,6 +18,19 @@ limitations under the License.
namespace nvblox {
template
+// NOTE(gogojjh): this function project a voxel in the world onto the camera
+// plane to check whether the voxel is visible or not
+/*
+ * This function projects a voxel in the world onto the camera plane to check
+ whether the voxel is visible or not
+ * @param [in] block_indices_device_ptr:
+ * @param [in] sensor: the sensor object, like the camera, lidar, etc
+ * @param [in] T_C_L: the transformation from the camera to the world
+ * @param [in] block_size:
+ * @param [out] u_px_ptr: the image coordinates projected from the voxel
+ * @param [out] u_depth_ptr: the depth of the 3D points
+ * @param [out] p_voxel_center_C_ptr: the voxel in the world coordinate system
+*/
__device__ inline bool projectThreadVoxel(
const Index3D* block_indices_device_ptr, const SensorType& sensor,
const Transform& T_C_L, const float block_size, Eigen::Vector2f* u_px_ptr,
diff --git a/nvblox/include/nvblox/integrators/internal/cuda/projective_integrators_common.cuh b/nvblox/include/nvblox/integrators/internal/cuda/projective_integrators_common.cuh
index 558ab1503..5e4f6def5 100644
--- a/nvblox/include/nvblox/integrators/internal/cuda/projective_integrators_common.cuh
+++ b/nvblox/include/nvblox/integrators/internal/cuda/projective_integrators_common.cuh
@@ -21,7 +21,6 @@ limitations under the License.
#include "nvblox/core/types.h"
namespace nvblox {
-
/// Camera projection of a voxel onto the image plane.
/// Projects the center of the voxel associated with this GPU block/thread into
/// the image plane. Internally uses threadIdx and blockIdx to select the
diff --git a/nvblox/include/nvblox/integrators/projective_color_integrator.h b/nvblox/include/nvblox/integrators/projective_color_integrator.h
index 2d0227fb7..87eb28161 100644
--- a/nvblox/include/nvblox/integrators/projective_color_integrator.h
+++ b/nvblox/include/nvblox/integrators/projective_color_integrator.h
@@ -49,8 +49,9 @@ class ProjectiveColorIntegrator : public ProjectiveIntegratorBase {
/// intergrated.
/// @param updated_blocks Optional pointer to a vector which will contain the
/// 3D indices of blocks affected by the integration.
+ template
void integrateFrame(const ColorImage& color_frame, const Transform& T_L_C,
- const Camera& camera, const TsdfLayer& tsdf_layer,
+ const CameraType& camera, const TsdfLayer& tsdf_layer,
ColorLayer* color_layer,
std::vector* updated_blocks = nullptr);
@@ -82,10 +83,11 @@ class ProjectiveColorIntegrator : public ProjectiveIntegratorBase {
protected:
// Given a set of blocks in view (block_indices) perform color updates on all
// voxels within these blocks on the GPU.
+ template
void updateBlocks(const std::vector& block_indices,
const ColorImage& color_frame,
const DepthImage& depth_frame, const Transform& T_L_C,
- const Camera& camera, const float truncation_distance_m,
+ const CameraType& camera, const float truncation_distance_m,
ColorLayer* layer);
// Takes a list of block indices and returns a subset containing the block
diff --git a/nvblox/include/nvblox/integrators/projective_integrator_base.h b/nvblox/include/nvblox/integrators/projective_integrator_base.h
index 71c3b5903..32e4e695e 100644
--- a/nvblox/include/nvblox/integrators/projective_integrator_base.h
+++ b/nvblox/include/nvblox/integrators/projective_integrator_base.h
@@ -18,6 +18,7 @@ limitations under the License.
#include
#include "nvblox/core/camera.h"
+#include "nvblox/core/camera_pinhole.h"
#include "nvblox/core/image.h"
#include "nvblox/core/types.h"
#include "nvblox/integrators/view_calculator.h"
@@ -82,7 +83,6 @@ class ProjectiveIntegratorBase {
ViewCalculator& view_calculator();
protected:
-
// Truncation distance in meters for this block size.
float truncation_distance_m(float block_size) const;
diff --git a/nvblox/include/nvblox/integrators/projective_tsdf_integrator.h b/nvblox/include/nvblox/integrators/projective_tsdf_integrator.h
index 4546cff4b..e2970ed2f 100644
--- a/nvblox/include/nvblox/integrators/projective_tsdf_integrator.h
+++ b/nvblox/include/nvblox/integrators/projective_tsdf_integrator.h
@@ -21,6 +21,7 @@ limitations under the License.
#include "nvblox/core/image.h"
#include "nvblox/core/layer.h"
#include "nvblox/core/lidar.h"
+#include "nvblox/core/oslidar.h"
#include "nvblox/core/types.h"
#include "nvblox/core/voxels.h"
#include "nvblox/gpu_hash/gpu_layer_view.h"
@@ -65,6 +66,19 @@ class ProjectiveTsdfIntegrator : public ProjectiveIntegratorBase {
const Lidar& lidar, TsdfLayer* layer,
std::vector* updated_blocks = nullptr);
+ /// Integrates a depth image in to the passed TSDF layer.
+ /// @param depth_frame A depth image.
+ /// @param T_L_C The pose of the camera. Supplied as a Transform mapping
+ /// points in the camera frame (C) to the layer frame (L).
+ /// @param lidar A the Ouster LiDAR model.
+ /// @param layer A pointer to the layer into which this observation will be
+ /// intergrated.
+ /// @param updated_blocks Optional pointer to a vector which will contain the
+ /// 3D indices of blocks affected by the integration.
+ void integrateFrame(DepthImage& depth_frame, const Transform& T_L_C,
+ OSLidar& oslidar, TsdfLayer* layer,
+ std::vector* updated_blocks = nullptr);
+
/// Blocks until GPU operations are complete
/// Ensure outstanding operations are finished (relevant for integrators
/// launching asynchronous work)
@@ -102,12 +116,16 @@ class ProjectiveTsdfIntegrator : public ProjectiveIntegratorBase {
float lidar_linear_interpolation_max_allowable_difference_vox_ = 2.0f;
float lidar_nearest_interpolation_max_allowable_dist_to_ray_vox_ = 0.5f;
+ // NOTE(gogojjh): the main function to implement the GPU-based integration
// Given a set of blocks in view (block_indices) perform TSDF updates on all
// voxels within these blocks on the GPU.
void integrateBlocks(const DepthImage& depth_frame, const Transform& T_C_L,
const Camera& camera, TsdfLayer* layer_ptr);
void integrateBlocks(const DepthImage& depth_frame, const Transform& T_C_L,
const Lidar& lidar, TsdfLayer* layer_ptr);
+ void integrateBlocks(const DepthImage& depth_frame, const Transform& T_C_L,
+ const OSLidar& lidar, TsdfLayer* layer_ptr);
+
template
void integrateBlocksTemplate(const std::vector& block_indices,
const DepthImage& depth_frame,
diff --git a/nvblox/include/nvblox/integrators/view_calculator.h b/nvblox/include/nvblox/integrators/view_calculator.h
index c6afdbd58..abe6a5f78 100644
--- a/nvblox/include/nvblox/integrators/view_calculator.h
+++ b/nvblox/include/nvblox/integrators/view_calculator.h
@@ -18,8 +18,10 @@ limitations under the License.
#include
#include "nvblox/core/camera.h"
+#include "nvblox/core/camera_pinhole.h"
#include "nvblox/core/image.h"
#include "nvblox/core/lidar.h"
+#include "nvblox/core/oslidar.h"
#include "nvblox/core/types.h"
#include "nvblox/core/unified_vector.h"
@@ -43,8 +45,9 @@ class ViewCalculator {
/// @param block_size The size of the blocks in the layer.
/// @param max_distance The maximum distance of blocks considered.
/// @return a vector of the 3D indices of the blocks in view.
+ template
static std::vector getBlocksInViewPlanes(const Transform& T_L_C,
- const Camera& camera,
+ const CameraType& camera,
const float block_size,
const float max_distance);
@@ -61,9 +64,10 @@ class ViewCalculator {
/// @param truncation_distance_m The truncation distance.
/// @param max_integration_distance_m The max integration distance.
/// @return a vector of the 3D indices of the blocks in view.
+ template
static std::vector getBlocksInImageViewPlanes(
const DepthImage& depth_frame, const Transform& T_L_C,
- const Camera& camera, const float block_size,
+ const CameraType& camera, const float block_size,
const float truncation_distance_m,
const float max_integration_distance_m);
@@ -80,9 +84,10 @@ class ViewCalculator {
/// @param truncation_distance_m The truncation distance.
/// @param max_integration_distance_m The max integration distance.
/// @return a vector of the 3D indices of the blocks in view.
+ template
std::vector getBlocksInImageViewRaycast(
const DepthImage& depth_frame, const Transform& T_L_C,
- const Camera& camera, const float block_size,
+ const CameraType& camera, const float block_size,
const float truncation_distance_m,
const float max_integration_distance_m);
@@ -104,6 +109,25 @@ class ViewCalculator {
const float block_size, const float truncation_distance_m,
const float max_integration_distance_m);
+ /// Gets blocks which fall into the lidar view (using a depth image)
+ /// Performs ray casting to get the blocks in view
+ /// Operates by ray through the grid returning the blocks traversed in the ray
+ /// casting process. The number of pixels on the image plane raycast is
+ /// determined by the class parameter raycast_subsampling_factor.
+ /// @param depth_frame the depth image.
+ /// @param T_L_C The pose of the camera. Supplied as a Transform mapping
+ /// points in the camera frame (C) to the layer frame (L).
+ /// @param Lidar The Ouster lidar (intrinsics) model.
+ /// @param block_size The size of the blocks in the layer.
+ /// @param truncation_distance_m The truncation distance.
+ /// @param max_integration_distance_m The max integration distance.
+ /// @return a vector of the 3D indices of the blocks in view.
+ std::vector getBlocksInImageViewRaycast(
+ const DepthImage& depth_frame, const Transform& T_L_C,
+ const OSLidar& lidar, const float block_size,
+ const float truncation_distance_m,
+ const float max_integration_distance_m);
+
/// A parameter getter
/// The rate at which we subsample pixels to raycast. Note that we always
/// raycast the edges of the frame, no matter the subsample rate. For example,
@@ -145,6 +169,7 @@ class ViewCalculator {
bool* aabb_updated_cuda);
// Raycasts through (possibly subsampled) pixels in the image.
+ // aabb_updated_cuda is the updated variables
template
void getBlocksByRaycastingPixels(
const Transform& T_L_C, // NOLINT
diff --git a/nvblox/include/nvblox/io/impl/pointcloud_io_impl.h b/nvblox/include/nvblox/io/impl/pointcloud_io_impl.h
index ff8a40186..56d4b5a35 100644
--- a/nvblox/include/nvblox/io/impl/pointcloud_io_impl.h
+++ b/nvblox/include/nvblox/io/impl/pointcloud_io_impl.h
@@ -21,6 +21,7 @@ limitations under the License.
namespace nvblox {
namespace io {
+/// NOTE(gogojjh): This function outputs TSDF and ESDF map to a file
/// Outputs a voxel layer as a pointcloud with the lambda function deciding the
/// intensity.
template
@@ -32,7 +33,7 @@ bool outputVoxelLayerToPly(
// Combine all the voxels in the mesh into a pointcloud.
std::vector points;
- std::vector intensities;
+ std::vector intensities; // NOTE(gogojjh): record the distance
constexpr int kVoxelsPerSide = VoxelBlock::kVoxelsPerSide;
const float block_size = layer.block_size();
@@ -87,5 +88,64 @@ bool outputVoxelLayerToPly(const EsdfLayer& layer,
return outputVoxelLayerToPly(layer, filename, lambda);
}
+/// NOTE(gogojjh): This function outputs obstance information to a file
+template
+bool outputObstacleToPly(
+ const VoxelBlockLayer& layer, const std::string& filename,
+ std::function lambda) {
+ // Create a ply writer object.
+ io::PlyWriter writer(filename);
+
+ // Combine all the voxels in the mesh into a pointcloud.
+ std::vector points;
+ std::vector intensities;
+
+ constexpr int kVoxelsPerSide = VoxelBlock::kVoxelsPerSide;
+ const float block_size = layer.block_size();
+ const float voxel_size = layer.voxel_size();
+
+ auto new_lambda = [&points, &intensities, &block_size, &voxel_size, &lambda](
+ const Index3D& block_index, const Index3D& voxel_index,
+ const VoxelType* voxel) {
+ float intensity = 0.0f;
+ if (lambda(voxel, &intensity)) {
+ points.push_back(getCenterPostionFromBlockIndexAndVoxelIndex(
+ block_size, block_index, voxel_index));
+ intensities.push_back(intensity);
+ }
+ };
+
+ // Call above lambda on every voxel in the layer.
+ callFunctionOnAllVoxels(layer, new_lambda);
+
+ // Add the obstacle pointcloud to the ply writer.
+ const float OBSTACLE_DISTANCE_TH = 0.02f;
+ std::vector obs_points;
+ obs_points.reserve(points.size() / 10);
+ for (size_t i = 0; i < points.size(); i++) {
+ if (abs(intensities[i]) <= OBSTACLE_DISTANCE_TH) {
+ obs_points.push_back(points[i]);
+ }
+ }
+ writer.setPoints(&obs_points);
+
+ // Write out the ply.
+ return writer.write();
+}
+
+/// Specialization for the obstacle information.
+// template <>
+bool outputObstacleToPly(const EsdfLayer& layer, const std::string& filename) {
+ const float voxel_size = layer.voxel_size();
+ auto lambda = [&voxel_size](const EsdfVoxel* voxel, float* distance) -> bool {
+ *distance = voxel_size * std::sqrt(voxel->squared_distance_vox);
+ if (voxel->is_inside) {
+ *distance = -*distance;
+ }
+ return voxel->observed;
+ };
+ return outputObstacleToPly(layer, filename, lambda);
+}
+
} // namespace io
} // namespace nvblox
\ No newline at end of file
diff --git a/nvblox/include/nvblox/io/pointcloud_io.h b/nvblox/include/nvblox/io/pointcloud_io.h
index 01318728e..098d86b12 100644
--- a/nvblox/include/nvblox/io/pointcloud_io.h
+++ b/nvblox/include/nvblox/io/pointcloud_io.h
@@ -38,6 +38,12 @@ template
bool outputVoxelLayerToPly(const VoxelBlockLayer& layer,
const std::string& filename);
+/// Without specifying a lambda, this outputs the point with smaller distance.
+template
+bool outputObstacleToPly(
+ const VoxelBlockLayer& layer, const std::string& filename,
+ std::function lambda);
+
/// Specializations for the TSDF type.
template <>
bool outputVoxelLayerToPly(const TsdfLayer& layer, const std::string& filename);
@@ -46,6 +52,10 @@ bool outputVoxelLayerToPly(const TsdfLayer& layer, const std::string& filename);
template <>
bool outputVoxelLayerToPly(const EsdfLayer& layer, const std::string& filename);
+/// Specialization for the obstacle information.
+// template <>
+bool outputObstacleToPly(const EsdfLayer& layer, const std::string& filename);
+
} // namespace io
} // namespace nvblox
diff --git a/nvblox/include/nvblox/rays/sphere_tracer.h b/nvblox/include/nvblox/rays/sphere_tracer.h
index 5fd12e00d..ba6a16832 100644
--- a/nvblox/include/nvblox/rays/sphere_tracer.h
+++ b/nvblox/include/nvblox/rays/sphere_tracer.h
@@ -19,6 +19,7 @@ limitations under the License.
#include
#include "nvblox/core/camera.h"
+#include "nvblox/core/camera_pinhole.h"
#include "nvblox/core/common_names.h"
#include "nvblox/core/image.h"
#include "nvblox/gpu_hash/gpu_layer_view.h"
@@ -49,9 +50,10 @@ class SphereTracer {
/// 100x100 pixels, we trace 50x50 pixels and return a syntheric depth image
/// of that size.
/// @returns A pointer to the internal (GPU) buffer where the image is stored.
+ template
std::shared_ptr renderImageOnGPU(
- const Camera& camera, const Transform& T_L_C, const TsdfLayer& tsdf_layer,
- const float truncation_distance_m,
+ const CameraType& camera, const Transform& T_L_C,
+ const TsdfLayer& tsdf_layer, const float truncation_distance_m,
const MemoryType output_image_memory_type = MemoryType::kDevice,
const int ray_subsampling_factor = 1);
diff --git a/nvblox/include/nvblox/utils/timing.h b/nvblox/include/nvblox/utils/timing.h
index 8286e66e3..e7c957e1b 100644
--- a/nvblox/include/nvblox/utils/timing.h
+++ b/nvblox/include/nvblox/utils/timing.h
@@ -201,6 +201,9 @@ class Timing {
static double GetHz(std::string const& tag);
static void Print(std::ostream& out);
static std::string Print();
+ static void Print(std::ostream& out,
+ std::vector const& keywords);
+ static std::string Print(std::vector const& keywords);
static std::string SecondsToTimeString(double seconds);
static void Reset();
static const map_t& GetTimers() { return Instance().tagMap_; }
diff --git a/nvblox/include/nvblox/utils/weight_function.h b/nvblox/include/nvblox/utils/weight_function.h
new file mode 100644
index 000000000..907528424
--- /dev/null
+++ b/nvblox/include/nvblox/utils/weight_function.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include
+
+namespace nvblox {
+
+__host__ __device__ inline float tsdf_constant_weight(const float& sdf) {
+ return 1.0f;
+}
+
+__host__ __device__ inline float tsdf_linear_weight(const float& sdf,
+ const float& trunc) {
+ float epsilon = trunc * 0.5f;
+ float a = 0.5f * trunc / (trunc - epsilon);
+ float b = 0.5f / (trunc - epsilon);
+ if (sdf < epsilon) return 1.0f;
+ if ((epsilon <= sdf) && (sdf <= trunc)) return (a - b * sdf + 0.5f);
+ return 0.5f;
+}
+
+__host__ __device__ inline float tsdf_exp_weight(const float& sdf,
+ const float& trunc) {
+ float epsilon = trunc * 0.5f;
+ if (sdf < epsilon) return 1.0f;
+ if ((epsilon <= sdf) && (sdf <= trunc)) {
+ float d = -trunc * (sdf - epsilon) * (sdf - epsilon);
+ return exp(d);
+ } else {
+ float d = -trunc * (trunc - epsilon) * (trunc - epsilon);
+ return exp(d);
+ }
+}
+
+__host__ __device__ inline float tsdf_sensor_weight(const float& dis,
+ const int m,
+ const float dis_near) {
+ if (dis <= dis_near) {
+ return 1.0f;
+ } else {
+ return 1.0f / pow(dis, m);
+ }
+}
+
+__host__ __device__ inline float tsdf_dropoff_weight(const float& sdf,
+ const float& trunc) {
+ float epsilon = trunc * 0.333f;
+ if (sdf <= -trunc) {
+ return 0.0f;
+ } else if (sdf > -trunc && sdf < -epsilon) {
+ return (trunc + sdf) / (trunc - epsilon);
+ } else {
+ return 1.0f;
+ }
+}
+
+} // namespace nvblox
diff --git a/nvblox/script/run_fuse_3dmatch.sh b/nvblox/script/run_fuse_3dmatch.sh
new file mode 100755
index 000000000..ba225d899
--- /dev/null
+++ b/nvblox/script/run_fuse_3dmatch.sh
@@ -0,0 +1,9 @@
+# /bin/bash
+cd build;
+make && \
+./executables/fuse_3dmatch \
+ /Spy/dataset/3DMatch/sun3d-mit_76_studyroom-76-1studyroom2 \
+ --voxel_size 0.05 \
+ --mesh_output_path /Spy/dataset/3DMatch/sun3d-mit_76_studyroom-76-1studyroom2/mesh_test.ply \
+ --esdf_frame_subsampling 3000 \
+
diff --git a/nvblox/script/run_fuse_fusionportable.sh b/nvblox/script/run_fuse_fusionportable.sh
new file mode 100755
index 000000000..8a1a0c74d
--- /dev/null
+++ b/nvblox/script/run_fuse_fusionportable.sh
@@ -0,0 +1,22 @@
+# /bin/bash
+cd build;
+make && \
+./executables/fuse_fusionportable \
+ /Spy/dataset/mapping_results/nvblox/20220216_garden_day/ \
+ --tsdf_integrator_max_integration_distance_m 70.0 \
+ --color_integrator_max_integration_distance_m 30.0 \
+ --num_frames 10 \
+ --voxel_size 0.1 \
+ --mesh_frame_subsampling 20 \
+ --color_frame_subsampling -1 \
+ --esdf_frame_subsampling 10 \
+ --esdf_mode 1 \
+ --esdf_zmin 0.5 \
+ --esdf_zmax 1.0 \
+ --mesh_output_path \
+ /Spy/dataset/mapping_results/nvblox/20220216_garden_day_mesh_test.ply \
+ --esdf_output_path \
+ /Spy/dataset/mapping_results/nvblox/20220216_garden_day_esdf_test.ply \
+ --obstacle_output_path \
+ /Spy/dataset/mapping_results/nvblox/20220216_garden_day_obs_test.ply \
+
diff --git a/nvblox/script/run_fuse_kitti.sh b/nvblox/script/run_fuse_kitti.sh
new file mode 100755
index 000000000..5de1250a0
--- /dev/null
+++ b/nvblox/script/run_fuse_kitti.sh
@@ -0,0 +1,22 @@
+# /bin/bash
+cd build;
+make && \
+./executables/fuse_kitti \
+ /Spy/dataset/mapping_results/nvblox/2011_09_30_drive_0027_sync/ \
+ --tsdf_integrator_max_integration_distance_m 70.0 \
+ --color_integrator_max_integration_distance_m 30.0 \
+ --num_frames 100 \
+ --voxel_size 0.2 \
+ --mesh_frame_subsampling 20 \
+ --color_frame_subsampling 1 \
+ --esdf_frame_subsampling 10 \
+ --esdf_mode 1 \
+ --esdf_zmin 0.5 \
+ --esdf_zmax 1.0 \
+ --mesh_output_path \
+ /Spy/dataset/mapping_results/nvblox/2011_09_30_drive_0027_sync_mesh_100_weightmethod6.ply \
+ --esdf_output_path \
+ /Spy/dataset/mapping_results/nvblox/2011_09_30_drive_0027_sync_esdf_100_weightmethod6.ply \
+ --obstacle_output_path \
+ /Spy/dataset/mapping_results/nvblox/2011_09_30_drive_0027_sync_obs_100_weightmethod6.ply \
+
diff --git a/nvblox/src/core/camera.cpp b/nvblox/src/core/camera.cpp
index 566fa85c6..4ba9a971c 100644
--- a/nvblox/src/core/camera.cpp
+++ b/nvblox/src/core/camera.cpp
@@ -80,113 +80,4 @@ Eigen::Matrix Camera::getViewCorners(const float min_depth,
corners_C.row(7) = max_depth * ray_3_C;
return corners_C;
}
-
-// Frustum definitions.
-Frustum::Frustum(const Camera& camera, const Transform& T_L_C, float min_depth,
- float max_depth) {
- Eigen::Matrix corners_C =
- camera.getViewCorners(min_depth, max_depth);
- computeBoundingPlanes(corners_C, T_L_C);
-}
-
-void Frustum::computeBoundingPlanes(const Eigen::Matrix& corners_C,
- const Transform& T_L_C) {
- // Transform the corners.
- const Eigen::Matrix corners_L =
- (T_L_C * corners_C.transpose()).transpose();
-
- // Near plane first.
- bounding_planes_L_[0].setFromPoints(corners_L.row(0), corners_L.row(2),
- corners_L.row(1));
- // Far plane.
- bounding_planes_L_[1].setFromPoints(corners_L.row(4), corners_L.row(5),
- corners_L.row(6));
- // Left.
- bounding_planes_L_[2].setFromPoints(corners_L.row(3), corners_L.row(7),
- corners_L.row(6));
- // Right.
- bounding_planes_L_[3].setFromPoints(corners_L.row(0), corners_L.row(5),
- corners_L.row(4));
- // Top.
- bounding_planes_L_[4].setFromPoints(corners_L.row(3), corners_L.row(4),
- corners_L.row(7));
- // Bottom.
- bounding_planes_L_[5].setFromPoints(corners_L.row(2), corners_L.row(6),
- corners_L.row(5));
-
- // Calculate AABB.
- Vector3f aabb_min, aabb_max;
-
- aabb_min.setConstant(std::numeric_limits::max());
- aabb_max.setConstant(std::numeric_limits::lowest());
-
- for (int i = 0; i < corners_L.cols(); i++) {
- for (size_t j = 0; j < corners_L.rows(); j++) {
- aabb_min(i) = std::min(aabb_min(i), corners_L(j, i));
- aabb_max(i) = std::max(aabb_max(i), corners_L(j, i));
- }
- }
-
- aabb_ = AxisAlignedBoundingBox(aabb_min, aabb_max);
-}
-
-bool Frustum::isPointInView(const Vector3f& point) const {
- // Skip the AABB check, assume already been done.
- for (size_t i = 0; i < bounding_planes_L_.size(); i++) {
- if (!bounding_planes_L_[i].isPointInside(point)) {
- return false;
- }
- }
- return true;
-}
-
-bool Frustum::isAABBInView(const AxisAlignedBoundingBox& aabb) const {
- // If we're not even close, don't bother checking the planes.
- if (!aabb_.intersects(aabb)) {
- return false;
- }
- constexpr int kNumCorners = 8;
-
- // Check the center of the bounding box to see if it's within the AABB.
- // This covers a corner case where the given AABB is larger than the
- // frustum.
- if (isPointInView(aabb.center())) {
- return true;
- }
-
- // Iterate over all the corners of the bounding box and see if any are
- // within the view frustum.
- for (int i = 0; i < kNumCorners; i++) {
- if (isPointInView(
- aabb.corner(static_cast(i)))) {
- return true;
- }
- }
- return false;
-}
-
-// Bounding plane definitions.
-void BoundingPlane::setFromPoints(const Vector3f& p1, const Vector3f& p2,
- const Vector3f& p3) {
- Vector3f p1p2 = p2 - p1;
- Vector3f p1p3 = p3 - p1;
-
- Vector3f cross = p1p2.cross(p1p3);
- normal_ = cross.normalized();
- distance_ = normal_.dot(p1);
-}
-
-void BoundingPlane::setFromDistanceNormal(const Vector3f& normal,
- float distance) {
- normal_ = normal;
- distance_ = distance;
-}
-
-bool BoundingPlane::isPointInside(const Vector3f& point) const {
- if (point.dot(normal_) >= distance_) {
- return true;
- }
- return false;
-}
-
} // namespace nvblox
diff --git a/nvblox/src/core/camera_pinhole.cpp b/nvblox/src/core/camera_pinhole.cpp
new file mode 100644
index 000000000..5dd86d6bc
--- /dev/null
+++ b/nvblox/src/core/camera_pinhole.cpp
@@ -0,0 +1,93 @@
+/*
+Copyright 2022 NVIDIA CORPORATION
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+#include "nvblox/core/camera_pinhole.h"
+
+namespace nvblox {
+
+std::ostream& operator<<(std::ostream& os, const CameraPinhole& camera) {
+ if (camera.isRectified()) {
+ os << "camera with intrinsics:\n\t"
+ << "\trectified: " << camera.isRectified() << "\n"
+ << "\tP: " << camera.P() << "\n"
+ << "\tRect: " << camera.Rect() << "\n"
+ << "\twidith: " << camera.width() << "\n"
+ << "\theight: " << camera.height() << "\n";
+ } else {
+ os << "camera with intrinsics:\n\t"
+ << "\trectified: " << camera.isRectified() << "\n"
+ << "\tK: " << camera.K() << "\n"
+ << "\twidith: " << camera.width() << "\n"
+ << "\theight: " << camera.height() << "\n";
+ }
+ return os;
+}
+
+AxisAlignedBoundingBox CameraPinhole::getViewAABB(const Transform& T_L_C,
+ const float min_depth,
+ const float max_depth) const {
+ // Get the bounding corners of this view.
+ Eigen::Matrix corners_C = getViewCorners(min_depth, max_depth);
+
+ Vector3f aabb_min, aabb_max;
+ aabb_min.setConstant(std::numeric_limits::max());
+ aabb_max.setConstant(std::numeric_limits::lowest());
+
+ // Transform it into the layer coordinate frame.
+ for (size_t i = 0; i < corners_C.rows(); i++) {
+ const Vector3f& corner_C = corners_C.row(i);
+ Vector3f corner_L = T_L_C * corner_C;
+ for (int i = 0; i < 3; i++) {
+ aabb_min(i) = std::min(aabb_min(i), corner_L(i));
+ aabb_max(i) = std::max(aabb_max(i), corner_L(i));
+ }
+ }
+
+ return AxisAlignedBoundingBox(aabb_min, aabb_max);
+}
+
+Frustum CameraPinhole::getViewFrustum(const Transform& T_L_C,
+ const float min_depth,
+ const float max_depth) const {
+ return Frustum(*this, T_L_C, min_depth, max_depth);
+}
+
+Eigen::Matrix CameraPinhole::getViewCorners(
+ const float min_depth, const float max_depth) const {
+ // Rays through the corners of the image plane
+ // Clockwise from the top left corner of the image.
+ const Vector3f ray_0_C =
+ vectorFromImagePlaneCoordinates(Vector2f(0.0f, 0.0f)); // NOLINT
+ const Vector3f ray_1_C =
+ vectorFromImagePlaneCoordinates(Vector2f(width_, 0.0f)); // NOLINT
+ const Vector3f ray_2_C =
+ vectorFromImagePlaneCoordinates(Vector2f(width_, height_)); // NOLINT
+ const Vector3f ray_3_C =
+ vectorFromImagePlaneCoordinates(Vector2f(0.0f, height_)); // NOLINT
+
+ // True bounding box from the 3D points
+ Eigen::Matrix corners_C;
+ corners_C.row(0) = min_depth * ray_2_C;
+ corners_C.row(1) = min_depth * ray_1_C;
+ corners_C.row(2) = min_depth * ray_0_C,
+ corners_C.row(3) = min_depth * ray_3_C;
+ corners_C.row(4) = max_depth * ray_2_C;
+ corners_C.row(5) = max_depth * ray_1_C;
+ corners_C.row(6) = max_depth * ray_0_C;
+ corners_C.row(7) = max_depth * ray_3_C;
+ return corners_C;
+}
+
+} // namespace nvblox
diff --git a/nvblox/src/core/cuda/image_operation.cu b/nvblox/src/core/cuda/image_operation.cu
new file mode 100644
index 000000000..c1fcfe8e9
--- /dev/null
+++ b/nvblox/src/core/cuda/image_operation.cu
@@ -0,0 +1,119 @@
+#include "nvblox/core/cuda/error_check.cuh"
+#include "nvblox/core/cuda/image_operation.h"
+
+namespace nvblox {
+namespace cuda {
+__global__ void computeNormalImageOSLidar(
+ const float* depth_image, const float* height_image, float* normal_image,
+ const int w, const int h, const float rads_per_pixel_azimuth,
+ const float rads_per_pixel_elevation) {
+ const float end_azimuth_rad = 3.1415926535f;
+ const int tid = blockIdx.x * blockDim.x + threadIdx.x;
+ int u_stride = blockDim.x;
+ int v_stride = 1;
+ for (int u = tid; u < w; u += u_stride) {
+ for (int v = 0; v < h; v += v_stride) {
+ normal_image[3 * (v * w + u)] = 0.0f;
+ normal_image[3 * (v * w + u) + 1] = 0.0f;
+ normal_image[3 * (v * w + u) + 2] = 0.0f;
+
+ float sign = 1.0f;
+ int uu, vv;
+ if (u == w - 1) {
+ uu = 0;
+ } else {
+ uu = u + 1;
+ }
+ if (v == h - 1) {
+ vv = 0;
+ sign *= -1.0f;
+ } else {
+ vv = v + 1;
+ }
+
+ float d = image::access(v, u, w, depth_image);
+ float d1 = image::access(v, uu, w, depth_image);
+ float d2 = image::access(vv, u, w, depth_image);
+ // on the boundary, not continous
+ if (abs(d - d1) > 0.9 * d) continue;
+ // on the boundary, not continous
+ if (abs(d - d2) > 0.9 * d) continue;
+
+ float px, py, pz;
+ float px1, py1, pz1;
+ float px2, py2, pz2;
+
+ {
+ float depth = image::access(v, u, w, depth_image);
+ float height = image::access(v, u, w, height_image);
+ float r = sqrt(depth * depth - height * height);
+ float azimuth_angle_rad = end_azimuth_rad - u * rads_per_pixel_azimuth;
+ px = r * cos(azimuth_angle_rad);
+ py = r * sin(azimuth_angle_rad);
+ pz = height;
+ }
+
+ {
+ float depth = image::access(v, uu, w, depth_image);
+ float height = image::access(v, uu, w, height_image);
+ float r = sqrt(depth * depth - height * height);
+ float azimuth_angle_rad = end_azimuth_rad - uu * rads_per_pixel_azimuth;
+ px1 = r * cos(azimuth_angle_rad);
+ py1 = r * sin(azimuth_angle_rad);
+ pz1 = height;
+ }
+
+ {
+ float depth = image::access(vv, u, w, depth_image);
+ float height = image::access(vv, u, w, height_image);
+ float r = sqrt(depth * depth - height * height);
+ float azimuth_angle_rad = end_azimuth_rad - u * rads_per_pixel_azimuth;
+ px2 = r * cos(azimuth_angle_rad);
+ py2 = r * sin(azimuth_angle_rad);
+ pz2 = height;
+ }
+
+ float nx, ny, nz;
+ {
+ nx = sign * (py - py2) * (pz - pz1) - (py - py1) * (pz - pz2);
+ ny = sign * (px - px1) * (pz - pz2) - (px - px2) * (pz - pz1);
+ nz = sign * (px - px2) * (py - py1) - (px - px1) * (py - py2);
+ float l = sqrt(nx * nx + ny * ny + nz * nz);
+ if (l == 0.0f) {
+ continue;
+ } else {
+ nx /= l;
+ ny /= l;
+ nz /= l;
+ }
+ // printf("%f %f, %f, %f\n", l, nx, ny, nz);
+ }
+ normal_image[3 * (v * w + u)] = nx;
+ normal_image[3 * (v * w + u) + 1] = ny;
+ normal_image[3 * (v * w + u) + 2] = nz;
+ }
+ }
+}
+
+// OSLidar
+void getNormalImageOSLidar(OSLidar& lidar) {
+ int w = lidar.num_azimuth_divisions();
+ int h = lidar.num_elevation_divisions();
+ float* normal_frame_cuda;
+ checkCudaErrors(cudaMalloc(&normal_frame_cuda, sizeof(float) * w * h * 3));
+ int block_size = idivup(w, 2);
+ int grid_size = 1;
+ computeNormalImageOSLidar<<>>(
+ lidar.getDepthFrameCUDA(), lidar.getHeightFrameCUDA(), normal_frame_cuda,
+ lidar.num_azimuth_divisions(), lidar.num_elevation_divisions(),
+ lidar.rads_per_pixel_azimuth(), lidar.rads_per_pixel_elevation());
+ lidar.setNormalFrameCUDA(normal_frame_cuda);
+}
+
+void freeNormalImageOSLidar(OSLidar& lidar) {
+ float* normal_image = lidar.getNormalFrameCUDA();
+ checkCudaErrors(cudaFree(normal_image));
+}
+
+} // namespace cuda
+} // namespace nvblox
\ No newline at end of file
diff --git a/nvblox/src/core/frustum.cpp b/nvblox/src/core/frustum.cpp
new file mode 100644
index 000000000..6df5c8a5f
--- /dev/null
+++ b/nvblox/src/core/frustum.cpp
@@ -0,0 +1,120 @@
+#include "nvblox/core/frustum.h"
+#include "nvblox/core/camera.h"
+#include "nvblox/core/camera_pinhole.h"
+
+namespace nvblox {
+
+template Frustum::Frustum(const Camera& camera, const Transform& T_L_C,
+ float min_depth, float max_depth);
+template Frustum::Frustum(const CameraPinhole& camera, const Transform& T_L_C,
+ float min_depth, float max_depth);
+
+// Frustum definitions.
+template
+Frustum::Frustum(const CameraType& camera, const Transform& T_L_C,
+ float min_depth, float max_depth) {
+ Eigen::Matrix