天天看点

视觉SLAM十四讲——第五讲相机与图像

@ 《视觉SLAM十四讲》知识点与习题

《视觉SLAM十四讲》第五讲知识点整理+习题

正在学习SLAM相关知识,将一些关键点及时记录下来。

知识点整理

本讲主要讨论了相机模型,描述投影关系,讲解相机内参和外参

  1. 相机的针孔模型:需要了解焦距(物理成像平面到小孔的距离)、光心(针孔模型中的小孔)、物理成像平面和像素平面

    像素坐标系与成像平面之间,相差了一个缩放和原点平移:x轴缩放α,y轴缩放β,原点平移[cx, cy]^T

  2. 相机的内参:K。常认为相机的内参在出场之后是固定的,不会在使用过程中发生变化
    视觉SLAM十四讲——第五讲相机与图像
  3. 相机的外参:R, t。由于相机在运动,所以外参应该是他的世界坐标系根据相机的当前位姿变换到相机坐标系下的结果。外参会随着相机运动发生改变
    视觉SLAM十四讲——第五讲相机与图像
  4. 透镜的畸变模型
  5. 径向畸变:由透镜自身的形状引起的畸变。越靠近图像的边缘,这种现象越明显。径向畸变包括桶形畸变和枕形畸变。在这两种畸变中,穿过图像中心和光轴有交点的直线还能保持形状不变
    1. 桶形畸变是由于图像放大率随着与光轴之间的距离的增加而减少
    2. 枕形畸变则恰好相反

      用与距中心的距离有关的二次及高次多项式函数进行纠正。对于畸变较小的图像中心区域,主要是k1起作用; 对于畸变较大的边缘区域,主要是k2起作用;对于鱼眼镜头等畸变很大的摄像头,可以加入k3进行纠正

  6. 切向畸变:是由于在相机的组装过程中由于不能使透镜和成像面严格平行引起的。

    在极坐标系中,径向畸变认为是坐标点沿着长度方向发生了变化,切向畸变认为是水平夹角发生了变化。

    用下图中的公式,分别对x,y的畸变进行纠正。其中k1,k2,k3进行的是径向畸变的纠正,p1, p2进行的是切向畸变的纠正

    视觉SLAM十四讲——第五讲相机与图像
  7. 基线:左右相机之间的距离
  8. 双目相机:通过同步采集左右相机的图像,计算图像间视差,来估计每一个像素的深度。其中,视差是指左右图的横坐标之差。视差越大,距离越近。同时,由于视差最小为1个像素,于是双目的深度存在一个理论上的最大值,由fb确认。且,当基线越长时,双目能测到的最大距离就会越远。

    但是视差d本身的计算比较困难,因为需要确切知道同一个点在左右图中的对应关系。当想计算每个像素的深度时,其计算量与精度都称为问题,而且只有在图像纹理变化丰富的地方才能计算视差

  9. RGB-D相机模型:按原理可以分为两类。
    1. 第一类是通过红外结构光(Structured Light)来测量像素距离
    2. 第二种是通过飞行时间法(ToF)原理测量像素距离,可以获得整个图像的像素深度

      无论是哪一种RGB-D相机都需要向探测目标发射一束光线,相机根据返回的结构光图案,计算物体与自身之间的距离。

      在测量深度之后,RGB-D相机通常按照生产时的各相机摆放位置,自己完成深度与彩色图像素之间的配对,输出一一对应的彩色图和深度图,因此可以在同一图像位置,读取到色彩和距离信息,计算像素的3D相机坐标,生成点云。

      对于RGB-D数据,既可以在图像层面进行处理,还可以在点云层面处理

      RGB-D相机能够实时的测量每个像素点的距离,但是由于这种发射-接收的测量方式,其适用范围比较受限。不能在室外使用,同时使用多个时也会相互干扰。对于透射材质的物体,因为接收不到反射光,所以无法测量这些点的距离

  10. 图像:这里需要注意的点是:在图像中,数组的行数对应图像的高度,列数对应图像的宽度。平时说的图像的宽度或列数对应着X轴,而图像的行数或高度对应着Y轴
    unsigned char pixel = image[y][x];
               
  11. 一般,图像灰度使用unsigned char表示8位整数记录;各个像素与相机的距离使用unsigned short 0-65536表示

