為了計算兩張圖的相機的姿態,我們需要知道兩張圖內共同的資訊,這個資訊就是特徵點 (feature point)。特徵點指的是圖片中的一些有特別代表性的像素,可以在多張圖片中找到對應的位置。然而有代表性是一個很抽象的概念,因此我們需要一些實際的方法來找到這些特徵點。
在設計特徵點的演算法時,我們會希望這些特徵點有以下特性:
那麼我們如何找到這些角落、邊緣呢?常見的方式是計算圖片的梯度 (image gradient),也就是計算圖片中每個像素的亮度變化。這樣我們就可以找到一些亮度變化較大的地方,這些地方通常就是我們所謂的特徵點。
以下是一個計算圖片梯度的函數:
def compute_gradients(image):
image = image.astype(np.float32) / 255.0
image_dx = image[:, 1:] - image[:, :-1]
image_dy = image[1:, :] - image[:-1, :]
image_dx = np.pad(image_dx, ((0, 0), (0, 1)), mode="constant")
image_dy = np.pad(image_dy, ((0, 1), (0, 0)), mode="constant")
grad = np.sqrt(image_dx ** 2 + image_dy ** 2)
return grad
會得到以下結果
實作上會利用這些梯度來找到特徵點,例如在某個像素點上,如果這個像素點的梯度是局部最大或最小,那麼這個像素點就有可能是特徵點。
在電腦視覺中,有很多種特徵點的演算法,例如 SIFT、SURF、ORB 等等。這些演算法的原理各有不同,但基本上都是透過一些特殊的濾波器來找到圖片中的特徵點,SIFT 由於其穩定性被廣泛使用,而 ORB 則是運行更快速。
同時,我們希望這些特徵點能夠被描述,這樣我們才能在不同的圖片中找到相同的特徵點。這個描述子 (descriptor) 通常是一個向量,可以用來比較兩個特徵點的相似度。
以下是範例程式碼,使用 OpenCV 的 SIFT 特徵點演算法:
from pathlib import Path
import numpy as np
from PIL import Image
from scipy.spatial.transform import Rotation
import cv2
import matplotlib.pyplot as plt
from dataset import TUMRGBD # Our dataset class
def detcet_features(image, type="sift", nfeatures=1000):
if type == "sift":
sift = cv2.SIFT_create(nfeatures=nfeatures)
keypoints, descriptors = sift.detectAndCompute(image, None)
elif type == "orb":
orb = cv2.ORB_create(nfeatures=nfeatures)
keypoints, descriptors = orb.detectAndCompute(image, None)
else:
raise ValueError(f"Unknown feature type: {type}")
return keypoints, descriptors
def main():
dataset = TUMRGBD("data/rgbd_dataset_freiburg2_desk")
frames = []
# Get the first two valid frames
for i in range(0, len(dataset), 100):
x = dataset[i]
if x is None:
continue
frames.append(x)
if len(frames) == 2:
break
rgb1 = cv2.imread(frames[0]["rgb_path"])
rgb1 = cv2.cvtColor(rgb1, cv2.COLOR_BGR2RGB)
gray1 = cv2.cvtColor(rgb1, cv2.COLOR_BGR2GRAY)
rgb2 = cv2.imread(frames[1]["rgb_path"])
rgb2 = cv2.cvtColor(rgb2, cv2.COLOR_BGR2RGB)
gray2 = cv2.cvtColor(rgb2, cv2.COLOR_BGR2GRAY)
keypoints1, descriptors1 = detcet_features(gray1)
keypoints2, descriptors2 = detcet_features(gray2)
print(f"Detected {len(keypoints1)} keypoints in frame 1")
print(f"Detected {len(keypoints2)} keypoints in frame 2")
# Draw the keypoints using opencv
img1 = cv2.drawKeypoints(rgb1, keypoints1, None)
img2 = cv2.drawKeypoints(rgb2, keypoints2, None)
fig, ax = plt.subplots(1, 2, figsize=(10, 5), tight_layout=True)
ax[0].axis("off")
ax[0].imshow(img1)
ax[1].axis("off")
ax[1].imshow(img2)
plt.show()
if __name__ == "__main__":
main()
會得到以下結果:
在這個程式中,我們使用了 OpenCV 的 SIFT 特徵點演算法,並且找到了兩張圖片中的特徵點。在這個例子中,我們找到了 1000 個特徵點,並且用 OpenCV 的 drawKeypoints
函數來畫出這些特徵點。
keypoints1
內存的是多個 cv2.KeyPoint
的物件,也就是每個特徵點,我們可以用 keypoints1[0].pt
來取得第一個特徵點的座標。
而 descriptors1
和 descriptors2
是特徵點的描述子,是一個 (1000, 128)
的陣列,其中 1000 是特徵點的數量,128 是描述子的維度。SIFT 的描述子是 128 維的,ORB 的描述子是 32 維的。我們可以用這些描述子來比較兩個特徵點的相似度,這樣我們就可以找到兩張圖片中相同的特徵點,進而計算相機的姿態。