超詳講解影像拼接/全景圖原理和應用 | 附源碼
- 2019 年 10 月 4 日
- 筆記
研究好玩又有用的技術第 008 期
在學習中發現快樂,在應用找到價值。這是我第八期分享影像技術應用的文章。 前七期歡迎閱讀和分享:
- 第一期《掃描全能王?原來影像技術可以這樣子玩》
- 第二期《來吧,見識科技的力量,無需手動找拍糊的圖》
- 第三期《這種方式打開會ctrl的流量明星cxk,簡直就是魔鬼》
- 第四期《酷炫騷操作,票圈裝13神技,極坐標全景圖》
- 第五期《用python和opencv檢測影像中的條形碼》
- 第六期《OpenCV測量物體的尺寸技能 get~》
- 第七期《還在用肉眼找不同嗎?這個技術輕鬆搞定》
概述
作者:Thalles Silva 編譯:AI演算法與影像處理 影像拼接是電腦視覺中最成功的應用之一。如今,很難找到不包含此功能的手機或影像處理API。 在這篇文章中,我們將討論如何使用Python和OpenCV執行影像拼接。鑒於一對共享一些共同區域的影像,我們的目標是「縫合」它們並創建全景影像場景。 在整篇文章中,我們將介紹一些最著名的電腦視覺技術。這些包括:
- 關鍵點檢測
- 局部不變描述符(SIFT,SURF等)
- 特徵匹配
- 使用RANSAC進行的旋轉估計(Homography estimation)
- 透視畸變(Perspective warping)
我們探索了許多特徵提取運算元,如SIFT,SURF,BRISK和ORB。你可以使用這款Colab筆記型電腦,甚至可以用你的照片試試。[這裡我已經調試好源碼並上傳到github上面]

特徵檢測和提取
給定一對像上面那樣的影像,我們想要把它們拼接起來創建一個全景場景。值得注意的是,兩個影像都需要共享一些共同的區域。
此外,即使圖片在以下一個或多個方面存在差異,我們的解決方案也必須強大:
- Scaling
- Angle
- Spacial position
- Capturing devices
朝這個方向邁出的第一步是提取一些感興趣的關鍵點和特徵。但是,這些功能需要具有一些特殊屬性。
我們首先考慮一個簡單的解決方案。
關鍵點檢測
一開始可能使用簡單些的方法,諸如使用Harris Corners之類的演算法提取關鍵點。然後,我們可以嘗試根據歐幾里德距離之類的相似度量來匹配相應的關鍵點。我們知道,角點(corner)有一個很好的屬性:它們對旋轉是不變的。 這意味著,一旦我們檢測到一個角點,如果我們旋轉影像,那個角點仍將存在。
但是,如果我們旋轉然後縮放影像怎麼辦?在這種情況下,我們會很難,因為角點不是規模不變的。也就是說,如果我們放大影像,先前檢測到的角可能會變成一條線!
總之,我們需要對旋轉和縮放不變的特徵。這就是SIFT,SURF和ORB等更強大的方法的用武之地。
關鍵點和描述子
像SIFT和SURF這樣的方法試圖解決角點檢測演算法的局限性。 通常,角點檢測器演算法使用固定大小的內核來檢測影像上的感興趣區域(角點)。很容易看出,當我們縮放影像時,這個內核可能會變得太小或太大。
為了解決這個限制,像SIFT這樣的方法使用高斯差分(DoD)。我們的想法是在同一影像的不同比例版本上應用DoD。它還使用相鄰像素資訊來查找和細化關鍵點和相應的描述子。
首先,我們需要載入2個影像,查詢圖片和訓練圖片。最初,我們首先從兩者中提取關鍵點和描述符。我們可以通過使用OpenCV detectAndCompute()函數一步完成。請注意,為了使用detectAndCompute(),我們需要一個關鍵點檢測器和描述符對象的實例。它可以是ORB,SIFT或SURF等。另外,在將影像饋送到detectAndCompute()之前,我們將它們轉換為灰度。
def detectAndDescribe(image, method=None): """ Compute key points and feature descriptors using an specific method """ assert method is not None, "You need to define a feature detection method. Values are: 'sift', 'surf'" # detect and extract features from the image if method == 'sift': descriptor = cv2.xfeatures2d.SIFT_create() elif method == 'surf': descriptor = cv2.xfeatures2d.SURF_create() elif method == 'brisk': descriptor = cv2.BRISK_create() elif method == 'orb': descriptor = cv2.ORB_create() # get keypoints and descriptors (kps, features) = descriptor.detectAndCompute(image, None) return (kps, features)
我們對查詢和訓練的圖片都運行detectAndCompute()。此時,我們為這兩個影像提供了一組關鍵點和描述子。如果我們使用SIFT作為特徵提取器,它將為每個關鍵點返回128維特徵向量。如果選擇SURF,我們將獲得64維特徵向量。以下影像顯示了使用SIFT,SURF,BRISK和ORB提取的一些功能。

