# 前言
昨天总结了基于特征的图像对齐,今天来尝试一下,基于特征对齐的二景图像拼接吧。如下图所示,这是同一地点不同角度拍摄的两幅图像,现在尝试将其进行拼接。
# 实验环境
- OpenCV:4.1.2
- Python:3.6.5
- 平台:Windows 10
# 实现思路
- 基于 ORB 特征检测算法检测两幅图像的特征关键点
- 对特征点进行匹配
- 从所匹配的全部关键点中筛选出优秀的特征点(基于距离筛选)
- 计算单应性变换矩阵
- 对右图进行映射变换
- 将左图拷贝到特定位置完成拼接
# 代码演示
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() |