# 前言

昨天总结了基于特征的图像对齐,今天来尝试一下,基于特征对齐的二景图像拼接吧。如下图所示,这是同一地点不同角度拍摄的两幅图像,现在尝试将其进行拼接。

# 实验环境

  • OpenCV:4.1.2
  • Python:3.6.5
  • 平台:Windows 10

# 实现思路

  1. 基于 ORB 特征检测算法检测两幅图像的特征关键点
  2. 对特征点进行匹配
  3. 从所匹配的全部关键点中筛选出优秀的特征点(基于距离筛选)
  4. 计算单应性变换矩阵
  5. 对右图进行映射变换
  6. 将左图拷贝到特定位置完成拼接

# 代码演示

import numpy as np
import cv2 as cv
# 导入自己写的一个工具库
import opencv_utils
MAX_FEATURES = 500
GOOD_MATCH_PERCENT = 0.15
def stitchImage(img1, img2):
    # Detect ORB features and compute descriptors.
    orb = cv.ORB_create(MAX_FEATURES)
    kp1, des1 = orb.detectAndCompute(img1, None)
    kp2, des2 = orb.detectAndCompute(img2, None)
    # Match features.
    matcher = cv.DescriptorMatcher_create(cv.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING)
    matches = matcher.match(des1, des2, None)
    # Sort matches by score
    matches.sort(key=lambda x: x.distance, reverse=False)
    # Remove not so good matches
    numGoodMatches = int(len(matches) * GOOD_MATCH_PERCENT)
    matches = matches[:numGoodMatches]
    # Draw top matches
    imMatches = cv.drawMatches(img1, kp1, img2, kp2, matches, None)
    cv.imwrite("matches.png", imMatches)
    # Extract location of good matches
    points1 = np.zeros((len(matches), 2), dtype=np.float32)
    points2 = np.zeros((len(matches), 2), dtype=np.float32)
    for i, match in enumerate(matches):
        points1[i, :] = kp1[match.queryIdx].pt
        points2[i, :] = kp2[match.trainIdx].pt
    # 通过两个图像的特征点计算变换矩阵,获得变换矩阵和掩模
    (M, mask) = cv.findHomography(points1, points2, cv.RANSAC)
    # 对 img1 透视变换,M 是变换矩阵, 变换后的大小是 (img1.w + img2.w, img1.h)
    result = cv.warpPerspective(img1, M, (img1.shape[1] + img2.shape[1], img1.shape[0]))
    # 将 img2 的值赋给结果图像
    result[0:img2.shape[0], 0:img2.shape[1]] = img2
    return result
if __name__ == "__main__":
    
    img1 = cv.imread(r"F:\opencvTest\stitch_right.png")
    img2 = cv.imread(r"F:\opencvTest\stitch_left.png")
    
    # 顺时针旋转 90 度
    img1_90 = opencv_utils.rotateClockWise90(img1)
    img2_90 = opencv_utils.rotateClockWise90(img2)
    # 进行拼接
    stitched = stitchImage(img1_90, img2_90)
    # 结果图逆时针旋转 90 度
    stitched = opencv_utils.rotateAntiClockWise90(stitched)
    cv.imwrite('stitched.png', stitched)
    # 原图与结果图一起展示,易于对比
    result = opencv_utils.merge3Image(img1, img2, stitched)
    cv.imshow('result', result)
    cv.imwrite('result.png', result)
    cv.waitKey(0)
    cv.destroyAllWindows()
# opencv_utils.py
import cv2 as cv
import numpy as np
# 合并两张图片为一张图片
def merge2Image(src1, src2):
    if not src1.shape == src2.shape:
        print("图片的尺寸不相等")
        return
    y = src1.shape[0]
    x = src1.shape[1]
    res = np.zeros((y, x * 2, 3), dtype=src1.dtype)
    res[:, :x, :] = src1
    res[:, x:, :] = src2
    return res
# 合并三张图象为一张图像,第一张第二张上下合并,新图像再与第三张左右合并
def merge3Image(src1, src2, src3):
    if (not src1.shape == src2.shape) or (not src1.shape[0] + src2.shape[0] == src3.shape[0]):
        print("图片的尺寸不合适")
        return
    height = src1.shape[0] + src2.shape[0]
    width = src1.shape[1] + src3.shape[1]
    res = np.zeros((height, width, 3), dtype=src1.dtype)
    res[:src1.shape[0], :src1.shape[1], :] = src1
    res[src1.shape[0]:, :src2.shape[1], :] = src2
    res[:, src1.shape[1]:, :] = src3
    return res
# 顺时针旋转 90 度
def rotateClockWise90(img):
    trans_img = cv.transpose(img)
    new_img = cv.flip(trans_img, 1)
    return new_img
# 逆时针旋转 90 度
def rotateAntiClockWise90(img):
    trans_img = cv.transpose(img)
    new_img = cv.flip(trans_img, 0)
    return new_img
    
if __name__ == "__main__":
    src1 = cv.imread(r"F:\opencvTest\stitch_left.png")
    src2 = cv.imread(r"F:\opencvTest\stitch_right.png")
    res = merge2Image(src1, src2)
    cv.imshow("res", res)
    cv.imwrite("src_merge.png", res)
    cv.waitKey()
    cv.destroyAllWindows()

# 结果展示