如何用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
相关产品推荐
相关产品推荐

