视觉前端的基本处理及代码

在视惯初始化的时候,判断imu是否有足够的激励,通过计算平均加速度、标准差来进行

AX=0 构成超定方程,可以通过SVD进行求解;也可以是AX=B–>A’AX=A’B 进行ldlt进行求解。
在这里插入图片描述在这里插入图片描述

计算视差角

https://www.cnblogs.com/cmt/p/14580194.html?from=https%3A%2F%2Fwww.cnblogs.com%2Fxuyi911204%2Fp%2F13154612.html&blogId=507077&postId=13154612

判断是不是纯旋转约束:

在这里插入图片描述初始化的第一帧的标准
在滑窗(0-9)中找到第一个满足要求的帧(第l帧),它与最新一帧(frame_count=10)有足够的共视点和平行度,并求出这两帧之间的相对位置变化关系 (1)定义容器

opencv中封装的关于slam位姿解算过程中用到的函数

cv::findFundamentalMat

https://blog.csdn.net/u011867581/article/details/24051885
Mat points1和Mat points2 32F

cv::recoverPose

bool MotionEstimator::solveRelativeRT(const vector<pair<Vector3d, Vector3d>> &corres, Matrix3d &Rotation, Vector3d &Translation)

{

    if (corres.size() >= 15)

    {

        vector<cv::Point2f> ll, rr;

        for (int i = 0; i < int(corres.size()); i++)

        {

            ll.push_back(cv::Point2f(corres[i].first(0), corres[i].first(1)));

            rr.push_back(cv::Point2f(corres[i].second(0), corres[i].second(1)));

        }

        cv::Mat mask;            //因为这里的ll,rr是归一化坐标,所以得到的是本质矩阵

        cv::Mat E = cv::findFundamentalMat(ll, rr, cv::FM_RANSAC, 0.3 / 460, 0.99, mask);
//得到的是ll向rr旋转的矩阵
        cv::Mat cameraMatrix = (cv::Mat_<double>(3, 3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);

        cv::Mat rot, trans;

        int inlier_cnt = cv::recoverPose(E, ll, rr, cameraMatrix, rot, trans, mask);

        //cout << "inlier_cnt " << inlier_cnt << endl;

 //下面这段是cv::Mat向eigen的转化

        Eigen::Matrix3d R;                             

        Eigen::Vector3d T;

        for (int i = 0; i < 3; i++)

        {   

            T(i) = trans.at<double>(i, 0);

            for (int j = 0; j < 3; j++)

                R(i, j) = rot.at<double>(i, j);

        }

        Rotation = R.transpose();

        Translation = -R.transpose() * T;//从第一帧向最新帧旋转的矩阵

        if(inlier_cnt > 12)

            return true;

        else

            return false;

    }

    return false;

}
/**

 *  Mat cv::findFundamentalMat(  返回通过RANSAC算法求解两幅图像之间的本质矩阵E

 *      nputArray  points1,             第一幅图像点的数组

 *      InputArray  points2,            第二幅图像点的数组

 *      int     method = FM_RANSAC,     RANSAC 算法

 *      double  param1 = 3.,            点到对极线的最大距离,超过这个值的点将被舍弃

 *      double  param2 = 0.99,          矩阵正确的可信度

 *      OutputArray mask = noArray()    输出在计算过程中没有被舍弃的点

 *  ) 

 */  

/**

 *  int cv::recoverPose (   通过本质矩阵得到Rt,返回通过手性校验的内点个数

 *      InputArray  E,              本质矩阵

 *      InputArray  points1,        第一幅图像点的数组

 *      InputArray  points2,        第二幅图像点的数组

 *      InputArray  cameraMatrix,   相机内参

 *      OutputArray     R,          第一帧坐标系到第二帧坐标系的旋转矩阵

 *      OutputArray     t,          第一帧坐标系到第二帧坐标系的平移向量

 *      InputOutputArray    mask = noArray()  在findFundamentalMat()中没有被舍弃的点

 *  )  

 */ 

邻域内一致性检测

  • 方法一

注意dx、dy两个数组,对这两个数组的对应序列遍历就得到了前后两幅图像的8邻域灰度值检测。

if(state[i] != 0)
        {
            int dx[10] = { -1, 0, 1, -1, 0, 1, -1, 0, 1 };
            int dy[10] = { -1, -1, -1, 0, 0, 0, 1, 1, 1 };
            int x1 = prepoint[i].x, y1 = prepoint[i].y;
            int x2 = nextpoint[i].x, y2 = nextpoint[i].y;
            if ((x1 < limit_edge_corner || x1 >= imgray.cols - limit_edge_corner || x2 < limit_edge_corner || x2 >= imgray.cols - limit_edge_corner
            || y1 < limit_edge_corner || y1 >= imgray.rows - limit_edge_corner || y2 < limit_edge_corner || y2 >= imgray.rows - limit_edge_corner))
            {
                state[i] = 0;
                continue;
            }
            double sum_check = 0;
            for (int j = 0; j < 9; j++)
                sum_check += abs(imGrayPre.at<uchar>(y1 + dy[j], x1 + dx[j]) - imgray.at<uchar>(y2 + dy[j], x2 + dx[j]));//这里有部分的一致性检测,一个邻域范围内的检测
            if (sum_check > limit_of_check) state[i] = 0;
            if (state[i])
            {
                F_prepoint.push_back(prepoint[i]);
                F_nextpoint.push_back(nextpoint[i]);
            }
        }

[点击并拖拽以移动]
  • 方法二
    Dyna slam geometry.cpp line362
    关于邻域检测,也有先记录相对中心点周围数据的序列号,然后用中心点的序例号加上相对序列号获得周围的点坐标。
 int x = (int)matCurrentFrame.at<float>(i,0) + (int)u1.at<float>(j,0);
 int y = (int)matCurrentFrame.at<float>(i,1) + (int)u1.at<float>(j,1);
 float _d = currentFrame.mImDepth.at<float>(y,x);
 // 看点周围一个patch的深度是否>0并且比中心点的深度小
 if ((_d > 0) && (_d < matProjDepth.at<float>(i,0)))
	 {
         // 符合条件就存储这个深度并且计算深度
         _matDepth.at<float>(s,0) = _d;
         _matDiffDepth.at<float>(s,0) = matProjDepth.at<float>(i,0) - _d; // 存储这个点的深度-这个patch点的深度
         s++;
                } 				
 

而u1中存放的就是

for (int i(-mDmax); i <= mDmax; i++){
	for (int j(-mDmax); j <= mDmax; j++){
	u1.at<float>(m,0) = i;
	u1.at<float>(m,1) = j;
    m++; // 存储一个patch的x,y坐标
    }
    }

求取图像patch块的方差

可以把要比较的点放在一个方阵内,然后根据序列号求取出一个patch,利用std标准库里的东西进行方差的求解。

  				int xIni = (int)matCurrentFrame.at<float>(i,0) - mDmax;
                int yIni = (int)matCurrentFrame.at<float>(i,1) - mDmax;
                int xEnd = (int)matCurrentFrame.at<float>(i,0) + mDmax + 1;
                int yEnd = (int)matCurrentFrame.at<float>(i,1) + mDmax + 1;
                cv::Mat patch = currentFrame.mImDepth.rowRange(yIni,yEnd).colRange(xIni,xEnd);
                cv::Mat mean, stddev;
                // 计算patch内深度的均值和方差
                // 对应论文中【如果关键点被设置为动态,但是它周围深度地图具有较大方差,
                // 我们改变编号为静态。】的部分
                cv::meanStdDev(patch,mean,stddev);

光流跟踪并进行一致性检验

    cv::goodFeaturesToTrack(imGrayPre, prepoint, 1000, 0.01, 8, cv::Mat(), 3, true, 0.04);
    cv::cornerSubPix(imGrayPre, prepoint, cv::Size(10, 10), cv::Size(-1, -1), cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03));
	cv::calcOpticalFlowPyrLK(imGrayPre, imgray, prepoint, nextpoint, state, err, cv::Size(22, 22), 5, cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.01));

	for (int i = 0; i < state.size(); i++)
    {
        if(state[i] != 0)
        {
            int dx[10] = { -1, 0, 1, -1, 0, 1, -1, 0, 1 };
            int dy[10] = { -1, -1, -1, 0, 0, 0, 1, 1, 1 };
            int x1 = prepoint[i].x, y1 = prepoint[i].y;
            int x2 = nextpoint[i].x, y2 = nextpoint[i].y;
            if ((x1 < limit_edge_corner || x1 >= imgray.cols - limit_edge_corner || x2 < limit_edge_corner || x2 >= imgray.cols - limit_edge_corner
            || y1 < limit_edge_corner || y1 >= imgray.rows - limit_edge_corner || y2 < limit_edge_corner || y2 >= imgray.rows - limit_edge_corner))
            {
                state[i] = 0;
                continue;
            }
            double sum_check = 0;
            for (int j = 0; j < 9; j++)
                sum_check += abs(imGrayPre.at<uchar>(y1 + dy[j], x1 + dx[j]) - imgray.at<uchar>(y2 + dy[j], x2 + dx[j]));//这里有部分的一致性检测,一个邻域范围内的检测
            if (sum_check > limit_of_check) state[i] = 0;
            if (state[i])
            {
                F_prepoint.push_back(prepoint[i]);
                F_nextpoint.push_back(nextpoint[i]);
            }
        }
    }
    // F-Matrix
    cv::Mat mask = cv::Mat(cv::Size(1, 300), CV_8UC1);
    cv::Mat F = cv::findFundamentalMat(F_prepoint, F_nextpoint, mask, cv::FM_RANSAC, 0.1, 0.99);//这里的F_prepoint是2维的,也就是像素坐标系的坐标,求出来的是F
    for (int i = 0; i < mask.rows; i++)
    {
        if (mask.at<uchar>(i, 0) == 0);
        else  //经过ransac是内点,但是没有经过F阵进行检验
        {
            // Circle(pre_frame, F_prepoint[i], 6, Scalar(255, 255, 0), 3);
            double A = F.at<double>(0, 0)*F_prepoint[i].x + F.at<double>(0, 1)*F_prepoint[i].y + F.at<double>(0, 2);
            double B = F.at<double>(1, 0)*F_prepoint[i].x + F.at<double>(1, 1)*F_prepoint[i].y + F.at<double>(1, 2);
            double C = F.at<double>(2, 0)*F_prepoint[i].x + F.at<double>(2, 1)*F_prepoint[i].y + F.at<double>(2, 2);
            double dd = fabs(A*F_nextpoint[i].x + B*F_nextpoint[i].y + C) / sqrt(A*A + B*B); //Epipolar constraints
            if (dd <= 0.1)// 这里的dd也不过是一个经验值而已
            {
                F2_prepoint.push_back(F_prepoint[i]);
                F2_nextpoint.push_back(F_nextpoint[i]);
            }
        }
    }
    F_prepoint = F2_prepoint;
    F_nextpoint = F2_nextpoint;

    for (int i = 0; i < prepoint.size(); i++)
    {
        if (state[i] != 0)//对光流正常跟踪上的点进行F检验
        {
            double A = F.at<double>(0, 0)*prepoint[i].x + F.at<double>(0, 1)*prepoint[i].y + F.at<double>(0, 2);
            double B = F.at<double>(1, 0)*prepoint[i].x + F.at<double>(1, 1)*prepoint[i].y + F.at<double>(1, 2);
            double C = F.at<double>(2, 0)*prepoint[i].x + F.at<double>(2, 1)*prepoint[i].y + F.at<double>(2, 2);
            double dd = fabs(A*nextpoint[i].x + B*nextpoint[i].y + C) / sqrt(A*A + B*B);

            // Judge outliers
            if (dd <= limit_dis_epi) continue;
            T_M.push_back(nextpoint[i]);
        }
    }

