Kalibr源码学习(一): 重投影误差

给自己挖一个大坑, 从标定结果来学习Kalibr的标定源码, 这里基本以KB模型为例, 也就是标定时, kalibr的模型设定为 --model pinhole-equi , 这里以重投影误差开始,希望能坚持;

重投影误差标定结果

首先以重投影误差的txt文档的结果开始,

重投影误差python_标定


可以看到上面的标定结果中, 重投影误差显示为 reprojection error: [-0.000002, -0.000000] +- [0.535272, 0.572115] 这里的两项对应的分别是 reprojection error: [x方向的重投影误差均值, y方向的重投影误差均值] +- [x方向重投影误差方差, y方向重投影误差方差]

其对应的代码片为:

//获取当前检测的角点,重投影坐标, 重投影误差
corners, reprojs, rerrs = getReprojectionErrors(cself, cidx) 
 if len(rerrs)>0:
 //获取重投影误差的均值me和方差se
 me, se = getReprojectionErrorStatistics(rerrs)
 //打印输出到txt文档中
 print >> dest, "\t reprojection error: [%f, %f] +- [%f, %f]" % (me[0], me[1], se[0], se[1])
 print >> dest

这里可以再说以下计算均值和方差的python代码:

在这里插入代码片
```def getReprojectionErrorStatistics(all_rerrs):
    #传入值是所有的误差 errors, 这里代码里面举出的例子usage是错误的,传入的应该是error,不是重投影
    if not len(all_rerrs)>0:
        raise RuntimeError("rerrs has invalid dimension")

    gc.disable() #append speed up
    rerr_matrix=list();
    for view_id, view_rerrs in enumerate(all_rerrs):
        if view_rerrs is not None: #if cam sees target in this view
            for rerr in view_rerrs:
                if not (rerr==np.array([None,None])).all(): #if corner was observed
                    rerr_matrix.append(rerr) #将误差值统一放入到rerr_matrix中
    rerr_matrix = np.array(rerr_matrix)
    gc.enable()
    
    mean = np.mean(rerr_matrix, 0, dtype=np.float)#计算均值
    std = np.std(rerr_matrix, 0, dtype=np.float)#计算方差
 
    return mean, std

重投影误差图像显示

重投影误差python_重投影误差python_02


这个图对应的代码为:

def plotAllReprojectionErrors(cself, cam_id, fno=1, noShow=False, clearFigure=True, title=""):
    # left: observations and projecitons
    # right: scatterplot of reprojection errors
    all_corners, reprojections, rerrs_xy = getReprojectionErrors(cself, cam_id)
    resolution = (cself.cameras[cam_id].geometry.projection().ru(), cself.cameras[cam_id].geometry.projection().rv())

    #创建图像
    values = np.arange(len(cself.views))/np.double(len(cself.views))
    cmap = pl.cm.jet(values,alpha=0.5)
    
    #子图一用来画重投影后的坐标 省略
    a=pl.subplot(121)

    #子图二用来画重投影误差
    sub = pl.subplot(122)
    for view_id, rerrs in enumerate(rerrs_xy):
        if rerrs is not None: #if this camerea sees the target in this view
            color = cmap[view_id,:]
            pl.plot(rerrs[:,0], rerrs[:,1], 'x', lw=3, mew=3, color=color)
   # 主要是这里,x代表的是重投影的x误差, y表示的是重投影的y值误差, 以 error_x 为横坐标, 以error_y为纵坐标,画图
    pl.axis('equal')
    pl.grid('on')
    pl.xlabel('error x (pix)')
    pl.ylabel('error y (pix)')

    #省略...

这里重点说一下获取重投影误差的函数, 经过层层调用, 最后计算出的其实是将三维点投影到图像上

//将传入的三维点投影到图像上
template<typename DISTORTION_T>
template<typename DERIVED_P, typename DERIVED_K>
bool PinholeProjection<DISTORTION_T>::euclideanToKeypoint(const Eigen::MatrixBase<DERIVED_P> & p,
    const Eigen::MatrixBase<DERIVED_K> & outKeypointConst) const {
  EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE_OR_DYNAMIC(Eigen::MatrixBase<DERIVED_P>, 3);
  EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE_OR_DYNAMIC(Eigen::MatrixBase<DERIVED_K>, 2);
  Eigen::MatrixBase<DERIVED_K> & outKeypoint = const_cast<Eigen::MatrixBase<DERIVED_K> &>(outKeypointConst);
   //shinan note: 转换到归一化相机平面 --> 加畸变 -->转换到像素平面
  outKeypoint.derived().resize(2);
  double rz = 1.0 / p[2];
  outKeypoint[0] = p[0] * rz;
  outKeypoint[1] = p[1] * rz;
   //加畸变
  _distortion.distort(outKeypoint);

  outKeypoint[0] = _fu * outKeypoint[0] + _cu;
  outKeypoint[1] = _fv * outKeypoint[1] + _cv;

  return isValid(outKeypoint) && p[2] > 0;
}

其中加畸变函数如下:

//将传入的归一化相机坐标系的三维点按照公式加畸变
template<typename DERIVED_Y>
void EquidistantDistortion::distort(const Eigen::MatrixBase<DERIVED_Y> & yconst) const {
  EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE_OR_DYNAMIC(Eigen::MatrixBase<DERIVED_Y>, 2);
  Eigen::MatrixBase<DERIVED_Y> & y = const_cast<Eigen::MatrixBase<DERIVED_Y> &>(yconst);
  y.derived().resize(2);
  double r, theta, theta2, theta4, theta6, theta8, thetad, scaling;
  r = sqrt(y[0] * y[0] + y[1] * y[1]);
  theta = atan(r);
  theta2 = theta * theta;
  theta4 = theta2 * theta2;
  theta6 = theta4 * theta2;
  theta8 = theta4 * theta4;
  thetad = theta* (1 + _k1 * theta2 + _k2 * theta4 + _k3 * theta6 + _k4 * theta8);

  scaling = (r > 1e-8) ? thetad / r : 1.0;
  y[0] *= scaling;
  y[1] *= scaling;
}

总结

这里有一点就是这里的重投影误差的均值很小, 主要的原因可以从上面的彩色图中看出, 重投影误差有正有负,均值会出现互相抵消的状态; 总体来讲是以(-0.000002, -0.000000)为中心, 方差分别是0.535272, 0.572115的正态分布;

所以说,均值不可信,方差大概率更靠谱一些: 如下面标定的很好的结果中:

reprojection error: [0.000000, 0.000001] ± [0.115878, 0.107625]

方差仅为0.1, 其对应的重投影误差的值也都偏小:

重投影误差python_重投影误差_03


根据3*sigma原则, 要想让大部分的重投影误差小于1个像素, 那么其所对应的方差应该是小于0.33.