*****************************************************
【本文详细代码(包含完整可运行代码即测试图像)请移步至公众号-算法Lab】
【更多算法实战项目及电子书资源,请前往公众号-算法Lab】
*****************************************************
环境
开发语言:python
opencv:opencv-python = 4.7.0.72
numpy :numpy = 1.23.0
前言
图像拼接技术就是将数张有重叠部分的图像(可能是不同时间、不同视角或者不同传感器获得的)拼成一幅无缝的全景图或高分辨率图像的技术。
而其中图像配准(image alignment)和图像融合是图像拼接的两个关键技术。图像配准是图像融合的基础,而且图像配准算法的计算量一般非常大,因此图像拼接技术的发展很大程度上取决于图像配准技术的创新。早期的图像配准技术主要采用点匹配法,这类方法速度慢、精度低,而且常常需要人工选取初始匹配点,无法适应大数据量图像的融合。图像拼接的方法很多,不同的算法步骤会有一定差异,但大致的过程是相同的。
算法步骤
1. 特征检测与提取
1.1 关键点检测
1.2 关键点和描述符提取
2.特征匹配
3.不同特征描述子匹配测试
4.透视变换
首先,我们需要准备好两张待拼接的图像,如下:
1.特征提取
对于上述一组图像,我们希望将它们拼接后达到完整全景场景效果。对于待拼接的图像,必须有重合或者重叠的图像部分,否则,算法无法找到图像的重叠关键点。本文只做算法演示,故上面给出的是简单且比较理想的图像。在实际项目中,虽然有时候待拼接图像确实存在重叠区域时,但是仍然存在关键点检测失败的可能性,因为图像还存在缩放、旋转、来自不同相机等因素的影响。但是无论哪种情况,我们都需要检测及获取图像中的特征点。
1.1关键点检测
简单提取关键点方法,比如使用Harris Corners之类的算法来提取关键点。然后,我们可以尝试基于某种相似性度量(例如欧几里得距离)来匹配相应的关键点。众所周知,角点具有一个不错的特性:角点不变。这意味着,一旦检测到角点,即使旋转图像,该角点仍将存在。
但是,如果我们旋转然后缩放图像怎么办?在这种情况下,我们会很困难,因为角点的大小不变。也就是说,如果我们放大图像,先前检测到的角可能会变成一条线!
总而言之,我们需要旋转和缩放不变的特征。那就是更强大的方法(如SIFT,SURF和ORB)。
1.2关键点和描述符
对于SIFT和SURF之类的方法试图解决角点检测算法的局限性。通常,角点检测器算法使用固定大小的内核来检测图像上的感兴趣区域(角)。不难看出,当我们缩放图像时,该内核可能变得太小或太大。为了解决此限制,诸如SIFT之类的方法使用高斯差分(DoD)。想法是将DoD应用于同一图像的不同缩放版本。它还使用相邻像素信息来查找和完善关键点和相应的描述符。
关键点和描述符的是三个主要的应用场景就是跟踪,目标识别和立体建模。不过不管是哪种用途,根本的处理逻辑确实类似的:首先找出图像中的关键点,之后选取一种描述符,最后基于关键点的描述符查找匹配 —— detect-describe-match。
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':
# maybe will get error "module 'cv2' has no attribute 'xfeatures2d'"
# please update the opencv version
descriptor = cv2.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)
获取关键点及描述符
SIFT算法得到的检测关键点和描述符
ORB算法得到的检测关键点和描述符
2.特征匹配
对上述2张图进行关键点检测和描述运算后,得到大量特征点。现在,我们想比较两组特征,并尽可能显示更多相似性的特征点对。使用OpenCV,特征点匹配需要Matcher对象。在这里,我们探索两种方式:暴力匹配器(BruteForce)和KNN(k最近邻)。
BruteForce(BF)Matcher的作用恰如其名。给定2组特征(来自图像A和图像B),将A组的每个特征与B组的所有特征进行比较。默认情况下,BF Matcher计算两点之间的欧式距离。因此,对于集合A中的每个特征,它都会返回集合B中最接近的特征。对于SIFT和SURF,OpenCV建议使用欧几里得距离。对于ORB和BRISK等其他特征提取器,建议使用汉明距离。我们要使用OpenCV创建BruteForce Matcher,一般情况下,我们只需要指定2个参数即可。第一个是距离度量。第二个是是否进行交叉检测的布尔参数。具体代码如下:
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)
bf = cv2.BFMatcher()
return bf
3.不同特征描述子匹配测试
基于KNN和SIFT的检测关键点和描述符
基于BF和SIFT的检测关键点和描述符
if feature_matching == 'bf':
matches = matchKeyPointsBF(featuresA, featuresB, method=feature_extractor)
img3 = cv2.drawMatches(trainImg,kpsA,queryImg,kpsB,matches[:100],
None,flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
elif feature_matching == 'knn':
matches = matchKeyPointsKNN(featuresA, featuresB, ratio=0.75, method=feature_extractor)
img3 = cv2.drawMatches(trainImg,kpsA,queryImg,kpsB,np.random.choice(matches,100),
None,flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
上述图像关键点检测图像中,KNN中交叉验证——暴力匹配(Brute force)测试之后,某些关键点也无法正确匹配。
然而,Matcher算法将为我们提供两个图像中最好的(更相似的)特征集。现在,我们需要获取这些点并找到基于匹配点将2个图像拼接在一起的变换矩阵。
这种转换称为Homography matrix(单应性矩阵)。简而言之,如果Homography是3x3矩阵,可用于许多应用,例如相机姿态估计,透视校正和图像拼接。如果Homography是2D变换。它将点从一个平面(图像)映射到另一个平面。让我们看看我们是如何得到它的。
4.透视变换
透视变换cv2.warpPerspective
result = cv2.warpPerspective(trainImg, H, (width, height))
result[0:queryImg.shape[0], 0:queryImg.shape[1]] = queryImg
生成的全景图像如下所示。如我们所见,结果中包含了两个图像中的内容。另外,我们可以看到一些与照明条件和图像边界边缘效应有关的问题。理想情况下,我们可以执行一些处理技术来标准化亮度,例如直方图匹配,这会使结果看起来更真实和自然一些。
【本项目还留有2组图像作为课后作业(包含完整可运行代码即测试图像),想要进一步了解的同学请移步至公众号-算法Lab】