3d-2d,使用PnP和g2o非线性优化解决

#include < iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <Eigen/Core>
#include <g2o/core/base_vertex.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/sparse_optimizer.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/solver.h>
#include <g2o/core/optimization_algorithm_gauss_newton.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
#include <sophus/se3.hpp>
#include < chrono>

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);

// BA by g2o
typedef vector<Eigen::Vector2d, Eigen::aligned_allocator< Eigen::Vector2d >> VecVector2d;//VecVector存储2d坐标的vector
typedef vector<Eigen::Vector3d, Eigen::aligned_allocator< Eigen::Vector3d >> VecVector3d;

void bundleAdjustmentG2O(
const VecVector3d &points_3d,
const VecVector2d &points_2d,
const Mat &K,
Sophus::SE3d &pose
);

// BA by gauss-newton
void bundleAdjustmentGaussNewton(
const VecVector3d &points_3d,
const VecVector2d &points_2d,
const Mat &K,
Sophus::SE3d &pose
);

int main() {

//-- 读取图像
Mat img_1 = imread("/home/hotfinda/example_slambook/slambook2-master/ch7/1.png", IMREAD_COLOR);
Mat img_2 = imread("/home/hotfinda/example_slambook/slambook2-master/ch7/2.png", IMREAD_COLOR);
assert(img_1.data && img_2.data && “Can not load images!”);

vector< KeyPoint> keypoints_1, keypoints_2;
vector< DMatch> matches;
find_feature_matches(img_1, img_2, keypoints_1, keypoints_2, matches);先建立两组匹配的2d图片,之后把其中一张图片的特征点转换为3d的坐标建立要处理的问题
cout << “一共找到了” << matches.size() << “组匹配点” << endl;

// 建立3D点
Mat d1 = imread("/home/hotfinda/example_slambook/slambook2-master/ch7/1_depth.png", -1); // 深度图为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 / 5000.0;
Point2d p1 = pixel2cam(keypoints_1[m.queryIdx].pt, K);
pts_3d.push_back(Point3f(p1.x * dd, p1.y * dd, dd));存储p1归一化坐标*深度dd
pts_2d.push_back(keypoints_2[m.trainIdx].pt);存储p2图片坐标
}

cout << "3d-2d pairs: " << pts_3d.size() << endl;

chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
Mat r, t;
solvePnP(pts_3d, pts_2d, K, Mat(), r, t, false); // 调用OpenCV 的 PnP 求解,可选择EPNP,DLS等方法,这里p1的相机坐标系相对于p2来说就是世界坐标系
Mat R;
cv::Rodrigues(r, R); // r为旋转向量形式,所以用Rodrigues公式转换为矩阵R
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration< double> time_used = chrono::duration_cast<chrono::duration>(t2 - t1);
cout << “solve pnp in opencv cost time: " << time_used.count() << " seconds.” << endl;

cout << “R=” << endl << R << endl;
cout << “t=” << endl << t << endl;

//建立非线性优化初始条件,pts_3d_eigen存储世界坐标,pts_2d_eigen存储图片坐标,此时的相机坐标系是p2, 世界坐标系是p1
VecVector3d pts_3d_eigen;
VecVector2d pts_2d_eigen;
for (size_t i = 0; i < pts_3d.size(); ++i) {
pts_3d_eigen.push_back(Eigen::Vector3d(pts_3d[i].x, pts_3d[i].y, pts_3d[i].z));
pts_2d_eigen.push_back(Eigen::Vector2d(pts_2d[i].x, pts_2d[i].y));
}

cout << “calling bundle adjustment by gauss newton” << endl;
Sophus::SE3d pose_gn//存放gaussNewton位姿结果;
t1 = chrono::steady_clock::now();
bundleAdjustmentGaussNewton(pts_3d_eigen, pts_2d_eigen, K, pose_gn);
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration< double>>(t2 - t1);
cout << “solve pnp by gauss newton cost time: " << time_used.count() << " seconds.” << endl;

cout << “calling bundle adjustment by g2o” << endl;
Sophus::SE3d pose_g2o //存放g20位姿结果;
t1 = chrono::steady_clock::now();
bundleAdjustmentG2O(pts_3d_eigen, pts_2d_eigen, K, pose_g2o);
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration>(t2 - t1);
cout << “solve pnp by g2o cost time: " << time_used.count() << " seconds.” << endl;
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();

