—— patch match应用到立体匹配的一篇论文,晦涩难懂,硬是硬着头皮看了几遍
NOTE
随机搜索的最优平面和视差的关系?
“estimate an individual 3D plane at each pixel”是对每一个窗口的中心点都找一个最优平面?
随机值初始化的理论基础?
在全局法中的作用
实际实现理解
answer:
- 当空间中的点x, y均确定,法向量也确定之后,最优平面和最优视差有着一一对应的关系,注意这个平面是定义在(x, y, 视差)空间下的平面
- 对,每一个点都会有一个平面,在搜索和传播的时候通过邻域视差相似性以及左右视差一致性来进行传播,而是否传播的条件就是对比传播前后该点的代价值是否减小。这时候就涉及到了代价聚合的思想,在搜索传播的过程中已经完成代价聚合。
- TODO
- 见With Global Methods部分
- 见第6部分:代码理解
前言
局部法窗口内视差为常数的假设
传统局部法求代价聚合的方法都是假设一个窗口内的视差为常数,在包含边缘的窗口以及斜面的窗口,此情况便不成立了;
对于包含边缘的窗口这类场景,已经有BFCA, GFCA等保边平滑滤波应用到代价聚合来处理这个问题,避免代价值跨边缘聚合;
亚像素精度的实现
通常有两种方法:
- disparity refine中用各种fitting来实现亚像素增强
- 在匹配过程中就将此问题考虑进去(这时候,看上去就不得用WTA了吧)
Abstract
主要思想:
对局部窗口中的每一个点,均估计一个最优平面;
最优平面对应着最优视差,最优视差对应着最低左右匹配代价值;
最优平面的估计方法:
- spatial propagation:planes propagate among the center point and its neighbor points
- view propagation: planes propagate among the left view and the right view
- temporal propagation: planes propagate among the preceding and consecutive frames
贡献点:
- 将Patch Match应用到立体匹配中,用于解决包含边缘的局部窗口以及包含斜面的窗口内视差不符合常数这个假设的问题;
- 在匹配过程中,同步搜索最优平面以实现亚像素精度视差匹配
- 可扩展至全局法立体匹配中,优化其效果;
贡献点1的体现可以下面这张图来体现:
常规的局部法匹配,通常都是用figure 1(a)来假设窗口内的视差值,和实际视差并不符,figure 1(b)是本文的思想,对比即可知。
性能与效果:
性能就不说了,真的是有点慢,n多个全图代价计算与视差更新循环...
效果:目前还未进行大规模图集测试,作者说是当时Middlebury上排第2的,局部法当中效果最好的。
Future:
关于future的第一要务,肯定是加速,作者也说要尝试在GPU上实现real-time了;
另一方面,还想尝试应用到光流领域。
研究背景
算法
模型
对每个像素点\(p\), 寻找一个最优平面\(f_p\), 一旦\(f_p\)找到,则这个点的视差便可由下式计算得到:
\[d_p = a_{f_p}p_x + b_{f_p}p_y + c_{f_p} \tag{1} \]
最优平面的定义:
\[f_p = \arg\min_{f\in\mathcal{F}}m(p, f) \tag{2}\]
值得注意:\(\mathcal{F}\)的取值是无穷的,\(m(p, f)\)就是常规的代价聚合公式:
\[m(p, f) = \sum_{q \in W_p}{w(p, q) \cdot \rho(q, q - (a_{f_p}p_x + b_{f_p}p_y + c_{f_p}))} \tag{3}\]
其中的\(w(p, q) = e^{- \frac{|I_p - I_q|}{\gamma}}\), 就是常规的颜色相似度度量,而\(\rho(q, q - (a_{f_p}p_x + b_{f_p}p_y + c_{f_p}))\)为匹配代价SAD + gradient,其的计算方式如下:
\[\rho(q, q') = (1 - \alpha) \cdot \min(|I_q - I_q'|, \tau_{col}) + \alpha \cdot \min{(|\nabla{I_q} - \nabla{I_q'}|, \tau_{grad})}\tag{4}\]
With Patch Match
前一部分已经很清楚的叙述了整个匹配的思想,遗留问题是对于每个像素点\(p\),寻找一个最优平面\(f_p\),这个平面要怎么寻找呢?
作者用patch match的思想来解决,他自己也称:
We show that an ideal algorithm to solve this problem is PatchMatch [1] that we extend to find an approximate nearest neighbor according to a plane
随机值初始化的理论基础——大数定理?
We find the plane for a region by initializing each pixel with a random plane. The hope is that after this random initialization at least one pixel of the region carries a plane that is close to the correct one.
基于patch match展开的一系列迭代优化:
Random Initialization
对于点\((x_0, y_0)\),随机赋值\(minDisp < z0 < maxDisp\),即有\(P = (x_0, y_0, z_0)\)在平面上, 设平面法向量为 \(\vec{n} = (n_x, n_y, n_z)\), 则公式1中的系数有:
\[a_f := -\frac{n_x}{n_z}, b_f := - \frac{n_y}{n_z}; c_f := \frac{n_xx_0 + n_yy_0 + n_zz_0}{n_z}\]
上式由点法式方程结合式1可推导得到,点法式方程如下:
\[ n_x \cdot (x - x_0) + n_y \cdot (y - y_0) + n_z \cdot (z - z_0) = 0 \tag{5}\]
而上式中的 \(z\) 即为传说中的视差 \(d_p\)
NOTE:
- 可以通过强制\(\vec{n} = (0, 0, 1)\)来将窗口设置为fronto-parallel
- 可以通过强制视差为整数来关闭亚像素优化
Iteration
作者一共进行了3次迭代,奇数次迭代从左上角像素遍历到右下角像素,偶数次迭代从右下角遍历到左上角;
每次迭代需要依次对左视角和右视角执行以下4步。。。
相当于以下4步要全图做6遍,这速度怎么可能快得起来。。。
-
Spatial Propagation
主要思想:相邻像素点间很可能具有相似的平面
伪代码: check \(m(p, f_q) < m(p, f_p)\),如果是,则将当前点的平面更新为邻域点\(q\)的平面\(f_q\)
Note: 在奇数次迭代,check当前点左边和上边两个点的平面,在偶数次迭代,check当前点右边和下边两个点的平面
-
View Propagation
主要思想:一个像素点和其在另一视角匹配点间很可能具有相似的平面
We check all pixels of the second view that have our current pixel p as a matching point according to their current plane.
伪代码: check \(m(p, f_p') < m(p, f_p)\),如果是,则将当前点的平面更新为另一视角匹配点\(p'\)的平面\(f_p'\)
-
Temporal Propagation
——主要用于视频处理
主要思想:在视频连续帧间,同一位置的像素点很可能具有相似的平面
伪代码:check \(m(p, f_p') < m(p, f_p)\),如果是,则将当前点的平面更新为另一帧中对应位置点\(p'\)的平面\(f_p'\)
-
Plane Refinement
——看上去逻辑和patch match那个搜索有关系
设\(z_0\)在空间中变化范围为:\([-\Delta_{z_0}^{max}, \Delta_{z_0}^{max}]\),法向量\(\vec{n}\)的变化范围为 \([-\Delta_{n}^{max}, \Delta_{n}^{max}]\), 设\(z_0' := z_0 + \Delta_{z_0}\), \(\vec{n'} := unit(\vec{n} + \vec{\Delta_n})\), 通过由点\(P' = (x_0, y_0, z_0')\)和法向量\(\vec{n'}\)来确定的新平面\(f_p'\);
check \(m(p, f_p') < m(p, f_p)\),如果是,则将当前点的平面更新为平面\(f_p'\)。
这部分的refine是迭代的,看上去就是一个二分搜索,同时对\(z\)和法向量\(\vec{n}\)的搜索:
刚开始\(\Delta_{z_0}^{max} = maxdisp / 2\), \(\Delta_{n}^{max}=1\), 接着以\(\Delta_{z_0}^{max} := \Delta_{z_0}^{max} / 2, \Delta_{n}^{max} := \Delta_{n}^{max} / 2\)幅度减小搜索半径;
作者在注释部分提出,阔以用梯度下降替代这一步。
Post-processing
整个后处理流程比较常规:LR check出可靠区域和不可靠区域(通常是遮挡区域和误匹配区域),对不可靠区域用可靠区域的值进行填充,最后使用权重中值滤波来refine,主要是为了去除一些水平条纹
- LR Check
- Invalidated Pixel Filling
- 用与此不可靠点最相近的可靠点左右视差最小值来进行填充,而非直接填充常数
- Weighted Median Filter
- 对填充区域使用WMF
With Gloabal Methods
全局法中使用的基于局部窗口计算的自适应加权匹配代价具有获取视差不连续区域的特性,但也继承了"fronto-parallel"的问题;
作者指出,只要窗口半径为1,就不存在这个问题,也就是对每个像素点计算了一个匹配代价,在补充材料里提出这种匹配代价阔以embed为能量函数的数据项,用于全局法优化;
数据项[8] + 平滑项[13] + \(\alpha\)-expansion[14]优化,思想如下:
代码理解
// spatial propagation
/*********************************************************************
1. 设当前点对应的平面为旧平面
2. 左邻域点对应的平面为新平面(设当前为奇数次迭代)
3. 注意p_neigb = planes[cpv](ny, nx)传入plane_match_cost函数中计算cost值:
实质就是将上一次(包含随机初始化)邻域点的视差作为当前点视差传入plane_match_cost
计算匹配代价;对应着Iteration部分的spatial propagation的思想“相邻像素点间很
可能具有相似的'平面' <=> '视差'”
*********************************************************************/
for(auto it = offsets.begin(); it < offsets.end(); ++it)
{
std::pair<int, int> ofs = *it;
int ny = y + ofs.first;
int nx = x + ofs.second;
if(!inside(nx, ny, 0, 0, cols, rows))
continue;
Plane p_neigb = planes[cpv](ny, nx);
float new_cost = plane_match_cost(p_neigb, x, y, WINDOW_SIZE, cpv);
if(new_cost < old_cost)
{
old_plane = p_neigb;
old_cost = new_cost;
}
}
// plane refinement
// 这部分比较直观,就是空间上的二分随机搜索,由随机平面法向量和随机视差确定,具体见理论部分
int sign = (cpv == 0) ? -1 : 1; // -1 processing left, +1 processing right
float max_dz = max_delta_z;
float max_dn = max_delta_n;
Plane& old_plane = planes[cpv](y, x);
float& old_cost = costs[cpv](y, x);
while(max_dz >= end_dz)
{
// Searching a random plane starting from the actual one
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> rand_z(-max_dz, +max_dz);
std::uniform_real_distribution<> rand_n(-max_dn, +max_dn);
float z = old_plane[0] * x + old_plane[1] * y + old_plane[2];
float delta_z = rand_z(gen);
cv::Vec3f new_point(x, y, z + delta_z);
cv::Vec3f n = old_plane.getNormal();
cv::Vec3f delta_n(rand_n(gen), rand_n(gen), rand_n(gen));
cv::Vec3f new_normal = n + delta_n;
cv::normalize(new_normal, new_normal);
// test the new plane
Plane new_plane(new_point, new_normal);
float new_cost = plane_match_cost(new_plane, x, y, WINDOW_SIZE, cpv);
if(new_cost < old_cost)
{
old_plane = new_plane;
old_cost = new_cost;
}
max_dz /= 2.0f;
max_dn /= 2.0f;
}
// view propagation,这部分逻辑有点绕,实际上就是左右视差一致性?
/*********************************************************************
1. 13行提取当前视角下当前点对应的平面
2. 19行将当前点视角变换后到对应视角的点计算出来(简单理解为根据当前点平面查到当前点视差,
然后找到对应视角找对应的点,将(x + sign * 视差, y, 视差)构造为新平面new_plane
3. 而对应视角下的点(x + sign * 视差, y)在上一次计算后会有自己的一个cost:old_cost
对应着Iteration部分的view propagation的思想“左右视角下,匹配的两个点具有相似的
'平面' <=> '视差'”
*********************************************************************/
int sign = (cpv == 0) ? -1 : 1; // -1 processing left, +1 processing right
// current plane
Plane view_plane = planes[cpv](y, x);
// computing matching point in other view
// reparameterized corresopndent plane in other view
int mx, my;
Plane new_plane = view_plane.viewTransform(x, y, sign, mx, my);
if(!inside(mx, my, 0, 0, views[0].cols, views[0].rows))
return;
// check if this reparameterized plane is better in the other view
float& old_cost = costs[1-cpv](my, mx);
float new_cost = plane_match_cost(new_plane, mx, my, WINDOW_SIZE, 1-cpv);
if(new_cost < old_cost)
{
planes[1-cpv](my, mx) = new_plane;
old_cost = new_cost;
}
【论文笔记】PatchMatch Stereo - Stereo Matching with Slanted Support Windows