OpenCV-Python 实现两张图片自动拼接成全景图

作者:ToBeDeveloper 时间:2022-12-07 11:25:49 

背景介绍

图片的全景拼接如今已不再稀奇,现在的智能摄像机和手机摄像头基本都带有图片自动全景拼接的功能,但是一般都会要求拍摄者保持设备的平稳以及单方向的移动取景以实现较好的拼接结果。这是因为拼接的图片之间必须要有相似的区域以保证拼接结果的准确性和完整性。本文主要简单描述如何用 Python 和 OpenCV 库实现两张图片的自动拼合,首先简单介绍一下两张图片拼接的原理。

基本原理

要实现两张图片的简单拼接,其实只需找出两张图片中相似的点 (至少四个,因为 homography 矩阵的计算需要至少四个点), 计算一张图片可以变换到另一张图片的变换矩阵 (homography 单应性矩阵),用这个矩阵把那张图片变换后放到另一张图片相应的位置 ( 就是相当于把两张图片中定好的四个相似的点給重合在一起)。如此,就可以实现简单的全景拼接。当然,因为拼合之后图片会重叠在一起,所以需要重新计算图片重叠部分的像素值,否则结果会很难看。所以总结起来其实就两个步骤:

1. 找两张图片中相似的点,计算变换矩阵

2. 变换一张图片放到另一张图片合适的位置,并计算重叠区域新的像素值 (这里就是图片融合所需要采取的策略)

具体实现

寻找相似点

当然,我们可以手动的寻找相似的点,但是这样比较麻烦。因为相似点越多或者相似点对应的位置越准确,所得的结果就越好,但是人的肉眼所找的位置总是有误差的,而且找出很多的点也不是一件容易的事。所以就有聪明的人设计了自动寻找相似点的算法,这里我们就用了 SIFT 算法,而 OpenCV 也给我们提供 SIFT 算法的接口,所以我们就不需要自己费力去实现了。如下是两张测试图片的原图和找出相似点后的图片。

OpenCV-Python 实现两张图片自动拼接成全景图 OpenCV-Python 实现两张图片自动拼接成全景图

OpenCV-Python 实现两张图片自动拼接成全景图

其中红色的点是 SIFT 算法找出的相似点,而绿色的线表示的是在所有找出的相似的点中所筛选出的可信度更高的相似的点。因为算法找出的相似点并不一定是百分百正确的。然后就可以根据这些筛选出的相似点计算变换矩阵,当然 OpenCV 也提供了相应的接口方便我们的计算,而具体的代码实现也可以在 OpenCV 的 Python tutorial 中找到 [1]。

图片拼接

计算出变换矩阵后,接下来就是第二步,用计算出的变换矩阵对其中一张图做变换,然后把变换的图片与另一张图片重叠在一起,并重新计算重叠区域新的像素值。对于计算重叠区域的像素值,其实可以有多种方法去实现一个好的融合效果,这里就用最简单粗暴的但效果也不错的方式。直白来说就是实现一个图像的线性渐变,对于重叠的区域,靠近左边的部分,让左边图像内容显示的多一些,靠近右边的部分,让右边图像的内容显示的多一些。用公式表示就是,假设 alpha 表示像素点横坐标到左右重叠区域边界横坐标的距离,新的像素值就为 newpixel = 左图像素值 × (1 - alpha) + 右图像素值 × alpha 。这样就可以实现一个简单的融合效果,如果想实现更复杂或更好的效果,可以去搜索和尝试一下 multi-band 融合,这里就不过多赘述了。最后附上实现的结果和代码,可供参考。

OpenCV-Python 实现两张图片自动拼接成全景图

Python 代码如下:


import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

if __name__ == '__main__':
   top, bot, left, right = 100, 100, 0, 500
   img1 = cv.imread('test1.jpg')
   img2 = cv.imread('test2.jpg')
   srcImg = cv.copyMakeBorder(img1, top, bot, left, right, cv.BORDER_CONSTANT, value=(0, 0, 0))
   testImg = cv.copyMakeBorder(img2, top, bot, left, right, cv.BORDER_CONSTANT, value=(0, 0, 0))
   img1gray = cv.cvtColor(srcImg, cv.COLOR_BGR2GRAY)
   img2gray = cv.cvtColor(testImg, cv.COLOR_BGR2GRAY)
   sift = cv.xfeatures2d_SIFT().create()
   # find the keypoints and descriptors with SIFT
   kp1, des1 = sift.detectAndCompute(img1gray, None)
   kp2, des2 = sift.detectAndCompute(img2gray, None)
   # FLANN parameters
   FLANN_INDEX_KDTREE = 1
   index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
   search_params = dict(checks=50)
   flann = cv.FlannBasedMatcher(index_params, search_params)
   matches = flann.knnMatch(des1, des2, k=2)