三角化

在这里插入图片描述注意推导对极几何、平面几何、三角测量等,需要知道哪些等式来源于哪里。
P2=RP1+t 空间刚体变换 ,所以是P1、P2是三维坐标;
sx=KP 这是由相机内参构成的,x是像素点坐标,s是空间三维点z向的值
s2x2=Rs1x1+t
推导对极几何的时候用归一化相机坐标比较容易,再转换到像素点中;
三角测量也是从相机归一化坐标系入手进行解决。
x1,x2是像素点的归一化平面坐标,也就是用三维齐次坐标表示图像上的像素坐标, 同时除以第三维的话也就是归一化坐标(这个第三项也是空间点的z向值);同时K的逆乘上x1也就是归一化相机平面上(z值为1)的坐标,

for ( DMatch m:matches )
{
// 将像素坐标转换至相机坐标
pts_1.push_back ( pixel2cam( keypoint_1[m.queryIdx].pt, K) );
pts_2.push_back ( pixel2cam( keypoint_2[m.trainIdx].pt, K) );//先转换到相机系
}
 
Mat pts_4d;
cv::triangulatePoints( T1, T2, pts_1, pts_2, pts_4d );//三角化pts_4d是四维的,要将这四维同时除以第四维,得到空间三维点坐标

三角化操作中涉及的AX=0