使用SIFT檢測關鍵點和描述子

使用SURF檢測關鍵點和描述子

使用BRISK和漢明距離檢測關鍵點和描述子

使用ORB和漢明距離檢測關鍵點和描述子
特徵匹配
我們可以看到,我們從兩個影像中都有大量的特徵。
現在,我們想比較兩組特徵並以線段相連的形式顯示更多相似性的特徵點對。
使用OpenCV,功能匹配需要Matcher對象。在這裡,我們探索兩種方法:
- Brute Force Matcher(暴力匹配法)
- KNN(k-最近鄰)
BruteForce(BF)Matcher正如其名稱所表明的那樣。給定2組特徵(來自圖片A和圖片B),來自集合A的每個特徵與集合B中的所有特徵進行比較。默認情況下,BF匹配器計算兩點之間的歐幾里德距離。因此,對於集合A中的每個特徵,它返回集合B中最接近的特徵。對於SIFT和SURF,OpenCV建議使用歐幾里德距離。對於其他特徵提取器,如ORB和BRISK,建議使用漢明距離。
要使用OpenCV創建BruteForce Matcher,我們只需要指定2個參數。第一個是距離度量。第二個是crossCheck布爾參數。
def createMatcher(method,crossCheck): "Create and return a Matcher Object" if method == 'sift' or method == 'surf': bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=crossCheck) elif method == 'orb' or method == 'brisk': bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=crossCheck) return bf
crossCheck bool參數指示兩個特徵是否必須相互匹配才能被視為有效。換句話說,對於被認為有效的一對特徵(f1,f2),f1需要匹配f2,並且f2也必須匹配f1作為最接近的匹配。此過程可確保更強大的匹配功能集,並在原始SIFT文章中進行了描述。
但是,對於我們想要考慮多個候選匹配的情況,我們可以使用基於KNN的匹配過程。
KNN不返回給定特徵的單個最佳匹配,而是返回k個最佳匹配
注意,k的值必須是由用戶預先定義的。如我們預期的那樣,KNN提供了一個更的的候選特徵集合。但是,我們需要確保所有這些匹配對在進一步發展之前都是健壯的。
比率測試(Ratio Testing)
為了確保KNN返回的特徵具有良好的可比性,SIFT論文的作者提出了一種稱為比率測試(Ratio Testing)的技術。基本上,我們迭代KNN返回的每個對並執行距離測試。對於每對特徵(f1,f2),如果f1和f2之間的距離在一定比例內,我們保留它,否則,我們將它丟棄。此外,必須手動選擇比率值。
從本質上講,比率測試與BruteForce Matcher的交叉檢查選項完成相同的工作。兩者都確保一對檢測到的特徵確實足夠近以至於被認為是相似的。下面的2個數字顯示了BF和KNN Matcher對SIFT特徵的結果。我們選擇僅顯示100個匹配點以清除可視化。

使用KNN和RIFT測試對SIFT特徵進行特徵匹配

