光流法计算PNP位姿

LK光流特征追踪计算PNP位姿

角点提取及光流追踪

        std::vector<cv::Point2f> prepoint, nextpoint;//前一帧角点,当前帧角点
        std::vector<float> err;
        std::vector<uchar> state;//追踪成功标志 追踪成功=1 失败=0

        cv::goodFeaturesToTrack(imlast, prepoint, 1000, 0.01, 8, cv::Mat(), 3, true, 0.04);//提取上一帧中的角点存在prepoint中
        cv::cornerSubPix(imlast, prepoint, cv::Size(10, 10), cv::Size(-1, -1), cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03));//亚像素提取
        cv::calcOpticalFlowPyrLK(imlast, imRGB, prepoint, nextpoint, state, err, cv::Size(22, 22), 5, 		cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.01));//根据prepointLK光流追踪 得到结果存放在nextpoint
		/*** 根据光流追踪的结果保存成功追踪的角点*/

        std::vector<cv::Point2f> match_pre,match_cur;//保存匹配成功的点

        int limit_edge_corner=5;//搜索边界
        int sta_label=0;
        for(int i=0;i<state.size();i++)
        {
            int x1 = prepoint[i].x, y1 = prepoint[i].y;
            int x2 = nextpoint[i].x, y2 = nextpoint[i].y;
            if ((x1 < limit_edge_corner || x1 >= imRGB.cols - limit_edge_corner || x2 < limit_edge_corner || x2 >= imRGB.cols - limit_edge_corner||
                 y1 < limit_edge_corner || y1 >= imRGB.rows - limit_edge_corner || y2 < limit_edge_corner || y2 >= imRGB.rows - limit_edge_corner))
                 //如果点的坐标在图像边界框内 舍弃掉
            {
                state[i] = 0;
                continue;
            }
            sta_label=(int)segMask.ptr<uchar>((int)nextpoint[i].y)[(int)nextpoint[i].x];//根据实例分割 判断点是否在先验动态Mask内
            //if(state[i]&&sta_label==0) {
            if(state[i]) {//追踪成功则保存到match数组内
                match_pre.push_back(prepoint[i]);
                match_cur.push_back(nextpoint[i]);
            }
        }

将提取的角点由2D坐标恢复为3D坐标

        ///pnp计算位姿
        int N=match_cur.size();
        // construct input
        std::vector<cv::Point2f> cur_2d(N);//当前帧的2D像素坐标
        std::vector<cv::Point3f> pre_3d(N);//前一帧的2D像素坐标
        std::vector<cv::Point3f> cur_3d(N);//世界坐标系下的3D点

        for(int i=0;i<N;i++) {
            float z1 = imDlast.at<float>(match_pre[i].y, match_pre[i].x);//前一帧的深度
            float z2 = imdepth.at<float>(match_cur[i].y, match_cur[i].x);//当前帧的深度
            if (z1 > 0 && z2 > 0) {//深度都为有效值 >=0
                float x = (match_pre[i].x - mCurrentFrame.cx) * z1 * mCurrentFrame.invfx;//将像素坐标转化为非归一化上一帧的相机坐标系
                float y = (match_pre[i].y - mCurrentFrame.cy) * z1 * mCurrentFrame.invfy;
                //恢复为上一帧中的相机坐标系坐标
                cv::Point3f tmp_3d;
                cv::Mat x3Dp = (cv::Mat_<float>(3, 1) << x, y, z1);
                ///这里得到的相机坐标为上一帧的相机坐标系下观测到的3D点坐标
                const cv::Mat Rlw = mLastFrame.mTcw.rowRange(0,3).colRange(0,3);
                const cv::Mat Rwl = Rlw.t();
                const cv::Mat tlw = mLastFrame.mTcw.rowRange(0,3).col(3);
                const cv::Mat twl = -Rlw.t()*tlw;
                //这里相当于求出了相机到世界的变换Twc,从而把上一帧中的相机坐标系3D点转换成了世界坐标系下的3D点 如果继续用TP 得到的是上一帧的3D在上一帧中的坐标系的点
                //x3Dp=Rwl*x3Dp+twl;
                x3Dp = mLastFrame.mRwc * x3Dp + mLastFrame.mOw;//这两行的转换是相同的
                tmp_3d.x = x3Dp.at<float>(0);
                tmp_3d.y = x3Dp.at<float>(1);
                tmp_3d.z = x3Dp.at<float>(2);
                pre_3d[i] = tmp_3d;

                cur_2d[i].x = match_cur[i].x;
                cur_2d[i].y = match_cur[i].y;
            }
        }

