双目Stereo重建算法SGM(1) - 互信息(Mutual Information)

1 概述

对于机器视觉,我目前还是刚起步,可能步还没起来。


近日开始接触双目Stereo的相关算法,主要是对Heiko Hirschmüller在2008年的文献(Hirschmüller 2008)中描述的算法进行研究。该文献,也就是利用互信息(Mutual Information)作为计算matching cost的基础进行stereo重建的工作,在相关领域内的影响还是比较大的。他所使用的cost aggregation的方法,也运用在了目前OpenCV(当前我使用的是3.4.1)中的SGBM方法中。这些日子试图复现文献中的算法,但是由于自己基础薄弱,很多地方并不能完全理解和复现,这里仅作为研究过程中的记录,以备自己进行回溯以及与同行们的交流讨论。


在复现算法的过程中,使用OpenCV 3.4.1和C++编写测试程序。程序运行在Ubuntu 16.04 64bit的系统上。OpenCV是在本地编译为Debug版本,以方便对程序进行调试。


对算法的复现将分步进行,本文主要描述对Mutual Information相关数值的计算。


2 基本假设

为了简化分析,现假设组成双目相机的两个相机是完全一样的,并且已经进行过stereo calibration,所获得图片已经是rectify过的灰度图像,在OpenCV中,这些图像的数据type为CV_8UC1。


3 Warp

为了计算matching cost,需要利用mutual information。根据(Hirschmüller 2008)在文中的描述,计算双目中两个图片的mutual information时,需要针对当前的disparity map,对其中一个图片进行warp处理。这里这个“warp”却也是我第一次遇到,辞典上有一个非常fancy的翻译,叫“翘曲”。其实有“弯曲,扭曲”的含义。我们这里通常可以定义双目中左相机的图像为参考图像(不变),右相机的图像为匹配图像,disparity map即代表右图像向左图像进行匹配时,右图像上每个像素与左图像上最佳匹配像素的水平(x)坐标差。原文要求我们在计算mutual information时,需要对右图像进行warp处理。所谓warp处理,就是对于左图像中的每一个像素(x1i, y1i),根据当前的disparity map,在右图中找到与之match的像素(x2i, y1i),从右图中将这个像素取出,放入一个新图像(warped image)中的(x1i, y1i)位置。这样就可以得到一个warped image。计算mutual information时,将使用左图像和这个warped image进行,而不再使用右图像。

这里实际上存在一个问题。原文并未指明如何进行warp,尤其在左右图像match时对无法得到disparity的像素如何处理。首先,由于左右相机在拍摄同一场景时必定存在一个overlap,这个overlap一般情况下不可能达到100%的FOV(Field of View),在不存在overlap的像素区域,不存在disparity。这个情况原文并未给出处理方法。其次,在实际进行disparity计算中,对应于某些处于overlap区域的像素,优化算法仍可能给出disparity不可靠的结果,并最终认为该像素不具备有效的disparity。对于无disparity结果的像素,原文也未给出处理方法。

我目前采用了naive的方法:对于右图中无disparity的像素,不对其进行warp,保留其在原始位置。这种处理方法并不一定正确,并且暂时无法保证像素覆盖的情况。后面需要进行优化和处理


4 Joint Entropy and Mutual Information

原文指出,计算mutual information的关键过程是对joint entropy的近似。这可以总结为原文中的式(4)、(5)和(6)。



其中,HI1,I2即为joint entroy。这里我花了蛮久时间理解它们。

首先需要明确的符号语言,I1I2表示以intensity表示的左右图像,而则I1pI2p表示左右图像上某个像素位置上的intensity。这里I2表示warp处理过的图像。从而HI1,I2为一个2D的数据,是一个2D表,这个表的维度是256x256。无论I1I2自身的size是多大,HI1,I2的维度始终为256x256。

