#include "gtest/gtest.h" #include "rtabmap/core/util3d.h" #include "rtabmap/core/util3d_surface.h" #include "rtabmap/core/CameraModel.h" #include "rtabmap/utilite/UException.h" #include "rtabmap/utilite/UConversion.h" #include "rtabmap/core/Version.h" #include using namespace rtabmap; // Utility to generate a flat plane of normals pointing up pcl::PointCloud createFlatNormalCloud(int count, const cv::Point3f& normal) { pcl::PointCloud cloud; cloud.resize(count); for (int i = 0; i < count; ++i) { cloud[i].normal_x = normal.x; cloud[i].normal_y = normal.y; cloud[i].normal_z = normal.z; } return cloud; } // Concatenate two point clouds. pcl::concatenate() only exists since PCL 1.10, // and the older pcl::concatenatePointCloud() only handles PCLPointCloud2, so // neither is portable. PointCloud::operator+= works on every version. pcl::PointCloud concatenated( const pcl::PointCloud & a, const pcl::PointCloud & b) { pcl::PointCloud out = a; out += b; return out; } TEST(Util3dSurfaceTest, ComputeNormalsComplexityVaryingNormals3D) { auto floor = createFlatNormalCloud(100, cv::Point3f(0.0f, 0.0f, 1.0f)); auto wallA = createFlatNormalCloud(100, cv::Point3f(0.0f, 1.0f, 0.0f)); auto wallB = createFlatNormalCloud(100, cv::Point3f(1.0f, 0.0f, 0.0f)); auto smallWallB = createFlatNormalCloud(10, cv::Point3f(1.0f, 0.0f, 0.0f)); // One flat surface float complexity = util3d::computeNormalsComplexity(floor); EXPECT_NEAR(complexity, 0.0f, 1e-3); pcl::PointCloud cloudA = concatenated(floor, wallA); // Two perpendicular surfaces complexity = util3d::computeNormalsComplexity(cloudA); EXPECT_NEAR(complexity, 0.0f, 1e-3); // Three perpendicular surfaces pcl::PointCloud cloudB = concatenated(cloudA, wallB); complexity = util3d::computeNormalsComplexity(cloudB); // Smallest PCA eigenvalue is ~0 for discrete normals on orthogonal axes. EXPECT_NEAR(complexity, 0.0f, 1e-3); // Three perpendicular surfaces (one small) pcl::PointCloud smallCloudB = concatenated(cloudA, smallWallB); complexity = util3d::computeNormalsComplexity(smallCloudB); EXPECT_NEAR(complexity, 0.0f, 1e-3); } TEST(Util3dSurfaceTest, ComputeNormalsComplexityIdentityVsRotated) { auto cloud = createFlatNormalCloud(50, cv::Point3f(0.0f, 1.0f, 0.0f)); Transform identity = Transform::getIdentity(); Transform rotated = Transform(0,0,0,0,M_PI / 4,0); float c1 = util3d::computeNormalsComplexity(cloud, identity, false, nullptr, nullptr); float c2 = util3d::computeNormalsComplexity(cloud, rotated, false, nullptr, nullptr); EXPECT_NEAR(c1, c2, 1e-5); // rotation should not affect complexity } TEST(Util3dSurfaceTest, ComputeNormalsComplexityEmptyOrInvalidNormals) { pcl::PointCloud cloud; pcl::PointNormal pt; pt.normal_x = std::numeric_limits::quiet_NaN(); pt.normal_y = 0.0f; pt.normal_z = 0.0f; cloud.push_back(pt); float complexity = util3d::computeNormalsComplexity(cloud, Transform(), false, nullptr, nullptr); EXPECT_EQ(complexity, 0.0f); // Should return 0 when all normals are invalid } TEST(Util3dSurfaceTest, ComputeNormalsComplexityVaryingNormals2D) { auto wallA = createFlatNormalCloud(100, cv::Point3f(0.0f, 1.0f, 0.0f)); auto negWallA = createFlatNormalCloud(100, cv::Point3f(0.0f, -1.0f, 0.0f)); auto wallB = createFlatNormalCloud(100, cv::Point3f(1.0f, 0.0f, 0.0f)); auto smalllWallB = createFlatNormalCloud(10, cv::Point3f(1.0f, 0.0f, 0.0f)); // One flat surface float complexity = util3d::computeNormalsComplexity(wallA, Transform(), true); EXPECT_NEAR(complexity, 0.0f, 1e-3); complexity = util3d::computeNormalsComplexity(wallB, Transform(), true); EXPECT_NEAR(complexity, 0.0f, 1e-3); pcl::PointCloud cloud = concatenated(wallA, wallB); // Two perpendicular surfaces complexity = util3d::computeNormalsComplexity(cloud, Transform(), true); EXPECT_NEAR(complexity, 0.0f, 1e-3); pcl::PointCloud cloudB = concatenated(wallA, smalllWallB); // Two perpendicular surfaces (one small) complexity = util3d::computeNormalsComplexity(cloudB, Transform(), true); EXPECT_NEAR(complexity, 0.0f, 1e-3); pcl::PointCloud corridorLikeCloud = concatenated(wallA, negWallA); // Two parallel surfaces simulating a corridor cv::Mat vector,values; complexity = util3d::computeNormalsComplexity(corridorLikeCloud, Transform(), true, &vector, &values); EXPECT_NEAR(complexity, 0.0f, 1e-3); EXPECT_NEAR(vector.at(0,0), 0, 1e-3); EXPECT_NEAR(vector.at(0,1), 1, 1e-3); // first eigen vector should be aligned with the normals }