基础方法
SIFT特征点和特征描述提取
SIFT算法广泛使用在计算机视觉领域,在OpenCV中也对其进行了实现。
import cv2
#这里使用的Python 3
def sift_kp(image):
gray_image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d_SIFT.create()
kp,des = sift.detectAndCompute(image,None)
kp_image = cv2.drawKeypoints(gray_image,kp,None)
return kp_image,kp,des
SIFT特征点匹配
SIFT算法得到了图像中的特征点以及相应的特征描述,如何把两张图像中的特征点匹配起来呢?一般的可以使用K近邻(KNN)算法。K近邻算法求取在空间中距离最近的K个数据点,并将这些数据点归为一类。在进行特征点匹配时,一般使用KNN算法找到最近邻的两个数据点,如果最接近和次接近的比值大于一个既定的值,那么我们保留这个最接近的值,认为它和其匹配的点为good match(有Lowe在SIFT论文中提出)。
import cv2
def get_good_match(des1,des2):
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
good = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good.append(m)
return good
单应性矩阵Homography Matrix
通过上面的步骤,我们找到了若干两张图中的匹配点,如何将其中一张图通过旋转、变换等方式将其与另一张图对齐呢?这就用到了单应性矩阵了。Homography这个词由Homo和graphy,Homo意为同一,graphy意为图像,也就是同一个东西产生的图像。不同视角的图像上的点具有如下关系:
其中[x1 y1 1]
和[x2 y2 1]
分别表示对应像素的齐次坐标。单应性矩阵就是下面这个矩阵:
可以看到,单应性矩阵有八个参数,如果要解这八个参数的话,需要八个方程,由于每一个对应的像素点可以产生2个方程(x一个,y一个),那么总共只需要四个像素点就能解出这个单应性矩阵。
在Opencv-Python中可以使用如下方式:
import cv2
H, status = cv2.findHomography(ptsA,ptsB,cv2.RANSAC,ransacReprojThreshold)
#其中H为求得的单应性矩阵矩阵
#status则返回一个列表来表征匹配成功的特征点。
#ptsA,ptsB为关键点
#cv2.RANSAC, ransacReprojThreshold这两个参数与RANSAC有关
这里就出现了一个问题,我们用SIFT算法已经找到了若干个匹配的点了(几十个甚至上百个),那么应该选择哪四个点来计算Homography matrix呢? RANSAC算法巧妙的解决了这一问题。
随机抽样一致算法(Random sample consensus:RANSAC)
说道估算模型的算法,很容易想到最小二乘法。最小二乘法在数据误差比较小的情况下是可以的,但是针对噪声很大的数据集的时候,容易出问题,我们可以通过下面的例子来说明
包含许多离群的一组数据,要找一条最适合的线很明显,我们的数据应该是一条由左下到右上的一条线,但是由于离群数据太多(左上和右下区域),如果用最小二乘法的话,就无法准确的找到我们期望的模型(因为最小二乘法无法剔除误差很大的点,一视同仁的将所有的点都用于模型的计算)。RANSAC(Random Sample Consensus)算法的不同之处就在于,它能够有效的去除误差很大的点,并且这些点不计入模型的计算之中。该算法的框架如下:
- 在数据中随机的选择几个点设定为内群(也就是用来计算模型的点)
- 用这些选择的数据计算出一个模型
- 把其它刚才没选到的点带入刚才建立的模型中,计算是否为内群(也就是看这些点是否符合模型,如果符合模型的话,将新的点也计入内群)
- 如果此时的内群数量足够多的话,可以认为这个模型还算OK,那么就用现在的内群数据重新计算一个稍微好些的模型。
- 重复以上步骤,最后,我们保留那个内群数量最多的模型。
可以看到,RANSAC利用其独有的方式,在每一次的计算中都将一些数据点排除在外,起到了消除它们噪声的作用,并在此基础上计算模型。
所以,在得到了众多的匹配点以后,使用RANSAC算法,每次从中筛选四个随机的点,然后求得H矩阵,不断的迭代,直到求得最优的H矩阵为止。所以在前面使用cv2.findHomography方法时,参数cv2.RANSAC
表明使用RANSAC算法来筛选关键点
import cv2
H, status = cv2.findHomography(ptsA,ptsB,cv2.RANSAC,reprojThresh)
#其中H为求得的单应性矩阵矩阵
#status则返回一个列表来表征匹配成功的特征点。
#ptsA,ptsB为关键点
#cv2.RANSAC
#ransacReprojThreshold 则表示一对内群点所能容忍的最大投影误差
#Maximum allowed reprojection error to treat a point pair as an inlier (used in the RANSAC method only)
其中最大投影误差的计算公式如下:
误差计算
应用
在OpenCV提供了上述基础方法以后,我们可以利用这些方法来对图像进行匹配。
def siftImageAlignment(img1,img2):
_,kp1,des1 = sift_kp(img1)
_,kp2,des2 = sift_kp(img2)
goodMatch = get_good_match(des1,des2)
if len(goodMatch) > 4:
ptsA= np.float32([kp1[m.queryIdx].pt for m in goodMatch]).reshape(-1, 1, 2)
ptsB = np.float32([kp2[m.trainIdx].pt for m in goodMatch]).reshape(-1, 1, 2)
ransacReprojThreshold = 4
H, status =cv2.findHomography(ptsA,ptsB,cv2.RANSAC,ransacReprojThreshold);
imgOut = cv2.warpPerspective(img2, H, (img1.shape[1],img1.shape[0]),flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP)
return imgOut,H,status
上面的函数首先找出img1和img2中的特征点,然后找到较好的匹配点对,最后通过warpPerspective方法对图像img2进行投影映射。其计算公式如下:
perspective transformation
其中:
- 第一个参数为需要投影的图像(
img2
) - 第二个参数为单应性矩阵(
H
) - 第三个参数为所得图像的矩阵大小((img1.shape[1],img1.shape[0]) )
- 最后的参数cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP,为插值时使用的插值方法INTER_LINEAR,cv2.WARP_INVERSE_MAP则将M设置为dst--->src的方向变换。
我们简单的测试一下我们的函数:
import numpy as np
import cv2
import Utility
img1 = cv2.imread('stack_alignment/IMG_4209.jpg')
img2 = cv2.imread('stack_alignment/IMG_4211.jpg')
result,_,_ = Utility.siftImageAlignment(img1,img2)
allImg = np.concatenate((img1,img2,result),axis=1)
cv2.namedWindow('Result',cv2.WINDOW_NORMAL)
cv2.imshow('Result',allImg)
cv2.waitKey(0)
测试
其中左一和左二为原始图像,但是两张图在拍摄的时候采用了不同的曝光,并且稍微有一些位移,使用我们提供的方法,可以将图二移动到和图一完全匹配的位置,也就是图三。
网友评论