ed_sensor_integration
test_furniture_fit.cpp
Go to the documentation of this file.
1 #include <gtest/gtest.h>
2 #include <math.h>
3 #include <iostream>
4 #include <opencv2/highgui/highgui.hpp>
5 
6 // ROS
7 #include <image_geometry/pinhole_camera_model.h>
8 #include <ros/console.h>
9 #include <ros/package.h>
10 #include <sensor_msgs/CameraInfo.h>
11 #include <sensor_msgs/distortion_models.h>
12 
13 // TU/e Robotics
14 #include <ed/entity.h>
15 #include <ed/world_model.h>
16 #include <ed/models/model_loader.h>
17 #include <ed/update_request.h>
18 #include <ed/rendering.h>
19 #include <ed/uuid.h>
21 #include <geolib/Shape.h>
23 #include <rgbd/image.h>
25 #include "tue/config/loaders/sdf.h"
26 
27 // ED sensor integration
28 #include <ed/kinect/fitter.h>
29 
30 const uint CAM_RESOLUTION_WIDTH = 640;
31 const uint CAM_RESOLUTION_HEIGHT = 480;
32 
33 const double MAX_POSITION_ERROR = 0.05;
34 const double MAX_YAW_ERROR_DEGREES = 5.0;
35 
36 // The following two constants are added for regression: not all poses
37 // will succeed. However, we don't want performance to get worse
38 const uint NR_TEST_POSES = 1089;
39 const uint NR_SUCCEEDED_POSES = 1089 - 106;
40 
41 bool SHOW_DEBUG_IMAGES = false;
42 
43 // Getting roll, pitch and yaw from a quaternion,
44 // copied from https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
45 // N.B.: we should have a proper implemenation
46 struct Quaternion {
47  double w, x, y, z;
48 };
49 
50 struct EulerAngles {
51  double roll, pitch, yaw;
52 };
53 
55  EulerAngles angles;
56 
57  // roll (x-axis rotation)
58  double sinr_cosp = 2 * (q.w * q.x + q.y * q.z);
59  double cosr_cosp = 1 - 2 * (q.x * q.x + q.y * q.y);
60  angles.roll = std::atan2(sinr_cosp, cosr_cosp);
61 
62  // pitch (y-axis rotation)
63  double sinp = 2 * (q.w * q.y - q.z * q.x);
64  if (std::abs(sinp) >= 1)
65  angles.pitch = std::copysign(M_PI / 2, sinp); // use 90 degrees if out of range
66  else
67  angles.pitch = std::asin(sinp);
68 
69  // yaw (z-axis rotation)
70  double siny_cosp = 2 * (q.w * q.z + q.x * q.y);
71  double cosy_cosp = 1 - 2 * (q.y * q.y + q.z * q.z);
72  angles.yaw = std::atan2(siny_cosp, cosy_cosp);
73 
74  return angles;
75 }
76 
77 
78 double getYaw(const geo::Mat3& rotation)
79 {
80  // Get quaternion
81  geo::Quaternion geo_quaternion;
82  rotation.getRotation(geo_quaternion);
83 
84  // Convert it to struct
85  Quaternion quaternion;
86  quaternion.x = geo_quaternion.getX();
87  quaternion.y = geo_quaternion.getY();
88  quaternion.z = geo_quaternion.getZ();
89  quaternion.w = geo_quaternion.getW();
90 
91  // Get the Euler angles
92  EulerAngles angles = ToEulerAngles(quaternion);
93  ROS_DEBUG_STREAM("Matrix: " << rotation << " --> yaw: " << angles.yaw);
94  return angles.yaw;
95 }
96 
97 
103 double degToRad(double input)
104 {
105  return input * M_PI / 180.0;
106 }
107 
108 
110 {
111  double width, height;
112 };
113 
114 
115 sensor_msgs::CameraInfo getDefaultCamInfo(const ImageResolution& resolution)
116 {
117  sensor_msgs::CameraInfo cam_info;
118  cam_info.K = sensor_msgs::CameraInfo::_K_type({554.2559327880068, 0.0, 320.5,
119  0.0, 554.2559327880068, 240.5,
120  0.0, 0.0, 1.0});
121  cam_info.P = sensor_msgs::CameraInfo::_P_type({554.2559327880068, 0.0, 320.5, 0.0,
122  0.0, 554.2559327880068, 240.5, 0.0,
123  0.0, 0.0, 1.0, 0.0});
124  cam_info.distortion_model = sensor_msgs::distortion_models::PLUMB_BOB;
125  cam_info.width = resolution.width;
126  cam_info.height = resolution.height;
127  return cam_info;
128 }
129 
130 
131 void moveFurnitureObject(const ed::UUID& id, const geo::Pose3D& new_pose, ed::WorldModel& wm)
132 {
133  ed::UpdateRequest request;
134  request.setPose(id, new_pose);
135  wm.update(request);
136 }
137 
138 
150 bool fitSupportingEntity(const rgbd::Image* image, const geo::Pose3D& sensor_pose,
151  const ed::WorldModel& wm, const ed::UUID& entity_id, const double max_yaw_change,
152  const Fitter& fitter, geo::Pose3D& new_pose)
153 {
154  // ToDo: create a function for this in the library
155  // ToDo: does it make sense to provide RGBD data here or rather a more standard data type?
156  FitterData fitterdata;
157  ROS_DEBUG_STREAM("Fitting: Processing sensor data");
158  fitter.processSensorData(*image, sensor_pose, fitterdata);
159 
160  ROS_DEBUG_STREAM("Fitting: getting entity");
161  ed::EntityConstPtr e = wm.getEntity(entity_id);
162  if (!e)
163  throw std::runtime_error("Entity not found in WM");
164  ROS_DEBUG_STREAM("Fitting: estimating entity pose");
165  return fitter.estimateEntityPose(fitterdata, wm, entity_id, e->pose(), new_pose, max_yaw_change);
166 }
167 
168 
169 class FurnitureFitTest : public ::testing::Test {
170 protected:
171  void SetUp() override
172  {
173  ROS_DEBUG_STREAM("Setting up... ");
174  extendPath("ED_MODEL_PATH");
175  extendPath("GAZEBO_MODEL_PATH");
176  extendPath("GAZEBO_RESOURCE_PATH");
177  }
178 
179  void extendPath(const std::string& name)
180  {
181  if (const char* original_path = ::getenv(name.c_str()))
182  original_env_values_[name] = original_path;
183  else
184  original_env_values_[name] = nullptr;
185  std::string package_path = ros::package::getPath("ed_sensor_integration");
186  std::string new_path = package_path + "/test";
187  if (original_env_values_[name])
188  {
189  new_path += ":";
190  new_path += original_env_values_[name];
191  }
192  ROS_INFO_STREAM("New " << name << ": " << new_path);
193  ::setenv(name.c_str(), new_path.c_str(), 1);
194  }
195 
196  void TearDown() override
197  {
198  ROS_DEBUG_STREAM("Tearing down... ");
199  for (auto it = original_env_values_.begin(); it != original_env_values_.end(); it++)
200  {
201  if(it->second)
202  ::setenv(it->first.c_str(), it->second, 1);
203  else
204  ::unsetenv(it->first.c_str());
205  }
206  }
207 
209 
210 };
211 
212 
213 // ToDo: make this class a member of the FurnitureFitTest and separate
214 // the test functions from the test setup
219 {
220 public:
222  {
224  setupRasterizer();
225  setupCamPose();
226  fitter_ = Fitter(200, 100);
227  }
228 
230 
231  bool testSinglePose(const geo::Pose3D& new_pose) const
232  {
233  // Move the table
234  ed::WorldModel wm_copy(world_model_);
235  moveFurnitureObject("table", new_pose, wm_copy);
236 
237  // Check to see if the pose of the table in the original world model has not changed
238  checkTablePose();
239 
240  // Render image
241  cv::Mat depth_image2(CAM_RESOLUTION_HEIGHT, CAM_RESOLUTION_WIDTH, CV_32FC1, 0.0); // ToDo: check!
242  renderImage(wm_copy, depth_image2);
243 
244  // Start fitting
245  ROS_DEBUG_STREAM("Creating RGBD image");
246  rgbd::Image rgbd_image(depth_image2, // ToDo: replace by colored image
247  depth_image2,
248  cam_model_,
249  "camera", // ToDo: check if frame id is necessay
250  0.0); // ToDo: check if valid stamp is necessary
251  ROS_DEBUG_STREAM("Instantiating 'new pose' ");
252  geo::Pose3D fitted_pose;
253  ROS_DEBUG_STREAM("Fitting supporting entity");
254  ed::UUID ed_furniture_id("table");
255  bool fit_result = fitSupportingEntity(&rgbd_image,
256  ed_furniture_id,
257  fitted_pose);
258  ROS_DEBUG_STREAM("Fit result: " << fit_result <<
259  "\nExpected pose: " << new_pose <<
260  "\nComputed pose: " << fitted_pose
261  );
262 
263  geo::Vec3 pos_error = new_pose.t - fitted_pose.t;
264  double yaw_error = getYaw(new_pose.R) - getYaw(fitted_pose.R);
265  if (pos_error.length() > MAX_POSITION_ERROR || fabs(yaw_error) > degToRad(MAX_YAW_ERROR_DEGREES))
266  return false;
267  else
268  return true;
269  }
270 
271  void testAllPoses(std::vector<geo::Pose3D>& succeeded_poses, std::vector<geo::Pose3D>& failed_poses) const
272  {
273  for (double x = -0.5; x <= 0.5; x += 0.1)
274  {
275  for (double y = -0.5; y <= 0.5; y += 0.1)
276  {
277  for (double yaw_deg = -40.0; yaw_deg <= 40.0; yaw_deg += 10.0)
278  {
279  geo::Pose3D new_pose(x, y, 0.0, 0.0, 0.0, degToRad(yaw_deg));
280  if (testSinglePose(new_pose))
281  succeeded_poses.push_back(new_pose);
282  else
283  failed_poses.push_back(new_pose);
284  } // end of yaw loop
285  } // end of y loop
286  } // end of x loop
287  }
288 
289 private:
290 
292  {
293  // Load the table model from the sdf file
294  std::string path = ros::package::getPath("ed_sensor_integration");
295  path += "/test/test_model.sdf";
296  ed::UpdateRequest request;
298 
299  world_model_.update(request);
300  }
301 
303  {
304  sensor_msgs::CameraInfo cam_info = getDefaultCamInfo({CAM_RESOLUTION_WIDTH, CAM_RESOLUTION_HEIGHT});
305  cam_model_.fromCameraInfo(cam_info);
307  }
308 
310  {
311  cam_pose_.t = geo::Vector3(-1.5, 0.0, 1.5);
312  cam_pose_.setRPY(0.0, -1.57, 0.0); // In view, rotated 90 degrees
313  cam_pose_.setRPY(1.57, 0.0, -1.57); // In view, straight
314  cam_pose_.setRPY(0.87, 0.0, -1.57); // In view, tilted at table
315  }
316 
317  void checkTablePose() const
318  {
319  ed::EntityConstPtr table_entity = world_model_.getEntity("table");
320  geo::Vec3 pos_diff = geo::Vec3(0.0, 0.0, 0.0) - table_entity->pose().t;
321  if (pos_diff.length() > 0.001)
322  throw std::runtime_error("Table has moved in the original world model");
323  ROS_DEBUG_STREAM("Pose of the table in the original WM: " << table_entity->pose());
324  }
325 
326  // ToDo: do or don't have as a member
327  void renderImage(const ed::WorldModel& wm, cv::Mat& depth_image) const
328  {
329  cv::Mat image(depth_image.rows, depth_image.cols, CV_8UC3, cv::Scalar(20, 20, 20));
330  bool result = ed::renderWorldModel(wm, ed::ShowVolumes::NoVolumes, rasterizer_, cam_pose_.inverse(), depth_image, image);
331  ROS_DEBUG_STREAM("\nRender result: " << result << "\n");
332 
333  if (SHOW_DEBUG_IMAGES)
334  {
335  cv::namedWindow("Colored image", 1);
336  cv::imshow("Colored image", image);
337  std::cout << "Press any key to continue" << std::endl;
338  cv::waitKey(0);
339  cv::namedWindow("Depth image", 1);
340  cv::imshow("Depth image", depth_image);
341  std::cout << "Press any key to continue" << std::endl;
342  cv::waitKey(0);
343  cv::destroyAllWindows();
344  }
345  }
346 
347  // ToDo: do or don't have as a member
348  bool fitSupportingEntity(const rgbd::Image* image, const ed::UUID& entity_id, geo::Pose3D& new_pose) const
349  {
350  // ToDo: create a function for this in the library
351  // ToDo: does it make sense to provide RGBD data here or rather a more standard data type?
352  FitterData fitterdata;
353  ROS_DEBUG_STREAM("Fitting: Processing sensor data");
354  fitter_.processSensorData(*image, cam_pose_, fitterdata);
355 
356  ROS_DEBUG_STREAM("Fitting: getting entity");
358  if (!e)
359  throw std::runtime_error("Entity not found in WM");
360  ROS_DEBUG_STREAM("Fitting: estimating entity pose");
361  return fitter_.estimateEntityPose(fitterdata, world_model_, entity_id, e->pose(), new_pose, max_yaw_change);
362  }
363 
365  image_geometry::PinholeCameraModel cam_model_;
369  double max_yaw_change = degToRad(45.0);
370 };
371 
372 
373 // ToDo: move to TestFurnitureFit
375 {
376  uint nr_succeeded_poses = succeeded_poses.size();
377  uint nr_failed_poses = failed_poses.size();
378  uint nr_poses_total = nr_succeeded_poses + nr_failed_poses;
379  ROS_INFO_STREAM("Tested " << nr_poses_total << " table poses, succeeded: "
380  << nr_succeeded_poses << ", failed: " << failed_poses.size());
381  for (auto& failed_pose: failed_poses)
382  {
383  ROS_DEBUG_STREAM("\n" << failed_pose);
384  }
385 
386 }
387 
388 
390 {
391 
392  ROS_INFO_STREAM("Starting testsuite");
393  TestSetup test_setup;
394 
395  // For single test
396 // geo::Pose3D test_pose(-0.5, -0.5, 0.0, 0.0, 0.0, 0.0);
397 // ASSERT_TRUE(test_setup.testSinglePose(test_pose));
398 
399  std::vector<geo::Pose3D> succeeded_poses, failed_poses;
400  test_setup.testAllPoses(succeeded_poses, failed_poses);
401 
402  summarizeResult(succeeded_poses, failed_poses);
403  ASSERT_TRUE(succeeded_poses.size() >= NR_SUCCEEDED_POSES);
404 }
405 
406 
407 int main(int argc, char **argv)
408 {
409  testing::InitGoogleTest(&argc, argv);
410 
411 // g_argc = argc;
412 // g_argv = argv;
413 
414  // ToDo: get this from CLI args
415 // SHOW_DEBUG_IMAGES = true;
416 
417  return RUN_ALL_TESTS();
418 }
FurnitureFitTest::TearDown
void TearDown() override
Definition: test_furniture_fit.cpp:196
geo::Vector3
Vec3 Vector3
MAX_YAW_ERROR_DEGREES
const double MAX_YAW_ERROR_DEGREES
Definition: test_furniture_fit.cpp:34
ed::WorldModel::getEntity
EntityConstPtr getEntity(const ed::UUID &id) const
uuid.h
EulerAngles
Definition: test_furniture_fit.cpp:50
ed::UpdateRequest
geo::QuaternionT
ToEulerAngles
EulerAngles ToEulerAngles(Quaternion q)
Definition: test_furniture_fit.cpp:54
std::string
geo::Mat3T::getRotation
void getRotation(QuaternionT< T > &q) const
TestSetup::fitter_
Fitter fitter_
Definition: test_furniture_fit.cpp:367
DepthCamera.h
FurnitureFitTest
Definition: test_furniture_fit.cpp:169
std::atan2
T atan2(T... args)
TestSetup::createWorldModel
void createWorldModel()
Definition: test_furniture_fit.cpp:291
TestSetup::TestSetup
TestSetup()
Definition: test_furniture_fit.cpp:221
fitSupportingEntity
bool fitSupportingEntity(const rgbd::Image *image, const geo::Pose3D &sensor_pose, const ed::WorldModel &wm, const ed::UUID &entity_id, const double max_yaw_change, const Fitter &fitter, geo::Pose3D &new_pose)
fitSupportingEntity fits a supporting entity
Definition: test_furniture_fit.cpp:150
update_request.h
TestSetup::rasterizer_
geo::DepthCamera rasterizer_
Definition: test_furniture_fit.cpp:366
std::asin
T asin(T... args)
summarizeResult
void summarizeResult(std::vector< geo::Pose3D > &succeeded_poses, std::vector< geo::Pose3D > &failed_poses)
Definition: test_furniture_fit.cpp:374
std::vector
std::vector::size
T size(T... args)
transform_cache.h
NR_TEST_POSES
const uint NR_TEST_POSES
Definition: test_furniture_fit.cpp:38
TestSetup::cam_model_
image_geometry::PinholeCameraModel cam_model_
Definition: test_furniture_fit.cpp:365
moveFurnitureObject
void moveFurnitureObject(const ed::UUID &id, const geo::Pose3D &new_pose, ed::WorldModel &wm)
Definition: test_furniture_fit.cpp:131
geo::Vec3T
main
int main(int argc, char **argv)
Definition: test_furniture_fit.cpp:407
ImageResolution
Definition: test_furniture_fit.cpp:109
Shape.h
Quaternion::y
double y
Definition: test_furniture_fit.cpp:47
ed::renderWorldModel
bool renderWorldModel(const ed::WorldModel &world_model, const enum ShowVolumes show_volumes, const geo::DepthCamera &cam, const geo::Pose3D &cam_pose_inv, cv::Mat &depth_image, cv::Mat &image, bool flatten=false)
Quaternion
Definition: test_furniture_fit.cpp:46
geo::Transform3T
geo::DepthCamera::initFromCamModel
void initFromCamModel(const image_geometry::PinholeCameraModel &cam_model)
Quaternion::w
double w
Definition: test_furniture_fit.cpp:47
Fitter::estimateEntityPose
bool estimateEntityPose(const FitterData &data, const ed::WorldModel &world, const ed::UUID &id, const geo::Pose3D &expected_pose, geo::Pose3D &fitted_pose, double max_yaw_change=M_PI) const
estimateEntityPose performs the entity pose estimation. Basically, tries to call estimateEntityPoseIm...
Definition: fitter.cpp:258
TestSetup::checkTablePose
void checkTablePose() const
Definition: test_furniture_fit.cpp:317
FitterData
The FitterData struct contains the downprojected (hence 2D) sensor readings as well as the sensor pos...
Definition: fitter.h:68
iostream
ed::WorldModel::update
void update(const UpdateRequest &req)
ed::EntityConstPtr
shared_ptr< const Entity > EntityConstPtr
TestSetup::world_model_
ed::WorldModel world_model_
Definition: test_furniture_fit.cpp:364
TestSetup::testSinglePose
bool testSinglePose(const geo::Pose3D &new_pose) const
Definition: test_furniture_fit.cpp:231
getYaw
double getYaw(const geo::Mat3 &rotation)
Definition: test_furniture_fit.cpp:78
std::vector::push_back
T push_back(T... args)
image
cv::Mat image
std::cout
geo::Transform3T::inverse
Transform3T inverse() const
rgbd::Image
getDefaultCamInfo
sensor_msgs::CameraInfo getDefaultCamInfo(const ImageResolution &resolution)
Definition: test_furniture_fit.cpp:115
TestSetup::~TestSetup
~TestSetup()
Definition: test_furniture_fit.cpp:229
image.h
TestSetup::setupRasterizer
void setupRasterizer()
Definition: test_furniture_fit.cpp:302
sdf.h
EulerAngles::roll
double roll
Definition: test_furniture_fit.cpp:51
TestSetup::cam_pose_
geo::Pose3D cam_pose_
Definition: test_furniture_fit.cpp:368
std::string::c_str
T c_str(T... args)
reader_writer.h
SHOW_DEBUG_IMAGES
bool SHOW_DEBUG_IMAGES
Definition: test_furniture_fit.cpp:41
ed::UpdateRequest::setPose
void setPose(const UUID &id, const geo::Pose3D &pose)
MAX_POSITION_ERROR
const double MAX_POSITION_ERROR
Definition: test_furniture_fit.cpp:33
geo::Transform3T::t
Vec3T< T > t
std::copysign
T copysign(T... args)
geo::QuaternionT::getZ
T getZ() const
TEST_F
TEST_F(FurnitureFitTest, testCase)
Definition: test_furniture_fit.cpp:389
EulerAngles::pitch
double pitch
Definition: test_furniture_fit.cpp:51
std::runtime_error
ed::UUID
rendering.h
TestSetup::setupCamPose
void setupCamPose()
Definition: test_furniture_fit.cpp:309
std::map< std::string, const char * >
CAM_RESOLUTION_HEIGHT
const uint CAM_RESOLUTION_HEIGHT
Definition: test_furniture_fit.cpp:31
geo::Vec3
Vec3T< real > Vec3
ed::WorldModel
TestSetup::renderImage
void renderImage(const ed::WorldModel &wm, cv::Mat &depth_image) const
Definition: test_furniture_fit.cpp:327
TestSetup::max_yaw_change
double max_yaw_change
Definition: test_furniture_fit.cpp:369
ImageResolution::width
double width
Definition: test_furniture_fit.cpp:111
geo::Mat3T
FurnitureFitTest::extendPath
void extendPath(const std::string &name)
Definition: test_furniture_fit.cpp:179
geo::QuaternionT::getY
T getY() const
world_model.h
std::endl
T endl(T... args)
TestSetup::testAllPoses
void testAllPoses(std::vector< geo::Pose3D > &succeeded_poses, std::vector< geo::Pose3D > &failed_poses) const
Definition: test_furniture_fit.cpp:271
geo::QuaternionT::getX
T getX() const
std::map::begin
T begin(T... args)
geo::Transform3T::R
Mat3T< T > R
fitter.h
NR_SUCCEEDED_POSES
const uint NR_SUCCEEDED_POSES
Definition: test_furniture_fit.cpp:39
CAM_RESOLUTION_WIDTH
const uint CAM_RESOLUTION_WIDTH
Definition: test_furniture_fit.cpp:30
TestSetup
The TestSetup class contains all constant data for the test.
Definition: test_furniture_fit.cpp:218
geo::DepthCamera
TestSetup::fitSupportingEntity
bool fitSupportingEntity(const rgbd::Image *image, const ed::UUID &entity_id, geo::Pose3D &new_pose) const
Definition: test_furniture_fit.cpp:348
FurnitureFitTest::original_env_values_
std::map< std::string, const char * > original_env_values_
Definition: test_furniture_fit.cpp:208
Fitter::processSensorData
void processSensorData(const rgbd::Image &image, const geo::Pose3D &sensor_pose, FitterData &data) const
processSensorData pre-processes sensor data, i.e., performs a downprojection of the input depth image...
Definition: fitter.cpp:486
std::map::end
T end(T... args)
degToRad
double degToRad(double input)
degToRad converts degrees to radians
Definition: test_furniture_fit.cpp:103
Fitter
The Fitter class contains the algorithm to do the 2D fit of an entity.
Definition: fitter.h:100
entity.h
ImageResolution::height
double height
Definition: test_furniture_fit.cpp:111
EulerAngles::yaw
double yaw
Definition: test_furniture_fit.cpp:51
ed::models::LoadType::FILE
@ FILE
ed::models::loadModel
bool loadModel(const LoadType load_type, const std::string &source, ed::UpdateRequest &req)
depth_image
cv::Mat depth_image
model_loader.h
Quaternion::z
double z
Definition: test_furniture_fit.cpp:47
geo::Vec3T::length
T length() const
geo::QuaternionT::getW
T getW() const
geo::Transform3T::setRPY
void setRPY(double roll, double pitch, double yaw)
Quaternion::x
double x
Definition: test_furniture_fit.cpp:47
FurnitureFitTest::SetUp
void SetUp() override
Definition: test_furniture_fit.cpp:171