首页 / 资讯中心 / 文章详情

U-V视差:双目视觉中地面检测与障碍物分割的实用后处理技术

U-V视差:双目视觉中地面检测与障碍物分割的实用后处理技术 ★ FEATURED ARTICLE
1. 从双目视差到U-V视差为什么需要换一个视角做双目视觉的人绕不开的一个核心问题就是拿到左右两张图之后怎么把稠密的深度信息算出来。最直接的想法是逐像素做块匹配在极线上搜一遍找到最相似的位置算出视差再转成深度。这个思路没错但真跑起来你会发现两个很现实的问题一是计算量巨大二是误匹配多得让人头疼。尤其是在纹理稀疏、重复纹理或者光照不一致的区域块匹配几乎必然翻车。U-V视差这个概念本质上是对传统视差图的一种“再组织”。它不改变双目成像的几何本质而是把视差信息按照图像的行列方向重新投影形成两个新的二维平面U视差图和V视差图。你可以把它理解成把一张视差图“压扁”成两个方向的统计直方图但这个“压扁”保留了空间结构信息所以能反过来帮我们做地面检测、障碍物分割、视差校正这些事。我第一次接触U-V视差是在做一个室外场景的障碍物检测项目。当时用SGM算出来的视差图已经算不错了但地面和远处天空区域的噪声还是很多直接做点云分割效果很差。后来换了个思路先把视差图转成V视差图发现地面在V视差里呈现出一条非常清晰的斜线障碍物则是竖直的条带。那一瞬间我就明白了有些问题在原始视差图里很难处理但换到U-V视差空间里几乎变成了送分题。这篇文章适合谁看如果你已经了解双目立体视觉的基本原理知道视差和深度的关系也跑过至少一种立体匹配算法那接下来的内容会让你对“视差后处理”有一个全新的认识。如果你还没接触过双目建议先补一下极线校正和视差计算的基础不然直接看U-V视差会有点跳。提示U-V视差不是一种新的立体匹配算法它是一种视差图的分析工具。它的价值在于把“逐像素”的问题转化成“按行按列统计”的问题从而引入更强的结构先验。2. U-V视差的核心原理与几何直觉2.1 视差图到底记录了什么先快速回顾一下。双目相机经过极线校正之后左右图像的对应点在同一水平线上。对于左图中某个像素点 $(u, v)$它在右图中的匹配点位于 $(u - d, v)$其中 $d$ 就是视差。视差越大说明物体越近视差越小说明物体越远。深度 $Z$ 和视差 $d$ 的关系是$$Z \frac{f \cdot B}{d}$$其中 $f$ 是焦距像素单位$B$ 是基线长度。这个公式是双目的根基U-V视差的所有推导都建立在它之上。一张视差图本质上是一个二维矩阵每个位置存的是视差值。但直接看这个矩阵你很难一眼看出“地面在哪”“障碍物在哪”。因为地面在图像里是逐渐变化的视差也是逐渐变化的没有明显的边界。U-V视差做的事情就是把这个矩阵按行和按列重新统计。2.2 U视差图和V视差图分别是什么V视差图V-disparity的构造方式是对视差图的每一行统计该行中每个视差值出现的次数形成一个二维直方图。横轴是视差 $d$纵轴是图像的行坐标 $v$。换句话说V视差图的每一行就是原视差图对应行的视差分布直方图。U视差图U-disparity同理只是方向换成了列对视差图的每一列统计该列中每个视差值出现的次数横轴是视差 $d$纵轴是图像的列坐标 $u$。这两个图看起来简单但信息量很大。V视差图里地面会呈现出一条斜线因为地面在图像中越往下$v$ 越大视差越大越近。这条斜线的斜率与相机的高度和俯仰角有关。而障碍物比如一辆车或者一堵墙它们在V视差图里会表现为竖直的亮条因为同一障碍物在图像中占据一定的高度范围但视差变化不大。U视差图则对横向结构更敏感。比如一排等距的柱子在U视差图里会看到周期性的亮斑。地面在U视差图里通常是一条水平亮带因为同一行地面像素的视差相近。2.3 为什么这种变换能简化问题关键在于“统计”这个动作。原始视差图里一个像素的视差值可能因为噪声而跳变但统计一整行之后噪声被平均掉了真实的视差分布会凸显出来。这就像你听一首歌单个采样点可能有杂音但看频谱图就能清楚看到主旋律。另一个原因是很多场景结构在U-V视差空间里变成了直线或规则形状。地面是斜线障碍物是竖条天空是低视差区域。这些几何特征在原始视差图里是隐含的但在U-V视差里是显式的。一旦变成直线检测问题就可以用霍夫变换之类的成熟工具来处理整个流程的鲁棒性会提升一个档次。注意U-V视差的效果高度依赖视差图的质量。如果视差图本身在某个区域大面积错误U-V视差图里也会出现对应的异常亮斑或缺失。所以它不能替代立体匹配只能作为后处理和分析工具。3. 从零实现U-V视差步骤、参数与代码细节3.1 准备工作视差图的获取与预处理要构造U-V视差首先得有一张视差图。你可以用OpenCV的StereoSGBM也可以用自己实现的块匹配。不管用什么方法拿到视差图之后建议先做两件事第一把无效视差通常标记为-1或-16统一替换成一个特殊值比如0或者一个很大的负数方便后续统计时排除。第二对视差图做一次中值滤波去掉明显的椒盐噪声。中值滤波的窗口大小建议从5开始试如果视差图本身比较干净3就够了如果噪声很多可以到7但再大就会抹掉小障碍物的细节。这里有个细节SGBM输出的视差图通常是16位有符号整数实际视差值需要除以16。很多人第一次用的时候忘了除导致U-V视差图的横轴范围完全不对。我建议在预处理阶段就统一转成浮点数视差范围根据你的相机参数来定。一般室内场景视差在0到128之间室外可能到256甚至更大。3.2 V视差图的构造逐行统计的实现构造V视差图的核心就是一个循环遍历视差图的每一行对该行的有效视差值做直方图统计。直方图的bin数量决定了V视差图的分辨率。bin太多噪声会分散亮线不明显bin太少不同视差的物体可能混在一起。根据我的经验bin的数量取视差范围的1/2到1/4比较合适。比如视差范围是0到128bin数量取64或者32。下面是一个用Python和NumPy实现的示例import numpy as np import cv2 def compute_v_disparity(disparity, max_disp, bin_size2): h, w disparity.shape num_bins max_disp // bin_size v_disparity np.zeros((h, num_bins), dtypenp.float32) for v in range(h): row disparity[v, :] valid row[row 0] if len(valid) 0: continue hist, _ np.histogram(valid, binsnum_bins, range(0, max_disp)) v_disparity[v, :] hist # 归一化方便可视化 v_disparity v_disparity / (np.max(v_disparity) 1e-6) return v_disparity这段代码里bin_size控制直方图的分辨率。我一般先用2跑一遍看效果如果地面斜线不够清晰再降到1如果噪声太多升到4。归一化是为了可视化实际做直线检测的时候可以用原始计数。构造完之后用cv2.applyColorMap或者简单的灰度显示你就能看到V视差图。地面那条斜线应该从左上到右下假设图像坐标系原点在左上角视差向右增大。如果斜线方向反了说明你的视差图可能左右颠倒或者相机安装方向有问题。3.3 U视差图的构造逐列统计的差异U视差图的构造和V视差图几乎一样只是把行循环换成列循环def compute_u_disparity(disparity, max_disp, bin_size2): h, w disparity.shape num_bins max_disp // bin_size u_disparity np.zeros((w, num_bins), dtypenp.float32) for u in range(w): col disparity[:, u] valid col[col 0] if len(valid) 0: continue hist, _ np.histogram(valid, binsnum_bins, range(0, max_disp)) u_disparity[u, :] hist u_disparity u_disparity / (np.max(u_disparity) 1e-6) return u_disparity注意U视差图的纵轴是图像的列坐标 $u$横轴是视差。显示的时候你可能需要转置一下才能符合直觉。U视差图里地面通常是一条水平亮带因为同一列的地面像素视差变化不大。而竖直的障碍物比如电线杆会在U视差图里形成一个竖直的亮条因为它在多个列上都占据相似的视差范围。3.4 参数选择bin大小、视差范围和滤波这里集中说一下参数选择。bin大小是最关键的参数。太小直方图稀疏亮线断断续续太大不同物体的视差混在一起分不开。我的经验是如果你的视差图分辨率是640x480视差范围0到128bin大小取2到4比较合适。如果分辨率更高比如1280x720bin大小可以适当增大到4到8因为像素多了统计更充分。视差范围要设对。如果你不确定最大视差是多少可以先跑一遍SGBM看视差图的最大值。或者根据相机参数估算最大视差出现在最近处$d_{max} f \cdot B / Z_{min}$。比如焦距500像素基线0.1米最近距离0.5米那最大视差就是100。设范围的时候留一点余量比如120。滤波方面除了前面说的中值滤波还可以在U-V视差图上做一次高斯模糊让亮线更连续。高斯核大小取3或5标准差取1左右。但注意不要过度模糊否则直线检测的精度会下降。实操心得我习惯在构造U-V视差之前先把视差图裁剪掉左右边缘各10%的区域。因为立体匹配在图像边缘的视差通常不可靠这些错误会污染U-V视差图。裁剪之后地面斜线和障碍物竖条都会干净很多。4. 用U-V视差做地面检测与障碍物分割4.1 地面斜线的检测与拟合V视差图里最显著的特征就是地面斜线。检测这条斜线的方法有很多最简单的是霍夫变换。OpenCV的HoughLinesP可以直接用但需要对V视差图做二值化。二值化的阈值怎么定我一般用最大值的0.3到0.5倍。如果地面斜线不够明显可以先用形态学闭运算把断线连起来。检测到直线之后用最小二乘法拟合出斜线的方程$d a \cdot v b$。这个方程就是地面的视差-行坐标关系。对于图像中任意一行 $v$如果某个像素的视差接近 $a \cdot v b$那它很可能属于地面。你可以设一个容差比如±3个视差单位把地面像素分割出来。这里有个坑如果场景中有斜坡或者相机俯仰角变化地面斜线可能不是一条直线而是分段直线或者曲线。这时候可以用分段拟合或者用RANSAC来鲁棒估计。RANSAC的迭代次数取100到200内点阈值取2到3个视差单位效果比较稳。4.2 障碍物在V视差图中的竖直条带障碍物在V视差图里表现为竖直的亮条。为什么是竖直的因为障碍物在图像中占据一定的高度范围从底部到顶部但它的视差在整个高度上变化不大假设障碍物是竖直的。所以统计每一行的时候障碍物对应的视差值都会出现形成一条竖直的亮带。检测竖直条带的方法对V视差图的每一列对应一个视差值统计该列的亮度和。如果某个视差值对应的列在多个行上都有高计数那这个视差值很可能对应一个障碍物。你可以设定一个阈值比如列和大于某个值就认为该视差是障碍物视差。然后结合地面斜线把不属于地面的像素归为障碍物。更精细的做法是用连通域分析。把V视差图二值化之后找连通域每个连通域对应一个潜在的障碍物。连通域的宽度对应障碍物的视差范围高度对应障碍物在图像中的行范围。根据这些信息你可以估计障碍物的大致距离和高度。4.3 结合U视差图做横向分割V视差图对地面和障碍物的前后关系很敏感但对横向位置不敏感。比如两辆车并排在V视差图里可能混在一起。这时候U视差图就派上用场了。U视差图的横轴是视差纵轴是列坐标。同一辆车在U视差图里会形成一个亮斑不同车对应不同的亮斑。通过检测U视差图里的亮斑你可以把不同障碍物在横向上分开。具体做法在U视差图上对每个视差值找到对应的列坐标范围。如果某个视差值的亮斑集中在某个列区间那这个列区间就是障碍物的横向位置。结合V视差图得到的视差范围你就能在原始图像中框出障碍物的三维区域。我试过把U视差和V视差的结果融合先用V视差检测地面和障碍物的视差范围再用U视差确定每个障碍物的横向边界。这样得到的障碍物分割比单独用任何一种都准。融合的时候可以用简单的逻辑与操作或者用概率图模型做更精细的融合。4.4 一个完整的处理流程示例把上面的步骤串起来一个完整的U-V视差处理流程大概是这样的用SGBM计算视差图转成浮点数无效值置0。中值滤波去噪裁剪边缘10%。构造V视差图bin大小取2归一化。在V视差图上做霍夫变换检测地面斜线拟合方程。根据地面方程分割地面像素和非地面像素。在V视差图上检测竖直条带得到障碍物视差范围。构造U视差图检测亮斑得到障碍物横向范围。融合V和U的结果输出障碍物的三维框。这个流程在室外场景下跑帧率大概能到10到15帧取决于分辨率和优化程度。如果只做地面检测可以跳过U视差速度更快。注意U-V视差对相机标定误差很敏感。如果极线校正不准地面斜线会变粗甚至分叉。所以做U-V视差之前一定要确保标定参数准确极线校正的误差在0.5像素以内。5. 常见问题与排查技巧实录5.1 V视差图里地面斜线不清晰怎么办这是最常见的问题。原因可能有几个视差图噪声太大、bin大小不合适、地面纹理太弱。排查顺序先看原始视差图如果地面区域本身就有很多空洞那问题在立体匹配不在U-V视差。可以尝试调SGBM的参数比如增大blockSize或者用WLS滤波做后处理。如果视差图看起来还行但V视差图里斜线断断续续那多半是bin大小太小。把bin从1调到2或4试试。另外归一化之前可以先做一次对数变换$log(1count)$这样弱信号也能显示出来。还有一个容易被忽略的原因地面在图像中占的比例太小。如果相机俯仰角很小地面只占图像底部几行那V视差图里地面斜线自然很短。这时候可以调整相机安装角度或者只关注底部区域。5.2 障碍物竖直条带和地面斜线混在一起当障碍物距离很近时它的视差很大在V视差图里可能和地面斜线的右下端重叠。这时候单纯靠竖直条带检测会误判。解决办法是先用地面斜线把地面像素分割掉然后在剩余像素里检测竖直条带。分割的时候容差不要设太大±2到3个视差单位就够了。如果地面斜线拟合不准可以先用RANSAC剔除异常点再拟合。另一个技巧利用视差的连续性。地面像素的视差在相邻行之间是渐变的而障碍物和地面交界处视差会突变。可以在V视差图上计算行方向的梯度梯度大的位置就是交界处。5.3 U视差图里亮斑弥散U视差图里亮斑弥散通常是因为视差图在横向上的噪声。比如同一个物体左边缘和右边缘的视差差了5个像素那在U视差图里就会形成一个宽亮斑。这时候可以先用双边滤波对视差图做保边平滑再构造U视差图。双边滤波的参数空间sigma取3到5颜色sigma取10到20。如果亮斑弥散是因为物体本身有斜面比如斜着的墙那这是真实情况不应该消除。你可以通过亮斑的宽度来估计斜面的倾斜程度。5.4 计算速度太慢怎么优化逐行逐列统计直方图在Python里用循环确实慢。优化方法用NumPy的np.apply_along_axis或者向量化操作。更快的做法是用np.bincount但需要先量化视差值。比如把视差除以bin大小取整然后用bincount统计。这样比np.histogram快很多。如果还是不够快可以考虑用C实现或者用CUDA加速。不过对于大多数离线分析场景Python的速度已经够了。实时场景的话建议把U-V视差的计算放在GPU上或者只对感兴趣区域ROI做统计。5.5 常见问题速查表问题现象可能原因排查方法解决技巧V视差图地面斜线断裂bin太小或视差噪声大检查原始视差图地面区域增大bin中值滤波对数变换障碍物条带与地面混叠近距离障碍物视差大查看V视差图右下角先分割地面再检测条带U视差图亮斑弥散视差横向噪声检查同一物体左右边缘视差双边滤波增大bin计算速度慢Python循环效率低计时各步骤向量化bincountROI地面斜线方向反了视差图左右颠倒检查极线校正翻转视差图或调整相机实操心得我习惯在调试U-V视差的时候把原始视差图、V视差图、U视差图并排显示。这样一眼就能看出问题出在哪一步。如果原始视差图就有问题那后面怎么调都没用。如果原始视差图OK但V视差图不对那就是统计参数的问题。6. 从U-V视差延伸出去还能做什么U-V视差的价值不止于地面检测和障碍物分割。我后来发现它还能做几件挺有意思的事。第一视差图校正。如果视差图在某些区域有系统性偏差比如地面区域整体偏大你可以在V视差图里看到地面斜线整体偏移。通过拟合实际斜线和理论斜线的差异可以反过来校正视差图。这个方法比逐像素校正更稳定因为它利用了全局结构。第二相机俯仰角估计。地面斜线的斜率与相机俯仰角有解析关系。如果你知道相机高度和焦距可以通过斜线斜率反推俯仰角。这在在线标定场景下很有用比如车辆行驶过程中相机因为震动而角度变化可以用U-V视差实时估计。第三可通行区域检测。地面斜线以下视差大于地面视差的区域通常是障碍物斜线附近是地面斜线以上视差小于地面视差可能是天空或者远处背景。根据这个关系你可以快速分割出可通行区域不需要做完整的点云分割。第四多帧融合。单帧的U-V视差可能有噪声但多帧累积之后地面斜线和障碍物条带会越来越清晰。你可以用滑动窗口做累积窗口大小取5到10帧。累积的时候要注意运动补偿否则运动物体会拖尾。这些扩展用法我在不同项目里都试过效果最好的是相机俯仰角估计和可通行区域检测。前者几乎不需要额外计算后者比基于点云的方法快一个数量级。当然它们都有前提条件地面要比较平坦相机安装要稳定。如果地面起伏很大或者相机晃动剧烈U-V视差的效果会打折扣。最后分享一个小技巧如果你觉得手动调bin大小和阈值太麻烦可以写一个简单的网格搜索。把bin大小从1到8阈值从0.1到0.9各跑一遍用地面斜线的拟合残差作为评价指标选残差最小的组合。虽然费点时间但一次调好之后同一套相机参数下基本不用再动。
阅读完成 · 觉得有帮助?
咨询建站