在SIFT功能上使用Brute Force Matcher進行特徵匹配
請注意,即使在KNN中交叉驗證——暴力匹配(Brute force)和比率測試之後,某些功能也無法正確匹配。
然而,Matcher演算法將為我們提供兩個影像中最好的(更相似的)特徵集。現在,我們需要獲取這些點並找到基於匹配點將2個影像拼接在一起的變換矩陣。
這種轉換稱為Homography matrix(單應性矩陣)。簡而言之,如果Homography是3×3矩陣,可用於許多應用,例如相機姿態估計,透視校正和影像拼接。如果Homography是2D變換。它將點從一個平面(影像)映射到另一個平面。讓我們看看我們是如何得到它的。
Estimating the Homograph
RANdom SAmple Consensus或RANSAC是一種適合線性模型的迭代演算法。與其他線性回歸器不同,RANSAC設計為對異常值具有魯棒性。
像線性回歸這樣的模型使用最小二乘估計來使最佳模型適合數據。然而,普通最小二乘法對異常值非常敏感。因此,如果異常值的數量很大,它可能會失敗。
RANSAC通過僅使用數據中的內部子集估計參數來解決此問題。下圖顯示了線性回歸和RANSAC之間的比較。首先,請注意數據集包含相當多的異常值。
我們可以看到線性回歸模型很容易受到異常值的影響。那是因為它試圖減少平均誤差。因此,它傾向於支援最小化從所有數據點到模型本身的總距離的模型。這包括異常值。
相反,RANSAC僅將模型擬合到被識別為內點的點子集上。
這個特性對我們的用例非常重要。在這裡,我們將使用RANSAC來估計Homography矩陣。事實證明,Homography對我們傳遞給它的數據品質非常敏感。因此,重要的是有一個演算法(RANSAC)可以過濾明顯不屬於數據分布的點。

最小二乘與RANSAC模型擬合的比較。請注意數據中的大量異常值
一旦我們得到 estimated Homography,我們需要將其中一個影像變換到一個共同的平面。
在這裡,我們將對其中一個影像應用透視變換。基本上,透視變換可以組合一個或多個操作,例如旋轉,縮放,平移或剪切。這個想法是轉換其中一個影像,使兩個影像合併為一個。為此,我們可以使用OpenCV warpPerspective()函數。它採用影像和homography作為輸入。然後,它根據homography將源影像變換到目的平面上。
# Apply panorama correction width = trainImg.shape[1] + queryImg.shape[1] height = trainImg.shape[0] + queryImg.shape[0] result = cv2.warpPerspective(trainImg, H, (width, height)) result[0:queryImg.shape[0], 0:queryImg.shape[1]] = queryImg plt.figure(figsize=(20,10)) plt.imshow(result) plt.axis('off') plt.show()
生成的全景影像如下所示。如我們所見,結果中有幾個工件。更具體地說,我們可以看到與影像邊界處的照明條件和邊緣效應有關的一些問題。理想情況下,我們可以執行後處理技術來標準化直方圖匹配等強度。這可能會使結果看起來更加真實。
謝謝閱讀!
效果圖

demo 2


可能遇到bug
可能存在的報錯
(1)Qt 鏈接報錯 version `Qt_5' not found]
python3: relocation error: /usr/lib/x86_64-linux-gnu/libQt5XcbQpa.so.5: symbol _ZN20QPlatformIntegration11screenAddedEP15QPlatformScreenb version Qt_5_PRIVATE_API not defined in file libQt5Gui.so.5 with link time reference
解決方案:https://www.cnblogs.com/sunchaothu/p/9962675.html
(2)AttributeError: module 'cv2.cv2' has no attribute 'xfeatures2d'
需要回退版本到3.4.2.16,記得要先卸載之前安裝好的opencv-python
解決方案:https://blog.csdn.net/weixin_43167047/article/details/82841750
程式碼:https://github.com/DWCTOD/AI_study
原文鏈接:
https://towardsdatascience.com/image-panorama-stitching-with-opencv-2402bde6b46c
