视觉SLAM位姿估计

在这里插入图片描述
相关代码:https://github.com/cggos/slamkit

2D-2D: Epipolar Geometry

2D-2D相机位姿估计 通常利用 对极几何 进行计算,是单目SLAM初始化时的关键技术。
2D-2D 对极几何 主要涉及到基础矩阵、本质矩阵和单应性矩阵的求解,并从中恢复出旋转矩阵 和平移向量 。
同时,还要根据匹配的特征点和计算出的相对位姿进行三角化,恢复出 3D空间点。
在单目视觉SLAM中,以上过程主要用于SLAM的初始化:计算第二关键帧的相对位姿(假设第一帧位姿为 ),并计算初始Map。
在这里插入图片描述
(1)计算基础矩阵或本质矩阵适用于特征点不共面的情况,计算单应矩阵适用于特征点共面的情况。
(2)当特征点共面 或者 相机发生纯旋转 时,基础矩阵 的自由度下降,就会出现所谓的退化(degenerate)。为了能够避免退化现象的影响,通常会 同时估计基础矩阵 F和 单应矩阵H,选择重投影误差比较小的那个作为最终的运动估计矩阵。
(3)平移向量t 的尺度不确定性。
(4)初始化的纯旋转问题:单目初始化不能只有旋转,必须要有一定程度的平移,否则由于t趋近于0,导致无从求解R或者误差非常大。
(5)多于8对点:RANSAC

OpenCV 中相关函数:

findFundamentalMat
findEssentialMat
findHomography
recoverPose
decomposeEssentialMat
triangulatePoints
相关参考代码:

vector<Point2f> pts1;
vector<Point2f> pts2;
for ( int i = 0; i < ( int ) matches.size(); i++ ) {
    pts1.push_back(keypoints_1[matches[i].queryIdx].pt);
    pts2.push_back(keypoints_2[matches[i].trainIdx].pt);
}
Mat matF = findFundamentalMat(pts1, pts2, CV_FM_8POINT);
double cx = K.at<double>(0,2);
double cy = K.at<double>(1,2);
double fx = K.at<double>(0,0);
Mat matE = findEssentialMat(pts1, pts2, fx, Point2d(cx,cy));
Mat matH = findHomography(pts1, pts2, RANSAC, 3);
recoverPose(matE, pts1, pts2, R, t, fx, Point2d(cx,cy));
3D-2D: PnP

PnP(Perspective-n-Point) 是求解3D到2D点对运动的方法,求解PnP问题目前主要有直接线性变换(DLT)、P3P、EPnP、UPnP以及非线性优化方法。
在双目或RGB-D的视觉里程计中,可以直接使用PnP估计相机运动;而在单目视觉里程计中,必须先进行初始化,然后才能使用PnP。
在SLAM中,通常先使用 P3P或EPnP 等方法估计相机位姿,再构建最小二乘优化问题对估计值进行调整(BA)。

DLT

直接构建一个12个未知数的[R|t]增广矩阵(先不考虑旋转矩阵的自由度只有3),取六个点对,去求解12个未知数(每一个3D点到归一化平面的映射给出两个约束),最后将[R|t]左侧3x3矩阵块进行QR分解,用一个旋转矩阵去近似(将3x3矩阵空间投影到SE(3)流形上)。

P3P

通过3对3D/2D匹配点,得到A、B、C在相机坐标系下的3D坐标;然后,扥局3D-3D的点对,计算相机运动的R和t。

EPnP

需要4对不共面的(对于共面的情况只需要3对)3D-2D匹配点,是目前最有效的PnP求解方法。

Motion Only BA(非线性优化)

把 PnP问题 构建成一个定义于李代数上的非线性最小二乘问题,求解最好的相机位姿。
定义 残差(观测值-预测值)或 重投影误差
在这里插入图片描述
构建最小二乘问题

OpenCV 中相关函数:

solvePnP
Rodrigues
相关参考代码:

vector<KeyPoint> kpts_1, kpts_2;
vector<DMatch> matches;
find_feature_matches ( img_1, img_2, kpts_1, kpts_2, matches );

vector<Point3f> pts_3d;
vector<Point2f> pts_2d;
for ( DMatch m : matches ) {
    int x1 = int(kpts_1[m.queryIdx].pt.x);
    int y1 = int(kpts_1[m.queryIdx].pt.y);
    ushort d = img_d.ptr<unsigned short>(y1)[x1];
    if (d == 0)   // bad depth
        continue;
    float dd = d / depth_scale;

    Point2d p1 = pixel2cam(kpts_1[m.queryIdx].pt, K);

    pts_3d.push_back(Point3f(p1.x * dd, p1.y * dd, dd));
    pts_2d.push_back(kpts_2[m.trainIdx].pt);
}

Mat om, t;
solvePnP ( pts_3d, pts_2d, K, Mat(), om, t, false, SOLVEPNP_EPNP );
Mat R;
cv::Rodrigues ( om, R ); // rotation vector to rotation matrix

bundle_adjustment_3d2d ( pts_3d, pts_2d, K, R, t );
3D-3D: ICP

对于3D-3D的位姿估计问题可以用 ICP(Iterative Closest Point) 求解,其求解方式分为两种:
线性代数方式(主要是 SVD)
非线性优化方式(类似于BA)

线性代数方式

根据ICP问题,建立第i对点的误差项
在这里插入图片描述
构建最小二乘问题,求使误差平方和达到极小的R、t
在这里插入图片描述
对目标函数处理,最终为
在这里插入图片描述
根据上式,可以先求解R,再求解t
在这里插入图片描述
相关参考代码:

cv::Point3f p1, p2;
int N = pts1.size();
for (int i = 0; i < N; i++) {
    p1 += pts1[i];
    p2 += pts2[i];
}
p1 /= N;
p2 /= N;

