|
Point Cloud Library (PCL)
1.6.0
|
00001 /* 00002 * Software License Agreement (BSD License) 00003 * 00004 * Copyright (c) 2010, Willow Garage, Inc. 00005 * All rights reserved. 00006 * 00007 * Redistribution and use in source and binary forms, with or without 00008 * modification, are permitted provided that the following conditions 00009 * are met: 00010 * 00011 * * Redistributions of source code must retain the above copyright 00012 * notice, this list of conditions and the following disclaimer. 00013 * * Redistributions in binary form must reproduce the above 00014 * copyright notice, this list of conditions and the following 00015 * disclaimer in the documentation and/or other materials provided 00016 * with the distribution. 00017 * * Neither the name of Willow Garage, Inc. nor the names of its 00018 * contributors may be used to endorse or promote products derived 00019 * from this software without specific prior written permission. 00020 * 00021 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00022 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00023 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00024 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00025 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00026 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 00027 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00028 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 00029 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00030 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 00031 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00032 * POSSIBILITY OF SUCH DAMAGE. 00033 * 00034 * $Id: gicp.hpp 6152 2012-07-04 22:58:53Z rusu $ 00035 * 00036 */ 00037 00038 #include <boost/unordered_map.hpp> 00039 #include <pcl/registration/exceptions.h> 00040 00042 template <typename PointSource, typename PointTarget> 00043 template<typename PointT> void 00044 pcl::GeneralizedIterativeClosestPoint<PointSource, PointTarget>::computeCovariances(typename pcl::PointCloud<PointT>::ConstPtr cloud, 00045 const typename pcl::KdTree<PointT>::Ptr kdtree, 00046 std::vector<Eigen::Matrix3d>& cloud_covariances) 00047 { 00048 if (k_correspondences_ > int (cloud->size ())) 00049 { 00050 PCL_ERROR ("[pcl::GeneralizedIterativeClosestPoint::computeCovariances] Number or points in cloud (%zu) is less than k_correspondences_ (%zu)!\n", cloud->size (), k_correspondences_); 00051 return; 00052 } 00053 00054 Eigen::Vector3d mean; 00055 std::vector<int> nn_indecies; nn_indecies.reserve (k_correspondences_); 00056 std::vector<float> nn_dist_sq; nn_dist_sq.reserve (k_correspondences_); 00057 00058 // We should never get there but who knows 00059 if(cloud_covariances.size () < cloud->size ()) 00060 cloud_covariances.resize (cloud->size ()); 00061 00062 typename pcl::PointCloud<PointT>::const_iterator points_iterator = cloud->begin (); 00063 std::vector<Eigen::Matrix3d>::iterator matrices_iterator = cloud_covariances.begin (); 00064 for(; 00065 points_iterator != cloud->end (); 00066 ++points_iterator, ++matrices_iterator) 00067 { 00068 const PointT &query_point = *points_iterator; 00069 Eigen::Matrix3d &cov = *matrices_iterator; 00070 // Zero out the cov and mean 00071 cov.setZero (); 00072 mean.setZero (); 00073 00074 // Search for the K nearest neighbours 00075 kdtree->nearestKSearch(query_point, k_correspondences_, nn_indecies, nn_dist_sq); 00076 00077 // Find the covariance matrix 00078 for(int j = 0; j < k_correspondences_; j++) { 00079 const PointT &pt = (*cloud)[nn_indecies[j]]; 00080 00081 mean[0] += pt.x; 00082 mean[1] += pt.y; 00083 mean[2] += pt.z; 00084 00085 cov(0,0) += pt.x*pt.x; 00086 00087 cov(1,0) += pt.y*pt.x; 00088 cov(1,1) += pt.y*pt.y; 00089 00090 cov(2,0) += pt.z*pt.x; 00091 cov(2,1) += pt.z*pt.y; 00092 cov(2,2) += pt.z*pt.z; 00093 } 00094 00095 mean /= static_cast<double> (k_correspondences_); 00096 // Get the actual covariance 00097 for (int k = 0; k < 3; k++) 00098 for (int l = 0; l <= k; l++) 00099 { 00100 cov(k,l) /= static_cast<double> (k_correspondences_); 00101 cov(k,l) -= mean[k]*mean[l]; 00102 cov(l,k) = cov(k,l); 00103 } 00104 00105 // Compute the SVD (covariance matrix is symmetric so U = V') 00106 Eigen::JacobiSVD<Eigen::Matrix3d> svd(cov, Eigen::ComputeFullU); 00107 cov.setZero (); 00108 Eigen::Matrix3d U = svd.matrixU (); 00109 // Reconstitute the covariance matrix with modified singular values using the column // vectors in V. 00110 for(int k = 0; k < 3; k++) { 00111 Eigen::Vector3d col = U.col(k); 00112 double v = 1.; // biggest 2 singular values replaced by 1 00113 if(k == 2) // smallest singular value replaced by gicp_epsilon 00114 v = gicp_epsilon_; 00115 cov+= v * col * col.transpose(); 00116 } 00117 } 00118 } 00119 00121 template <typename PointSource, typename PointTarget> void 00122 pcl::GeneralizedIterativeClosestPoint<PointSource, PointTarget>::computeRDerivative(const Vector6d &x, const Eigen::Matrix3d &R, Vector6d& g) const 00123 { 00124 Eigen::Matrix3d dR_dPhi; 00125 Eigen::Matrix3d dR_dTheta; 00126 Eigen::Matrix3d dR_dPsi; 00127 00128 double phi = x[3], theta = x[4], psi = x[5]; 00129 00130 double cphi = cos(phi), sphi = sin(phi); 00131 double ctheta = cos(theta), stheta = sin(theta); 00132 double cpsi = cos(psi), spsi = sin(psi); 00133 00134 dR_dPhi(0,0) = 0.; 00135 dR_dPhi(1,0) = 0.; 00136 dR_dPhi(2,0) = 0.; 00137 00138 dR_dPhi(0,1) = sphi*spsi + cphi*cpsi*stheta; 00139 dR_dPhi(1,1) = -cpsi*sphi + cphi*spsi*stheta; 00140 dR_dPhi(2,1) = cphi*ctheta; 00141 00142 dR_dPhi(0,2) = cphi*spsi - cpsi*sphi*stheta; 00143 dR_dPhi(1,2) = -cphi*cpsi - sphi*spsi*stheta; 00144 dR_dPhi(2,2) = -ctheta*sphi; 00145 00146 dR_dTheta(0,0) = -cpsi*stheta; 00147 dR_dTheta(1,0) = -spsi*stheta; 00148 dR_dTheta(2,0) = -ctheta; 00149 00150 dR_dTheta(0,1) = cpsi*ctheta*sphi; 00151 dR_dTheta(1,1) = ctheta*sphi*spsi; 00152 dR_dTheta(2,1) = -sphi*stheta; 00153 00154 dR_dTheta(0,2) = cphi*cpsi*ctheta; 00155 dR_dTheta(1,2) = cphi*ctheta*spsi; 00156 dR_dTheta(2,2) = -cphi*stheta; 00157 00158 dR_dPsi(0,0) = -ctheta*spsi; 00159 dR_dPsi(1,0) = cpsi*ctheta; 00160 dR_dPsi(2,0) = 0.; 00161 00162 dR_dPsi(0,1) = -cphi*cpsi - sphi*spsi*stheta; 00163 dR_dPsi(1,1) = -cphi*spsi + cpsi*sphi*stheta; 00164 dR_dPsi(2,1) = 0.; 00165 00166 dR_dPsi(0,2) = cpsi*sphi - cphi*spsi*stheta; 00167 dR_dPsi(1,2) = sphi*spsi + cphi*cpsi*stheta; 00168 dR_dPsi(2,2) = 0.; 00169 00170 g[3] = matricesInnerProd(dR_dPhi, R); 00171 g[4] = matricesInnerProd(dR_dTheta, R); 00172 g[5] = matricesInnerProd(dR_dPsi, R); 00173 } 00174 00176 template <typename PointSource, typename PointTarget> void 00177 pcl::GeneralizedIterativeClosestPoint<PointSource, PointTarget>::estimateRigidTransformationBFGS (const PointCloudSource &cloud_src, 00178 const std::vector<int> &indices_src, 00179 const PointCloudTarget &cloud_tgt, 00180 const std::vector<int> &indices_tgt, 00181 Eigen::Matrix4f &transformation_matrix) 00182 { 00183 if (indices_src.size () < 4) // need at least 4 samples 00184 { 00185 PCL_THROW_EXCEPTION (NotEnoughPointsException, 00186 "[pcl::GeneralizedIterativeClosestPoint::estimateRigidTransformationBFGS] Need at least 4 points to estimate a transform! Source and target have " << indices_src.size () << " points!"); 00187 return; 00188 } 00189 // Set the initial solution 00190 Vector6d x = Vector6d::Zero (); 00191 x[0] = transformation_matrix (0,3); 00192 x[1] = transformation_matrix (1,3); 00193 x[2] = transformation_matrix (2,3); 00194 x[3] = atan2 (transformation_matrix (2,1), transformation_matrix (2,2)); 00195 x[4] = asin (-transformation_matrix (2,0)); 00196 x[5] = atan2 (transformation_matrix (1,0), transformation_matrix (0,0)); 00197 00198 // Set temporary pointers 00199 tmp_src_ = &cloud_src; 00200 tmp_tgt_ = &cloud_tgt; 00201 tmp_idx_src_ = &indices_src; 00202 tmp_idx_tgt_ = &indices_tgt; 00203 00204 // Optimize using forward-difference approximation LM 00205 const double gradient_tol = 1e-2; 00206 OptimizationFunctorWithIndices functor(this); 00207 BFGS<OptimizationFunctorWithIndices> bfgs (functor); 00208 bfgs.parameters.sigma = 0.01; 00209 bfgs.parameters.rho = 0.01; 00210 bfgs.parameters.tau1 = 9; 00211 bfgs.parameters.tau2 = 0.05; 00212 bfgs.parameters.tau3 = 0.5; 00213 bfgs.parameters.order = 3; 00214 00215 int inner_iterations_ = 0; 00216 int result = bfgs.minimizeInit (x); 00217 do 00218 { 00219 inner_iterations_++; 00220 result = bfgs.minimizeOneStep (x); 00221 if(result) 00222 { 00223 break; 00224 } 00225 result = bfgs.testGradient(gradient_tol); 00226 } while(result == BFGSSpace::Running && inner_iterations_ < max_inner_iterations_); 00227 if(result == BFGSSpace::NoProgress || result == BFGSSpace::Success || inner_iterations_ == max_inner_iterations_) 00228 { 00229 PCL_DEBUG ("[pcl::registration::TransformationEstimationBFGS::estimateRigidTransformation]"); 00230 PCL_DEBUG ("BFGS solver finished with exit code %i \n", result); 00231 transformation_matrix.setIdentity(); 00232 applyState(transformation_matrix, x); 00233 } 00234 else 00235 PCL_THROW_EXCEPTION(SolverDidntConvergeException, 00236 "[pcl::" << getClassName () << "::TransformationEstimationBFGS::estimateRigidTransformation] BFGS solver didn't converge!"); 00237 } 00238 00240 template <typename PointSource, typename PointTarget> inline double 00241 pcl::GeneralizedIterativeClosestPoint<PointSource, PointTarget>::OptimizationFunctorWithIndices::operator() (const Vector6d& x) 00242 { 00243 Eigen::Matrix4f transformation_matrix = gicp_->base_transformation_; 00244 gicp_->applyState(transformation_matrix, x); 00245 double f = 0; 00246 int m = static_cast<int> (gicp_->tmp_idx_src_->size ()); 00247 for (int i = 0; i < m; ++i) 00248 { 00249 // The last coordinate, p_src[3] is guaranteed to be set to 1.0 in registration.hpp 00250 Vector4fMapConst p_src = gicp_->tmp_src_->points[(*gicp_->tmp_idx_src_)[i]].getVector4fMap (); 00251 // The last coordinate, p_tgt[3] is guaranteed to be set to 1.0 in registration.hpp 00252 Vector4fMapConst p_tgt = gicp_->tmp_tgt_->points[(*gicp_->tmp_idx_tgt_)[i]].getVector4fMap (); 00253 Eigen::Vector4f pp (transformation_matrix * p_src); 00254 // Estimate the distance (cost function) 00255 // The last coordiante is still guaranteed to be set to 1.0 00256 Eigen::Vector3d res(pp[0] - p_tgt[0], pp[1] - p_tgt[1], pp[2] - p_tgt[2]); 00257 Eigen::Vector3d temp (gicp_->mahalanobis((*gicp_->tmp_idx_src_)[i]) * res); 00258 //increment= res'*temp/num_matches = temp'*M*temp/num_matches (we postpone 1/num_matches after the loop closes) 00259 f+= double(res.transpose() * temp); 00260 } 00261 return f/m; 00262 } 00263 00265 template <typename PointSource, typename PointTarget> inline void 00266 pcl::GeneralizedIterativeClosestPoint<PointSource, PointTarget>::OptimizationFunctorWithIndices::df (const Vector6d& x, Vector6d& g) 00267 { 00268 Eigen::Matrix4f transformation_matrix = gicp_->base_transformation_; 00269 gicp_->applyState(transformation_matrix, x); 00270 //Zero out g 00271 g.setZero (); 00272 //Eigen::Vector3d g_t = g.head<3> (); 00273 Eigen::Matrix3d R = Eigen::Matrix3d::Zero (); 00274 int m = static_cast<int> (gicp_->tmp_idx_src_->size ()); 00275 for (int i = 0; i < m; ++i) 00276 { 00277 // The last coordinate, p_src[3] is guaranteed to be set to 1.0 in registration.hpp 00278 Vector4fMapConst p_src = gicp_->tmp_src_->points[(*gicp_->tmp_idx_src_)[i]].getVector4fMap (); 00279 // The last coordinate, p_tgt[3] is guaranteed to be set to 1.0 in registration.hpp 00280 Vector4fMapConst p_tgt = gicp_->tmp_tgt_->points[(*gicp_->tmp_idx_tgt_)[i]].getVector4fMap (); 00281 00282 Eigen::Vector4f pp (transformation_matrix * p_src); 00283 // The last coordiante is still guaranteed to be set to 1.0 00284 Eigen::Vector3d res (pp[0] - p_tgt[0], pp[1] - p_tgt[1], pp[2] - p_tgt[2]); 00285 // temp = M*res 00286 Eigen::Vector3d temp (gicp_->mahalanobis ((*gicp_->tmp_idx_src_)[i]) * res); 00287 // Increment translation gradient 00288 // g.head<3> ()+= 2*M*res/num_matches (we postpone 2/num_matches after the loop closes) 00289 g.head<3> ()+= temp; 00290 // Increment rotation gradient 00291 pp = gicp_->base_transformation_ * p_src; 00292 Eigen::Vector3d p_src3 (pp[0], pp[1], pp[2]); 00293 R+= p_src3 * temp.transpose(); 00294 } 00295 g.head<3> ()*= 2.0/m; 00296 R*= 2.0/m; 00297 gicp_->computeRDerivative(x, R, g); 00298 } 00299 00301 template <typename PointSource, typename PointTarget> inline void 00302 pcl::GeneralizedIterativeClosestPoint<PointSource, PointTarget>::OptimizationFunctorWithIndices::fdf (const Vector6d& x, double& f, Vector6d& g) 00303 { 00304 Eigen::Matrix4f transformation_matrix = gicp_->base_transformation_; 00305 gicp_->applyState(transformation_matrix, x); 00306 f = 0; 00307 g.setZero (); 00308 Eigen::Matrix3d R = Eigen::Matrix3d::Zero (); 00309 const int m = static_cast<const int> (gicp_->tmp_idx_src_->size ()); 00310 for (int i = 0; i < m; ++i) 00311 { 00312 // The last coordinate, p_src[3] is guaranteed to be set to 1.0 in registration.hpp 00313 Vector4fMapConst p_src = gicp_->tmp_src_->points[(*gicp_->tmp_idx_src_)[i]].getVector4fMap (); 00314 // The last coordinate, p_tgt[3] is guaranteed to be set to 1.0 in registration.hpp 00315 Vector4fMapConst p_tgt = gicp_->tmp_tgt_->points[(*gicp_->tmp_idx_tgt_)[i]].getVector4fMap (); 00316 Eigen::Vector4f pp (transformation_matrix * p_src); 00317 // The last coordiante is still guaranteed to be set to 1.0 00318 Eigen::Vector3d res (pp[0] - p_tgt[0], pp[1] - p_tgt[1], pp[2] - p_tgt[2]); 00319 // temp = M*res 00320 Eigen::Vector3d temp (gicp_->mahalanobis((*gicp_->tmp_idx_src_)[i]) * res); 00321 // Increment total error 00322 f+= double(res.transpose() * temp); 00323 // Increment translation gradient 00324 // g.head<3> ()+= 2*M*res/num_matches (we postpone 2/num_matches after the loop closes) 00325 g.head<3> ()+= temp; 00326 pp = gicp_->base_transformation_ * p_src; 00327 Eigen::Vector3d p_src3 (pp[0], pp[1], pp[2]); 00328 // Increment rotation gradient 00329 R+= p_src3 * temp.transpose(); 00330 } 00331 f/= double(m); 00332 g.head<3> ()*= double(2.0/m); 00333 R*= 2.0/m; 00334 gicp_->computeRDerivative(x, R, g); 00335 } 00336 00338 template <typename PointSource, typename PointTarget> inline void 00339 pcl::GeneralizedIterativeClosestPoint<PointSource, PointTarget>::computeTransformation (PointCloudSource &output, const Eigen::Matrix4f& guess) 00340 { 00341 using namespace std; 00342 // Difference between consecutive transforms 00343 double delta = 0; 00344 // Get the size of the target 00345 const size_t N = indices_->size (); 00346 // Set the mahalanobis matrices to identity 00347 mahalanobis_.resize (N, Eigen::Matrix3d::Identity ()); 00348 // Compute target cloud covariance matrices 00349 computeCovariances<PointTarget> (target_, tree_, target_covariances_); 00350 // Compute input cloud covariance matrices 00351 computeCovariances<PointSource> (input_, input_tree_, input_covariances_); 00352 00353 base_transformation_ = guess; 00354 nr_iterations_ = 0; 00355 converged_ = false; 00356 double dist_threshold = corr_dist_threshold_ * corr_dist_threshold_; 00357 std::vector<int> nn_indices (1); 00358 std::vector<float> nn_dists (1); 00359 00360 while(!converged_) 00361 { 00362 size_t cnt = 0; 00363 std::vector<int> source_indices (indices_->size ()); 00364 std::vector<int> target_indices (indices_->size ()); 00365 00366 // guess corresponds to base_t and transformation_ to t 00367 Eigen::Matrix4d transform_R = Eigen::Matrix4d::Zero (); 00368 for(size_t i = 0; i < 4; i++) 00369 for(size_t j = 0; j < 4; j++) 00370 for(size_t k = 0; k < 4; k++) 00371 transform_R(i,j)+= double(transformation_(i,k)) * double(guess(k,j)); 00372 00373 Eigen::Matrix3d R = transform_R.topLeftCorner<3,3> (); 00374 00375 for (size_t i = 0; i < N; i++) 00376 { 00377 PointSource query = output[i]; 00378 query.getVector4fMap () = guess * query.getVector4fMap (); 00379 query.getVector4fMap () = transformation_ * query.getVector4fMap (); 00380 00381 if (!searchForNeighbors (query, nn_indices, nn_dists)) 00382 { 00383 PCL_ERROR ("[pcl::%s::computeTransformation] Unable to find a nearest neighbor in the target dataset for point %d in the source!\n", getClassName ().c_str (), (*indices_)[i]); 00384 return; 00385 } 00386 00387 // Check if the distance to the nearest neighbor is smaller than the user imposed threshold 00388 if (nn_dists[0] < dist_threshold) 00389 { 00390 Eigen::Matrix3d &C1 = input_covariances_[i]; 00391 Eigen::Matrix3d &C2 = target_covariances_[nn_indices[0]]; 00392 Eigen::Matrix3d &M = mahalanobis_[i]; 00393 // M = R*C1 00394 M = R * C1; 00395 // temp = M*R' + C2 = R*C1*R' + C2 00396 Eigen::Matrix3d temp = M * R.transpose(); 00397 temp+= C2; 00398 // M = temp^-1 00399 M = temp.inverse (); 00400 source_indices[cnt] = static_cast<int> (i); 00401 target_indices[cnt] = nn_indices[0]; 00402 cnt++; 00403 } 00404 } 00405 // Resize to the actual number of valid correspondences 00406 source_indices.resize(cnt); target_indices.resize(cnt); 00407 /* optimize transformation using the current assignment and Mahalanobis metrics*/ 00408 previous_transformation_ = transformation_; 00409 //optimization right here 00410 try 00411 { 00412 rigid_transformation_estimation_(output, source_indices, *target_, target_indices, transformation_); 00413 /* compute the delta from this iteration */ 00414 delta = 0.; 00415 for(int k = 0; k < 4; k++) { 00416 for(int l = 0; l < 4; l++) { 00417 double ratio = 1; 00418 if(k < 3 && l < 3) // rotation part of the transform 00419 ratio = 1./rotation_epsilon_; 00420 else 00421 ratio = 1./transformation_epsilon_; 00422 double c_delta = ratio*fabs(previous_transformation_(k,l) - transformation_(k,l)); 00423 if(c_delta > delta) 00424 delta = c_delta; 00425 } 00426 } 00427 } 00428 catch (PCLException &e) 00429 { 00430 PCL_DEBUG ("[pcl::%s::computeTransformation] Optimization issue %s\n", getClassName ().c_str (), e.what ()); 00431 break; 00432 } 00433 nr_iterations_++; 00434 // Check for convergence 00435 if (nr_iterations_ >= max_iterations_ || delta < 1) 00436 { 00437 converged_ = true; 00438 previous_transformation_ = transformation_; 00439 PCL_DEBUG ("[pcl::%s::computeTransformation] Convergence reached. Number of iterations: %d out of %d. Transformation difference: %f\n", 00440 getClassName ().c_str (), nr_iterations_, max_iterations_, (transformation_ - previous_transformation_).array ().abs ().sum ()); 00441 } 00442 else 00443 PCL_DEBUG ("[pcl::%s::computeTransformation] Convergence failed\n", getClassName ().c_str ()); 00444 } 00445 //for some reason the static equivalent methode raises an error 00446 // final_transformation_.block<3,3> (0,0) = (transformation_.block<3,3> (0,0)) * (guess.block<3,3> (0,0)); 00447 // final_transformation_.block <3, 1> (0, 3) = transformation_.block <3, 1> (0, 3) + guess.rightCols<1>.block <3, 1> (0, 3); 00448 final_transformation_.topLeftCorner (3,3) = previous_transformation_.topLeftCorner (3,3) * guess.topLeftCorner (3,3); 00449 final_transformation_(0,3) = previous_transformation_(0,3) + guess(0,3); 00450 final_transformation_(1,3) = previous_transformation_(1,3) + guess(1,3); 00451 final_transformation_(2,3) = previous_transformation_(2,3) + guess(2,3); 00452 } 00453 00454 template <typename PointSource, typename PointTarget> void 00455 pcl::GeneralizedIterativeClosestPoint<PointSource, PointTarget>::applyState(Eigen::Matrix4f &t, const Vector6d& x) const 00456 { 00457 // !!! CAUTION Stanford GICP uses the Z Y X euler angles convention 00458 Eigen::Matrix3f R; 00459 R = Eigen::AngleAxisf (static_cast<float> (x[5]), Eigen::Vector3f::UnitZ ()) 00460 * Eigen::AngleAxisf (static_cast<float> (x[4]), Eigen::Vector3f::UnitY ()) 00461 * Eigen::AngleAxisf (static_cast<float> (x[3]), Eigen::Vector3f::UnitX ()); 00462 t.topLeftCorner<3,3> () = R * t.topLeftCorner<3,3> (); 00463 Eigen::Vector4f T (static_cast<float> (x[0]), static_cast<float> (x[1]), static_cast<float> (x[2]), 0.0f); 00464 t.col (3) += T; 00465 } 00466
1.7.6.1