社群計算機視覺課程文件
特徵匹配
並獲得增強的文件體驗
開始使用
特徵匹配
我們如何將一幅影像中檢測到的特徵與另一幅影像進行匹配?特徵匹配涉及比較不同影像中的關鍵屬性以發現相似之處。特徵匹配在許多計算機視覺應用中都非常有用,包括場景理解、影像拼接、目標跟蹤和模式識別。
暴力搜尋
想象一下你有一個巨大的拼圖盒,你正在嘗試找到一個適合你的拼圖的特定拼圖塊。這類似於在影像中搜索匹配的特徵。你決定逐個檢查每個拼圖塊,直到找到正確的,而不是使用任何特殊的策略。這種直接的方法就是暴力搜尋。暴力搜尋的優點是它的簡單性。你不需要任何特殊的技巧——只需要耐心。然而,它可能很耗時,特別是當有許多拼圖塊需要檢查時。在特徵匹配的背景下,這種暴力方法類似於將一幅影像中的每個畫素與另一幅影像中的每個畫素進行比較,以檢視它們是否匹配。它是窮舉的,可能需要大量時間,特別是對於大型影像。
既然我們對暴力匹配的查詢方式有了直觀的瞭解,接下來讓我們深入瞭解這些演算法。我們將使用前一章中學習到的描述符來查詢兩幅影像中的匹配特徵。
首先安裝並載入庫。
!pip install opencv-python
import cv2
import numpy as np使用 SIFT 進行暴力搜尋
讓我們從初始化 SIFT 檢測器開始。
sift = cv2.SIFT_create()
使用 SIFT 查詢關鍵點和描述符。
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)使用 k 近鄰查詢匹配項。
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)應用比率測試以篩選最佳匹配。
good = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good.append([m])繪製匹配項。
img3 = cv2.drawMatchesKnn(
img1, kp1, img2, kp2, good, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)
使用 ORB(二進位制)描述符進行暴力搜尋
初始化 ORB 描述符。
orb = cv2.ORB_create()
查詢關鍵點和描述符。
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)因為 ORB 是二進位制描述符,我們使用漢明距離來查詢匹配項,漢明距離是衡量兩個等長字串之間差異的指標。
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)我們現在將查詢匹配項。
matches = bf.match(des1, des2)我們可以按距離對它們進行排序,如下所示。
matches = sorted(matches, key=lambda x: x.distance)繪製前 n 個匹配項。
img3 = cv2.drawMatches(
img1,
kp1,
img2,
kp2,
matches[:n],
None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS,
)近似最近鄰快速庫(FLANN)
FLANN 由 Muja 和 Lowe 在《帶有自動演算法配置的快速近似最近鄰》中提出。為了解釋 FLANN,我們將繼續以解謎為例。想象一個巨大的拼圖,散落著數百個拼圖塊。你的目標是根據它們之間的契合度來組織這些拼圖塊。FLANN 不會隨機嘗試匹配拼圖塊,而是使用一些巧妙的技巧來快速找出哪些拼圖塊最有可能搭配在一起。FLANN 不會嘗試將每個拼圖塊與所有其他拼圖塊進行比較,而是透過查詢近似相似的拼圖塊來簡化過程。這意味著即使它們不是完全匹配,它也可以對哪些拼圖塊可能很好地契合在一起做出有根據的猜測。在底層,FLANN 使用一種稱為 k-D 樹的結構。把它想象成以特殊方式組織拼圖塊。FLANN 不會檢查每個拼圖塊與所有其他拼圖塊,而是將它們排列成樹狀結構,從而加快匹配查詢速度。在 k-D 樹的每個節點中,FLANN 將具有相似特徵的拼圖塊放在一起。這就像將具有相似形狀或顏色的拼圖塊分類成堆。這樣,當您尋找匹配項時,您可以快速檢查最有可能具有相似拼圖塊的堆。假設您正在尋找一個“天空”拼圖塊。FLANN 會將您引導到 k-D 樹中正確的位置,在那裡排序著天空顏色的拼圖塊,而不是搜尋所有拼圖塊。FLANN 還會根據拼圖塊的特徵調整其策略。如果您的拼圖有很多顏色,它將專注於顏色特徵。反之,如果它是一個形狀複雜的拼圖,它會關注這些形狀。透過平衡查詢匹配特徵時的速度和準確性,FLANN 大大地縮短了查詢時間。
首先,我們建立一個字典來指定我們將使用的演算法,對於 SIFT 或 SURF,它看起來像這樣。
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)對於 ORB,將使用論文中的引數。
FLANN_INDEX_LSH = 6
index_params = dict(
algorithm=FLANN_INDEX_LSH, table_number=12, key_size=20, multi_probe_level=2
)我們還建立了一個字典來指定要訪問的最大葉子數量,如下所示。
search_params = dict(checks=50)啟動 SIFT 檢測器。
sift = cv2.SIFT_create()
使用 SIFT 查詢關鍵點和描述符。
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)我們現在將定義 FLANN 引數。在這裡,`trees` 是您想要的桶的數量。
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)我們只會繪製好的匹配項,因此建立一個掩碼。
matchesMask = [[0, 0] for i in range(len(matches))]我們可以進行比率測試以確定好的匹配項。
for i, (m, n) in enumerate(matches):
if m.distance < 0.7 * n.distance:
matchesMask[i] = [1, 0]現在讓我們視覺化這些匹配項。
draw_params = dict(
matchColor=(0, 255, 0),
singlePointColor=(255, 0, 0),
matchesMask=matchesMask,
flags=cv2.DrawMatchesFlags_DEFAULT,
)
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, matches, None, **draw_params)
基於 Transformer 的區域性特徵匹配 (LoFTR)
LoFTR 由 Sun 等人在《LoFTR:基於 Transformer 的無檢測器區域性特徵匹配》中提出。LoFTR 不使用特徵檢測器,而是使用基於學習的方法進行特徵匹配。
讓我們簡化一下,再次使用我們的拼圖示例。LoFTR 不僅僅是逐畫素比較影像,它還會尋找每張影像中的特定關鍵點或特徵。這就像識別每個拼圖塊的角和邊緣。就像一個非常擅長拼圖的人可能會專注於獨特的標記一樣,LoFTR 會識別一張影像中這些獨特的點。這些可以是突出的關鍵地標或結構。正如我們已經瞭解到的,匹配演算法處理旋轉或比例變化非常重要。如果特徵被旋轉或調整大小,LoFTR 仍然會識別它。這就像解決拼圖,其中拼圖塊可能會被翻轉或調整。當 LoFTR 匹配特徵時,它會分配一個相似度分數,以指示特徵的對齊程度。分數越高意味著匹配越好。這就像給一個拼圖塊與另一個拼圖塊的契合度打分一樣。LoFTR 還對某些變換具有不變性,這意味著它可以處理光照、角度或透視的變化。這在處理可能在不同條件下拍攝的影像時至關重要。LoFTR 強大的特徵匹配能力使其對影像拼接等任務非常有價值,您可以透過識別和連線共同特徵來無縫地組合多張影像。
我們可以使用 Kornia 使用 LoFTR 在兩幅影像中查詢匹配特徵。
!pip install kornia kornia-rs kornia_moons opencv-python --upgrade
匯入必要的庫。
import cv2
import kornia as K
import kornia.feature as KF
import matplotlib.pyplot as plt
import numpy as np
import torch
from kornia_moons.viz import draw_LAF_matches載入並調整影像大小。
from kornia.feature import LoFTR
img1 = K.io.load_image(image1.jpg, K.io.ImageLoadType.RGB32)[None, ...]
img2 = K.io.load_image(image2.jpg, K.io.ImageLoadType.RGB32)[None, ...]
img1 = K.geometry.resize(img1, (512, 512), antialias=True)
img2 = K.geometry.resize(img2, (512, 512), antialias=True)指明影像是“室內”還是“室外”影像。
matcher = LoFTR(pretrained="outdoor")LoFTR 只適用於灰度影像,因此將影像轉換為灰度。
input_dict = {
"image0": K.color.rgb_to_grayscale(img1),
"image1": K.color.rgb_to_grayscale(img2),
}讓我們執行推理。
with torch.inference_mode():
correspondences = matcher(input_dict)使用隨機抽樣一致性 (RANSAC) 清理對應關係。這有助於處理資料中的噪聲或異常值。
mkpts0 = correspondences["keypoints0"].cpu().numpy()
mkpts1 = correspondences["keypoints1"].cpu().numpy()
Fm, inliers = cv2.findFundamentalMat(mkpts0, mkpts1, cv2.USAC_MAGSAC, 0.5, 0.999, 100000)
inliers = inliers > 0最後,我們可以視覺化匹配項。
draw_LAF_matches(
KF.laf_from_center_scale_ori(
torch.from_numpy(mkpts0).view(1, -1, 2),
torch.ones(mkpts0.shape[0]).view(1, -1, 1, 1),
torch.ones(mkpts0.shape[0]).view(1, -1, 1),
),
KF.laf_from_center_scale_ori(
torch.from_numpy(mkpts1).view(1, -1, 2),
torch.ones(mkpts1.shape[0]).view(1, -1, 1, 1),
torch.ones(mkpts1.shape[0]).view(1, -1, 1),
),
torch.arange(mkpts0.shape[0]).view(-1, 1).repeat(1, 2),
K.tensor_to_image(img1),
K.tensor_to_image(img2),
inliers,
draw_dict={
"inlier_color": (0.1, 1, 0.1, 0.5),
"tentative_color": None,
"feature_color": (0.2, 0.2, 1, 0.5),
"vertical": False,
},
)最佳匹配以綠色顯示,而不太確定的匹配以藍色顯示。

資源和進一步閱讀
- FLANN Github
- 使用 SIFT、SURF、BRIEF 和 ORB 進行影像匹配:扭曲影像的效能比較
- ORB (Oriented FAST and Rotated BRIEF) 教程
- Kornia 影像匹配教程
- LoFTR Github
- OpenCV Github
- OpenCV 特徵匹配教程
- OpenGlue:基於圖神經網路的開源影像匹配管線