在本文中,我们将学习如何使用OpenCV执行基于特征的图像对齐。
我们将通过一个示例演示这些步骤,其中将使用手机拍摄的表格照片与表格模板对齐。我们将使用的技术通常称为“基于特征的”图像对齐,因为在此技术中,一幅图像中检测到一组稀疏特征,并与另一幅图像中的特征匹配。然后根据这些匹配的特征(将一张图像扭曲到另一张图像)计算出一个转换。
在许多应用程序中,我们具有相同场景或相同文档的两个图像,但是它们没有对齐。换句话说,如果您在一幅图像上选择一个要素(例如一个角点),则另一幅图像中相同角的坐标会非常不同。
图像对齐(也称为图像配准)是使一个图像(或有时两个图像)变形以使两个图像中的特征完美对齐的技术。
图1.左:下载的表格。中心:填写的DMV表格,使用手机拍摄。右:将移动照片(中心)与原始模板(左)对齐的结果。
在上面的示例中,我们左侧有一个来自汽车部门的表格。表格被打印,填写,然后使用手机(中心)照相。在此文档分析应用程序中,在进行任何分析之前,首先将表格的移动照片与原始模板对齐是有意义的。对齐后的输出如右图所示。
图像对齐有许多应用。
在许多文档处理应用程序中,第一步是将扫描或拍照的文档与模板对齐。例如,如果您要编写自动表单阅读器,则最好先将表单与表单模板对齐,然后根据模板中的固定位置读取字段。
在某些医疗应用中,可以在稍有不同的时间进行组织的多次扫描,并且使用本教程描述的技术组合来记录两个图像。
图像对齐最有趣的应用可能是创建全景图。在这种情况下,这两个图像不是平面的图像,而是3D场景的图像。通常,3D对齐需要深度信息。但是,当通过围绕相机的光轴旋转相机来拍摄两个图像时(如在全景图的情况下),我们可以使用本教程中介绍的技术来对齐全景图的两个图像。
图像对齐技术的核心是称为Homography的简单3×3矩阵。有关单应性矩阵的Wikipedia条目看起来非常可怕。
场景的两个图像在两个条件下通过单应性关联。
如前所述,单应性就是3×3矩阵,如下所示。
我们 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)是第一个图像中的点, ( x 2 , y 2 ) (x_2,y_2) (x2,y2)是第二图像在同一个物理点的坐标。然后,Homography 通过以下方式将它们关联
如果知道单应性,则可以将其应用于一个图像的所有像素,以获得与第二个图像对齐的扭曲图像。
图2.同一图的两个3D平面图像(本书顶部)通过单应性矩阵关联
如果我们知道两个图像中有4个或更多对应点,则可以使用OpenCV函数findHomography查找单应性。上图中显示了四个对应点的示例。红色,绿色,黄色和橙色点是对应的点。
利用函数findHomography
,求解线性方程组以寻找单应性,但是在本文中,我们将不讨论该数学运算。
python
h, status = cv2.findHomography(points1, points2)
其中,points1和points2是对应点的向量/阵列,h是单应性矩阵。
在许多计算机视觉应用程序中,我们经常需要识别图像中有趣的稳定点。这些点称为关键点或特征点。OpenCV中实现了多个关键点检测器(例如SIFT,SURF和ORB)。
在本教程中,我们将使用**ORB特征检测器,**因为它是由我的前实验室工作人员Vincent Rabaud共同发明的。开玩笑!我们将使用ORB,因为SIFT和SURF已获得专利,如果要在实际应用中使用它,则需要支付许可费。ORB快速,准确且无许可证!
下图使用圆圈显示了ORB关键点。
ORB代表“FAST算法”和“BRIEF算法”。
特征点检测器分为两部分
当我们知道两个图像中的对应特征时,才能计算出两个图像的单应性。因此,使用匹配算法来查找一个图像中的哪些特征与另一图像中的特征匹配。为此,将一个图像中每个特征的描述符与第二个图像中每个特征的描述符进行比较,以找到良好的匹配。
在本节中,我们介绍使用OpenCV进行图像对齐Python代码。
现在,我们可以总结图像对齐中涉及的步骤。下面的描述引用了下一部分中的代码。
读取图像:我们首先Python代码的56-65 行中读取参考图像(或模板图像)以及我们要与该模板对齐的图像。
检测特征:然后,我们检测两个图像中的ORB特征。尽管我们仅需要4个特征即可计算出单应性,但是通常在两个图像中会检测到数百个特征。我们使用Python代码中的参数MAX_FEATURES控制功能的数量。Python代码中的第16-19行检测功能并使用**detectAndCompute
**计算描述符。
匹配功能:Python的第21-34行中,
我们在两个图像中找到了匹配的特征,并根据匹配的优劣对其进行了排序,仅保留了原始匹配的一小部分。最后,我们在图像上显示匹配良好的特征点与特征描述符。我们使用汉明距离作为两个特征描述符之间相似度的量度。
下图通过画一条连接它们的线来显示匹配的特征。请注意,我们有许多不正确的匹配项,因此在下一步中,我们将需要使用可靠的方法来计算单应性。
图4.通过在它们之间画一条线来显示匹配的关键点。点击放大图片。匹配并不完美,因此我们需要一种可靠的方法来计算下一步的单应性。
计算单应性:当两个图像中有4个或更多对应点时,可以计算单应性。上一节中说明的自动匹配并不总是能产生100%准确的匹配。20%至30%的匹配不正确的情况并不少见。
幸运的是,findHomography
方法利用了一种称为随机样本一致性(RANSAC)的鲁棒估计技术,即使在存在大量不好匹配的情况下,该技术也能得出正确的结果。Python中的第36-45行在代码中完成了此任务。
扭曲图像:计算出正确的单应性后,可以将变换应用于一个图像中的所有像素,以将其映射到另一图像。这是使用OpenCV中的warpPerspective
函数完成的。这是Python的第49行中完成的。
from __future__ import print_function
import cv2
import numpy as np
MAX_FEATURES = 500
GOOD_MATCH_PERCENT = 0.15
def alignImages(im1, im2):
# Convert images to grayscale
im1Gray = cv2.cvtColor(im1, cv2.COLOR_BGR2GRAY)
im2Gray = cv2.cvtColor(im2, cv2.COLOR_BGR2GRAY)
# Detect ORB features and compute descriptors.
orb = cv2.ORB_create(MAX_FEATURES)
keypoints1, descriptors1 = orb.detectAndCompute(im1Gray, None)
keypoints2, descriptors2 = orb.detectAndCompute(im2Gray, None)
# Match features.
matcher = cv2.DescriptorMatcher_create(cv2.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING)
matches = matcher.match(descriptors1, descriptors2, None)
# Sort matches by score
matches.sort(key=lambda x: x.distance, reverse=False)
# Remove not so good matches
numGoodMatches = int(len(matches) * GOOD_MATCH_PERCENT)
matches = matches[:numGoodMatches]
# Draw top matches
imMatches = cv2.drawMatches(im1, keypoints1, im2, keypoints2, matches, None)
cv2.imwrite("matches.jpg", imMatches)
# Extract location of good matches
points1 = np.zeros((len(matches), 2), dtype=np.float32)
points2 = np.zeros((len(matches), 2), dtype=np.float32)
for i, match in enumerate(matches):
points1[i, :] = keypoints1[match.queryIdx].pt
points2[i, :] = keypoints2[match.trainIdx].pt
# Find homography
h, mask = cv2.findHomography(points1, points2, cv2.RANSAC)
# Use homography
height, width, channels = im2.shape
im1Reg = cv2.warpPerspective(im1, h, (width, height))
return im1Reg, h
if __name__ == '__main__':
# Read reference image
refFilename = "form.jpg"
print("Reading reference image : ", refFilename)
imReference = cv2.imread(refFilename, cv2.IMREAD_COLOR)
# Read image to be aligned
imFilename = "scanned-form.jpg"
print("Reading image to align : ", imFilename);
im = cv2.imread(imFilename, cv2.IMREAD_COLOR)
print("Aligning images ...")
# Registered image will be resotred in imReg.
# The estimated homography will be stored in h.
imReg, h = alignImages(im, imReference)
# Write aligned image to disk.
outFilename = "aligned.jpg"
print("Saving aligned image : ", outFilename);
cv2.imwrite(outFilename, imReg)
# Print estimated homography
print("Estimated homography : \n", h)
结果:
Reading reference image : form.jpg
Reading image to align : scanned-form.jpg
Aligning images ...
Saving aligned image : aligned.jpg
Estimated homography :
[[ 1.40104964e+00 -2.02959669e-01 -7.41342584e+00]
[ 2.25280216e-01 1.52880342e+00 -4.85162159e+02]
[-6.39599712e-05 1.34896590e-04 1.00000000e+00]]