根据匹配好的3D点和2D点进行PNP位姿求解

     // output
            cv::Mat Rvec(3, 1, CV_64FC1);//旋转向量
            cv::Mat Tvec(3, 1, CV_64FC1);//平移向量
            cv::Mat d(3, 3, CV_64FC1);//旋转矩阵
            cv::Mat inliers;//内点数
        //Ransac优化
            int iter_num = 500;//迭代次数
            double reprojectionError = 0.4, confidence = 0.98; // 0.5 0.3 PNP中RANSAC判断内点的重投影误差阈值 小于0.4视为内点
            cv::Mat camera_mat(3, 3, CV_64FC1);//内参矩阵
            cv::Mat distCoeffs = cv::Mat::zeros(1, 4, CV_64FC1);
            camera_mat.at<double>(0, 0) = mCurrentFrame.mK.at<float>(0,0);
            camera_mat.at<double>(1, 1) = mCurrentFrame.mK.at<float>(1,1);
            camera_mat.at<double>(0, 2) = mCurrentFrame.mK.at<float>(0,2);
            camera_mat.at<double>(1, 2) = mCurrentFrame.mK.at<float>(1,2);
            camera_mat.at<double>(2, 2) = 1.0;

            cv::solvePnPRansac(pre_3d, cur_2d, camera_mat, distCoeffs, Rvec, Tvec, false,
                               iter_num, reprojectionError, confidence, inliers, cv::SOLVEPNP_P3P); // AP3P EPNP P3P ITERATIVE DLS
            cv::Rodrigues(Rvec, d);//罗德里格斯公式转换成旋转矩阵

重投影误差计算

            cv::Mat Mod = cv::Mat::eye(4,4,CV_32F);//初始估计模型 欧式变换矩阵T 包含旋转矩阵和平移向量
            // assign the result to current pose
            Mod.at<float>(0,0) = d.at<double>(0,0); Mod.at<float>(0,1) = d.at<double>(0,1); Mod.at<float>(0,2) = d.at<double>(0,2); Mod.at<float>(0,3) = Tvec.at<double>(0,0);
            Mod.at<float>(1,0) = d.at<double>(1,0); Mod.at<float>(1,1) = d.at<double>(1,1); Mod.at<float>(1,2) = d.at<double>(1,2); Mod.at<float>(1,3) = Tvec.at<double>(1,0);
            Mod.at<float>(2,0) = d.at<double>(2,0); Mod.at<float>(2,1) = d.at<double>(2,1); Mod.at<float>(2,2) = d.at<double>(2,2); Mod.at<float>(2,3) = Tvec.at<double>(2,0);

            // calculate the re-projection error
            std::vector<int> MM_inlier;//自己手动计算的内点数
            cv::Mat MotionModel;//ORB中的匀速运动模型
            if (mVelocity.empty())
                MotionModel = cv::Mat::eye(4,4,CV_32F)*mLastFrame.mTcw;
            else
                MotionModel = mVelocity*mLastFrame.mTcw;

            for (int i = 0; i < pre_3d.size(); ++i) {
                if (pre_3d[i].z > 0&&(cur_2d[i].x!=0&&cur_2d[i].y!=0)) {//遍历每个匹配的有效的3D-2D点 深度大于0 且 当前像素点不为0
                    const cv::Mat x3D = (cv::Mat_<float>(3, 1) << pre_3d[i].x, pre_3d[i].y, pre_3d[i].z);//point3f转换为cv::Mat
                    const cv::Mat x3D_c =MotionModel.rowRange(0, 3).colRange(0, 3) * x3D + MotionModel.rowRange(0, 3).col(3);
                    //R*x+t 从世界坐标系转换为当前帧的相机坐标系
                    cout<<x3D_c<<endl;
                    cout<<"u="<<cur_2d[i].x<<"v="<<cur_2d[i].y<<endl;
                    if(x3D_c.at<float>(2)<0)
                        continue;

                    const float xc = x3D_c.at<float>(0);
                    const float yc = x3D_c.at<float>(1);
                    const float invzc = 1.0 / x3D_c.at<float>(2);

                    const float u = mCurrentFrame.fx * xc * invzc + mCurrentFrame.cx;//相机坐标系转化为图像像素坐标
                    const float v = mCurrentFrame.fy * yc * invzc + mCurrentFrame.cy;
                    const float u_ = cur_2d[i].x - u;
                    const float v_ = cur_2d[i].y - v;
                    const float Rpe = std::sqrt(u_ * u_ + v_ * v_);

                    mCurrentFrame.mvMatch.push_back(cur_2d[i]);
                    mCurrentFrame.mvRpe.push_back(Rpe);

                    if (Rpe < reprojectionError) {
                        MM_inlier.push_back(i);
                    }
                }
            }
            cv::Mat output;
            cout<<"PNPinliers:"<<inliers.rows<<endl<<"MM_inliers:"<<MM_inlier.size()<<endl;