std::vector<cv::Point3f> q1(N), q2(N);
for (int i = 0; i < N; i++) {
    q1[i] = pts1[i] - p1;
    q2[i] = pts2[i] - p2;
}

Eigen::Matrix3d W = Eigen::Matrix3d::Zero();
for (int i = 0; i < N; i++) {
    Eigen::Vector3d v3q1 = Eigen::Vector3d(q1[i].x, q1[i].y, q1[i].z);
    Eigen::Vector3d v3q2 = Eigen::Vector3d(q2[i].x, q2[i].y, q2[i].z);
    W += v3q1 * v3q2.transpose();
}

double determinantW =
 W(0,0)*W(1,1)*W(2,2) + W(0,1)*W(1,2)*W(2,0) + W(0,2)*W(1,0)*W(2,1) -
(W(0,0)*W(1,2)*W(2,1) + W(0,1)*W(1,0)*W(2,2) + W(0,2)*W(1,1)*W(2,0));

assert(determinantW>1e-8);

// SVD on W
Eigen::JacobiSVD<Eigen::Matrix3d> svd(W, Eigen::ComputeFullU | Eigen::ComputeFullV);
Eigen::Matrix3d U = svd.matrixU();
Eigen::Matrix3d V = svd.matrixV();

R = U * (V.transpose());
t = Eigen::Vector3d(p1.x, p1.y, p1.z) - R * Eigen::Vector3d(p2.x, p2.y, p2.z);
Optical Flow

光流是一种描述像素随时间在图像之间运动的方法,计算部分像素的称为 稀疏光流,计算所有像素的称为 稠密光流。稀疏光流以 Lucas-Kanade光流 为代表,并可以在SLAM中用于跟踪特征点位置。
在这里插入图片描述

LK光流

灰度不变假设
在这里插入图片描述
对左边一阶泰勒展开
在这里插入图片描述

在这里插入图片描述
整理得
在这里插入图片描述
简写为
在这里插入图片描述

在这里插入图片描述
在LK光流中,假设某个窗口(w x w)内的像素具有相同的运动
在这里插入图片描述
简写为
在这里插入图片描述
计算
在这里插入图片描述
从而得到图像间的运动速度或者某块像素的位置。

OpenCV 中相关函数:

calcOpticalFlowPyrLK
相关参考代码:

for (int index = 0; index < count; index++) {
    color = colorImgs[index];
    depth = depthImgs[index];

    if (index == 0) {
        vector<cv::KeyPoint> kps;
        cv::Ptr<cv::FastFeatureDetector> detector =
         cv::FastFeatureDetector::create();
        detector->detect(color, kps);
        for (auto kp:kps)
            keypoints.push_back(kp.pt);
        last_color = color;
        continue;
    }
    if (color.data == nullptr || depth.data == nullptr)
        continue;

    vector<cv::Point2f> next_keypoints;
    vector<cv::Point2f> prev_keypoints;
    for (auto kp:keypoints)
        prev_keypoints.push_back(kp);

    vector<unsigned char> status;
    vector<float> error;
    cv::calcOpticalFlowPyrLK(
      last_color, color, prev_keypoints, next_keypoints, status, error);

    int i = 0;
    for (auto iter = keypoints.begin(); iter != keypoints.end(); i++) {
        if (status[i] == 0) {
            iter = keypoints.erase(iter);
            continue;
        }
        * iter = next_keypoints[i];
        iter++;
    }

    last_color = color;
}
Direct Method

根据使用像素的数量,直接法分为以下三种
(1)稀疏直接法:使用稀疏关键点,不计算描述子
(2)半稠密直接法:只使用带有梯度的像素点,舍弃像素梯度不明显的地方
(3)稠密直接法:使用所有像素
利用直接法计算相机位姿,建立优化问题时,最小化的是 光度误差(Photometric Error)
在这里插入图片描述
构建最小二乘问题
在这里插入图片描述
更具体地,稀疏直接法
在这里插入图片描述
相关参考代码:

bool pose_estimation_direct(
        const vector<Measurement>& measurements,
        cv::Mat* gray, Eigen::Matrix3f& K, Eigen::Isometry3d& Tcw) {

    typedef g2o::BlockSolver<g2o::BlockSolverTraits<6, 1>> DirectBlock;
    DirectBlock::LinearSolverType * linearSolver =
    new g2o::LinearSolverDense<DirectBlock::PoseMatrixType>();
    DirectBlock * solver_ptr = new DirectBlock(linearSolver);

    g2o::OptimizationAlgorithmLevenberg * solver =
    new g2o::OptimizationAlgorithmLevenberg(solver_ptr);

    g2o::SparseOptimizer optimizer;
    optimizer.setAlgorithm(solver);
    optimizer.setVerbose(true);

    g2o::VertexSE3Expmap * pose = new g2o::VertexSE3Expmap();
    pose->setEstimate(g2o::SE3Quat(Tcw.rotation(), Tcw.translation()));
    pose->setId(0);
    optimizer.addVertex(pose);

    int id = 1;
    for (Measurement m: measurements) {
        EdgeSE3ProjectDirect * edge =
          new EdgeSE3ProjectDirect(
            m.pos_world, K(0, 0), K(1, 1), K(0, 2), K(1, 2), gray);
        edge->setVertex(0, pose);
        edge->setMeasurement(m.grayscale);
        edge->setInformation(Eigen::Matrix<double, 1, 1>::Identity());
        edge->setId(id++);
        optimizer.addEdge(edge);
    }

    optimizer.initializeOptimization();
    optimizer.optimize(30);
    Tcw = pose->estimate();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

和风细动帘帷暖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值