第二,原文式(5)中,对PI1,I2进行了2次卷积。卷积核皆为Gaussian kernel。由于我对概率统计方面的理论基础很薄弱,花了很多时间试图去理解式(5)的意图。原文解释第一次卷积是进行了一次Parzen estimation。所谓Parzen estimation实际是指Kernel Density Estimation (KDE)。KDE似乎是概率统计中很基础的概念,是一种无参数化的概率密度估计方法。所谓“无参数化”即指在对随机变量的概率密度进行估计时,不指定概率密度函数的形式,而完全依赖所测得的实际数据。式(5)的第一次卷积即为进行了一次KDE,从而得到了PI1,I2的概率分布的估计。对于KDE的理解,可参考如下几个网页。


Kernel density estimation: https://en.wikipedia.org/wiki/Kernel_density_estimation

Kernel (statistics): https://en.wikipedia.org/wiki/Kernel_(statistics)

Can you explain Parzen window (kernel) density estimation in layman's terms?: https://stats.stackexchange.com/questions/244012/can-you-explain-parzen-window-kernel-density-estimation-in-laymans-terms

How to interpret the bandwidth value in a kernel density estimation?: https://stats.stackexchange.com/questions/226232/how-to-interpret-the-bandwidth-value-in-a-kernel-density-estimation


KDE与卷积运算的关系可参考

http://ieor.berkeley.edu/~ieor165/ 页面上的第10讲,Distribution Estimation。


原文式(5)的第二次Gauss卷积,真的没有理解它的具体意图。原文解释是将KDE的结果进一步变为lookup table,然而我并没有get到。


原文式(5)的计算中有两个不确定的问题。第一,n的取值。n的取值我认为是图像的像素总数(number of all correspondences)。第二,式(5)中出现了对数计算,这个计算不允许自变量为零。原文中也指出自变量不可以为零,但是实际情况中自变量很可能出现零,甚至是必然有零。原文推荐将零替换为一个较小的正数,以应适应对数函数的定义域。那么多小算是“小”,原文并未给出。在后面的算法复现中,使用的值为1e-30。该数值实际上是随意取出的。在mutual information的计算中,我预计使用单精度的浮点数类型进行数值运算。在IEEE标准中,单精度浮点数能够表达的数值范围下限在10-38左右(Single-precision floating-point format ),取1e-30仅是一个个人爱好而已。然后需要注意的一点是OpenCV的版本问题。在2.x的OpenCV版本中,对于Mat对象的对数运算,其特性是与在3.4版本不一致的。可以参考


v2.4: https://docs.opencv.org/2.4/modules/core/doc/operations_on_arrays.html#void%20log(InputArray%20src,%20OutputArray%20dst)

v3.4:

https://docs.opencv.org/3.4/d2/de8/group__core__array.html#ga937ecdce4679a77168730830a955bea7


总结起来就是3.4版本对于自变量出现负值、零、NaN和Inf的处理更符合对log( )函数预期。3.4版本中,log( )函数对自变量为负值、零、NaN和Inf的处理是“未定义”,所以强制用户对上述情况做出处理,实际上我认为这确实提高了程序的健壮性和可调式性。


讨论完上述两个问题,接下来是如何实现卷积运算。实际上卷积运算可以看作是一次图像的虚化处理(smoothing)。使用Gaussian kernel作为卷积核时,就是对图像进行Gaussian blur。这样可以使用OpenCV提供的GaussianBlur( )函数进行处理。所需要特殊注意的是OpenCV的GaussianBlur( )函数默认提供对图像边界(border)的处理,其默认的边界类型为BORDER_DEFAULT也就是BORDER_REFLECT_101。这个设计可见OpenCV源码,所在位置为opencv2/core/base.hpp的268行。



我预计使用BORDER_ISOLATED,表示不使用边界外的数据,但是ROI如何定义,OpenCV在GaussianBlur的文档里也没有明确表达。我暂时就这样使用了。


而关键的问题是原文并未说明进行卷积时,图像的边界如何处理。


进行Gaussian smoothing时,原文推荐使用过7x7的核,这在OpenCV的实现中是非常容易的,仅是GaussianBlur( )函数的一个参数。


