前言
- 目标读者:对密集匹配,三维重建等有基本概念并感兴趣的人群。
- 文章及代码资源:
提取码:ypda
- 导读:
1. 核心公式
SGM虽然名字上称为半全局匹配,但实际上还是采用的全局匹配算法中最优化能量函数的思想,即寻找每个像素的最优视差来使得整张影像的全局能量函数最小,下式为SGM所采用的能量函数:![bba9281ff1deaec32f21b804b024b14b.png](https://i-blog.csdnimg.cn/blog_migrate/abbffceb2a9471c4cf674b8d14bf8872.jpeg)
![2e78c73fdbab4904fdbd71a3327e21ef.png](https://i-blog.csdnimg.cn/blog_migrate/d8028bbaf72b2b2df71d4711c000548b.png)
struct path {short rowDiff;short colDiff;short index;
};
实际上读者可以将所谓的路径理解为像素P(x,y)领域中的某一个特定位置的像素点。比如下图中的path1可设为(rowDiff = 0,colDiff = -1,index = 1),path2可设为(rowDiff = -1,colDiff = 0, index = 2)等。
![805812771912faef5ff006b3e07c5f79.png](https://i-blog.csdnimg.cn/blog_migrate/0fe33f306178ffe68de0b40cc273190a.png)
2. 代价计算
在视差计算之前,首先需要定义大小为W×H×D(W为影像宽度,H为影像高度,D则为事先给定的视差范围)的三维矩阵C来存储每个像素在视差范围内每个视差下的匹配代价值。矩阵C通常称为DSI(Disparity Space Image),这个长方块也是我们常说的视差空间。下图为DSI的示意图:![5efbb2d094360caef38e70197a35421f.png](https://i-blog.csdnimg.cn/blog_migrate/388a2a0e6e6efd3ef0b683287dd7edcf.png)
3. 代价聚合
感性上来讲,SGM能够扬名的一个很重要的点就是它将一个NP的全局优化问题简化成了一个多方向的代价问题,即用多个一维路径代价聚合的方式来近似二维的最优。虽然还是在全局的框架下,但是整体的计算效率已相较于全局算法有了很大提升。上一步代价计算步骤中所计算出来的代价仅仅是能量函数中的数据项,在经过聚合步骤后的代价才会被用来计算最优视差。所谓的代价聚合实质上是对上一步的代价矩阵进行全局优化,得到一个存有聚集后代价的新的矩阵,用三维矩阵S来表示。就路径聚合,以下将简要介绍三个示意图。第一个示意图,也即原论文中路径聚合的示意图如下图所示,做出了一个感性的十六方向代价聚合示意。![d1b7eefb8198217a9564a1d5017ef4ba.png](https://i-blog.csdnimg.cn/blog_migrate/298dd2b8d7dd3382c055f2034119a1d3.png)
![a8de54c02c18d768144f06e5c6227e4c.png](https://i-blog.csdnimg.cn/blog_migrate/85398bc73c2cfe6e77e250cacd37d630.png)
![0a84a930d9e8b60b6c04172bde970b77.png](https://i-blog.csdnimg.cn/blog_migrate/8851859fda49a26344250fa164d610f3.png)
for (int row = 0; row {for (int col = 0; col {for (unsigned int path = 0; path {for (int d = 0; d {
S[row][col][d] += aggregateCost(row, col, d, firstScanPaths[path], rows, cols, disparityRange, C, A[path]);
}
}
}
lastProgressPrinted = printProgress(row, rows - 1, lastProgressPrinted);
}
即对于每一个像素点,对其第一次扫描序列中的每一条路径,对其视差范围内的每一个视差,求取单路径聚集后的代价,再进行累加,存到S[rol][col][d]中,最后用于WTA视差计算。此处关键的函数为
aggregateCost(row, col, d, firstScanPaths[path], rows, cols, disparityRange, C, A[path])
。输入的参数中
(row,col,d)
相当于确定了视差空间中三维点的位置,
firstScanPaths[path]
则确定了是哪条路径,或者可以更直观的理解为是像素领域内哪个相邻像素,
(rows,cols,disparityRange)
则确定了视差空间的范围,
C
为上一步代价计算得到的三维代价矩阵,最后的
A[path]
也是一个三维矩阵,用来存储对应方向的聚集代价,返回值则是
A[row][col][d]
。以下为该函数的实现:
unsigned short aggregateCost(int row, int col, int d, path &p, int rows, int cols, int disparityRange, unsigned short ***C, unsigned short ***A) {unsigned short aggregatedCost = 0;
aggregatedCost += C[row][col][d]; //像素匹配的 cost 值// 1. 边界条件,直接为C if (row + p.rowDiff 0 || row + p.rowDiff >= rows || col + p.colDiff 0 || col + p.colDiff >= cols)
{
A[row][col][d] += aggregatedCost; return A[row][col][d];
}// 2. 若未超出边界 ,则进行相应方向的代价聚合 unsigned short minPrev, minPrevOther, prev, prevPlus, prevMinus;
prev = minPrev = minPrevOther = prevPlus = prevMinus = MAX_SHORT; //设置初始代价为最大值//minPrev: 对应路径的视差代价最小值// 对于该路径方向上,上一个像素,在其视差范围内进行循环for (int disp = 0; disp {unsigned short tmp = A[row + p.rowDiff][col + p.colDiff][disp];//找到这个路径下,前一个像素取不同视差值时最小的A,即为最后减去的那一项,minPrevif(minPrev > tmp){minPrev = tmp;} //前一个像素视差取值为d时,即和当前像素的视差相等时,最小的A. if(disp == d)
{ prev = tmp;}//前一个像素视差取值为d+1时,即和当前像素的视差相差1时,最小的A,最后将加惩罚系数P1.else if(disp == d + 1)
{ prevPlus = tmp;}//前一个像素视差取值为d-1时,即和当前像素的视差相差1时,最小的A,最后将加惩罚系数P1. else if (disp == d - 1)
{ prevMinus = tmp;}//前一个像素视差与当前像素的视差相差大于等于2时,最小的A,最后将加惩罚系数P2. else
{ minPrevOther = tmp;}
} /* 计算四种情况下的代价最小值 */
aggregatedCost += std::min(std::min((int)prevPlus + SMALL_PENALTY, (int)prevMinus + SMALL_PENALTY), std::min((int)prev, (int)minPrevOther + LARGE_PENALTY));
aggregatedCost -= minPrev; //避免值过大,减小内存
A[row][col][d] += aggregatedCost;return A[row][col][d];
}
第二次扫描及其他代码细节具体见网盘资源。