图像查找
通过特征匹配和单应性矩阵我们可以实现图像查找.
基本的原理是通过特征匹配得到匹配结果, 作为输入, 得到单应性矩阵, 再经过透视变换就能够找到最终的图像
单应性矩阵
单应性(Homography)变换 :可以简单的理解为它用来描述物体在世界坐标系和像素坐标系之间的位置映射关系。对应的变换矩阵称为单应性矩阵。
-
单应性矩阵的应用
-
把图片摆正
-
图片替换
-
findHomography(srcPoints, dstPoints[, method[, ransacReprojThreshold[, mask[, maxIters[, confidence]]]]])
-
srcPoints: 源平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector
<Point2f>
类型 -
dstPoints: 目标平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector
<Point2f>
类型 -
method: 计算单应矩阵所使用的方法。不同的方法对应不同的参数,具体如下:
- 0 - 利用所有点的常规方法
- RANSAC - RANSAC-基于RANSAC的鲁棒算法
- LMEDS - 最小中值鲁棒算法
- PROSAC-基于PROSAC的鲁棒算法
-
ransacReprojThreshold: 将点对视为内点的最大允许重投影错误阈值(仅用于RANSAC和RHO方法)。若srcPoints和dstPoints是以像素为单位的,则该参数通常设置在1到10的范围内。
-
mask: 可选输出掩码矩阵,通常由鲁棒算法(RANSAC或LMEDS)设置。 请注意,输入掩码矩阵是不需要设置的。
-
maxIters: RANSAC算法的最大迭代次数,默认值为2000。
-
confidence: 可信度值,取值范围为0到1.
-
import cv2
import numpy as np
# 打开图片
img1 = cv2.imread('opencv_search.png')
img2 = cv2.imread('opencv_orig.png')
# 灰度化
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
#创建特征检测器
sift = cv2.SIFT_create()
# 计算特征点和描述子
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)
# 创建特征匹配器
index_params = dict(algorithm=1, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
# 对描述子进行特征匹配
matches = flann.knnMatch(des1, des2, k=2)
# 过滤match对象
goods = []
for (m, n) in matches:
# 阈值一般设0.7到0.8之间.
if m.distance < 0.75 * n.distance:
goods.append(m)
# 通过goods把对应的特征点找到
# 因为计算单应性矩阵要求最少4个点
if len(goods) >= 4:
# 找到对应的四个点
src_points = np.float32([kp1[m.queryIdx].pt for m in goods]).reshape(-1, 1, 2)
dst_points = np.float32([kp2[m.trainIdx].pt for m in goods]).reshape(-1, 1, 2)
# 根据匹配上的关键点去计算单应性矩阵.
H, _ = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5)
# 通过单应性矩阵, 计算小图(img1)小图在大图中的对应位置.
h, w = img1.shape[:2] # 小图的高和宽
# 小图中的四个角点
pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]).reshape(-1, 1, 2)
# warpPerspective是对图片进行透视变换的.
# 将小图中的四个角点,投射到大图中,找到大图中对应的四个点
dst = cv2.perspectiveTransform(pts, H)
print(dst)
# 在大图中, 把dst区域框出来
cv2.polylines(img2, [np.int32(dst)], True, (0, 0, 255), 2)
else:
print('not enough point number to compute homography matrix')
exit()
# 画出匹配的特征点
ret = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [goods], None)
cv2.imshow('ret', ret)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)
[[[ 97.57899 86.01002]] [[ 92.22688 435.28094]] [[471.19507 435.65234]] [[465.97748 85.17265]]] -1
图像拼接
方法一
变换右图,使之适应左图
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 打开两个文件
img1 = cv2.imread('./map1.png')
img2 = cv2.imread('./map2.png')
# 灰度化
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 创建SIFT特征检测器
sift = cv2.SIFT_create()
# 计算描述子与特征点
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)
# 创建匹配器
index_params = dict(algorithm=1, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
# 对描述子进行匹配计算
matchs = flann.knnMatch(des1, des2, k=2)
# 过滤match对象,选出合适的匹配点
good = []
for i, (m, n) in enumerate(matchs):
if m.distance < 0.75 * n.distance:
good.append(m)
if len(good) >= 10:
# 找出两图对应的匹配点
srcPts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
dstPts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
# 查找单应性矩阵
M, mask = cv2.findHomography(srcPts, dstPts, cv2.RANSAC, 5.0)
# 利用M矩阵的逆 -- 将img2图变换,使之与img1匹配
# 返回变换之后的图像,宽高得自己设定
warpImg = cv2.warpPerspective(img2, np.linalg.inv(M), (img1.shape[1]+img2.shape[1], img2.shape[0]+6)) # 后面广播的时候高度会缺失6个像素
warpImg[0:img1.shape[0], 0:img1.shape[1]] = img1 # 将左边img1的部分重新赋值
# 处理中间黑线问题.
# 经过仔细观察, 中间的黑线是左图自带的. 黑线在第743列的位置, 我们把这一列删掉
warpImg = np.hstack((warpImg[:, :742], warpImg[:, 744:]))
# 然后再对局部做一个高斯模糊.
temp = cv2.GaussianBlur(warpImg[:, 740:747], (5, 5), sigmaX=0)
# 替换
warpImg[:, 740:747] = temp
cv2.imshow('result', warpImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)
-1
方法二
变换左图,使之适应右图
图像拼接的思路.
- 读图片
- 灰度化处理
- 计算各自的特征点和描述子
- 匹配特征.
- 根据匹配到的特征, 计算单应性矩阵.
- 对图片进行透视变换.
- 创建一个大图.
- 放入两张图.
import cv2
import numpy as np
# 读图片
img1 = cv2.imread('left_01.png')
img2 = cv2.imread('right_01.png')
# 把两张图的尺寸设置成同样大小
img1 = cv2.resize(img1, (640, 480))
img2 = cv2.resize(img2, (640, 480))
# 灰度化处理
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 创建sift对象
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# 创建特征匹配器
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
# 过滤match对象,使之更准确地匹配
goods = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
goods.append(m)
if len(goods) >= 4:
# 根据DMatch对象拿到各自的特征点
src_points = np.float32([kp1[m.queryIdx].pt for m in goods]).reshape(-1, 1, 2)
dst_points = np.float32([kp2[m.trainIdx].pt for m in goods]).reshape(-1, 1, 2)
# 计算单应性矩阵
# 第一个对变成第二个图的视角, 计算出来的单应性矩阵.
H, _ = cv2.findHomography(src_points, dst_points, cv2.RANSAC, 5)
else:
print('not enough point number to compute homography matrix')
exit()
# 获取原始图的高和宽
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
# 获取两个图的角点
img1_pts = np.float32([[0, 0], [0, h1 - 1], [w1 - 1, h1 - 1], [w1 -1, 0]]).reshape(-1, 1, 2)
img2_pts = np.float32([[0, 0], [0, h2 - 1], [w2 - 1, h2 - 1], [w2 -1, 0]]).reshape(-1, 1, 2)
# 根据前面计算出来的H, 计算img1的四个角变换之后的坐标
img1_transform = cv2.perspectiveTransform(img1_pts, H)
# 计算合成图的大小
## 先获取角点坐标的最大值和最小值
result_pts = np.concatenate((img2_pts, img1_transform), axis=0)
[x_min, y_min] = np.int32(result_pts.min(axis=0).ravel() - 1)
[x_max, y_max] = np.int32(result_pts.max(axis=0).ravel() + 1)
# 手动构造平移矩阵:使img1的变换图像 移到显示区域
# 如果不平移, img1很大一部分都在显示窗口外面, 我们看不到.
move_matrix = np.array([[1, 0, -x_min],[0, 1, -y_min], [0, 0, 1]])
# 对img1进行平移后透视变换,并设置结果画面的大小
result_img = cv2.warpPerspective(img1, move_matrix.dot(H), (x_max -x_min, y_max - y_min))
# 把img2放进来
result_img[-y_min: -y_min + h2,-x_min: -x_min + w2] = img2
cv2.imshow('result_img', result_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)
[[[ 0. 0. ]] [[ 0. 479. ]] [[ 639. 479. ]] [[ 639. 0. ]] [[-910.4307 -314.8995 ]] [[-885.33563 692.0095 ]] [[ 269.34866 428.4168 ]] [[ 289.10074 20.58108]]] -1