![07720abc54563b649ad1e063bc6cb0f6.png](https://i-blog.csdnimg.cn/blog_migrate/f8bb386fc70daf9988cb0a5a10919c88.jpeg)
Image Stitching (图像拼接)是一个非常有用的技术,本文将基于 OpenCV 对其进行实现,Image Stitching 的步骤可以大致分为如下几个部分:
- 获取两张输入的图像中关键点和局部不变性描述符,这里使用 SIFT 算法完成
- 匹配两张输入图像的局部不变性描述符
- 使用 RANSAC 算法基于我们匹配的特征向量合成 Homography 矩阵
- 使用第三步得到的 Homography 矩阵对图像进行拼接
1. SIFT特征点和特征描述提取
SIFT 算法广泛使用在计算机视觉领域,我们可以直接调用 OpenCV 中已经实现了的库来完成SIFT 算法,代码如下:
import cv2 def sift_kp_des(img): sift = cv2.xfeatures2d_SIFT.create() kp, des = sift.detectAndCompute(img, None) kp_img = cv2.drawKeypoints(img, kp, None) return kp_img, kp, des
![fc3e6c7c0a44b1c868605c689f77e77f.png](https://i-blog.csdnimg.cn/blog_migrate/83c8865af39a2d7b9e2d4b178e887f47.jpeg)
图像中的圆圈就是 SIFT算法检测出的关键点。
2. 特征点匹配
SIFT 算法得到了图像中的特征点以及相应的特征描述,接下来就可以使用K近邻算法求取在空间中距离最近的K个数据点,并将这些数据点归为一类。
在进行特征点匹配时,使用KNN算法找到最近邻的两个数据点,如果这两个点的距离比值小于一个我们指定的值,那么我们就可以认为这两个点是最接近的,并认为这两个点是好的匹配点加入到我们的列表中。实现代码如下:
def get_good_match(des1,des2): bf = cv2.BFMatcher() maches = bf.knnMatch(des1, des2, k=2) good_kp = [] for (i,j) in maches: if i.distance < 0.75*j.distance: good_kp.append(i) return good_kp
3. 生成 Homography 矩阵
通过上面的步骤,我们找到了许多的匹配点,但是要完成图像的拼接还需要用到 Homography 矩阵。 不同视角的图像上的点具有如下关系:
![51916139ee25f1c089cfb3a965dc2402.png](https://i-blog.csdnimg.cn/blog_migrate/23d96e7a9d60a1336cdc50dff5e00397.jpeg)
其中[x1,y1 ,1]和[x2,y2,1]分别表示两张图像对应像素的齐次坐标。Homography 矩阵就是下面这个矩阵:
![462cb67829bae38ded8850f0171484fc.png](https://i-blog.csdnimg.cn/blog_migrate/92fa4b59d033b39b06c97b1310dcbdd0.jpeg)
一般设 h22 为 1,所以 Homography 矩阵只有八个未知参数,要求解这八个未知参数只需要四个像素点即可。
4. RANSAC 算法
我们的匹配点非常的多,那么要如何挑出最合适的四个点,来求得最优的 Homography 矩阵。这个过程要用到 RANSAC 算法(Random Sample Consensus,随机抽样一致算法),它能够有效的去除误差很大的点,并且这些点不计入模型的计算之中。过程图像如下:
![c8ab640d3bffa2627765a5bedf31887d.png](https://i-blog.csdnimg.cn/blog_migrate/d90e4b7b4c86719159e58af409fb3849.jpeg)
算法步骤如下:
- 在数据中随机的选择几个点设定为内点
- 用内点拟合出一个模型
- 把除内点以外的点(外点)代入我们拟合的模型中,如果计算得到的Loss在指定的阈值子内,就将这些点标记为内点
- 如果此时内点的数量足够多的话,可以认为这个模型比较理想了,那么就可以用现有的内点重新拟合出一个更好的模型,否则重复以上步骤,最后得到最理想的模型。
5. 拼接
基于第三和第四部分进行图像拼接,代码如下:
def siftImage(img_1,img_2): kp_img_1, kp_1, des_1 = sift_kp_des(img_1) kp_img_2, kp_2, des_2 = sift_kp_des(img_2) good_kp = get_good_match(des_1, des_2) if len(good_kp) > 4: ptsA= np.float32([kp_1[m.queryIdx].pt for m in good_kp]).reshape(-1, 1, 2) ptsB = np.float32([kp_2[m.trainIdx].pt for m in good_kp]).reshape(-1, 1, 2) ransacReprojThreshold = 4 H, status =cv2.findHomography(ptsA,ptsB,cv2.RANSAC,ransacReprojThreshold) imgOutput = cv2.warpPerspective(img_1, H, (img_1.shape[1]+img_2.shape[1], img_1.shape[0])) imgOutput[0:img_2.shape[0], 0:img_2.shape[1]] = img_2 return imgOutput
结果如下:
![3ac9445714545c067c4a7a0f00b806b1.png](https://i-blog.csdnimg.cn/blog_migrate/6831235d04d52882ba1da92bae7dad57.jpeg)
输入图像1
![27612f8e00fe75666e1e84fd8fec78e1.png](https://i-blog.csdnimg.cn/blog_migrate/7fa9b2be68d985fa73871c825938fd3c.jpeg)
输入图像2
![52ddbad60abca167d13211d6cbbd1ce6.png](https://i-blog.csdnimg.cn/blog_migrate/508fd05018735b70c8b16cf9cd33cd19.jpeg)
拼接图像
其中第一张和第二张是输入的原图像,最后一张是我们利用 SIFT 算法和 RANSAC 算法拼接出来的的图像。