SGBM 算法流程(一)

SGBM 算法流程(一)

SGBM 算法流程详解 (Semi-Global Block Matching)

目录

  1. 算法概述
  2. 完整算法流程
  3. 步骤1: 预处理 (Pre-processing)
  4. 步骤2: 代价计算 (Cost Computation)
  5. 步骤3: 代价聚合 (Cost Aggregation)
  6. 步骤4: 视差计算 (Disparity Computation)
  7. 步骤5: 后处理 (Post-processing)
  8. 完整代码示例
  9. 工程优化技巧

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 ) O(W \times H \times D \times P) O(W×H×D×P)
    • W , H W, H W,H: 图像宽高
    • D D D: 视差搜索范围 (numDisparities)
    • P P P: 路径数量 (通常 5 或 8)
  • 空间复杂度: O ( W × H × D ) O(W \times H \times D) O(W×H×D)

2. 完整算法流程

输入: 左右校正图像

预处理: Sobel滤波

代价计算: BT/Census

代价聚合: 多路径DP

视差计算: WTA + 亚像素

后处理: 一致性检查

输出: 视差图

流程概览:

步骤输入输出核心操作OpenCV参数
1. 预处理左右图像滤波后图像Sobel梯度preFilterCap
2. 代价计算滤波图像代价体 C ( x , y , d ) C(x,y,d) C(x,y,d)BT距离/CensusblockSize
3. 代价聚合代价体聚合代价 S ( x , y , d ) S(x,y,d) S(x,y,d)多路径DPP1, P2
4. 视差计算聚合代价粗视差图WTAnumDisparities
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, y) = \text{clip}\left( \frac{\partial I(x,y)}{\partial x}, -\text{preFilterCap}, +\text{preFilterCap} \right) I′(x,y)=clip(∂x∂I(x,y)​,−preFilterCap,+preFilterCap)

数学展开:
∂ I ∂ x ≈ I ( x + 1 , y ) − I ( x − 1 , y ) \frac{\partial I}{\partial x} \approx I(x+1, y) - I(x-1, y) ∂x∂I​≈I(x+1,y)−I(x−1,y)

代码等效实现:

voidpreFilterXSobel(const cv::Mat& src, cv::Mat& dst,int preFilterCap){ cv::Sobel(src, dst, CV_16S,1,0,3);// dx=1, dy=0, ksize=3// 截断到 [-preFilterCap, +preFilterCap]for(int y =0; y < dst.rows; y++){for(int x =0; x < dst.cols; x++){short val = dst.at<short>(y, x); dst.at<short>(y, x)= cv::saturate_cast<short>( std::max(-preFilterCap, std::min(preFilterCap,(int)val)));}}}

3.3 参数影响

  • preFilterCap = 31: 适用于室内、光照均匀场景
  • preFilterCap = 63: 适用于室外、光照变化大的场景
  • 过小 → 梯度信息不足;过大 → 噪声增加

4. 步骤2: 代价计算 (Cost Computation)

4.1 代价体构建

构建 3D 代价体 (Cost Volume):
C : R W × H × D where  D = numDisparities C: \mathbb{R}^{W \times H \times D} \quad \text{where } D = \text{numDisparities} C:RW×H×Dwhere D=numDisparities

对于每个像素 ( x , y ) (x, y) (x,y) 和视差 d d d,计算左右图像的匹配代价。

4.2 方法1: BT (Birchfield-Tomasi) 距离

核心思想: 考虑亚像素采样,减少离散化误差。

公式:
C B T ( x , y , d ) = 1 N ∑ ( i , j ) ∈ W ρ ( I L ( x + i , y + j ) , I R ( x + i − d , y + j ) ) C_{BT}(x, y, d) = \frac{1}{N} \sum_{(i,j) \in W} \rho\left( I_L(x+i, y+j), I_R(x+i-d, y+j) \right) CBT​(x,y,d)=N1​(i,j)∈W∑​ρ(IL​(x+i,y+j),IR​(x+i−d,y+j))

其中 ρ \rho ρ 为鲁棒代价函数:
ρ ( a , b ) = min ⁡ ( ∣ a − b ∣ , ∣ a − b − ∣ , ∣ a − b + ∣ ) \rho(a, b) = \min\left( |a - b|, |a - b^-|, |a - b^+| \right) ρ(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 b^- = \frac{I_R(x-d) + I_R(x-d-1)}{2}, \quad b^+ = \frac{I_R(x-d) + I_R(x-d+1)}{2} b−=2IR​(x−d)+IR​(x−d−1)​,b+=2IR​(x−d)+IR​(x−d+1)​

伪代码:

floatcomputeBT(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 在某些模式下使用。

步骤:

  1. 对每个像素的邻域进行二值化编码
  2. 使用 Hamming 距离比较左右图像的 Census 串

公式:
ξ ( x , y ) = ⨂ ( i , j ) ∈ W 1 I ( x + i , y + j ) > I ( x , y ) \xi(x, y) = \bigotimes_{(i,j) \in W} \mathbb{1}_{I(x+i, y+j) > I(x, y)} ξ(x,y)=(i,j)∈W⨂​1I(x+i,y+j)>I(x,y)​

C C e n s u s ( x , y , d ) = Hamming ( ξ L ( x , y ) , ξ R ( x − d , y ) ) C_{Census}(x, y, d) = \text{Hamming}\left( \xi_L(x, y), \xi_R(x-d, y) \right) CCensus​(x,y,d)=Hamming(ξL​(x,y),ξR​(x−d,y))

示例 (9×9窗口):

uint64_tcomputeCensus(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;}inthammingDistance(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 ) C(x, y, d) C(x,y,d) 存储像素 ( x , y ) (x, y) (x,y) 在视差 d d d 下的匹配代价。


5. 步骤3: 代价聚合 (Cost Aggregation)

5.1 核心思想

这是 SGBM 的灵魂步骤。通过多个方向的路径进行动态规划,将局部代价聚合成考虑全局约束的代价。

5.2 单路径动态规划公式

对于路径方向 r r r,递推计算累积代价:

在这里插入图片描述

符号说明:

  • L r ( p , d ) L_r(p, d) Lr​(p,d): 沿路径 r r r 到像素 p p p 视差为 d d d 的最小累积代价
  • p − r p - r p−r: 路径 r r r 方向上的前一个像素
  • C ( p , d ) C(p, d) C(p,d): 当前像素的原始匹配代价
  • P 1 P_1 P1​: 视差变化 ±1 的惩罚(平滑项)
  • P 2 P_2 P2​: 视差大幅变化的惩罚(断裂项)
  • 最后的归一化项防止数值溢出

5.3 路径方向

OpenCV SGBM 支持多种模式:

模式路径数量方向角度计算量质量
MODE_SGBM50°, 45°, 90°, 135°, 垂直加强
MODE_HH8全方向
MODE_SGBM_3WAY83 方向×3 遍历较高

8方向示意图:

 135° 90° 45° ↖ ↑ ↗ 180° ← [p] → 0° ↙ ↓ ↘ 225° 270° 315° 

5.4 动态规划详细过程

以水平方向 (0°) 为例:

voidaggregateCostPath(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];// 例如: 0° → dx=1, dy=0int 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));}// 对当前像素的每个视差进行DPfor(int d =0; d < dispRange; d++){float C_curr = cost.at<float>(y, x, d);// 4 种情况的最小值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;// +1float cost2 =(d < dispRange-1)? L.at<float>(prev_y, prev_x, d+1)+ P1 : FLT_MAX;// -1float 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 P L r ( p , d ) S(p, d) = \sum_{r=1}^{P} L_r(p, d) S(p,d)=r=1∑P​Lr​(p,d)

voidaggregateAllPaths(const Mat& cost, Mat& S,int P1,int P2){int numPaths =8;// 或 5// 初始化聚合代价为 0 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 自适应惩罚系数

在图像边缘处降低 P 2 P_2 P2​,允许视差断裂:

P 2 a d a p t i v e ( p , p − r ) = P 2 max ⁡ ( ∣ ∇ I p ∣ , ∣ ∇ I p − r ∣ , ϵ ) P_2^{adaptive}(p, p-r) = \frac{P_2}{\max(|\nabla I_p|, |\nabla I_{p-r}|, \epsilon)} P2adaptive​(p,p−r)=max(∣∇Ip​∣,∣∇Ip−r​∣,ϵ)P2​​

floatgetAdaptiveP2(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 ) = arg ⁡ min ⁡ d ∈ [ 0 , D m a x ] S ( x , y , d ) D(x, y) = \arg\min_{d \in [0, D_{max}]} S(x, y, d) D(x,y)=argd∈[0,Dmax​]min​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;// OpenCV 用16倍存储}}return disparity;}

6.2 亚像素增强 (Subpixel Refinement)

通过二次曲线拟合提升精度到 1/16 像素:

数学原理:
假设代价函数在最优视差附近呈二次曲线:

S ( d ) ≈ a ( d − d 0 ) 2 + b S(d) \approx a(d - d_0)^2 + b S(d)≈a(d−d0​)2+b

通过三点 ( d − 1 , S d − 1 ) , ( d , S d ) , ( d + 1 , S d + 1 ) (d-1, S_{d-1}), (d, S_d), (d+1, S_{d+1}) (d−1,Sd−1​),(d,Sd​),(d+1,Sd+1​) 拟合,得到:

d s u b = d − S ( d − 1 ) − S ( d + 1 ) 2 ( S ( d − 1 ) − 2 S ( d ) + S ( d + 1 ) ) d_{sub} = d - \frac{S(d-1) - S(d+1)}{2(S(d-1) - 2S(d) + S(d+1))} dsub​=d−2(S(d−1)−2S(d)+S(d+1))S(d−1)−S(d+1)​

代码实现:

floatsubpixelInterpolation(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){return0.0f;// 避免除零}float delta = numerator / denominator;// 限制在 [-1, 1] 范围内return std::max(-1.0f, std::min(1.0f, delta));}voidrefineDisparity(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);// 更新为亚像素视差 (16倍精度) 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 D_R DR​ (将左右图像交换再计算)
  • 对于左图像素 ( x , y ) (x, y) (x,y),其对应右图像素为 ( x − D L ( x , y ) , y ) (x - D_L(x, y), y) (x−DL​(x,y),y)
  • 若两者视差不一致,则标记为无效

公式:
Valid = { 1 if  ∣ D L ( x , y ) − D R ( x − D L ( x , y ) , y ) ∣ ≤ disp12MaxDiff 0 otherwise \text{Valid} = \begin{cases} 1 & \text{if } |D_L(x, y) - D_R(x - D_L(x, y), y)| \leq \text{disp12MaxDiff} 0 & \text{otherwise} \end{cases} Valid={1​if ∣DL​(x,y)−DR​(x−DL​(x,y),y)∣≤disp12MaxDiff0​otherwise​

代码实现:

voidleftRightCheck(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 2 n d m i n − S m i n S m i n × 100 \text{Uniqueness} = \frac{S_{2ndmin} - S_{min}}{S_{min}} \times 100 Uniqueness=Smin​S2ndmin​−Smin​​×100

若 Uniqueness < uniquenessRatio \text{Uniqueness} < \text{uniquenessRatio} Uniqueness<uniquenessRatio,则标记为无效。

voiduniquenessCheck(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)

目的: 去除孤立的噪声点和小连通区域。

算法:

  1. 使用连通域标记找到所有连通区域
  2. 计算每个区域的大小和内部视差方差
  3. 若区域大小 < speckleWindowSize 且方差 > speckleRange,则标记为噪声
voidspeckleFilter(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++){// 跳过背景 (label=0)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>intmain(){// 1. 读取校正后的立体图像对 cv::Mat leftRect = cv::imread("left_rect.png", cv::IMREAD_GRAYSCALE); cv::Mat rightRect = cv::imread("right_rect.png", cv::IMREAD_GRAYSCALE);// 2. 创建 SGBM 对象int minDisparity =0;int numDisparities =128;// 必须是 16 的倍数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,// disp12MaxDiff63,// preFilterCap10,// uniquenessRatio100,// speckleWindowSize32,// speckleRange cv::StereoSGBM::MODE_SGBM_3WAY );// 3. 计算视差图 cv::Mat disparity16S; sgbm->compute(leftRect, rightRect, disparity16S);// 4. 转换为浮点视差 (除以 16) cv::Mat disparity32F; disparity16S.convertTo(disparity32F, CV_32F,1.0/16.0);// 5. 处理无效视差 cv::Mat validMask = disparity16S >0; disparity32F.setTo(0,~validMask);// 6. 可视化 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);// 7. 转换为深度图float baseline =0.120;// 120mmfloat focal =718.856;// 像素 cv::Mat depth =(baseline * focal)/ disparity32F;return0;}

8.2 高级参数调优

classSGBMTuner{public:static cv::Ptr<cv::StereoSGBM>createForIndoor(){return cv::StereoSGBM::create(0,128,3,72,// P1 = 8*1*3²288,// P2 = 4*P11,// 严格一致性31,// 小预滤波10,// 中等唯一性50,// 小斑点窗口1,// 严格斑点过滤 cv::StereoSGBM::MODE_HH // 高质量模式);}static cv::Ptr<cv::StereoSGBM>createForOutdoor(){return cv::StereoSGBM::create(0,192,7,392,// P1 = 8*1*7²1568,// P2 = 4*P12,// 宽松一致性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 // 5路径模式);}};

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;// 无效深度continue;}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 性能优化

1. 多线程加速

sgbm->setNumThreads(8);// OpenCV 4.x+ 支持

2. ROI 限制

cv::Rect roi(100,100,800,600); cv::Mat dispROI; sgbm->compute(leftRect(roi),rightRect(roi), dispROI);

3. 降采样

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 参数自适应

根据图像纹理密度调整 blockSize:

intcomputeOptimalBlockSize(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){return3;// 纹理丰富 → 小窗口}elseif(meanGrad[0]>20){return5;}else{return7;// 纹理稀疏 → 大窗口}}

9.3 错误诊断

常见问题及解决:

问题可能原因解决方案
视差图全黑未进行极线校正使用 stereoRectify + remap
大量空洞uniquenessRatio 过小增大到 10-15
过度平滑 P 2 P_2 P2 过大降低到 4 × P 1 4 \times P_1 4×P1
噪点多 P 1 P_1 P1 过小增大到 8 × blockSize 2 8 \times \text{blockSize}^2 8×blockSize2
边缘断裂未启用自适应 P 2 P_2 P2使用 MODE_HH 模式
计算慢路径过多改用 MODE_SGBM (5路径)

9.4 质量评估

使用 Middlebury 标准评估:

floatevaluateDisparity(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 进阶方向

  1. 深度学习融合: 使用 CNN 计算初始代价体 (如 PSMNet)
  2. 时序一致性: 利用视频序列前后帧约束
  3. GPU 加速: CUDA 实现路径聚合并行化
  4. 自适应参数: 根据场景动态调整 P 1 , P 2 P_1, P_2 P1​,P2​

参考文献

  1. Hirschmüller, H. (2007). “Stereo Processing by Semiglobal Matching and Mutual Information”. IEEE TPAMI, 30(2), 328-341.
  2. OpenCV Documentation: StereoSGBM Class Reference
  3. Birchfield, S., & Tomasi, C. (1998). “A Pixel Dissimilarity Measure That Is Insensitive to Image Sampling”. IEEE TPAMI, 20(4), 401-406.

Could not load content