vSLAM重读(5): vSLAM中对双目相机的数据处理及与单目相对比

1. 双目相机概述

  • 双目立体视觉模型

    双目模型求取深度

    • 双目立体相机分别校准可参考 ROS_单目相机_分别校准
  • 双目立体匹配算法案例

    https://www.cnblogs.com/riddick/p/8486223.html
    https://www.cnblogs.com/zyly/p/9373991.html

  • 双目相机的特性

    1.双目相机有左右两个视野图,所以有了参数基线

    • 基线的特性

      1.当系统的硬件结构固定不变,则通过外参校准的T中的位移量可对比参考基线长;且工作距离越大,测量精度越低。

      2.当基线增大时,FOV中水平角在增大,其对精度的影响是非线性的。

  • 双目相机的矫正

    • 相机内参标定

      相机内参标定,获取相机座标系与图像座标系之间的投影关系,如(fx, fy, cx, cy, k1, k2, k3, 等)

    • 相机外参标定

      相机外参反应的是摄像机座标系和世界座标系之间的旋转R+T关系,双目相机外参主要获取两个相机之间“基线”

  • 双目相机的立体匹配算法

    在双目相机中特征点的匹配与单目类似,用光流法跟踪特征,选取匹配点。

    然后对匹配的特征点对计算本质矩阵,再分解矩阵获得旋转矩阵R和平移矩阵t;

    • 基于局部的块匹配 - BM(Block Matching StereoBM)

    • 半全局块匹配 - SGBM(Semi-Global-Block-Matching)

    SGBM算法要远远优于BM算法。

2. 单目相机+双目相机+深度相机(结构光)+TOF对比

  • 单目无法确认深度

单目SLAM不受环境大小的影响,因此既可以用于室内,又可以用于室外。

  • 双目相机

双目相机标定配置较为复杂(why?)(好像还有一个对准,有点不太记得…欢迎补充):

1. 双目相机的各个单独的镜头需要内参校准;
2. 双目相机的各个单独的镜头需要外参校准;
3. 双目相机两个相机之间需要校准;
4. 双目相机的两个相机校准之后还要与出厂的基线对齐,若误差较大则校准失败。

双目相机的优势: 它不管在静止和运动下都可以直接估计场景的深度值。其深度量程受双目的基线和分辨率限制。

另外,使用双目相机去计算视差,还原深度值,比较耗时(如SGBM立体匹配)。

  • 结构光测量 (主动式测量:可以直接获取距离值)

3D结构光投射的是散斑或编码图案,接收模组需要拍摄到清晰的图案才能计算出深度。

而随着距离的增加,投出的图案或出现模糊,或出现亮度能量上的衰减,导致深度图不完整,出现破洞,甚至于失效,所以3D结构光并不适用于远距离深度信息采集。

结构光在室外容易受到强光的影响,效果很差。因为光斑成像很容易受到环境的干扰。
结构光的测量距离一般较近(0.1~10m)如realsense D435
  • TOF(飞行时间测距法) (主动式测量:可以直接获取距离值)

TOF是通过红外光发射器发射调制后的红外光脉冲,不停地打在物体表面,经反射后被接收器接收,通过相位的变化来计算时间差,进而结合光速计算出物体深度信息

不受环境光照的影响,室内室外都可行。TOF测量的距离一般较远(0.4~130m) 如VLP16

3. 视差图和深度图的填充方法

以视差图dispImg为例。计算图像的积分图integral,并保存对应积分图中每个积分值处所有累加的像素点个数n(空洞处的像素点不计入n中,因为空洞处像素值为0,对积分值没有任何作用,反而会平滑图像)。

采用多层次均值滤波。首先以一个较大的初始窗口去做均值滤波(积分图实现均值滤波就不多做介绍了,可以参考我之前的一篇博客),将大区域的空洞赋值。然后下次滤波时,将窗口尺寸缩小为原来的一半,利用原来的积分图再次滤波,给较小的空洞赋值(覆盖原来的值);依次类推,直至窗口大小变为3x3,此时停止滤波,得到最终结果。

