c++ opencv cv::solvePnPRansac 的使用和注意事项

最近在做关于RGBD人脸拟合的事情,遇到一个关于 solvePnPRansac 使用的问题,记录一下. 顺便记录几个函数,里面包含了自己对 eigen 使用的一些尝试. 终于对eigen由陌生到可以较为熟练地使用了.

问题描述

一开始的现象是同样的参数使用solvePnP是正确的,而使用solvePnPRansac 就有问题,报错如下

terminate called after throwing an instance of 'cv::Exception'
  what():  OpenCV(3.4.5) /data/soft/opencv-3.4.5/modules/core/src/matrix_wrap.cpp:1235: error: (-215:Assertion failed) !fixedSize() || ((Mat*)obj)->size.operator()() == Size(_cols, _rows) in function 'create'

开始挺摸不着头脑的, 猜测是传入的数据类型不对,后来才知道 rvec, tvec 不能指定类型 (在solvePnp中是可以的)

利用solve pnp求解到相机姿态下的方法

// 这个是针对已经把相机内参去掉的p2d
void Calc_perspective_pm(float * pm, int n, const float * p3d, const float *p2d, int type){
    const cv::Mat1f noK = cv::Mat1f::eye(3, 3); // 去掉相机内参之后,就是单位阵了

    const cv::Mat1f noK_dst(n, 2, const_cast<float *>(p2d));
    const cv::Mat1f src(n, 3, const_cast<float *>(p3d));
    //auto noK_dst = cv::Mat1f(n, 2, p2d);

    //cv::Mat1f rvec, tvec;  这个对于 ransac 不行
    cv::Mat rvec, tvec;
    cv::Mat1f distCoeffs = cv::Mat1f::zeros(1, 5);
    if(type==0){
        cv::solvePnP(src, noK_dst, noK, distCoeffs, rvec, tvec);
    }else if(type==1){
        //cv::solvePnPRansac(cv::Mat(src), cv::Mat(noK_dst), cv::Mat(noK), cv::Mat(distCoeffs), rvec, tvec);
        cv::solvePnPRansac(src, noK_dst, noK, distCoeffs, rvec, tvec,
                false, 100, 8.0, 0.99, cv::noArray(), cv::SOLVEPNP_P3P);

    }else{
        cout<<"ERR: Calc_perspective_pm not support type: "<<type<<endl;
        exit(1);
    }

    //@7-10
    //cout<<rvec.type()<<" "<<src.type()<<endl;
    if(rvec.type()==5){
        memcpy(pm, rvec.ptr<float>(), 3*sizeof(float));
        memcpy(pm+3, tvec.ptr<float>(), 3*sizeof(float));
    }else if(rvec.type()==6){ // after solvePnPRansac
        memcpy(pm, rvec.ptr<double>(), 3*sizeof(double));
        memcpy(pm+3, tvec.ptr<double>(), 3*sizeof(double));
    }else{
        cout<<"ERR: Calc_perspective_pm, something wrong in ocv "<<endl;
        exit(1);
    }
    /*
    如果要求相机在世界坐标系下的位置
cv::Mat R;
cv::Rodrigues(rvec, R); // R is 3x3

R = R.t();  // rotation of inverse
tvec = -R * tvec; // translation of inverse

cv::Mat T = cv::Mat::eye(4, 4, R.type()); // T is 4x4
T( cv::Range(0,3), cv::Range(0,3) ) = R * 1; // copies R into T
T( cv::Range(0,3), cv::Range(3,4) ) = tvec * 1; // copies tvec into T
    */

}

去除相机参数 (use eigen)

上面我给的函数需要提前把相机内参给去掉,这里我把这个函数提供出来,同时也是对 eigen 矩阵操作的记录

// type: 0: 只有四个数, 且不是矩阵; 1: 只有四个数且是矩阵; 2: 按照2*3 矩阵来算
void Calc_remove_camK(int n, float *p2_5d, const float *p2d, const float *camK, int type) {
    using namespace Eigen;
    using namespace define_utils;

    MapMatXf P2_5d(p2_5d, 2, n);
    constMapMatXf P2d(p2d, 2, n);

    assert(type<3);
    if(type==2){
        MatrixXf inv_K2x2(2, 2);
        VectorXf txy(2);

        txy[0] = camK[2];
        txy[1] = camK[3+2];
        float ad_bc = camK[0]*camK[3+1] - camK[1]*camK[3];

        inv_K2x2(0, 0) = camK[3+1];
        inv_K2x2(0, 1) = -camK[1];
        inv_K2x2(1, 0) = -camK[3];
        inv_K2x2(1, 1) = camK[0];
        inv_K2x2 /= ad_bc;

        P2_5d = inv_K2x2 * P2d;
        P2_5d.colwise() -= inv_K2x2 * txy;
    }else{
        VectorXf fxy(2), txy(2);
        if(type==0){
            fxy(0) = camK[0];
            fxy(1) = camK[2];
            txy(0) = camK[1];
            txy(1) = camK[3];
        }else{
            fxy(0) = camK[0];
            fxy(1) = camK[3+1];
            txy(0) = camK[2];
            txy(1) = camK[3+2];
        }

        if(p2_5d!=p2d) P2_5d = P2d;
        P2_5d.colwise() -= txy;

        P2_5d.block(0, 0, 1, n) /= fxy(0);
        P2_5d.block(1, 0, 1, n) /= fxy(1);

    }

}