SVD求最小二乘解
T是l帧到各个帧的转换(因为所有位姿和特征点目前都是在l帧表示的),仅仅是关系变换矩阵而不是位姿矩阵
所以下面求解出来的点的坐标也是在l帧参考下的坐标
特别要注意opencv求解出来的位姿矩阵、特征点都是谁相对于谁的
在这里插入图片描述
这里的r1、r2、r3是代表的行向量的意思

在这里插入图片描述x向量是四维了,解出来的值最后一个数需要确保它是1

triangulatePoint()
void GlobalSFM::triangulatePoint(Eigen::Matrix<double, 3, 4> &Pose0, Eigen::Matrix<double, 3, 4> &Pose1,

                        Vector2d &point0, Vector2d &point1, Vector3d &point_3d)

{//https://blog.csdn.net/jsf921942722/article/details/95511167 

    Matrix4d design_matrix = Matrix4d::Zero();

    design_matrix.row(0) = point0[0] * Pose0.row(2) - Pose0.row(0);

    design_matrix.row(1) = point0[1] * Pose0.row(2) - Pose0.row(1);

    design_matrix.row(2) = point1[0] * Pose1.row(2) - Pose1.row(0);

    design_matrix.row(3) = point1[1] * Pose1.row(2) - Pose1.row(1);

    Vector4d triangulated_point;

    triangulated_point =

              design_matrix.jacobiSvd(Eigen::ComputeFullV).matrixV().rightCols<1>();

    point_3d(0) = triangulated_point(0) / triangulated_point(3);

    point_3d(1) = triangulated_point(1) / triangulated_point(3);

    point_3d(2) = triangulated_point(2) / triangulated_point(3);

}