卷积运算部分讨论后,最后就是原文式(6)的计算。实际上我最开始直接想到,式(6)实际上是运用两幅单通道的灰度图计算出了一个2D的直方图。只不过这个直方图比较特殊,它的每个维度范围都是0-256,并且分成了256份。这一点也在如下网页的描述上得到了印证


How can I calculate the joint entropy of two images in Opencv: https://stackoverflow.com/questions/14898349/how-can-i-calculate-the-joint-entropy-of-two-images-in-opencv


于是式(6)的运算就变为在OpenCV中进行一次2D直方图运算。OpenCV提供了calcHist( )函数。这个函数的channels参数的定义非常迷。经过几次调试之后,可以确定当calcHist( )的输入图像为两个单通道的灰度图像时,channels定义为{0, 1},这表示使用第一幅图像的第一个通道,和第二个图像的第一个通道。然后剩下一个问题是与OpenCV自身的设计有关的,即所生成直方图的数据type。OpenCV在这里的处理也比较隐晦,经过测试,使用两个CV_8UC1类型的灰度图像,生成的直方图的数据type为CV_32FC1,也就是单精度浮点数(float)。这是正确的方法,若生成直方图仍使用某些整数类型,可能导致类型的数值溢出。


以上便是对原文式(4)、(5)和(6)的讨论。


5 Entropy of a Single Image

为了最后计算得到mutual information值,仍然需要计算两个图像自身的entropy。这在原文中被推荐通过joint entropy得到。具体方法非常直接,即对2D的PI1,I2沿着行或列累加,得到两个图像各自的P。然后通过原文的式(7a)和(7b)求得各个图像的entropy。这里仅有一个问题,即式(7a)和(7b)中的n的取值。我认为仍然去整张图像的像素数量。


OpenCV提供了直接进行这个工作的函数,reduce( )。使用reduce( )函数时,数据type使用的仍然是CV_32FC1。


6 Mutual Information

经过上述讨论,并结合原文中式(8a)、(8b)、(9a)和(9b)的说明,初步的互信息计算已经可以进行了。但实际上现阶段仍然需要假定一个disparity map。


使用目前自己搭建的一个custom stereo camera,对一个静态的墙面进行拍摄,利用所获得的图像为例进行mutual information的计算。


左右相机的图像如图 1图 2所示。


图 1 左图像(缩放调整后)

图 2 右图像(缩放调整后)


计算所得到的互信息图像如图 3所示,此时采用的是常量disparity map,所用的数值是随意选取的。为了能够采用灰度图像的形式显示mutual information,这里将每个位置上的数值进行了归一化处理。经归一化处理的浮点数Mat对象,OpenCV的imshow( )函数是能够以灰度形式显示的。若需要保存一个灰度图像,则需要将这个Mat对象与255相乘,而后才能正确保存。


图 3 Mutual information


从图形上看,整体形貌与(Hirschmüller 2008)中的实例相接近,不同的是原文中图形的y坐标都是朝上的,而默认的图片坐标系,y轴都是自上而下的。另外,当disparity map不够理想时,mutual information的分布会出现如图 3所示的沿对角线的弥散状。当disparity map非常理想时,mutual information的图形应该在对角线上出现高值(白亮)在远离对角线处出现低值(暗淡)。较好的disparity map表示左图和warped image之间的match非常好,大部分joint entropy都将出现在对角线上,最后形成的mutual information图形,在对角线上应当能够看到较为白亮的带。这里,进行了一个额外的测试,使用左图替代右图,即两幅图完全一样。所生成的mutual information灰度图如图 4所示(此时disparity map为全零),可以看到,在图像上确实出现了较亮的对角线带。


图 4 相同输入图像生成的mutual information图像


后期会针对原文的算法进行进一步研究和复现,现在先做到这里。

参考文献

Hirschmüller, Heiko. 2008. “Stereo Processing by Semiglobal Matching and Mutual Information.” IEEE Transactions on Pattern Analysis and Machine Intelligence 30 (2): 328–41. https://doi.org/ 10.1109/TPAMI.2007.1166 .

你可能感兴趣的:(robotics)