双目立体匹配——归一化互相关(NCC)

  归一化相关性,normalization cross-correlation,因此简称NCC,下文中笔者将用NCC来代替这冗长的名称。

  NCC,顾名思义,就是用于归一化待匹配目标之间的相关程度,注意这里比较的是原始像素。通过在待匹配像素位置p(px,py)构建3*3邻域匹配窗口,与目标像素位置p'(px+d,py)同样构建邻域匹配窗口的方式建立目标函数来对匹配窗口进行度量相关性,注意这里构建相关窗口的前提是两帧图像之间已经校正到水平位置,即光心处于同一水平线上,此时极线是水平的,否则匹配过程只能在倾斜的极线方向上完成,这将消耗更多的计算资源。相关程度的度量方式由如下式子定义:

  

  上式中的变量需要解释一下:其中p点表示图像I1待匹配像素坐标(px,py),d表示在图像I2被查询像素位置在水平方向上与px的距离。如下图所示: 

    

  左边为图像I1,右边为图像I2。图像I1,蓝色方框表示待匹配像素坐标(px,py),图像I2蓝色方框表示坐标位置为(px,py),红色方框表示坐标位置(px+d,py)。(由于画图水平有限,只能文字和图片双重说明来完成了~)

  Wp表示以待匹配像素坐标为中心的匹配窗口,通常为3*3匹配窗口。

  没有上划线的I1表示匹配窗口中某个像素位置的像素值,带上划线的I1表示匹配窗口所有像素的均值。I2同理。

  上述公式表示度量两个匹配窗口之间的相关性,通过归一化将匹配结果限制在 [-1,1]的范围内,可以非常方便得到判断匹配窗口相关程度:

  若NCC = -1,则表示两个匹配窗口完全不相关,相反,若NCC = 1时,表示两个匹配窗口相关程度非常高。

  我们很自然的可以想到,如果同一个相机连续拍摄两张图像(注意,此时相机没有旋转也没有位移,此外光照没有明显变化,因为基于原始像素的匹配方法通常对上述条件是不具备不变性的),其中有一个位置是重复出现在两帧图像中的。比如桌子上的一个可乐瓶。那么我们就可以对这个可乐瓶的位置做一下匹配。直观的看,第一帧中可乐瓶上某一个点,它所构成邻域窗口按理说应该是与第二帧相同的,就算不完全相同,也应该是具有非常高相关性的。基于这种感性的理解,于是才有前辈提出上述的NCC匹配方法。(纯属个人理解)

  


  双目立体匹配,这一部分是说明NCC如何用于双目匹配。

  假设有校正过的两帧图像I1,I2,由上述NCC计算流程的描述可知,对图像I1一个待匹配像素构建3*3匹配窗口,在图像I2极线上对每一个像素构建匹配窗口与待匹配像素匹配窗口计算相关性,相关性最高的视为最优匹配。很明显,这是一个一对多的过程。如果图像尺寸是640*480,则每一个像素的匹配过程是是1对640,两帧图像完全匹配需要计算640*480*640 = 196608000,即一亿九千多万次~ 尽管计算机计算速度非常快,但也着实是非常消耗计算资源的。由于NCC匹配流程是通过在同一行中查找最优匹配,因此它可以并行处理,这大概也算是一种弥补吧~

  双目立体匹配流程如下:

  1. 采集图像:通过标定好的双目相机采集图像,当然也可以用两个单目相机来组合成双目相机。(标定方法下次再说)

  2. 极线校正:校正的目的是使两帧图像极线处于水平方向,或者说是使两帧图像的光心处于同一水平线上。通过校正极线可以方便后续的NCC操作。

        

      2.1 由标定得到的内参中畸变信息中可以对图像去除畸变,在OpenCV中有函数对去畸变做了实现

      void stereoRectify(InputArray cameraMatrix1, InputArray distCoeffs1, InputArray cameraMatrix2, InputArray distCoeffs2, Size imageSize, InputArray R, InputArray T, OutputArray R1, OutputArray R2, OutputArray P1, OutputArray P2, OutputArray Q, int flags=CALIB_ZERO_DISPARITY, double alpha=-1, Size newImageSize=Size(), Rect* validPixROI1=0, Rect* validPixROI2=0 )
      cameraMatrix1:第一个相机矩阵(这里我们是双目的左相机).
        cameraMatrix2: 第二个相机矩阵(双目的右相机).
        distCoeffs1:第一个相机畸变参数.
        distCoeffs2: 第二个相机畸变参数.
        imageSize:用于校正的图像大小.
        R:第一和第二相机坐标系之间的旋转矩阵(左相机相对于右相机的旋转)
        T:第一和第二相机坐标系之间的平移矩阵(左相机相对于右相机的位移)
      R1:输出第一个相机的3x3矫正变换(旋转矩阵) .
      R2:输出第二个相机的3x3矫正变换(旋转矩阵) .
      P1:在第一台相机的新的坐标系统(矫正过的)输出 3x4 的投影矩阵
      P2:在第二台相机的新的坐标系统(矫正过的)输出 3x4 的投影矩阵
      Q:输出深度视差映射矩阵
      flags:操作的 flag可以是零或者是CV_CALIB_ZERO_DISPARITY。如果设置了CV_CALIB_ZERO_DISPARITY,函数的作用是使每个相机的主点在校正后的图像上有相同的像素坐标;如果未设置标志,功能还可以改变图像在水平或垂直方向(取决于极线的方向)来最大化有用的图像区域。
      alpha:自由缩放参数。如果是-1或没有,该函数执行默认缩放。否则,该参数应在0和1之间。alpha=0,校正后的图像进行缩放和偏移,只有有效像素是可见的(校正后没有黑色区域);alpha= 1意味着校正图像的抽取和转移,所有相机原始图像素像保留在校正后的图像(源图像像素没有丢失)。显然,任何中间值产生这两种极端情况之间的中间结果。
         newImageSize:校正后新的图像分辨率。
      validPixROI1: 校正后的图像可选的输出矩形,里面所有像素都是有效的。如果alpha= 0,ROIs覆盖整个图像。否则,他们可能会比较小。
      validPixROI2: 校正后的图像可选的输出矩形,里面所有像素都是有效的。如果alpha= 0,ROIs覆盖整个图像。否则,他们可能会比较小。
      
      2.2 通过校正函数校正以后得到相机的矫正变换R和新的投影矩阵P,接下来是要对左右视图进行去畸变,并得到重映射矩阵。这里,我们还是用OpenCV函数
      void initUndistortRectifyMap( InputArray cameraMatrix, InputArray distCoeffs, InputArray R, InputArray newCameraMatrix, Size size, int m1type, OutputArray map1, OutputArray map2 )
      cameraMatrix:输入的摄像机内参数矩阵
      distCoeffs:输入的摄像机畸变系数矩阵
      R:输入的第一和第二相机坐标系之间的旋转矩阵(我们这里是利用上述校正得到的旋转矩阵)
      newCameraMatrix:输入的校正后的3X3摄像机矩阵(我们这里是使用上述校正得到的投影矩阵)
      size:摄像机采集的无失真图像尺寸
      m1type:map1的数据类型,可以是CV_32FC1或CV_16SC2
      map1:输出的X坐标重映射参数
      map2:输出的Y坐标重映射参数
      2.2 根据上述得到的重映射参数map1,map2,我们需要进一步对原始图像进行重映射到新的平面中才能去除图像畸变,同样,实现方式仍是使用现有的OpenCV函数

                    void remap(InputArray src, OutputArray dst, InputArray map1, InputArray map2, int interpolation, intborderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar() )

      src:输入图像,即原图像,需要单通道8位或者浮点类型的图像

      dst:输出图像,即目标图像,需和原图形一样的尺寸和类型

      map1:它有两种可能表示的对象:(1)表示点(x,y)的第一个映射;(2)表示CV_16SC2,CV_32FC1等类型的x值矩阵

      map2:它有两种可能表示的对象:(1)若map1表示点(x,y)时,这个参数不代表任何值;(2)表示CV_16UC1,CV_32FC1等类型y值矩阵

      interpolation:插值方式,有四中插值方式:(1)INTER_NEAREST——最近邻插值,(2)INTER_LINEAR——双线性插值(默认),(3)INTER_CUBIC——双三样条插值(默认),(4)INTER_LANCZOS4——lanczos插值(默认)

      intborderMode :边界模式,默认BORDER_CONSTANT

      borderValue :边界颜色,默认Scalar()黑色

      2.3 通过上述两步操作,我们成功地对图像去除了畸变,并且校正了图像极线。注意,在立体校正阶段需要设置alpha = 0才能完成对图像的裁剪,否则会有黑边。

  3. 特征匹配:这里便是我们利用NCC做匹配的步骤啦,匹配方法如上所述,右视图中与左视图待测像素同一水平线上相关性最高的即为最优匹配。完成匹配后,我们需要记录其视差d,即待测像素水平方向xl与匹配像素水平方向xr之间的差值d = x- xl,最终我们可以得到一个与原始图像尺寸相同的视差图D。

  4. 深度恢复:通过上述匹配结果得到的视差图D,我们可以很简单的利用相似三角形反推出以左视图为参考系的深度图。计算原理如下图所示:
        

        如图,Tx为双目相机基线,f为相机焦距,这些可以通过相机标定步骤得到。而x- xl就是视差d。

        通过公式 z = f * Tx / d可以很简单地得到以左视图为参考系的深度图了。

  至此,我们便完成了双目立体匹配。倘若只是用于图像识别,那么到步骤3时已经可以结束了。


  OK,最后一部分就是代码实现部分了,哎~ 写太累了,下次再补上。

  未完待续。。。

  

      

 

 

        

 

  

posted @ 2017-08-13 17:30  小C酱油兵  阅读(2784)  评论(0编辑  收藏