一、项目所涉及到的一些知识点
Ⅰ,BF(Brute-Force)暴力匹配:把两张图像的特征点全部给算出来,然后使用归一化的欧氏距离比较这两张图像上特征点之间的大小关系,越小越相似。
SIFT算法
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)cv2.destroyAllWindows()
image1 = cv2.imread("book1.jpg")
image2 = cv2.imread("book2.jpg")
cv_show('image1',image1)
cv_show('image2',image2)
sift = cv2.xfeatures2d.SIFT_create()#SIFT构造特征
kp1,des1 = sift.detectAndCompute(image1,None)#计算图像的关键点并且去计算特征向量
kp2,des2 = sift.detectAndCompute(image2,None)#得到kp1图像的特征点、des特征点所对应的特征向量
bf = cv2.BFMatcher(crossCheck=True)
1对1匹配
matches = bf.match(des1,des2)
matches = sorted(matches,key=lambda x : x.distance)#因为获取到的特征太多了,这里根据相似程度来进行排序
image3 = cv2.drawMatches(image1,kp1,image2,kp2,matches[:10],None,flags=2)#将图像的关键点相近的进行连接在一块,把相似的关键点前十给绘制出来
cv_show('image3',image3)
1对K匹配
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1,des2,k=2)#一个特征点找打与它2个相近的点
good = []
for m,n in matches:#将关键点进行过滤操作if m.distance < 0.75*n.distance:good.append([m])
image3 = cv2.drawMatchesKnn(image1,kp1,image2,kp2,good,None,flags=2)
cv_show('image3',image3)
Ⅱ,RANSAC(Random sample consensus)随机抽样一致算法
最小二乘法
回归算法中有最小二乘法
,也就是有一些数据点,需要进行拟合这些数据点,拟合的函数要尽可能多的去满足所有的数据点。
由于最小二乘法为了满足所有的数据点,此时拟合的函数并不是特别理想的,因为有一些噪音点进行了误导。
RANSAC
RANSAC
效果就显然好多了,因为它不受一些噪音点的误导。
RANSAC算法思路:选择初始样本点进行拟合,给定一个容忍范围,不断的进行更新迭代。
例如:初始样本点的格式为2
设置容忍范围,也就是这个线周围的虚线,这里找到有9个点属于局内点,是自己人,在容忍范围之内
接下来接着迭代,找到8个局内点,在容忍范围内,以此类推一直迭代,直到最后找出在容忍范围内拟合点最多的那一条曲线,就是最后求解的拟合曲线。
单应性矩阵
图像可以通过单应性矩阵进行空间维度的变换,一般为3*3的矩阵,为了归一化方便,最后一个值常设置为1。即,单应性矩阵需要求解8个未知数,,需要8个方程,最少需要4对特征点。
为了构建单应性矩阵最少需要4对特征点,但是选取哪几对特征点好呢?图片进行暴力匹配之后会有很多的特征点,但是并不是所有的特征点都是正确的,故需要通过RANSAC算法进行筛选出效果最好的4对特征点来进行构建单应性矩阵。
二、完整代码
from Stitcher import Stitcher
import numpy as np
import cv2class Stitcher:# 拼接函数def stitch(self, images, ratio=0.75, reprojThresh=4.0, showMatches=False):# 获取输入图片(imageB, imageA) = images# 检测A、B图片的SIFT关键特征点,并计算特征描述子(kpsA, featuresA) = self.detectAndDescribe(imageA)(kpsB, featuresB) = self.detectAndDescribe(imageB)# 匹配两张图片的所有特征点,返回匹配结果M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)# 如果返回结果为空,没有匹配成功的特征点,退出算法if M is None:return None# 否则,提取匹配结果# H是3x3视角变换矩阵(matches, H, status) = M# 将图片A进行视角变换,result是变换后图片result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))self.cv_show('result', result)# 将图片B传入result图片最左端result[0:imageB.shape[0], 0:imageB.shape[1]] = imageBself.cv_show('result', result)# 检测是否需要显示图片匹配if showMatches:# 生成匹配图片vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status)# 返回结果return (result, vis)# 返回匹配结果return resultdef cv_show(self, name, img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()def detectAndDescribe(self, image):# 将彩色图片转换成灰度图gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 建立SIFT生成器descriptor = cv2.xfeatures2d.SIFT_create()# 检测SIFT特征点,并计算描述子(kps, features) = descriptor.detectAndCompute(image, None)# 将结果转换成NumPy数组kps = np.float32([kp.pt for kp in kps])# 返回特征点集,及对应的描述特征return (kps, features)def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh):# 建立暴力匹配器matcher = cv2.BFMatcher()# 使用KNN检测来自A、B图的SIFT特征匹配对,K=2rawMatches = matcher.knnMatch(featuresA, featuresB, 2)matches = []for m in rawMatches:# 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对if len(m) == 2 and m[0].distance < m[1].distance * ratio:# 存储两个点在featuresA, featuresB中的索引值matches.append((m[0].trainIdx, m[0].queryIdx))# 当筛选后的匹配对大于4时,计算视角变换矩阵if len(matches) > 4:# 获取匹配对的点坐标ptsA = np.float32([kpsA[i] for (_, i) in matches])ptsB = np.float32([kpsB[i] for (i, _) in matches])# 计算视角变换矩阵(H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh)# 返回结果return (matches, H, status)# 如果匹配对小于4时,返回Nonereturn Nonedef drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):# 初始化可视化图片,将A、B图左右连接到一起(hA, wA) = imageA.shape[:2](hB, wB) = imageB.shape[:2]vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")vis[0:hA, 0:wA] = imageAvis[0:hB, wA:] = imageB# 联合遍历,画出匹配对for ((trainIdx, queryIdx), s) in zip(matches, status):# 当点对匹配成功时,画到可视化图上if s == 1:# 画出匹配对ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))cv2.line(vis, ptA, ptB, (0, 255, 0), 1)# 返回可视化结果return vis# 读取拼接图片
imageA = cv2.imread("left.jpg")
imageB = cv2.imread("right.jpg")# 把图片拼接成全景图
stitcher = Stitcher()
(result, vis) = stitcher.stitch([imageA, imageB], showMatches=True)# 显示所有图片
cv2.imshow("Image A", imageA)
cv2.imshow("Image B", imageB)
cv2.imshow("Keypoint Matches", vis)
cv2.imshow("Result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()