返回

不同机位图像拼接:解决视差问题的深度解析与实战

python

解决不同机位拍摄图像拼接问题

遇到的问题挺有意思:要拼接两张从不同机位拍摄的照片。一般图像拼接都是假设照片是从同一位置、不同角度拍摄的。 但这次,两张照片拍摄时间相同,但视角不同,导致拼接困难。

问题图片如下:

不同机位照片

直接用 OpenCV 的 cv2.Stitcher 拼接, 结果是这样:

直接拼接结果

能看到蓝色线对上了,红色线却断了。 这就是因为两张照片拍摄位置不同的缘故。

问题根源:视差

核心问题在于"视差(Parallax)"。当相机位置改变时,场景中物体之间的相对位置也会发生变化。 离相机近的物体,位置变化更明显;离相机远的,变化较小。 这种现象在拼接图像时,就会造成重影、断裂等问题。

尝试的解决思路及遇到的问题

原本想先对图像做透视变换,然后再拼接。 尝试的代码如下:

image1 = cv2.imread('./VideosToStitch/video1.png')

tl = (907,0)
bl = (0,1280)
tr = (1047,0)
br = (1920,1280)

pts1 = np.float32([tl,bl,tr,br])
pts2 = np.float32([[0,0],[0,1280],[1920,0],[1920,1280]])

matrix = cv2.getPerspectiveTransform(pts1,pts2)
transformedFrame = cv2.warpPerspective(image1, matrix,(1920,1280), flags=3)

结果是这样的:

透视变换结果

红、蓝线是直了,但冰面细节全没了。 这说明,简单的透视变换不足以解决这个问题。

可行的解决方案

1. 深度估计 + 3D 重建 (比较复杂,效果可能好)

  • 原理: 先估计出两张图像中每个像素的深度信息(即离相机的距离)。 有了深度信息,就可以把两张图像重建到三维空间中。 然后,可以从一个新的虚拟相机位置,重新渲染出两张“修正”过的图像。 这样,“修正”过的图像就相当于从同一位置拍摄的,可以进行拼接。

  • 步骤:

    1. 特征点匹配: 在两张图像中找到相同的特征点 (比如用 SIFT, SURF, ORB 等算法)。
    2. 立体匹配 / 深度估计: 利用匹配到的特征点,计算出图像中每个像素的深度。 可以用立体匹配算法 (比如 Semi-Global Block Matching, SGBM), 或者更先进的基于深度学习的方法。
    3. 3D 重建: 有了图像和对应的深度图,就可以重建出三维点云。
    4. 虚拟相机渲染: 选择一个合适的虚拟相机位置,用 OpenGL, Blender, 或其他渲染工具,从点云重新渲染出两张图像。
    5. 图像拼接: 用 OpenCV 的 cv2.Stitcher 拼接渲染后的图像。
  • 进阶: 实际操作时,需要仔细调整相机内参、外参,才能得到较好的重建效果。

  • 代码示例 (简化):

    # (这部分代码比较复杂,需要用到 OpenCV 的 stereo 模块,以及深度学习模型)
    # 这里只给出大致思路
    import cv2
    import numpy as np
    
    # 1. 特征点匹配 (略)
    # ...
    
    # 2. 立体匹配/深度估计 (以 SGBM 为例)
    stereo = cv2.StereoSGBM_create(...) # 设置参数
    disparity = stereo.compute(image1_gray, image2_gray).astype(np.float32) / 16.0
    
    # 3. 3D 重建 (需要相机内参)
    # ...
    
    # 4. 虚拟相机渲染 (需要用到渲染引擎, 此处省略)
    # ...
    
    # 5. 图像拼接 (同上)
    # ...
    
  • 安全提示: 无特定安全风险.

