在OpenCV中使用StereoBM生成的视差图不佳

14 投票
1 回答
14821 浏览
提问于 2025-04-18 02:29

我搭建了一个立体相机系统,但在生成好的视差图时遇到了问题。下面是我用两个校正过的图像和生成的视差图的例子:

校正后的图像和视差图

如你所见,结果相当糟糕。调整StereoBM的设置也没有太大变化。

我的设置

  • 两个相机是同一型号,通过USB连接到我的电脑。
  • 它们固定在一个坚固的木板上,这样就不会移动。我尽量把它们对齐,但当然不是完美的。它们无法移动,所以在标定过程中和之后的位置是一样的。
  • 我使用OpenCV对这对相机进行了标定,并用OpenCV的StereoBM类生成视差图。
  • 可能不太相关,但我用的是Python编程。

我能想到的问题

这是我第一次做这个,所以我远不是专家,但我猜问题可能出在标定或立体校正上,而不是视差图的计算。我尝试了StereoBM的所有设置组合,虽然得到的结果不同,但都像上面显示的视差图:黑白相间的块状。

这个想法得到了进一步支持,因为我理解立体校正应该让每张图片上的所有点都对齐,使它们通过一条直线(在我这个例子中是水平线)连接起来。如果我把两个校正后的图像放在一起看,很明显并不是这样。右边图像上的对应点比左边的高得多。不过我不确定是标定还是校正出了问题。

代码

实际的代码封装在对象中——如果你有兴趣看到完整的代码,可以在GitHub上找到。这里是一个简化的示例,展示了实际运行的内容(当然在真实代码中,我使用的不止两张图片进行标定):

import cv2
import numpy as np

## Load test images
# TEST_IMAGES is a list of paths to test images
input_l, input_r = [cv2.imread(image, cv2.CV_LOAD_IMAGE_GRAYSCALE)
                    for image in TEST_IMAGES]
image_size = input_l.shape[:2]

## Retrieve chessboard corners
# CHESSBOARD_ROWS and CHESSBOARD_COLUMNS are the number of inside rows and
# columns in the chessboard used for calibration
pattern_size = CHESSBOARD_ROWS, CHESSBOARD_COLUMNS
object_points = np.zeros((np.prod(pattern_size), 3), np.float32)
object_points[:, :2] = np.indices(pattern_size).T.reshape(-1, 2)
# SQUARE_SIZE is the size of the chessboard squares in cm
object_points *= SQUARE_SIZE
image_points = {}
ret, corners_l = cv2.findChessboardCorners(input_l, pattern_size, True)
cv2.cornerSubPix(input_l, corners_l,
                 (11, 11), (-1, -1),
                 (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS,
                  30, 0.01))
image_points["left"] = corners_l.reshape(-1, 2)
ret, corners_r = cv2.findChessboardCorners(input_r, pattern_size, True)
cv2.cornerSubPix(input_r, corners_r,
                 (11, 11), (-1, -1),
                 (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS,
                  30, 0.01))
image_points["right"] = corners_r.reshape(-1, 2)

## Calibrate cameras
(cam_mats, dist_coefs, rect_trans, proj_mats, valid_boxes,
 undistortion_maps, rectification_maps) = {}, {}, {}, {}, {}, {}, {}
criteria = (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS,
            100, 1e-5)
flags = (cv2.CALIB_FIX_ASPECT_RATIO + cv2.CALIB_ZERO_TANGENT_DIST +
         cv2.CALIB_SAME_FOCAL_LENGTH)
(ret, cam_mats["left"], dist_coefs["left"], cam_mats["right"],
 dist_coefs["right"], rot_mat, trans_vec, e_mat,
 f_mat) = cv2.stereoCalibrate(object_points,
                              image_points["left"], image_points["right"],
                              image_size, criteria=criteria, flags=flags)
(rect_trans["left"], rect_trans["right"],
 proj_mats["left"], proj_mats["right"],
 disp_to_depth_mat, valid_boxes["left"],
 valid_boxes["right"]) = cv2.stereoRectify(cam_mats["left"],
                                           dist_coefs["left"],
                                           cam_mats["right"],
                                           dist_coefs["right"],
                                           image_size,
                                           rot_mat, trans_vec, flags=0)
for side in ("left", "right"):
    (undistortion_maps[side],
     rectification_maps[side]) = cv2.initUndistortRectifyMap(cam_mats[side],
                                                           dist_coefs[side],
                                                           rect_trans[side],
                                                           proj_mats[side],
                                                           image_size,
                                                           cv2.CV_32FC1)

## Produce disparity map
rectified_l = cv2.remap(input_l, undistortion_maps["left"],
                        rectification_maps["left"],
                        cv2.INTER_NEAREST)
rectified_r = cv2.remap(input_r, undistortion_maps["right"],
                        rectification_maps["right"],
                        cv2.INTER_NEAREST)
cv2.imshow("left", rectified_l)
cv2.imshow("right", rectified_r)
block_matcher = cv2.StereoBM(cv2.STEREO_BM_BASIC_PRESET, 0, 5)
disp = block_matcher.compute(rectified_l, rectified_r, disptype=cv2.CV_32F)
cv2.imshow("disparity", disp)

这里出了什么问题呢?

1 个回答

17

结果发现问题出在可视化上,而不是数据本身。我在某个地方看到过,cv2.reprojectImageTo3D需要用浮点数格式的视差图,所以我一直在从block_matcher.compute请求cv2.CV_32F

不过,仔细阅读OpenCV的文档后,我意识到我之前的想法可能是错的。其实为了速度,我更想用整数而不是浮点数。但是关于cv2.imshow处理16位有符号整数(和16位无符号整数)的说明并不清楚,所以在可视化时我还是把值保留为浮点数。

根据关于cv2.imshow的文档,32位浮点数的值被假设在0到1之间,因此会乘以255。255是一个饱和点,表示像素会显示为白色。在我的情况下,这种假设导致了一个二进制图。我手动将其缩放到0-255的范围,然后再除以255,以抵消OpenCV也做了同样的事情。我知道,这样做并不好,但我只是为了调试我的StereoBM,所以性能不是关键。解决方案看起来是这样的:

# Other code as above
disp = block_matcher.compute(rectified_l, rectified_r, disptype=cv2.CV_32F)
norm_coeff = 255 / disp.max()
cv2.imshow("disparity", disp * norm_coeff / 255)

这样,视差图看起来就不错了。

撰写回答