调试输出是
PNPinliers:221
MM_inliers:5
利用cv::projector函数计算重投影误差也得不到对应的模型内点数目。平均重投影误差大小在5左右。
也就是PNP内部计算的重投影误差小于0.4的点是221个
我用PNP位姿手动计算重投影误差小于0.4的点是5个
问题不知道出在哪里

<think>好的,我现在需要回答用户关于单目相机视觉姿估计的算法实现和相关技术的问题。首先,我应该先理解用户的需求。用户提到了参考内容中的PnP问题、solvePnP方法以及单目测距的应用,所以可能需要详细解释这些方面的内容。 首先,姿估计的基本概念是什么?根据引用[^1],PnP问题是通过已知的3D点坐标和对应的2D图像点来估计相机姿。这应该是核心部分。我需要解释PnP问题的定义和它在单目相机中的应用。 接下来,主要的算法有哪些?经典的算法比如P3P、EPnP、UPnP等,这些都需要提到。然后,OpenCV中的solvePnP函数是一个关键点,用户可能对具体的实现方法感兴趣,所以需要说明solvePnP的使用方法和参数,比如使用QR码或棋盘格作为已知3D点的情况,引用[^1]中的相关文章提到了利用二维码求解相机世界坐标,这部分可以作为例子。 另外,引用[^2]提到了通过3D-2D解算相机姿实现测距的方法,这可能涉及到姿估计的应用场景。因此,在应用场景部分,可以提到AR、机器人导航、自动驾驶中的定,以及测距方法,尽管单目测距精度有限,但在特定条件下可行。 然后,实现流程需要分步骤说明,包括标定、特征检测、匹配、解算姿和优化。每个步骤需要简要说明,比如相机标定获取内参,特征检测如角点检测(引用[^1]的第三篇文章),匹配使用RANSAC去除误匹配,解算姿用solvePnP,以及优化方法如光束法平差。 可能还需要提到挑战,比如尺度不确定性、噪声敏感性和实时性要求,以及相应的解决方法,比如融合IMU或使用深度学习模型。 最后,根据用户的要求生成相关问题,这些问题应该围绕算法优化、应用场景、挑战、与深度学习的结合等方面展开。 需要确保使用正确的中文术语,数学公式用$$...$$,行内公式用$...$,并正确引用参考内容。检查是否有遗漏的重要点,比如标定步骤中的内参矩阵,或者不同算法的比较。同时,代码示例可能用户需要,但用户提供的示例中没有代码,但引用中有使用solvePnP的案例,可以提到但不需要给出具体代码,除非用户要求。不过用户的问题中没有明确要代码,所以可能不需要。 总之,结构应该是:基本概念、核心算法、实现流程、应用场景、挑战与优化,然后相关问题。确保每个部分都覆盖了用户提到的引用内容,并自然地添加引用标识。</think>### 单目相机视觉姿计算法实现 #### 1. 基本概念 单目视觉姿估计的目标是通过单张图像中的特征点与已知3D空间点的对应关系,求解相机在空间中的置和姿态(即外参矩阵)。核心问题可抽象为**Perspective-n-Point(PnP)问题**,数学表达为: $$ \text{已知} \quad \mathbf{X}_i \in \mathbb{R}^3 \quad \text{和} \quad \mathbf{x}_i \in \mathbb{R}^2 \quad \text{,求解} \quad \mathbf{R} \in SO(3), \mathbf{t} \in \mathbb{R}^3 \quad \text{满足} \quad \mathbf{x}_i = \pi(\mathbf{R}\mathbf{X}_i + \mathbf{t}) $$ 其中$\pi(\cdot)$为相机投影模型。 --- #### 2. 核心算法 ##### (1) 经典解析法 - **P3P算法**:通过3对点计算4组解,结合RANSAC筛选最优解; - **EPnP/UPnP**:利用控制点参数化,将问题转换为线性求解; - **DLT算法**:直接线性变换,通过SVD分解求初值。 ##### (2) 数值优化法 常用**Levenberg-Marquardt**非线性优化,结合重投影误差最小化: $$ \min_{\mathbf{R},\mathbf{t}} \sum_i \|\mathbf{x}_i - \pi(\mathbf{R}\mathbf{X}_i + \mathbf{t})\|^2 $$ ##### (3) OpenCV实现 调用`cv::solvePnP`函数,支持EPnP、迭代法等多种模式,示例流程: ```python # 输入3D点(如二维码角点[^1])、2D点、相机内参 retval, rvec, tvec = cv2.solvePnP(object_points, image_points, camera_matrix, dist_coeffs) ``` --- #### 3. 实现流程 1. **相机标定** 获取内参矩阵$K$和畸变系数,通常采用棋盘格标定法: $$ K = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} $$ 2. **特征检测与匹配** - 使用二维码/棋盘格角点检测(如`cv2.aruco.detectMarkers`); - 或提取SIFT/ORB特征点,通过RANSAC剔除误匹配。 3. **姿解算** 调用PnP算法求解旋转向量$\mathbf{r}$和平移向量$\mathbf{t}$,转换为姿矩阵$[\mathbf{R}|\mathbf{t}]$。 4. **优化与融合** - 光束法平差(Bundle Adjustment)优化姿; - 融合IMU数据提升鲁棒性(如SLAM系统[^2])。 --- #### 4. 应用场景 - **增强现实(AR)**:虚拟物体与真实场景对齐; - **机器人导航**:基于标志物的定; - **单目测距**:通过已知尺寸目标估算距离(精度受限)。 --- #### 5. 挑战与优化 | 挑战 | 解决方案 | |--------------------|------------------------------| | 尺度不确定性 | 引入已知尺寸物体或传感器融合 | | 特征匹配噪声 | RANSAC + 优化算法 | | 实时性要求 | 轻量级特征提取(如FAST) | | 动态场景 | 结合光流跟踪或深度学习模型 | --- §§ 相关问题 §§ 1. 如何解决单目姿估计中的尺度漂移问题? 2. PnP算法在特征点数量不足时如何优化? 3. 深度学习模型(如PoseNet)与传统方法有何优劣? 4. 单目视觉与IMU融合的常见方法有哪些?
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值