本系列文章旨在系統性地闡述如何利用 Python 與 OpenCV 庫,從零開始構建一個完整的雙目立體視覺系統。
本項目github地址:https://github.com/present-cjn/stereo-vision-python.git
在上一篇文章中,我們完成了相機標定這一最關鍵的基礎步驟,并得到了一份高精度的相機參數文件。現在,我們將利用這份參數文件,來執行雙目視覺的核心任務——立體匹配 (Stereo Matching)。
我們的目標是,模仿人類大腦處理左右眼圖像差異的方式,為左圖中的每一個像素,在右圖中找到它的對應點。通過計算它們之間的像素位置差異,即視差 (Disparity),我們就能得到一張灰色的、包含深度信息的圖像——視差圖 (Disparity Map)。這張圖上的每一個像素點的強度值,都直接對應著真實世界的深度信息。
本文將詳細講解立體匹配的全過程,從準備工作“立體校正”,到核心算法“SGBM”,再到決定最終效果的“參數調優”。
1. 匹配前的準備:立體校正 (Rectification)
在原始的雙目圖像中,由于相機安裝可能存在微小的角度偏差,左圖中的一個點,其在右圖中的對應點可能位于一條傾斜的直線上(我們稱之為“對極線”)。在整張圖上沿著成千上萬條斜線去搜索匹配點,計算量巨大且效率低下。
立體校正 (Stereo Rectification) 的目的,就是通過數學變換,將這種復雜的二維搜索問題,簡化為高效的一維搜索。
- 目標: 利用標定得到的旋轉矩陣
R
和平移向量T
,對左右圖像進行虛擬的旋轉,使得兩個相機的成像平面完全共面,并且像素行嚴格對齊。 - 效果: 校正后,對于左圖上的任意一個像素點
(x, y)
,其在右圖上的對應點必定位于同一水平線y
上。現在,算法只需要在這一行上進行一維搜索即可,效率和可靠性都大大提升。
原左右圖像為
極線校正后左右圖像為
代碼實現 (utils/image_utils.py
)
我們的 rectify_stereo_pair
函數封裝了整個校正過程。
# utils/image_utils.py
def rectify_stereo_pair(left_img, right_img, stereo_params, calib_image_size):# 從標定參數中提取 K, D, R, T 矩陣K1, D1, K2, D2, R, T = (stereo_params[k] for k in ('K1', 'D1', 'K2', 'D2', 'R', 'T'))# 1. 計算校正變換和投影矩陣# 這一步是核心,它計算出將相機坐標系旋轉到理想平行狀態所需的旋轉矩陣 R1, R2# 以及將3D點投影到校正后圖像平面上的投影矩陣 P1, P2# 最重要的是,它還輸出了用于三維重建的 Q 矩陣R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(K1, D1, K2, D2, calib_image_size, R, T, alpha=0)# 2. 計算從原始圖像到校正圖像的像素映射表# `initUndistortRectifyMap` 會為每個像素計算出它在校正后圖像中的新位置left_map1, left_map2 = cv2.initUndistortRectifyMap(K1, D1, R1, P1, left_img.shape[1::-1], cv2.CV_16SC2)right_map1, right_map2 = cv2.initUndistortRectifyMap(K2, D2, R2, P2, right_img.shape[1::-1], cv2.CV_16SC2)# 3. 應用映射表,執行校正left_rectified = cv2.remap(left_img, left_map1, left_map2, cv2.INTER_LINEAR)right_rectified = cv2.remap(right_img, right_map1, right_map2, cv2.INTER_LINEAR)return left_rectified, right_rectified, Q
這個函數的輸出,就是我們進行立體匹配所需要的、高質量的校正圖像和Q矩陣。
2. 核心算法:SGBM (Semi-Global Block Matching)
現在我們有了對齊的左右圖像,接下來就要計算視差了。本項目采用的是 SGBM (半全局塊匹配) 算法,它是對傳統 BM (塊匹配) 算法的巨大改進。
- 基本思想: 它不僅像 BM 算法那樣,通過比較小像素塊的相似度(SAD)來尋找匹配,更引入了一個“全局”的視角。它會懲罰那些導致視差劇烈跳變的匹配,鼓勵生成一條平滑、連續的視差路徑。
- 優勢: 相比 BM,SGBM 能更好地處理弱紋理區域(如白墻)和重復紋理區域,生成的視差圖更完整、噪聲更少。
代碼實現 (processing/stereo_matcher.py
)
我們將 SGBM 的所有邏輯都封裝在 StereoMatcher
類中。
# processing/stereo_matcher.py
import cv2
import configclass StereoMatcher:def __init__(self):# 在構造函數中,從 config.py 文件一次性加載所有 SGBM 參數# 這使得調參變得非常方便self.matcher = cv2.StereoSGBM_create(minDisparity=config.SGBM_MIN_DISPARITY,numDisparities=config.SGBM_NUM_DISPARITIES,blockSize=config.SGBM_BLOCK_SIZE,P1=config.SGBM_P1,P2=config.SGBM_P2,disp12MaxDiff=config.SGBM_DISP12_MAX_DIFF,preFilterCap=config.SGBM_PRE_FILTER_CAP,uniquenessRatio=config.SGBM_UNIQUENESS_RATIO,speckleWindowSize=config.SGBM_SPECKLE_WINDOW_SIZE,speckleRange=config.SGBM_SPECKLE_RANGE,mode=config.SGBM_MODE)def compute_disparity(self, left_rectified_img, right_rectified_img):# SGBM 算法要求輸入單通道的灰度圖gray_left = cv2.cvtColor(left_rectified_img, cv2.COLOR_BGR2GRAY)gray_right = cv2.cvtColor(right_rectified_img, cv2.COLOR_BGR2GRAY)# 計算視差圖disparity_map = self.matcher.compute(gray_left, gray_right)return disparity_map
4. 視差圖的秘密:*16
的含義
當你拿到 compute
函數返回的 disparity_map
時,需要注意一個關鍵細節:它的數據類型是 CV_16S
(16位有符號整數),并且里面的值是真實視差的16倍。
這是 OpenCV 為了在內部用高效的整數運算來表示亞像素精度而做的設計。一個值為 800
的點,其真實的、以像素為單位的視差是 800 / 16.0 = 50.0
。
因此,當我們需要使用或顯示真實的視差值時,必須記得將它除以16.0。
用項目的中的測試圖片可以得到以下視差圖
總結
通過立體校正的預處理和強大的 SGBM 算法,我們成功地從兩張2D圖像中提取出了包含深度信息的視差圖。更重要的是,我們學會了如何通過交互式調參來優化匹配結果,這是所有立體視覺應用開發者的核心技能之一。
在下一篇文章中,我們將進入一個核心步驟:利用這張高質量的視差圖和 Q
矩陣,計算出深度圖并重建出真正的三維點云。