文章目录
- 完整代码
- 导入库与辅助函数
- [特征提取函数 detectAndDescribe](#特征提取函数 detectAndDescribe)
- 提取两张图的特征
- [特征匹配 ------ 暴力匹配器与比值筛选](#特征匹配 —— 暴力匹配器与比值筛选)
- 透视变换矩阵
- 透视变换与图像拼接
完整代码
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 图本身已经在正确位置,所以直接覆盖即可。