OpenCV实践

  1. 图片基础
    1. 注意对位于x,y处的像素的访问。所以可以得到,对于每一个像素点,它们的多通道值是连续存储的
      for ( size_t y=0; y<image.rows; y++ )
      	{
          // 用cv::Mat::ptr获得图像的行指针
          unsigned char* row_ptr = image.ptr<unsigned char> ( y );  // row_ptr是第y行的头指针
          for ( size_t x=0; x<image.cols; x++ )
          {
              // 访问位于 x,y 处的像素
              unsigned char* data_ptr = &row_ptr[ x*image.channels() ]; // data_ptr 指向待访问的像素数据
              // 输出该像素的每个通道,如果是灰度图就只有一个通道
              for ( int c = 0; c != image.channels(); c++ )
              {
                  unsigned char data = data_ptr[c]; // data为I(x,y)第c个通道的值
              }
          }
      }
                 

PCL实践

  1. 安装PCL点云库
    1. 因为先前已经安装了OpenCV和ROS,所以很多依赖不需要重复安装了

      安装sudo apt-get install libopenni-dev

      sudo apt-get install libopenni2-dev

  2. 从PCL的github上的release目录下,网址为1。 通过git clone方式下载下来
  3. mkdir build然后进入build目录,进行cmake和make操作即可。注意,PCL库的make过程比较漫长,如果卡了,可以尝试关闭当前terminal,重新make一下
  4. 下面进入了编译joinMap的工作。同3操作,不过最后运行时,需要在包含pose.txt的目录下运行,所以,输入指令为
    ./build/joinMap 
               
    此时会显示下述文字,同时在当前目录下生成一个map.pcd文件
    视觉SLAM十四讲——第五讲相机与图像
    视觉SLAM十四讲——第五讲相机与图像
    这是使用cloud_viewer显示的map.pcd影像,是可以进行任意角度旋转的
    视觉SLAM十四讲——第五讲相机与图像
  5. 源码分析:从源码中可以获得对于深度图、彩色图等更多相关的知识点
    1. for ( int i=0; i<5; i++ )
       {
           boost::format fmt( "./%s/%d.%s" ); //图像文件格式
           colorImgs.push_back( cv::imread( (fmt%"color"%(i+1)%"png").str() ));
           depthImgs.push_back( cv::imread( (fmt%"depth"%(i+1)%"pgm").str(), -1 )); // 使用-1读取原始图像
           
           double data[7] = {0};
           for ( auto& d:data )
               fin>>d;
           Eigen::Quaterniond q( data[6], data[3], data[4], data[5] );
           Eigen::Isometry3d T(q);
           T.pretranslate( Eigen::Vector3d( data[0], data[1], data[2] ));
           poses.push_back( T );
       }
                 
    从上述代码可以知道,位姿记录格式为x, y, z, rx, ry, rx, w格式,其中x, y, z表示平移, rx, ry, rx, w表示四元数,其中w表示四元数的实数部分
    1. // 计算点云并拼接
       // 相机内参 
       double cx = 325.5;
       double cy = 253.5;
       double fx = 518.0;
       double fy = 519.0;
       double depthScale = 1000.0;
                 
      其中,固定了depthScale表明样例中给定的图片的深度尺度已知
    2. cv::Mat color = colorImgs[i]; 
       cv::Mat depth = depthImgs[i];
       Eigen::Isometry3d T = poses[i];
       for ( int v=0; v<color.rows; v++ )
           for ( int u=0; u<color.cols; u++ )
           {
               unsigned int d = depth.ptr<unsigned short> ( v )[u]; // 深度值
               if ( d==0 ) continue; // 为0表示没有测量到
               Eigen::Vector3d point; 
               point[2] = double(d)/depthScale; 
               point[0] = (u-cx)*point[2]/fx;
               point[1] = (v-cy)*point[2]/fy; 
               Eigen::Vector3d pointWorld = T*point;
               
               PointT p ;
               p.x = pointWorld[0];
               p.y = pointWorld[1];
               p.z = pointWorld[2];
               p.b = color.data[ v*color.step+u*color.channels() ];
               p.g = color.data[ v*color.step+u*color.channels()+1 ];
               p.r = color.data[ v*color.step+u*color.channels()+2 ];
               pointCloud->points.push_back( p );
           }
                 

      其中,对color和depth的每一个元素同时进行遍历,就暗含了“相机已经将彩色图和深度图一一对应”

      对于u,v是指图片上的像素位置,表示像素空间

      Eigen::Vector3d point表示对应像素点在相机坐标系中的位置

      根据书本P60的公式,**相机位姿T(即相机坐标系在世界坐标系中的表示)**左乘当前点在相机中的坐标,等于当前点在世界坐标系中的坐标。p_w=T_w_c p_c

习题

  1. 由于暂时使用的是开源数据集,后续才开始实时采集,暂时不进行相机标定,后续用到了再来补充

继续阅读