透视投影函数 (use eigen)

用了一些 eigen的矩阵操作技巧记录一下

// 自己的简化定义
namespace define_utils{
    using c_F = float;
    using c_Fp = float*;
    using c_cFp = const float*;
    using c_cIp = const int*;
    using c_uint32 = unsigned int;

    using MapVecXf = Eigen::Map<Eigen::VectorXf>;
    using constMapVecXf = Eigen::Map<const Eigen::VectorXf>;
    using MapMatXf = Eigen::Map<Eigen::MatrixXf>;
    using constMapMatXf = Eigen::Map<const Eigen::MatrixXf>;

    // dynamic = -1
    using constMapMatXf_RM = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> >;

}


// 里面注释掉的都是我想的eigen操作方式,但是不成功
void Calc_project3D_to_2D(int n, float *p2d, const float *p3d, const float *camK, int type) {
    using namespace Eigen;
    using namespace define_utils;

    constMapMatXf P3d(p3d, 3, n);
    MapMatXf P2d(p2d, 2, n);
    assert(type<3);

    if(type==0 || type==1){
        VectorXf fxy(2), txy(2);
        if(type==0){
            fxy(0) = camK[0];
            fxy(1) = camK[2];
            txy(0) = camK[1];
            txy(1) = camK[3];
        }else{
            fxy(0) = camK[0];
            fxy(1) = camK[3+1];
            txy(0) = camK[2];
            txy(1) = camK[3+2];
        }
        //P2d = P3d.block(0, 0, 2, n).colwise() * fxy; // error
        //P2d += txy *  P3d.block(2, 0, 1, n); then div z

        //P2d = P2d.rowwise() / P3d.block(2, 0, 1, n) + txy;

        P2d =  P3d.block(0, 0, 2, n).array().rowwise() / P3d.row(2).array();

        P2d.block(0, 0, 1, n) *= fxy(0);
        P2d.block(1, 0, 1, n) *= fxy(1);

        //P2d = P2d.colwise() + txy;
        P2d.colwise() += txy;

        //P2d.block(0, 0, 1, n) = P3d.row(0).array() / P3d.row(2).array() ; // * fxy(0);
        //P2d.block(1, 0, 1, n) = P3d.block(1, 0, 1, n) / P3d.block(2, 0, 1, n) * fxy(1);
        //P2d = P2d.rowwise() / P3d.block(2, 0, 1, n) + txy;


    }else{
        constMapMatXf_RM K(camK, 2, 3);
        //cout<<K<<endl;
        P2d = K * P3d;
        //P2d = P2d.array().rowwise() / P3d.row(2).array();
        //P2d.array().rowwise() /= P3d.row(2).array();
        P2d.array().rowwise() /= P3d.row(2).array();
    }

}
#include <map> #include <vector> #include <Eigen/Eigen> #include <opencv2/opencv.hpp> #include <cv_bridge/cv_bridge.h> #include <iostream> #include <fstream> // 棋盘格检测器类,用于检测棋盘格并计算机器人/相机坐标系转换 class ChessboardDetector_cls { private: cv::Mat rvec; // 旋转向量(世界坐标系到相机坐标系的旋转) cv::Mat tvec; // 平移向量(世界坐标系原点到相机坐标系原点的平移) // 相机内参矩阵 [fx, 0, cx; 0, fy, cy; 0, 0, 1] cv::Mat cameraMatrix; // 畸变系数 [k1, k2, p1, p2, k3] cv::Mat distCoeffs; // 棋盘格方块物理尺寸(单位:米,需与实际应用场景一致) float squareSize; int boardWidth = 7; // 棋盘格宽度(单位:内角点) int boardHeight = 7; // 棋盘格高度(单位:内角点) bool cvVerPlus45 = false; // OpenCV版本判断标志 public: // 构造函数,初始化相机参数棋盘格尺寸 ChessboardDetector_cls(float square_size = 12.5,int nDevType=1) // 默认1cm方块,1=r1d,0=r8c { if(nDevType == 0){ // ✅ 相机内参矩阵 cameraMatrix: // [[1.04969613e+03 0.00000000e+00 5.73570763e+02] // [0.00000000e+00 1.05086275e+03 4.05877726e+02] // [0.00000000e+00 0.00000000e+00 1.00000000e+00]] // ✅ 畸变系数 distCoeffs: // [[-1.85926395e-03 1.61417431e-03 1.14303737e-03 7.58650886e-05 // -1.17211371e-03]] this->cameraMatrix = (cv::Mat_<double>(3, 3) << 593.52455247, 0, 359.99576897, 0, 591.1469869, 231.48422218, 0, 0, 1); this->distCoeffs = (cv::Mat_<double>(1, 5) << 0.03468398, 0.44016135, -0.00044522, 0.01792056, -1.15909218); }else{ // ✅ 相机内参矩阵 cameraMatrix: // [[1.05382819e+03 0.00000000e+00 5.72604459e+02] // [0.00000000e+00 1.05425060e+03 4.02170466e+02] // [0.00000000e+00 0.00000000e+00 1.00000000e+00]] // ✅ 畸变系数 distCoeffs: // [[ 0.00272168 -0.04679861 0.00091088 -0.00053716 0.14500516]] this->cameraMatrix = (cv::Mat_<double>(3, 3) << 1.05382819e+03, 0.00000000e+00, 5.72604459e+02, 0.00000000e+00, 1.05425060e+03, 4.02170466e+02, 0.00000000e+00, 0.00000000e+00, 1.00000000e+00); this->distCoeffs = (cv::Mat_<double>(1, 5) << 0.00272168, -0.04679861, 0.00091088, -0.00053716, 0.14500516); } this->squareSize = square_size; // 初始化棋盘格方块尺寸 int major_ver = 0, minor_ver = 0; std::sscanf(CV_VERSION, "%d.%d", &major_ver, &minor_ver); // 使用自适应阈值检测棋盘格角点 // 这里的版本判断是为了兼容OpenCV 4.5+4.4-的不同函数调用 // 4.5+版本使用findChessboardCornersSB函数,4.4-版本使用findChessboardCorners函数 if (major_ver > 4 || (major_ver == 4 && minor_ver >= 5)){ // OpenCV 4.5+ cvVerPlus45 = true; } } // 棋盘格检测服务回调函数 // 输入:原始图像,输出:位姿向量[x,y,z](相机坐标系下的棋盘格原点坐标) bool ChessboardDetectionCallback(cv::Mat image, std::vector<float>& vecRt) { if(!ChessboardDetection(image)) { // 执行检测 return false; // 检测失败返回false } // 生成唯一文件名 auto now = std::chrono::system_clock::now(); auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch()).count(); std::string filename = "/home/nvidia/YaLongR8/src/bringup/detected_" + std::to_string(timestamp) + ".jpg"; // 保存带角点的图像 if(!cv::imwrite(filename, image)){ std::cerr << "Failed to save image: " << filename << std::endl; } else { std::cout << "Saved image with corners: " << filename << std::endl; } vecRt = PoseCalculation(); // 计算并返回位姿 return true; } // 计算标定板坐标系到相机坐标系的转换矩阵 cv::Point3f transformToCameraFrame(const cv::Point3f& obj_in_board) { cv::Mat R; cv::Rodrigues(rvec, R); // 将旋转向量转为旋转矩阵 cv::Mat pt_board = (cv::Mat_<double>(3,1) << obj_in_board.x, obj_in_board.y, obj_in_board.z); cv::Mat pt_camera = R * pt_board + tvec; return cv::Point3f( static_cast<float>(pt_camera.at<double>(0)), static_cast<float>(pt_camera.at<double>(1)), static_cast<float>(pt_camera.at<double>(2)) ); } private: // 核心检测函数 bool ChessboardDetection(cv::Mat image) { if(image.empty()) { std::cerr << "图像为空!" << std::endl; return false; } const cv::Size boardSize(boardWidth, boardHeight); // 棋盘格内部角点数量(7x7网格) std::vector<cv::Point2f> corners; // 存储检测到的角点坐标 // 转换为灰度图(棋盘格检测需要灰度图像) cv::Mat gray_image; cv::cvtColor(image, gray_image, cv::COLOR_BGR2GRAY); // 使用自适应阈值检测棋盘格角点 bool found = false; if (cvVerPlus45) { ROS_INFO("OpenCV version >= 4.5 detected, using findChessboardCornersSB."); // OpenCV 4.5+ found = cv::findChessboardCornersSB( gray_image, boardSize, corners, cv::CALIB_CB_EXHAUSTIVE | cv::CALIB_CB_ACCURACY ); } else { ROS_INFO("OpenCV version < 4.5 detected, using findChessboardCorners."); // OpenCV 4.4- or earlier fallback // found = cv::findChessboardCorners(gray_image, boardSize, corners); found = cv::findChessboardCorners( gray_image, boardSize, corners, cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE + cv::CALIB_CB_FAST_CHECK ); } if (!found) { // std::cerr << "未检测到棋盘格!" << std::endl; // cv::imshow("检测结果", image); // 显示原始图像辅助调试 // cv::waitKey(1000); // 显示1秒后自动关闭 // cv::destroyWindow("检测结果"); ROS_ERROR("Chessboard not found!"); return false; } // 亚像素级角点精确化 cv::cornerSubPix( gray_image, corners, cv::Size(11, 11), // 搜索窗口大小 cv::Size(-1, -1), // 死区大小(-1表示无死区) cv::TermCriteria( cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, // 最大迭代次数 0.1 // 精度阈值 ) ); // 绘制角点(绿色) cv::drawChessboardCorners(image, boardSize, cv::Mat(corners), found); // 生成三维物体点(假设棋盘格在Z=0平面) std::vector<cv::Point3f> objectPoints; // 棋盘格原点在左上第一个角点处 // for (int i = 0; i < boardSize.height; ++i) { // for (int j = 0; j < boardSize.width; ++j) { // objectPoints.emplace_back( // j * squareSize, // X坐标(沿宽度方向) // i * squareSize, // Y坐标(沿高度方向) // 0 // Z坐标固定为0 // ); // } // } // 棋盘格原点在中心位置 for (int i = 0; i < boardSize.height; ++i) { for (int j = 0; j < boardSize.width; ++j) { objectPoints.emplace_back( (j - (boardSize.width - 1) / 2.0) * squareSize, // X坐标(沿宽度方向) (i - (boardSize.height - 1) / 2.0) * squareSize, // Y坐标(沿高度方向) 0 // Z坐标固定为0 ); } } // 求解PnP问题(透视n点定位) // if (this->rvec.empty() || this->tvec.empty()){ // cv::solvePnP( // objectPoints, // 物体坐标系中的3D点 // corners, // 图像坐标系中的2D点 // cameraMatrix, // 相机内参矩阵 // distCoeffs, // 畸变系数 // rvec, // 输出旋转向量 // tvec, // 输出平移向量 // false, // 使用初始估计(连续帧时提高稳定性) // cv::SOLVEPNP_ITERATIVE // 使用迭代算法 // ); // } else { // cv::solvePnP( // objectPoints, // 物体坐标系中的3D点 // corners, // 图像坐标系中的2D点 // cameraMatrix, // 相机内参矩阵 // distCoeffs, // 畸变系数 // rvec, // 输出旋转向量 // tvec, // 输出平移向量 // true, // 使用初始估计(连续帧时提高稳定性) // cv::SOLVEPNP_ITERATIVE // 使用迭代算法 // ); // } std::vector<int> inliers; cv::solvePnPRansac( objectPoints, corners, cameraMatrix, distCoeffs, rvec, tvec, false, // 不使用初始估计 100, // 最大迭代次数 8.0, // 重投影误差阈值(像素) 0.99, // 置信度 inliers, // 返回的内点索引 cv::SOLVEPNP_ITERATIVE ); return true; } // 位姿计算函数 std::vector<float> PoseCalculation() { // 欧拉角(ZYX) cv::Mat R; cv::Rodrigues(rvec, R); float roll = atan2(R.at<double>(2,1), R.at<double>(2,2)); float pitch = atan2(-R.at<double>(2,0), sqrt(R.at<double>(2,1)*R.at<double>(2,1) + R.at<double>(2,2)*R.at<double>(2,2))); float yaw = atan2(R.at<double>(1,0), R.at<double>(0,0)); return { static_cast<float>(tvec.at<double>(0)), // X static_cast<float>(tvec.at<double>(1)), // Y static_cast<float>(tvec.at<double>(2)), // Z // static_cast<float>(rvec.at<double>(0)), // Rx // static_cast<float>(rvec.at<double>(1)), // Ry // static_cast<float>(rvec.at<double>(2)) // Rz static_cast<float>(roll), // Roll static_cast<float>(pitch), // Pitch static_cast<float>(yaw) // Yaw }; // // 将旋转向量转换为旋转矩阵 // cv::Mat rotationMatrix; // cv::Rodrigues(rvec, rotationMatrix); // // 计算棋盘格原点在相机坐标系中的坐标 // cv::Mat chessboardOrigin = -rotationMatrix.t() * tvec; // // 转换为float类型向量 // return { // static_cast<float>(chessboardOrigin.at<double>(0)), // X坐标 // static_cast<float>(chessboardOrigin.at<double>(1)), // Y坐标 // static_cast<float>(chessboardOrigin.at<double>(2)) // Z坐标 // }; } };这是啥如何使用
最新发布
07-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值