//-- 第一步:检测 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(0, 2)) / K.at(0, 0),
(p.y - K.at(1, 2)) / K.at(1, 1)
);
}

void bundleAdjustmentGaussNewton(
const VecVector3d &points_3d,
const VecVector2d &points_2d,
const Mat &K,
Sophus::SE3d &pose) {
typedef Eigen::Matrix<double, 6, 1> Vector6d;
const int iterations = 10;
double cost = 0, lastCost = 0;
double fx = K.at< double>(0, 0);
double fy = K.at< double>(1, 1);
double cx = K.at< double>(0, 2);
double cy = K.at< double>(1, 2);

for (int iter = 0; iter < iterations; iter++) {
Eigen::Matrix<double, 6, 6> H = Eigen::Matrix<double, 6, 6>::Zero();
Vector6d b = Vector6d::Zero();准备Hx=g
//
cost = 0;
// compute cost
for (int i = 0; i < points_3d.size(); i++) 由于有很多3d点,每次迭代要把他们都考虑到内
{
Eigen::Vector3d pc = pose * points_3d[i]; 世界坐标转为相机坐标Pc=T*Pw
double inv_z = 1.0 / pc[2];
double inv_z2 = inv_z * inv_z;
Eigen::Vector2d proj(fx * pc[0] / pc[2] + cx, fy * pc[1] / pc[2] + cy);相机坐标Pc转为像素坐标【u,v]
//
Eigen::Vector2d e = points_2d[i] - proj;误差函数
//
cost += e.squaredNorm();
Eigen::Matrix<double, 2, 6> J;利用K写出雅克比矩阵J
J << -fx * inv_z,
0,
fx * pc[0] * inv_z2,
fx * pc[0] * pc[1] * inv_z2,
-fx - fx * pc[0] * pc[0] * inv_z2,
fx * pc[1] * inv_z,
0,
-fy * inv_z,
fy * pc[1] * inv_z2,
fy + fy * pc[1] * pc[1] * inv_z2,
-fy * pc[0] * pc[1] * inv_z2,
-fy * pc[0] * inv_z;
//
H += J.transpose() * J; 定义H和b
b += -J.transpose() * e;
}
//
Vector6d dx;
dx = H.ldlt().solve(b);解出dx
//
if (isnan(dx[0])) {
cout << “result is nan!” << endl;
break;
}
//
if (iter > 0 && cost >= lastCost) {
// cost increase, update is not good
cout << "cost: " << cost << ", last cost: " << lastCost << endl;
break;
}
//
// update your estimation
**pose = Sophus::SE3d::exp(dx) * pose;**更新位姿T
lastCost = cost;注意cost在每次迭代时都重新置0,加上之前的判断语句 if (iter > 0 && cost >= lastCost) 使得cost一旦增加就立即停止迭代
//
cout << “iteration " << iter << " cost=” << std::setprecision(12) << cost << endl;
if (dx.norm() < 1e-6)dx足够小停止迭代
{
// converge
break;
}
}

cout << “pose by g-n: \n” << pose.matrix() << endl;
}

/// vertex and edges used in g2o ba
//定义顶点(待优化变量,_estimate)属性,变量维度和类型,四大基本函数(重置函数,更新函数:定义dx如何更新_estimate,存盘和读盘函数:留空)
class VertexPose : public g2o::BaseVertex<6, Sophus::SE3d> {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

virtual void setToOriginImpl() override重置函数,没有参数 {
_estimate = Sophus::SE3d();
}

/// left multiplication on SE3
virtual void oplusImpl(const double *update) override更新函数,参数为update {
Eigen::Matrix<double, 6, 1> update_eigen;
update_eigen << update[0], update[1], update[2], update[3], update[4], update[5];
_estimate = Sophus::SE3d::exp(update_eigen) * _estimate;_estimate更新公式
}

virtual bool read(istream &in) override {}

virtual bool write(ostream &out) const override {}
};
//定义边(观测值ui,_measurement)属性,观测值维度和类型,连接顶点的类型;初始化参数;四大基本函数(误差函数,雅克比函数,存盘和读盘函数:留空)

