Tsai手眼标定算法基于Eigen库实现

采集数据格式

T_t_cal: transform from robot tool to calibration board (fixed)
T_b_t: transform from robot base to tool
T_c_cal: transform from camera to calibration board
T_b_c: transform from robot base to camera (fixed). You need to calibrate this transform
Data is in the form of [x, y, z, ox, oy, oz, ow], position + quaternion


这里我们实现的是hand to eye 标定,给定机器人基座到末端,相机到标定板的两组数据(数据集大小为30),求解从基座到相机坐标系的转化。

数据格式做适当转换便于Eigen处理

这里分为两步,首先读取csv格式的数据,将30组数据存到30*7矩阵里面,然后将每一行转化为一个4*4齐次矩阵,最终单个数据集(eg.相机到标定板变换)以 std::vector<Eigen::MatrixXd>存储。
这里直接贴上代码

1.读取csv数据

int CsvRead(MatrixXd& outputMatrix, const string& fileName, const streamsize dPrec) {
    
  ifstream inputData;
  inputData.open(fileName);
  cout.precision(dPrec); //设置输出精度
  if (!inputData)
    return -1;
  string fileline, filecell;
  unsigned int  noOfRows = 0, noOfCols = 0;
  //确定输入矩阵行列
    while (getline(inputData, fileline)) {
        noOfCols = 0;
        stringstream linestream(fileline);
        while (getline(linestream, filecell, ',')) {
            try {
                stod(filecell);
            }
            catch (...) {
                return -1;
            }
            noOfCols++;
        }
        noOfRows++;
    }
    inputData.close();
    outputMatrix.resize(noOfRows, noOfCols);
    inputData.open(fileName);
    noOfRows = 0;
    while (getline(inputData, fileline)) {
        noOfCols = 0;
        stringstream linestream(fileline);
        while (getline(linestream, filecell, ',')) {
            outputMatrix(noOfRows, noOfCols++) = stod(filecell);
        }
        noOfRows++;
    }
    return 0;
}

上面读取csv的代码块,参考了网上的,比较好的方面是做了一些异常判断,比如关于文件的读取以及字符转化是否成功。但是不好的方面是做了两次大循环,第一次循环仅仅为了判断输出矩阵大小,第二次才是将数据写入矩阵。这样在时间效率上我觉得不行。

自己最早写的一个读取csv的代码块,出现了一些暂时还不能理解的错误,思路是将csv所有数据存在一个vector里面,然后通过MatrixXd::Map转化,我觉得这个思路比上述读取csv更加简单明了,但是当我输出读取的数据,发现并没有按照我预想的格式存储。也将代码贴在这,望各位大佬指正。

Eigen::MatrixXd csv2matrix(std::string path) { //path csv文件路径
    std::ifstream fin;
    fin.open(path);
    std::string line;
    vector<double>values;
    int rows = 0;
    while (getline(fin,line))
    {
        stringstream ss(line);
        std::string cell;
        while (getline(ss, cell, ',')) {    
            double value = stod(cell);
            values.push_back(value);
        }
        rows++;
    }
    MatrixXd re = MatrixXd::Map(&values[0], rows, values.size() / rows);
    cout << re.size() << endl;
    return re;
}

2.将四元数转化为旋转矩阵

这个没有太多要讲的,直接上代码

void Vector2Homogeneous(MatrixXd source, poses&target)
{
    for (int i = 0; i < source.rows(); i++) {
        Eigen::Vector3d translation_base = source.block<1, 3>(i, 0);
        Eigen::VectorXd rotation_base = source.block<1, 4>(i, 3);
        Eigen::Quaterniond q_rotaion_base(rotation_base[3], rotation_base[0], rotation_base[1], rotation_base[2]);
        Eigen::Matrix3d Rotation_base = q_rotaion_base.matrix();
        Eigen::Matrix4d T = Eigen::Matrix4d::Identity();
        T.block<3,3>(0,0)=Rotation_base;
        T.block<3,1>(0,3)=translation_base;
        target.push_back(T);
    }
}

需要注意几点:

  • pose 为我自己定义的数据类型 typedef vector<MatrixXd>poses;
  • 通过传引用形式实现输出target
  • 我们采集到的四元数格式为x,y,z,wEigen里面四元数构造格式为w x y z

复现Tsai论文,求解AX=XB

