由于课本前六讲基本都是公式推导,所以就写在了平板上,而后面的多是理论知识以及代码的实现,所以特此在csdn上记录分享,
除了ORB之外,一般还有还有SIFT、SURF、BRISK、AKAZE,这些都是在OpenCV中已经实现了的。
SIFT算法,又称为尺度不变特征转换(Scale-invariant feature transform),大致方法是首先搜索所有尺度下图像的位置,通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点,然后在候选位置通过拟合精细的模型来确定位置和尺度,再基于图像梯度,分配给关键点一个或多个方向,最后在每个关键点的周围邻域,在选定的尺度下测量图像局部梯度来描述关键点。使用斑点检测方法和浮点型特征描述子,在建立高斯差分空间金字塔的基础上提取初具有尺度不变性的特征点,然后对特征点邻域内的点的梯度方向进行直方图统计。特征点的主方向就是直方图中比重最大的方向,必要时可选一个辅方向。SIFT特征集旋转不变性,尺度不变性,对图像变形和光照鲁棒鲁等优点于一身,不足之处是计算量大,计算速度慢,需要GPU加速才可满足实时性需求。
SURF算法,又称为加速稳健特征(Speeded Up Robust Features),其方法和构建图像金字塔的方法相反,其主要方法是通过修改滤波器的尺寸和模糊度从而得到不同层级的影像,但是寻找特征点的方法和SIFT类似。使用基于DoH的斑点特征检测方法,在特征点的描述上,SURF算法通过积分图,利用两个方向上的Harr小波模型进行梯度计算,然后对邻域内点的梯度方向以扇形的方式进行统计,得到特征点的主方向。其算法速度快且稳定性好。
SIFT和SURF能够比较稳定地提供较高的准确率,其中SIFT比SURF更准确一点,但是二者都特别慢。相较而言ORB速度更快,但是更容易出现问题,而且错误率远比其他二者大。
计算速度: ORB>>SURF>>SIFT(各差一个量级)
旋转鲁棒性: SURF>ORB~SIFT(表示差不多)
模糊鲁棒性: SURF>ORB~SIFT
尺度变换鲁棒性: SURF>SIFT>ORB(ORB并不具备尺度变换性)
综上所述,如果对计算实时性要求非常高,可选用ORB算法,但基本要保证正对拍摄;如果对实行性要求稍高,可以选择SURF;基本不用SIFT
参考文献《视觉SLAM十四讲》课后习题—ch7(更新中……)
如果上述文章失效,那么可以自取工程包,是我存储的,当时缺少opencv_contrib没有实现,CMakeLists.txt可能会有我问题,如果你的opencv符合要求,可以试试:链接:https://pan.baidu.com/s/1XE1u3FS2lAEGU_jdyT0KnQ 提取码:ny2r
但是这个工程的实现,需要首先我们要知道如果要调用opencv中的SIFT和SURF特征点,SIFT和SURF都在nonfree模块中,所以我们就需要nonfree模块。但是在opencv3中,SURF/SIFT 以及其它的一些东西被移动到了独立的库(opencv_contrib)中。所以首先我们需要安装opencv_contrib:(如果你用的是opencv2可以省略安装opencv_contrib这一步骤)
如果你已经安装了opencv3,那么要重装,可以参考ubuntu18.04安装opencv+opencv_contrib安装详细教程(亲测有效,包含安装包3.4.1及boostdesc_bgm.i文件),这里我没有亲自验证,有时间再回来
如果你需要opencv3.4.1和对应版本的opencv_contrib3.4.1安装包,自取:链接https://pan.baidu.com/s/1mEpZSEeTj4TWBCX2Rv3vUw 提取码:usgp
目前除了EPnP外,OpenCV还提供了另外两种方法:迭代法和P3P法,其中,在OpenCV3中还另外提供了DLS法和UPnP法。
EPnP(Efficient PnP)的思路是将空间中的任意3D点可以用4个不共面的3D点加权表示,然后通过n个3D点在相机平面的投影关系以及四个控制点的权重关系构建一个的矩阵,求这个矩阵的特征向量,就可以得到这个相机平面的坐标,再用POSIT正交投影变换算法得到相机位姿。
迭代法实质是迭代求出重投影误差的最小解先用DLT直接线性变换求解,然后用LM算法进行优化,这个解显然不是正解,而且这个方法只能使用4个共面特征点才能求得。
P3P法,它需要3对3D-2D的匹配点,然后通过三角形投影关系得到的方程组,然后将方程组进行变换得到两个二元二次方程进行求解。
DLS(Direct Least-Squares)算法整体思路是首先对PnP非线性最小贰乘建模,重新定义LS模型以便于参数降维然后构造Maculy矩阵进行求解,将PnP问题重构为无约束LSM问题然后优化求解。
UPnP(Uncalibrated PnP)算法跟EPnP差不了太多,仅仅是多估计了焦距。因此,比较适合未标定场合。
代码基础是【slam十四讲第二版】【课本例题代码向】【第七讲~视觉里程计Ⅰ】【1OpenCV的ORB特征】【2手写ORB特征】【3对极约束求解相机运动】【4三角测量】【5求解PnP】【3D-3D:ICP】中的第5小节,这里我的本工程的代码自取:链接:链接: https://pan.baidu.com/s/1t63wx7OInkjuu3NaZqM2Uw 提取码: 9a9v
// Created by czy on 2022/4/7
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
void find_feature_matches(const cv::Mat& img_1, const cv::Mat& img_2,
std::vector<cv::KeyPoint>& keypoints_1, std::vector<cv::KeyPoint>& keypoints_2,
std::vector<cv::DMatch>& matches);
// 像素坐标转相机归一化坐标
cv::Point2d pixel2cam(const cv::Point2d& p, const cv::Mat& K);
int main(int argc, char ** argv) {
cv::Mat img_1,img_2,d1,d2;
if(argc!=5)
{
//读取图片
img_1 = cv::imread("../src/1.png", CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread("../src/2.png",1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
d1 = cv::imread("../src/1_depth.png", CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
d2 = cv::imread("../src/2_depth.png", -1);
}
else
{
//读取图片
img_1 = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread(argv[2],1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
d1 = cv::imread(argv[3], CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
d2 = cv::imread(argv[4], -1);
}
assert(img_1.data != nullptr && img_2.data != nullptr);//若读取的图片没有内容,就终止程序
vector<cv::KeyPoint> keypoints_1, keypoints_2;
vector<cv::DMatch> matches;
find_feature_matches(img_1, img_2, keypoints_1, keypoints_2, matches);//得到两个图片的特征匹配点
cout << "一共找到了" << matches.size() << "组匹配点" << endl;
cv::Mat K = (cv::Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
vector<cv::Point3f> pts_3d;//创建容器pts_3d存放3d点(图1对应的特征点的相机坐标下的3d点)
vector<cv::Point2f> pts_2d;//创建容器pts_2d存放图2的特征点
for (cv::DMatch m: matches) {
//把对应的图1的特征点的深度信息拿出来
ushort d = d1.ptr<unsigned short>(int(keypoints_1[m.queryIdx].pt.y))[int(keypoints_1[m.queryIdx].pt.x)];
if (d == 0) // bad depth
continue;
float dd = d / 5000.0;//用dd存放换算过尺度的深度信息
cv::Point2d p1 = pixel2cam(keypoints_1[m.queryIdx].pt, K);//p1里面放的是图1特征点在相机坐标下的归一化坐标(只包含 x,y)
pts_3d.push_back(cv::Point3f(p1.x * dd, p1.y * dd, dd));//得到图1特征点在相机坐标下的3d坐标
pts_2d.push_back(keypoints_2[m.trainIdx].pt);//得到图2特张点的像素坐标
}
cout << "3d-2d pairs: " << pts_3d.size() << endl;//3d-2d配对个数得用pts_3d的size
cout << "使用cv_PnP求解 位姿" << endl;
cv::Mat r, t;
cv::Mat R;
chrono::steady_clock::time_point t1,t2;
chrono::duration<double> time_used;
cout << "***********************************SOLVEPNP_ITERATIVE***********************************" << endl;
t1 = chrono::steady_clock::now();
//Mat()这个参数指的是畸变系数向量
cv::solvePnP(pts_3d, pts_2d, K, cv::Mat(), r, t, false,cv::SOLVEPNP_ITERATIVE); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cv::Rodrigues(r, R);//r为旋转向量形式,利用cv的Rodrigues()函数将旋转向量转换为旋转矩阵
cout << "solve pnp in opencv cost time: " << time_used.count() << " seconds." << endl;
cout << "R=" << endl << R << endl;
cout << "t=" << endl << t << endl;
cout << "calling bundle adjustment" << endl;
cout << "***********************************SOLVEPNP_ITERATIVE***********************************" << endl;
cout << "***********************************SOLVEPNP_EPNP***********************************" << endl;
t1 = chrono::steady_clock::now();
//Mat()这个参数指的是畸变系数向量
cv::solvePnP(pts_3d, pts_2d, K, cv::Mat(), r, t, false,cv::SOLVEPNP_EPNP); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cv::Rodrigues(r, R);//r为旋转向量形式,利用cv的Rodrigues()函数将旋转向量转换为旋转矩阵
cout << "solve pnp in opencv cost time: " << time_used.count() << " seconds." << endl;
cout << "R=" << endl << R << endl;
cout << "t=" << endl << t << endl;
cout << "calling bundle adjustment" << endl;
cout << "***********************************SOLVEPNP_EPNP***********************************" << endl;
cout << "***********************************SOLVEPNP_UPNP***********************************" << endl;
t1 = chrono::steady_clock::now();
//Mat()这个参数指的是畸变系数向量
cv::solvePnP(pts_3d, pts_2d, K, cv::Mat(), r, t, false,cv::SOLVEPNP_UPNP); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cv::Rodrigues(r, R);//r为旋转向量形式,利用cv的Rodrigues()函数将旋转向量转换为旋转矩阵
cout << "solve pnp in opencv cost time: " << time_used.count() << " seconds." << endl;
cout << "R=" << endl << R << endl;
cout << "t=" << endl << t << endl;
cout << "calling bundle adjustment" << endl;
cout << "***********************************SOLVEPNP_UPNP***********************************" << endl;
cout << "***********************************SOLVEPNP_DLS***********************************" << endl;
t1 = chrono::steady_clock::now();
//Mat()这个参数指的是畸变系数向量
cv::solvePnP(pts_3d, pts_2d, K, cv::Mat(), r, t, false,cv::SOLVEPNP_DLS); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cv::Rodrigues(r, R);//r为旋转向量形式,利用cv的Rodrigues()函数将旋转向量转换为旋转矩阵
cout << "solve pnp in opencv cost time: " << time_used.count() << " seconds." << endl;
cout << "R=" << endl << R << endl;
cout << "t=" << endl << t << endl;
cout << "calling bundle adjustment" << endl;
cout << "***********************************SOLVEPNP_DLS***********************************" << endl;
cout << "***********************************SOLVEPNP_P3P***********************************" << endl;
vector<cv::Point3f> pts_p3p_3d;//创建容器pts_3d存放3d点(图1对应的特征点的相机坐标下的3d点)
vector<cv::Point2f> pts_p3p_2d;//创建容器pts_2d存放图2的特征点
//取出其中的4个点对
for (int i = 0; i < 4; i++)
{
pts_p3p_3d.push_back(pts_3d[i]);
pts_p3p_2d.push_back(pts_2d[i]);
}
/* cv::Mat pts_p3p = (cv::Mat_(4,3) <<
pts_p3p_3d[0].x,pts_p3p_3d[0].y,pts_p3p_3d[0].z,
pts_p3p_3d[1].x,pts_p3p_3d[1].y,pts_p3p_3d[1].z,
pts_p3p_3d[2].x,pts_p3p_3d[2].y,pts_p3p_3d[2].z,
pts_p3p_3d[3].x,pts_p3p_3d[3].y,pts_p3p_3d[3].z
);*/
t1 = chrono::steady_clock::now();
//Mat()这个参数指的是畸变系数向量
cv::solvePnP(pts_p3p_3d, pts_p3p_2d, K, cv::Mat(), r, t, false,cv::SOLVEPNP_P3P); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cv::Rodrigues(r, R);//r为旋转向量形式,利用cv的Rodrigues()函数将旋转向量转换为旋转矩阵
cout << "solve pnp in opencv cost time: " << time_used.count() << " seconds." << endl;
cout << "R=" << endl << R << endl;
cout << "t=" << endl << t << endl;
cout << "calling bundle adjustment" << endl;
cout << "***********************************SOLVEPNP_P3P***********************************" << endl;
}
//实现特征匹配
void find_feature_matches(const cv::Mat& img_1, const cv::Mat& img_2,
std::vector<cv::KeyPoint>& keypoints_1, std::vector<cv::KeyPoint>& keypoints_2,
std::vector<cv::DMatch>& matches)
{
//-- 初始化
cv::Mat descriptors_1, descriptors_2;
// used in OpenCV3
cv::Ptr<cv::FeatureDetector> detector = cv::ORB::create();
cv::Ptr<cv::DescriptorExtractor> descriptor = cv::ORB::create();
// use this if you are in OpenCV2
// Ptr detector = FeatureDetector::create ( "ORB" );
// Ptr descriptor = DescriptorExtractor::create ( "ORB" );
cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
//-- 第一步:检测 Oriented FAST 角点位置
detector->detect(img_1, keypoints_1);
detector->detect(img_2, keypoints_2);
//-- 第二步:根据角点位置计算 BRIEF 描述子
descriptor->compute(img_1, keypoints_1, descriptors_1);
descriptor->compute(img_2, keypoints_2, descriptors_2);
//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
vector<cv::DMatch> match;
// BFMatcher matcher ( NORM_HAMMING );
matcher->match(descriptors_1, descriptors_2, match);
//-- 第四步:匹配点对筛选
double min_dist = 10000, max_dist = 0;
//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
for (int i = 0; i < descriptors_1.rows;i++)
{
double dist = match[i].distance;
if(dist > max_dist) max_dist = dist;
if(dist < min_dist) min_dist = dist;
}
printf("-- Max dist : %f \n", max_dist);;
printf("-- Min dist : %f \n", min_dist);
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
for ( int i = 0; i < descriptors_1.rows; i++ )
{
if ( match[i].distance <= max ( 2*min_dist, 30.0 ))
{
matches.push_back ( match[i] );
}
}
}
//实现像素坐标到相机坐标的转换(求出来的只是包含相机坐标下的x,y的二维点)
cv::Point2d pixel2cam(const cv::Point2d& p, const cv::Mat& K)
{
return cv::Point2d(
((p.x - K.at<double>(0,2))/K.at<double>(0,0)),
((p.y - K.at<double>(1,2))/K.at<double>(1,1))
);
}
cmake_minimum_required(VERSION 2.8)
project(pnps)
set( CMAKE_CXX_STANDARD 14)
set( CMAKE_BUILD_TYPE Release )
find_package(OpenCV 3.1 REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRECTORIES})
include_directories("/usr/include/eigen3")
add_executable(pnps src/pnps.cpp)
target_link_libraries( pnps
${OpenCV_LIBS}
)
/home/bupo/shenlan/zuoye/cap7/pnps/cmake-build-release/pnps ./src/1.png ./src/2.png ./src/1_depth.png ./src/2_depth.png
[ INFO:0] Initialize OpenCL runtime...
-- Max dist : 95.000000
-- Min dist : 7.000000
一共找到了81组匹配点
3d-2d pairs: 77
使用cv_PnP求解 位姿
***********************************SOLVEPNP_ITERATIVE***********************************
solve pnp in opencv cost time: 0.000981819 seconds.
R=
[0.9979193252225089, -0.05138618904650331, 0.03894200717386666;
0.05033852907733834, 0.9983556574295412, 0.02742286944793203;
-0.04028712992734059, -0.02540552801469367, 0.9988651091656532]
t=
[-0.1255867099750398;
-0.007363525258777434;
0.06099926588678889]
calling bundle adjustment
***********************************SOLVEPNP_ITERATIVE***********************************
***********************************SOLVEPNP_EPNP***********************************
solve pnp in opencv cost time: 0.00010083 seconds.
R=
[0.9978654469053291, -0.05171629787598204, 0.03987448314938394;
0.05055337177925334, 0.9982813811698801, 0.02964187260119165;
-0.04133892202484725, -0.02756281087914256, 0.9987649297919227]
t=
[-0.1266609334454452;
-0.01111413717711941;
0.05673412814657697]
calling bundle adjustment
***********************************SOLVEPNP_EPNP***********************************
***********************************SOLVEPNP_UPNP***********************************
solve pnp in opencv cost time: 5.0345e-05 seconds.
R=
[0.9978654469053291, -0.05171629787598204, 0.03987448314938394;
0.05055337177925334, 0.9982813811698801, 0.02964187260119165;
-0.04133892202484725, -0.02756281087914256, 0.9987649297919227]
t=
[-0.1266609334454452;
-0.01111413717711941;
0.05673412814657697]
calling bundle adjustment
***********************************SOLVEPNP_UPNP***********************************
***********************************SOLVEPNP_DLS***********************************
solve pnp in opencv cost time: 4.746e-05 seconds.
R=
[0.9978654469053291, -0.05171629787598204, 0.03987448314938394;
0.05055337177925334, 0.9982813811698801, 0.02964187260119165;
-0.04133892202484725, -0.02756281087914256, 0.9987649297919227]
t=
[-0.1266609334454452;
-0.01111413717711941;
0.05673412814657697]
calling bundle adjustment
***********************************SOLVEPNP_DLS***********************************
***********************************SOLVEPNP_P3P***********************************
solve pnp in opencv cost time: 2.0098e-05 seconds.
R=
[0.9982328467626991, -0.04009678360638762, 0.0438569445864655;
0.03857211555269049, 0.9986400289063435, 0.03507541258100014;
-0.04520371163773695, -0.03332177381773087, 0.9984218967169202]
t=
[-0.1276949195981435;
-0.02542013264231638;
0.04538521283166519]
calling bundle adjustment
***********************************SOLVEPNP_P3P***********************************
进程已结束,退出代码0
OpenCV(3.4.1) Error: Assertion failed (npoints == 4) in solvePnP, file /home/bupo/opencv-3.4.1/modules/calib3d/src/solvepnp.cpp, line 113 terminate called after throwing an instance of 'cv::Exception' what(): OpenCV(3.4.1) /home/bupo/opencv-3.4.1/modules/calib3d/src/solvepnp.cpp:113: error: (-215) npoints == 4 in function solvePnP
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
#define MyselfBAFunc 1 //1 课后习题6需要的BA优化函数
//0 例程用的
void find_feature_matches (
const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches );
// 像素坐标转相机归一化坐标
Point2d pixel2cam ( const Point2d& p, const Mat& K );
#if MyselfBAFunc
void MyselfBAFun(
const vector< cv::Point3f> &points1_3d, //第一帧3d点(匹配好且有深度信息的点)
const vector< cv::Point2f> &points1_2d, //第一帧像素平面2d点(匹配好的点)
const vector< cv::Point2f> &points2_2d, //第二帧像素平面2d点(匹配好的点)
const Mat&K, //因为里面没有修改相应的值所以用const
const Mat&R,
const Mat&t
);
#else
void bundleAdjustment (
const vector<Point3f> &points_3d,
const vector<Point2f> &points_2d,
const Mat& K,
Mat& R, Mat& t
);
#endif
int main ( int argc, char** argv )
{
cv::Mat img_1,img_2,d1,d2;
if(argc!=5)
{
//读取图片
img_1 = cv::imread("../src/1.png", CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread("../src/2.png",1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
d1 = cv::imread("../src/1_depth.png", CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
d2 = cv::imread("../src/2_depth.png", -1);
}
else
{
//读取图片
img_1 = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread(argv[2],1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
d1 = cv::imread(argv[3], CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
d2 = cv::imread(argv[4], -1);
}
assert(img_1.data != nullptr && img_2.data != nullptr);//若读取的图片没有内容,就终止程序
vector<KeyPoint> keypoints_1, keypoints_2;
vector<DMatch> matches;
find_feature_matches ( img_1, img_2, keypoints_1, keypoints_2, matches );
cout<<"一共找到了"<<matches.size() <<"组匹配点"<<endl;
Mat K = ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
vector<Point3f> pts_3d;
vector<Point2f> pts_2d;
#if MyselfBAFunc
vector<Point2f> pts1_2d; //第一帧下的像素坐标
#endif
for ( DMatch m:matches )
{
//可以参考书上101页对应的程序,表示获取对应位置的深度图片的深度值
ushort d = d1.ptr<unsigned short> (int ( keypoints_1[m.queryIdx].pt.y )) [ int ( keypoints_1[m.queryIdx].pt.x ) ];
if ( d == 0 ) // bad depth
continue;
float dd = d/5000.0;
Point2d p1 = pixel2cam ( keypoints_1[m.queryIdx].pt, K );
pts_3d.push_back ( Point3f ( p1.x*dd, p1.y*dd, dd ) );//表示的是 相机坐标系下的3D坐标 //这里是通过RGBD获取的深度信息,但是我们可以用三角测量获得第一帧下的3D坐标
pts_2d.push_back ( keypoints_2[m.trainIdx].pt ); //第二帧 匹配好的点 2D像素坐标
#if MyselfBAFunc
pts1_2d.push_back( keypoints_1[m.queryIdx].pt ); //第一帧 匹配好的点 2D像素坐标
#endif
}
//上面已经获得了第一帧坐标系的下的3d坐标 相当于世界坐标系下的坐标 (因为仅仅有两针图像帧 所以 以第一帧为世界坐标,也就是说 世界坐标到第一帧图像的R=I T=0 )
cout<<"3d-2d pairs: "<<pts_3d.size() <<endl;
Mat r, t; //定义旋转和平移变量
//参数信息: 第一帧3D 第二帧像素2D 内参矩阵k 无失真补偿 旋转向量r 平移向量t false表示输入的r t不作为初始化值 如果是true则此时会把t r作为初始值进行迭代
solvePnP ( pts_3d, pts_2d, K, Mat(), r, t, false,SOLVEPNP_EPNP ); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法SOLVEPNP_EPNP
Mat R;
cv::Rodrigues ( r, R ); // r为旋转向量形式,用Rodrigues公式转换为矩阵
cout<<"R="<<endl<<R<<endl;
cout<<"t="<<endl<<t<<endl;
cout<<"calling bundle adjustment"<<endl;
#if MyselfBAFunc
MyselfBAFun( pts_3d, pts1_2d, pts_2d, K, R, t);
#else
bundleAdjustment ( pts_3d, pts_2d, K, R, t );
#endif
}
void find_feature_matches ( const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches )
{
//-- 初始化
Mat descriptors_1, descriptors_2;
// used in OpenCV3
Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
// use this if you are in OpenCV2
// Ptr detector = FeatureDetector::create ( "ORB" );
// Ptr descriptor = DescriptorExtractor::create ( "ORB" );
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create ( "BruteForce-Hamming" );
//-- 第一步:检测 Oriented FAST 角点位置
detector->detect ( img_1,keypoints_1 );
detector->detect ( img_2,keypoints_2 );
//-- 第二步:根据角点位置计算 BRIEF 描述子
descriptor->compute ( img_1, keypoints_1, descriptors_1 );
descriptor->compute ( img_2, keypoints_2, descriptors_2 );
//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
vector<DMatch> match;
// BFMatcher matcher ( NORM_HAMMING );
matcher->match ( descriptors_1, descriptors_2, match );
//-- 第四步:匹配点对筛选
double min_dist=10000, max_dist=0;
//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
for ( int i = 0; i < descriptors_1.rows; i++ )
{
double dist = match[i].distance;
if ( dist < min_dist ) min_dist = dist;
if ( dist > max_dist ) max_dist = dist;
}
printf ( "-- Max dist : %f \n", max_dist );
printf ( "-- Min dist : %f \n", min_dist );
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
for ( int i = 0; i < descriptors_1.rows; i++ )
{
if ( match[i].distance <= max ( 2*min_dist, 30.0 ) )
{
matches.push_back ( match[i] );
}
}
}
Point2d pixel2cam ( const Point2d& p, const Mat& K )
{
return Point2d
(
( p.x - K.at<double> ( 0,2 ) ) / K.at<double> ( 0,0 ),
( p.y - K.at<double> ( 1,2 ) ) / K.at<double> ( 1,1 )
);
}
#if MyselfBAFunc
void MyselfBAFun(
const vector< cv::Point3f> &points1_3d, //第一帧3d点(匹配好且有深度信息的点)
const vector< cv::Point2f> &points1_2d, //第一帧像素平面2d点(匹配好的点)
const vector< cv::Point2f> &points2_2d, //第二帧像素平面2d点(匹配好的点)
const Mat&K, //因为里面没有修改相应的值所以用const
const Mat&R,
const Mat&t
){
#define PoseVertexNum 2 //定义位姿节点个数 本试验仅仅有2帧图
//设置优化器
typedef g2o::BlockSolver< g2o::BlockSolverTraits<6,3> > Block; //优化位姿6维 优化路标点3维
std::unique_ptr<Block::LinearSolverType> linearSolver=g2o::make_unique < g2o::LinearSolverCSparse<Block::PoseMatrixType> >();//线性求解设为CSparse
std::unique_ptr<Block> solver_ptr (new Block(std::move(linearSolver) ) );
g2o::OptimizationAlgorithmLevenberg *solver = new g2o::OptimizationAlgorithmLevenberg( std::move(solver_ptr) );
/* Block::LinearSolverType *linearSolver = new g2o::LinearSolverCSparse(); //设置线性求解器类型
Block *solver_ptr = new Block( std::unique_ptr(linearSolver) ); //矩阵块求解器
g2o::OptimizationAlgorithmLevenberg *solver = new g2o::OptimizationAlgorithmLevenberg( std::unique_ptr(solver_ptr) ); //LM优化算法
*/
g2o::SparseOptimizer optimizer; //设置稀疏优化器
optimizer.setAlgorithm(solver); //设置优化算法
//向优化器中添加节点和边
//添加节点 Vertex
//(1)添加位姿节点 第一帧作为世界坐标系(不优化) 同时也是相机坐标系
int poseVertexIndex = 0; //位姿节点索引为0 总共两个位姿态节点 第一帧和第二帧
Eigen::Matrix3d R2Init;
R2Init <<
R.at<double> ( 0,0 ), R.at<double> ( 0,1 ), R.at<double> ( 0,2 ) ,
R.at<double> ( 1,0 ), R.at<double> ( 1,1 ), R.at<double> ( 1,2 ) ,
R.at<double> ( 2,0 ), R.at<double> ( 2,1 ), R.at<double> ( 2,2 ) ;
for( ; poseVertexIndex < PoseVertexNum ; poseVertexIndex++ )
{
auto pose = new g2o::VertexSE3Expmap(); //相机位姿
pose->setId( poseVertexIndex ); //设置节点标号
pose->setFixed( poseVertexIndex == 0 ); //如果是第一帧 则固定住 不优化
if( poseVertexIndex == 1 ) //第二帧时让RT估计值为pnp得到的值 加快优化速度!
pose->setEstimate(
g2o::SE3Quat( R2Init,
Eigen::Vector3d( t.at<double> ( 0,0 ), t.at<double> ( 1,0 ), t.at<double> ( 2,0 ) )
)
); //两帧图像的位姿预设值都为 r=单位矩阵 t=0(当然这里可以填写自己设定的预设值 比如Pnp估计值)
else
pose->setEstimate( g2o::SE3Quat() );
optimizer.addVertex( pose ); //位姿节点加入优化器
}
//(2)添加路标节点
int landmarkVertexIndex = PoseVertexNum ;
for( int i = 0; i < points1_3d.size() ; i ++ ){
auto point = new g2o::VertexPointXYZ(); //路标点
point->setId( landmarkVertexIndex + i ); //设置路标点节点标号
point->setMarginalized( true ); //设置边缘化
point->setEstimate( Eigen::Vector3d( points1_3d[i].x, points1_3d[i].y, points1_3d[i].z ) ); //设置估计值 实际上就是第一帧坐标下的3d点坐标(也是世界坐标系下的坐标)
optimizer.addVertex( point ); //路标节点加入优化器
}
//加入相机参数(当然有另一种方式:查看笔记两种边的不同点)(最后一项为0 默认fx = fy 然后优化位姿 与g2o::EdegeSE3ProjectXYZ不同 笔记以记载 )
g2o::CameraParameters *camera = new g2o::CameraParameters(
K.at< double >(0,0), Eigen::Vector2d( K.at< double >(0,2), K.at< double >(1,2) ),0
);
camera->setId( 0 );
optimizer.addParameter( camera );
//加入边edge
//世界坐标下路标点连接到第一帧位姿节点(因为以第一帧坐标系当做世界坐标系 所以我们前面没有优化第一帧的RT 仅仅优化第一帧到第二帧的RT)
for(int i= 0 ;i < points1_2d.size() ; i++ ){
auto edge = new g2o::EdgeProjectXYZ2UV(); //设置连接到第一帧的边
//二元边 连接节点
edge->setVertex( 0, dynamic_cast< g2o::VertexPointXYZ * >( optimizer.vertex( landmarkVertexIndex + i ) ) ); //世界坐标系下的路标节点
edge->setVertex( 1, dynamic_cast< g2o::VertexSE3Expmap * >( optimizer.vertex(0) ) );
edge->setMeasurement( Eigen::Vector2d ( points1_2d[i].x, points1_2d[i].y ) ); //设置测量值为第一帧下的相机归一化平面坐标
edge->setParameterId(0,0); //最后一位设置使用的相机参数(因为上面仅仅输入了一个相机参数id=0, 对应上面camer->setId(0),第一个参数0不知道是什么,但是必须为0)
edge->setInformation ( Eigen::Matrix2d::Identity() ); //信息矩阵2x2的单位阵
optimizer.addEdge( edge );
}
//第一帧路标点链接到第二帧位姿节点
for( int i=0 ;i < points1_2d.size() ; i++){
auto edge = new g2o::EdgeProjectXYZ2UV(); //设置链接到第二帧的边
edge->setVertex( 0, dynamic_cast< g2o::VertexPointXYZ * >( optimizer.vertex( landmarkVertexIndex + i) ) ); //第一帧坐标系下的路标点
edge->setVertex( 1, dynamic_cast< g2o::VertexSE3Expmap *> ( optimizer.vertex(1) ) ); //连接到第二个位姿节点
edge->setMeasurement( Eigen::Vector2d ( points2_2d[i].x, points2_2d[i].y ) ); //设置测量值为第二帧下的相机归一化平面坐标
edge->setInformation( Eigen::Matrix2d::Identity() ); //信息矩阵为2x2 实际上就是误差的加权为1:1的
edge->setParameterId(0,0);
optimizer.addEdge( edge );
}
//run 算法
cout<<"开始优化!"<<endl;
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
optimizer.setVerbose ( true ); //设置详细信息
optimizer.initializeOptimization( ); //优化器初始化
optimizer.optimize( 100 ); //进行最多100次的优化
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
cout<<"优化结束!"<<endl;
chrono::duration< double > time_used = chrono::duration_cast< chrono::duration<double> >( t2 -t1 );
cout<<"optimization costs time: "<<time_used.count() << "seconds."<<endl;
cout<<endl<<"after optimization:"<<endl;
//输出优化节点的位姿 estimate()输出的是SE3类型 Eigen::Isometry3d 实际上就是4x4的一个矩阵表示变换矩阵 这个类初始化可以是李代数
//这里有一点不明白的是 Eigen::Isometry3d()为什么可以用estimate()返回的SE3Quat类型初始化???????
cout<<"T="<<endl<<Eigen::Isometry3d ( dynamic_cast<g2o::VertexSE3Expmap *>(optimizer.vertex(1))->estimate()).matrix() <<endl;
/* g2o::SE3Quat a();
Eigen::Isometry3d( a);*/
}
#else
void bundleAdjustment (
const vector< Point3f > &points_3d,
const vector< Point2f > &points_2d,//这里加不加引用 都是会报错
const Mat& K,
Mat& R, Mat& t )
{
// 初始化g2o
typedef g2o::BlockSolver< g2o::BlockSolverTraits<6,3> > Block; // pose 维度为 6, landmark 维度为 3
std::unique_ptr<Block::LinearSolverType> linearSolver( new g2o::LinearSolverCSparse<Block::PoseMatrixType>() );
std::unique_ptr<Block> solver_ptr ( new Block ( std::move(linearSolver) ) );
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg ( std::move(solver_ptr) );
/* Block::LinearSolverType* linearSolver = new g2o::LinearSolverCSparse(); // 线性方程求解器
Block* solver_ptr = new Block ( std::unique_ptr(linearSolver) ); // 矩阵块求解器
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg ( std::unique_ptr(solver_ptr) );*/
g2o::SparseOptimizer optimizer;
optimizer.setAlgorithm ( solver );
// vertex
g2o::VertexSE3Expmap* pose = new g2o::VertexSE3Expmap(); // camera pose
Eigen::Matrix3d R_mat;
R_mat <<
R.at<double> ( 0,0 ), R.at<double> ( 0,1 ), R.at<double> ( 0,2 ),
R.at<double> ( 1,0 ), R.at<double> ( 1,1 ), R.at<double> ( 1,2 ),
R.at<double> ( 2,0 ), R.at<double> ( 2,1 ), R.at<double> ( 2,2 );
pose->setId ( 0 );
//设置顶点估计值 然后才会用BA再次优化
pose->setEstimate ( g2o::SE3Quat (
R_mat,
Eigen::Vector3d ( t.at<double> ( 0,0 ), t.at<double> ( 1,0 ), t.at<double> ( 2,0 ) )
) );
optimizer.addVertex ( pose );
int index = 1;
for ( const Point3f &p:points_3d ) // landmarks 在执行这里的时候,实际上是所有空间点(匹配好的)组成的顶点
{
g2o::VertexPointXYZ* point = new g2o::VertexPointXYZ();
point->setId ( index++ );
point->setEstimate ( Eigen::Vector3d ( p.x, p.y, p.z ) );
point->setMarginalized ( true ); // g2o 中必须设置 marg 参见第十讲内容 待注释??
optimizer.addVertex ( point );
}
// parameter: camera intrinsics
g2o::CameraParameters* camera = new g2o::CameraParameters (
K.at<double> ( 0,0 ), Eigen::Vector2d ( K.at<double> ( 0,2 ), K.at<double> ( 1,2 ) ), 0
);
camera->setId ( 0 );
optimizer.addParameter ( camera );
// edges
index = 1;
for ( const Point2f &p:points_2d )//每个点都会与位姿节点链接 所以就有那么多的边
{
g2o::EdgeProjectXYZ2UV* edge = new g2o::EdgeProjectXYZ2UV();
edge->setId ( index );
//下行转化 要用动态类型转换
//将2元边连接上顶点
edge->setVertex ( 0, dynamic_cast<g2o::VertexPointXYZ*> ( optimizer.vertex ( index ) ) );//空间点类型指针
edge->setVertex ( 1, pose ); //位姿类型指针
edge->setMeasurement ( Eigen::Vector2d ( p.x, p.y ) ); //设置测量值
edge->setParameterId ( 0,0 );
edge->setInformation ( Eigen::Matrix2d::Identity() ); //因为误差向量为2维,所以信息矩阵也是2维,这里设置加权为1 即单位阵
optimizer.addEdge ( edge );
index++;
}
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
optimizer.setVerbose ( true );
optimizer.initializeOptimization();
optimizer.optimize ( 100 );
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>> ( t2-t1 );
cout<<"optimization costs time: "<<time_used.count() <<" seconds."<<endl;
cout<<endl<<"after optimization:"<<endl;
cout<<"T="<<endl<<Eigen::Isometry3d ( pose->estimate() ).matrix() <<endl;
}
#endif
cmake_minimum_required(VERSION 2.8)
project(cap7_6_pnp)
set( CMAKE_CXX_STANDARD 14)
#SET(G2O_LIBS g2o_cli g2o_ext_freeglut_minimal g2o_simulator g2o_solver_slam2d_linear g2o_types_icp g2o_types_slam2d g2o_core g2o_interface g2o_solver_csparse g2o_solver_structure_only g2o_types_sba g2o_types_slam3d g2o_csparse_extension g2o_opengl_helper g2o_solver_dense g2o_stuff g2o_types_sclam2d g2o_parser g2o_solver_pcg g2o_types_data g2o_types_sim3 cxsparse )
set( CMAKE_BUILD_TYPE Release )
# 添加cmake模块以使用g2o
list( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules )
find_package(OpenCV 3 REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRECTORIES})
include_directories("/usr/include/eigen3")
find_package(G2O REQUIRED)
include_directories(${G2O_INCLUDE_DIRECTORIES})
find_package(CSparse REQUIRED)
include_directories(${CSPARSE_INCLUDE_DIR})
find_package(Sophus REQUIRED)
include_directories(${Sophus_INCLUDE_DIRS})
add_executable(cap7_6_pnp src/cap7_6_pnp.cpp)
target_link_libraries( cap7_6_pnp
${OpenCV_LIBS}
g2o_core g2o_stuff g2o_types_sba g2o_csparse_extension
g2o_types_slam3d
#${G2O_LIBS}
${CSPARSE_LIBRARY}
${Sophus_LIBRARIES} fmt
)
/home/bupo/shenlan/zuoye/cap7/cap7_6_pnp/cmake-build-release/cap7_6_pnp /home/bupo/shenlan/zuoye/cap7/cap7_6_pnp/src/1.png /home/bupo/shenlan/zuoye/cap7/cap7_6_pnp/src/2.png /home/bupo/shenlan/zuoye/cap7/cap7_6_pnp/src/1_depth.png /home/bupo/shenlan/zuoye/cap7/cap7_6_pnp/src/2_depth.png
[ INFO:0] Initialize OpenCL runtime...
-- Max dist : 95.000000
-- Min dist : 7.000000
一共找到了81组匹配点
3d-2d pairs: 77
R=
[0.9978654469053291, -0.05171629787598204, 0.03987448314938394;
0.05055337177925334, 0.9982813811698801, 0.02964187260119165;
-0.04133892202484725, -0.02756281087914256, 0.9987649297919227]
t=
[-0.1266609334454452;
-0.01111413717711941;
0.05673412814657697]
calling bundle adjustment
开始优化!
优化结束!
optimization costs time: 0.00311775seconds.
after optimization:
T=
0.997695 -0.0513271 0.0443762 -0.133492
0.0498358 0.998176 0.0340829 -0.0182294
-0.0460446 -0.0317928 0.998433 0.0413093
0 0 0 1
iteration= 0 chi2= 73.911450 time= 0.000859681 cumTime= 0.000859681 edges= 154 schur= 1 lambda= 80.062423 levenbergIter= 1
iteration= 1 chi2= 44.088323 time= 5.2844e-05 cumTime= 0.000912525 edges= 154 schur= 1 lambda= 26.687474 levenbergIter= 1
iteration= 2 chi2= 40.590311 time= 4.6602e-05 cumTime= 0.000959127 edges= 154 schur= 1 lambda= 8.895825 levenbergIter= 1
iteration= 3 chi2= 39.648871 time= 4.6151e-05 cumTime= 0.00100528 edges= 154 schur= 1 lambda= 2.965275 levenbergIter= 1
iteration= 4 chi2= 39.446579 time= 6.8225e-05 cumTime= 0.0010735 edges= 154 schur= 1 lambda= 0.988425 levenbergIter= 1
iteration= 5 chi2= 39.428292 time= 4.6492e-05 cumTime= 0.00111999 edges= 154 schur= 1 lambda= 0.329475 levenbergIter= 1
iteration= 6 chi2= 39.427612 time= 4.6212e-05 cumTime= 0.00116621 edges= 154 schur= 1 lambda= 0.219650 levenbergIter= 1
iteration= 7 chi2= 39.427596 time= 4.6332e-05 cumTime= 0.00121254 edges= 154 schur= 1 lambda= 0.146433 levenbergIter= 1
iteration= 8 chi2= 39.427596 time= 4.581e-05 cumTime= 0.00125835 edges= 154 schur= 1 lambda= 0.097622 levenbergIter= 1
iteration= 9 chi2= 39.427596 time= 5.9337e-05 cumTime= 0.00131769 edges= 154 schur= 1 lambda= 0.065081 levenbergIter= 1
iteration= 10 chi2= 39.427596 time= 4.5951e-05 cumTime= 0.00136364 edges= 154 schur= 1 lambda= 0.043388 levenbergIter= 1
iteration= 11 chi2= 39.427596 time= 4.579e-05 cumTime= 0.00140943 edges= 154 schur= 1 lambda= 0.028925 levenbergIter= 1
iteration= 12 chi2= 39.427596 time= 4.571e-05 cumTime= 0.00145514 edges= 154 schur= 1 lambda= 0.019283 levenbergIter= 1
iteration= 13 chi2= 39.427596 time= 4.552e-05 cumTime= 0.00150066 edges= 154 schur= 1 lambda= 0.012856 levenbergIter= 1
iteration= 14 chi2= 39.427596 time= 5.2464e-05 cumTime= 0.00155312 edges= 154 schur= 1 lambda= 0.008570 levenbergIter= 1
iteration= 15 chi2= 39.427596 time= 4.6152e-05 cumTime= 0.00159927 edges= 154 schur= 1 lambda= 0.005714 levenbergIter= 1
iteration= 16 chi2= 39.427596 time= 4.555e-05 cumTime= 0.00164482 edges= 154 schur= 1 lambda= 0.003809 levenbergIter= 1
iteration= 17 chi2= 39.427596 time= 8.2714e-05 cumTime= 0.00172754 edges= 154 schur= 1 lambda= 0.020315 levenbergIter= 3
iteration= 18 chi2= 39.427596 time= 0.000217741 cumTime= 0.00194528 edges= 154 schur= 1 lambda= 731925863083109.000000 levenbergIter= 10
进程已结束,退出代码0
参考:视觉slam十四讲第七章课后习题6
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
/*+++++++++++++++++++++++++++++++++++++++++配置信息+++++++++++++++++++++++++++++++++++++++++++++***/
#define MyselfOptimizerMethod 1 // 1: 课后习题7代码结果
// 0: 3d-3d书上例子
#define ISBAProvideInitValue 0 // 1: 用ICP结果为BA提供初值
// 0: 用单位矩阵I和0平移向量作为初始值
#define BAOptimizer 1 // 1: 加入BA优化
// 0: 不加入BA优化
/*+++++++++++++++++++++++++++++++++++++++++End配置信息+++++++++++++++++++++++++++++++++++++++++++++*/
void find_feature_matches (
const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches );
// 像素坐标转相机归一化坐标
Point2d pixel2cam ( const Point2d& p, const Mat& K );
void pose_estimation_3d3d (
const vector<Point3f>& pts1,
const vector<Point3f>& pts2,
Mat& R, Mat& t
);
#if BAOptimizer
void bundleAdjustment (
const vector< Point3f >& pts1,
const vector< Point3f >& pts2,
Mat& R, Mat& t );
#if MyselfOptimizerMethod
//课后习题7题
class EdgeProjectXYZRGBDPoseOnly : public g2o::BaseBinaryEdge<3, Eigen::Vector3d,g2o::VertexPointXYZ, g2o::VertexSE3Expmap>
{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
EdgeProjectXYZRGBDPoseOnly( ) {}
virtual void computeError()
{
const g2o::VertexPointXYZ * point = dynamic_cast< const g2o::VertexPointXYZ *> ( _vertices[0] );
const g2o::VertexSE3Expmap * pose = dynamic_cast<const g2o::VertexSE3Expmap*> ( _vertices[1] );
// measurement is p, point is p'
//pose->estimate().map( _point );中用estimate()估计一个值后,然后用映射函数 就是旋转加平移 将其_point映射到另一个相机坐标系下去
//观测值 - 映射值 因为我们做的试验是3D-3D 所以这里把第一帧的3D坐标当做测量值,然后把第二帧坐标映射到第一帧坐标系中
_error = _measurement - pose->estimate().map( point->estimate() );
}
//表示线性化 误差函数 就是要计算雅克比矩阵
virtual void linearizeOplus()override final //这里override 表示override覆盖基类的同名同参函数, final表示派生类的某个函数不能覆盖这个函数
{
g2o::VertexSE3Expmap* pose = dynamic_cast<g2o::VertexSE3Expmap *> (_vertices[1]);
g2o::VertexPointXYZ * point = dynamic_cast< g2o::VertexPointXYZ * > (_vertices[0] );
g2o::SE3Quat T(pose->estimate());
Eigen::Vector3d xyz_trans = T.map( point->estimate() );//映射到第二帧相机坐标系下的坐标值
double x = xyz_trans[0]; //第一帧到第二帧坐标系下变换后的坐标值
double y = xyz_trans[1];
double z = xyz_trans[2];
//关于空间点的雅克比矩阵-R, 误差对空间点,也就是第二张图片的空间点求导,de/dp',可以看书上p187
_jacobianOplusXi = -T.rotation().toRotationMatrix();
//3x6的关于优化变量的雅克比矩阵 可以看书上p187/p86页 自己推到的结果
_jacobianOplusXj(0,0) = 0;
_jacobianOplusXj(0,1) = -z;
_jacobianOplusXj(0,2) = y;
_jacobianOplusXj(0,3) = -1;
_jacobianOplusXj(0,4) = 0;
_jacobianOplusXj(0,5) = 0;
_jacobianOplusXj(1,0) = z;
_jacobianOplusXj(1,1) = 0;
_jacobianOplusXj(1,2) = -x;
_jacobianOplusXj(1,3) = 0;
_jacobianOplusXj(1,4) = -1;
_jacobianOplusXj(1,5) = 0;
_jacobianOplusXj(2,0) = -y;
_jacobianOplusXj(2,1) = x;
_jacobianOplusXj(2,2) = 0;
_jacobianOplusXj(2,3) = 0;
_jacobianOplusXj(2,4) = 0;
_jacobianOplusXj(2,5) = -1;
}
bool read ( istream& in ) {}
bool write ( ostream& out ) const {}
};
#else
// g2o edge 边代表误差 所以在里面要override一个computerError函数
class EdgeProjectXYZRGBDPoseOnly : public g2o::BaseUnaryEdge<3, Eigen::Vector3d, g2o::VertexSE3Expmap>
{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
EdgeProjectXYZRGBDPoseOnly( const Eigen::Vector3d& point ) : _point(point) {}
virtual void computeError()
{
const g2o::VertexSE3Expmap* pose = dynamic_cast<const g2o::VertexSE3Expmap*> ( _vertices[0] );
// measurement is p, point is p'
//pose->estimate().map( _point );中用estimate()估计一个值后,然后用映射函数 就是旋转加平移 将其_point映射到另一个相机坐标系下去
//观测值 - 映射值 因为我们做的试验是3D-3D 所以这里把第一帧的3D坐标当做测量值,然后把第二帧坐标映射到第一帧坐标系中
_error = _measurement - pose->estimate().map( _point );
}
//表示线性化 误差函数 就是要计算雅克比矩阵
virtual void linearizeOplus()
{
g2o::VertexSE3Expmap* pose = dynamic_cast<g2o::VertexSE3Expmap *>(_vertices[0]);
g2o::SE3Quat T(pose->estimate());
Eigen::Vector3d xyz_trans = T.map(_point);//映射到第二帧相机坐标系下的坐标值
double x = xyz_trans[0]; //第一帧到第二帧坐标系下变换后的坐标值
double y = xyz_trans[1];
double z = xyz_trans[2];
//3x6的关于优化变量的雅克比矩阵 可以看书上p179页 自己推到的结果
_jacobianOplusXi(0,0) = 0;
_jacobianOplusXi(0,1) = -z;
_jacobianOplusXi(0,2) = y;
_jacobianOplusXi(0,3) = -1;
_jacobianOplusXi(0,4) = 0;
_jacobianOplusXi(0,5) = 0;
_jacobianOplusXi(1,0) = z;
_jacobianOplusXi(1,1) = 0;
_jacobianOplusXi(1,2) = -x;
_jacobianOplusXi(1,3) = 0;
_jacobianOplusXi(1,4) = -1;
_jacobianOplusXi(1,5) = 0;
_jacobianOplusXi(2,0) = -y;
_jacobianOplusXi(2,1) = x;
_jacobianOplusXi(2,2) = 0;
_jacobianOplusXi(2,3) = 0;
_jacobianOplusXi(2,4) = 0;
_jacobianOplusXi(2,5) = -1;
}
bool read ( istream& in ) {}
bool write ( ostream& out ) const {}
protected:
Eigen::Vector3d _point; //设立误差点 之后将其与测量值进行相减 求得误差
};
#endif
#endif
//自己设置的打印配置信息
void printConfigInfo(){
cout<<"This is the example from GaoXiang's book : ICP结果是第二帧到第一帧的RT."<<endl;
#if BAOptimizer
cout<<"BA Optimizer is Provided!"<<endl;
#if ISBAProvideInitValue
cout<<"The RT from ICP's result is Provided for BA as a initialization."<<endl;
#else
cout<<" But,Not provide initialization for BA!"<<endl;
#endif
#if MyselfOptimizerMethod
cout<<"优化了空间点和位姿节点!"<<endl;
#else
cout<<"未对空间点进行优化!"<<endl;
#endif
#endif
}
int main ( int argc, char** argv )
{
cv::Mat img_1,img_2,depth1,depth2;
if(argc!=5)
{
//读取图片
img_1 = cv::imread("../src/1.png", CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread("../src/2.png",1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
depth1 = cv::imread("../src/1_depth.png", CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
depth2 = cv::imread("../src/2_depth.png", -1);
}
else
{
//读取图片
img_1 = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread(argv[2],1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
depth1 = cv::imread(argv[3], CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
depth2 = cv::imread(argv[4], -1);
}
assert(img_1.data != nullptr && img_2.data != nullptr);//若读取的图片没有内容,就终止程序
vector<KeyPoint> keypoints_1, keypoints_2;
vector<DMatch> matches;
find_feature_matches ( img_1, img_2, keypoints_1, keypoints_2, matches );
cout<<"一共找到了"<<matches.size() <<"组匹配点"<<endl;
Mat K = ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
vector<Point3f> pts1, pts2;
for ( DMatch m:matches )
{
ushort d1 = depth1.ptr<unsigned short> ( int ( keypoints_1[m.queryIdx].pt.y ) ) [ int ( keypoints_1[m.queryIdx].pt.x ) ];
ushort d2 = depth2.ptr<unsigned short> ( int ( keypoints_2[m.trainIdx].pt.y ) ) [ int ( keypoints_2[m.trainIdx].pt.x ) ];
if ( d1==0 || d2==0 ) // bad depth
continue;
Point2d p1 = pixel2cam ( keypoints_1[m.queryIdx].pt, K );
Point2d p2 = pixel2cam ( keypoints_2[m.trainIdx].pt, K );
float dd1 = float ( d1 ) /5000.0;
float dd2 = float ( d2 ) /5000.0;
//存储特征点的3D坐标
pts1.push_back ( Point3f ( p1.x*dd1, p1.y*dd1, dd1 ) );
pts2.push_back ( Point3f ( p2.x*dd2, p2.y*dd2, dd2 ) );
}
cout<<"3d-3d pairs: "<<pts1.size() <<endl;
Mat R, t;
pose_estimation_3d3d ( pts1, pts2, R, t ); //ICP方式的位姿估计
cout<<"ICP via SVD results: "<<endl;
cout<<"R = "<<R<<endl;
cout<<"t = "<<t<<endl;
//实际上是第一帧到第二帧的R T
cout<<"第一帧到第二帧的旋转和平移:" << endl << "R_inv = "<<R.t() <<endl;
cout<<"t_inv = "<<-R.t() *t<<endl;
cout<<"calling bundle adjustment"<<endl;
#if BAOptimizer
bundleAdjustment( pts1, pts2, R, t ); //BA优化估计相机位姿 RT 是提供优化的初始值
#endif
// verify p1 = R*p2 + t
/* for ( int i=0; i<5; i++ )
{
cout<<"p1 = "<(3,1)<
}
void find_feature_matches ( const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches )
{
//-- 初始化
Mat descriptors_1, descriptors_2;
// used in OpenCV3
Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
// use this if you are in OpenCV2
// Ptr detector = FeatureDetector::create ( "ORB" );
// Ptr descriptor = DescriptorExtractor::create ( "ORB" );
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");
//-- 第一步:检测 Oriented FAST 角点位置
detector->detect ( img_1,keypoints_1 );
detector->detect ( img_2,keypoints_2 );
//-- 第二步:根据角点位置计算 BRIEF 描述子
descriptor->compute ( img_1, keypoints_1, descriptors_1 );
descriptor->compute ( img_2, keypoints_2, descriptors_2 );
//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
vector<DMatch> match;
// BFMatcher matcher ( NORM_HAMMING );
matcher->match ( descriptors_1, descriptors_2, match );
//-- 第四步:匹配点对筛选
double min_dist=10000, max_dist=0;
//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
for ( int i = 0; i < descriptors_1.rows; i++ )
{
double dist = match[i].distance;
if ( dist < min_dist ) min_dist = dist;
if ( dist > max_dist ) max_dist = dist;
}
printf ( "-- Max dist : %f \n", max_dist );
printf ( "-- Min dist : %f \n", min_dist );
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
for ( int i = 0; i < descriptors_1.rows; i++ )
{
if ( match[i].distance <= max ( 2*min_dist, 30.0 ) )
{
matches.push_back ( match[i] );
}
}
}
Point2d pixel2cam ( const Point2d& p, const Mat& K )
{
return Point2d
(
( p.x - K.at<double> ( 0,2 ) ) / K.at<double> ( 0,0 ),
( p.y - K.at<double> ( 1,2 ) ) / K.at<double> ( 1,1 )
);
}
void pose_estimation_3d3d (
const vector<Point3f>& pts1,
const vector<Point3f>& pts2,
Mat& R, Mat& t
)
{
Point3f p1, p2; // center of mass
int N = pts1.size();
for ( int i=0; i<N; i++ )
{
p1 += pts1[i];
p2 += pts2[i];
}
p1 = Point3f( Vec3f(p1) / N);
p2 = Point3f( Vec3f(p2) / N);
vector<Point3f> q1 ( N ), q2 ( N ); // remove the center 去质心坐标
for ( int i=0; i<N; i++ )
{
q1[i] = pts1[i] - p1;
q2[i] = pts2[i] - p2;
}
// compute q1*q2^T
Eigen::Matrix3d W = Eigen::Matrix3d::Zero();
for ( int i=0; i<N; i++ )
{
W += Eigen::Vector3d ( q1[i].x, q1[i].y, q1[i].z ) * Eigen::Vector3d ( q2[i].x, q2[i].y, q2[i].z ).transpose();
}
cout<<"W="<<W<<endl;
// SVD on W
Eigen::JacobiSVD<Eigen::Matrix3d> svd ( W, Eigen::ComputeFullU|Eigen::ComputeFullV );//因为知道矩阵W的类型 所以在分解的时候直接是FullU | FullV
Eigen::Matrix3d U = svd.matrixU();
Eigen::Matrix3d V = svd.matrixV();
cout<<"U="<<U<<endl;
cout<<"V="<<V<<endl;
//例程本身的实现方式 求出的R T是第二帧到第一帧的
Eigen::Matrix3d R_ = U* ( V.transpose() );
Eigen::Vector3d t_ = Eigen::Vector3d ( p1.x, p1.y, p1.z ) - R_ * Eigen::Vector3d ( p2.x, p2.y, p2.z );
// convert to cv::Mat
R = ( Mat_<double> ( 3,3 ) <<
R_ ( 0,0 ), R_ ( 0,1 ), R_ ( 0,2 ),
R_ ( 1,0 ), R_ ( 1,1 ), R_ ( 1,2 ),
R_ ( 2,0 ), R_ ( 2,1 ), R_ ( 2,2 )
);
t = ( Mat_<double> ( 3,1 ) << t_ ( 0,0 ), t_ ( 1,0 ), t_ ( 2,0 ) );
}
#if BAOptimizer
void bundleAdjustment (
const vector< Point3f >& pts1,
const vector< Point3f >& pts2,
Mat& R, Mat& t ) //实际上 R t在这里并不是必要的 这个只是用来提供BA初始值
{
// 初始化g2o
typedef g2o::BlockSolver< g2o::BlockSolverTraits<6,3> > Block; // pose维度为 6, landmark 维度为 3
std::unique_ptr<Block::LinearSolverType>linearSolver = g2o::make_unique<g2o::LinearSolverEigen<Block::PoseMatrixType>>(); //线性方程求解器
std::unique_ptr<Block>solver_ptr = g2o::make_unique<Block>( std::move(linearSolver) ); //矩阵块求解器
g2o::OptimizationAlgorithmLevenberg * solver = new g2o::OptimizationAlgorithmLevenberg( std::move(solver_ptr) ); //LM法
/* //另一种修改错误的方式
Block::LinearSolverType* linearSolver = new g2o::LinearSolverEigen(); // 线性方程求解器
Block* solver_ptr = new Block( std::unique_ptr(linearSolver) ); // 矩阵块求解器
g2o::OptimizationAlgorithmLevenberg * solver = new g2o::OptimizationAlgorithmLevenberg ( std::unique_ptr (solver_ptr) ); //LM法
*/
g2o::SparseOptimizer optimizer;
optimizer.setAlgorithm( solver );
// vertex 这里仅仅添加了第二帧节点
auto pose = new g2o::VertexSE3Expmap(); // camera pose
pose->setId(0); // 位姿节点编号为0
#if ISBAProvideInitValue // 为图优化提供ICP结果作为初值
Eigen::Matrix3d R_mat;
R_mat <<
R.at<double> ( 0,0 ), R.at<double> ( 0,1 ), R.at<double> ( 0,2 ),
R.at<double> ( 1,0 ), R.at<double> ( 1,1 ), R.at<double> ( 1,2 ),
R.at<double> ( 2,0 ), R.at<double> ( 2,1 ), R.at<double> ( 2,2 );
pose->setEstimate( g2o::SE3Quat(
R_mat,
Eigen::Vector3d( t.at<double> ( 0,0 ),t.at<double> ( 0,1 ),t.at<double> ( 0,2 ) )
)
);
#else // 提供默认初值
pose->setEstimate( g2o::SE3Quat(
Eigen::Matrix3d::Identity(),
Eigen::Vector3d( 0,0,0 )
) );
#endif
optimizer.addVertex( pose ); //向优化器中添加节点
#if MyselfOptimizerMethod //添加空间节点 并且按照书上的方式进行优化的
int pointIndex = 1; //因为上面的位姿节点就1个 所以我们这里标号为1
for( auto &p: pts2 ){
auto point = new g2o::VertexPointXYZ();
point->setId( pointIndex++ );
point->setMarginalized( true ); //设置边缘化(必须设置 否则出错)
point->setEstimate( Eigen::Vector3d( p.x, p.y, p.z ) );
optimizer.addVertex( point );
}
#endif
// edges
int index = 0;
vector<EdgeProjectXYZRGBDPoseOnly*> edges;
for ( size_t i=0; i<pts1.size(); i++ )
{
#if MyselfOptimizerMethod
auto edge = new EdgeProjectXYZRGBDPoseOnly( ); //在课后习题中修改的EdgeProjectXYZRGBDPoseOnly可以去掉_point成员变量
#else
auto edge = new EdgeProjectXYZRGBDPoseOnly(
Eigen::Vector3d(pts2[i].x, pts2[i].y, pts2[i].z) );
#endif
edge->setId( index );
#if MyselfOptimizerMethod
edge->setVertex( 0 , dynamic_cast< g2o::VertexPointXYZ *> ( optimizer.vertex(index)) );
edge->setVertex( 1 , dynamic_cast< g2o::OptimizableGraph::Vertex *> ( optimizer.vertex(0) ) );
#else //参考ORB_SLAM:这里将原来的g2o::VertexSE3Expmap* 替换为g2o::OptimizableGraph::Vertex *
edge->setVertex( 0 , dynamic_cast< g2o::OptimizableGraph::Vertex *> ( optimizer.vertex(0) ) );
#endif
//这里以第一帧为测量值 说明优化的是第二帧到第一帧的旋转r和平移t
edge->setMeasurement( Eigen::Vector3d (
pts1[i].x, pts1[i].y, pts1[i].z)
);
edge->setInformation( Eigen::Matrix3d::Identity()*1e4 );
optimizer.addEdge(edge); //向优化器中添加边
index++;
edges.push_back(edge); //存储边指针
}
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
optimizer.setVerbose( true );
optimizer.initializeOptimization();
optimizer.optimize(100);
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2-t1);
cout<<"optimization costs time: "<<time_used.count()<<" seconds."<<endl;
cout<<endl<<"after optimization:"<<endl;
cout<<"T="<<endl<<Eigen::Isometry3d( pose->estimate() ).matrix()<<endl;
/*
cout<<"输出优化后的point值:"< ( optimizer.vertex(j+1) )->estimate()<
}
#endif
cmake_minimum_required(VERSION 2.8)
project(cap7_7_icp)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_BUILD_TYPE Release )
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules)
# eigen3
include_directories("/usr/include/eigen3")
#opencv2
find_package(OpenCV 3.1 REQUIRED)
#g2o
find_package(G2O REQUIRED)
find_package(CSparse REQUIRED)
include_directories(
${OpenCV_INCLUDE_DIRECTORIES}
${G2O_INCLUDE_DIRECTORIES}
${CSPARSE_INCLUDE_DIR}
)
add_executable(cap7_7_icp src/cap7_7_icp.cpp)
target_link_libraries(cap7_7_icp
${OpenCV_LIBRARIES}
g2o_core g2o_stuff g2o_types_sba
g2o_csparse_extension
g2o_types_slam3d
${CSPARSE_LIBRARY}
)
/home/bupo/shenlan/zuoye/cap7/cap7_7_icp/cmake-build-release/cap7_7_icp /home/bupo/shenlan/zuoye/cap7/cap7_7_icp/src/1.png /home/bupo/shenlan/zuoye/cap7/cap7_7_icp/src/2.png /home/bupo/shenlan/zuoye/cap7/cap7_7_icp/src/1_depth.png /home/bupo/shenlan/zuoye/cap7/cap7_7_icp/src/2_depth.png
This is the example from GaoXiang's book : ICP结果是第二帧到第一帧的RT.
BA Optimizer is Provided!
But,Not provide initialization for BA!
优化了空间点和位姿节点!
[ INFO:0] Initialize OpenCL runtime...
-- Max dist : 95.000000
-- Min dist : 7.000000
一共找到了81组匹配点
3d-3d pairs: 75
W= 11.8688 -0.717698 1.89486
-1.88065 3.83391 -5.78219
3.25846 -5.82734 9.65267
U= 0.592295 -0.805658 -0.0101195
-0.418171 -0.318113 0.850845
0.688709 0.499719 0.525319
V= 0.64736 -0.761401 -0.0345329
-0.388765 -0.368829 0.844291
0.655581 0.533135 0.534772
ICP via SVD results:
R = [0.9972065647956201, 0.05834273442898391, -0.04663895869192625;
-0.05787745545449196, 0.998260122172866, 0.01126626067193237;
0.04721511705620757, -0.008535444848613793, 0.9988482762174666]
t = [0.1379879629890433;
-0.06551699470729988;
-0.02981649388290575]
第一帧到第二帧的旋转和平移:
R_inv = [0.9972065647956201, -0.05787745545449196, 0.04721511705620757;
0.05834273442898391, 0.998260122172866, -0.008535444848613793;
-0.04663895869192625, 0.01126626067193237, 0.9988482762174666]
t_inv = [-0.1399866702492459;
0.05709791102272541;
0.03695589996443214]
calling bundle adjustment
optimization costs time: 0.00367217 seconds.
after optimization:
T=
0.998656 0.0272915 -0.0440561 0.132469
-0.0282053 0.999397 -0.020255 0.00437166
0.0434768 0.0214704 0.998824 -0.0484505
0 0 0 1
iteration= 0 chi2= 12417.623861 time= 8.9301e-05 cumTime= 8.9301e-05 edges= 74 schur= 1 lambda= 7.813187 levenbergIter= 1
iteration= 1 chi2= 159.861027 time= 6.7209e-05 cumTime= 0.00015651 edges= 74 schur= 1 lambda= 2.604396 levenbergIter= 1
iteration= 2 chi2= 0.003082 time= 3.7542e-05 cumTime= 0.000194052 edges= 74 schur= 1 lambda= 0.868132 levenbergIter= 1
iteration= 3 chi2= 0.000000 time= 3.693e-05 cumTime= 0.000230982 edges= 74 schur= 1 lambda= 0.578755 levenbergIter= 1
iteration= 4 chi2= 0.000000 time= 3.6379e-05 cumTime= 0.000267361 edges= 74 schur= 1 lambda= 0.385836 levenbergIter= 1
iteration= 5 chi2= 0.000000 time= 3.6459e-05 cumTime= 0.00030382 edges= 74 schur= 1 lambda= 0.257224 levenbergIter= 1
iteration= 6 chi2= 0.000000 time= 0.00014618 cumTime= 0.00045 edges= 74 schur= 1 lambda= 46032074.362190 levenbergIter= 8
iteration= 7 chi2= 0.000000 time= 3.6359e-05 cumTime= 0.000486359 edges= 74 schur= 1 lambda= 92064148.724380 levenbergIter= 1
进程已结束,退出代码0
参考:视觉slam十四讲第七章课后习题7
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "rotation.h"
using namespace std;
using namespace cv;
void find_feature_matches (
const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches );
// 像素坐标转相机归一化坐标
Point2d pixel2cam ( const Point2d& p, const Mat& K );
/*ceres求解BA的话:
(1)构建cost fuction,即代价函数,也就是寻优的目标式。
这个部分需要使用仿函数(functor)这一技巧来实现,
做法是定义一个cost function的结构体,在结构体内重载()运算符。
*/
struct cost_function_define
{
cost_function_define(Point3f p1,Point2f p2):_p1(p1),_p2(p2){}
/*这里目标函数是重投影误差,
* 将第一帧观测到的3D点坐标先通过R,T变化到第二帧的坐标系下,
* 然后用内参转到图像坐标系下,即重投影的坐标u,v。
* 然后残差是第二帧观测到的该三维点的坐标u1,v1分别减去u,v。
*/
template<typename T>
bool operator()(const T* const cere_r,const T* const cere_t,T* residual)const
{
/*这里因为有模板,因此要将Point3f类型的_p1转为模板类型p_1,
* 这样才可以在模板类型中的元素进行运算。否则会报错。*/
T p_1[3];
T p_2[3];
p_1[0]=T(_p1.x);
p_1[1]=T(_p1.y);
p_1[2]=T(_p1.z);
/* 这里的R不是旋转矩阵,也不是四元数表示的,而是用欧拉角表示的。
通过函数 AngleAxisRotatePoint(cere_r,p_1,p_2)可以对3D点进行旋转。相当于用旋转矩阵去左乘。*/
//cout<<"point_3d: "<
AngleAxisRotatePoint(cere_r,p_1,p_2);
p_2[0]=p_2[0]+cere_t[0];
p_2[1]=p_2[1]+cere_t[1];
p_2[2]=p_2[2]+cere_t[2];
const T x=p_2[0]/p_2[2];
const T y=p_2[1]/p_2[2];
//三维点重投影计算的像素坐标
/*这里相机内参没有进行优化,而是直接写入,要一起优化可以稍加修改即可。这里优化的只有相机外参。*/
const T u=x*520.9+325.1;
const T v=y*521.0+249.7;
//观测的在图像坐标下的值
T u1=T(_p2.x);
T v1=T(_p2.y);
//都可以
/* residual[0]=u-u1;
residual[1]=v-v1;*/
//都可以
residual[0]=u1-u;
residual[1]=v1-v;
return true;
}
/*如果将观测变量_p1由类型 Point3f改为double*,则优化结果完全错误。
* 调试发现,传入的观测_p1始终是第一次的值,后面没有再改变。
* 猜测可能是数组必须按地址传递造成的。我将其类型改为自己写的struct类型则正确,
* 初步验证了我的猜想,但是ceres内部怎么写的造成这样,还不太清楚。
*/
Point3f _p1;
Point2f _p2;
};
int main ( int argc, char** argv )
{
if ( argc != 5 )
{
cout<<"usage: pose_estimation_3d2d img1 img2 depth1 depth2"<<endl;
return 1;
}
//-- 读取图像
Mat img_1 = imread ( argv[1], CV_LOAD_IMAGE_COLOR );
Mat img_2 = imread ( argv[2], CV_LOAD_IMAGE_COLOR );
vector<KeyPoint> keypoints_1, keypoints_2;
vector<DMatch> matches;
find_feature_matches ( img_1, img_2, keypoints_1, keypoints_2, matches );
cout<<"一共找到了"<<matches.size() <<"组匹配点"<<endl;
// 建立3D点
Mat d1 = imread ( argv[3], CV_LOAD_IMAGE_UNCHANGED ); // 深度图为16位无符号数,单通道图像
Mat K = ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
vector<Point3f> pts_3d;
vector<Point2f> pts_2d;
for ( DMatch m:matches )
{
ushort d = d1.ptr<unsigned short> (int ( keypoints_1[m.queryIdx].pt.y )) [ int ( keypoints_1[m.queryIdx].pt.x ) ];
if ( d == 0 ) // bad depth
continue;
float dd = d/1000.0;
Point2d p1 = pixel2cam ( keypoints_1[m.queryIdx].pt, K );
pts_3d.push_back ( Point3f ( p1.x*dd, p1.y*dd, dd ) );
pts_2d.push_back ( keypoints_2[m.trainIdx].pt );
}
cout<<"3d-2d pairs: "<<pts_3d.size() <<endl;
Mat r, t;
solvePnP ( pts_3d, pts_2d, K, Mat(), r, t, false ); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法
Mat R;
cv::Rodrigues ( r, R ); // r为旋转向量形式,用Rodrigues公式转换为矩阵
cout<<"----------------optional berore--------------------"<<endl;
cout<<"R="<<endl<<R<<endl;
cout<<"t="<<endl<<t<<endl;
cout<<"R_inv = "<<R.t() <<endl;
cout<<"t_inv = "<<-R.t() *t<<endl;
cout<<"calling bundle adjustment"<<endl;
//给rot,和tranf初值
/*传入的观测是第一帧坐标系下的3D点坐标,和第二帧图像坐标系下的二维点。
* 因为类型不统一,而又必须要用模板参数,因此这里统一类型,修改为double*。*/
double cere_rot[3],cere_tranf[3];
// cere_rot[0]=r.at(0,0);
// cere_rot[1]=r.at(1,0);
// cere_rot[2]=r.at(2,0);
cere_rot[0]=0;
cere_rot[1]=1;
cere_rot[2]=2;
cere_tranf[0]=t.at<double>(0,0);
cere_tranf[1]=t.at<double>(1,0);
cere_tranf[2]=t.at<double>(2,0);
ceres::Problem problem;
for(int i=0;i<pts_3d.size();i++)
{
/*这里可以看到,待优化的变量为cere_rot和cere_tranf,都是3维的变量,残差是2维的,
* 因此ceres::AutoDiffCostFunction对应2,3,3.
*/
ceres::CostFunction* costfunction=new ceres::AutoDiffCostFunction<cost_function_define,2,3,3>(new cost_function_define(pts_3d[i],pts_2d[i]));
problem.AddResidualBlock( // 向问题中添加误差项
costfunction, // 使用自动求导,模板参数:误差类型,输出维度,输入维度,维数要与前面struct中一致
NULL, // 核函数,这里不使用,为空
cere_rot,cere_tranf);//注意,cere_rot不能为Mat类型 // 待估计参数
}
// 配置求解器
ceres::Solver::Options option; // 这里有很多配置项可以填
option.linear_solver_type=ceres::DENSE_SCHUR;// 增量方程如何求解 使用稠密的Cholesky求解器求解简化的线性系统
//输出迭代信息到屏幕
option.minimizer_progress_to_stdout=true; // 输出到cout
//显示优化信息
ceres::Solver::Summary summary; // 优化信息
//开始求解
ceres::Solve(option,&problem,&summary); // 开始优化
//显示优化信息
cout<<summary.BriefReport()<<endl;
cout<<"----------------optional after--------------------"<<endl;
Mat cam_3d = ( Mat_<double> ( 3,1 )<<cere_rot[0],cere_rot[1],cere_rot[2]);
Mat cam_9d;
/*注意cere_rot是旋转向量,因此可以Rodrigues公式转换为旋转矩阵*/
cv::Rodrigues ( cam_3d, cam_9d ); // r为旋转向量形式,用Rodrigues公式转换为矩阵
cout<<"cam_9d:"<<endl<<cam_9d<<endl;
/*输出结果和EPnP求解的基本一致。*/
cout<<"cam_t:"<<cere_tranf[0]<<" "<<cere_tranf[1]<<" "<<cere_tranf[2]<<endl;
Mat tranf_3d=(Mat_<double>(3,1)<<cere_tranf[0],cere_tranf[1],cere_tranf[2]);
Mat tf_3d_2 = (Mat_<double>(3,1) << 0,0,0);
Mat tf_3d_2_1 = (Mat_<double>(3,1) << 0,0,0);
Mat tf_2d_2 = (Mat_<double>(2,1) << 0,0);
for(int i=0;i<5;++i)
{
cout<<"p1= "<<pts_3d[i]<<endl;
cout<<"p2= "<<pts_2d[i]<<endl;
tf_3d_2 = cam_9d*(Mat_<double>(3,1)<<pts_3d[i].x,pts_3d[i].y,pts_3d[i].z)+tranf_3d;
tf_3d_2_1 = K * tf_3d_2/tf_3d_2.at<double>(2);
tf_2d_2 = (Mat_<double>(2,1) << tf_3d_2_1.at<double>(0), tf_3d_2_1.at<double>(1));
cout<<"(R*p1+t)= " << tf_2d_2.t() << endl;
cout<<endl;
}
}
void find_feature_matches ( const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches )
{
//-- 初始化
Mat descriptors_1, descriptors_2;
// used in OpenCV3
Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
// use this if you are in OpenCV2
// Ptr detector = FeatureDetector::create ( "ORB" );
// Ptr descriptor = DescriptorExtractor::create ( "ORB" );
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create ( "BruteForce-Hamming" );
//-- 第一步:检测 Oriented FAST 角点位置
detector->detect ( img_1,keypoints_1 );
detector->detect ( img_2,keypoints_2 );
//-- 第二步:根据角点位置计算 BRIEF 描述子
descriptor->compute ( img_1, keypoints_1, descriptors_1 );
descriptor->compute ( img_2, keypoints_2, descriptors_2 );
//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
vector<DMatch> match;
// BFMatcher matcher ( NORM_HAMMING );
matcher->match ( descriptors_1, descriptors_2, match );
//-- 第四步:匹配点对筛选
double min_dist=10000, max_dist=0;
//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
for ( int i = 0; i < descriptors_1.rows; i++ )
{
double dist = match[i].distance;
if ( dist < min_dist ) min_dist = dist;
if ( dist > max_dist ) max_dist = dist;
}
printf ( "-- Max dist : %f \n", max_dist );
printf ( "-- Min dist : %f \n", min_dist );
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
for ( int i = 0; i < descriptors_1.rows; i++ )
{
if ( match[i].distance <= max ( 2*min_dist, 30.0 ) )
{
matches.push_back ( match[i] );
}
}
}
Point2d pixel2cam ( const Point2d& p, const Mat& K )
{
return Point2d
(
( p.x - K.at<double> ( 0,2 ) ) / K.at<double> ( 0,0 ),
( p.y - K.at<double> ( 1,2 ) ) / K.at<double> ( 1,1 )
);
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "rotation.h"
using namespace std;
using namespace cv;
//第一步:定义Cost Function函数
struct cost_function
{
cost_function(Point3f p_3D,Point2f p_p):_p_3D(p_3D),_p_p(p_p) {}//3D-2D:知道n个3D空间点以及其投影位置,然后估计相机位姿
//计算残差
template <typename T>//模板:使得在定义时可以模糊类型
bool operator() (
const T* const r, const T* const t,//r,t为待优化的参数
const T* K,//k为待优化的参数
T* residual ) const //殘差
{
T p_3d[3];
T p_Cam[3];//相机坐标系下空间点的坐标
p_3d[0]=T(_p_3D.x);
p_3d[1]=T(_p_3D.y);
p_3d[2]=T(_p_3D.z);
// 通过tool文件夹中的rotation.h中的AngleAxisRotatePoint()函数
// 计算在相机仅旋转的情况下,新坐标系下的坐标
// 也就是p_Cam=R*p_3d
//cout<<"point_3d: "<
AngleAxisRotatePoint(r,p_3d,p_Cam);
p_Cam[0]=p_Cam[0]+t[0];
p_Cam[1]=p_Cam[1]+t[1];
p_Cam[2]=p_Cam[2]+t[2];
//R,t计算T
//Eigen::Isometry3d T_w_c;
// T_w_c.rotate(r);
const T x=p_Cam[0]/p_Cam[2];
const T y=p_Cam[1]/p_Cam[2];
//3D点投影后的像素坐标
// const T u=x*520.9+325.1;
// const T v=y*521.0+249.7;
const T u=x*K[0]+K[1];
const T v=y*K[2]+K[3];
//观测到的投影位置的像素坐标
T u1=T(_p_p.x);
T v1=T(_p_p.y);
//残差
residual[0]=u-u1;
residual[1]=v-v1;
return true;
}
Point3f _p_3D;
Point2f _p_p;
};
void find_feature_matches (
const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches );
// 像素坐标转相机归一化坐标
Point2d pixel2cam ( const Point2d& p, const Mat& K );
void bundleAdjustment(const vector<Point3f> points_3d,
const vector<Point2f> points_2d, Mat K, Mat &r, Mat &t);
int main ( int argc, char** argv )
{
cv::Mat img_1,img_2,d1,d2;
if(argc!=5)
{
//读取图片
img_1 = cv::imread("../src/1.png", CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread("../src/2.png",1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
d1 = cv::imread("../src/1_depth.png", CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
d2 = cv::imread("../src/2_depth.png", -1);
}
else
{
//读取图片
img_1 = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread(argv[2],1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
d1 = cv::imread(argv[3], CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
d2 = cv::imread(argv[4], -1);
}
assert(img_1.data != nullptr && img_2.data != nullptr);//若读取的图片没有内容,就终止程序
vector<KeyPoint> keypoints_1, keypoints_2;
vector<DMatch> matches;
find_feature_matches ( img_1, img_2, keypoints_1, keypoints_2, matches );
cout<<"一共找到了"<<matches.size() <<"组匹配点"<<endl;
Mat K = ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
vector<Point3f> pts_3d;
vector<Point2f> pts_2d;
for ( DMatch m:matches )
{
ushort d = d1.ptr<unsigned short> (int ( keypoints_1[m.queryIdx].pt.y )) [ int ( keypoints_1[m.queryIdx].pt.x ) ];
if ( d == 0 ) // bad depth
continue;
float dd = d/1000.0;
Point2d p1 = pixel2cam ( keypoints_1[m.queryIdx].pt, K );
pts_3d.push_back ( Point3f ( p1.x*dd, p1.y*dd, dd ) );
pts_2d.push_back ( keypoints_2[m.trainIdx].pt );
}
cout<<"3d-2d pairs: "<<pts_3d.size() <<endl;
Mat r, t;
// solvePnP ( pts_3d, pts_2d, K, Mat(), r, t, false,cv::SOLVEPNP_EPNP ); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法
// solvePnP ( pts_3d, pts_2d, K, Mat(), r, t, false,CV_ITERATIVE );
solvePnP ( pts_3d, pts_2d, K, Mat(), r, t, false);
Mat R;
cv::Rodrigues ( r, R ); // r为旋转向量形式,用Rodrigues公式转换为矩阵
cout<<"optional before: "<<endl;
cout<<"R="<<endl<<R<<endl;
cout<<"t="<<endl<<t<<endl<<endl;
cout<<"calling bundle adjustment"<<endl;
bundleAdjustment(pts_3d,pts_2d,K,r,t);
}
void find_feature_matches ( const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches )
{
//-- 初始化
Mat descriptors_1, descriptors_2;
// used in OpenCV3
Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
// use this if you are in OpenCV2
// Ptr detector = FeatureDetector::create ( "ORB" );
// Ptr descriptor = DescriptorExtractor::create ( "ORB" );
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create ( "BruteForce-Hamming" );
//-- 第一步:检测 Oriented FAST 角点位置
detector->detect ( img_1,keypoints_1 );
detector->detect ( img_2,keypoints_2 );
//-- 第二步:根据角点位置计算 BRIEF 描述子
descriptor->compute ( img_1, keypoints_1, descriptors_1 );
descriptor->compute ( img_2, keypoints_2, descriptors_2 );
//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
vector<DMatch> match;
// BFMatcher matcher ( NORM_HAMMING );
matcher->match ( descriptors_1, descriptors_2, match );
//-- 第四步:匹配点对筛选
double min_dist=10000, max_dist=0;
//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
for ( int i = 0; i < descriptors_1.rows; i++ )
{
double dist = match[i].distance;
if ( dist < min_dist ) min_dist = dist;
if ( dist > max_dist ) max_dist = dist;
}
printf ( "-- Max dist : %f \n", max_dist );
printf ( "-- Min dist : %f \n", min_dist );
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
for ( int i = 0; i < descriptors_1.rows; i++ )
{
if ( match[i].distance <= max ( 2*min_dist, 30.0 ) )
{
matches.push_back ( match[i] );
}
}
}
Point2d pixel2cam ( const Point2d& p, const Mat& K )
{
return Point2d
(
( p.x - K.at<double> ( 0,2 ) ) / K.at<double> ( 0,0 ),
( p.y - K.at<double> ( 1,2 ) ) / K.at<double> ( 1,1 )
);
}
//构建最小二乘问题
void bundleAdjustment(const vector<Point3f> points_3d,
const vector<Point2f> points_2d,Mat K, Mat &r, Mat &t)
{
// cout<<"R = "<
cout<<"start:"<<endl;
double rotation_vector[3],tranf[3];//旋转向量r,平移t
double k[4];
rotation_vector[0]=r.at<double>(0,0);
rotation_vector[1]=r.at<double>(0,1);
rotation_vector[2]=r.at<double>(0,2);
tranf[0]=t.at<double>(0,0);
tranf[1]=t.at<double>(1,0);
tranf[2]=t.at<double>(2,0);
k[0]=520.9;
k[1]=325.1;
k[2]=521.0;
k[3]=249.7;
ceres::Problem problem;
for(int i=0;i<points_3d.size();++i)
{
ceres::CostFunction* costfunction=new ceres::AutoDiffCostFunction<cost_function,2,3,3,4>(new cost_function(points_3d[i],points_2d[i]));
problem.AddResidualBlock(costfunction,NULL,rotation_vector,tranf,k);
}
// cout<
//配置求解器
ceres::Solver::Options option;
option.linear_solver_type=ceres::DENSE_QR;//DENSE_SCHUR
//true:迭代信息输出到屏幕.false:不输出
option.minimizer_progress_to_stdout=true;
ceres::Solver::Summary summary;//优化信息
//可以和g2o优化做对比
chrono::steady_clock::time_point t1=chrono::steady_clock::now();
//开始优化
ceres::Solve(option,&problem,&summary);
chrono::steady_clock::time_point t2=chrono::steady_clock::now();
chrono::duration<double> time_used=chrono::duration_cast<chrono::duration<double>>(t2-t1);
cout<<"solve time cost = "<<time_used.count()<<" second."<<endl;
//输出结果
cout<<summary.BriefReport()<<endl;
Mat Rotaion_vector=(Mat_<double>(3,1)<<rotation_vector[0],rotation_vector[1],rotation_vector[2]);
// cout<
Mat Rotation_matrix;
Rodrigues(Rotaion_vector,Rotation_matrix);//r为旋转向量形式,用Rodrigues公式转换为矩阵
cout<<"after optional:"<<endl;
cout<<"R = "<<endl<<Rotation_matrix<<endl;
// cout<<"R = "<
cout<<"t = "<<tranf[0]<<" "<<tranf[1]<<" "<<tranf[2]<<endl;
cout << "k = " << k[0] << " " << k[1] << " " << k[2] << " " << k[3] << endl;
}
cmake_minimum_required(VERSION 2.8)
project(cap7_10_ceres_pnp)
set( CMAKE_CXX_STANDARD 14)
set( CMAKE_BUILD_TYPE Release )
# 添加cmake模块以使用ceres库
list( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules )
find_package(OpenCV 3.1 REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRECTORIES})
# 寻找Ceres库并添加它的头文件
find_package( Ceres REQUIRED )
include_directories( ${CERES_INCLUDE_DIRS} )
include_directories("/usr/include/eigen3")
add_executable(cap7_10_ceres_pnp src/cap7_10_ceres_pnp.cpp)
target_link_libraries( cap7_10_ceres_pnp
${OpenCV_LIBS}
${CERES_LIBRARIES}
)
/home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_pnp/cmake-build-release/cap7_10_ceres_pnp /home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_pnp/src/1.png /home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_pnp/src/2.png /home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_pnp/src/1_depth.png /home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_pnp/src/2_depth.png
[ INFO:0] Initialize OpenCL runtime...
-- Max dist : 95.000000
-- Min dist : 7.000000
一共找到了81组匹配点
3d-2d pairs: 77
----------------optional berore--------------------
R=
[0.9979193252686379, -0.05138618892180048, 0.03894200615632972;
0.05033852889628791, 0.9983556573826996, 0.02742287148559438;
-0.04028712901094069, -0.0254055301076531, 0.9988651091493811]
t=
[-0.6279335377101095;
-0.03681764073299486;
0.3049963487769202]
R_inv = [0.9979193252686379, 0.05033852889628791, -0.04028712901094069;
-0.05138618892180048, 0.9983556573826996, -0.0254055301076531;
0.03894200615632972, 0.02742287148559438, 0.9988651091493811]
t_inv = [0.6407677853881939;
0.01223858243973925;
-0.2791875740897244]
calling bundle adjustment
iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time
0 1.748561e+07 0.00e+00 5.22e+07 0.00e+00 0.00e+00 1.00e+04 0 3.70e-05 1.18e-04
1 5.467247e+06 1.20e+07 9.71e+06 6.54e+00 6.90e-01 1.06e+04 1 7.10e-05 2.03e-04
2 1.636783e+06 3.83e+06 2.81e+06 7.28e+00 7.03e-01 1.13e+04 1 3.89e-05 2.47e-04
3 1.852413e+07 -1.69e+07 2.81e+06 3.16e+01 -1.03e+01 5.67e+03 1 1.91e-05 2.70e-04
4 1.870843e+07 -1.71e+07 2.81e+06 3.15e+01 -1.05e+01 1.42e+03 1 1.81e-05 2.92e-04
5 1.988564e+07 -1.82e+07 2.81e+06 3.12e+01 -1.12e+01 1.77e+02 1 1.69e-05 3.14e-04
6 4.437442e+07 -4.27e+07 2.81e+06 2.86e+01 -2.62e+01 1.11e+01 1 3.19e-05 3.50e-04
7 6.460253e+05 9.91e+05 2.78e+06 1.04e+01 6.78e-01 1.16e+01 1 1.18e-04 4.74e-04
8 5.770896e+04 5.88e+05 9.14e+05 3.49e+00 1.00e+00 3.48e+01 1 7.89e-05 5.73e-04
9 8.993651e+03 4.87e+04 7.95e+04 1.30e+00 9.95e-01 1.04e+02 1 7.80e-05 6.68e-04
10 1.356294e+03 7.64e+03 7.48e+03 7.89e-01 9.81e-01 3.13e+02 1 7.70e-05 7.61e-04
11 2.032478e+02 1.15e+03 3.89e+03 4.08e-01 9.96e-01 9.39e+02 1 8.70e-05 8.64e-04
12 1.600523e+02 4.32e+01 3.45e+02 9.20e-02 1.00e+00 2.82e+03 1 7.61e-05 9.55e-04
13 1.597797e+02 2.73e-01 7.35e+00 7.88e-03 1.00e+00 8.45e+03 1 7.61e-05 1.05e-03
14 1.597795e+02 2.67e-04 8.87e-02 2.56e-04 1.00e+00 2.54e+04 1 7.70e-05 1.14e-03
Ceres Solver Report: Iterations: 15, Initial cost: 1.748561e+07, Final cost: 1.597795e+02, Termination: CONVERGENCE
----------------optional after--------------------
cam_9d:
[0.9979193163023975, -0.05138607093001038, 0.03894239161802245;
0.0503383924215025, 0.9983556583913398, 0.0274230852825386;
-0.04028752162859244, -0.02540572912495342, 0.9988650882519897]
cam_t:-0.627937 -0.0368194 0.304997
p1= [-0.187062, -4.15408, 13.724]
p2= [323, 109]
(R*p1+t)= [322.6404327753651, 108.9202588469664]
p1= [-1.21849, -0.588597, 7.924]
p2= [231, 219]
(R*p1+t)= [230.4614792571009, 220.2360932450788]
p1= [-3.13876, 0.800932, 6.698]
p2= [65, 308]
(R*p1+t)= [65.40258102155319, 307.546289342931]
p1= [-1.61722, 0.524364, 7.133]
p2= [185, 292]
(R*p1+t)= [186.4838654527213, 291.5433045317255]
p1= [-3.13611, 0.50727, 6.558]
p2= [61, 287]
(R*p1+t)= [61.38406743181415, 286.4510353335888]
进程已结束,退出代码0
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "rotation.h"
using namespace std;
using namespace cv;
void find_feature_matches(const Mat& img_1,const Mat& img_2,
vector<KeyPoint>& keypoints_1,
vector<KeyPoint>& keypoints_2,
vector<DMatch>& matches);
//像素坐标转相机归一化坐标
Point2d pixel2cam(const Point2d& p,const Mat& K);
void pose_estimation_3d3d(const vector<Point3f>& pts1,
const vector<Point3f>& pts2,
Mat& r,Mat& t_inv);
void bundleAdjustment(const vector<Point3f>& points_3f,
const vector<Point3f>& points_2f,
Mat& R, Mat& t_inv);//test 声明的行参和定义的不同是否可行:可以的!
struct cost_function_define
{
cost_function_define(Point3f p1,Point3f p2):_p1(p1),_p2(p2){}
template<typename T>
bool operator()(const T* const cere_r,const T* const cere_t,T* residual) const
{
T p_1[3];
T p_2[3];
p_1[0]=T(_p1.x);
p_1[1]=T(_p1.y);
p_1[2]=T(_p1.z);
AngleAxisRotatePoint(cere_r,p_1,p_2);
p_2[0]=p_2[0]+cere_t[0];
p_2[1]=p_2[1]+cere_t[1];
p_2[2]=p_2[2]+cere_t[2];
const T x=p_2[0]/p_2[2];
const T y=p_2[1]/p_2[2];
const T u=x*520.9+325.1;
const T v=y*521.0+249.7;
T p_3[3];
p_3[0]=T(_p2.x);
p_3[1]=T(_p2.y);
p_3[2]=T(_p2.z);
const T x1=p_3[0]/p_3[2];
const T y1=p_3[1]/p_3[2];
const T u1=x1*520.9+325.1;
const T v1=y1*521.0+249.7;
residual[0]=u-u1;
residual[1]=v-v1;
return true;
}
Point3f _p1,_p2;
};
int main(int argc,char** argv)
{
cv::Mat img_1,img_2,depth_1,depth_2;
if(argc!=5)
{
//读取图片
img_1 = cv::imread("../src/1.png", CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread("../src/2.png",1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
depth_1 = cv::imread("../src/1_depth.png", CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
depth_2 = cv::imread("../src/2_depth.png", -1);
}
else
{
//读取图片
img_1 = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);//读取彩色图
img_2 = cv::imread(argv[2],1);
//接下来的是建立3d点 利用深度图可以获取深度信息
//depth1是图1对应的深度图 depth2是图2对应的深度图
depth_1 = cv::imread(argv[3], CV_LOAD_IMAGE_UNCHANGED);// 深度图为16位无符号数,单通道图像
depth_2 = cv::imread(argv[4], -1);
}
assert(img_1.data != nullptr && img_2.data != nullptr);//若读取的图片没有内容,就终止程序
vector<KeyPoint> keypoints_1,keypoints_2;
vector<DMatch> matches;
find_feature_matches(img_1,img_2,keypoints_1,keypoints_2,matches);
cout<<"一共找到了"<<matches.size()<<"组匹配点"<<endl;
Mat K=(Mat_<double>(3,3)<<520.9,0,325.1,
0,521.0,249.7,
0,0,1);
vector<Point3f> pts1,pts2;
for(DMatch m:matches)
{
ushort d1=depth_1.ptr<unsigned short>(int(keypoints_1[m.queryIdx].pt.y))[int(keypoints_1[m.queryIdx].pt.x)];
ushort d2=depth_2.ptr<unsigned short>(int(keypoints_2[m.trainIdx].pt.y))[int(keypoints_2[m.trainIdx].pt.x)];
if(d1==0 || d2==0)//bad depth
continue;
Point2d p1=pixel2cam(keypoints_1[m.queryIdx].pt,K);
Point2d p2=pixel2cam(keypoints_2[m.trainIdx].pt,K);
float dd1=float(d1)/5000.0;
float dd2=float(d2)/5000.0;
pts1.push_back(Point3f(p1.x*dd1,p1.y*dd1,dd1));
pts2.push_back(Point3f(p2.x*dd2,p2.y*dd2,dd2));
}
cout<<"3d-3d pairs: "<<pts1.size()<<endl;
Mat R,t;
pose_estimation_3d3d(pts1,pts2,R,t);
cout<<"ICP via SVD results: "<<endl;
cout<<"R ="<<endl<<R<<endl;
cout<<"t ="<<endl<<t<<endl;
Mat R_inv=R.t();
Mat t_inv=-R.t()*t;
cout<<"R_inv ="<<endl<<R_inv<<endl;//R^(-1)
cout<<"t_inv ="<<endl<<t_inv<<endl;
Mat r;
Rodrigues(R_inv,r);//R_inv->r
cout<<"r= "<<endl<<r<<endl;
for(int i=0;i<5;++i)
{
cout<<"p1= "<<pts1[i]<<endl;//??
cout<<"p2= "<<pts2[i]<<endl;//??
cout<<"(R*p2+t) = "<<
R*(Mat_<double>(3,1)<<pts2[i].x,pts2[i].y,pts2[i].z)+t
<<endl;cout<<endl;
}
cout<<"calling bundle adjustment"<<endl;
bundleAdjustment(pts1,pts2,r,t_inv);
return 0;
}
void find_feature_matches ( const Mat& img_1, const Mat& img_2,
std::vector<KeyPoint>& keypoints_1,
std::vector<KeyPoint>& keypoints_2,
std::vector< DMatch >& matches )
{
//-- 初始化
Mat descriptors_1, descriptors_2;
// used in OpenCV3
Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
// use this if you are in OpenCV2
// Ptr detector = FeatureDetector::create ( "ORB" );
// Ptr descriptor = DescriptorExtractor::create ( "ORB" );
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create ( "BruteForce-Hamming" );
//-- 第一步:检测 Oriented FAST 角点位置
detector->detect ( img_1,keypoints_1 );
detector->detect ( img_2,keypoints_2 );
//-- 第二步:根据角点位置计算 BRIEF 描述子
descriptor->compute ( img_1, keypoints_1, descriptors_1 );
descriptor->compute ( img_2, keypoints_2, descriptors_2 );
//-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
vector<DMatch> match;
// BFMatcher matcher ( NORM_HAMMING );
matcher->match ( descriptors_1, descriptors_2, match );
//-- 第四步:匹配点对筛选
double min_dist=10000, max_dist=0;
//找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
for ( int i = 0; i < descriptors_1.rows; i++ )
{
double dist = match[i].distance;
if ( dist < min_dist ) min_dist = dist;
if ( dist > max_dist ) max_dist = dist;
}
printf ( "-- Max dist : %f \n", max_dist );
printf ( "-- Min dist : %f \n", min_dist );
//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
for ( int i = 0; i < descriptors_1.rows; i++ )
{
if ( match[i].distance <= max ( 2*min_dist, 30.0 ) )
{
matches.push_back ( match[i] );
}
}
}
Point2d pixel2cam ( const Point2d& p, const Mat& K )
{
return Point2d
(
( p.x - K.at<double> ( 0,2 ) ) / K.at<double> ( 0,0 ),
( p.y - K.at<double> ( 1,2 ) ) / K.at<double> ( 1,1 )
);
}
void pose_estimation_3d3d(const vector<cv::Point3f> &pts1,
const vector<cv::Point3f> &pts2,
cv::Mat &R, cv::Mat &t)
{
int N = pts1.size();//匹配的3d点个数
cv::Point3f p1,p2;//质心
for (int i = 0; i < N; i++)
{
p1+=pts1[i];
p2+=pts2[i];
}
p1 = cv::Point3f(cv::Vec3f(p1)/N);//得到质心
p2 = cv::Point3f(cv::Vec3f(p2)/N);
vector<cv::Point3f> q1(N), q2(N);
for(int i = 0; i < N; i++)
{
//去质心
q1[i] = pts1[i] - p1;
q2[i] = pts2[i] - p2;
}
//计算 W+=q1*q2^T(求和)
Eigen::Matrix3d W = Eigen::Matrix3d::Zero();//初始化
for(int i = 0; i < N; i++)
{
W += Eigen::Vector3d(q1[i].x,q1[i].y,q1[i].z) * Eigen::Vector3d(q2[i].x,q2[i].y,q2[i].z).transpose();
}
cout << "W = " << endl << W << endl;
//利用svd分解 W=U*sigema*V
//Eigen::ComputeFullU : 在JacobiSVD中使用,表示要计算方阵U
//Eigen::ComputeFullV : 在JacobiSVD中使用,表示要计算方阵V
Eigen::JacobiSVD<Eigen::Matrix3d> svd(W,Eigen::ComputeFullU | Eigen::ComputeFullV);
Eigen::Matrix3d U = svd.matrixU();//得到U矩阵
Eigen::Matrix3d V = svd.matrixV();//得到V矩阵
cout << "U=" << U << endl;
cout << "V=" << V << endl;
Eigen::Matrix3d R_ = U * (V.transpose());
if(R_.determinant() < 0)//若旋转矩阵R_的行列式<0 则取负号
{
R_ = -R_;
}
//得到平移向量
Eigen::Vector3d t_ = Eigen::Vector3d (p1.x,p1.y,p1.z) - R_ * Eigen::Vector3d(p2.x,p2.y,p2.z);
//把 Eigen形式的 r 和 t_ 转换为CV 中的Mat格式
R = (cv::Mat_<double>(3,3) <<
R_(0, 0), R_(0, 1), R_(0, 2),
R_(1, 0), R_(1, 1), R_(1, 2),
R_(2, 0), R_(2, 1), R_(2, 2)
);
t = (cv::Mat_<double>(3, 1) << t_(0, 0), t_(1, 0), t_(2, 0));
}
void bundleAdjustment(const vector<Point3f> &pts1, const vector<Point3f> &pts2, Mat &r, Mat &t_inv)
{
double cere_rot[3], cere_tranf[3];
//关于初值的选取有疑问,随便选择一个初值时,结果不正确,why??
cere_rot[0]=r.at<double>(0,0);
cere_rot[1]=r.at<double>(1,0);
cere_rot[2]=r.at<double>(2,0);
cere_tranf[0]=t_inv.at<double>(0,0);
cere_tranf[1]=t_inv.at<double>(1,0);
cere_tranf[2]=t_inv.at<double>(2,0);
ceres::Problem problem;
for(int i=0;i<pts1.size();++i)
{
ceres::CostFunction* costfunction=new ceres::AutoDiffCostFunction<cost_function_define,2,3,3>(new cost_function_define(pts1[i],pts2[i]));
problem.AddResidualBlock(costfunction,NULL,cere_rot,cere_tranf);
}
ceres::Solver::Options option;
option.linear_solver_type=ceres::DENSE_SCHUR;
option.minimizer_progress_to_stdout=true;
ceres::Solver::Summary summary;
ceres::Solve(option,&problem,&summary);
cout<<summary.BriefReport()<<endl;
//std::cout << summary.FullReport() << "\n";
cout<<"optional after: "<<endl;
Mat cam_3d=(Mat_<double>(3,1)<<cere_rot[0],cere_rot[1],cere_rot[2]);
// cout<<"cam_3d : "<
Mat cam_9d;
Rodrigues(cam_3d,cam_9d);
cout<<"cam_9d: "<<endl<<cam_9d<<endl;
cout<<"cam_t: "<<endl<<cere_tranf[0]<<" "<<cere_tranf[1]<<" "<<cere_tranf[2]<<endl;
Mat tranf_3d=(Mat_<double>(3,1)<<cere_tranf[0],cere_tranf[1],cere_tranf[2]);
for(int i=0;i<5;++i)
{
cout<<"p1= "<<pts1[i]<<endl;
cout<<"p2= "<<pts2[i]<<endl;
cout<<"(R*p1+t)= "<<
cam_9d*(Mat_<double>(3,1)<<pts1[i].x,pts1[i].y,pts1[i].z)+tranf_3d<<endl;
cout<<endl;
}
}
cmake_minimum_required(VERSION 2.8)
project(cap7_10_ceres_icp)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_BUILD_TYPE Release)
# 添加cmake模块以使用ceres库
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules)
find_package(OpenCV 3.1 REQUIRED)
find_package(Ceres REQUIRED)
include_directories(
${OpenCV_INCLUDE_DIRECTORIES}
"/usr/include/eigen3"
${CERES_INCLUDE_DIRECTORIES}
)
add_executable(cap7_10_ceres_icp src/cap7_10_ceres_icp.cpp)
target_link_libraries(cap7_10_ceres_icp
${OpenCV_LIBRARIES}
${CERES_LIBRARIES}
)
/home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_icp/cmake-build-release/cap7_10_ceres_icp /home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_icp/src/1.png /home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_icp/src/2.png /home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_icp/src/1_depth.png /home/bupo/shenlan/zuoye/cap7/cap7_10_ceres_icp/src/2_depth.png
[ INFO:0] Initialize OpenCL runtime...
-- Max dist : 95.000000
-- Min dist : 7.000000
一共找到了81组匹配点
3d-3d pairs: 75
W =
11.8688 -0.717698 1.89486
-1.88065 3.83391 -5.78219
3.25846 -5.82734 9.65267
U= 0.592295 -0.805658 -0.0101195
-0.418171 -0.318113 0.850845
0.688709 0.499719 0.525319
V= 0.64736 -0.761401 -0.0345329
-0.388765 -0.368829 0.844291
0.655581 0.533135 0.534772
ICP via SVD results:
R =
[0.9972065647956201, 0.05834273442898391, -0.04663895869192625;
-0.05787745545449196, 0.998260122172866, 0.01126626067193237;
0.04721511705620757, -0.008535444848613793, 0.9988482762174666]
t =
[0.1379879629890433;
-0.06551699470729988;
-0.02981649388290575]
R_inv =
[0.9972065647956201, -0.05787745545449196, 0.04721511705620757;
0.05834273442898391, 0.998260122172866, -0.008535444848613793;
-0.04663895869192625, 0.01126626067193237, 0.9988482762174666]
t_inv =
[-0.1399866702492459;
0.05709791102272541;
0.03695589996443214]
r=
[0.009910244558423291;
0.04697155214755139;
0.05816521729285219]
p1= [-0.0374123, -0.830816, 2.7448]
p2= [-0.0111479, -0.746763, 2.7652]
(R*p2+t) = [-0.04566300488482969;
-0.7791822151698653;
2.738046267661636]
p1= [-0.243698, -0.117719, 1.5848]
p2= [-0.299118, -0.0975683, 1.6558]
(R*p2+t) = [-0.243212054430598;
-0.1269486029625337;
1.610786345002579]
p1= [-0.627753, 0.160186, 1.3396]
p2= [-0.709645, 0.159033, 1.4212]
(R*p2+t) = [-0.6266796777024644;
0.1503229238263245;
1.354883323538178]
p1= [-0.323443, 0.104873, 1.4266]
p2= [-0.399079, 0.12047, 1.4838]
(R*p2+t) = [-0.3221508525590339;
0.09455772952719482;
1.432403794814274]
p1= [-0.627221, 0.101454, 1.3116]
p2= [-0.709709, 0.100216, 1.3998]
(R*p2+t) = [-0.6291763602679332;
0.09137127679150181;
1.334006907588644]
calling bundle adjustment
iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time
0 1.386936e+03 0.00e+00 7.80e+04 0.00e+00 0.00e+00 1.00e+04 0 2.70e-04 3.74e-04
1 1.581843e+02 1.23e+03 1.74e+03 7.83e-02 9.99e-01 3.00e+04 1 1.07e-04 6.52e-04
2 1.573157e+02 8.69e-01 1.57e+00 1.71e-03 1.00e+00 9.00e+04 1 5.98e-05 7.43e-04
Ceres Solver Report: Iterations: 3, Initial cost: 1.386936e+03, Final cost: 1.573157e+02, Termination: CONVERGENCE
optional after:
cam_9d:
[0.9979163765916491, -0.0513269110025363, 0.03909544138555834;
0.05027495981204224, 0.9983586154877093, 0.02743179354985397;
-0.04043925995974022, -0.02540911427818745, 0.9988588704944791]
cam_t:
-0.125957 -0.00740074 0.0609871
p1= [-0.0374123, -0.830816, 2.7448]
p2= [-0.0111479, -0.746763, 2.7652]
(R*p1+t)= [-0.01333943894784084;
-0.7634388360382136;
2.825278250152581]
p1= [-0.243698, -0.117719, 1.5848]
p2= [-0.299118, -0.0975683, 1.6558]
(R*p1+t)= [-0.3011474122086973;
-0.09370484161307237;
1.656824782674852]
p1= [-0.627753, 0.160186, 1.3396]
p2= [-0.709645, 0.159033, 1.4212]
(R*p1+t)= [-0.7082517178879888;
0.1577100606269968;
1.420374084677415]
p1= [-0.323443, 0.104873, 1.4266]
p2= [-0.399079, 0.12047, 1.4838]
(R*p1+t)= [-0.3983357466712113;
0.1201731356860113;
1.49637422656784]
p1= [-0.627221, 0.101454, 1.3116]
p2= [-0.709709, 0.100216, 1.3998]
(R*p1+t)= [-0.7058015075804205;
0.09833264832376766;
1.393876887667482]
进程已结束,退出代码0
WARNING: Logging before InitGoogleLogging() is written to STDERR E0408 11:45:04.339355 7237 trust_region_minimizer.cc:73] Terminating: Residual and Jacobian evaluation failed.
,找不到合适的解决方案(补充,一一排查找到原因了,是因为大佬王家在自定义的仿函数类中,返回布尔量了,可以看我的1.10.1.2)#ifndef ROTATION_H
#define ROTATION_H
#include
#include
#include
//
// math functions needed for rotation conversion.
// dot and cross production
template<typename T>
inline T DotProduct(const T x[3], const T y[3]) {
return (x[0] * y[0] + x[1] * y[1] + x[2] * y[2]);
}
template<typename T>
inline void CrossProduct(const T x[3], const T y[3], T result[3]){
result[0] = x[1] * y[2] - x[2] * y[1];
result[1] = x[2] * y[0] - x[0] * y[2];
result[2] = x[0] * y[1] - x[1] * y[0];
}
//
// Converts from a angle anxis to quaternion :
template<typename T>
inline void AngleAxisToQuaternion(const T* angle_axis, T* quaternion){
const T& a0 = angle_axis[0];
const T& a1 = angle_axis[1];
const T& a2 = angle_axis[2];
const T theta_squared = a0 * a0 + a1 * a1 + a2 * a2;
if(theta_squared > T(std::numeric_limits<double>::epsilon()) ){
const T theta = sqrt(theta_squared);
const T half_theta = theta * T(0.5);
const T k = sin(half_theta)/theta;
quaternion[0] = cos(half_theta);
quaternion[1] = a0 * k;
quaternion[2] = a1 * k;
quaternion[3] = a2 * k;
}
else{ // in case if theta_squared is zero
const T k(0.5);
quaternion[0] = T(1.0);
quaternion[1] = a0 * k;
quaternion[2] = a1 * k;
quaternion[3] = a2 * k;
}
}
template<typename T>
inline void QuaternionToAngleAxis(const T* quaternion, T* angle_axis){
const T& q1 = quaternion[1];
const T& q2 = quaternion[2];
const T& q3 = quaternion[3];
const T sin_squared_theta = q1 * q1 + q2 * q2 + q3 * q3;
// For quaternions representing non-zero rotation, the conversion
// is numercially stable
if(sin_squared_theta > T(std::numeric_limits<double>::epsilon()) ){
const T sin_theta = sqrt(sin_squared_theta);
const T& cos_theta = quaternion[0];
// If cos_theta is negative, theta is greater than pi/2, which
// means that angle for the angle_axis vector which is 2 * theta
// would be greater than pi...
const T two_theta = T(2.0) * ((cos_theta < 0.0)
? atan2(-sin_theta, -cos_theta)
: atan2(sin_theta, cos_theta));
const T k = two_theta / sin_theta;
angle_axis[0] = q1 * k;
angle_axis[1] = q2 * k;
angle_axis[2] = q3 * k;
}
else{
// For zero rotation, sqrt() will produce NaN in derivative since
// the argument is zero. By approximating with a Taylor series,
// and truncating at one term, the value and first derivatives will be
// computed correctly when Jets are used..
const T k(2.0);
angle_axis[0] = q1 * k;
angle_axis[1] = q2 * k;
angle_axis[2] = q3 * k;
}
}
template<typename T>
inline void AngleAxisRotatePoint(const T angle_axis[3], const T pt[3], T result[3]) {
const T theta2 = DotProduct(angle_axis, angle_axis);
if (theta2 > T(std::numeric_limits<double>::epsilon())) {
// Away from zero, use the rodriguez formula
//
// result = pt costheta +
// (w x pt) * sintheta +
// w (w . pt) (1 - costheta)
//
// We want to be careful to only evaluate the square root if the
// norm of the angle_axis vector is greater than zero. Otherwise
// we get a division by zero.
//
const T theta = sqrt(theta2);
const T costheta = cos(theta);
const T sintheta = sin(theta);
const T theta_inverse = 1.0 / theta;
const T w[3] = { angle_axis[0] * theta_inverse,
angle_axis[1] * theta_inverse,
angle_axis[2] * theta_inverse };
// Explicitly inlined evaluation of the cross product for
// performance reasons.
/*const T w_cross_pt[3] = { w[1] * pt[2] - w[2] * pt[1],
w[2] * pt[0] - w[0] * pt[2],
w[0] * pt[1] - w[1] * pt[0] };*/
T w_cross_pt[3];
CrossProduct(w, pt, w_cross_pt);
const T tmp = DotProduct(w, pt) * (T(1.0) - costheta);
// (w[0] * pt[0] + w[1] * pt[1] + w[2] * pt[2]) * (T(1.0) - costheta);
result[0] = pt[0] * costheta + w_cross_pt[0] * sintheta + w[0] * tmp;
result[1] = pt[1] * costheta + w_cross_pt[1] * sintheta + w[1] * tmp;
result[2] = pt[2] * costheta + w_cross_pt[2] * sintheta + w[2] * tmp;
} else {
// Near zero, the first order Taylor approximation of the rotation
// matrix R corresponding to a vector w and angle w is
//
// R = I + hat(w) * sin(theta)
//
// But sintheta ~ theta and theta * w = angle_axis, which gives us
//
// R = I + hat(w)
//
// and actually performing multiplication with the point pt, gives us
// R * pt = pt + w x pt.
//
// Switching to the Taylor expansion near zero provides meaningful
// derivatives when evaluated using Jets.
//
// Explicitly inlined evaluation of the cross product for
// performance reasons.
/*const T w_cross_pt[3] = { angle_axis[1] * pt[2] - angle_axis[2] * pt[1],
angle_axis[2] * pt[0] - angle_axis[0] * pt[2],
angle_axis[0] * pt[1] - angle_axis[1] * pt[0] };*/
T w_cross_pt[3];
CrossProduct(angle_axis, pt, w_cross_pt);
result[0] = pt[0] + w_cross_pt[0];
result[1] = pt[1] + w_cross_pt[1];
result[2] = pt[2] + w_cross_pt[2];
}
}
#endif // rotation.h