You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python+OpenCV的ORB、RANSAC实现3张序列图像全景拼接

3图全景拼接实现方案(以img2为中心)

你的现有代码已经完成了特征提取、匹配和两两变换矩阵计算,只需要调整变换逻辑,将img1、img3都统一变换到img2的坐标系下再合并即可,无需分次拼接(分次拼接会累积变换误差)。

实现逻辑

  • 首先明确两个变换矩阵的含义:我们需要将img1、img3都映射到img2的坐标系,img2本身不需要变换
  • 计算三张图所有角点经过对应变换后的坐标范围,确定全景画布的尺寸和偏移值
  • 分别将三张图映射到全景画布的对应位置即可

完整可运行代码

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

# 你原有代码中的匹配、矩阵计算函数保持不变
def draw_matches(img1, kpt1, img2, kpt2, matches):
    h1, w1 = img1.shape[:2]
    h2, w2 = img2.shape[:2]
    new_img = np.zeros((max([h1, h2]), w1 + w2, 3), dtype='uint8')
    new_img[:h1, :w1, :] = np.dstack([img1, img1, img1])
    new_img[:h2, w1:w1 + w2, :] = np.dstack([img2, img2, img2])
    for m in matches:
        (x1, y1) = kpt1[m.queryIdx].pt
        (x2, y2) = kpt2[m.trainIdx].pt
        cv.circle(new_img, (int(x1), int(y1)), 4, (0, 255, 0), 1)
        cv.circle(new_img, (int(x2) + w1, int(y2)), 4, (0, 255, 0), 1)
        cv.line(new_img, (int(x1), int(y1)), (int(x2) + w1, int(y2)), (255, 0, 255), 1)
    return new_img

def find_homography(kpt1, kpt2, matches):
    src_pts = np.float32([kpt1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
    dst_pts = np.float32([kpt2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)
    transformation_rigid_matrix, rigid_mask = cv.estimateAffinePartial2D(src_pts, dst_pts)
    affine_row = [0, 0, 1]
    transformation_rigid_matrix = np.vstack((transformation_rigid_matrix, affine_row))
    return transformation_rigid_matrix

# 读取和预处理图片
img1 = cv.imread('1.jpg', 1)
img2 = cv.imread('2.jpg', 1)
img3 = cv.imread('3.jpg', 1)
img1_gray = cv.cvtColor(img1, cv.COLOR_BGR2GRAY)
img2_gray = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
img3_gray = cv.cvtColor(img3, cv.COLOR_BGR2GRAY)
h2, w2 = img2_gray.shape[:2]

# ORB特征提取匹配
orb = cv.ORB_create()
kpts1, des1 = orb.detectAndCompute(img1_gray, None)
kpts2, des2 = orb.detectAndCompute(img2_gray, None)
kpts3, des3 = orb.detectAndCompute(img3_gray, None)

bf = cv.BFMatcher_create(cv.NORM_HAMMING)
matches1to2 = bf.knnMatch(des1, des2, k=2)
matches3to2 = bf.knnMatch(des3, des2, k=2) # 调整匹配方向,直接得到img3到img2的变换

# 筛选优质匹配
good1to2 = []
for m, n in matches1to2:
    if m.distance < 0.6 * n.distance:
        good1to2.append(m)
good3to2 = []
for m, n in matches3to2:
    if m.distance < 0.6 * n.distance:
        good3to2.append(m)

# 计算变换矩阵:M1是img1到img2的变换,M3是img3到img2的变换
M1 = find_homography(kpts1, kpts2, good1to2)
M3 = find_homography(kpts3, kpts2, good3to2)

# 计算三张图所有角点变换后的坐标范围
h1, w1 = img1_gray.shape[:2]
h3, w3 = img3_gray.shape[:2]
# img1的四个角点变换到img2坐标系
corners1 = np.float32([[0,0], [0,h1], [w1,h1], [w1,0]]).reshape(-1,1,2)
corners1_trans = cv.perspectiveTransform(corners1, M1)
# img3的四个角点变换到img2坐标系
corners3 = np.float32([[0,0], [0,h3], [w3,h3], [w3,0]]).reshape(-1,1,2)
corners3_trans = cv.perspectiveTransform(corners3, M3)
# img2的四个角点
corners2 = np.float32([[0,0], [0,h2], [w2,h2], [w2,0]]).reshape(-1,1,2)
# 合并所有角点求最大最小坐标
all_corners = np.concatenate((corners1_trans, corners2, corners3_trans), axis=0)
x_min, y_min = np.int32(all_corners.min(axis=0).ravel() - 0.5)
x_max, y_max = np.int32(all_corners.max(axis=0).ravel() + 0.5)
# 计算偏移量,把所有坐标移到正数范围
offset_x = -x_min
offset_y = -y_min
pan_w = x_max - x_min
pan_h = y_max - y_min

# 创建全景画布
panorama = np.zeros((pan_h, pan_w, 3), dtype=np.uint8)
# 偏移后的变换矩阵
M1_offset = np.array([[1,0,offset_x], [0,1,offset_y], [0,0,1]]) @ M1
M3_offset = np.array([[1,0,offset_x], [0,1,offset_y], [0,0,1]]) @ M3
M2_offset = np.array([[1,0,offset_x], [0,1,offset_y], [0,0,1]])

# 把三张图warp到画布
cv.warpPerspective(img1, M1_offset, (pan_w, pan_h), dst=panorama, borderMode=cv.BORDER_TRANSPARENT)
cv.warpPerspective(img2, M2_offset, (pan_w, pan_h), dst=panorama, borderMode=cv.BORDER_TRANSPARENT)
cv.warpPerspective(img3, M3_offset, (pan_w, pan_h), dst=panorama, borderMode=cv.BORDER_TRANSPARENT)

# 显示结果
plt.figure(figsize=(15,5))
plt.imshow(cv.cvtColor(panorama, cv.COLOR_BGR2RGB))
plt.axis('off')
plt.show()

补充说明

  • 代码保留了你原有逻辑,仅调整了变换矩阵的计算方向,一次性将所有图映射到img2的坐标系,避免了分次拼接的误差累积
  • 重叠区域直接采用后绘制的图像覆盖的逻辑,如果需要更自然的过渡,可以在重叠区域做加权平均融合
  • 可以根据实际匹配效果调整特征匹配的阈值(0.6那个参数),如果匹配误差大可以适当调低阈值

内容的提问来源于stack exchange,提问作者Jonas Zerbib

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 11:12:03