对于论文的理解,我有个过程,并不是一步到位的。在A,B矩阵,以及(P_ {gij}+P_ {cij})*(P_{ce}^{'})=(P_ {gij}-P_ {cij})里面旋转轴相加的含义都有过误解。

A,B对应的是两次运动中,末端到末端,相机到相机之间的变换矩阵。这个在hand to eyehand in eye求解时候有细微差别。

if (t == EyeToHand)
            {
                MatrixXd A = vH_robot[j] * vH_robot[i].inverse();//vH_robot:base to  tool
                MatrixXd B = vH_mark[j] * vH_mark[i].inverse();//vH_mark:camera to cal   

                vA.push_back(A);
                vB.push_back(B);
            }
  else if (t == EyeInHand)
            {

                MatrixXd A = vH_robot[j].inverse() * vH_robot[i];
                MatrixXd B = vH_mark[j] * vH_mark[i].inverse();

                vA.push_back(A);
                vB.push_back(B);

旋转轴相加并不是对应旋转矩阵的乘法,上述的等式在论文里面有详细推导,这个是基于矢量的封闭运算。具体还是要详细阅读论文。

这里贴出代码块,全部代码参见我的github

MatrixXd Solve_Axxb(const poses AA,const poses BB) {
    Matrix4d Hx = Matrix4d::Identity(); //待求解标定矩阵
    int num = AA.size();//观察数据组个数
    cout << "数据对个数:" << num << endl;
    //MatrixXd output=MatrixXd::Identity();
    MatrixXd A = MatrixXd::Zero(num * 3, 3); // solve Ax=B for rotation
    VectorXd B = MatrixXd::Zero(num * 3, 1);
    //solve rotation
    for (size_t i = 0; i < AA.size(); i++) {
        //将旋转矩阵转化为旋转轴形式
        AngleAxisd rgij, rcij;
        rgij.fromRotationMatrix(AA[i].block<3,3>(0,0));
        rcij.fromRotationMatrix(BB[i].block<3,3>(0,0));
        double theta_gij = rgij.angle();
        double theta_cij = rcij.angle();
        Vector3d rngij = rgij.axis();
        Vector3d rncij = rcij.axis();
        AngleAxisd Pgij(2*sin(theta_gij / 2),rngij);
        AngleAxisd Pcij (2 * sin(theta_cij / 2),rncij);
        
        Vector3d pgij_vector = Pgij.axis()*Pgij.angle();
        Vector3d pcij_vector = Pcij.axis()*Pcij.angle();
        Vector3d vec_sum_axis = pgij_vector + pcij_vector;
        Matrix3d S = skew(vec_sum_axis);
        Vector3d vec_sub_axis = pcij_vector - pgij_vector;
        A.block<3, 3>(3 * i, 0) = S;
        //cout << A.block<3, 3>(3 * i, 0) << endl;
        B.block<3, 1>(3 * i, 0) = vec_sub_axis;
    }
    //求解旋转
    VectorXd Pce1 = SvdInverse(A)*B;
    VectorXd Pce = 2 * Pce1 / sqrt(1 + Pce1.norm()*Pce1.norm());
    Matrix3d I=Matrix3d::Identity();
    double np2 = Pce.norm()*Pce.norm();
    MatrixXd Rx = (1 - np2 * 0.5)*I + 0.5*(Pce*Pce.transpose() + sqrt(4 - np2)*skew(Pce));
    cout<<"旋转矩阵是否正交:" <<Rx * Rx.transpose()<<endl;
    //求解平移
    A.setZero();
    B.setZero();
    for (size_t i = 0; i < AA.size(); i++) {
        Vector3d T_A, T_B;
        T_A = AA[i].block<3, 1>(0, 3);
        T_B = BB[i].block<3, 1>(0, 3);
        MatrixXd R_A = AA[i].block<3, 3>(0, 0);
        A.block<3, 3>(3 * i, 0) = R_A - I;
        B.block<3, 1>(3 * i, 0) = Rx*T_B-T_A;
    }
    VectorXd Tx = SvdInverse(A)*B;
    Hx.block<3, 3>(0, 0) = Rx;
    Hx.block<3, 1>(0, 3) = Tx;
    return Hx;

总结

网上大多教程都语焉不详,这种时候还是需要阅读原论文

参考

1.A New Technique for Fully Autonomous and
Efficient 3D Robotics Hand/Eye Calibration

2.码云

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容