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


Posted in Python onJune 11, 2021

背景介绍

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

到此这篇关于OpenCV-Python 实现两张图片自动拼接成全景图的文章就介绍到这了,更多相关OpenCV 图片自动拼接成全景图内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Django发送html邮件的方法
May 26 Python
如何处理Python3.4 使用pymssql 乱码问题
Jan 08 Python
Python利用前序和中序遍历结果重建二叉树的方法
Apr 27 Python
pycharm安装图文教程
May 02 Python
Python实现将SQLite中的数据直接输出为CVS的方法示例
Jul 13 Python
Python实现图片滑动式验证识别方法
Nov 09 Python
python文件拆分与重组实例
Dec 10 Python
检测python爬虫时是否代理ip伪装成功的方法
Jul 12 Python
python代码实现逻辑回归logistic原理
Aug 07 Python
TensorFlow学习之分布式的TensorFlow运行环境
Feb 05 Python
python GUI库图形界面开发之PyQt5美化窗体与控件(异形窗体)实例
Feb 25 Python
pycharm激活码免费分享适用最新pycharm2020.2.3永久激活
Nov 25 Python
matplotlib如何设置坐标轴刻度的个数及标签的方法总结
PyQt5结合QtDesigner实现文本框读写操作
Python中seaborn库之countplot的数据可视化使用
Python爬取某拍短视频
anaconda python3.8安装后降级
OpenCV-Python实现人脸美白算法的实例
Matplotlib可视化之添加让统计图变得简单易懂的注释
You might like
phpfans留言版用到的install.php
2007/01/04 PHP
PHP 抓取新浪读书频道的小说并生成txt电子书的代码
2009/12/18 PHP
ThinkPHP之R方法实例详解
2014/06/20 PHP
Symfony的安装和配置方法
2016/03/17 PHP
Zend Framework教程之Zend_Registry对象用法分析
2016/03/22 PHP
JSON两种结构之对象和数组的理解
2016/07/19 PHP
jquery插件 autoComboBox 下拉框
2010/12/22 Javascript
时间戳转换为时间 年月日时间的JS函数
2013/08/19 Javascript
javascript实现画不相交的圆
2015/04/07 Javascript
深入分析jsonp协议原理
2015/09/26 Javascript
详解Vuex中mapState的具体用法
2017/09/28 Javascript
vue2中使用sass并配置全局的sass样式变量的方法
2018/09/04 Javascript
axios取消请求的实践记录分享
2018/09/26 Javascript
React中使用外部样式的3种方式(小结)
2019/05/28 Javascript
vue实现登录页面的验证码以及验证过程解析(面向新手)
2019/08/02 Javascript
JS中FormData类实现文件上传
2020/03/27 Javascript
浅谈python字符串方法的简单使用
2016/07/18 Python
利用Python暴力破解zip文件口令的方法详解
2017/12/21 Python
python如何实现内容写在图片上
2018/03/23 Python
用python统计代码行的示例(包括空行和注释)
2018/07/24 Python
用python做游戏的细节详解
2019/06/25 Python
Django中F函数的使用示例代码详解
2020/07/06 Python
一款纯css3实现的漂亮的404页面的实例教程
2014/11/27 HTML / CSS
印度网上购物首选目的地:Flipkart
2016/08/01 全球购物
美国美发品牌:Bumble and Bumble
2016/10/08 全球购物
科颜氏香港官方网店:Kiehl’s香港
2021/03/07 全球购物
某/etc/fstab文件中的某行如下: /dev/had5 /mnt/dosdata msdos defaults,usrquota 1 2 请解释其含义
2013/09/18 面试题
酒店总经理助理岗位职责
2014/02/01 职场文书
教师现实表现材料
2014/02/14 职场文书
关爱老人标语
2014/06/21 职场文书
村党的群众路线教育实践活动总结材料
2014/10/31 职场文书
小爸爸观后感
2015/06/15 职场文书
小学语文教师竞聘演讲稿范文
2019/08/09 职场文书
Python实现8种常用抽样方法
2021/06/27 Python
Python办公自动化解决world文件批量转换
2021/09/15 Python
为什么MySQL 删除表数据 磁盘空间还一直被占用
2021/10/16 MySQL