2. 图像扭曲 + 基于特征的对齐 (简单,但可能有局限)

  • 原理: 手动选择一些控制点,然后通过变换,把一张图片扭曲,尽可能与另一张图片对齐。 然后,再用特征点匹配、融合等方法,拼接扭曲后的图像。
    如果仅仅是冰球场景, 只需要手动选择冰球场地周围的几条参考线作为匹配,也许会很有效.

  • 步骤:

    1. 手动选择控制点: 在两张图中,手动选择几组对应的点 (比如,球场边缘的几个角)。
    2. 计算变换矩阵: 用选定的点,计算出一个变换矩阵 (比如单应性矩阵 Homography)。
    3. 图像扭曲: 用计算出的矩阵,对其中一张图像进行扭曲。
    4. 特征点匹配: 在扭曲后的图像和另一张图像上,提取特征点 (SIFT, SURF, ORB 等)。
    5. 图像拼接: 根据匹配到的特征点,进行图像拼接 (可以用 OpenCV 的 cv2.findHomographycv2.warpPerspective)。
  • 进阶: 对结果做一些图像融合,如曝光补偿(Exposure Compensation),多频段融合(Multi-Band Blending)可以有效降低拼接产生的瑕疵

  • 代码示例:

    import cv2
    import numpy as np
    
    # 1. 手动选择控制点 (假设已经选好了, 存在 pts1, pts2 里)
    # pts1: image1 中的点, pts2: image2 中对应的点
    
    image1 = cv2.imread('./VideosToStitch/video2.png')
    image2 = cv2.imread('./VideosToStitch/video1.png')
    
    pts1 = np.float32([[947,28],[1,1280],[1152,5],[1919, 1278]]) #tl, bl, tr, br
    pts2 = np.float32([[0,0],[0,1280],[1044,0],[1919,1104]])
    
    # 2. 计算变换矩阵
    matrix = cv2.getPerspectiveTransform(pts2, pts1)
    
    # 3. 图像扭曲
    warped_image2 = cv2.warpPerspective(image2, matrix, (image1.shape[1], image1.shape[0]))
    
    # 4&5. 拼接 (最简单的, 直接叠加)
    result = cv2.addWeighted(image1, 0.5, warped_image2, 0.5, 0)
    
    cv2.imwrite('./VideosToStitch/warped_stitch.png', result)
    
    
  • 注意: 手动选择控制点这个比较麻烦。而且,如果两张图视角差异太大,这种方法可能效果不好。 因为这种变换只是一个近似,不能完全消除视差的影响. 如下示意,既要保证水平线对其,又要保证垂直线对其几乎不可能。
    不同视角展示

3. 尝试只拼接特定区域

  • 原理:
    放弃完美的全图拼接. 如果只是需要其中一部分区域(比如冰面)的拼接,那么可以裁剪、调整,然后做局部拼接。

  • 代码示例: (具体代码实现取决于需求,下面仅仅展示拼接前的裁切工作)

import cv2
import numpy as np

images = []
image1 = cv2.imread('./VideosToStitch/video2.png')
images.append(image1)
image2 = cv2.imread('./VideosToStitch/video1.png')
images.append(image2)

#对图片进行裁剪,这里可以根据具体要求对H W进行裁切。
cropped_images = []
for img in images:
	cropped_images.append(img[0:720, 0:1920].copy()) # 例如,只保留下半部分


stitcher = cv2.Stitcher.create()
status, stitched = stitcher.stitch(cropped_images)

cv2.imwrite('./VideosToStitch/stitched_crop.png', stitched)

如果需要保证裁切之后的图像还能匹配得上,那么裁切就需要依据场景中可以作为基准的部分来做.

总结

不同机位图像拼接是个难题。 没有完美的通用方法。 具体选择哪种方法,取决于对拼接质量的要求、图像的具体内容、以及愿意投入的精力。 如果对效果要求高,可能需要尝试复杂的 3D 重建方法。如果能接受一定程度的误差,可以尝试图像扭曲的方法. 甚至直接放弃完美拼接。