mirror of
https://github.com/introlab/rtabmap.git
synced 2026-09-03 01:50:24 +08:00
* added doc and tests for util2d.h * updated cmake-ros ci * Added util3d.h doc and tests * util3d_transforms.h: Added doc and tests * util3d_filtering.h: started doc and test * util3d_filtering.h: more tests and doc * Added more doc/tests * finished util3d_filtering doc and tests * added test for util2d::depthBleedingFiltering * Added util3d_registration tests * Added util3d_features.h doc/tests * added doc/tests for util3d_correspondences.h * added doc/gtest for util3d_mapping.h (missing hpp functions) * finished testing util3d_mapping.hpp * Added util3d_motion_estimation.h tests (2D->3D done) * finished util3d_motion_estimation.h tests * minimal util3d_surface.h * Added Transform and VisualWord tests * Added doc for CameraModel and StereoCameraModel * Added more logs in ros ci * Passing tests on fical * improved all devcontainer * added devcontainer kilted, fixed source setup.bash, removed ldconfig in ros-cmake workflow * cleanup * source ros * Added utilite tests * Added testing to appveyor, github actions cancellable on re-commit on same branch * appveyor testing without all targets * appveyor: specifying ALL_BUILD target * Fixed Util2dTest.NMSImageBoundsRespected test * Fixing PCL Indices error on old pcl * Added VWDictionary tests and doc. Fixed LSH not working (fix from https://github.com/flann-lib/flann/pull/472 * fixing some appveyor CI errors, added test to check dictionary serialization against all type * Added StereoDense, StereoBM and StereoSGBM doc and tests * Added Stereo tests * Added CameraModel and StereoCameraModel tests * Added doc and test for Statistics * Added doc/tests for Signature * Added doc/test for SensorEvent, added doc for SensorCaptureInfo * Added doc to SensorData * Added SensorData tests * Added SensorCapture and SensorCaptureThread doc and tests * fixed sensordata test * updated SSC test and doc * Added doc and tests for BayesFilter class * Enabled testing on mac, updated windows testing like on linux * added test_link * fixed unresolved on windows * fixed ThreadHandle error on macos ci * Added GPS and GeodeticCoords tests * Added tests for compression * Added Odometry tests (base class only) * Added DBDriver tests * Added coverage report * uniformized test names * fixing concurancy and coverage ci * dont built tools, examples and app for coverage build * fixed report tool rebuilt without qt compilation error * updated coverage option * updated coverage config * added doc CI job * fixing windows and mac ci errors * Added DBDriverSqlite3 tests * Added IMU tests * Added Graph tests * fixing flaky macos test * Added IMUThread and IMUFilter tests * Added Landmarks tests * Added LASWriter tests * fixing seed flaky test * fixing flaky macos timing tests * Added LocalGrid tests * Added LocalGridMaker tests * fixing ci errors * Added GlobalMap tests * Added doc for EnvSensor * Added Features2D tests * Added Registration tests * Added RegistrationVis tests * Added doc for Rtabmap and Memory classes * Added Memory and Rtabmap tests * making some tests less flaky * lcov 1.14 support * updated compatible tool arguments * Added integration tests (RGB-D, Stereo, Lidar2d, Lidar3d) * More octomap checks * Refactored how/when python interpretor is created to simplify library usage * Added python tests * fixed some flaky tests * suppressed some third party related warnings * fixed ceres tests * more flaky fixes * Fixing tests without libpointmatcher * Added RANSAC rejection filter to PCL ICP * fixing multi platform flakiness * Added test to detect regression * Fixing windows pcl link error * fixed some macos flakiness * bigger 2D2D registration error on opencv 4.6.0 * flakiness * fixing flaky tests on windows and mac * flaky thread test on slow mac VM * windows slow test * fixing more ci erros * fxing temp dir on windows * Added Optimizer tests and discovered some bugs (fixed) * fixing flaky tests in mac and windows * Added Optimizer doc * Added GTSAM BA, updated Ceres to use g2o ba parameters. Renamed g2o's ba related parameters to Optimizer group and used by both gtsam and ceres. * fixing build without gtsam * fixing home dir * fixing python ci isssues * Added multicam ba tests * Added Ceres multicam BA support * Aligned BundleAdjustment parameters with Optimizer/Strategy to avoid confusion in the code * Added BA integration test * Added robust graph optimization integration test * Added loop3it test * Added stereo20Hz test * Added smartfactor gtsam * Fixed bugged check and warn if python didn't return any descriptors * Fixing gtsam version build issues * fixing tilt on windows ci * loosing ceres integration test for ci * mac ci flakiness * updating missing param in gui * updating test bound for mac * added appearance-based tests, set min gftt quality to quality level * testing more stuff * improving features2d tests * ci flakiness * fixing flaky ci * ci fixes * flaky fixes * Added RegistrationIcp tests * Added icp integration test with real-worl corridor like env * intermediate nodes * fixing enum * Updated test to catch #1714 * Fixed 2d corridor failing on pcl * flaky pnp test * flaky brisk test * Set rtabmap_integration test as long * updating loop closure test * flaky ci tests * TEsting roundtrip g2o/toro save/load * loosing test bound * fixed cuda capable checks * flaky tests * Debugging test hanging * more debugging stuff * updating limit * windows: disabled cuda on ci to avoid incompatible driver issue. Fixing a bad test mem allocation * trying fixing cuda hanging issue * fixing ci flakyness * flaky tests * Updated BOW flaky tests by checking min precision/recall instead of recall@100precision. Fixed signature test * CameraModel::load() test initRectificationMap param * test dbdriver load dictionary idsOnly * Memory: test keepLinkedInDb param * added dummyDictionary tests * test intermediate nodes count * Added MarkerDetector tests * reverted breaking change of UMutex and USemaphore * Features2d: fixed compiltion warnings with clang about override * clang warnings * fixing test build with pcl 1.8 * g2o and gtsam build errors on android * opencv5 test fixes * disabled testing for ios and android builds * normalized endline characters for easier diff * added LF CRLF rule * bump 0.23.10. fixing doc version * Publish rtabmap website doc from ci * fixing MSCVC build error * macos icp flaky test * fixing ceres macos test bound * ficing more flaky tests * fixing opencv5 related test errors. Also fixed an actual bug in ENU_WGS84ToGeocentric_WGS84() * added comment about mrpt change * removed rosdoc2 (will add it for rtabmap_ros later) * fixing website style * updated download links * locally deployable website with api * sweep doxygen issues * improved/revised doxygen main pages * removed examples empty page * Updated doxygen style * more concise doxygen groups * added api link on main readme * fixing utilite test error * fixing CommonFilteringGroundNormalsUp test * updated precisionRecall test bounds for Freak and brief descriptors * fixing scale check in ba tests * disabled tests on windows cuda build (missing dlls amd runner cannot test cuda anyway) * ceres: missing suitesparse dep in windows ci * adjusting recall thr for fast/freak * ficing more flaky tests * fixing flaky tests * disabled coverage in ros ci * Enable integration tests for ros ci jobs * loosing up some threshold for failing tests * trigger cache * fixing test data in ros ci. Updated flaky test for mac * slaking some test limit * Fixed rtabmap-detectMoreLoopClosures inverted output value * loosing up sift recall on mac * optimizer re-ordered distribution for reproducible results (mac g2o) * macos dump test crash log * combining all tests to save time on shared library reload. Also fixed Logs with missing arguments. * Added ENABLE_FORMAT_ERRORS cmake option * do test only one time * fixed all format warnings * format security android build errors * less verbose tests * updated ImuUThread test * fixed a log * Fixed libpointmatcher 2d normals eigen issue * Fixing libpointmatcher conversion issues * fixing libpointmatcher test on windows ci * cleanup comments, relax some test thr * disabled sequoia-intel ci build (too flaky, would need extensive testing directly on that machine)
2076 lines
76 KiB
C++
2076 lines
76 KiB
C++
#include "gtest/gtest.h"
|
|
#include "rtabmap/core/util3d.h"
|
|
#include "rtabmap/core/util2d.h"
|
|
#include "rtabmap/utilite/UException.h"
|
|
#include <pcl/common/point_tests.h>
|
|
#include <pcl/io/pcd_io.h>
|
|
#include <pcl/io/ply_io.h>
|
|
|
|
using namespace rtabmap;
|
|
|
|
TEST(Util3dTest, RgbFromCloudGeneratesCorrectRGB)
|
|
{
|
|
// Create a 2x2 organized point cloud with known colors
|
|
pcl::PointCloud<pcl::PointXYZRGBA> cloud;
|
|
cloud.width = 2;
|
|
cloud.height = 2;
|
|
cloud.is_dense = true;
|
|
cloud.points.resize(cloud.width * cloud.height);
|
|
|
|
// Fill in the cloud with distinct colors
|
|
cloud.at(0).r = 255; cloud.at(0).g = 0; cloud.at(0).b = 0; // Red
|
|
cloud.at(1).r = 0; cloud.at(1).g = 255; cloud.at(1).b = 0; // Green
|
|
cloud.at(2).r = 0; cloud.at(2).g = 0; cloud.at(2).b = 255; // Blue
|
|
cloud.at(3).r = 255; cloud.at(3).g = 255; cloud.at(3).b = 255; // White
|
|
|
|
// Test with bgrOrder = true
|
|
cv::Mat bgr = util3d::rgbFromCloud(cloud, true);
|
|
ASSERT_EQ(bgr.rows, 2);
|
|
ASSERT_EQ(bgr.cols, 2);
|
|
EXPECT_EQ(bgr.at<cv::Vec3b>(0,0), cv::Vec3b(0,0,255)); // BGR for red
|
|
EXPECT_EQ(bgr.at<cv::Vec3b>(0,1), cv::Vec3b(0,255,0)); // BGR for green
|
|
EXPECT_EQ(bgr.at<cv::Vec3b>(1,0), cv::Vec3b(255,0,0)); // BGR for blue
|
|
EXPECT_EQ(bgr.at<cv::Vec3b>(1,1), cv::Vec3b(255,255,255)); // White
|
|
|
|
// Test with bgrOrder = false
|
|
cv::Mat rgb = util3d::rgbFromCloud(cloud, false);
|
|
EXPECT_EQ(rgb.at<cv::Vec3b>(0,0), cv::Vec3b(255,0,0)); // RGB for red
|
|
EXPECT_EQ(rgb.at<cv::Vec3b>(0,1), cv::Vec3b(0,255,0)); // RGB for green
|
|
EXPECT_EQ(rgb.at<cv::Vec3b>(1,0), cv::Vec3b(0,0,255)); // RGB for blue
|
|
EXPECT_EQ(rgb.at<cv::Vec3b>(1,1), cv::Vec3b(255,255,255)); // White
|
|
}
|
|
|
|
TEST(Util3dTest, DepthFromCloudBasic32F)
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGBA> cloud;
|
|
cloud.width = 3;
|
|
cloud.height = 2;
|
|
cloud.is_dense = false;
|
|
cloud.points.resize(cloud.width * cloud.height);
|
|
|
|
// Fill the cloud with predictable Z values
|
|
for (size_t i = 0; i < cloud.points.size(); ++i)
|
|
{
|
|
cloud.points[i].x = float(i);
|
|
cloud.points[i].y = float(i);
|
|
cloud.points[i].z = 1.0f + float(i) * 0.1f;
|
|
}
|
|
|
|
cv::Mat depth = util3d::depthFromCloud(cloud, false);
|
|
|
|
std::cout << depth << std::endl;
|
|
|
|
ASSERT_EQ(depth.type(), CV_32FC1);
|
|
ASSERT_EQ(depth.rows, 2);
|
|
ASSERT_EQ(depth.cols, 3);
|
|
|
|
// Check specific depth values
|
|
for (int r = 0; r < depth.rows; ++r)
|
|
{
|
|
for (int c = 0; c < depth.cols; ++c)
|
|
{
|
|
float expected = 1.0f + float(r * depth.cols + c) * 0.1f;
|
|
EXPECT_FLOAT_EQ(depth.at<float>(r, c), expected);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(Util3dTest, DepthFromCloudBasic16U)
|
|
{
|
|
pcl::PointCloud<pcl::PointXYZRGBA> cloud;
|
|
cloud.width = 2;
|
|
cloud.height = 2;
|
|
cloud.is_dense = false;
|
|
cloud.points.resize(cloud.width * cloud.height);
|
|
|
|
for (size_t i = 0; i < cloud.points.size(); ++i)
|
|
{
|
|
cloud.points[i].x = float(i);
|
|
cloud.points[i].y = float(i);
|
|
cloud.points[i].z = 1.0f; // 1 meter
|
|
}
|
|
|
|
cv::Mat depth = util3d::depthFromCloud(cloud, true);
|
|
|
|
ASSERT_EQ(depth.type(), CV_16UC1);
|
|
ASSERT_EQ(depth.rows, 2);
|
|
ASSERT_EQ(depth.cols, 2);
|
|
|
|
for (int r = 0; r < depth.rows; ++r)
|
|
{
|
|
for (int c = 0; c < depth.cols; ++c)
|
|
{
|
|
EXPECT_EQ(depth.at<uint16_t>(r, c), 1000); // 1000 mm
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(Util3dTest, RgbdFromCloudBasicConversion)
|
|
{
|
|
// Create a 2x2 point cloud with synthetic values
|
|
pcl::PointCloud<pcl::PointXYZRGBA> cloud;
|
|
cloud.width = 2;
|
|
cloud.height = 2;
|
|
cloud.is_dense = true;
|
|
cloud.points.resize(4);
|
|
|
|
for (size_t i = 0; i < 4; ++i)
|
|
{
|
|
cloud.points[i].x = static_cast<float>(i);
|
|
cloud.points[i].y = static_cast<float>(i) * 0.5f;
|
|
cloud.points[i].z = 1.0f + 0.1f * i;
|
|
cloud.points[i].r = 10 * i;
|
|
cloud.points[i].g = 20 * i;
|
|
cloud.points[i].b = 30 * i;
|
|
}
|
|
|
|
cv::Mat bgr, depth;
|
|
|
|
util3d::rgbdFromCloud(cloud, bgr, depth, true, false);
|
|
|
|
// Check image sizes and types
|
|
ASSERT_EQ(bgr.rows, 2);
|
|
ASSERT_EQ(bgr.cols, 2);
|
|
ASSERT_EQ(bgr.type(), CV_8UC3);
|
|
ASSERT_EQ(depth.rows, 2);
|
|
ASSERT_EQ(depth.cols, 2);
|
|
ASSERT_EQ(depth.type(), CV_32FC1);
|
|
|
|
// Check a pixel's values
|
|
cv::Vec3b color = bgr.at<cv::Vec3b>(0,1);
|
|
EXPECT_EQ(color[0], 30); // B
|
|
EXPECT_EQ(color[1], 20); // G
|
|
EXPECT_EQ(color[2], 10); // R
|
|
|
|
float d = depth.at<float>(0,0);
|
|
EXPECT_NEAR(d, 1.0f, 1e-5);
|
|
}
|
|
|
|
TEST(Util3dTest, ProjectDepthTo3D)
|
|
{
|
|
// Create a synthetic 5x5 depth image with all values set to 1.0 meter
|
|
cv::Mat depthImage = cv::Mat::ones(5, 5, CV_32FC1);
|
|
|
|
float fx = 525.0f;
|
|
float fy = 525.0f;
|
|
float cx = 2.0f;
|
|
float cy = 2.0f;
|
|
|
|
// Project the center pixel (2, 2)
|
|
pcl::PointXYZ pt = util3d::projectDepthTo3D(depthImage, 2, 2, cx, cy, fx, fy, false, 0.0f);
|
|
|
|
// Since we project the principal point, x and y should be zero
|
|
EXPECT_FLOAT_EQ(pt.x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 1.0f);
|
|
|
|
// Project a pixel to the right of center (3, 2)
|
|
pt = util3d::projectDepthTo3D(depthImage, 3, 2, cx, cy, fx, fy, false, 0.0f);
|
|
EXPECT_NEAR(pt.x, (3.0f - cx) * 1.0f / fx, 1e-5);
|
|
EXPECT_FLOAT_EQ(pt.y, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 1.0f);
|
|
}
|
|
|
|
TEST(Util3dTest, ProjectDepthTo3DRayBasicRayProjection)
|
|
{
|
|
cv::Size imageSize(640, 480);
|
|
float fx = 525.0f;
|
|
float fy = 525.0f;
|
|
float cx = 319.5f;
|
|
float cy = 239.5f;
|
|
|
|
// Center pixel should return (0,0,1)
|
|
Eigen::Vector3f ray = util3d::projectDepthTo3DRay(imageSize, cx, cy, cx, cy, fx, fy);
|
|
EXPECT_NEAR(ray[0], 0.0f, 1e-5);
|
|
EXPECT_NEAR(ray[1], 0.0f, 1e-5);
|
|
EXPECT_NEAR(ray[2], 1.0f, 1e-5);
|
|
|
|
// A pixel 1 unit right should return a small positive x component
|
|
ray = util3d::projectDepthTo3DRay(imageSize, cx + 1, cy, cx, cy, fx, fy);
|
|
EXPECT_NEAR(ray[0], 1.0f / fx, 1e-5);
|
|
EXPECT_NEAR(ray[1], 0.0f, 1e-5);
|
|
EXPECT_NEAR(ray[2], 1.0f, 1e-5);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromDepthWithCameraModel) {
|
|
// Create a sample depth image (CV_32FC1 format)
|
|
cv::Mat depthImage(4, 4, CV_32FC1);
|
|
for(int j=0;j<depthImage.rows; ++j)
|
|
{
|
|
for(int i=0;i<depthImage.cols; ++i)
|
|
{
|
|
depthImage.at<float>(j, i) = j*depthImage.cols + i + 1;
|
|
}
|
|
}
|
|
|
|
// Camera model with arbitrary parameters
|
|
CameraModel model(10.0f, 10.0f, 1.5f, 1.5f);
|
|
|
|
// Create test cloud from depth image
|
|
std::vector<int> validIndices;
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = util3d::cloudFromDepth(depthImage, model, 1, 0.0f, 0.0f, &validIndices);
|
|
|
|
// Check if cloud is created
|
|
ASSERT_NE(cloud, nullptr);
|
|
ASSERT_EQ(cloud->height, 4);
|
|
ASSERT_EQ(cloud->width, 4);
|
|
|
|
ASSERT_EQ(cloud->size(), validIndices.size());
|
|
|
|
// Check if the first point in the cloud has reasonable values
|
|
pcl::PointXYZ & pt = cloud->at(0);
|
|
ASSERT_LT(pt.x, 0.0f); // Sample assert, check actual expected values
|
|
ASSERT_LT(pt.y, 0.0f); // Sample assert, check actual expected values
|
|
ASSERT_FLOAT_EQ(pt.z, 1.0f); // Check depth
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromDepthRGBValidInputCreatesPointCloud) {
|
|
// Create a simple test depth image (e.g., 5x5)
|
|
cv::Mat imageRgb = cv::Mat::zeros(5, 5, CV_8UC3); // Black RGB image
|
|
cv::Mat imageDepth = cv::Mat::ones(5, 5, CV_32FC1); // Depth image with all values 1.0
|
|
|
|
CameraModel model(500, 500, 2.5, 2.5); // Example CameraModel parameters
|
|
std::vector<int> validIndices;
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = util3d::cloudFromDepthRGB(imageRgb, imageDepth, model, 1, 10.0f, 0.1f, &validIndices);
|
|
|
|
// Check if the cloud contains points
|
|
ASSERT_FALSE(cloud->empty());
|
|
ASSERT_EQ(cloud->width, 5);
|
|
ASSERT_EQ(cloud->height, 5);
|
|
|
|
// Check if some valid indices exist
|
|
ASSERT_EQ(validIndices.size(), cloud->size());
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromDepthRGBEmptyInput) {
|
|
cv::Mat imageRgb = cv::Mat(); // Empty RGB image
|
|
cv::Mat imageDepth = cv::Mat(); // Empty Depth image
|
|
|
|
CameraModel model(500, 500, 2.5, 2.5);
|
|
std::vector<int> validIndices;
|
|
|
|
ASSERT_THROW(util3d::cloudFromDepthRGB(imageRgb, imageDepth, model, 1, 10.0f, 0.1f, &validIndices), UException);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromDepthRGBDecimationReducesPointCloudSize) {
|
|
// Create a simple test depth image (e.g., 6x6) and RGB image
|
|
cv::Mat imageRgb = cv::Mat::zeros(6, 6, CV_8UC3);
|
|
cv::Mat imageDepth = cv::Mat::ones(6, 6, CV_32FC1);
|
|
|
|
CameraModel model(500, 500, 3.0, 3.0);
|
|
std::vector<int> validIndices;
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = util3d::cloudFromDepthRGB(imageRgb, imageDepth, model, 2, 10.0f, 0.1f, &validIndices);
|
|
|
|
// Decimation by 2 should result in 3x3 cloud
|
|
ASSERT_EQ(cloud->width, 3);
|
|
ASSERT_EQ(cloud->height, 3);
|
|
}
|
|
|
|
// Test depth constraints: points outside the range should not be included in the cloud
|
|
TEST(Util3dTest, CloudFromDepthRGBDepthConstraintsLimitPointCloud) {
|
|
cv::Mat imageRgb = cv::Mat::zeros(5, 5, CV_8UC3);
|
|
cv::Mat imageDepth = cv::Mat::ones(5, 5, CV_32FC1) * 0.05f; // Depth values smaller than the minimum
|
|
|
|
CameraModel model(500, 500, 2.5, 2.5);
|
|
std::vector<int> validIndices;
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = util3d::cloudFromDepthRGB(imageRgb, imageDepth, model, 1, 10.0f, 0.1f, &validIndices);
|
|
|
|
// Check that no points are valid (because the depth is below the minimum)
|
|
ASSERT_EQ(cloud->size(), 25);
|
|
ASSERT_EQ(validIndices.size(), 0);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromDepthRGBImageSizeMismatchThrowsError) {
|
|
cv::Mat imageRgb = cv::Mat::zeros(6, 6, CV_8UC3); // 6x6 RGB image
|
|
cv::Mat imageDepth = cv::Mat::ones(6, 6, CV_32FC1); // 6x6 Depth image
|
|
|
|
CameraModel model(500, 500, 2.5, 2.5, CameraModel::opticalRotation(), 0, cv::Size(5,5)); // should mismatch
|
|
|
|
// Test should fail since image size and calibration model don't match
|
|
ASSERT_THROW(util3d::cloudFromDepthRGB(imageRgb, imageDepth, model, 1, 10.0f, 0.1f, nullptr), UException);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromDisparityValidInputs) {
|
|
cv::Mat imageDisparity = cv::Mat::ones(10, 10, CV_32FC1) * 50.0f; // 50.0 as the disparity value
|
|
StereoCameraModel model(10, 10, 4.5f, 4.5f, 0.05f, CameraModel::opticalRotation(), imageDisparity.size());
|
|
|
|
// Decimation, maxDepth, and minDepth values
|
|
int decimation = 1;
|
|
float maxDepth = 0.0f;
|
|
float minDepth = 0.0f;
|
|
|
|
std::vector<int> validIndices;
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = util3d::cloudFromDisparity(imageDisparity, model, decimation, maxDepth, minDepth, &validIndices);
|
|
|
|
// Verify the size of the cloud and valid indices
|
|
ASSERT_NE(cloud, nullptr);
|
|
ASSERT_EQ(cloud->size(), imageDisparity.total());
|
|
ASSERT_EQ(validIndices.size(), imageDisparity.total());
|
|
|
|
// try with
|
|
imageDisparity = cv::Mat::ones(10, 10, CV_16SC1) * 50 * 16;
|
|
cloud = util3d::cloudFromDisparity(imageDisparity, model, decimation, maxDepth, minDepth, &validIndices);
|
|
|
|
// Verify the size of the cloud and valid indices
|
|
ASSERT_NE(cloud, nullptr);
|
|
ASSERT_EQ(cloud->size(), imageDisparity.total());
|
|
ASSERT_EQ(validIndices.size(), imageDisparity.total());
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromDisparityRGBValidInputs) {
|
|
cv::Mat imageRgb = cv::Mat(10, 10, CV_8UC3, cv::Scalar(255, 200, 150)); // BGR
|
|
cv::Mat imageDisparity = cv::Mat::ones(10, 10, CV_32FC1) * 50.0f; // 50.0 as the disparity value
|
|
StereoCameraModel model(10, 10, 4.5f, 4.5f, 0.05f, CameraModel::opticalRotation(), imageDisparity.size());
|
|
|
|
// Decimation, maxDepth, and minDepth values
|
|
int decimation = 1;
|
|
float maxDepth = 0.0f;
|
|
float minDepth = 0.0f;
|
|
|
|
std::vector<int> validIndices;
|
|
|
|
// Call the function
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = util3d::cloudFromDisparityRGB(imageRgb, imageDisparity, model, decimation, maxDepth, minDepth, &validIndices);
|
|
|
|
// Verify the size of the cloud and valid indices
|
|
ASSERT_NE(cloud, nullptr);
|
|
ASSERT_EQ(cloud->size(), imageDisparity.total());
|
|
ASSERT_EQ(validIndices.size(), imageDisparity.total());
|
|
|
|
// Test if the values are within the expected depth range and color assignment
|
|
for (const auto& pt : *cloud) {
|
|
ASSERT_GE(pt.z, minDepth);
|
|
ASSERT_EQ(pt.r, 150); // Check if the color is correctly assigned to red
|
|
ASSERT_EQ(pt.g, 200); // Check if the color is correctly assigned to green
|
|
ASSERT_EQ(pt.b, 255); // Check if the color is correctly assigned to blue
|
|
}
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromDisparityInvalidDecimation) {
|
|
// Create a mock disparity image (CV_32FC1)
|
|
cv::Mat imageDisparity = cv::Mat::ones(10, 10, CV_32FC1) * 50.0f;
|
|
StereoCameraModel model(10, 10, 4.5f, 4.5f, 0.05f, CameraModel::opticalRotation(), imageDisparity.size());
|
|
|
|
// Set invalid decimation factor
|
|
int decimation = 3; // Should be divisible by image size
|
|
float maxDepth = 0.0f;
|
|
float minDepth = 0.0f;
|
|
|
|
std::vector<int> validIndices;
|
|
|
|
// Call the function
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = util3d::cloudFromDisparity(imageDisparity, model, decimation, maxDepth, minDepth, &validIndices);
|
|
|
|
// Verify that the cloud is still generated and decimation is adjusted
|
|
ASSERT_NE(cloud, nullptr);
|
|
ASSERT_GT(cloud->size(), 0);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromDisparityEmptyImages) {
|
|
// Call the function with empty images
|
|
ASSERT_THROW(util3d::cloudFromDisparity(cv::Mat(), StereoCameraModel()), UException);
|
|
ASSERT_THROW(util3d::cloudFromDisparityRGB(cv::Mat(), cv::Mat(), StereoCameraModel()), UException);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromStereoImagesBasicTest) {
|
|
cv::Mat imageLeft = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/stereo_rect/left/50.jpg", cv::IMREAD_UNCHANGED);
|
|
cv::Mat imageRight = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/stereo_rect/right/50.jpg", cv::IMREAD_GRAYSCALE);
|
|
|
|
ASSERT_FALSE(imageLeft.empty());
|
|
ASSERT_FALSE(imageRight.empty());
|
|
|
|
// Stereo camera model
|
|
StereoCameraModel model;
|
|
ASSERT_TRUE(model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/stereo_rect", "stereo"));
|
|
|
|
// Decimation factor (reduce resolution by half)
|
|
int decimation = 2;
|
|
|
|
// Depth range settings
|
|
float maxDepth = 10.0f; // Max depth in meters
|
|
float minDepth = 0.1f; // Min depth in meters
|
|
|
|
// Vector to hold valid indices (optional)
|
|
std::vector<int> validIndices;
|
|
|
|
ParametersMap parameters;
|
|
parameters[Parameters::kStereoBMNumDisparities()] = "64";
|
|
|
|
// Call the function
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = util3d::cloudFromStereoImages(
|
|
imageLeft, imageRight, model, decimation, maxDepth, minDepth, &validIndices, parameters);
|
|
|
|
// Verify the cloud is not empty and has the expected properties
|
|
ASSERT_NE(cloud, nullptr);
|
|
ASSERT_EQ(cloud->width, imageLeft.cols/decimation);
|
|
ASSERT_EQ(cloud->height, imageLeft.rows/decimation);
|
|
|
|
ASSERT_GT(validIndices.size(), 0);
|
|
|
|
std::set<int> validSet(validIndices.begin(), validIndices.end());
|
|
|
|
// Check that the cloud points are within expected bounds (e.g., not NaN)
|
|
for (size_t i=0; i<cloud->size(); ++i)
|
|
{
|
|
const pcl::PointXYZRGB & point = cloud->at(i);
|
|
if(validSet.find(i) != validSet.end())
|
|
{
|
|
ASSERT_TRUE(std::isfinite(point.x));
|
|
ASSERT_TRUE(std::isfinite(point.y));
|
|
ASSERT_TRUE(std::isfinite(point.z));
|
|
ASSERT_GT(point.z, minDepth);
|
|
ASSERT_LT(point.z, maxDepth);
|
|
|
|
int v = i / cloud->width;
|
|
int u = i - v*cloud->width;
|
|
ASSERT_EQ(point.b, imageLeft.at<cv::Vec3b>(v*decimation, u*decimation)[0]);
|
|
ASSERT_EQ(point.g, imageLeft.at<cv::Vec3b>(v*decimation, u*decimation)[1]);
|
|
ASSERT_EQ(point.r, imageLeft.at<cv::Vec3b>(v*decimation, u*decimation)[2]);
|
|
}
|
|
else
|
|
{
|
|
ASSERT_FALSE(std::isfinite(point.x));
|
|
ASSERT_FALSE(std::isfinite(point.y));
|
|
ASSERT_FALSE(std::isfinite(point.z));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(Util3dTest, CloudsFromSensorDataBasicCloudGeneration) {
|
|
cv::Mat depthImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/depth/17.png", cv::IMREAD_UNCHANGED);
|
|
cv::Mat rgbImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/rgb/17.jpg", cv::IMREAD_COLOR);
|
|
|
|
ASSERT_FALSE(depthImage.empty());
|
|
ASSERT_FALSE(rgbImage.empty());
|
|
|
|
CameraModel model;
|
|
ASSERT_TRUE(model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/calib/17.yaml"));
|
|
SensorData sensorData(rgbImage, depthImage, model);
|
|
|
|
// Test Parameters
|
|
int decimation = 1; // No decimation
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<float> roiRatios = {0.0f, 0.0f, 0.0f, 0.0f}; // Full image
|
|
|
|
// Call the function to generate clouds
|
|
std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> clouds = util3d::cloudsFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
nullptr, // No validIndices in this test
|
|
ParametersMap(),
|
|
roiRatios
|
|
);
|
|
|
|
// Check that we got clouds
|
|
ASSERT_GT(clouds.size(), 0) << "Expected at least one point cloud";
|
|
|
|
// Check the size of the first cloud to be non-zero
|
|
ASSERT_GT(clouds[0]->size(), 0) << "First point cloud is empty";
|
|
}
|
|
|
|
TEST(Util3dTest, CloudsFromSensorDataCloudGenerationWithDecimation) {
|
|
cv::Mat depthImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/depth/17.png", cv::IMREAD_UNCHANGED);
|
|
cv::Mat rgbImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/rgb/17.jpg", cv::IMREAD_COLOR);
|
|
|
|
CameraModel model;
|
|
model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/calib/17.yaml");
|
|
SensorData sensorData(rgbImage, depthImage, model);
|
|
|
|
// Test Parameters
|
|
int decimation = 2; // Apply decimation
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<float> roiRatios = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
// Generate clouds
|
|
std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> clouds = util3d::cloudsFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
nullptr,
|
|
ParametersMap(),
|
|
roiRatios
|
|
);
|
|
|
|
// Check that we got clouds
|
|
ASSERT_GT(clouds.size(), 0) << "Expected at least one point cloud";
|
|
|
|
// Check that the cloud size after decimation is smaller than the original
|
|
ASSERT_GT(clouds[0]->size(), 0) << "First point cloud is empty after decimation";
|
|
ASSERT_LT(clouds[0]->size(), 640 * 480) << "Cloud size should be smaller due to decimation";
|
|
}
|
|
|
|
TEST(Util3dTest, CloudsFromSensorDataCloudGenerationWithEmptyImages) {
|
|
// Create empty sensor data
|
|
SensorData sensorData;
|
|
|
|
// Test Parameters
|
|
int decimation = 1;
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<float> roiRatios = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
// Call the function with empty images
|
|
std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> clouds = util3d::cloudsFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
nullptr,
|
|
ParametersMap(),
|
|
roiRatios
|
|
);
|
|
|
|
// Assert that no clouds are generated from empty images
|
|
ASSERT_EQ(clouds.size(), 0) << "Expected no point clouds for empty input images";
|
|
}
|
|
|
|
TEST(Util3dTest, CloudFromSensorDataGeneratesPointCloud) {
|
|
cv::Mat depthImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/depth/17.png", cv::IMREAD_UNCHANGED);
|
|
cv::Mat rgbImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/rgb/17.jpg", cv::IMREAD_COLOR);
|
|
|
|
ASSERT_FALSE(depthImage.empty());
|
|
ASSERT_FALSE(rgbImage.empty());
|
|
|
|
CameraModel model;
|
|
ASSERT_TRUE(model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/calib/17.yaml"));
|
|
SensorData sensorData(rgbImage, depthImage, model);
|
|
|
|
int decimation = 1; // No decimation
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<int> validIndices;
|
|
std::vector<float> roiRatios = {0.0f, 0.0f, 0.0f, 0.0f}; // Full image
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud = util3d::cloudFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
&validIndices,
|
|
ParametersMap(),
|
|
roiRatios
|
|
);
|
|
|
|
// Assert: Check that the cloud is not null and has points
|
|
ASSERT_NE(cloud, nullptr); // The cloud should not be null
|
|
ASSERT_EQ(cloud->size(), rgbImage.total());
|
|
|
|
// Check if valid indices are filled correctly
|
|
ASSERT_GT(validIndices.size(), 0);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudsRGBFromSensorDataTestSingleCamera) {
|
|
cv::Mat depthImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/depth/17.png", cv::IMREAD_UNCHANGED);
|
|
cv::Mat rgbImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/rgb/17.jpg", cv::IMREAD_COLOR);
|
|
|
|
ASSERT_FALSE(depthImage.empty());
|
|
ASSERT_FALSE(rgbImage.empty());
|
|
|
|
CameraModel model;
|
|
ASSERT_TRUE(model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/calib/17.yaml"));
|
|
SensorData sensorData(rgbImage, depthImage, model);
|
|
|
|
int decimation = 1;
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<pcl::IndicesPtr> validIndices;
|
|
std::vector<float> roiRatios = {0.0f, 0.0f, 0.0f, 0.0f}; // ROI ratios for the test
|
|
|
|
// Call the function
|
|
auto clouds = util3d::cloudsRGBFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
&validIndices,
|
|
ParametersMap(),
|
|
roiRatios
|
|
);
|
|
|
|
// Check if the returned clouds are not empty
|
|
ASSERT_FALSE(clouds.empty());
|
|
|
|
// Check if the first cloud has a reasonable size (not empty)
|
|
ASSERT_GT(clouds[0]->size(), 0);
|
|
ASSERT_GT(validIndices[0]->size(), 0);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudsRGBFromSensorDataTestStereoCameras) {
|
|
cv::Mat imageLeft = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/stereo_rect/left/50.jpg", cv::IMREAD_UNCHANGED);
|
|
cv::Mat imageRight = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/stereo_rect/right/50.jpg", cv::IMREAD_GRAYSCALE);
|
|
|
|
ASSERT_FALSE(imageLeft.empty());
|
|
ASSERT_FALSE(imageRight.empty());
|
|
|
|
// Stereo camera model
|
|
StereoCameraModel model;
|
|
ASSERT_TRUE(model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/stereo_rect", "stereo"));
|
|
SensorData sensorData(imageLeft, imageRight, model);
|
|
|
|
int decimation = 2;
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<pcl::IndicesPtr> validIndices;
|
|
ParametersMap stereoParameters; // Fill with necessary stereo parameters
|
|
std::vector<float> roiRatios = {0.1f, 0.1f, 0.9f, 0.9f}; // ROI ratios for the test
|
|
|
|
// Call the function for stereo cameras
|
|
auto clouds = util3d::cloudsRGBFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
&validIndices,
|
|
stereoParameters,
|
|
roiRatios
|
|
);
|
|
|
|
// Check if the returned clouds are not empty
|
|
ASSERT_FALSE(clouds.empty());
|
|
|
|
// Check if the number of clouds matches the number of stereo camera models
|
|
ASSERT_EQ(clouds.size(), sensorData.stereoCameraModels().size());
|
|
|
|
// Check if the first cloud has a reasonable size (not empty)
|
|
ASSERT_GT(clouds[0]->size(), 0);
|
|
ASSERT_GT(validIndices[0]->size(), 0);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudsRGBFromSensorDataTestEmptySensorData) {
|
|
SensorData sensorData; // Create empty sensor data (or no data at all)
|
|
int decimation = 1;
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<pcl::IndicesPtr> validIndices;
|
|
ParametersMap stereoParameters;
|
|
std::vector<float> roiRatios = {0.0f, 0.0f, 0.0f, 0.0f};
|
|
|
|
// Call the function with empty sensor data
|
|
auto clouds = util3d::cloudsRGBFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
&validIndices,
|
|
stereoParameters,
|
|
roiRatios
|
|
);
|
|
|
|
// The function should return an empty vector if no valid data is available
|
|
ASSERT_TRUE(clouds.empty());
|
|
}
|
|
|
|
TEST(Util3dTest, CloudsRGBFromSensorDataTestInvalidROIRatios) {
|
|
cv::Mat depthImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/depth/17.png", cv::IMREAD_UNCHANGED);
|
|
cv::Mat rgbImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/rgb/17.jpg", cv::IMREAD_COLOR);
|
|
|
|
ASSERT_FALSE(depthImage.empty());
|
|
ASSERT_FALSE(rgbImage.empty());
|
|
|
|
CameraModel model;
|
|
ASSERT_TRUE(model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/calib/17.yaml"));
|
|
SensorData sensorData(rgbImage, depthImage, model);
|
|
|
|
int decimation = 1;
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<pcl::IndicesPtr> validIndices;
|
|
ParametersMap stereoParameters;
|
|
std::vector<float> roiRatios = {0.5f, 0.5f, 0.5f, 1.5f}; // Invalid ROI (this can be an edge case to test)
|
|
|
|
// Call the function
|
|
auto clouds = util3d::cloudsRGBFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
&validIndices,
|
|
stereoParameters,
|
|
roiRatios
|
|
);
|
|
|
|
// Check if clouds are still returned, but possibly with invalid results
|
|
ASSERT_FALSE(clouds.empty());
|
|
ASSERT_EQ(clouds[0]->size(), rgbImage.total());
|
|
ASSERT_GT(validIndices[0]->size(), 0);
|
|
}
|
|
|
|
TEST(Util3dTest, CloudRGBFromSensorDataGeneratesCloudWithValidData) {
|
|
cv::Mat depthImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/depth/17.png", cv::IMREAD_UNCHANGED);
|
|
cv::Mat rgbImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/rgb/17.jpg", cv::IMREAD_COLOR);
|
|
|
|
ASSERT_FALSE(depthImage.empty());
|
|
ASSERT_FALSE(rgbImage.empty());
|
|
|
|
CameraModel model;
|
|
ASSERT_TRUE(model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/calib/17.yaml"));
|
|
SensorData sensorData(rgbImage, depthImage, model);
|
|
|
|
int decimation = 1; // No decimation
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<int> validIndices;
|
|
ParametersMap stereoParameters; // Assuming stereoParameters are empty or predefined
|
|
std::vector<float> roiRatios = {0.0f, 0.0f, 0.0f, 0.0f}; // Some ROI ratio
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = util3d::cloudRGBFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
&validIndices,
|
|
stereoParameters,
|
|
roiRatios
|
|
);
|
|
|
|
// Verify results
|
|
ASSERT_NE(cloud, nullptr); // The cloud should not be null
|
|
EXPECT_EQ(cloud->size(), rgbImage.total());
|
|
EXPECT_GT(validIndices.size(), 0);
|
|
|
|
std::set<int> validSet(validIndices.begin(), validIndices.end());
|
|
|
|
// Optionally verify point data (for example, checking if points have RGB values within expected ranges)
|
|
for (size_t i=0; i<cloud->size(); ++i)
|
|
{
|
|
const pcl::PointXYZRGB & point = cloud->at(i);
|
|
if(validSet.find(i) != validSet.end())
|
|
{
|
|
ASSERT_TRUE(std::isfinite(point.x));
|
|
ASSERT_TRUE(std::isfinite(point.y));
|
|
ASSERT_TRUE(std::isfinite(point.z));
|
|
ASSERT_GT(point.x, minDepth); // in base frame
|
|
ASSERT_LT(point.x, maxDepth); // in base frame
|
|
|
|
int v = i / cloud->width;
|
|
int u = i - v*cloud->width;
|
|
ASSERT_EQ(point.b, rgbImage.at<cv::Vec3b>(v*decimation, u*decimation)[0]);
|
|
ASSERT_EQ(point.g, rgbImage.at<cv::Vec3b>(v*decimation, u*decimation)[1]);
|
|
ASSERT_EQ(point.r, rgbImage.at<cv::Vec3b>(v*decimation, u*decimation)[2]);
|
|
}
|
|
else
|
|
{
|
|
ASSERT_FALSE(std::isfinite(point.x));
|
|
ASSERT_FALSE(std::isfinite(point.y));
|
|
ASSERT_FALSE(std::isfinite(point.z));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(Util3dTest, CloudRGBFromSensorDataHandlesEmptyData) {
|
|
SensorData sensorData;
|
|
int decimation = 1;
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<int> validIndices;
|
|
ParametersMap stereoParameters;
|
|
std::vector<float> roiRatios;
|
|
|
|
// Call the function under test
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = util3d::cloudRGBFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
&validIndices,
|
|
stereoParameters,
|
|
roiRatios
|
|
);
|
|
|
|
// Verify the cloud is generated, even with empty input data
|
|
ASSERT_NE(cloud, nullptr);
|
|
EXPECT_EQ(cloud->size(), 0); // The cloud should be empty due to lack of data
|
|
EXPECT_EQ(validIndices.size(), 0); // No valid indices due to empty cloud
|
|
}
|
|
|
|
TEST(Util3dTest, CloudRGBFromSensorDataHandlesInvalidROI) {
|
|
cv::Mat depthImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/depth/17.png", cv::IMREAD_UNCHANGED);
|
|
cv::Mat rgbImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/rgb/17.jpg", cv::IMREAD_COLOR);
|
|
|
|
ASSERT_FALSE(depthImage.empty());
|
|
ASSERT_FALSE(rgbImage.empty());
|
|
|
|
CameraModel model;
|
|
ASSERT_TRUE(model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/calib/17.yaml"));
|
|
SensorData sensorData(rgbImage, depthImage, model);
|
|
|
|
int decimation = 1;
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.1f;
|
|
std::vector<int> validIndices;
|
|
ParametersMap stereoParameters;
|
|
std::vector<float> roiRatios = {1.2f, 0.1f, 0.9f, 1.5f}; // Invalid ROI ratios
|
|
|
|
// Call the function under test
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud = util3d::cloudRGBFromSensorData(
|
|
sensorData,
|
|
decimation,
|
|
maxDepth,
|
|
minDepth,
|
|
&validIndices,
|
|
stereoParameters,
|
|
roiRatios
|
|
);
|
|
|
|
// Verify the cloud is generated even if ROI ratios are invalid
|
|
ASSERT_NE(cloud, nullptr);
|
|
EXPECT_EQ(cloud->size(), rgbImage.total());
|
|
EXPECT_GT(validIndices.size(), 0);
|
|
}
|
|
|
|
TEST(Util3dTest, LaserScanFromDepthImage) {
|
|
cv::Mat depthImage = cv::imread(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/depth/17.png", cv::IMREAD_UNCHANGED);
|
|
ASSERT_FALSE(depthImage.empty());
|
|
|
|
CameraModel model;
|
|
ASSERT_TRUE(model.load(std::string(RTABMAP_TEST_DATA_ROOT) + "/rgbd/calib/17.yaml"));
|
|
|
|
// Depth range settings
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.5f;
|
|
|
|
// Call the function to get the laser scan
|
|
pcl::PointCloud<pcl::PointXYZ> scan = util3d::laserScanFromDepthImage(
|
|
depthImage, model.fx(), model.fy(), model.cx(), model.cy(), maxDepth, minDepth);
|
|
|
|
// Check that the resulting point cloud has points
|
|
ASSERT_GT(scan.size(), 0);
|
|
for (const auto& pt : scan) {
|
|
if(pcl::isFinite(pt)) {
|
|
ASSERT_GE(pt.z, minDepth); // Depth must be >= minDepth
|
|
ASSERT_LT(pt.z, maxDepth); // Depth must be <= maxDepth
|
|
}
|
|
}
|
|
|
|
// empty image
|
|
ASSERT_THROW(util3d::laserScanFromDepthImage(
|
|
cv::Mat(), 0, 0, 0, 0, 0, 0, CameraModel::opticalRotation()), UException);
|
|
}
|
|
|
|
TEST(Util3dTest, LaserScanFromDepthImageEmptyDepthImage) {
|
|
|
|
}
|
|
|
|
TEST(Util3dTest, LaserScanFromDepthImages) {
|
|
int width = 640;
|
|
int height = 480;
|
|
cv::Mat depthImage(height, width*2, CV_32FC1, cv::Scalar(1.0)); // Depth set to 1.0 for all points
|
|
|
|
// Create camera models (assuming two cameras in a stereo setup)
|
|
std::vector<CameraModel> cameraModels;
|
|
cameraModels.push_back(CameraModel(
|
|
525.0f, 525.0f, 319.5f, 239.5f,
|
|
CameraModel::opticalRotation(), 0, cv::Size(640,480)));
|
|
cameraModels.push_back(CameraModel(
|
|
525.0f, 525.0f, 319.5f, 239.5f,
|
|
Transform(0,0,-M_PI/2.0)*CameraModel::opticalRotation(), 0, cv::Size(640,480)));
|
|
|
|
// Depth range settings
|
|
float maxDepth = 10.0f;
|
|
float minDepth = 0.5f;
|
|
|
|
// Call the function to get the laser scan
|
|
pcl::PointCloud<pcl::PointXYZ> scan = util3d::laserScanFromDepthImages(depthImage, cameraModels, maxDepth, minDepth);
|
|
|
|
// Check that the resulting point cloud has points
|
|
ASSERT_GT(scan.size(), 0);
|
|
for (const auto& pt : scan) {
|
|
ASSERT_TRUE(pcl::isFinite(pt));
|
|
float d = uNorm(pt.x, pt.y, pt.z);
|
|
ASSERT_GE(d, minDepth); // Depth must be >= minDepth
|
|
ASSERT_LT(d, maxDepth); // Depth must be <= maxDepth
|
|
}
|
|
|
|
// invalid model(s)
|
|
ASSERT_THROW(util3d::laserScanFromDepthImages(depthImage, std::vector<CameraModel>(1, cameraModels.front()), 0, 0), UException);
|
|
ASSERT_THROW(util3d::laserScanFromDepthImages(depthImage, std::vector<CameraModel>(1), 0, 0), UException);
|
|
ASSERT_THROW(util3d::laserScanFromDepthImages(depthImage, std::vector<CameraModel>(), 0, 0), UException);
|
|
|
|
// invalid/empty images
|
|
ASSERT_THROW(util3d::laserScanFromDepthImages(depthImage.colRange(0,depthImage.cols/2), cameraModels,0,0), UException);
|
|
ASSERT_THROW(util3d::laserScanFromDepthImages(cv::Mat(), cameraModels,0,0), UException);
|
|
ASSERT_THROW(util3d::laserScanFromDepthImages(cv::Mat(), std::vector<CameraModel>(2),0,0), UException);
|
|
}
|
|
|
|
TEST(Util3dTest, LaserScanFromPointCloudXYZ) {
|
|
pcl::PointCloud<pcl::PointXYZ> cloud;
|
|
cloud.push_back(pcl::PointXYZ(1.0f, 2.0f, 3.0f));
|
|
|
|
LaserScan scan = util3d::laserScanFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
pcl::PointXYZ pt = util3d::laserScanToPoint(scan, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
}
|
|
|
|
// Test for pcl::PointXYZRGB
|
|
TEST(Util3dTest, LaserScanFromPointCloudXYZRGB) {
|
|
pcl::PointCloud<pcl::PointXYZRGB> cloud;
|
|
pcl::PointXYZRGB point;
|
|
point.x = 1.0f; point.y = 2.0f; point.z = 3.0f;
|
|
point.r = 255; point.g = 75; point.b = 120;
|
|
cloud.push_back(point);
|
|
|
|
LaserScan scan = util3d::laserScanFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
EXPECT_TRUE(scan.hasRGB());
|
|
pcl::PointXYZRGB pt = util3d::laserScanToPointRGB(scan, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_EQ(pt.r, 255);
|
|
EXPECT_EQ(pt.g, 75);
|
|
EXPECT_EQ(pt.b, 120);
|
|
}
|
|
|
|
// Test for pcl::PointNormal
|
|
TEST(Util3dTest, LaserScanFromPointCloudNormal) {
|
|
pcl::PointCloud<pcl::PointNormal> cloud;
|
|
pcl::PointNormal point;
|
|
point.x = 1.0f; point.y = 2.0f; point.z = 3.0f;
|
|
point.normal_x = 0.0f; point.normal_y = 0.0f; point.normal_z = 1.0f;
|
|
cloud.push_back(point);
|
|
|
|
LaserScan scan = util3d::laserScanFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
EXPECT_TRUE(scan.hasNormals());
|
|
pcl::PointNormal pt = util3d::laserScanToPointNormal(scan, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 1.0f);
|
|
}
|
|
|
|
// Test for pcl::PointXYZI
|
|
TEST(Util3dTest, LaserScanFromPointCloudXYZI) {
|
|
pcl::PointCloud<pcl::PointXYZI> cloud;
|
|
pcl::PointXYZI point;
|
|
point.x = 1.0f; point.y = 2.0f; point.z = 3.0f; point.intensity = 100.0f;
|
|
cloud.push_back(point);
|
|
|
|
LaserScan scan = util3d::laserScanFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
EXPECT_TRUE(scan.hasIntensity());
|
|
pcl::PointXYZI pt = util3d::laserScanToPointI(scan, 0, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_FLOAT_EQ(pt.intensity, 100.0f);
|
|
}
|
|
|
|
// Test for pcl::PointXYZRGBNormal
|
|
TEST(Util3dTest, LaserScanFromPointCloudXYZRGBNormal) {
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal> cloud;
|
|
pcl::PointXYZRGBNormal point;
|
|
point.x = 1.0f; point.y = 2.0f; point.z = 3.0f;
|
|
point.r = 255; point.g = 75; point.b = 120;
|
|
point.normal_x = 0.0f; point.normal_y = 0.0f; point.normal_z = 1.0f;
|
|
cloud.push_back(point);
|
|
|
|
LaserScan scan = util3d::laserScanFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
EXPECT_TRUE(scan.hasRGB());
|
|
EXPECT_TRUE(scan.hasNormals());
|
|
pcl::PointXYZRGBNormal pt = util3d::laserScanToPointRGBNormal(scan, 0, 0, 0, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_EQ(pt.r, 255);
|
|
EXPECT_EQ(pt.g, 75);
|
|
EXPECT_EQ(pt.b, 120);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 1.0f);
|
|
}
|
|
|
|
// Test for pcl::PointXYZINormal
|
|
TEST(Util3dTest, LaserScanFromPointCloudXYZINormal) {
|
|
pcl::PointCloud<pcl::PointXYZINormal> cloud;
|
|
pcl::PointXYZINormal point;
|
|
point.x = 1.0f; point.y = 2.0f; point.z = 3.0f;
|
|
point.intensity = 100.0f;
|
|
point.normal_x = 0.0f; point.normal_y = 0.0f; point.normal_z = 1.0f;
|
|
cloud.push_back(point);
|
|
|
|
LaserScan scan = util3d::laserScanFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
EXPECT_TRUE(scan.hasIntensity());
|
|
EXPECT_TRUE(scan.hasNormals());
|
|
pcl::PointXYZINormal pt = util3d::laserScanToPointINormal(scan, 0, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_FLOAT_EQ(pt.intensity, 100.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 1.0f);
|
|
}
|
|
|
|
TEST(Util3dTest, LaserScan2dFromPointCloudXYZ) {
|
|
pcl::PointCloud<pcl::PointXYZ> cloud;
|
|
cloud.push_back(pcl::PointXYZ(1.0f, 2.0f, 3.0f));
|
|
|
|
LaserScan scan = util3d::laserScan2dFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
EXPECT_TRUE(scan.is2d());
|
|
pcl::PointXYZ pt = util3d::laserScanToPoint(scan, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 0.0f);
|
|
}
|
|
|
|
// Test for pcl::PointNormal
|
|
TEST(Util3dTest, LaserScan2dFromPointCloudNormal) {
|
|
pcl::PointCloud<pcl::PointNormal> cloud;
|
|
pcl::PointNormal point;
|
|
point.x = 1.0f; point.y = 2.0f; point.z = 3.0f;
|
|
point.normal_x = 0.0f; point.normal_y = 1.0f; point.normal_z = 0.0f;
|
|
cloud.push_back(point);
|
|
|
|
LaserScan scan = util3d::laserScan2dFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
EXPECT_TRUE(scan.is2d());
|
|
EXPECT_TRUE(scan.hasNormals());
|
|
pcl::PointNormal pt = util3d::laserScanToPointNormal(scan, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 0.0f);
|
|
}
|
|
|
|
// Test for pcl::PointXYZI
|
|
TEST(Util3dTest, LaserScan2dFromPointCloudXYZI) {
|
|
pcl::PointCloud<pcl::PointXYZI> cloud;
|
|
pcl::PointXYZI point;
|
|
point.x = 1.0f; point.y = 2.0f; point.z = 3.0f; point.intensity = 100.0f;
|
|
cloud.push_back(point);
|
|
|
|
LaserScan scan = util3d::laserScan2dFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
EXPECT_TRUE(scan.is2d());
|
|
EXPECT_TRUE(scan.hasIntensity());
|
|
pcl::PointXYZI pt = util3d::laserScanToPointI(scan, 0, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.intensity, 100.0f);
|
|
}
|
|
|
|
// Test for pcl::PointXYZINormal
|
|
TEST(Util3dTest, LaserScan2dFromPointCloudXYZINormal) {
|
|
pcl::PointCloud<pcl::PointXYZINormal> cloud;
|
|
pcl::PointXYZINormal point;
|
|
point.x = 1.0f; point.y = 2.0f; point.z = 3.0f;
|
|
point.intensity = 100.0f;
|
|
point.normal_x = 0.0f; point.normal_y = 1.0f; point.normal_z = 0.0f;
|
|
cloud.push_back(point);
|
|
|
|
LaserScan scan = util3d::laserScan2dFromPointCloud(cloud);
|
|
|
|
EXPECT_EQ(scan.size(), 1);
|
|
EXPECT_TRUE(scan.is2d());
|
|
EXPECT_TRUE(scan.hasIntensity());
|
|
EXPECT_TRUE(scan.hasNormals());
|
|
pcl::PointXYZINormal pt = util3d::laserScanToPointINormal(scan, 0, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.intensity, 100.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 0.0f);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TEST(Util3dTest, LaserScanToPointCloud)
|
|
{
|
|
// XYZ point
|
|
cv::Mat dataXYZ = (cv::Mat_<float>(1, 3) << 1.0f, 2.0f, 3.0f);
|
|
LaserScan scanXYZ = LaserScan(dataXYZ.reshape(3, 1), 1, 0.0f, LaserScan::kXYZ, Transform::getIdentity());
|
|
|
|
// XYZ + Normals
|
|
cv::Mat dataNormal = (cv::Mat_<float>(1, 6) << 1.0f, 2.0f, 3.0f, 0.0f, 0.0f, 1.0f);
|
|
LaserScan scanNormal = LaserScan(dataNormal.reshape(6, 1), 1, 0.0f, LaserScan::kXYZNormal, Transform::getIdentity());
|
|
|
|
// XYZ + RGB
|
|
cv::Mat dataRGB = (cv::Mat_<float>(1, 4) << 1.0f, 2.0f, 3.0f, LaserScan::packRGB(255,128,64));
|
|
LaserScan scanRGB = LaserScan(dataRGB.reshape(4, 1), 1, 0.0f, LaserScan::kXYZRGB, Transform::getIdentity());
|
|
|
|
// XYZ + Intensity
|
|
cv::Mat dataI = (cv::Mat_<float>(1, 4) << 1.0f, 2.0f, 3.0f, 0.5f);
|
|
LaserScan scanI = LaserScan(dataI.reshape(4, 1), 1, 0.0f, LaserScan::kXYZI, Transform::getIdentity());
|
|
|
|
// XYZ + RGB + Normals
|
|
cv::Mat dataRGBNormal = (cv::Mat_<float>(1, 7) << 1.0f, 2.0f, 3.0f, LaserScan::packRGB(255,128,64), 0.0f, 1.0f, 0.0f);
|
|
LaserScan scanRGBNormal = LaserScan(dataRGBNormal.reshape(7, 1), 1, 0.0f, LaserScan::kXYZRGBNormal, Transform::getIdentity());
|
|
|
|
// XYZ + Intensity + Normals
|
|
cv::Mat dataINormal = (cv::Mat_<float>(1, 7) << 1.0f, 2.0f, 3.0f, 0.75f, 1.0f, 0.0f, 0.0f);
|
|
LaserScan scanINormal = LaserScan(dataINormal.reshape(7, 1), 1, 0.0f, LaserScan::kXYZINormal, Transform::getIdentity());
|
|
|
|
{
|
|
pcl::PointXYZ pt = util3d::laserScanToPoint(scanXYZ, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
auto cloud = util3d::laserScanToPointCloud(scanXYZ);
|
|
ASSERT_EQ(cloud->size(), 1u);
|
|
pt = cloud->points[0];
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
}
|
|
{
|
|
pcl::PointNormal pt = util3d::laserScanToPointNormal(scanNormal, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 1.0f);
|
|
auto cloud = util3d::laserScanToPointCloudNormal(scanNormal);
|
|
ASSERT_EQ(cloud->size(), 1u);
|
|
pt = cloud->points[0];
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 1.0f);
|
|
}
|
|
{
|
|
pcl::PointXYZRGB pt = util3d::laserScanToPointRGB(scanRGB, 0);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_EQ(pt.r, 255);
|
|
EXPECT_EQ(pt.g, 128);
|
|
EXPECT_EQ(pt.b, 64);
|
|
auto cloud = util3d::laserScanToPointCloudRGB(scanRGB);
|
|
ASSERT_EQ(cloud->size(), 1u);
|
|
pt = cloud->points[0];
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_EQ(pt.r, 255);
|
|
EXPECT_EQ(pt.g, 128);
|
|
EXPECT_EQ(pt.b, 64);
|
|
}
|
|
{
|
|
pcl::PointXYZI pt = util3d::laserScanToPointI(scanI, 0, 0.5f);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_FLOAT_EQ(pt.intensity, 0.5f);
|
|
auto cloud = util3d::laserScanToPointCloudI(scanI);
|
|
ASSERT_EQ(cloud->size(), 1u);
|
|
pt = cloud->points[0];
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_FLOAT_EQ(pt.intensity, 0.5f);
|
|
}
|
|
{
|
|
pcl::PointXYZRGBNormal pt = util3d::laserScanToPointRGBNormal(scanRGBNormal, 0, 100, 100, 100);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_EQ(pt.r, 255);
|
|
EXPECT_EQ(pt.g, 128);
|
|
EXPECT_EQ(pt.b, 64);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 0.0f);
|
|
auto cloud = util3d::laserScanToPointCloudRGBNormal(scanRGBNormal);
|
|
ASSERT_EQ(cloud->size(), 1u);
|
|
pt = cloud->points[0];
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_EQ(pt.r, 255);
|
|
EXPECT_EQ(pt.g, 128);
|
|
EXPECT_EQ(pt.b, 64);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 0.0f);
|
|
}
|
|
{
|
|
pcl::PointXYZINormal pt = util3d::laserScanToPointINormal(scanINormal, 0, 0.75f);
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_FLOAT_EQ(pt.intensity, 0.75f);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 0.0f);
|
|
auto cloud = util3d::laserScanToPointCloudINormal(scanINormal);
|
|
ASSERT_EQ(cloud->size(), 1u);
|
|
pt = cloud->points[0];
|
|
EXPECT_FLOAT_EQ(pt.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(pt.z, 3.0f);
|
|
EXPECT_FLOAT_EQ(pt.intensity, 0.75f);
|
|
EXPECT_FLOAT_EQ(pt.normal_x, 1.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_y, 0.0f);
|
|
EXPECT_FLOAT_EQ(pt.normal_z, 0.0f);
|
|
}
|
|
}
|
|
|
|
TEST(Util3dTest, GetMinMax3D) {
|
|
// Create a 3x3 CV matrix of type CV_32FC3 (3D points)
|
|
cv::Mat laserScan(1, 3, CV_32FC3);
|
|
float data[9] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, -1.0f, -2.0f, -3.0f};
|
|
memcpy(laserScan.data, data, sizeof(data));
|
|
|
|
{
|
|
// cv::Point3f
|
|
cv::Point3f min, max;
|
|
util3d::getMinMax3D(laserScan, min, max);
|
|
|
|
// Test the expected min and max values
|
|
EXPECT_FLOAT_EQ(min.x, -1.0f);
|
|
EXPECT_FLOAT_EQ(min.y, -2.0f);
|
|
EXPECT_FLOAT_EQ(min.z, -3.0f);
|
|
EXPECT_FLOAT_EQ(max.x, 4.0f);
|
|
EXPECT_FLOAT_EQ(max.y, 5.0f);
|
|
EXPECT_FLOAT_EQ(max.z, 6.0f);
|
|
}
|
|
|
|
// pcl::PointXYZ
|
|
{
|
|
pcl::PointXYZ min, max;
|
|
util3d::getMinMax3D(laserScan, min, max);
|
|
|
|
// Test the expected min and max values
|
|
EXPECT_FLOAT_EQ(min.x, -1.0f);
|
|
EXPECT_FLOAT_EQ(min.y, -2.0f);
|
|
EXPECT_FLOAT_EQ(min.z, -3.0f);
|
|
EXPECT_FLOAT_EQ(max.x, 4.0f);
|
|
EXPECT_FLOAT_EQ(max.y, 5.0f);
|
|
EXPECT_FLOAT_EQ(max.z, 6.0f);
|
|
}
|
|
}
|
|
|
|
// Test with a laser scan that contains only 2D data (no Z values)
|
|
TEST(Util3dTest, GetMinMax3DCVPoint3f2DData) {
|
|
// Create a 1x2 CV matrix of type CV_32FC2 (2D points)
|
|
cv::Mat laserScan(1, 2, CV_32FC2);
|
|
// 2 points * 2 channels = 4 floats.
|
|
float data[4] = {1.0f, 2.0f, 4.0f, 5.0f}; // No Z values
|
|
memcpy(laserScan.data, data, sizeof(data));
|
|
|
|
cv::Point3f min, max;
|
|
|
|
// Call the function
|
|
util3d::getMinMax3D(laserScan, min, max);
|
|
|
|
// Test the expected min and max values
|
|
EXPECT_FLOAT_EQ(min.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(min.y, 2.0f);
|
|
EXPECT_FLOAT_EQ(min.z, 0.0f); // Z is set to 0
|
|
EXPECT_FLOAT_EQ(max.x, 4.0f);
|
|
EXPECT_FLOAT_EQ(max.y, 5.0f);
|
|
EXPECT_FLOAT_EQ(max.z, 0.0f); // Z is set to 0
|
|
}
|
|
|
|
// Test with a matrix where all points are the same (edge case)
|
|
TEST(Util3dTest, GetMinMax3DSamePoints) {
|
|
// Create a 1x3 CV matrix of type CV_32FC3 where all points are the same
|
|
cv::Mat laserScan(1, 3, CV_32FC3);
|
|
float data[9] = {2.0f, 3.0f, 4.0f, 2.0f, 3.0f, 4.0f, 2.0f, 3.0f, 4.0f};
|
|
memcpy(laserScan.data, data, sizeof(data));
|
|
|
|
cv::Point3f min, max;
|
|
|
|
// Call the function
|
|
util3d::getMinMax3D(laserScan, min, max);
|
|
|
|
// Test that min and max are the same for all points
|
|
EXPECT_FLOAT_EQ(min.x, 2.0f);
|
|
EXPECT_FLOAT_EQ(min.y, 3.0f);
|
|
EXPECT_FLOAT_EQ(min.z, 4.0f);
|
|
EXPECT_FLOAT_EQ(max.x, 2.0f);
|
|
EXPECT_FLOAT_EQ(max.y, 3.0f);
|
|
EXPECT_FLOAT_EQ(max.z, 4.0f);
|
|
}
|
|
|
|
// Test with an empty laser scan (should trigger an error or assertion)
|
|
TEST(Util3dTest, GetMinMax3DEmptyLaserScan) {
|
|
// Create an empty CV matrix
|
|
cv::Mat laserScan;
|
|
|
|
cv::Point3f min, max;
|
|
|
|
// Expect the function to assert or throw an exception
|
|
EXPECT_THROW(util3d::getMinMax3D(laserScan, min, max), UException);
|
|
}
|
|
|
|
// Test for projectDisparityTo3D with valid disparity
|
|
TEST(Util3dTest, ProjectDisparityTo3DValidDisparity) {
|
|
StereoCameraModel model(525, 525, 319.5f, 219.5f, 0.05f, CameraModel::opticalRotation(), cv::Size(640,480));
|
|
|
|
// Define a 2D point (u, v) in the left image
|
|
cv::Point2f pt(100.0f, 150.0f);
|
|
float disparity = 10.0f; // Example disparity value in pixels
|
|
|
|
// Call the function
|
|
cv::Point3f result = util3d::projectDisparityTo3D(pt, disparity, model);
|
|
|
|
// Check that the result is not NaN and the z value is greater than 0 (since disparity > 0)
|
|
ASSERT_FALSE(std::isnan(result.x));
|
|
ASSERT_FALSE(std::isnan(result.y));
|
|
ASSERT_FALSE(std::isnan(result.z));
|
|
ASSERT_GT(result.z, 0.0f);
|
|
}
|
|
|
|
// Test for projectDisparityTo3D with invalid disparity (0.0f)
|
|
TEST(Util3dTest, ProjectDisparityTo3DInvalidDisparityZero) {
|
|
StereoCameraModel model(525, 525, 319.5f, 219.5f, 0.05f, CameraModel::opticalRotation(), cv::Size(640,480));
|
|
|
|
// Define a 2D point (u, v) in the left image
|
|
cv::Point2f pt(100.0f, 150.0f);
|
|
float disparity = 0.0f; // Invalid disparity value (disparity can't be 0)
|
|
|
|
// Call the function
|
|
cv::Point3f result = util3d::projectDisparityTo3D(pt, disparity, model);
|
|
|
|
// Check that the result is NaN
|
|
ASSERT_TRUE(std::isnan(result.x));
|
|
ASSERT_TRUE(std::isnan(result.y));
|
|
ASSERT_TRUE(std::isnan(result.z));
|
|
}
|
|
|
|
// Test for projectDisparityTo3D with disparity map (CV_32FC1 type)
|
|
TEST(Util3dTest, ProjectDisparityTo3DDisparityMapValid) {
|
|
StereoCameraModel model(525, 525, 319.5f, 219.5f, 0.05f, CameraModel::opticalRotation(), cv::Size(640,480));
|
|
|
|
// Create a sample disparity map (1 channel, float type)
|
|
cv::Mat disparity_map(480, 640, CV_32FC1, cv::Scalar(2.0f)); // All pixels have a disparity of 2
|
|
|
|
// Define a point in the left image (u, v)
|
|
cv::Point2f pt(320, 240); // Test center of the disparity map
|
|
|
|
// Call the function
|
|
cv::Point3f result = util3d::projectDisparityTo3D(pt, disparity_map, model);
|
|
|
|
// Check that the result is not NaN and has a valid z value
|
|
ASSERT_FALSE(std::isnan(result.x));
|
|
ASSERT_FALSE(std::isnan(result.y));
|
|
ASSERT_FALSE(std::isnan(result.z));
|
|
ASSERT_GT(result.z, 0.0f);
|
|
}
|
|
|
|
// Test for projectDisparityTo3D with disparity map out of bounds (invalid pixel)
|
|
TEST(Util3dTest, ProjectDisparityTo3DDisparityMapOutOfBounds) {
|
|
StereoCameraModel model(525, 525, 319.5f, 219.5f, 0.05f, CameraModel::opticalRotation(), cv::Size(640,480));
|
|
|
|
// Create a sample disparity map (1 channel, float type)
|
|
cv::Mat disparity_map(480, 640, CV_32FC1, cv::Scalar(2.f)); // All pixels have a disparity of 2
|
|
|
|
// Define a point outside the disparity map (invalid u, v)
|
|
cv::Point2f pt(720.0f, 20.0f); // Point is out of bounds
|
|
|
|
// Call the function
|
|
cv::Point3f result = util3d::projectDisparityTo3D(pt, disparity_map, model);
|
|
|
|
// Check that the result is NaN since the point is out of bounds
|
|
ASSERT_TRUE(std::isnan(result.x));
|
|
ASSERT_TRUE(std::isnan(result.y));
|
|
ASSERT_TRUE(std::isnan(result.z));
|
|
}
|
|
|
|
// Test for projectDisparityTo3D with invalid disparity map type
|
|
TEST(Util3dTest, ProjectDisparityTo3DInvalidDisparityMapType) {
|
|
StereoCameraModel model(525, 525, 319.5f, 219.5f, 0.05f, CameraModel::opticalRotation(), cv::Size(640,480));
|
|
|
|
// Create a sample disparity map with invalid type (CV_8UC1)
|
|
cv::Mat disparity_map(480, 640, CV_8UC1, cv::Scalar(2)); // Invalid type (should be CV_32FC1 or CV_16SC1)
|
|
|
|
// Define a point in the left image (u, v)
|
|
cv::Point2f pt(5.0f, 5.0f); // Test center of the disparity map
|
|
|
|
// Call the function
|
|
ASSERT_THROW(util3d::projectDisparityTo3D(pt, disparity_map, model), UException);
|
|
}
|
|
|
|
TEST(Util3dTest, ProjectCloudToCameraCvMatLaserScan) {
|
|
// Mock image size
|
|
cv::Size imageSize(640, 480);
|
|
|
|
// Mock camera matrix (fx, fy, cx, cy)
|
|
cv::Mat cameraMatrixK = (cv::Mat_<double>(3, 3) << 500, 0, 320, 0, 500, 240, 0, 0, 1);
|
|
|
|
// Create mock laser scan (CV_32FC2)
|
|
cv::Mat laserScan = cv::Mat::zeros(1, 640, CV_32FC2); // 2D points
|
|
for (int i = 0; i < 640; ++i) {
|
|
laserScan.at<cv::Vec2f>(0, i) = cv::Vec2f(10, (i-640/2)*0.1); // Mock some points
|
|
}
|
|
|
|
Transform cameraTransform = CameraModel::opticalRotation();
|
|
|
|
// Call the function, normally we should retrieve a depth image with depth values only on the middle row
|
|
cv::Mat result = util3d::projectCloudToCamera(imageSize, cameraMatrixK, laserScan, cameraTransform);
|
|
|
|
// Test the result (checking the type and size of the result)
|
|
EXPECT_EQ(result.type(), CV_32FC1);
|
|
EXPECT_EQ(result.size(), imageSize);
|
|
EXPECT_EQ(cv::countNonZero(result.rowRange(0,480/2-1)), 0);
|
|
EXPECT_EQ(cv::countNonZero(result.rowRange(480/2+1,480)), 0);
|
|
EXPECT_GT(cv::countNonZero(result.rowRange(480/2-1, 480/2+1)), 0);
|
|
cv::Mat mask = result != 0;
|
|
EXPECT_EQ(cv::mean(result, mask), cv::Scalar(10));
|
|
}
|
|
|
|
// Test for projectCloudToCamera with pcl::PointCloud<pcl::PointXYZ>
|
|
TEST(Util3dTest, ProjectCloudToCameraPclPointCloud) {
|
|
// Mock image size
|
|
cv::Size imageSize(640, 480);
|
|
|
|
// Mock camera matrix (fx, fy, cx, cy)
|
|
cv::Mat cameraMatrixK = (cv::Mat_<double>(3, 3) << 500, 0, 320, 0, 500, 240, 0, 0, 1);
|
|
|
|
// Create mock pcl::PointCloud<pcl::PointXYZ>
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr laserScan(new pcl::PointCloud<pcl::PointXYZ>());
|
|
for (int i = 0; i < 640; ++i) {
|
|
pcl::PointXYZ point(10, (i-640/2)*0.1, 0); // horizontal line
|
|
laserScan->points.push_back(point);
|
|
point = pcl::PointXYZ(10, 0, (i-640/2)*0.1); // vertical line
|
|
laserScan->points.push_back(point);
|
|
}
|
|
|
|
Transform cameraTransform = CameraModel::opticalRotation();
|
|
|
|
// Call the function
|
|
cv::Mat result = util3d::projectCloudToCamera(imageSize, cameraMatrixK, laserScan, cameraTransform);
|
|
|
|
// Test the result (checking the type and size of the result)
|
|
EXPECT_EQ(result.type(), CV_32FC1);
|
|
EXPECT_EQ(result.size(), imageSize);
|
|
EXPECT_EQ(cv::countNonZero(result(cv::Range(0,480/2-1), cv::Range(0,640/2-1))), 0); // top-left quadrant
|
|
EXPECT_EQ(cv::countNonZero(result(cv::Range(0,480/2-1), cv::Range(640/2+1, 640))), 0); // top-right quadrant
|
|
EXPECT_EQ(cv::countNonZero(result(cv::Range(480/2+1, 480), cv::Range(0,640/2-1))), 0); // bottom-left quadrant
|
|
EXPECT_EQ(cv::countNonZero(result(cv::Range(480/2+1, 480), cv::Range(640/2+1, 640))), 0); // bottom-right quadrant
|
|
EXPECT_GT(cv::countNonZero(result.colRange(640/2-1,640/2+1)), 0); // vertical line
|
|
EXPECT_GT(cv::countNonZero(result.rowRange(480/2-1, 480/2+1)), 0); // horizontal line
|
|
cv::Mat mask = result != 0;
|
|
EXPECT_EQ(cv::mean(result, mask), cv::Scalar(10));
|
|
}
|
|
|
|
// Test for projectCloudToCamera with pcl::PCLPointCloud2
|
|
TEST(Util3dTest, ProjectCloudToCameraPCLPointCloud2) {
|
|
// Mock image size
|
|
cv::Size imageSize(640, 480);
|
|
|
|
// Mock camera matrix (fx, fy, cx, cy)
|
|
cv::Mat cameraMatrixK = (cv::Mat_<double>(3, 3) << 500, 0, 320, 0, 500, 240, 0, 0, 1);
|
|
|
|
// Create mock pcl::PCLPointCloud2
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>());
|
|
for (int i = 0; i < 640; ++i) {
|
|
pcl::PointXYZ point(10, (i-640/2)*0.1, 0); // horizontal line
|
|
cloud->points.push_back(point);
|
|
point = pcl::PointXYZ(10, 0, (i-640/2)*0.1); // vertical line
|
|
cloud->points.push_back(point);
|
|
}
|
|
pcl::PCLPointCloud2::Ptr laserScan(new pcl::PCLPointCloud2());
|
|
pcl::toPCLPointCloud2(*cloud, *laserScan);
|
|
|
|
Transform cameraTransform = CameraModel::opticalRotation();
|
|
|
|
// Call the function
|
|
cv::Mat result = util3d::projectCloudToCamera(imageSize, cameraMatrixK, laserScan, cameraTransform);
|
|
|
|
// Test the result (checking the type and size of the result)
|
|
EXPECT_EQ(result.type(), CV_32FC1);
|
|
EXPECT_EQ(result.size(), imageSize);
|
|
EXPECT_EQ(cv::countNonZero(result(cv::Range(0,480/2-1), cv::Range(0,640/2-1))), 0); // top-left quadrant
|
|
EXPECT_EQ(cv::countNonZero(result(cv::Range(0,480/2-1), cv::Range(640/2+1, 640))), 0); // top-right quadrant
|
|
EXPECT_EQ(cv::countNonZero(result(cv::Range(480/2+1, 480), cv::Range(0,640/2-1))), 0); // bottom-left quadrant
|
|
EXPECT_EQ(cv::countNonZero(result(cv::Range(480/2+1, 480), cv::Range(640/2+1, 640))), 0); // bottom-right quadrant
|
|
EXPECT_GT(cv::countNonZero(result.colRange(640/2-1,640/2+1)), 0); // vertical line
|
|
EXPECT_GT(cv::countNonZero(result.rowRange(480/2-1, 480/2+1)), 0); // horizontal line
|
|
cv::Mat mask = result != 0;
|
|
EXPECT_EQ(cv::mean(result, mask), cv::Scalar(10));
|
|
}
|
|
|
|
// Helper function to compare matrices
|
|
bool compareMatrices(const cv::Mat& mat1, const cv::Mat& mat2, float tolerance = 1e-5) {
|
|
if (mat1.size() != mat2.size() || mat1.type() != mat2.type()) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < mat1.rows; ++i) {
|
|
for (int j = 0; j < mat1.cols; ++j) {
|
|
if (std::abs(mat1.at<float>(i, j) - mat2.at<float>(i, j)) > tolerance) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Helper function to create a sample registered depth image (for testing)
|
|
cv::Mat createTestDepthImage(int rows, int cols) {
|
|
cv::Mat depthImage = cv::Mat::zeros(rows, cols, CV_32FC1);
|
|
// Manually set some non-zero values to simulate depth points
|
|
depthImage.at<float>(2, 3) = 1.0f;
|
|
depthImage.at<float>(5, 4) = 2.0f;
|
|
depthImage.at<float>(7, 7) = 1.5f;
|
|
return depthImage;
|
|
}
|
|
|
|
TEST(Util3dTest, FillProjectedCloudHolesFillTest) {
|
|
cv::Mat depthImage = cv::Mat::zeros(10, 10, CV_32FC1);
|
|
depthImage.at<float>(2, 3) = 10.0f;
|
|
depthImage.at<float>(4, 3) = 10.0f;
|
|
depthImage.at<float>(2, 5) = 10.0f;
|
|
|
|
cv::Mat original = depthImage.clone();
|
|
|
|
// Call the function to fill the holes vertically
|
|
util3d::fillProjectedCloudHoles(depthImage, true, false);
|
|
|
|
EXPECT_FLOAT_EQ(depthImage.at<float>(3, 3), 10.0f);
|
|
EXPECT_FLOAT_EQ(depthImage.at<float>(2, 4), 0.0f);
|
|
|
|
// Call the function to fill the holes horizontally
|
|
depthImage = original.clone();
|
|
util3d::fillProjectedCloudHoles(depthImage, false, false);
|
|
EXPECT_FLOAT_EQ(depthImage.at<float>(3, 3), 0.0f);
|
|
EXPECT_FLOAT_EQ(depthImage.at<float>(2, 4), 10.0f);
|
|
|
|
// Call the function to fill to border vertically
|
|
depthImage = original.clone();
|
|
util3d::fillProjectedCloudHoles(depthImage, true, true);
|
|
EXPECT_EQ(cv::countNonZero(depthImage.colRange(0, 3)), 0);
|
|
EXPECT_EQ(cv::countNonZero(depthImage.colRange(3, 4)), 8); // current implementation only fills up to last pixel
|
|
EXPECT_EQ(cv::countNonZero(depthImage.colRange(4, 5)), 0);
|
|
EXPECT_EQ(cv::countNonZero(depthImage.colRange(5, 6)), 8);
|
|
EXPECT_EQ(cv::countNonZero(depthImage.colRange(6, 10)), 0);
|
|
|
|
// Call the function to fill to border horizontally
|
|
depthImage = original.clone();
|
|
util3d::fillProjectedCloudHoles(depthImage, false, true);
|
|
EXPECT_EQ(cv::countNonZero(depthImage.rowRange(0, 2)), 0);
|
|
EXPECT_EQ(cv::countNonZero(depthImage.rowRange(2, 3)), 8);
|
|
EXPECT_EQ(cv::countNonZero(depthImage.rowRange(3, 4)), 0);
|
|
EXPECT_EQ(cv::countNonZero(depthImage.rowRange(4, 5)), 8);
|
|
EXPECT_EQ(cv::countNonZero(depthImage.rowRange(5, 10)), 0);
|
|
|
|
//Test case with no holes in the depth image (nothing should change).
|
|
depthImage = cv::Mat::ones(10, 10, CV_32FC1); // No holes, all values are 1.0f
|
|
|
|
// Call the function to fill the holes (shouldn't change anything)
|
|
util3d::fillProjectedCloudHoles(depthImage, true, true); // Fill to border
|
|
|
|
// Assert that the image hasn't changed
|
|
ASSERT_EQ(cv::mean(depthImage), cv::Scalar(1.0f));
|
|
}
|
|
|
|
TEST(Util3dTest, FilterFloorThreshold) {
|
|
// Setup: Create a synthetic depth image and CameraModel
|
|
cv::Mat depth = cv::Mat::zeros(4, 4, CV_16UC1);
|
|
depth.at<unsigned short>(1, 1) = 1000;
|
|
depth.at<unsigned short>(2, 2) = 500;
|
|
depth.at<unsigned short>(3, 3) = 1500;
|
|
|
|
// Camera Model: camera is 75 cm over the ground, looking directly down
|
|
std::vector<CameraModel> cameraModels;
|
|
cameraModels.push_back(CameraModel(500.0f, 500.0f, 320.0f, 240.0f,
|
|
Transform(0,0,0.75, 0,M_PI/2,0)*CameraModel::opticalRotation(), 0, cv::Size(640, 480)));
|
|
|
|
// Test case where threshold is 10 cm
|
|
float threshold = 0.1f; // In meters
|
|
|
|
cv::Mat depthBelow;
|
|
|
|
// Call the filterFloor function
|
|
cv::Mat filteredDepth = util3d::filterFloor(depth, cameraModels, threshold, &depthBelow);
|
|
|
|
// Check that depthBelow contains the points below the threshold
|
|
EXPECT_EQ(depthBelow.at<unsigned short>(1, 1), 1000);
|
|
EXPECT_EQ(depthBelow.at<unsigned short>(2, 2), 0);
|
|
EXPECT_EQ(depthBelow.at<unsigned short>(3, 3), 1500);
|
|
|
|
// Check that depth kept points over the threshold
|
|
EXPECT_EQ(filteredDepth.at<unsigned short>(1, 1), 0);
|
|
EXPECT_EQ(filteredDepth.at<unsigned short>(2, 2), 500);
|
|
EXPECT_EQ(filteredDepth.at<unsigned short>(3, 3), 0);
|
|
}
|
|
|
|
TEST(Util3dTest, FilterFloorEmptyDepthImage) {
|
|
// Test case with empty depth image
|
|
cv::Mat emptyDepth = cv::Mat::zeros(0, 0, CV_16UC1);
|
|
std::vector<CameraModel> cameraModels;
|
|
cameraModels.push_back(CameraModel(500.0f, 500.0f, 320.0f, 240.0f, CameraModel::opticalRotation(), 0, cv::Size(640, 480)));
|
|
|
|
cv::Mat depthBelow;
|
|
cv::Mat filteredDepth = util3d::filterFloor(emptyDepth, cameraModels, 1.0f, &depthBelow);
|
|
|
|
// Expect empty matrices for both filteredDepth and depthBelow
|
|
EXPECT_TRUE(filteredDepth.empty());
|
|
EXPECT_TRUE(depthBelow.empty());
|
|
}
|
|
|
|
TEST(Util3dTest, ProjectCloudToCameras) {
|
|
|
|
// Create mock point cloud
|
|
pcl::PointCloud<pcl::PointXYZRGBNormal> cloud;
|
|
pcl::PointXYZRGBNormal pt1;
|
|
pt1.x = 1.0f;
|
|
pt1.y = 0.0f;
|
|
pt1.z = 0.0f;
|
|
pt1.normal_x = 0.0f;
|
|
pt1.normal_y = 0.0f;
|
|
pt1.normal_z = 1.0f; // Normal pointing upwards
|
|
cloud.push_back(pt1);
|
|
|
|
pcl::PointXYZRGBNormal pt2;
|
|
pt2.x = 1.0f;
|
|
pt2.y = 0.05f;
|
|
pt2.z = 0.0f;
|
|
pt2.normal_x = -1.0f; // Normal pointing backwards
|
|
pt2.normal_y = 0.0f;
|
|
pt2.normal_z = 0.0f;
|
|
cloud.push_back(pt2);
|
|
|
|
pcl::PointXYZRGBNormal pt3;
|
|
pt3.x = 0.5f;
|
|
pt3.y = 1.0f;
|
|
pt3.z = 0.0f;
|
|
pt3.normal_x = 0.0f;
|
|
pt3.normal_y = -1.0f; // Normal pointing right
|
|
pt3.normal_z = 0.0f;
|
|
cloud.push_back(pt3);
|
|
|
|
pcl::PointXYZRGBNormal pt4;
|
|
pt4.x = 1.0f;
|
|
pt4.y = 0.0f;
|
|
pt4.z = -0.025f; // below pt1 by 2.5 cm
|
|
pt4.normal_x = 0.0f;
|
|
pt4.normal_y = 0.0f;
|
|
pt4.normal_z = 1.0f; // Normal pointing upwards
|
|
cloud.push_back(pt4);
|
|
|
|
pcl::PointXYZRGBNormal pt5;
|
|
pt5.x = 1.0f;
|
|
pt5.y = 0.0f;
|
|
pt5.z = -0.1f; // below pt1 by 10 cm
|
|
pt5.normal_x = 0.0f;
|
|
pt5.normal_y = 0.0f;
|
|
pt5.normal_z = 1.0f; // Normal pointing upwards
|
|
cloud.push_back(pt5);
|
|
|
|
std::map<int, Transform> cameraPoses;
|
|
cameraPoses[1] = Transform(0.5f, 0.0f, 0.0f,0,0,0); // Camera 1 looking at pt2, but closer to pt1 than camera 2
|
|
cameraPoses[2] = Transform(1.0f, 0.0f, 1.0f,0,M_PI/2,0); // Camera 2 looking at pt1 (looking down)
|
|
|
|
std::map<int, std::vector<CameraModel>> cameraModels;
|
|
CameraModel model(500, 500, 319.5f, 239.5f, CameraModel::opticalRotation(), 0, cv::Size(640,480));
|
|
cameraModels[1].push_back(model);
|
|
cameraModels[2].push_back(model);
|
|
|
|
model.setLocalTransform(Transform(0,0,0,0,0,M_PI/2)*CameraModel::opticalRotation()); // this view from camera 1 position is looking left (only pt3 in FOV)
|
|
cameraModels[1].push_back(model);
|
|
|
|
// Set parameters for projection
|
|
float maxDistance = 10.0f;
|
|
float maxAngle = 45.0f * M_PI/ 180.0f;
|
|
float maxDepthError = 0.05f; // For camera 1, it should see pt1 and pt4, but not pt5
|
|
std::vector<float> roiRatios = {0.0f, 0.0f, 0.0f, 0.0f}; // Full image ROI
|
|
cv::Mat projMask = cv::Mat::ones(480, 640, CV_8UC1); // Projection mask (all valid)
|
|
bool distanceToCamPolicy = true;
|
|
ProgressState* state = nullptr; // Not using progress state in this test
|
|
|
|
// Call the function to test
|
|
auto result = util3d::projectCloudToCameras(cloud, cameraPoses, cameraModels, maxDistance, maxAngle, maxDepthError, roiRatios, projMask, distanceToCamPolicy, state);
|
|
|
|
// Validate the result
|
|
ASSERT_EQ(result.size(), cloud.size()); // The result should have the same size as the input point cloud
|
|
|
|
EXPECT_EQ(result[0].first.first, 2); // Camera node ID
|
|
EXPECT_EQ(result[0].first.second, 0); // Camera index
|
|
EXPECT_NEAR(result[0].second.x, 0.5f, 0.1f); // UV x-coordinate, close to the center
|
|
EXPECT_NEAR(result[0].second.y, 0.5f, 0.1f); // UV y-coordinate, close to the center
|
|
|
|
EXPECT_EQ(result[1].first.first, 1); // Camera node ID
|
|
EXPECT_EQ(result[1].first.second, 0); // Camera index
|
|
EXPECT_NEAR(result[1].second.x, 0.5f, 0.1f); // UV x-coordinate, close to the center
|
|
EXPECT_NEAR(result[1].second.y, 0.5f, 0.1f); // UV y-coordinate, close to the center
|
|
|
|
EXPECT_EQ(result[2].first.first, 1); // Camera node ID
|
|
EXPECT_EQ(result[2].first.second, 1); // Camera index
|
|
EXPECT_NEAR(result[2].second.x, 0.5f, 0.1f); // UV x-coordinate, close to the center
|
|
EXPECT_NEAR(result[2].second.y, 0.5f, 0.1f); // UV y-coordinate, close to the center
|
|
|
|
EXPECT_EQ(result[3].first.first, 2); // Camera node ID
|
|
EXPECT_EQ(result[3].first.second, 0); // Camera index
|
|
EXPECT_NEAR(result[3].second.x, 0.5f, 0.1f); // UV x-coordinate, close to the center
|
|
EXPECT_NEAR(result[3].second.y, 0.5f, 0.1f); // UV y-coordinate, close to the center
|
|
|
|
EXPECT_EQ(result[4].first.first, 0); // Camera node ID (not found = 0)
|
|
}
|
|
|
|
TEST(Util3dTest, IsFinite) {
|
|
cv::Point3f pt1(1.0f, 2.0f, 3.0f);
|
|
EXPECT_TRUE(util3d::isFinite(pt1));
|
|
|
|
cv::Point3f pt2(NAN, 2.0f, 3.0f);
|
|
EXPECT_FALSE(util3d::isFinite(pt2));
|
|
|
|
cv::Point3f pt3(1.0f, INFINITY, 3.0f);
|
|
EXPECT_FALSE(util3d::isFinite(pt3));
|
|
|
|
cv::Point3f pt4(NAN, -INFINITY, INFINITY);
|
|
EXPECT_FALSE(util3d::isFinite(pt4));
|
|
}
|
|
|
|
TEST(Util3dTest, ConcatenateCloudsXYZ)
|
|
{
|
|
std::list<pcl::PointCloud<pcl::PointXYZ>::Ptr> cloudList;
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud1(new pcl::PointCloud<pcl::PointXYZ>());
|
|
cloud1->push_back(pcl::PointXYZ(1, 2, 3));
|
|
cloud1->push_back(pcl::PointXYZ(4, 5, 6));
|
|
|
|
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud2(new pcl::PointCloud<pcl::PointXYZ>());
|
|
cloud2->push_back(pcl::PointXYZ(7, 8, 9));
|
|
|
|
cloudList.push_back(cloud1);
|
|
cloudList.push_back(cloud2);
|
|
|
|
auto result = util3d::concatenateClouds(cloudList);
|
|
|
|
ASSERT_EQ(result->size(), 3);
|
|
EXPECT_EQ(result->at(0).x, 1);
|
|
EXPECT_EQ(result->at(2).z, 9);
|
|
}
|
|
|
|
TEST(Util3dTest, ConcatenateCloudsXYZRGB)
|
|
{
|
|
std::list<pcl::PointCloud<pcl::PointXYZRGB>::Ptr> cloudList;
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud1(new pcl::PointCloud<pcl::PointXYZRGB>());
|
|
pcl::PointXYZRGB pt1; pt1.x = 1; pt1.y = 1; pt1.z = 1; pt1.r = 255; pt1.g = 0; pt1.b = 0;
|
|
pcl::PointXYZRGB pt2; pt2.x = 2; pt2.y = 2; pt2.z = 2; pt2.r = 0; pt2.g = 255; pt2.b = 0;
|
|
cloud1->push_back(pt1);
|
|
cloud1->push_back(pt2);
|
|
|
|
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud2(new pcl::PointCloud<pcl::PointXYZRGB>());
|
|
pcl::PointXYZRGB pt3; pt3.x = 3; pt3.y = 3; pt3.z = 3; pt3.r = 0; pt3.g = 0; pt3.b = 255;
|
|
cloud2->push_back(pt3);
|
|
|
|
cloudList.push_back(cloud1);
|
|
cloudList.push_back(cloud2);
|
|
|
|
auto result = util3d::concatenateClouds(cloudList);
|
|
|
|
ASSERT_EQ(result->size(), 3);
|
|
EXPECT_EQ(result->at(0).r, 255);
|
|
EXPECT_EQ(result->at(1).g, 255);
|
|
EXPECT_EQ(result->at(2).b, 255);
|
|
}
|
|
|
|
|
|
TEST(Util3dTest, ConcatenateIndices)
|
|
{
|
|
pcl::IndicesPtr indices1(new std::vector<int>({1, 2}));
|
|
pcl::IndicesPtr indices2(new std::vector<int>({3, 4}));
|
|
pcl::IndicesPtr indices3(new std::vector<int>({5}));
|
|
|
|
std::vector<pcl::IndicesPtr> inputs = {indices1, indices2, indices3};
|
|
pcl::IndicesPtr result = util3d::concatenate(inputs);
|
|
|
|
ASSERT_EQ(result->size(), 5u);
|
|
EXPECT_EQ((*result)[0], 1);
|
|
EXPECT_EQ((*result)[1], 2);
|
|
EXPECT_EQ((*result)[2], 3);
|
|
EXPECT_EQ((*result)[3], 4);
|
|
EXPECT_EQ((*result)[4], 5);
|
|
|
|
pcl::IndicesPtr indicesA(new std::vector<int>({10, 20}));
|
|
pcl::IndicesPtr indicesB(new std::vector<int>({30}));
|
|
|
|
result = util3d::concatenate(indicesA, indicesB);
|
|
|
|
ASSERT_EQ(result->size(), 3u);
|
|
EXPECT_EQ((*result)[0], 10);
|
|
EXPECT_EQ((*result)[1], 20);
|
|
EXPECT_EQ((*result)[2], 30);
|
|
}
|
|
|
|
TEST(Util3dTest, SavePCDWordsFromPCLPoints) {
|
|
std::multimap<int, pcl::PointXYZ> words;
|
|
words.insert({0, pcl::PointXYZ(1, 2, 3)});
|
|
words.insert({1, pcl::PointXYZ(4, 5, 6)});
|
|
Transform t = Transform::getIdentity();
|
|
|
|
std::string file = "test_pcd_pcl.pcd";
|
|
util3d::savePCDWords(file, words, t);
|
|
|
|
pcl::PointCloud<pcl::PointXYZ> cloud;
|
|
pcl::io::loadPCDFile(file, cloud);
|
|
|
|
EXPECT_EQ(cloud.size(), 2);
|
|
EXPECT_FLOAT_EQ(cloud[0].x, 1);
|
|
EXPECT_FLOAT_EQ(cloud[1].z, 6);
|
|
std::remove(file.c_str());
|
|
}
|
|
|
|
TEST(Util3dTest, SavePCDWordsFromCVPoints) {
|
|
std::multimap<int, cv::Point3f> words;
|
|
words.insert({0, cv::Point3f(1, 0, 0)});
|
|
words.insert({1, cv::Point3f(0, 1, 0)});
|
|
Transform t = Transform(1, 0, 0, 1,
|
|
0, 1, 0, 2,
|
|
0, 0, 1, 3); // Translate
|
|
|
|
std::string file = "test_pcd_cv.pcd";
|
|
util3d::savePCDWords(file, words, t);
|
|
|
|
pcl::PointCloud<pcl::PointXYZ> cloud;
|
|
pcl::io::loadPCDFile(file, cloud);
|
|
|
|
EXPECT_EQ(cloud.size(), 2);
|
|
EXPECT_FLOAT_EQ(cloud[0].x, 2); // 1 + tx
|
|
EXPECT_FLOAT_EQ(cloud[1].y, 3); // 1 + ty
|
|
std::remove(file.c_str());
|
|
}
|
|
|
|
TEST(Util3dTest, LoadBINScanValid) {
|
|
std::string tmp_file = "test.bin";
|
|
std::ofstream file(tmp_file, std::ios::binary);
|
|
pcl::PointCloud<pcl::PointXYZI> cloud;
|
|
cloud.resize(5);
|
|
for (int i = 0; i < 5; ++i) {
|
|
float x = static_cast<float>(rand()) / RAND_MAX;
|
|
float y = static_cast<float>(rand()) / RAND_MAX;
|
|
float z = static_cast<float>(rand()) / RAND_MAX;
|
|
float intensity = static_cast<float>(rand()) / RAND_MAX;
|
|
file.write(reinterpret_cast<char*>(&x), sizeof(float));
|
|
file.write(reinterpret_cast<char*>(&y), sizeof(float));
|
|
file.write(reinterpret_cast<char*>(&z), sizeof(float));
|
|
file.write(reinterpret_cast<char*>(&intensity), sizeof(float));
|
|
cloud[i].x = x;
|
|
cloud[i].y = y;
|
|
cloud[i].z = z;
|
|
cloud[i].intensity = intensity;
|
|
}
|
|
file.close();
|
|
|
|
cv::Mat result = util3d::loadBINScan(tmp_file);
|
|
|
|
// Check if the matrix dimensions are correct (1 x 5 x 4)
|
|
EXPECT_EQ(result.rows, 1);
|
|
EXPECT_EQ(result.cols, cloud.size());
|
|
EXPECT_EQ(result.channels(), 4);
|
|
EXPECT_EQ(result.type(), CV_32FC4);
|
|
|
|
for(size_t i=0; i<cloud.size(); ++i)
|
|
{
|
|
EXPECT_EQ(cloud[i].x, result.at<cv::Vec4f>(0, i)[0]);
|
|
EXPECT_EQ(cloud[i].y, result.at<cv::Vec4f>(0, i)[1]);
|
|
EXPECT_EQ(cloud[i].z, result.at<cv::Vec4f>(0, i)[2]);
|
|
EXPECT_EQ(cloud[i].intensity, result.at<cv::Vec4f>(0, i)[3]);
|
|
}
|
|
|
|
std::remove(tmp_file.c_str()); // Clean up the test file
|
|
}
|
|
|
|
TEST(Util3dTest, LoadBINScanEmpty) {
|
|
std::string tmp_file = "empty_test.bin";
|
|
std::ofstream(tmp_file, std::ios::binary).close(); // Create empty file
|
|
|
|
cv::Mat result = util3d::loadBINScan(tmp_file);
|
|
|
|
// Ensure the output is empty
|
|
EXPECT_TRUE(result.empty());
|
|
|
|
std::remove(tmp_file.c_str()); // Clean up the test file
|
|
}
|
|
|
|
// Test case to check loading a binary scan
|
|
TEST(Util3dTest, LoadScanBINFile) {
|
|
std::string tmp_file = "test.bin";
|
|
std::ofstream file(tmp_file, std::ios::binary);
|
|
pcl::PointCloud<pcl::PointXYZI> cloud;
|
|
cloud.resize(5);
|
|
for (int i = 0; i < 5; ++i) {
|
|
float x = static_cast<float>(rand()) / RAND_MAX;
|
|
float y = static_cast<float>(rand()) / RAND_MAX;
|
|
float z = static_cast<float>(rand()) / RAND_MAX;
|
|
float intensity = static_cast<float>(rand()) / RAND_MAX;
|
|
file.write(reinterpret_cast<char*>(&x), sizeof(float));
|
|
file.write(reinterpret_cast<char*>(&y), sizeof(float));
|
|
file.write(reinterpret_cast<char*>(&z), sizeof(float));
|
|
file.write(reinterpret_cast<char*>(&intensity), sizeof(float));
|
|
cloud[i].x = x;
|
|
cloud[i].y = y;
|
|
cloud[i].z = z;
|
|
cloud[i].intensity = intensity;
|
|
}
|
|
file.close();
|
|
|
|
LaserScan result = util3d::loadScan(tmp_file);
|
|
|
|
// Check that result is valid and has expected properties
|
|
EXPECT_TRUE(!result.empty());
|
|
EXPECT_EQ(result.data().rows, 1);
|
|
EXPECT_EQ(result.data().cols, cloud.size());
|
|
EXPECT_EQ(result.data().channels(), 4);
|
|
EXPECT_EQ(result.data().type(), CV_32FC4);
|
|
EXPECT_EQ(result.format(), LaserScan::kXYZI);
|
|
|
|
for(size_t i=0; i<cloud.size(); ++i)
|
|
{
|
|
EXPECT_EQ(cloud[i].x, result.field(i, 0));
|
|
EXPECT_EQ(cloud[i].y, result.field(i, 1));
|
|
EXPECT_EQ(cloud[i].z, result.field(i, 2));
|
|
EXPECT_EQ(cloud[i].intensity, result.field(i, result.getIntensityOffset()));
|
|
}
|
|
|
|
std::remove(tmp_file.c_str()); // Clean up the test file
|
|
}
|
|
|
|
// Test case to check loading a PCD file
|
|
TEST(Util3dTest, LoadScanPCDFile) {
|
|
std::string tmp_file = "test.pcd";
|
|
|
|
// Create a dummy PCD file
|
|
pcl::PointCloud<pcl::PointXYZ> cloud;
|
|
cloud.push_back(pcl::PointXYZ(1.0, 2.0, 3.0));
|
|
cloud.push_back(pcl::PointXYZ(4.0, 5.0, 6.0));
|
|
|
|
pcl::io::savePCDFile(tmp_file, cloud);
|
|
|
|
LaserScan result = util3d::loadScan(tmp_file);
|
|
|
|
// Check that result is valid and contains points
|
|
EXPECT_TRUE(!result.empty());
|
|
EXPECT_EQ(result.data().rows, 1);
|
|
EXPECT_EQ(result.data().cols, 2);
|
|
EXPECT_EQ(result.data().channels(), 3);
|
|
EXPECT_EQ(result.data().type(), CV_32FC3);
|
|
EXPECT_EQ(result.format(), LaserScan::kXYZ);
|
|
|
|
for(size_t i=0; i<cloud.size(); ++i)
|
|
{
|
|
EXPECT_EQ(cloud[i].x, result.field(i, 0));
|
|
EXPECT_EQ(cloud[i].y, result.field(i, 1));
|
|
EXPECT_EQ(cloud[i].z, result.field(i, 2));
|
|
}
|
|
|
|
std::remove(tmp_file.c_str()); // Clean up the test file
|
|
}
|
|
|
|
// Test case to check loading a PLY file
|
|
TEST(Util3dTest, LoadScanPLYFile) {
|
|
std::string tmp_file = "test.ply";
|
|
|
|
// Create a dummy PLY file
|
|
pcl::PointCloud<pcl::PointXYZ> cloud;
|
|
cloud.push_back(pcl::PointXYZ(7.0, 8.0, 9.0));
|
|
|
|
pcl::io::savePLYFile(tmp_file, cloud);
|
|
|
|
LaserScan result = util3d::loadScan(tmp_file);
|
|
|
|
// Check that result is valid and contains points
|
|
EXPECT_TRUE(!result.empty());
|
|
EXPECT_EQ(result.data().rows, 1);
|
|
EXPECT_EQ(result.data().cols, 1);
|
|
EXPECT_EQ(result.data().channels(), 3);
|
|
EXPECT_EQ(result.data().type(), CV_32FC3);
|
|
EXPECT_EQ(result.format(), LaserScan::kXYZ);
|
|
|
|
for(size_t i=0; i<cloud.size(); ++i)
|
|
{
|
|
EXPECT_EQ(cloud[i].x, result.field(i, 0));
|
|
EXPECT_EQ(cloud[i].y, result.field(i, 1));
|
|
EXPECT_EQ(cloud[i].z, result.field(i, 2));
|
|
}
|
|
|
|
std::remove(tmp_file.c_str()); // Clean up the test file
|
|
}
|
|
|
|
TEST(Util3dTest, DeskewValidScan) {
|
|
// Prepare mock LaserScan data (simulating a 3x3 point cloud with time info)
|
|
cv::Mat mockData = cv::Mat::zeros(1, 3, CV_32FC(5));
|
|
float * dataPtr = (float*)mockData.data;
|
|
dataPtr[0] = 1; // x1
|
|
dataPtr[1] = 0; // y1, deskewed it should be -1
|
|
dataPtr[4] = -1; // t1, 1 sec in past
|
|
dataPtr[5] = 1; // x2
|
|
dataPtr[6] = 0; // y2
|
|
dataPtr[9] = 0; // t2
|
|
dataPtr[10] = 1; // x3
|
|
dataPtr[11] = 0; // y3, deskewed it should be 1
|
|
dataPtr[14] = 1; // t3, 1 sec in future
|
|
|
|
// Create a mock LaserScan object
|
|
LaserScan scan(mockData, 3, 10.0f, LaserScan::kXYZIT);
|
|
|
|
// Create a mock Transform for velocity
|
|
Transform velocity(0.0, 1.0, 0.0, 0.0, 0.0, 0.0); // moving on Y at 1 meter per second
|
|
|
|
double inputStamp = 1000.0; // Example timestamp
|
|
|
|
// Perform the deskew operation
|
|
LaserScan result = util3d::deskew(scan, inputStamp, velocity);
|
|
|
|
// Verify the result (in a real test, we would assert against the expected values)
|
|
EXPECT_EQ(result.size(), 3); // Ensure result is not empty
|
|
EXPECT_EQ(result.format(), LaserScan::kXYZI); // Ensure the format is as expected
|
|
float * resultPtr = (float*)result.data().data;
|
|
|
|
EXPECT_EQ(resultPtr[0], dataPtr[0]); // x1: should stay the same
|
|
EXPECT_EQ(resultPtr[1], -1); // y1: should have moved
|
|
EXPECT_EQ(resultPtr[2], dataPtr[2]); // z1: should stay the same
|
|
EXPECT_EQ(resultPtr[3], dataPtr[3]); // i1: should stay the same
|
|
|
|
EXPECT_EQ(resultPtr[4], dataPtr[5]); // x2: should stay the same
|
|
EXPECT_EQ(resultPtr[5], dataPtr[6]); // y2: should stay the same
|
|
EXPECT_EQ(resultPtr[6], dataPtr[7]); // z2: should stay the same
|
|
EXPECT_EQ(resultPtr[7], dataPtr[8]); // i2: should stay the same
|
|
|
|
EXPECT_EQ(resultPtr[8], dataPtr[10]); // x3: should stay the same
|
|
EXPECT_EQ(resultPtr[9], 1); // y3: should have moved
|
|
EXPECT_EQ(resultPtr[10], dataPtr[12]); // z3: should stay the same
|
|
EXPECT_EQ(resultPtr[11], dataPtr[13]); // i3: should stay the same
|
|
|
|
// invalid velocity
|
|
EXPECT_TRUE(util3d::deskew(scan, inputStamp, Transform()).empty());
|
|
|
|
// invalid format
|
|
EXPECT_TRUE(util3d::deskew(LaserScan(cv::Mat::zeros(1, 3, CV_32FC(4)), 3, 10.0f, LaserScan::kXYZI), inputStamp, velocity).empty());
|
|
|
|
// empty scan
|
|
EXPECT_TRUE(util3d::deskew(LaserScan(), inputStamp, velocity).empty());
|
|
}
|