跳到主要内容C++AI算法
SGBM 半全局块匹配算法流程详解
综述由AI生成SGBM 是一种基于半全局动态规划的立体匹配算法,通过多路径代价聚合近似全局能量最小化。其五大核心步骤:预处理(Sobel 滤波)、代价计算(BT/Census)、代价聚合(多方向 DP)、视差计算(WTA+ 亚像素)及后处理(一致性检查/斑点滤波)。提供了 OpenCV C++ 实现代码、参数调优建议及工程优化技巧,适用于自动驾驶、机器人导航等深度估计场景。
灵魂伴侣30 浏览 SGBM 算法流程详解
1. 算法概述
1.1 SGBM 的本质
SGBM (Semi-Global Block Matching) 是 Heiko Hirschmüller 提出的 SGM 算法的工程化实现,核心思想是:
通过多路径动态规划 (Multi-path Dynamic Programming) 近似全局能量最小化
- 全局匹配: 考虑整张图像的一致性约束
- 半全局近似: 用多个 1D 路径代替 2D 全局优化,降低复杂度
- 块匹配: 结合局部块匹配的鲁棒性
1.2 算法复杂度
- 时间复杂度: O(W × H × D × P)
- W, H: 图像宽高
- D: 视差搜索范围 (numDisparities)
- P: 路径数量 (通常 5 或 8)
- 空间复杂度: O(W × H × D)
2. 完整算法流程
输入:左右校正图像
预处理:Sobel 滤波
代价计算:BT/Census
代价聚合:多路径 DP
视差计算:WTA + 亚像素
后处理:一致性检查
输出:视差图
流程概览:
| 步骤 | 输入 | 输出 | 核心操作 | OpenCV 参数 |
|---|
| 1. 预处理 | 左右图像 | 滤波后图像 | Sobel 梯度 | preFilterCap |
| 2. 代价计算 | 滤波图像 | 代价体 C(x,y,d) | BT 距离/Census | blockSize |
| 3. 代价聚合 | 代价体 | 聚合代价 S(x,y,d) | 多路径 DP | P1, P2 |
| 4. 视差计算 | 聚合代价 | 粗视差图 | WTA | numDisparities |
| 5. 后处理 | 粗视差图 | 精细视差图 | 滤波 | uniquenessRatio |
3. 步骤 1: 预处理 (Pre-processing)
3.1 目的
- 减少光照影响: 归一化亮度差异
- 增强纹理: 提升边缘和细节的匹配可靠性
3.2 Sobel 预滤波
OpenCV 对左右图像分别进行水平方向的 Sobel 梯度计算:
I'(x, y) = clip(∂I(x,y)/∂x, -preFilterCap, +preFilterCap)
数学展开:
∂I/∂x ≈ I(x+1, y) - I(x-1, y)
代码等效实现:
{
cv::(src, dst, CV_16S, , , );
( y = ; y < dst.rows; y++){
( x = ; x < dst.cols; x++){
val = dst.<>(y, x);
dst.<>(y, x) = cv::<>( std::(-preFilterCap, std::(preFilterCap, ()val)));
}
}
}
void preFilterXSobel(const cv::Mat& src, cv::Mat& dst, int preFilterCap)
Sobel
1
0
3
for
int
0
for
int
0
short
at
short
at
short
saturate_cast
short
max
min
int
3.3 参数影响
- preFilterCap = 31: 适用于室内、光照均匀场景
- preFilterCap = 63: 适用于室外、光照变化大的场景
- 过小 → 梯度信息不足;过大 → 噪声增加
4. 步骤 2: 代价计算 (Cost Computation)
4.1 代价体构建
构建 3D 代价体 (Cost Volume):
C: R^{W × H × D} where D = numDisparities
对于每个像素 (x, y) 和视差 d,计算左右图像的匹配代价。
4.2 方法 1: BT (Birchfield-Tomasi) 距离
公式:
C_BT(x, y, d) = (1/N) Σ_(i,j)∈W ρ(I_L(x+i, y+j), I_R(x+i-d, y+j))
其中 ρ 为鲁棒代价函数:
ρ(a, b) = min(|a - b|, |a - b^-|, |a - b^+|)
b^- = (I_R(x-d) + I_R(x-d-1))/2, b^+ = (I_R(x-d) + I_R(x-d+1))/2
float computeBT(int x, int y, int d, const Mat& left, const Mat& right){
float cost = 0;
int halfBlock = blockSize / 2;
for(int dy = -halfBlock; dy <= halfBlock; dy++){
for(int dx = -halfBlock; dx <= halfBlock; dx++){
int xl = x + dx;
int yl = y + dy;
int xr = xl - d;
if(xr < 0 || xr >= right.cols) continue;
float IL = left.at<uchar>(yl, xl);
float IR = right.at<uchar>(yl, xr);
float IR_minus = (right.at<uchar>(yl, xr) + right.at<uchar>(yl, xr-1))/2.0;
float IR_plus = (right.at<uchar>(yl, xr) + right.at<uchar>(yl, xr+1))/2.0;
float diff = std::min({std::abs(IL - IR), std::abs(IL - IR_minus), std::abs(IL - IR_plus)});
cost += diff;
}
}
return cost / ((blockSize * blockSize));
}
4.3 方法 2: Census 变换
更鲁棒于光照变化,OpenCV 在某些模式下使用。
- 对每个像素的邻域进行二值化编码
- 使用 Hamming 距离比较左右图像的 Census 串
公式:
ξ(x, y) = ⊗(i,j)∈W 1{I(x+i, y+j) > I(x, y)}
C_Census(x, y, d) = Hamming(ξ_L(x, y), ξ_R(x-d, y))
uint64_t computeCensus(int x, int y, const Mat& img){
uint64_t census = 0;
uchar center = img.at<uchar>(y, x);
for(int dy = -4; dy <= 4; dy++){
for(int dx = -4; dx <= 4; dx++){
if(dx == 0 && dy == 0) continue;
uchar neighbor = img.at<uchar>(y + dy, x + dx);
census <<= 1;
if(neighbor > center) census |= 1;
}
}
return census;
}
int hammingDistance(uint64_t a, uint64_t b){
return __builtin_popcountll(a ^ b);
}
4.4 代价体示意图
代价体 C[x, y, d]: d (视差) ↓
0 1 2 ... 63
x=0 [12, 45, 78, ..., 23]
x=1 [34, 12, 56, ..., 89]
...
每个 C(x, y, d) 存储像素 (x, y) 在视差 d 下的匹配代价。
5. 步骤 3: 代价聚合 (Cost Aggregation)
5.1 核心思想
这是 SGBM 的灵魂步骤。通过多个方向的路径进行动态规划,将局部代价聚合成考虑全局约束的代价。
5.2 单路径动态规划公式
对于路径方向 r,递推计算累积代价:
L_r(p, d): 沿路径 r 到像素 p 视差为 d 的最小累积代价
p-r: 路径 r 方向上的前一个像素
C(p, d): 当前像素的原始匹配代价
P1: 视差变化 ±1 的惩罚(平滑项)
P2: 视差大幅变化的惩罚(断裂项)
最后的归一化项防止数值溢出
5.3 路径方向
| 模式 | 路径数量 | 方向角度 | 计算量 | 质量 |
|---|
MODE_SGBM | 5 | 0°, 45°, 90°, 135°, 垂直加强 | 低 | 中 |
MODE_HH | 8 | 全方向 | 高 | 高 |
MODE_SGBM_3WAY | 8 | 3 方向×3 遍历 | 中 | 较高 |
135° 90° 45° ↖ ↑ ↗
180° ← [p] → 0°
↙ ↓ ↘
225° 270° 315°
5.4 动态规划详细过程
void aggregateCostPath(const Mat& cost, Mat& L, int direction, int P1, int P2){
int rows = cost.rows;
int cols = cost.cols;
int dispRange = cost.size[2];
int dx = direction_dx[direction];
int dy = direction_dy[direction];
for(int y = 0; y < rows; y++){
for(int x = 0; x < cols; x++){
int prev_x = x - dx;
int prev_y = y - dy;
if(prev_x < 0 || prev_x >= cols || prev_y < 0 || prev_y >= rows){
for(int d = 0; d < dispRange; d++){ L.at<float>(y, x, d) = cost.at<float>(y, x, d); }
continue;
}
float prev_min = FLT_MAX;
for(int d = 0; d < dispRange; d++){
prev_min = std::min(prev_min, L.at<float>(prev_y, prev_x, d));
}
for(int d = 0; d < dispRange; d++){
float C_curr = cost.at<float>(y, x, d);
float cost0 = L.at<float>(prev_y, prev_x, d);
float cost1 = (d > 0)? L.at<float>(prev_y, prev_x, d-1) + P1 : FLT_MAX;
float cost2 = (d < dispRange-1)? L.at<float>(prev_y, prev_x, d+1) + P1 : FLT_MAX;
float cost3 = prev_min + P2;
float min_cost = std::min({cost0, cost1, cost2, cost3});
L.at<float>(y, x, d) = C_curr + min_cost - prev_min;
}
}
}
}
5.5 多路径聚合
将所有路径的累积代价求和:
S(p, d) = Σ_(r=1 to P) L_r(p, d)
void aggregateAllPaths(const Mat& cost, Mat& S, int P1, int P2){
int numPaths = 8;
S = Mat::zeros(cost.size(), CV_32F);
for(int r = 0; r < numPaths; r++){
Mat L_r;
aggregateCostPath(cost, L_r, r, P1, P2);
S += L_r;
}
}
5.6 自适应惩罚系数
在图像边缘处降低 P2,允许视差断裂:
P2_adaptive(p, p-r) = P2 / max(|∇I_p|, |∇I_{p-r}|, ε)
float getAdaptiveP2(int x, int y, int prev_x, int prev_y, const Mat& gradX, const Mat& gradY, float P2_base){
float grad1 = std::abs(gradX.at<short>(y, x)) + std::abs(gradY.at<short>(y, x));
float grad2 = std::abs(gradX.at<short>(prev_y, prev_x)) + std::abs(gradY.at<short>(prev_y, prev_x));
float maxGrad = std::max({grad1, grad2, 1.0f});
return P2_base / maxGrad;
}
6. 步骤 4: 视差计算 (Disparity Computation)
6.1 WTA (Winner-Takes-All) 策略
选择累积代价最小的视差作为最终视差:
D(x, y) = argmin_(d ∈ [0, D_max]) S(x, y, d)
cv::Mat computeDisparityWTA(const Mat& S){
int rows = S.rows;
int cols = S.cols;
int dispRange = S.size[2];
Mat disparity(rows, cols, CV_16S);
for(int y = 0; y < rows; y++){
for(int x = 0; x < cols; x++){
float minCost = FLT_MAX;
int bestDisp = 0;
for(int d = 0; d < dispRange; d++){
float cost = S.at<float>(y, x, d);
if(cost < minCost){ minCost = cost; bestDisp = d; }
}
disparity.at<short>(y, x) = bestDisp * 16;
}
}
return disparity;
}
6.2 亚像素增强 (Subpixel Refinement)
数学原理:
假设代价函数在最优视差附近呈二次曲线:
S(d) ≈ a(d - d_0)^2 + b
通过三点 (d-1, S_{d-1}), (d, S_d), (d+1, S_{d+1}) 拟合,得到:
d_sub = d - (S(d-1) - S(d+1)) / (2(S(d-1) - 2S(d) + S(d+1)))
float subpixelInterpolation(float cost_prev, float cost_curr, float cost_next){
float numerator = cost_prev - cost_next;
float denominator = 2.0*(cost_prev - 2.0* cost_curr + cost_next);
if(std::abs(denominator)<1e-6){ return 0.0f;
float delta = numerator / denominator;
return std::max(-1.0f, std::min(1.0f, delta));
}
void refineDisparity(Mat& disparity, const Mat& S){
int dispRange = S.size[2];
for(int y = 0; y < disparity.rows; y++){
for(int x = 0; x < disparity.cols; x++){
short d16 = disparity.at<short>(y, x);
int d = d16 / 16;
if(d <= 0 || d >= dispRange -1) continue;
float cost_prev = S.at<float>(y, x, d -1);
float cost_curr = S.at<float>(y, x, d);
float cost_next = S.at<float>(y, x, d +1);
float delta = subpixelInterpolation(cost_prev, cost_curr, cost_next);
disparity.at<short>(y, x) = (d + delta)*16;
}
}
}
像素级:d = 10 → 深度误差 ±5cm (假设基线 100mm, 焦距 500px)
亚像素:d = 10.3125 → 深度误差 ±0.3cm (提升 16 倍)
7. 步骤 5: 后处理 (Post-processing)
7.1 左右一致性检查 (Left-Right Consistency Check)
- 计算右视差图 D_R (将左右图像交换再计算)
- 对于左图像素 (x, y),其对应右图像素为 (x - D_L(x, y), y)
- 若两者视差不一致,则标记为无效
公式:
Valid = { 1 if |D_L(x, y) - D_R(x - D_L(x, y), y)| ≤ disp12MaxDiff; 0 otherwise }
void leftRightCheck(Mat& dispLeft, const Mat& dispRight, int disp12MaxDiff){
for(int y = 0; y < dispLeft.rows; y++){
for(int x = 0; x < dispLeft.cols; x++){
short dL = dispLeft.at<short>(y, x);
if(dL == -16) continue;
int d = dL / 16;
int x_right = x - d;
if(x_right < 0 || x_right >= dispRight.cols){ dispLeft.at<short>(y, x)=-16; continue; }
short dR = dispRight.at<short>(y, x_right);
if(std::abs(dL - dR) > disp12MaxDiff * 16){ dispLeft.at<short>(y, x)=-16; }
}
}
}
disp12MaxDiff = 1: 严格模式,适用于高质量标定
disp12MaxDiff = 2: 宽松模式,适用于有畸变的场景
disp12MaxDiff = -1: 禁用检查(不推荐)
7.2 唯一性检查 (Uniqueness Ratio Check)
目的: 过滤匹配不唯一的像素(多个视差代价接近)。
公式:
Uniqueness = (S_2ndmin - S_min) / S_min × 100
若 Uniqueness < uniquenessRatio,则标记为无效。
void uniquenessCheck(Mat& disparity, const Mat& S, int uniquenessRatio){
int dispRange = S.size[2];
for(int y = 0; y < disparity.rows; y++){
for(int x = 0; x < disparity.cols; x++){
short d16 = disparity.at<short>(y, x);
if(d16 == -16) continue;
int d = d16 / 16;
float minCost = S.at<float>(y, x, d);
float secondMinCost = FLT_MAX;
for(int d2 = 0; d2 < dispRange; d2++){
if(d2 == d) continue;
float cost = S.at<float>(y, x, d2);
if(cost < secondMinCost){ secondMinCost = cost; }
}
float ratio = (secondMinCost - minCost)/ minCost * 100.0;
if(ratio < uniquenessRatio){ disparity.at<short>(y, x)=-16;
}
}
}
uniquenessRatio = 5: 非常严格,会产生较多空洞
uniquenessRatio = 10: 平衡模式(推荐)
uniquenessRatio = 15: 宽松模式,保留更多匹配
7.3 斑点滤波 (Speckle Filtering)
- 使用连通域标记找到所有连通区域
- 计算每个区域的大小和内部视差方差
- 若区域大小 <
speckleWindowSize 且方差 > speckleRange,则标记为噪声
void speckleFilter(Mat& disparity, int speckleWindowSize, int speckleRange){
if(speckleWindowSize == 0) return;
Mat labels, stats, centroids;
int numComponents = cv::connectedComponentsWithStats( disparity > 0,
labels, stats, centroids, 8);
for(int i = 1; i < numComponents; i++){
int area = stats.at<int>(i, cv::CC_STAT_AREA);
if(area < speckleWindowSize){
std::vector<short> disparities;
for(int y = 0; y < disparity.rows; y++){
for(int x = 0; x < disparity.cols; x++){
if(labels.at<int>(y, x) == i){ disparities.push_back(disparity.at<short>(y, x)/16); }
}
}
float mean = std::accumulate(disparities.begin(), disparities.end(), 0.0)/ disparities.size();
float variance = 0;
for(auto d : disparities){ variance += (d - mean)*(d - mean); }
float stddev = std::sqrt(variance / disparities.size());
if(stddev > speckleRange){
for(int y = 0; y < disparity.rows; y++){
for(int x = 0; x < disparity.cols; x++){
if(labels.at<int>(y, x) == i){ disparity.at<short>(y, x)=-16; }
}
}
}
}
}
}
speckleWindowSize = 100: 过滤小于 100 像素的连通区域
speckleRange = 2: 区域内视差变化超过 2 则视为噪声
- 设为 0 可禁用斑点滤波(提升速度)
7.4 中值滤波 (可选)
对最终视差图进行 3×3 或 5×5 中值滤波平滑:
cv::medianBlur(disparity, disparity, 5);
8. 完整代码示例
8.1 基础调用
#include<opencv2/opencv.hpp>
#include<opencv2/calib3d.hpp>
int main(){
cv::Mat leftRect = cv::imread("left_rect.png", cv::IMREAD_GRAYSCALE);
cv::Mat rightRect = cv::imread("right_rect.png", cv::IMREAD_GRAYSCALE);
int minDisparity = 0;
int numDisparities = 128;
int blockSize = 5;
int P1 = 8* blockSize * blockSize;
int P2 = 32* blockSize * blockSize;
cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create(
minDisparity, numDisparities, blockSize, P1, P2,
1,
63,
10,
100,
32,
cv::StereoSGBM::MODE_SGBM_3WAY
);
cv::Mat disparity16S;
sgbm->compute(leftRect, rightRect, disparity16S);
cv::Mat disparity32F;
disparity16S.convertTo(disparity32F, CV_32F, 1.0/16.0);
cv::Mat validMask = disparity16S > 0;
disparity32F.setTo(0, ~validMask);
cv::Mat disparityVis;
cv::normalize(disparity32F, disparityVis, 0, 255, cv::NORM_MINMAX, CV_8U);
cv::applyColorMap(disparityVis, disparityVis, cv::COLORMAP_JET);
cv::imshow("Disparity Map", disparityVis);
cv::waitKey(0);
float baseline = 0.120;
float focal = 718.856;
cv::Mat depth = (baseline * focal)/ disparity32F;
return 0;
}
8.2 高级参数调优
class SGBMTuner{
public:
static cv::Ptr<cv::StereoSGBM> createForIndoor(){
return cv::StereoSGBM::create(0, 128, 3, 72,
288,
1,
31,
10,
50,
1,
cv::StereoSGBM::MODE_HH
);
}
static cv::Ptr<cv::StereoSGBM> createForOutdoor(){
return cv::StereoSGBM::create(0, 192, 7, 392,
1568,
2,
63,
15,
200,
2,
cv::StereoSGBM::MODE_SGBM_3WAY
);
}
static cv::Ptr<cv::StereoSGBM> createForRealtime(){
return cv::StereoSGBM::create(0, 64, 5, 200, 800, -1,
63, 5, 0,
0, cv::StereoSGBM::MODE_SGBM
);
}
};
8.3 视差转深度
cv::Mat disparityToDepth(const cv::Mat& disparity, float baseline, float focal, float minDepth = 0.1, float maxDepth = 10.0){
cv::Mat depth = cv::Mat::zeros(disparity.size(), CV_32F);
for(int y = 0; y < disparity.rows; y++){
for(int x = 0; x < disparity.cols; x++){
float disp = disparity.at<float>(y, x);
if(disp <= 0){ depth.at<float>(y, x)=0;
float d = (baseline * focal)/ disp;
if(d < minDepth || d > maxDepth){ depth.at<float>(y, x)=0; }
else{ depth.at<float>(y, x)= d; }
}
}
return depth;
}
9. 工程优化技巧
9.1 性能优化
cv::Rect roi(100, 100, 800, 600);
cv::Mat dispROI;
sgbm->compute(leftRect(roi), rightRect(roi), dispROI);
cv::Mat leftSmall, rightSmall;
cv::resize(leftRect, leftSmall, cv::Size(), 0.5, 0.5);
cv::resize(rightRect, rightSmall, cv::Size(), 0.5, 0.5);
cv::resize(disparity, disparity, leftRect.size());
disparity *= 2;
9.2 参数自适应
int computeOptimalBlockSize(const cv::Mat& image){
cv::Mat grad;
cv::Sobel(image, grad, CV_16S, 1, 1);
cv::Scalar meanGrad = cv::mean(grad);
if(meanGrad[0]>50){ return 3;
else if(meanGrad[0]>20){ return 5; }
else{ return 7;
}
9.3 错误诊断
| 问题 | 可能原因 | 解决方案 |
|---|
| 视差图全黑 | 未进行极线校正 | 使用 stereoRectify + remap |
| 大量空洞 | uniquenessRatio 过小 | 增大到 10-15 |
| 过度平滑 | P2 过大 | 降低到 4 × P1 |
| 噪点多 | P1 过小 | 增大到 8 × blockSize² |
| 边缘断裂 | 未启用自适应 P2 | 使用 MODE_HH 模式 |
| 计算慢 | 路径过多 | 改用 MODE_SGBM (5 路径) |
9.4 质量评估
float evaluateDisparity(const cv::Mat& computed, const cv::Mat& groundTruth, float threshold = 1.0){
int badPixels = 0;
int totalPixels = 0;
for(int y = 0; y < computed.rows; y++){
for(int x = 0; x < computed.cols; x++){
float gt = groundTruth.at<float>(y, x);
if(gt == 0) continue;
float comp = computed.at<float>(y, x);
if(std::abs(comp - gt) > threshold){ badPixels++; }
totalPixels++;
}
}
return (float)badPixels / totalPixels * 100.0;
}
10. 总结
10.1 算法特点
| 优点 | 缺点 |
|---|
| 密集匹配 (90%+ 覆盖率) | 计算量大 (实时需优化) |
| 边缘保持好 | 弱纹理区域易失败 |
| 抗噪声能力强 | 参数敏感 |
| 亚像素精度 | 需要良好的极线校正 |
10.2 适用场景
- ✅ 自动驾驶: 道路障碍物检测
- ✅ 机器人导航: SLAM 中的深度估计
- ✅ 工业检测: 3D 尺寸测量
- ✅ AR/VR: 实时深度感知
- ❌ 高速运动: 帧率要求 > 60fps (考虑 GPU 加速)
- ❌ 极弱纹理: 如白墙 (需结合结构光)
10.3 进阶方向
- 深度学习融合: 使用 CNN 计算初始代价体 (如 PSMNet)
- 时序一致性: 利用视频序列前后帧约束
- GPU 加速: CUDA 实现路径聚合并行化
- 自适应参数: 根据场景动态调整 P1, P2
参考文献
- Hirschmüller, H. (2007). Stereo Processing by Semiglobal Matching and Mutual Information. IEEE TPAMI, 30(2), 328-341.
- OpenCV Documentation: StereoSGBM Class Reference
- Birchfield, S., & Tomasi, C. (1998). A Pixel Dissimilarity Measure That Is Insensitive to Image Sampling. IEEE TPAMI, 20(4), 401-406.
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online