一般来讲,求位姿,2D-2D对极几何只是在第一次使用,也就是没有3D特征点坐标的时候使用,一旦有了特征点,之后都会用3D-2D的方式求位姿。然后会进入PnP求新位姿,然后三角化求新3D坐标的循环中。

pnp求解

   cv::Mat r, rvec, t, D, tmp_r;

    cv::eigen2cv(R_initial, tmp_r);//转换成solvePnP能处理的格式

    cv::Rodrigues(tmp_r, rvec);

    cv::eigen2cv(P_initial, t);

    cv::Mat K = (cv::Mat_<double>(3, 3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
//注意这里K取单位阵 写代码的时候要特别注意
    bool pnp_succ;

    pnp_succ = cv::solvePnP(pts_3_vector, pts_2_vector, K, D, rvec, t, 1);//得到了第i帧到第l帧的旋转平移(以l帧为参考)  pts_3_vector是l帧下的坐标,pts_2_vector是i帧下归一化相机坐标的值

    if(!pnp_succ)

    {

        return false;

    }

    cv::Rodrigues(rvec, r);//罗德里格斯公式将旋转矩阵转换成旋转向量

    //cout << "r " << endl << r << endl;

    MatrixXd R_pnp;

    cv::cv2eigen(r, R_pnp);//转换成原有格式

    MatrixXd T_pnp;

    cv::cv2eigen(t, T_pnp);

    R_initial = R_pnp;//覆盖原先的旋转平移

    P_initial = T_pnp;

    return true;

特别要注意opencv求解出来的位姿矩阵、特征点都是谁相对于谁的

pnp_succ = cv::solvePnP(pts_3_vector, pts_2_vector, K, D, rvec, t, 1);//得到了第i帧到第l帧的旋转平移(以l帧为参考)  pts_3_vector是l帧下的坐标,pts_2_vector是i帧下归一化相机坐标的值

关于sfm的一些处理技巧

以10帧大小滑窗,找到一个第l帧,使得第l帧和最新帧间的视差角大的那个(越靠近最新帧的话,视差会变小 if(average_parallax * 460 > 30 && m_estimator.solveRelativeRT(corres, relative_R, relative_T)) { //solveRelativeRT()通过基础矩阵计算当前帧与第l帧之间的R和T,并判断内点数目是否足够),利用匹配上的特征点的范数(vector.norm)进行判断,先以第l帧为参考,通过2D-2D特征点找到本质矩阵,解算出相对位姿,并进行三角化。对l+1、l+2、l+3到最新帧,和前一帧进行3D-2D的匹配解算相对矩阵。对于在sliding window里在第l帧之后的每一帧,分别都和前一帧用PnP求它的位姿,得到位姿后再和最新一帧三角化得到它们共视点的3D坐标 。从第l+1帧到滑窗的最后的每一帧再与第l帧进行三角化补充3D坐标 。第一1、2、到l-1帧,后一帧进行3D-2D的pnp矩阵。得到位姿后再和第l帧三角化得到它们共视点的3D坐标 。三角化其他未恢复的特征点 至此得到了滑动窗口中所有图像帧的位姿以及特征点的3D坐标。

对于滑窗内的帧,把它们设为关键帧,并获得它们对应的IMU坐标系到l系的旋转平移
对滑窗外的所有帧,求出它们对应的IMU坐标系到l帧的旋转平移

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值