mirror of
https://github.com/introlab/rtabmap.git
synced 2026-10-04 00:57:46 +08:00
finished testing util3d_mapping.hpp
This commit is contained in:
@@ -85,7 +85,8 @@ void segmentObstaclesFromGround(
|
|||||||
viewPoint,
|
viewPoint,
|
||||||
groundNormalsUp);
|
groundNormalsUp);
|
||||||
|
|
||||||
if(segmentFlatObstacles && flatSurfaces->size())
|
if(flatSurfaces->size() &&
|
||||||
|
(segmentFlatObstacles || maxGroundHeight != 0.0f || minClusterSize>1))
|
||||||
{
|
{
|
||||||
int biggestFlatSurfaceIndex;
|
int biggestFlatSurfaceIndex;
|
||||||
std::vector<pcl::IndicesPtr> clusteredFlatSurfaces = extractClusters(
|
std::vector<pcl::IndicesPtr> clusteredFlatSurfaces = extractClusters(
|
||||||
@@ -135,7 +136,9 @@ void segmentObstaclesFromGround(
|
|||||||
{
|
{
|
||||||
Eigen::Vector4f centroid(0,0,0,1);
|
Eigen::Vector4f centroid(0,0,0,1);
|
||||||
pcl::compute3DCentroid(*cloud, *clusteredFlatSurfaces.at(i), centroid);
|
pcl::compute3DCentroid(*cloud, *clusteredFlatSurfaces.at(i), centroid);
|
||||||
if(maxGroundHeight==0.0f || centroid[2] <= maxGroundHeight || centroid[2] <= biggestSurfaceMax[2]) // epsilon
|
if((maxGroundHeight!=0.0f && centroid[2] <= maxGroundHeight) ||
|
||||||
|
centroid[2] <= biggestSurfaceMax[2] ||
|
||||||
|
(maxGroundHeight==0.0f && !segmentFlatObstacles))
|
||||||
{
|
{
|
||||||
ground = util3d::concatenate(ground, clusteredFlatSurfaces.at(i));
|
ground = util3d::concatenate(ground, clusteredFlatSurfaces.at(i));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,11 +311,54 @@ cv::Mat RTABMAP_CORE_EXPORT convertImage8U2Map(const cv::Mat & map8U, bool pgmFo
|
|||||||
*/
|
*/
|
||||||
cv::Mat RTABMAP_CORE_EXPORT erodeMap(const cv::Mat & map);
|
cv::Mat RTABMAP_CORE_EXPORT erodeMap(const cv::Mat & map);
|
||||||
|
|
||||||
|
// templated methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Projects a point cloud onto the XY plane by setting all Z coordinates to zero.
|
||||||
|
*
|
||||||
|
* This function creates a copy of the input point cloud and modifies each point's Z coordinate
|
||||||
|
* to be zero, effectively projecting the entire cloud onto the XY plane.
|
||||||
|
*
|
||||||
|
* @tparam PointT The type of point used in the point cloud (e.g., pcl::PointXYZ).
|
||||||
|
* @param cloud The input point cloud to project.
|
||||||
|
* @return typename pcl::PointCloud<PointT>::Ptr A pointer to the projected point cloud
|
||||||
|
* with Z coordinates set to zero.
|
||||||
|
*/
|
||||||
template<typename PointT>
|
template<typename PointT>
|
||||||
typename pcl::PointCloud<PointT>::Ptr projectCloudOnXYPlane(
|
typename pcl::PointCloud<PointT>::Ptr projectCloudOnXYPlane(
|
||||||
const typename pcl::PointCloud<PointT> & cloud);
|
const typename pcl::PointCloud<PointT> & cloud);
|
||||||
|
|
||||||
// templated methods
|
|
||||||
|
/**
|
||||||
|
* @brief Segments ground and obstacle indices from a point cloud using surface normals and clustering.
|
||||||
|
*
|
||||||
|
* This function analyzes a point cloud to identify flat surfaces (e.g., ground) and separates them
|
||||||
|
* from potential obstacles based on normal orientation, height constraints, and optional clustering.
|
||||||
|
* Optionally, flat obstacles (e.g., tables, ramps) can be segmented separately.
|
||||||
|
*
|
||||||
|
* @tparam PointT The type of point used in the point cloud (e.g., pcl::PointXYZ).
|
||||||
|
*
|
||||||
|
* @param cloud The input point cloud.
|
||||||
|
* @param indices Optional input indices to consider from the cloud (e.g., from a prior ROI extraction).
|
||||||
|
* @param ground Output pointer where indices corresponding to ground points will be stored.
|
||||||
|
* @param obstacles Output pointer where indices corresponding to obstacle points will be stored.
|
||||||
|
* @param normalKSearch Number of neighbors to use for normal estimation.
|
||||||
|
* @param groundNormalAngle Maximum angle (in radians) between the estimated normal and the "up" direction
|
||||||
|
* for a surface to be considered ground.
|
||||||
|
* @param clusterRadius The Euclidean distance threshold for clustering flat surfaces and obstacles.
|
||||||
|
* @param minClusterSize The minimum number of points required to form a valid cluster.
|
||||||
|
* @param segmentFlatObstacles If true, flat but non-ground surfaces (e.g., tables) are detected and
|
||||||
|
* optionally returned via `flatObstacles`.
|
||||||
|
* @param maxGroundHeight Maximum Z-height for a surface to be considered ground (0 disables filtering).
|
||||||
|
* Note that all obstacle points under that threshold will be ignored (i.e., won't
|
||||||
|
* be returned in `obstacles`).
|
||||||
|
* @param flatObstacles Optional output pointer where indices corresponding to flat obstacles will be stored
|
||||||
|
* (only valid if `segmentFlatObstacles` is true).
|
||||||
|
* @param viewPoint The viewpoint to use for normal estimation (important for consistent orientation).
|
||||||
|
* @param groundNormalsUp Threshold (between 0 and 1) used to detect and flip ground-facing normals
|
||||||
|
* (set to 0.0f to disable). If the Z component of a normal is less than `-groundNormalsUp`
|
||||||
|
* and the corresponding point is below the viewpoint, the normal will be flipped.
|
||||||
|
*/
|
||||||
template<typename PointT>
|
template<typename PointT>
|
||||||
void segmentObstaclesFromGround(
|
void segmentObstaclesFromGround(
|
||||||
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
||||||
@@ -331,6 +374,10 @@ void segmentObstaclesFromGround(
|
|||||||
pcl::IndicesPtr * flatObstacles = 0,
|
pcl::IndicesPtr * flatObstacles = 0,
|
||||||
const Eigen::Vector4f & viewPoint = Eigen::Vector4f(0,0,100,0),
|
const Eigen::Vector4f & viewPoint = Eigen::Vector4f(0,0,100,0),
|
||||||
float groundNormalsUp = 0);
|
float groundNormalsUp = 0);
|
||||||
|
/**
|
||||||
|
* @brief Segments ground and obstacle indices from a point cloud using surface normals and clustering.
|
||||||
|
* @see `segmentObstaclesFromGround()` with indices
|
||||||
|
*/
|
||||||
template<typename PointT>
|
template<typename PointT>
|
||||||
void segmentObstaclesFromGround(
|
void segmentObstaclesFromGround(
|
||||||
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
||||||
@@ -346,6 +393,33 @@ void segmentObstaclesFromGround(
|
|||||||
const Eigen::Vector4f & viewPoint = Eigen::Vector4f(0,0,100,0),
|
const Eigen::Vector4f & viewPoint = Eigen::Vector4f(0,0,100,0),
|
||||||
float groundNormalsUp = 0);
|
float groundNormalsUp = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Projects 3D ground and obstacle point clouds onto the 2D XY plane and voxelizes them into 2D occupancy data.
|
||||||
|
*
|
||||||
|
* This function takes two 3D point clouds—representing ground and obstacles—and performs the following steps:
|
||||||
|
* - Projects them onto the XY plane (setting Z = 0).
|
||||||
|
* - Voxelizes them based on the specified cell size.
|
||||||
|
* - Converts the resulting 2D points into OpenCV matrices (1-row, N-columns, `CV_32FC2`) where each element is a (x, y) coordinate.
|
||||||
|
*
|
||||||
|
* @tparam PointT The type of point in the input point clouds (e.g., pcl::PointXYZ).
|
||||||
|
*
|
||||||
|
* @param groundCloud The input point cloud representing ground points.
|
||||||
|
* @param obstaclesCloud The input point cloud representing obstacle points.
|
||||||
|
* @param ground Output matrix containing 2D (x, y) coordinates of projected ground points (type: `CV_32FC2`).
|
||||||
|
* @param obstacles Output matrix containing 2D (x, y) coordinates of projected obstacle points (type: `CV_32FC2`).
|
||||||
|
* @param cellSize The size of each voxel/grid cell used for downsampling the projected cloud (in meters).
|
||||||
|
*/
|
||||||
|
template<typename PointT>
|
||||||
|
void occupancy2DFromGroundObstacles(
|
||||||
|
const typename pcl::PointCloud<PointT>::Ptr & groundCloud,
|
||||||
|
const typename pcl::PointCloud<PointT>::Ptr & obstaclesCloud,
|
||||||
|
cv::Mat & ground,
|
||||||
|
cv::Mat & obstacles,
|
||||||
|
float cellSize);
|
||||||
|
/**
|
||||||
|
* @brief Projects 3D ground and obstacle point clouds onto the 2D XY plane and voxelizes them into 2D occupancy data.
|
||||||
|
* @see occupancy2DFromGroundObstacles() without indices
|
||||||
|
*/
|
||||||
template<typename PointT>
|
template<typename PointT>
|
||||||
void occupancy2DFromGroundObstacles(
|
void occupancy2DFromGroundObstacles(
|
||||||
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
||||||
@@ -355,17 +429,36 @@ void occupancy2DFromGroundObstacles(
|
|||||||
cv::Mat & obstacles,
|
cv::Mat & obstacles,
|
||||||
float cellSize);
|
float cellSize);
|
||||||
|
|
||||||
template<typename PointT>
|
/**
|
||||||
void occupancy2DFromGroundObstacles(
|
* @brief Generates 2D ground and obstacle occupancy data from a 3D point cloud.
|
||||||
const typename pcl::PointCloud<PointT>::Ptr & groundCloud,
|
*
|
||||||
const typename pcl::PointCloud<PointT>::Ptr & obstaclesCloud,
|
* This function performs segmentation on a 3D point cloud to separate ground and obstacle points
|
||||||
cv::Mat & ground,
|
* based on normal orientation and clustering, then projects both onto the XY plane to create
|
||||||
cv::Mat & obstacles,
|
* 2D occupancy representations using voxelization.
|
||||||
float cellSize);
|
*
|
||||||
|
* The resulting occupancy data is returned as two OpenCV matrices (`cv::Mat`) of type `CV_32FC2`,
|
||||||
|
* where each entry contains a 2D point (x, y) in meters corresponding to a ground or obstacle voxel.
|
||||||
|
*
|
||||||
|
* @tparam PointT The type of point used in the input point cloud (e.g., pcl::PointXYZ).
|
||||||
|
*
|
||||||
|
* @param cloud The input 3D point cloud.
|
||||||
|
* @param indices Optional subset of points from the cloud to use for processing (can be full cloud indices).
|
||||||
|
* @param ground Output matrix containing 2D ground points projected and voxelized (`CV_32FC2`).
|
||||||
|
* @param obstacles Output matrix containing 2D obstacle points projected and voxelized (`CV_32FC2`).
|
||||||
|
* @param cellSize The voxel size (in meters) for projecting and grouping points in 2D.
|
||||||
|
* @param groundNormalAngle Maximum allowable angle (in radians) between a point's normal and the vertical axis for it to be considered part of the ground.
|
||||||
|
* @param minClusterSize Minimum number of points required to form a valid obstacle cluster.
|
||||||
|
* @param segmentFlatObstacles Whether to separate flat horizontal surfaces (e.g., tables) from the ground and treat them as obstacles.
|
||||||
|
* @param maxGroundHeight Maximum Z value (in meters) for a surface to be considered ground. If 0, height filtering is disabled.
|
||||||
|
*
|
||||||
|
* @note Internally, this function calls:
|
||||||
|
* - `segmentObstaclesFromGround()` to classify ground vs. obstacle points.
|
||||||
|
* - `occupancy2DFromGroundObstacles()` to project and voxelize the classified data.
|
||||||
|
*/
|
||||||
template<typename PointT>
|
template<typename PointT>
|
||||||
void occupancy2DFromCloud3D(
|
void occupancy2DFromCloud3D(
|
||||||
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
||||||
|
const pcl::IndicesPtr & indices,
|
||||||
cv::Mat & ground,
|
cv::Mat & ground,
|
||||||
cv::Mat & obstacles,
|
cv::Mat & obstacles,
|
||||||
float cellSize = 0.05f,
|
float cellSize = 0.05f,
|
||||||
@@ -373,10 +466,13 @@ void occupancy2DFromCloud3D(
|
|||||||
int minClusterSize = 20,
|
int minClusterSize = 20,
|
||||||
bool segmentFlatObstacles = false,
|
bool segmentFlatObstacles = false,
|
||||||
float maxGroundHeight = 0.0f);
|
float maxGroundHeight = 0.0f);
|
||||||
|
/**
|
||||||
|
* @brief Generates 2D ground and obstacle occupancy data from a 3D point cloud.
|
||||||
|
* @see `occupancy2DFromCloud3D()` with indices
|
||||||
|
*/
|
||||||
template<typename PointT>
|
template<typename PointT>
|
||||||
void occupancy2DFromCloud3D(
|
void occupancy2DFromCloud3D(
|
||||||
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
const typename pcl::PointCloud<PointT>::Ptr & cloud,
|
||||||
const pcl::IndicesPtr & indices,
|
|
||||||
cv::Mat & ground,
|
cv::Mat & ground,
|
||||||
cv::Mat & obstacles,
|
cv::Mat & obstacles,
|
||||||
float cellSize = 0.05f,
|
float cellSize = 0.05f,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include "rtabmap/core/util3d.h"
|
#include "rtabmap/core/util3d.h"
|
||||||
#include "rtabmap/core/util3d_mapping.h"
|
#include "rtabmap/core/util3d_mapping.h"
|
||||||
|
#include "rtabmap/core/util3d_surface.h"
|
||||||
#include "rtabmap/core/CameraModel.h"
|
#include "rtabmap/core/CameraModel.h"
|
||||||
#include "rtabmap/utilite/UException.h"
|
#include "rtabmap/utilite/UException.h"
|
||||||
#include "rtabmap/utilite/UConversion.h"
|
#include "rtabmap/utilite/UConversion.h"
|
||||||
@@ -418,4 +419,264 @@ TEST(Util3dMapping, erodeMapNoErosionWithUnknown)
|
|||||||
|
|
||||||
// Obstacle should NOT be eroded because adjacent unknown cell (-1)
|
// Obstacle should NOT be eroded because adjacent unknown cell (-1)
|
||||||
EXPECT_EQ(erodedMap.at<signed char>(1,1), 100);
|
EXPECT_EQ(erodedMap.at<signed char>(1,1), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Util3dMapping, projectCloudOnXYPlaneZCoordinatesAreZero)
|
||||||
|
{
|
||||||
|
// Create a test point cloud
|
||||||
|
pcl::PointCloud<pcl::PointXYZ>::Ptr input_cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
||||||
|
input_cloud->push_back(pcl::PointXYZ(1.0, 2.0, 3.0));
|
||||||
|
input_cloud->push_back(pcl::PointXYZ(4.0, 5.0, -1.0));
|
||||||
|
input_cloud->push_back(pcl::PointXYZ(0.0, 0.0, 10.0));
|
||||||
|
|
||||||
|
// Project the cloud
|
||||||
|
auto projected_cloud = util3d::projectCloudOnXYPlane<pcl::PointXYZ>(*input_cloud);
|
||||||
|
|
||||||
|
// Check that the size remains the same
|
||||||
|
ASSERT_EQ(projected_cloud->size(), input_cloud->size());
|
||||||
|
|
||||||
|
// Check that x and y remain the same, and z is set to zero
|
||||||
|
for (size_t i = 0; i < projected_cloud->size(); ++i)
|
||||||
|
{
|
||||||
|
EXPECT_FLOAT_EQ(projected_cloud->at(i).x, input_cloud->at(i).x);
|
||||||
|
EXPECT_FLOAT_EQ(projected_cloud->at(i).y, input_cloud->at(i).y);
|
||||||
|
EXPECT_FLOAT_EQ(projected_cloud->at(i).z, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Util3dMapping, segmentObstaclesFromGround)
|
||||||
|
{
|
||||||
|
// Create a cloud of a floor, then elevate some part of it to make a flat obstacle
|
||||||
|
pcl::IndicesPtr expected_ground(new std::vector<int>);
|
||||||
|
pcl::IndicesPtr expected_big_obstacles(new std::vector<int>);
|
||||||
|
pcl::IndicesPtr expected_small_obstacles(new std::vector<int>);
|
||||||
|
pcl::IndicesPtr expected_flat_obstacles(new std::vector<int>);
|
||||||
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
||||||
|
for(int i=0; i<10; ++i) {
|
||||||
|
for(int j=0; j<10; ++j) {
|
||||||
|
if(i>5 && j>5) {
|
||||||
|
expected_flat_obstacles->push_back(cloud->size());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
expected_ground->push_back(cloud->size());
|
||||||
|
}
|
||||||
|
cloud->push_back(pcl::PointXYZ(0.05f*i, 0.05f*j, 0.01f*i+(i>5 && j>5 ? 0.25f:0.0f)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add a wall and a small obstacle
|
||||||
|
for(int i=0; i<10; ++i) {
|
||||||
|
for(int k=0; k<10; ++k) {
|
||||||
|
if(i>5 && k>5) {
|
||||||
|
expected_small_obstacles->push_back(cloud->size());
|
||||||
|
cloud->push_back(pcl::PointXYZ(0.05f*i, -0.35f, 0.05f*k));
|
||||||
|
}
|
||||||
|
expected_big_obstacles->push_back(cloud->size());
|
||||||
|
cloud->push_back(pcl::PointXYZ(0.05f*i, -0.15f, 0.05f*k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pcl::IndicesPtr ground, obstacles, flatObs;
|
||||||
|
|
||||||
|
// test basic
|
||||||
|
float normalKSearch = 5;
|
||||||
|
float angleMax = 20.0f*M_PI/180.0f; // Allow 20 degrees of deviation
|
||||||
|
float clusterRadius = 0.1f;
|
||||||
|
util3d::segmentObstaclesFromGround<pcl::PointXYZ>(
|
||||||
|
cloud,
|
||||||
|
ground,
|
||||||
|
obstacles,
|
||||||
|
normalKSearch,
|
||||||
|
angleMax,
|
||||||
|
clusterRadius,
|
||||||
|
1, // minClusterSize
|
||||||
|
false, // segmentFlatObstacles
|
||||||
|
0.0f, // maxGroundHeight
|
||||||
|
&flatObs, // flatObstacles
|
||||||
|
Eigen::Vector4f(0,0,2,0), // viewpoint
|
||||||
|
0.8f // groundNormalsUp
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_EQ(ground->size(), expected_ground->size() + expected_flat_obstacles->size());
|
||||||
|
ASSERT_EQ(obstacles->size(), expected_big_obstacles->size() + expected_small_obstacles->size());
|
||||||
|
|
||||||
|
// test indices
|
||||||
|
util3d::segmentObstaclesFromGround<pcl::PointXYZ>(
|
||||||
|
cloud,
|
||||||
|
expected_ground,
|
||||||
|
ground,
|
||||||
|
obstacles,
|
||||||
|
normalKSearch,
|
||||||
|
angleMax,
|
||||||
|
clusterRadius,
|
||||||
|
1, // minClusterSize
|
||||||
|
false, // segmentFlatObstacles
|
||||||
|
0.0f, // maxGroundHeight
|
||||||
|
&flatObs, // flatObstacles
|
||||||
|
Eigen::Vector4f(0,0,2,0), // viewpoint
|
||||||
|
0.8f // groundNormalsUp
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_EQ(ground->size(), expected_ground->size());
|
||||||
|
ASSERT_EQ(obstacles->size(), 0);
|
||||||
|
|
||||||
|
// test flat obstacles
|
||||||
|
util3d::segmentObstaclesFromGround<pcl::PointXYZ>(
|
||||||
|
cloud,
|
||||||
|
ground,
|
||||||
|
obstacles,
|
||||||
|
normalKSearch,
|
||||||
|
angleMax,
|
||||||
|
clusterRadius,
|
||||||
|
1, // minClusterSize
|
||||||
|
true, // segmentFlatObstacles
|
||||||
|
0.0f, // maxGroundHeight
|
||||||
|
&flatObs, // flatObstacles
|
||||||
|
Eigen::Vector4f(0,0,2,0), // viewpoint
|
||||||
|
0.8f // groundNormalsUp
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_EQ(flatObs->size(), expected_flat_obstacles->size());
|
||||||
|
ASSERT_EQ(ground->size(), expected_ground->size());
|
||||||
|
ASSERT_EQ(obstacles->size(), expected_big_obstacles->size() + expected_small_obstacles->size() + expected_flat_obstacles->size());
|
||||||
|
|
||||||
|
// test flat obstacles with maxGroundHeight
|
||||||
|
for(int i=0; i<2; ++i) {
|
||||||
|
util3d::segmentObstaclesFromGround<pcl::PointXYZ>(
|
||||||
|
cloud,
|
||||||
|
ground,
|
||||||
|
obstacles,
|
||||||
|
normalKSearch,
|
||||||
|
angleMax,
|
||||||
|
clusterRadius,
|
||||||
|
1, // minClusterSize
|
||||||
|
i==0, // segmentFlatObstacles
|
||||||
|
0.1f, // maxGroundHeight
|
||||||
|
&flatObs, // flatObstacles
|
||||||
|
Eigen::Vector4f(0,0,2,0), // viewpoint
|
||||||
|
0.8f // groundNormalsUp
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_EQ(flatObs->size(), expected_flat_obstacles->size());
|
||||||
|
ASSERT_EQ(ground->size(), expected_ground->size());
|
||||||
|
// all obstacles under maxGroundHeight are ignored
|
||||||
|
ASSERT_EQ(obstacles->size(), expected_big_obstacles->size() + expected_small_obstacles->size() + expected_flat_obstacles->size() - 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test viewpoint (ceiling segmentation)
|
||||||
|
util3d::segmentObstaclesFromGround<pcl::PointXYZ>(
|
||||||
|
cloud,
|
||||||
|
ground,
|
||||||
|
obstacles,
|
||||||
|
normalKSearch,
|
||||||
|
angleMax,
|
||||||
|
clusterRadius,
|
||||||
|
1, // minClusterSize
|
||||||
|
false, // segmentFlatObstacles
|
||||||
|
0.0f, // maxGroundHeight
|
||||||
|
&flatObs, // flatObstacles
|
||||||
|
Eigen::Vector4f(0,0,0.15f,0), // viewpoint under the top flat obstacle
|
||||||
|
0.8f // groundNormalsUp
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_EQ(ground->size(), expected_ground->size());
|
||||||
|
ASSERT_EQ(obstacles->size(), expected_big_obstacles->size() + expected_small_obstacles->size() + expected_flat_obstacles->size());
|
||||||
|
|
||||||
|
// test min cluster radius
|
||||||
|
util3d::segmentObstaclesFromGround<pcl::PointXYZ>(
|
||||||
|
cloud,
|
||||||
|
ground,
|
||||||
|
obstacles,
|
||||||
|
normalKSearch,
|
||||||
|
angleMax,
|
||||||
|
clusterRadius,
|
||||||
|
17, // minClusterSize
|
||||||
|
false, // segmentFlatObstacles
|
||||||
|
0.0f, // maxGroundHeight
|
||||||
|
&flatObs, // flatObstacles
|
||||||
|
Eigen::Vector4f(0,0,2,0),
|
||||||
|
0.8f // groundNormalsUp
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_EQ(ground->size(), expected_ground->size());
|
||||||
|
ASSERT_EQ(obstacles->size(), expected_big_obstacles->size());
|
||||||
|
|
||||||
|
// Everything obstacles
|
||||||
|
util3d::segmentObstaclesFromGround<pcl::PointXYZ>(
|
||||||
|
cloud,
|
||||||
|
ground,
|
||||||
|
obstacles,
|
||||||
|
normalKSearch,
|
||||||
|
angleMax,
|
||||||
|
clusterRadius,
|
||||||
|
1, // minClusterSize
|
||||||
|
false, // segmentFlatObstacles
|
||||||
|
-0.1f, // maxGroundHeight
|
||||||
|
&flatObs, // flatObstacles
|
||||||
|
Eigen::Vector4f(0,0,2,0),
|
||||||
|
0.8f // groundNormalsUp
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT_EQ(ground->size(), 0);
|
||||||
|
ASSERT_EQ(obstacles->size(), expected_ground->size() + expected_big_obstacles->size() + expected_small_obstacles->size() + expected_flat_obstacles->size());
|
||||||
|
|
||||||
|
// Everything ground or ignored
|
||||||
|
for(int i=0; i<2; ++i) {
|
||||||
|
util3d::segmentObstaclesFromGround<pcl::PointXYZ>(
|
||||||
|
cloud,
|
||||||
|
ground,
|
||||||
|
obstacles,
|
||||||
|
normalKSearch,
|
||||||
|
angleMax,
|
||||||
|
clusterRadius,
|
||||||
|
1, // minClusterSize
|
||||||
|
i==0, // segmentFlatObstacles
|
||||||
|
10, // maxGroundHeight
|
||||||
|
&flatObs, // flatObstacles
|
||||||
|
Eigen::Vector4f(0,0,2,0),
|
||||||
|
0.8f // groundNormalsUp
|
||||||
|
);
|
||||||
|
// wether we segment or not, all flat surfaces are under 10 meters
|
||||||
|
ASSERT_EQ(ground->size(), expected_ground->size() + expected_flat_obstacles->size());
|
||||||
|
ASSERT_EQ(obstacles->size(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Util3dMapping, occupancy2DFromGroundObstaclesBasic)
|
||||||
|
{
|
||||||
|
pcl::PointCloud<pcl::PointXYZ>::Ptr groundCloud(new pcl::PointCloud<pcl::PointXYZ>);
|
||||||
|
pcl::PointCloud<pcl::PointXYZ>::Ptr obstaclesCloud(new pcl::PointCloud<pcl::PointXYZ>);
|
||||||
|
|
||||||
|
// Ground points (clustered near 0,0)
|
||||||
|
groundCloud->push_back(pcl::PointXYZ(0.05f, 0.05f, -0.2f));
|
||||||
|
groundCloud->push_back(pcl::PointXYZ(0.06f, 0.04f, -0.1f));
|
||||||
|
groundCloud->push_back(pcl::PointXYZ(0.5f, 0.5f, -0.2f)); // Separate voxel
|
||||||
|
|
||||||
|
// Obstacle points
|
||||||
|
obstaclesCloud->push_back(pcl::PointXYZ(1.0f, 1.0f, 1.0f));
|
||||||
|
obstaclesCloud->push_back(pcl::PointXYZ(1.02f, 1.01f, 1.2f)); // Same voxel
|
||||||
|
obstaclesCloud->push_back(pcl::PointXYZ(2.0f, 2.0f, 1.5f));
|
||||||
|
|
||||||
|
cv::Mat groundMat, obstaclesMat;
|
||||||
|
float cellSize = 0.1f;
|
||||||
|
|
||||||
|
util3d::occupancy2DFromGroundObstacles<pcl::PointXYZ>(groundCloud, obstaclesCloud, groundMat, obstaclesMat, cellSize);
|
||||||
|
|
||||||
|
// Check that points were voxelized and projected
|
||||||
|
EXPECT_EQ(groundMat.rows, 1);
|
||||||
|
EXPECT_EQ(groundMat.cols, 2); // Expect 2 distinct voxels
|
||||||
|
EXPECT_EQ(groundMat.type(), CV_32FC2);
|
||||||
|
|
||||||
|
EXPECT_NEAR(groundMat.at<cv::Vec2f>(0)[0], 0.055, 0.001);
|
||||||
|
EXPECT_NEAR(groundMat.at<cv::Vec2f>(0)[1], 0.045, 0.001);
|
||||||
|
EXPECT_NEAR(groundMat.at<cv::Vec2f>(1)[0], 0.5, 0.001);
|
||||||
|
EXPECT_NEAR(groundMat.at<cv::Vec2f>(1)[1], 0.5, 0.001);
|
||||||
|
|
||||||
|
EXPECT_EQ(obstaclesMat.rows, 1);
|
||||||
|
EXPECT_EQ(obstaclesMat.cols, 2); // Expect 2 voxels (1.0,1.0) and (2.0,2.0)
|
||||||
|
EXPECT_EQ(obstaclesMat.type(), CV_32FC2);
|
||||||
|
|
||||||
|
EXPECT_NEAR(obstaclesMat.at<cv::Vec2f>(0)[0], 1.01, 0.001);
|
||||||
|
EXPECT_NEAR(obstaclesMat.at<cv::Vec2f>(0)[1], 1.005, 0.001);
|
||||||
|
EXPECT_NEAR(obstaclesMat.at<cv::Vec2f>(1)[0], 2, 0.001);
|
||||||
|
EXPECT_NEAR(obstaclesMat.at<cv::Vec2f>(1)[1], 2, 0.001);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user