class EdgeProjection : public g2o::BaseUnaryEdge<2, Eigen::Vector2d, VertexPose> {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
初始化参数
EdgeProjection(const Eigen::Vector3d &pos, const Eigen::Matrix3d &K) : _pos3d(pos), _K(K) {}

virtual void computeError() override误差函数 {
首先获得一个初始待优化变量T的值
const VertexPose *v = static_cast<VertexPose *> (_vertices[0]);
Sophus::SE3d T = v->estimate();
然后转为像素坐标,_measurment
Eigen::Vector3d pos_pixel = _K * (T * _pos3d);
pos_pixel /= pos_pixel[2];
_error = _measurement - pos_pixel.head<2>();
}

virtual void linearizeOplus() override雅克比函数 {
雅克比函数使用初始化的T
const VertexPose *v = static_cast<VertexPose *> (_vertices[0]);
Sophus::SE3d T = v->estimate();
Eigen::Vector3d pos_cam = T * _pos3d;
double fx = _K(0, 0);
double fy = _K(1, 1);
double cx = _K(0, 2);
double cy = _K(1, 2);
double X = pos_cam[0];
double Y = pos_cam[1];
double Z = pos_cam[2];
double Z2 = Z * Z;
_jacobianOplusXi
<< -fx / Z, 0, fx * X / Z2, fx * X * Y / Z2, -fx - fx * X * X / Z2, fx * Y / Z,
0, -fy / Z, fy * Y / (Z * Z), fy + fy * Y * Y / Z2, -fy * X * Y / Z2, -fy * X / Z;
}

virtual bool read(istream &in) override {}

virtual bool write(ostream &out) const override {}

private:
Eigen::Vector3d _pos3d;
Eigen::Matrix3d _K;
};

void bundleAdjustmentG2O(
const VecVector3d &points_3d,
const VecVector2d &points_2d,
const Mat &K,
Sophus::SE3d &pose) {

// 边和顶点定义完成,设定g2o要求解的结构。
首先根据定义的顶点和边来设定solver的待优化变量和观测值维度,定义为type
typedef g2o::BlockSolver<g2o::BlockSolverTraits<6, 3>> BlockSolverType; // pose is 6, landmark is 3
// solver的线性求解器类型
typedef g2o::LinearSolverDense< BlockSolverType::PoseMatrixType> LinearSolverType; // 线性求解器类型
// 梯度下降方法,使用上面定义的type和求解器类型,可以从GN, LM, DogLeg 中选
auto solver = new g2o::OptimizationAlgorithmGaussNewton(
g2o::make_unique< BlockSolverType>(g2o::make_unique< LinearSolverType>()));

g2o::SparseOptimizer optimizer; // 图模型
optimizer.setAlgorithm(solver); // 设置求解器
optimizer.setVerbose(true); // 打开调试输出

// 设置顶点vertex的ID为0和优化变量类型,将顶点添加到结构中
VertexPose *vertex_pose = new VertexPose(); // camera vertex_pose
vertex_pose->setId(0);
vertex_pose->setEstimate(Sophus::SE3d());
optimizer.addVertex(vertex_pose);

// K
Eigen::Matrix3d K_eigen;
K_eigen <<
K.at< double>(0, 0), K.at< double>(0, 1), K.at< double>(0, 2),
K.at< double>(1, 0), K.at< double>(1, 1), K.at< double>(1, 2),
K.at< double>(2, 0), K.at< double>(2, 1), K.at< double>(2, 2);

// edges
int index = 1;
有多少观测值,就有多少边的ID,设置每条边连接的顶点ID,设置观测值,将边添加到结构中
for (size_t i = 0; i < points_2d.size(); ++i) {
auto p2d = points_2d[i];
auto p3d = points_3d[i];
EdgeProjection *edge = new EdgeProjection(p3d, K_eigen);
edge->setId(index);
edge->setVertex(0, vertex_pose);
edge->setMeasurement(p2d);p2d赋值给_measurment,作为观测值
edge->setInformation(Eigen::Matrix2d::Identity());
optimizer.addEdge(edge);
index++;
}

chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
optimizer.setVerbose(true);
optimizer.initializeOptimization();
optimizer.optimize(10);
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 << “pose estimated by g2o =\n” << vertex_pose->estimate().matrix() << endl;
pose = vertex_pose->estimate();
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值