essential_mat_reconstr.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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. #include "opencv2/calib3d.hpp"
  5. #include "opencv2/highgui.hpp"
  6. #include "opencv2/imgproc.hpp"
  7. #include <vector>
  8. #include <iostream>
  9. #include <fstream>
  10. using namespace cv;
  11. static double getError2EpipLines (const Mat &F, const Mat &pts1, const Mat &pts2, const Mat &mask) {
  12. Mat points1, points2;
  13. vconcat(pts1, Mat::ones(1, pts1.cols, pts1.type()), points1);
  14. vconcat(pts2, Mat::ones(1, pts2.cols, pts2.type()), points2);
  15. double mean_error = 0;
  16. for (int pt = 0; pt < (int) mask.total(); pt++)
  17. if (mask.at<uchar>(pt)) {
  18. const Mat l2 = F * points1.col(pt);
  19. const Mat l1 = F.t() * points2.col(pt);
  20. mean_error += (fabs(points1.col(pt).dot(l1)) / sqrt(pow(l1.at<double>(0), 2) + pow(l1.at<double>(1), 2)) +
  21. fabs(points2.col(pt).dot(l2) / sqrt(pow(l2.at<double>(0), 2) + pow(l2.at<double>(1), 2)))) / 2;
  22. }
  23. return mean_error / mask.total();
  24. }
  25. static int sgn(double val) { return (0 < val) - (val < 0); }
  26. /*
  27. * @points3d - vector of Point3 or Mat of size Nx3
  28. * @planes - vector of found planes
  29. * @labels - vector of size point3d. Every point which has non-zero label is classified to this plane.
  30. */
  31. static void getPlanes (InputArray points3d_, std::vector<int> &labels, std::vector<Vec4d> &planes, int desired_num_planes, double thr_, double conf_, int max_iters_) {
  32. Mat points3d = points3d_.getMat();
  33. points3d.convertTo(points3d, CV_64F); // convert points to have double precision
  34. if (points3d_.isVector())
  35. points3d = Mat((int)points3d.total(), 3, CV_64F, points3d.data);
  36. else {
  37. if (points3d.type() != CV_64F)
  38. points3d = points3d.reshape(1, (int)points3d.total()); // convert point to have 1 channel
  39. if (points3d.rows < points3d.cols)
  40. transpose(points3d, points3d); // transpose so points will be in rows
  41. CV_CheckEQ(points3d.cols, 3, "Invalid dimension of point");
  42. }
  43. /*
  44. * 3D plane fitting with RANSAC
  45. * @best_model contains coefficients [a b c d] s.t. ax + by + cz = d
  46. *
  47. */
  48. auto plane_ransac = [] (const Mat &pts, double thr, double conf, int max_iters, Vec4d &best_model, std::vector<bool> &inliers) {
  49. const int pts_size = pts.rows, max_lo_inliers = 15, max_lo_iters = 10;
  50. int best_inls = 0;
  51. if (pts_size < 3) return false;
  52. RNG rng;
  53. const auto * const points = (double *) pts.data;
  54. std::vector<int> min_sample(3);
  55. inliers = std::vector<bool>(pts_size);
  56. const double log_conf = log(1-conf);
  57. Vec4d model, lo_model;
  58. std::vector<int> random_pool (pts_size);
  59. for (int p = 0; p < pts_size; p++)
  60. random_pool[p] = p;
  61. // estimate plane coefficients using covariance matrix
  62. auto estimate = [&] (const std::vector<int> &sample, Vec4d &model_) {
  63. // https://www.ilikebigbits.com/2017_09_25_plane_from_points_2.html
  64. const int n = static_cast<int>(sample.size());
  65. if (n < 3) return false;
  66. double sum_x = 0, sum_y = 0, sum_z = 0;
  67. for (int s : sample) {
  68. sum_x += points[3*s ];
  69. sum_y += points[3*s+1];
  70. sum_z += points[3*s+2];
  71. }
  72. const double c_x = sum_x / n, c_y = sum_y / n, c_z = sum_z / n;
  73. double xx = 0, yy = 0, zz = 0, xy = 0, xz = 0, yz = 0;
  74. for (int s : sample) {
  75. const double x_ = points[3*s] - c_x, y_ = points[3*s+1] - c_y, z_ = points[3*s+2] - c_z;
  76. xx += x_*x_; yy += y_*y_; zz += z_*z_; xy += x_*y_; yz += y_*z_; xz += x_*z_;
  77. }
  78. xx /= n; yy /= n; zz /= n; xy /= n; yz /= n; xz /= n;
  79. Vec3d weighted_normal(0,0,0);
  80. const double det_x = yy*zz - yz*yz, det_y = xx*zz - xz*xz, det_z = xx*yy - xy*xy;
  81. Vec3d axis_x (det_x, xz*xz-xy*zz, xy*yz-xz*yy);
  82. Vec3d axis_y (xz*yz-xy*zz, det_y, xy*xz-yz*xx);
  83. Vec3d axis_z (xy*yz-xz*yy, xy*xz-yz*xx, det_z);
  84. weighted_normal += axis_x * det_x * det_x;
  85. weighted_normal += sgn(weighted_normal.dot(axis_y)) * axis_y * det_y * det_y;
  86. weighted_normal += sgn(weighted_normal.dot(axis_z)) * axis_z * det_z * det_z;
  87. weighted_normal /= norm(weighted_normal);
  88. if (std::isinf(weighted_normal(0)) ||
  89. std::isinf(weighted_normal(1)) ||
  90. std::isinf(weighted_normal(2))) return false;
  91. // find plane model from normal and centroid
  92. model_ = Vec4d(weighted_normal(0), weighted_normal(1), weighted_normal(2),
  93. weighted_normal.dot(Vec3d(c_x, c_y, c_z)));
  94. return true;
  95. };
  96. // calculate number of inliers
  97. auto getInliers = [&] (const Vec4d &model_) {
  98. const double a = model_(0), b = model_(1), c = model_(2), d = model_(3);
  99. int num_inliers = 0;
  100. std::fill(inliers.begin(), inliers.end(), false);
  101. for (int p = 0; p < pts_size; p++) {
  102. inliers[p] = fabs(a * points[3*p] + b * points[3*p+1] + c * points[3*p+2] - d) < thr;
  103. if (inliers[p]) num_inliers++;
  104. if (num_inliers + pts_size - p < best_inls) break;
  105. }
  106. return num_inliers;
  107. };
  108. // main RANSAC loop
  109. for (int iters = 0; iters < max_iters; iters++) {
  110. // find minimal sample: 3 points
  111. min_sample[0] = rng.uniform(0, pts_size);
  112. min_sample[1] = rng.uniform(0, pts_size);
  113. min_sample[2] = rng.uniform(0, pts_size);
  114. if (! estimate(min_sample, model))
  115. continue;
  116. int num_inliers = getInliers(model);
  117. if (num_inliers > best_inls) {
  118. // store so-far-the-best
  119. std::vector<bool> best_inliers = inliers;
  120. // do Local Optimization
  121. for (int lo_iter = 0; lo_iter < max_lo_iters; lo_iter++) {
  122. std::vector<int> inliers_idx; inliers_idx.reserve(max_lo_inliers);
  123. randShuffle(random_pool);
  124. for (int p : random_pool) {
  125. if (best_inliers[p]) {
  126. inliers_idx.emplace_back(p);
  127. if ((int)inliers_idx.size() >= max_lo_inliers)
  128. break;
  129. }
  130. }
  131. if (! estimate(inliers_idx, lo_model))
  132. continue;
  133. int lo_inls = getInliers(lo_model);
  134. if (best_inls < lo_inls) {
  135. best_model = lo_model;
  136. best_inls = lo_inls;
  137. best_inliers = inliers;
  138. }
  139. }
  140. if (best_inls < num_inliers) {
  141. best_model = model;
  142. best_inls = num_inliers;
  143. }
  144. // update max iters
  145. // because points are quite noisy we need more iterations
  146. const double max_hyp = 3 * log_conf / log(1 - pow(double(best_inls) / pts_size, 3));
  147. if (! std::isinf(max_hyp) && max_hyp < max_iters)
  148. max_iters = static_cast<int>(max_hyp);
  149. }
  150. }
  151. getInliers(best_model);
  152. return best_inls != 0;
  153. };
  154. labels = std::vector<int>(points3d.rows, 0);
  155. Mat pts3d_plane_fit = points3d.clone();
  156. // keep array of indices of points corresponding to original points3d
  157. std::vector<int> to_orig_pts_arr(pts3d_plane_fit.rows);
  158. for (int i = 0; i < (int) to_orig_pts_arr.size(); i++)
  159. to_orig_pts_arr[i] = i;
  160. for (int num_planes = 1; num_planes <= desired_num_planes; num_planes++) {
  161. Vec4d model;
  162. std::vector<bool> inl;
  163. if (!plane_ransac(pts3d_plane_fit, thr_, conf_, max_iters_, model, inl))
  164. break;
  165. planes.emplace_back(model);
  166. const int pts3d_size = pts3d_plane_fit.rows;
  167. pts3d_plane_fit = Mat();
  168. pts3d_plane_fit.reserve(points3d.rows);
  169. int cnt = 0;
  170. for (int p = 0; p < pts3d_size; p++) {
  171. if (! inl[p]) {
  172. // if point is not inlier to found plane - add it to next run
  173. to_orig_pts_arr[cnt] = to_orig_pts_arr[p];
  174. pts3d_plane_fit.push_back(points3d.row(to_orig_pts_arr[cnt]));
  175. cnt++;
  176. } else labels[to_orig_pts_arr[p]] = num_planes; // otherwise label this point
  177. }
  178. }
  179. }
  180. int main(int args, char** argv) {
  181. std::string data_file, image_dir;
  182. if (args < 3) {
  183. CV_Error(Error::StsBadArg,
  184. "Path to data file and directory to image files are missing!\nData file must have"
  185. " format:\n--------------\n image_name_1\nimage_name_2\nk11 k12 k13\n0 k22 k23\n"
  186. "0 0 1\n--------------\nIf image_name_{1,2} are not in the same directory as "
  187. "the data file then add argument with directory to image files.\nFor example: "
  188. "./essential_mat_reconstr essential_mat_data.txt ./");
  189. } else {
  190. data_file = argv[1];
  191. image_dir = argv[2];
  192. }
  193. std::ifstream file(data_file, std::ios_base::in);
  194. CV_CheckEQ((int)file.is_open(), 1, "Data file is not found!");
  195. std::string filename1, filename2;
  196. std::getline(file, filename1);
  197. std::getline(file, filename2);
  198. Mat image1 = imread(image_dir+filename1);
  199. Mat image2 = imread(image_dir+filename2);
  200. CV_CheckEQ((int)image1.empty(), 0, "Image 1 is not found!");
  201. CV_CheckEQ((int)image2.empty(), 0, "Image 2 is not found!");
  202. // read calibration
  203. Matx33d K;
  204. for (int i = 0; i < 3; i++)
  205. for (int j = 0; j < 3; j++)
  206. file >> K(i,j);
  207. file.close();
  208. Mat descriptors1, descriptors2;
  209. std::vector<KeyPoint> keypoints1, keypoints2;
  210. // detect points with SIFT
  211. Ptr<SIFT> detector = SIFT::create();
  212. detector->detect(image1, keypoints1);
  213. detector->detect(image2, keypoints2);
  214. detector->compute(image1, keypoints1, descriptors1);
  215. detector->compute(image2, keypoints2, descriptors2);
  216. FlannBasedMatcher matcher(makePtr<flann::KDTreeIndexParams>(5), makePtr<flann::SearchParams>(32));
  217. // get k=2 best match that we can apply ratio test explained by D.Lowe
  218. std::vector<std::vector<DMatch>> matches_vector;
  219. matcher.knnMatch(descriptors1, descriptors2, matches_vector, 2);
  220. // filter keypoints with Lowe ratio test
  221. std::vector<Point2d> pts1, pts2;
  222. pts1.reserve(matches_vector.size()); pts2.reserve(matches_vector.size());
  223. for (const auto &m : matches_vector) {
  224. // compare best and second match using Lowe ratio test
  225. if (m[0].distance / m[1].distance < 0.75) {
  226. pts1.emplace_back(keypoints1[m[0].queryIdx].pt);
  227. pts2.emplace_back(keypoints2[m[0].trainIdx].pt);
  228. }
  229. }
  230. Mat inliers;
  231. const int pts_size = (int) pts1.size();
  232. const auto begin_time = std::chrono::steady_clock::now();
  233. // fine essential matrix
  234. const Mat E = findEssentialMat(pts1, pts2, Mat(K), RANSAC, 0.99, 1.0, inliers);
  235. std::cout << "RANSAC essential matrix time " << std::chrono::duration_cast<std::chrono::microseconds>
  236. (std::chrono::steady_clock::now() - begin_time).count() <<
  237. "mcs.\nNumber of inliers " << countNonZero(inliers) << "\n";
  238. Mat points1 = Mat((int)pts1.size(), 2, CV_64F, pts1.data());
  239. Mat points2 = Mat((int)pts2.size(), 2, CV_64F, pts2.data());
  240. points1 = points1.t(); points2 = points2.t();
  241. std::cout << "Mean error to epipolar lines " <<
  242. getError2EpipLines(K.inv().t() * E * K.inv(), points1, points2, inliers) << "\n";
  243. // decompose essential into rotation and translation
  244. Mat R1, R2, t;
  245. decomposeEssentialMat(E, R1, R2, t);
  246. // Create two relative pose
  247. // P1 = K [ I | 0 ]
  248. // P2 = K [R{1,2} | {+-}t]
  249. Mat P1;
  250. hconcat(K, Vec3d::zeros(), P1);
  251. std::vector<Mat> P2s(4);
  252. hconcat(K * R1, K * t, P2s[0]);
  253. hconcat(K * R1, -K * t, P2s[1]);
  254. hconcat(K * R2, K * t, P2s[2]);
  255. hconcat(K * R2, -K * t, P2s[3]);
  256. // find objects point by enumerating over 4 different projection matrices of second camera
  257. // vector to keep object points
  258. std::vector<std::vector<Vec3d>> obj_pts_per_cam(4);
  259. // vector to keep indices of image points corresponding to object points
  260. std::vector<std::vector<int>> img_idxs_per_cam(4);
  261. int cam_idx = 0, best_cam_idx = 0, max_obj_pts = 0;
  262. for (const auto &P2 : P2s) {
  263. obj_pts_per_cam[cam_idx].reserve(pts_size);
  264. img_idxs_per_cam[cam_idx].reserve(pts_size);
  265. for (int i = 0; i < pts_size; i++) {
  266. // process only inliers
  267. if (! inliers.at<uchar>(i))
  268. continue;
  269. Vec4d obj_pt;
  270. // find object point using triangulation
  271. triangulatePoints(P1, P2, points1.col(i), points2.col(i), obj_pt);
  272. obj_pt /= obj_pt(3); // normalize 4d point
  273. if (obj_pt(2) > 0) { // check if projected point has positive depth
  274. obj_pts_per_cam[cam_idx].emplace_back(Vec3d(obj_pt(0), obj_pt(1), obj_pt(2)));
  275. img_idxs_per_cam[cam_idx].emplace_back(i);
  276. }
  277. }
  278. if (max_obj_pts < (int) obj_pts_per_cam[cam_idx].size()) {
  279. max_obj_pts = (int) obj_pts_per_cam[cam_idx].size();
  280. best_cam_idx = cam_idx;
  281. }
  282. cam_idx++;
  283. }
  284. std::cout << "Number of object points " << max_obj_pts << "\n";
  285. const int circle_sz = 7;
  286. // draw image points that are inliers on two images
  287. std::vector<int> labels;
  288. std::vector<Vec4d> planes;
  289. getPlanes (obj_pts_per_cam[best_cam_idx], labels, planes, 4, 0.002, 0.99, 10000);
  290. const int num_found_planes = (int) planes.size();
  291. RNG rng;
  292. std::vector<Scalar> plane_colors (num_found_planes);
  293. for (int pl = 0; pl < num_found_planes; pl++)
  294. plane_colors[pl] = Scalar (rng.uniform(0,256), rng.uniform(0,256), rng.uniform(0,256));
  295. for (int obj_pt = 0; obj_pt < max_obj_pts; obj_pt++) {
  296. const int pt = img_idxs_per_cam[best_cam_idx][obj_pt];
  297. if (labels[obj_pt] > 0) { // plot plane points
  298. circle (image1, pts1[pt], circle_sz, plane_colors[labels[obj_pt]-1], -1);
  299. circle (image2, pts2[pt], circle_sz, plane_colors[labels[obj_pt]-1], -1);
  300. } else { // plot inliers
  301. circle (image1, pts1[pt], circle_sz, Scalar(0,0,0), -1);
  302. circle (image2, pts2[pt], circle_sz, Scalar(0,0,0), -1);
  303. }
  304. }
  305. // concatenate two images
  306. hconcat(image1, image2, image1);
  307. const int new_img_size = 1200 * 800; // for example
  308. // resize with the same aspect ratio
  309. resize(image1, image1, Size((int)sqrt ((double) image1.cols * new_img_size / image1.rows),
  310. (int)sqrt ((double) image1.rows * new_img_size / image1.cols)));
  311. imshow("image 1-2", image1);
  312. imwrite("planes.png", image1);
  313. waitKey(0);
  314. }