
图像拼接是计算机视觉中最成功的应用之一。现在,很难找到不包含此功能的手机或图像处理API。在本文中,咱们将讨论如何使用Python和OpenCV进行图像拼接。也就是,给定两张共享某些公共区域的图像,目标是“缝合”它们并建立一个全景图像场景。固然也能够是给定多张图像,可是总会转换成两张共享某些公共区域图像拼接的问题,所以本文以最简单的形式进行介绍。python
本文主要的知识点包含一下内容:
算法
关键点检测微信
局部不变描述符(SIFT,SURF等)app
特征匹配函数
使用RANSAC进行单应性估计测试
透视变换ui
咱们须要拼接的两张图像以下:
spa
特征检测与提取.net
给定上述一对图像,咱们但愿将它们缝合以建立全景场景。重要的是要注意,两个图像都须要有一些公共区域。固然,咱们上面给出的两张图像时比较理想的,有时候两个图像虽然具备公共区域,可是一样还可能存在缩放、旋转、来自不一样相机等因素的影响。可是不管哪一种状况,咱们都须要检测图像中的特征点。设计
关键点检测
最初的而且多是幼稚的方法是使用诸如Harris Corners之类的算法来提取关键点。而后,咱们能够尝试基于某种类似性度量(例如欧几里得距离)来匹配相应的关键点。众所周知,角点具备一个不错的特性:角点不变。这意味着,一旦检测到角点,即便旋转图像,该角点仍将存在。
可是,若是咱们旋转而后缩放图像怎么办?在这种状况下,咱们会很困难,由于角点的大小不变。也就是说,若是咱们放大图像,先前检测到的角可能会变成一条线!
总而言之,咱们须要旋转和缩放不变的特征。那就是更强大的方法(如SIFT,SURF和ORB)。
关键点和描述符
诸如SIFT和SURF之类的方法试图解决角点检测算法的局限性。一般,角点检测器算法使用固定大小的内核来检测图像上的感兴趣区域(角)。不难看出,当咱们缩放图像时,该内核可能变得过小或太大。为了解决此限制,诸如SIFT之类的方法使用高斯差分(DoD)。想法是将DoD应用于同一图像的不一样缩放版本。它还使用相邻像素信息来查找和完善关键点和相应的描述符。
首先,咱们须要加载2个图像,一个查询图像和一个训练图像。最初,咱们首先从二者中提取关键点和描述符。经过使用OpenCV detectAndCompute()函数,咱们能够一步完成它。请注意,为了使用detectAndCompute(),咱们须要一个关键点检测器和描述符对象的实例。它能够是ORB,SIFT或SURF等。此外,在将图像输入给detectAndCompute()以前,咱们将其转换为灰度。
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': descriptor = cv2.xfeatures2d.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做为特征提取器,它将为每一个关键点返回一个128维特征向量。若是选择SURF,咱们将得到64维特征向量。下图显示了使用SIFT,SURF,BRISK和ORB获得的结果。
使用ORB和汉明距离检测关键点和描述符
使用SIFT检测关键点和描述符
使用BRISK和汉明距离检测关键点和描述符
特征匹配
如咱们所见,两个图像都有大量特征点。如今,咱们想比较两组特征,并尽量显示更多类似性的特征点对。使用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) return bf
交叉检查布尔参数表示这两个特征是否具备相互匹配才视为有效。换句话说,对于被认为有效的一对特征(f1,f2),f1须要匹配f2,f2也必须匹配f1做为最接近的匹配。此过程可确保提供更强大的匹配功能集,这在原始SIFT论文中进行了描述。
可是,对于要考虑多个候选匹配的状况,可使用基于KNN的匹配过程。KNN不会返回给定特征的单个最佳匹配,而是返回k个最佳匹配。须要注意的是,k的值必须由用户预先定义。如咱们所料,KNN提供了更多的候选功能。可是,在进一步操做以前,咱们须要确保全部这些匹配对都具备鲁棒性。
比率测试
为了确保KNN返回的特征具备很好的可比性,SIFT论文的做者提出了一种称为比率测试的技术。通常状况下,咱们遍历KNN获得匹配对,以后再执行距离测试。对于每对特征(f1,f2),若是f1和f2之间的距离在必定比例以内,则将其保留,不然将其丢弃。一样,必须手动选择比率值。
本质上,比率测试与BruteForce Matcher的交叉检查选项具备相同的做用。二者都确保一对检测到的特征确实足够接近以致于被认为是类似的。下面2个图显示了BF和KNN Matcher在SIFT特征上的匹配结果。咱们选择仅显示100个匹配点以清晰显示。
使用KNN和SIFT的定量测试进行功能匹配
在SIFT特征上使用暴力匹配器进行特征匹配
须要注意的是,即便作了多种筛选来保证匹配的正确性,也没法彻底保证特征点彻底正确匹配。尽管如此,Matcher算法仍将为咱们提供两幅图像中最佳(更类似)的特征集。接下来,咱们利用这些点来计算将两个图像的匹配点拼接在一块儿的变换矩阵。
这种变换称为单应矩阵。简而言之,单应性是一个3x3矩阵,可用于许多应用中,例如相机姿态估计,透视校订和图像拼接。它将点从一个平面(图像)映射到另外一平面。
估计单应性
随机采样一致性(RANSAC)是用于拟合线性模型的迭代算法。与其余线性回归器不一样,RANSAC被设计为对异常值具备鲁棒性。
像线性回归这样的模型使用最小二乘估计将最佳模型拟合到数据。可是,普通最小二乘法对异常值很是敏感。若是异常值数量很大,则可能会失败。RANSAC经过仅使用数据中的一组数据估计参数来解决此问题。下图显示了线性回归和RANSAC之间的比较。须要注意数据集包含至关多的离群值。
咱们能够看到线性回归模型很容易受到异常值的影响。那是由于它试图减小平均偏差。所以,它倾向于支持使全部数据点到模型自己的总距离最小的模型。包括异常值。相反,RANSAC仅将模型拟合为被识别为点的点的子集。
这个特性对咱们的用例很是重要。在这里,咱们将使用RANSAC来估计单应矩阵。事实证实,单应矩阵对咱们传递给它的数据质量很是敏感。所以,重要的是要有一种算法(RANSAC),该算法能够从不属于数据分布的点中筛选出明显属于数据分布的点。
估计了单应矩阵后,咱们须要将其中一张图像变换到一个公共平面上。在这里,咱们将对其中一张图像应用透视变换。透视变换能够组合一个或多个操做,例如旋转,缩放,平移或剪切。咱们可使用OpenCV warpPerspective()函数。它以图像和单应矩阵做为输入。
# Apply panorama correctionwidth = trainImg.shape[1] + queryImg.shape[1]height = trainImg.shape[0] + queryImg.shape[0]
result = cv2.warpPerspective(trainImg, H, (width, height))result[0:queryImg.shape[0], 0:queryImg.shape[1]] = queryImg
plt.figure(figsize=(20,10))plt.imshow(result)
plt.axis('off')plt.show()
生成的全景图像以下所示。如咱们所见,结果中包含了两个图像中的内容。另外,咱们能够看到一些与照明条件和图像边界边缘效应有关的问题。理想状况下,咱们能够执行一些处理技术来标准化亮度,例如直方图匹配,这会使结果看起来更真实和天然一些。
交流群
欢迎加入公众号读者群一块儿和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(之后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,不然不予经过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,不然会请出群,谢谢理解~
本文分享自微信公众号 - 小白学视觉(NoobCV)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。