OpenCV 图像拼接实战:SIFT 特征匹配 + 透视变换实现全景图

文章目录


完整代码

A图:

B图:

c 复制代码
import cv2
import numpy as np
import sys

def cv_show(name, image):
    cv2.imshow(name, image)
    cv2.waitKey(0)

def detectAndDescribe(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    sift = cv2.SIFT_create()
    (kps, des) = sift.detectAndCompute(gray, None)
    kps_float = np.float32([kp.pt for kp in kps])
    return (kps, kps_float, des)

imageA = cv2.imread('A.jpg')
cv_show('imageA', imageA)
imageB = cv2.imread('B.jpg')
cv_show('imageB', imageB)

(kpsA, kpsA_float, desA) = detectAndDescribe(imageA)
(kpsB, kpsB_float, desB) = detectAndDescribe(imageB)

matcher = cv2.BFMatcher()
rawMatches = matcher.knnMatch(desB, desA, 2)
good = []
matches = []
for m in rawMatches:
    if len(m) == 2 and m[0].distance < 0.65 * m[1].distance:
        good.append(m)
        matches.append((m[0].queryIdx, m[0].trainIdx))
print(len(good))
print(matches)

vis = cv2.drawMatchesKnn(imageB, kpsB, imageA, kpsA, good, None,
                         flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv_show('keypoint Matcher', vis)

if len(matches) > 4:
    ptsB = np.float32([kpsB_float[i] for (i, _) in matches])
    ptsA = np.float32([kpsA_float[i] for (_, i) in matches])
    (H, mask) = cv2.findHomography(ptsB, ptsA, cv2.RANSAC, 10)
else:
    print('图片未找到4个匹配点')
    sys.exit()

result = cv2.warpPerspective(imageB, H,
                             (imageB.shape[1] + imageA.shape[1], imageB.shape[0]))
cv_show('resultB', result)
result[0:imageA.shape[0], 0:imageA.shape[1]] = imageA
cv_show('result', result)
cv2.imwrite('ping_jie.jpg', result)

导入库与辅助函数

c 复制代码
import cv2
import numpy as np
import sys

def cv_show(name, image):
    cv2.imshow(name, image)
    cv2.waitKey(0)

自定义的显示函数,显示图像并等待按键,方便观察每一步结果。

特征提取函数 detectAndDescribe

c 复制代码
def detectAndDescribe(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    sift = cv2.SIFT_create()
    (kps, des) = sift.detectAndCompute(gray, None)
    kps_float = np.float32([kp.pt for kp in kps])
    return (kps, kps_float, des)

首先将彩色图转为灰度图,因为特征检测不需要颜色信息,灰度图计算更快;再创建 SIFT 检测器,找出图像中稳定的关键点(比如角点)。

保存三个值:

kps:原始关键点对象(用于绘图)

kps_float:是关键点的坐标 (x, y) 提取出来转为 float 类型的数组

des:描述子矩阵

提取两张图的特征

c 复制代码
imageA = cv2.imread('A.jpg')
cv_show('imageA', imageA)
imageB = cv2.imread('B.jpg')
cv_show('imageB', imageB)
(kpsA, kpsA_float, desA) = detectAndDescribe(imageA)
(kpsB, kpsB_float, desB) = detectAndDescribe(imageB)

读取两张图片 A.jpg 和 B.jpg,它们有重叠区域。

分别调用 detectAndDescribe 得到两幅图的关键点、坐标数组和描述子。

kps:

数据类型是keypoint,存放的是关键点坐标。

kps_float:

将坐标点信息转换成float形式的为数组。

特征匹配 ------ 暴力匹配器与比值筛选

c 复制代码
matcher = cv2.BFMatcher()
rawMatches = matcher.knnMatch(desB, desA, 2)
good = []
matches = []
for m in rawMatches:
    if len(m) == 2 and m[0].distance < 0.65 * m[1].distance:
        good.append(m)
        matches.append((m[0].queryIdx, m[0].trainIdx))
print(len(good))
print(matches)
vis = cv2.drawMatchesKnn(imageB,kpsB,imageA,kpsA,good,None,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv_show('keypoint Matcher',vis)

创建暴力匹配器,它会计算 B 图中每个描述子与 A 图中所有描述子的距离,然后找出最近的两个。

让m循环遍历两个点之间距离的信息,如果如果最近距离 m0.distance 小于 0.65 倍的第二近距离 m1.distance,就认为这个点的信息是有效的。

queryIdx:是 B 图中特征点的索引

trainIdx: 是 A 图中特征点的索引

打印匹配数量和A,B图的对应索引号

输出:

一共有31个有效匹配

c 复制代码
31
[(14, 76), (36, 105), (39, 105), (63, 118), (65, 121), (66, 122), (74, 130), (83, 128), (87, 136), (93, 140), (105, 147), (118, 172), (138, 176), (154, 191), (155, 192), (158, 198), (164, 213), (165, 206), (176, 217), (185, 227), (201, 242), (202, 243), (204, 246), (207, 250), (209, 255), (212, 257), (217, 7), (228, 275), (229, 276), (231, 275), (233, 276)]

可以看到有很多连线,如果匹配大多平行且一致,说明找得不错。

如果效果不好,就得修改阈值。

透视变换矩阵

c 复制代码
if len(matches) > 4:
    ptsB = np.float32([kpsB_float[i] for (i, _) in matches])
    ptsA = np.float32([kpsA_float[i] for (_, i) in matches])
    (H, mask) = cv2.findHomography(ptsB, ptsA, cv2.RANSAC, 10)
else:
    print('图片未找到4个匹配点')
    sys.exit()

从 matches 中提取 B 图中的点坐标(ptsB)和 A 图中对应的点坐标(ptsA)。

cv2.findHomography:利用匹配点对求解从 B 图到 A 图的透视变换矩阵 H。使用 RANSAC 算法(随机抽样一致性)来剔除错误的匹配(外点),提高鲁棒性。10 是重投影误差阈值,表示如果某对点变换后的误差大于 10 像素,则视为外点。

如果匹配点不足 4 个,程序输出提示并退出。

透视变换与图像拼接

c 复制代码
result = cv2.warpPerspective(imageB, H,
                             (imageB.shape[1] + imageA.shape[1], imageB.shape[0]))
cv_show('resultB', result)
result[0:imageA.shape[0], 0:imageA.shape[1]] = imageA
cv_show('result', result)
cv2.imwrite('ping_jie.jpg', result)

图像 imageB 按照透视变换矩阵 H 进行透视变换,输出到目标画布;画布的尺寸设置为 宽度 = B 的宽度 + A 的宽度,高度 = B 的高度。

变换后,B 图被"拉"到 A 图的坐标系中,但由于画布大小,B 会出现在右侧;然后将 A 图直接复制到画布的左上角(result0:hA, 0:wA = imageA)。因为 A 图本身已经在正确位置,所以直接覆盖即可。