# Need to draw only good matches, so create a mask
   matchesMask = [[0, 0] for i in range(len(matches))]

good = []
   pts1 = []
   pts2 = []
   # ratio test as per Lowe's paper
   for i, (m, n) in enumerate(matches):
       if m.distance < 0.7*n.distance:
           good.append(m)
           pts2.append(kp2[m.trainIdx].pt)
           pts1.append(kp1[m.queryIdx].pt)
           matchesMask[i] = [1, 0]

draw_params = dict(matchColor=(0, 255, 0),
                      singlePointColor=(255, 0, 0),
                      matchesMask=matchesMask,
                      flags=0)
   img3 = cv.drawMatchesKnn(img1gray, kp1, img2gray, kp2, matches, None, **draw_params)
   plt.imshow(img3, ), plt.show()

rows, cols = srcImg.shape[:2]
   MIN_MATCH_COUNT = 10
   if len(good) > MIN_MATCH_COUNT:
       src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
       dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
       M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)
       warpImg = cv.warpPerspective(testImg, np.array(M), (testImg.shape[1], testImg.shape[0]), flags=cv.WARP_INVERSE_MAP)

for col in range(0, cols):
           if srcImg[:, col].any() and warpImg[:, col].any():
               left = col
               break
       for col in range(cols-1, 0, -1):
           if srcImg[:, col].any() and warpImg[:, col].any():
               right = col
               break

res = np.zeros([rows, cols, 3], np.uint8)
       for row in range(0, rows):
           for col in range(0, cols):
               if not srcImg[row, col].any():
                   res[row, col] = warpImg[row, col]
               elif not warpImg[row, col].any():
                   res[row, col] = srcImg[row, col]
               else:
                   srcImgLen = float(abs(col - left))
                   testImgLen = float(abs(col - right))
                   alpha = srcImgLen / (srcImgLen + testImgLen)
                   res[row, col] = np.clip(srcImg[row, col] * (1-alpha) + warpImg[row, col] * alpha, 0, 255)

# opencv is bgr, matplotlib is rgb
       res = cv.cvtColor(res, cv.COLOR_BGR2RGB)
       # show the result
       plt.figure()
       plt.imshow(res)
       plt.show()
   else:
       print("Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT))
       matchesMask = None

Reference

[1] OpenCV tutorial: https://docs.opencv.org/3.4.1/d1/de0/tutorial_py_feature_homography.html

来源:https://www.cnblogs.com/ToBeDeveloper-Zhen/p/9314760.html

标签:OpenCV,图片,拼接
0
投稿

猜你喜欢

  • SQL Server端口更改后的数据库连接方式

    2008-12-29 14:11:00
  • JavaScript变量声明var,let.const及区别浅析

    2024-05-09 15:05:37
  • mysql5.6.19下子查询为什么无法使用索引

    2024-01-15 01:04:29
  • JavaScript控制台的更多功能

    2024-02-24 12:46:42
  • python flask几分钟实现web服务的例子

    2023-05-21 13:04:58
  • 利用Python演示数型数据结构的教程

    2022-04-29 07:05:40
  • python statsmodel的使用

    2021-04-22 13:38:13
  • 在sqlserver2005中安装sql server 2000的示例数据库northwind的方法

    2024-01-18 11:00:12
  • Python实现拷贝多个文件到同一目录的方法

    2021-09-03 20:56:25
  • MySQL数据库的索引原理与慢SQL优化的5大原则

    2024-01-18 14:46:01
  • python 3.7.0 安装配置方法图文教程

    2023-10-14 22:24:28
  • 用python发送微信消息

    2022-04-14 07:16:55
  • Python IDLE Subprocess Connection Error的简单解决方法

    2022-12-15 04:31:09
  • Vue集成lodop插件实现打印功能

    2023-07-02 17:01:20
  • 跟我学习javascript的闭包

    2024-04-23 09:11:51
  • python 格式化输出百分号的方法

    2023-07-17 08:23:27
  • python爬虫之场内ETF基金获取

    2021-08-18 17:51:34
  • 简述Asp与XML之间的关系

    2008-04-17 10:46:00
  • 利用Fn.py库在Python中进行函数式编程

    2021-11-14 22:40:38
  • oracle 中 sqlplus命令大全

    2024-01-23 21:07:27
  • asp之家 网络编程 m.aspxhome.com