多层次滤波考虑的是对于初始较大的空洞区域,需要参考更多的邻域值,如果采用较小的滤波窗口,不能够完全填充,而如果全部采用较大的窗口,则图像会被严重平滑。因此根据空洞的大小,不断调整滤波窗口。先用大窗口给所有空洞赋值,然后利用逐渐变成小窗口滤波覆盖原来的值,这样既能保证空洞能被填充上,也能保证图像不会被过度平滑

void insertDepth32f(cv::Mat& depth)
{
    const int width = depth.cols;
    const int height = depth.rows;
    float* data = (float*)depth.data;
    cv::Mat integralMap = cv::Mat::zeros(height, width, CV_64F);
    cv::Mat ptsMap = cv::Mat::zeros(height, width, CV_32S);
    double* integral = (double*)integralMap.data;
    int* ptsIntegral = (int*)ptsMap.data;
    memset(integral, 0, sizeof(double) * width * height);
    memset(ptsIntegral, 0, sizeof(int) * width * height);
    for (int i = 0; i < height; ++i)
    {
        int id1 = i * width;
        for (int j = 0; j < width; ++j)
        {
            int id2 = id1 + j;
            if (data[id2] > 1e-3)
            {
                integral[id2] = data[id2];
                ptsIntegral[id2] = 1;
            }
        }
    }
    // 积分区间
    for (int i = 0; i < height; ++i)
    {
        int id1 = i * width;
        for (int j = 1; j < width; ++j)
        {
            int id2 = id1 + j;
            integral[id2] += integral[id2 - 1];
            ptsIntegral[id2] += ptsIntegral[id2 - 1];
        }
    }
    for (int i = 1; i < height; ++i)
    {
        int id1 = i * width;
        for (int j = 0; j < width; ++j)
        {
            int id2 = id1 + j;
            integral[id2] += integral[id2 - width];
            ptsIntegral[id2] += ptsIntegral[id2 - width];
        }
    }
    int wnd;
    double dWnd = 2;
    while (dWnd > 1)
    {
        wnd = int(dWnd);
        dWnd /= 2;
        for (int i = 0; i < height; ++i)
        {
            int id1 = i * width;
            for (int j = 0; j < width; ++j)
            {
                int id2 = id1 + j;
                int left = j - wnd - 1;
                int right = j + wnd;
                int top = i - wnd - 1;
                int bot = i + wnd;
                left = max(0, left);
                right = min(right, width - 1);
                top = max(0, top);
                bot = min(bot, height - 1);
                int dx = right - left;
                int dy = (bot - top) * width;
                int idLeftTop = top * width + left;
                int idRightTop = idLeftTop + dx;
                int idLeftBot = idLeftTop + dy;
                int idRightBot = idLeftBot + dx;
                int ptsCnt = ptsIntegral[idRightBot] + ptsIntegral[idLeftTop] - (ptsIntegral[idLeftBot] + ptsIntegral[idRightTop]);
                double sumGray = integral[idRightBot] + integral[idLeftTop] - (integral[idLeftBot] + integral[idRightTop]);
                if (ptsCnt <= 0)
                {
                    continue;
                }
                data[id2] = float(sumGray / ptsCnt);
            }
        }
        int s = wnd / 2 * 2 + 1;
        if (s > 201)
        {
            s = 201;
        }
        cv::GaussianBlur(depth, depth, cv::Size(s, s), s, s);
    }
}

4. 相对於单目, 双目相机的劣势?

1.配置及标定比较复杂,两个镜头都需要单独标定内参和外参矩阵,然后将两个相机联合校准计算基线,并与出厂参数进行对照,
    若相差较大,则需要重新校准;

2.双目(900--1600)成本相对於单目相机(50--200)贵;

3.双目相机是被动式数据获取,单目相机是主动式数据获取; 双目通过视差可以获取深度值,而单目需要运动才能获取深度信息;

4.双目在计算立体配准获取视差时(像素级计算,若想图像块计算则会模糊深度值),需要消耗较大的计算量。而单目不需要大量或者GPU计算;

5.所以可能就是因为从成本、计算量、实时性等方面考虑, 目前单目落地应用相比于双目要多一些。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章