修改 OpenCV 一行代码,提升 14% 图像匹配效果
OpenCV 4.5.1 中最令人兴奋的特性之一是 BEBLID (Boosted Efficient Binary Local Image Descriptor),一个新的描述符能够提高图像匹配精度,同时减少执行时间!
这篇文章将向你展示这个魔法是如何实现的。
所有的源代码都在这个 GitHub 库中:
https://github.com/iago-suarez/beblid-opencv-demo/blob/main/demo.ipynb
在这个例子中,我们将匹配这两个视角不一样的图像:
首先,确保安装了正确的 OpenCV 版本是很重要的。在你喜欢的环境中,你可以通过以下方式安装并检查 OpenCV Contrib 版本:
pip install "opencv-contrib-python>=4.5.1"
python
>>> import cv2 as cv
>>> print(f"OpenCV Version: {cv.version}")
OpenCV Version: 4.5.1
在 Python 中加载这两个图像所需的代码是:
import cv2 as cv
# Load grayscale images
img1 = cv.imread("graf1.png", cv.IMREAD_GRAYSCALE)
img2 = cv.imread("graf3.png", cv.IMREAD_GRAYSCALE)
if img1 is None or img2 is None:
print('Could not open or find the images!')
exit(0)
为了评估我们的图像匹配程序,我们需要在两幅图像之间进行正确的(即 ground truth)几何变换。它是一个称为单应性的 3x3 矩阵,当我们从第一个图像中乘以一个点(在齐次坐标中)时,它返回第二个图像中这个点的坐标。加载这个矩阵:
# Load homography (geometric transformation between image)
fs = cv.FileStorage("H1to3p.xml", cv.FILESTORAGEREAD)
homography = fs.getFirstTopLevelNode().mat()
print(f"Homography from img1 to img2:\n{homography}")
下一步是检测图像中容易在其他图像中找到的部分:Local image features。在本例中,我们将使用 ORB,一个快速可靠的检测器来检测角点。ORB 检测到强角,在不同的尺度上比较它们,并使用 FAST 或 Harris 响应来挑选最好的。它还使用局部 patch 的一阶矩来寻找每个角点的方向。我们检测每个图像中最多 10000 个角点:
detector = cv.ORB_create(10000)
kpts1 = detector.detect(img1, None)
kpts2 = detector.detect(img2, None)
在下面的图片中,你可以看到 500 个用绿点标记的检测响应最强的角点特征:
ORB(导向快速和旋转简短):一个经典的方法,有 10 年的历史,工作相当好。
BEBLID (Boosted Efficient Binary Local Image Descriptor):2020 年引入的一个新的描述符,已被证明在几个任务中改善了 ORB。由于 BEBLID 适用于多种检测方法,所以必须将 ORB 关键点的比例设置为 0.75~1。
# Comment or uncomment to use ORB or BEBLID
descriptor = cv.xfeatures2d.BEBLID_create(0.75)
# descriptor = cv.ORB_create()
kpts1, desc1 = descriptor.compute(img1, kpts1)
kpts2, desc2 = descriptor.compute(img2, kpts2)
现在可以匹配这两个图像的描述符来建立对应关系了。让我们使用暴力求解算法,它基本上比较了第一张图像中的每个描述符和第二张图像中的所有描述符。当我们处理二进制描述符时,使用汉明距离进行比较,即计算每对描述符之间不同的比特数。
这里还使用了一个叫做比率检验的小技巧。它不仅确保描述符 1 和 2 彼此相似,而且确保没有其他像 2 一样接近 1 的描述符。
matcher = cv.DescriptorMatcher_create(cv.DescriptorMatcher_BRUTEFORCE_HAMMING)
nn_matches = matcher.knnMatch(desc1, desc2, 2)
matched1 = []
matched2 = []
nn_match_ratio = 0.8 # Nearest neighbor matching ratio
for m, n in nn_matches:
if m.distance < nn_match_ratio * n.distance:
matched1.append(kpts1[m.queryIdx])
matched2.append(kpts2[m.trainIdx])
因为我们知道正确的几何变换,让我们检查有多少匹配是正确的(inliners)。如果图像 2 中的点和从图像 1 投射到图像 2 的点距离小于 2.5 像素,我们认为匹配是有效的。
inliers1 = []
inliers2 = []
good_matches = []
inlier_threshold = 2.5 # Distance threshold to identify inliers with homography check
for i, m in enumerate(matched1):
# Create the homogeneous point
col = np.ones((3, 1), dtype=np.float64)
col[0:2, 0] = m.pt
# Project from image 1 to image 2
col = np.dot(homography, col)
col /= col[2, 0]
# Calculate euclidean distance
dist = sqrt(pow(col[0, 0] - matched2[i].pt[0], 2) + pow(col[1, 0] - matched2[i].pt[1], 2))
if dist < inlier_threshold:
good_matches.append(cv.DMatch(len(inliers1), len(inliers2), 0))
inliers1.append(matched1[i])
inliers2.append(matched2[i])
现在我们在 inliers1 和 inliers2 变量中有了正确的匹配,我们可以使用 cv.drawMatches 定性地评估结果。每一个对应点可以在更高级别的任务上对我们有帮助,比如 homography estimation, Perspective-n-Point, plane tracking, real-time pose estimation 以及 images stitching。
由于很难定性地比较这种结果,让我们绘制一些定量的评价指标。最能反映描述符可靠程度的指标是 inlier 的百分比:
Matching Results (BEBLID)
*******************************
# Keypoints 1: 9105
# Keypoints 2: 9927
# Matches: 660
# Inliers: 512
# Percentage of Inliers: 77.57%
使用 BEBLID 描述符获得 77.57%的 inliers。如果我们在描述符部分注释掉 BEBLID 并取消注释 ORB 描述符,结果下降到 63.20%:
# Comment or uncomment to use ORB or BEBLID
# descriptor = cv.xfeatures2d.BEBLID_create(0.75)
descriptor = cv.ORB_create()
kpts1, desc1 = descriptor.compute(img1, kpts1)
kpts2, desc2 = descriptor.compute(img2, kpts2)
Matching Results (ORB)
*******************************
# Keypoints 1: 9105
# Keypoints 2: 9927
# Matches: 780
# Inliers: 493
# Percentage of Inliers: 63.20%
总之,只需更改一行代码,将 ORB 描述符替换为 BEBLID ,就可以将这两个图像的匹配结果提高 14%。这在需要局部特征匹配的高级任务中会产生很大影响,所以不要犹豫,试试 BEBLID。
评论