diff --git a/README.html b/README.html index 6c13281..c163d77 100644 --- a/README.html +++ b/README.html @@ -343,7 +343,7 @@

Constant function approximation

Linear function approximation

-

If we make make a slightly more appropriate assuming that in the neighborhood +

If we make make a slightly more appropriate assumption that in the neighborhood of the \(P_Y(\z₀)\) the surface \(Y\) is a plane, then we can improve this approximation while keeping \(\f\) linear in \(\z\):

diff --git a/README.md b/README.md index bce5dc2..ce58753 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,7 @@ derived our gradients geometrically. ### Linear function approximation -If we make make a slightly more appropriate assuming that in the neighborhood +If we make make a slightly more appropriate assumption that in the neighborhood of the $P_Y(\z₀)$ the surface $Y$ is a plane, then we can improve this approximation while keeping $\f$ linear in $\z$: diff --git a/main.cpp b/main.cpp index 3115cc9..807f74d 100644 --- a/main.cpp +++ b/main.cpp @@ -20,7 +20,7 @@ int main(int argc, char *argv[]) int num_samples = 100; bool show_samples = true; - ICPMethod method = ICP_METHOD_POINT_TO_POINT; + ICPMethod method = ICP_METHOD_POINT_TO_PLANE; igl::viewer::Viewer viewer; std::cout< +#include + void closest_rotation( const Eigen::Matrix3d & M, Eigen::Matrix3d & R) { - // Replace with your code - R = Eigen::Matrix3d::Identity(); + Eigen::JacobiSVD svd(M, Eigen::ComputeFullU | Eigen::ComputeFullV); + + Eigen::MatrixXd U = svd.matrixU(); + Eigen::MatrixXd Vt = svd.matrixV().transpose(); + + R << 1, 0, 0, + 0, 1, 0, + 0, 0, (U * Vt).determinant(); + R = (U * R) * Vt; } diff --git a/src/hausdorff_lower_bound.cpp b/src/hausdorff_lower_bound.cpp index 8608964..19592fc 100644 --- a/src/hausdorff_lower_bound.cpp +++ b/src/hausdorff_lower_bound.cpp @@ -1,4 +1,6 @@ #include "hausdorff_lower_bound.h" +#include "random_points_on_mesh.h" +#include "point_mesh_distance.h" double hausdorff_lower_bound( const Eigen::MatrixXd & VX, @@ -7,6 +9,17 @@ double hausdorff_lower_bound( const Eigen::MatrixXi & FY, const int n) { - // Replace with your code - return 0; + + // Sample `n` points from `X` + Eigen::MatrixXd X; + random_points_on_mesh(n, VX, FX, X); + + // Directed Hausdorff's lower bound is supremum of + // point-to-mesh distances for all sampled X + // i.e. worst of bests + Eigen::VectorXd D; + Eigen::MatrixXd P, N; + point_mesh_distance(X, VY, FY, D, P, N); + + return D.maxCoeff(); } diff --git a/src/icp_single_iteration.cpp b/src/icp_single_iteration.cpp index 1e8fda9..7065fc2 100644 --- a/src/icp_single_iteration.cpp +++ b/src/icp_single_iteration.cpp @@ -1,4 +1,8 @@ #include "icp_single_iteration.h" +#include "random_points_on_mesh.h" +#include "point_mesh_distance.h" +#include "point_to_plane_rigid_matching.h" +#include "point_to_point_rigid_matching.h" void icp_single_iteration( const Eigen::MatrixXd & VX, @@ -10,7 +14,24 @@ void icp_single_iteration( Eigen::Matrix3d & R, Eigen::RowVector3d & t) { - // Replace with your code - R = Eigen::Matrix3d::Identity(); - t = Eigen::RowVector3d::Zero(); + + auto transform = [&R, &t](Eigen::MatrixXd& X) { + return (X * R.transpose()).rowwise() + t; + }; + + // Sample X, project X to mesh Y after transformation + // P = P_Y(T(X)) \subset Y + Eigen::VectorXd D; + Eigen::MatrixXd X, P, N; + random_points_on_mesh(num_samples,VX,FX,X); + point_mesh_distance(X, VY, FY, D, P, N); + + // Update rigid transformation to match X and P + R = Eigen::Matrix3d::Identity(); + t = Eigen::RowVector3d::Zero(); + if (method == ICP_METHOD_POINT_TO_POINT) { + point_to_point_rigid_matching(X, P, R, t); + } else if (method == ICP_METHOD_POINT_TO_PLANE) { + point_to_plane_rigid_matching(X, P, N, R, t); + } } diff --git a/src/point_mesh_distance.cpp b/src/point_mesh_distance.cpp index 2e6a070..3f3ba2c 100644 --- a/src/point_mesh_distance.cpp +++ b/src/point_mesh_distance.cpp @@ -1,4 +1,11 @@ #include "point_mesh_distance.h" +#include "point_triangle_distance.h" + +#include +#include +#include +#include + void point_mesh_distance( const Eigen::MatrixXd & X, @@ -8,9 +15,40 @@ void point_mesh_distance( Eigen::MatrixXd & P, Eigen::MatrixXd & N) { - // Replace with your code - P.resizeLike(X); - N = Eigen::MatrixXd::Zero(X.rows(),X.cols()); - for(int i = 0;i::max(); + + // Computes closest distance & points from `x` to every triangle `t` + // Find triangle that minimizes point-to-mesh distance + for (int j = 0; j < ny; ++j) { + auto t = FY.row(j); + point_triangle_distance(x, VY.row(t(0)), VY.row(t(1)), VY.row(t(2)), d, p); + if (d < min_d) { + min_d = d; + min_idx = j; + closest_p = p; + } + } + + // Saves closest distance, closest point, and normal + D(i) = min_d; + P.row(i) = closest_p; + N.row(i) = NY.row(min_idx); + } } + diff --git a/src/point_to_plane_rigid_matching.cpp b/src/point_to_plane_rigid_matching.cpp index 1978510..3023d91 100644 --- a/src/point_to_plane_rigid_matching.cpp +++ b/src/point_to_plane_rigid_matching.cpp @@ -1,4 +1,7 @@ #include "point_to_plane_rigid_matching.h" +#include "closest_rotation.h" + +#include void point_to_plane_rigid_matching( const Eigen::MatrixXd & X, @@ -7,7 +10,39 @@ void point_to_plane_rigid_matching( Eigen::Matrix3d & R, Eigen::RowVector3d & t) { - // Replace with your code - R = Eigen::Matrix3d::Identity(); - t = Eigen::RowVector3d::Zero(); + // Construct matrix A + int k = X.rows(); + Eigen::VectorXd u(6); + Eigen::MatrixXd A(3*k, 6); + Eigen::MatrixXd b(3*k, 1); + Eigen::MatrixXd dN(k, 3*k); + + Eigen::VectorXd zeros = Eigen::VectorXd::Zero(k); + Eigen::VectorXd ones = Eigen::VectorXd::Ones(k); + + A << zeros, X.col(2), -X.col(1), ones, zeros, zeros, + -X.col(2), zeros, X.col(0), zeros, ones, zeros, + X.col(1), -X.col(0), zeros, zeros, zeros, ones; + + b << X.col(0) - P.col(0), + X.col(1) - P.col(1), + X.col(2) - P.col(2); + + dN << Eigen::MatrixXd(N.col(0).asDiagonal()), + Eigen::MatrixXd(N.col(1).asDiagonal()), + Eigen::MatrixXd(N.col(2).asDiagonal()); + + A = dN * A; + b = dN * b; + + // solve for u + u = (A.transpose()*A).inverse() * (-A.transpose()*b); + + // fill R and t + Eigen::Matrix3d M; + M << 1, -u(2), u(1), + u(2), 1, -u(0), + -u(1), u(0), 1; + closest_rotation(M, R); + t = u.tail(3); } diff --git a/src/point_to_point_rigid_matching.cpp b/src/point_to_point_rigid_matching.cpp index 079151f..4c35142 100644 --- a/src/point_to_point_rigid_matching.cpp +++ b/src/point_to_point_rigid_matching.cpp @@ -1,5 +1,7 @@ #include "point_to_point_rigid_matching.h" -#include +#include "closest_rotation.h" + +#include void point_to_point_rigid_matching( const Eigen::MatrixXd & X, @@ -7,8 +9,32 @@ void point_to_point_rigid_matching( Eigen::Matrix3d & R, Eigen::RowVector3d & t) { - // Replace with your code - R = Eigen::Matrix3d::Identity(); - t = Eigen::RowVector3d::Zero(); + // Construct matrix A + int k = X.rows(); + Eigen::VectorXd u(6); + Eigen::MatrixXd A(3*k, 6); + Eigen::MatrixXd b(3*k, 1); + + Eigen::VectorXd zeros = Eigen::VectorXd::Zero(k); + Eigen::VectorXd ones = Eigen::VectorXd::Ones(k); + + A << zeros, X.col(2), -X.col(1), ones, zeros, zeros, + -X.col(2), zeros, X.col(0), zeros, ones, zeros, + X.col(1), -X.col(0), zeros, zeros, zeros, ones; + + b << X.col(0) - P.col(0), + X.col(1) - P.col(1), + X.col(2) - P.col(2); + + // solve for u + u = (A.transpose()*A).inverse() * (-A.transpose()*b); + + // fill R and t + Eigen::Matrix3d M; + M << 1, -u(2), u(1), + u(2), 1, -u(0), + -u(1), u(0), 1; + closest_rotation(M, R); + t = u.tail(3); } diff --git a/src/point_triangle_distance.cpp b/src/point_triangle_distance.cpp index 6405100..093f039 100644 --- a/src/point_triangle_distance.cpp +++ b/src/point_triangle_distance.cpp @@ -1,5 +1,7 @@ #include "point_triangle_distance.h" +#include + void point_triangle_distance( const Eigen::RowVector3d & x, const Eigen::RowVector3d & a, @@ -8,7 +10,67 @@ void point_triangle_distance( double & d, Eigen::RowVector3d & p) { - // Replace with your code - d = 0; - p = a; + // Reference + // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.479.8237&rep=rep1&type=pdf + + + // Find point `q` on plane defined by a point `p` and normal `n` {z: (z-p) * n = 0} + // such that `q` is the (orthogonally) projected point of `x` + auto project = []( + const Eigen::RowVector3d& p, + const Eigen::RowVector3d& n, + const Eigen::RowVector3d& x) { + return x - ((x - p).dot(n) / (n.squaredNorm())) * n; + }; + + auto n = (b-a).cross(c-a); + p = project(a, n, x); + + // Find line connecting center of triangle and the vertices + auto va = (a-c).normalized() + (a-b).normalized(); + auto vb = (b-c).normalized() + (b-a).normalized(); + auto vc = (c-b).normalized() + (c-a).normalized(); + + // Find location of projected point `p` + // In particular, `p` is closest to edge represented by `uv` + auto fa = (va.cross(p-a)).dot(n); + auto fb = (vb.cross(p-b)).dot(n); + auto fc = (vc.cross(p-c)).dot(n); + + int ui, vi; + if (fa > 0 && fb < 0) { + ui = 0; vi = 1; + } else if (fb > 0 && fc < 0) { + ui = 1; vi = 2; + } else if (fc > 0 && fa < 0) { + ui = 2; vi = 0; + } + + auto get = [&](int i){ return (i == 0) ? a : ((i == 1) ? b : c); }; + auto u = get(ui); + auto v = get(vi); + + if ((v-p).cross(u-p).dot(n) > 0) { + // project `p` to the closest edge `uv` if `p` outside triangle + // the closest point is either a projected point `q` on edge `uv` + // or the vertices `u` or `v` + auto n2 = ((v-p).cross(u-p)).cross(u-v); + auto q = project(u, n2, p); + auto t = (q-u).dot(v-u) / (v-u).dot(v-u); + + if (t >= 0 && t <= 1) { + d = sqrt((p-q).squaredNorm() + (x-p).squaredNorm()); + p = q; + } else if (t < 0) { + d = sqrt((p-u).squaredNorm() + (x-p).squaredNorm()); + p = u; + } else { + d = sqrt((p-v).squaredNorm() + (x-p).squaredNorm()); + p = v; + } + } else { + // `p` is the closest point on the triangle if `p` inside triangle + d = (x-p).norm(); + } + } diff --git a/src/random_points_on_mesh.cpp b/src/random_points_on_mesh.cpp index 6e85d75..a22305a 100644 --- a/src/random_points_on_mesh.cpp +++ b/src/random_points_on_mesh.cpp @@ -1,13 +1,70 @@ #include "random_points_on_mesh.h" +#include +#include +#include +#include + void random_points_on_mesh( const int n, const Eigen::MatrixXd & V, const Eigen::MatrixXi & F, Eigen::MatrixXd & X) { - // REPLACE WITH YOUR CODE: - X.resize(n,3); - for(int i = 0;i(A.rows()); + + for (int i = 0; i < C.size(); ++i) { + if (i == 0) { + C[i] = A(i); + } else { + C[i] = C[i-1] + A(i); + } + } + for (int i = 0; i < C.size(); ++i) { + C[i] = C[i] / total_area; + } + + // Uniform random sampling of n triangles + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution unif(0., 1.); + + int v, T; + double alpha, beta; + std::vector::iterator upper; + Eigen::RowVector3i triangle; + Eigen::RowVector3d v1, v2, v3; + + for (int i = 0; i < n; ++i) { + upper = std::upper_bound(C.begin(), C.end(), unif(gen)); + T = upper - C.begin(); + triangle = F.row(T); + + alpha = unif(gen); + beta = unif(gen); + + if (alpha + beta > 1) { + alpha = 1. - alpha; + beta = 1. - beta; + } + + v = rand() % 3; + v1 = V.row(triangle(v)); + v2 = V.row(triangle((v+1) % 3)); + v3 = V.row(triangle((v+2) % 3)); + + X.row(i) = v1.array() + \ + alpha*(v2.array() - v1.array()) + \ + beta*(v3.array() - v1.array()); + } }