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 相关文章推荐
python 示例分享---逻辑推理编程解决八皇后
Jul 20 Python
Django中处理出错页面的方法
Jul 15 Python
Python实现翻转数组功能示例
Jan 12 Python
python正则表达式及使用正则表达式的例子
Jan 22 Python
对Xpath 获取子标签下所有文本的方法详解
Jan 02 Python
python Pexpect 实现输密码 scp 拷贝的方法
Jan 03 Python
PyQt5中QTableWidget如何弹出菜单的示例代码
Feb 23 Python
使用python实现微信小程序自动签到功能
Apr 27 Python
Python基于Faker假数据构造库
Nov 30 Python
Django开发RESTful API实现增删改查(入门级)
May 10 Python
python process模块的使用简介
May 14 Python
python实现手机推送 代码也就10行左右
Apr 12 Python
matplotlib如何设置坐标轴刻度的个数及标签的方法总结
PyQt5结合QtDesigner实现文本框读写操作
Python中seaborn库之countplot的数据可视化使用
Python爬取某拍短视频
anaconda python3.8安装后降级
OpenCV-Python实现人脸美白算法的实例
Matplotlib可视化之添加让统计图变得简单易懂的注释
You might like
PHP 数组遍历方法大全(foreach,list,each)
2010/06/30 PHP
php中autoload的用法总结
2013/11/08 PHP
CodeIgniter框架URL路由总结
2014/09/03 PHP
php数组冒泡排序算法实例
2016/05/06 PHP
PHP面向对象程序设计组合模式与装饰模式详解
2016/12/02 PHP
accesskey 提交
2006/06/26 Javascript
JS定时关闭窗口的实例
2013/05/22 Javascript
用jQuery模拟select下拉框的简单示例代码
2014/01/26 Javascript
js冒泡、捕获事件及阻止冒泡方法详细总结
2014/05/08 Javascript
nodejs简单实现中英文翻译
2015/05/04 NodeJs
jquery实现可自动收缩的TAB网页选项卡代码
2015/09/06 Javascript
jQuery给指定的table动态添加删除行的操作方法
2016/10/12 Javascript
jQuery实现两列等高并自适应高度
2016/12/22 Javascript
不使用JavaScript实现菜单的打开和关闭效果demo
2018/05/01 Javascript
vue 更改连接后台的api示例
2019/11/11 Javascript
Python OOP类中的几种函数或方法总结
2019/02/22 Python
六行python代码的爱心曲线详解
2019/05/17 Python
一行python实现树形结构的方法
2019/08/09 Python
python elasticsearch环境搭建详解
2019/09/02 Python
python Plotly绘图工具的简单使用
2020/03/03 Python
python如何快速生成时间戳
2020/07/21 Python
解决python 执行shell命令无法获取返回值的问题
2020/12/05 Python
非常漂亮的CSS3百叶窗焦点图动画
2016/02/24 HTML / CSS
css3媒体查询中device-width和width的区别详解
2020/03/27 HTML / CSS
是什么让J2EE适合用来开发多层的分布式的应用
2015/01/16 面试题
介绍Java的内部类
2012/10/27 面试题
介绍一下XMLHttpRequest对象的常用方法和属性
2013/05/24 面试题
优秀交警事迹材料
2014/01/26 职场文书
工程安全员岗位职责
2014/03/09 职场文书
六年级语文下册教学计划
2015/01/22 职场文书
宾馆前台接待岗位职责
2015/04/02 职场文书
检察院起诉意见书
2015/05/20 职场文书
2016幼儿园中班开学寄语
2015/12/03 职场文书
Python机器学习之PCA降维算法详解
2021/05/19 Python
Python制作表白爱心合集
2022/01/22 Python
python画条形图的具体代码
2022/04/20 Python