test_odometry.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. // This file is part of OpenCV project.
  2. // It is subject to the license terms in the LICENSE file found in the top-level directory
  3. // of this distribution and at http://opencv.org/license.html
  4. // This code is also subject to the license terms in the LICENSE_KinectFusion.md file found in this module's directory
  5. // This code is also subject to the license terms in the LICENSE_WillowGarage.md file found in this module's directory
  6. #include "test_precomp.hpp"
  7. namespace opencv_test { namespace {
  8. #define SHOW_DEBUG_LOG 0
  9. #define SHOW_DEBUG_IMAGES 0
  10. static
  11. void warpFrame(const Mat& image, const Mat& depth, const Mat& rvec, const Mat& tvec, const Mat& K,
  12. Mat& warpedImage, Mat& warpedDepth)
  13. {
  14. CV_Assert(!image.empty());
  15. CV_Assert(image.type() == CV_8UC1);
  16. CV_Assert(depth.size() == image.size());
  17. CV_Assert(depth.type() == CV_32FC1);
  18. CV_Assert(!rvec.empty());
  19. CV_Assert(rvec.total() == 3);
  20. CV_Assert(rvec.type() == CV_64FC1);
  21. CV_Assert(!tvec.empty());
  22. CV_Assert(tvec.size() == Size(1, 3));
  23. CV_Assert(tvec.type() == CV_64FC1);
  24. warpedImage.create(image.size(), CV_8UC1);
  25. warpedImage = Scalar(0);
  26. warpedDepth.create(image.size(), CV_32FC1);
  27. warpedDepth = Scalar(FLT_MAX);
  28. Mat cloud;
  29. depthTo3d(depth, K, cloud);
  30. Mat Rt = Mat::eye(4, 4, CV_64FC1);
  31. {
  32. Mat R, dst;
  33. Rodrigues(rvec, R);
  34. dst = Rt(Rect(0,0,3,3));
  35. R.copyTo(dst);
  36. dst = Rt(Rect(3,0,1,3));
  37. tvec.copyTo(dst);
  38. }
  39. Mat warpedCloud, warpedImagePoints;
  40. perspectiveTransform(cloud, warpedCloud, Rt);
  41. projectPoints(warpedCloud.reshape(3, 1), Mat(3,1,CV_32FC1, Scalar(0)), Mat(3,1,CV_32FC1, Scalar(0)), K, Mat(1,5,CV_32FC1, Scalar(0)), warpedImagePoints);
  42. warpedImagePoints = warpedImagePoints.reshape(2, cloud.rows);
  43. Rect r(0, 0, image.cols, image.rows);
  44. for(int y = 0; y < cloud.rows; y++)
  45. {
  46. for(int x = 0; x < cloud.cols; x++)
  47. {
  48. Point p = warpedImagePoints.at<Point2f>(y,x);
  49. if(r.contains(p))
  50. {
  51. float curDepth = warpedDepth.at<float>(p.y, p.x);
  52. float newDepth = warpedCloud.at<Point3f>(y, x).z;
  53. if(newDepth < curDepth && newDepth > 0)
  54. {
  55. warpedImage.at<uchar>(p.y, p.x) = image.at<uchar>(y,x);
  56. warpedDepth.at<float>(p.y, p.x) = newDepth;
  57. }
  58. }
  59. }
  60. }
  61. warpedDepth.setTo(std::numeric_limits<float>::quiet_NaN(), warpedDepth > 100);
  62. }
  63. static
  64. void dilateFrame(Mat& image, Mat& depth)
  65. {
  66. CV_Assert(!image.empty());
  67. CV_Assert(image.type() == CV_8UC1);
  68. CV_Assert(!depth.empty());
  69. CV_Assert(depth.type() == CV_32FC1);
  70. CV_Assert(depth.size() == image.size());
  71. Mat mask(image.size(), CV_8UC1, Scalar(255));
  72. for(int y = 0; y < depth.rows; y++)
  73. for(int x = 0; x < depth.cols; x++)
  74. if(cvIsNaN(depth.at<float>(y,x)) || depth.at<float>(y,x) > 10 || depth.at<float>(y,x) <= FLT_EPSILON)
  75. mask.at<uchar>(y,x) = 0;
  76. image.setTo(255, ~mask);
  77. Mat minImage;
  78. erode(image, minImage, Mat());
  79. image.setTo(0, ~mask);
  80. Mat maxImage;
  81. dilate(image, maxImage, Mat());
  82. depth.setTo(FLT_MAX, ~mask);
  83. Mat minDepth;
  84. erode(depth, minDepth, Mat());
  85. depth.setTo(0, ~mask);
  86. Mat maxDepth;
  87. dilate(depth, maxDepth, Mat());
  88. Mat dilatedMask;
  89. dilate(mask, dilatedMask, Mat(), Point(-1,-1), 1);
  90. for(int y = 0; y < depth.rows; y++)
  91. for(int x = 0; x < depth.cols; x++)
  92. if(!mask.at<uchar>(y,x) && dilatedMask.at<uchar>(y,x))
  93. {
  94. image.at<uchar>(y,x) = static_cast<uchar>(0.5f * (static_cast<float>(minImage.at<uchar>(y,x)) +
  95. static_cast<float>(maxImage.at<uchar>(y,x))));
  96. depth.at<float>(y,x) = 0.5f * (minDepth.at<float>(y,x) + maxDepth.at<float>(y,x));
  97. }
  98. }
  99. class CV_OdometryTest : public cvtest::BaseTest
  100. {
  101. public:
  102. CV_OdometryTest(const Ptr<Odometry>& _odometry,
  103. double _maxError1,
  104. double _maxError5,
  105. double _idError = DBL_EPSILON) :
  106. odometry(_odometry),
  107. maxError1(_maxError1),
  108. maxError5(_maxError5),
  109. idError(_idError)
  110. { }
  111. protected:
  112. bool readData(Mat& image, Mat& depth) const;
  113. static void generateRandomTransformation(Mat& R, Mat& t);
  114. virtual void run(int);
  115. Ptr<Odometry> odometry;
  116. double maxError1;
  117. double maxError5;
  118. double idError;
  119. };
  120. bool CV_OdometryTest::readData(Mat& image, Mat& depth) const
  121. {
  122. std::string imageFilename = ts->get_data_path() + "rgbd/rgb.png";
  123. std::string depthFilename = ts->get_data_path() + "rgbd/depth.png";
  124. image = imread(imageFilename, 0);
  125. depth = imread(depthFilename, -1);
  126. if(image.empty())
  127. {
  128. ts->printf( cvtest::TS::LOG, "Image %s can not be read.\n", imageFilename.c_str() );
  129. ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA );
  130. return false;
  131. }
  132. if(depth.empty())
  133. {
  134. ts->printf( cvtest::TS::LOG, "Depth %s can not be read.\n", depthFilename.c_str() );
  135. ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_TEST_DATA );
  136. ts->set_gtest_status();
  137. return false;
  138. }
  139. CV_DbgAssert(image.type() == CV_8UC1);
  140. CV_DbgAssert(depth.type() == CV_16UC1);
  141. {
  142. Mat depth_flt;
  143. depth.convertTo(depth_flt, CV_32FC1, 1.f/5000.f);
  144. depth_flt.setTo(std::numeric_limits<float>::quiet_NaN(), depth_flt < FLT_EPSILON);
  145. depth = depth_flt;
  146. }
  147. return true;
  148. }
  149. void CV_OdometryTest::generateRandomTransformation(Mat& rvec, Mat& tvec)
  150. {
  151. const float maxRotation = (float)(3.f / 180.f * CV_PI); //rad
  152. const float maxTranslation = 0.02f; //m
  153. RNG& rng = theRNG();
  154. rvec.create(3, 1, CV_64FC1);
  155. tvec.create(3, 1, CV_64FC1);
  156. randu(rvec, Scalar(-1000), Scalar(1000));
  157. normalize(rvec, rvec, rng.uniform(0.007f, maxRotation));
  158. randu(tvec, Scalar(-1000), Scalar(1000));
  159. normalize(tvec, tvec, rng.uniform(0.008f, maxTranslation));
  160. }
  161. void CV_OdometryTest::run(int)
  162. {
  163. float fx = 525.0f, // default
  164. fy = 525.0f,
  165. cx = 319.5f,
  166. cy = 239.5f;
  167. Mat K = Mat::eye(3,3,CV_32FC1);
  168. {
  169. K.at<float>(0,0) = fx;
  170. K.at<float>(1,1) = fy;
  171. K.at<float>(0,2) = cx;
  172. K.at<float>(1,2) = cy;
  173. }
  174. Mat image, depth;
  175. if(!readData(image, depth))
  176. return;
  177. odometry->setCameraMatrix(K);
  178. Mat calcRt;
  179. // 1. Try to find Rt between the same frame (try masks also).
  180. bool isComputed = odometry->compute(image, depth, Mat(image.size(), CV_8UC1, Scalar(255)),
  181. image, depth, Mat(image.size(), CV_8UC1, Scalar(255)),
  182. calcRt);
  183. if(!isComputed)
  184. {
  185. ts->printf(cvtest::TS::LOG, "Can not find Rt between the same frame");
  186. ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT);
  187. }
  188. double diff = cv::norm(calcRt, Mat::eye(4,4,CV_64FC1));
  189. if(diff > idError)
  190. {
  191. ts->printf(cvtest::TS::LOG, "Incorrect transformation between the same frame (not the identity matrix), diff = %f", diff);
  192. ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
  193. }
  194. // 2. Generate random rigid body motion in some ranges several times (iterCount).
  195. // On each iteration an input frame is warped using generated transformation.
  196. // Odometry is run on the following pair: the original frame and the warped one.
  197. // Comparing a computed transformation with an applied one we compute 2 errors:
  198. // better_1time_count - count of poses which error is less than ground truth pose,
  199. // better_5times_count - count of poses which error is 5 times less than ground truth pose.
  200. int iterCount = 100;
  201. int better_1time_count = 0;
  202. int better_5times_count = 0;
  203. for(int iter = 0; iter < iterCount; iter++)
  204. {
  205. Mat rvec, tvec;
  206. generateRandomTransformation(rvec, tvec);
  207. Mat warpedImage, warpedDepth;
  208. warpFrame(image, depth, rvec, tvec, K, warpedImage, warpedDepth);
  209. dilateFrame(warpedImage, warpedDepth); // due to inaccuracy after warping
  210. Mat imageMask(image.size(), CV_8UC1, Scalar(255));
  211. isComputed = odometry->compute(image, depth, imageMask, warpedImage, warpedDepth, imageMask, calcRt);
  212. if(!isComputed)
  213. continue;
  214. Mat calcR = calcRt(Rect(0,0,3,3)), calcRvec;
  215. Rodrigues(calcR, calcRvec);
  216. calcRvec = calcRvec.reshape(rvec.channels(), rvec.rows);
  217. Mat calcTvec = calcRt(Rect(3,0,1,3));
  218. #if SHOW_DEBUG_IMAGES
  219. imshow("image", image);
  220. imshow("warpedImage", warpedImage);
  221. Mat resultImage, resultDepth;
  222. warpFrame(image, depth, calcRvec, calcTvec, K, resultImage, resultDepth);
  223. imshow("resultImage", resultImage);
  224. waitKey();
  225. #endif
  226. // compare rotation
  227. double rdiffnorm = cv::norm(rvec - calcRvec),
  228. rnorm = cv::norm(rvec);
  229. double tdiffnorm = cv::norm(tvec - calcTvec),
  230. tnorm = cv::norm(tvec);
  231. if(rdiffnorm < rnorm && tdiffnorm < tnorm)
  232. better_1time_count++;
  233. if(5. * rdiffnorm < rnorm && 5 * tdiffnorm < tnorm)
  234. better_5times_count++;
  235. #if SHOW_DEBUG_LOG
  236. std::cout << "Iter " << iter << std::endl;
  237. std::cout << "rdiffnorm " << rdiffnorm << "; rnorm " << rnorm << std::endl;
  238. std::cout << "tdiffnorm " << tdiffnorm << "; tnorm " << tnorm << std::endl;
  239. std::cout << "better_1time_count " << better_1time_count << "; better_5time_count " << better_5times_count << std::endl;
  240. #endif
  241. }
  242. if(static_cast<double>(better_1time_count) < maxError1 * static_cast<double>(iterCount))
  243. {
  244. ts->printf(cvtest::TS::LOG, "\nIncorrect count of accurate poses [1st case]: %f / %f", static_cast<double>(better_1time_count), maxError1 * static_cast<double>(iterCount));
  245. ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
  246. }
  247. if(static_cast<double>(better_5times_count) < maxError5 * static_cast<double>(iterCount))
  248. {
  249. ts->printf(cvtest::TS::LOG, "\nIncorrect count of accurate poses [2nd case]: %f / %f", static_cast<double>(better_5times_count), maxError5 * static_cast<double>(iterCount));
  250. ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
  251. }
  252. }
  253. /****************************************************************************************\
  254. * Tests registrations *
  255. \****************************************************************************************/
  256. TEST(RGBD_Odometry_Rgbd, algorithmic)
  257. {
  258. CV_OdometryTest test(cv::rgbd::Odometry::create("RgbdOdometry"), 0.99, 0.89);
  259. test.safe_run();
  260. }
  261. TEST(RGBD_Odometry_ICP, algorithmic)
  262. {
  263. CV_OdometryTest test(cv::rgbd::Odometry::create("ICPOdometry"), 0.99, 0.99);
  264. test.safe_run();
  265. }
  266. TEST(RGBD_Odometry_RgbdICP, algorithmic)
  267. {
  268. CV_OdometryTest test(cv::rgbd::Odometry::create("RgbdICPOdometry"), 0.99, 0.99);
  269. test.safe_run();
  270. }
  271. TEST(RGBD_Odometry_FastICP, algorithmic)
  272. {
  273. CV_OdometryTest test(cv::rgbd::Odometry::create("FastICPOdometry"), 0.99, 0.99, FLT_EPSILON);
  274. test.safe_run();
  275. }
  276. }} // namespace