一、項目所涉及到的一些知識點
Ⅰ,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()