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 getopt模块处理命令行选项实例
May 13 Python
利用Python实现简单的相似图片搜索的教程
Apr 23 Python
python的staticmethod与classmethod实现实例代码
Feb 11 Python
浅谈python中拼接路径os.path.join斜杠的问题
Oct 23 Python
解决pycharm工程启动卡住没反应的问题
Jan 19 Python
用Python识别人脸,人种等各种信息
Jul 15 Python
利用Python实现手机短信监控通知的方法
Jul 22 Python
python-tornado的接口用swagger进行包装的实例
Aug 29 Python
python manage.py runserver流程解析
Nov 08 Python
使用 Python 遍历目录树的方法
Feb 29 Python
如何基于matlab相机标定导出xml文件
Nov 02 Python
python读取并查看npz/npy文件数据以及数据显示方法
Apr 14 Python
matplotlib如何设置坐标轴刻度的个数及标签的方法总结
PyQt5结合QtDesigner实现文本框读写操作
Python中seaborn库之countplot的数据可视化使用
Python爬取某拍短视频
anaconda python3.8安装后降级
OpenCV-Python实现人脸美白算法的实例
Matplotlib可视化之添加让统计图变得简单易懂的注释
You might like
PHP根据传来的16进制颜色代码自动改变背景颜色
2014/06/13 PHP
CI框架中zip类应用示例
2014/06/17 PHP
php支付宝接口用法分析
2015/01/04 PHP
php文件上传后端处理小技巧
2016/05/22 PHP
google 搜索框添加关键字实现代码
2010/04/24 Javascript
一个JavaScript去除字符串末尾的空白实例代码
2014/09/22 Javascript
javascript的函数作用域
2014/11/12 Javascript
AngularJS在IE下取数据总是缓存问题的解决方法
2016/08/05 Javascript
jQuery使用ajax方法解析返回的json数据功能示例
2017/01/10 Javascript
Vue.js实现按钮的动态绑定效果及实现代码
2017/08/21 Javascript
React-Native之定时器Timer的实现代码
2017/10/04 Javascript
js中let和var定义变量的区别
2018/02/08 Javascript
vue编译打包本地查看index文件的方法
2018/02/23 Javascript
基于Vue插入视频的2种方法小结
2019/04/02 Javascript
JS/jQuery实现超简单的Table表格添加,删除行功能示例
2019/07/31 jQuery
layui上传图片到服务器的非项目目录下的方法
2019/09/26 Javascript
javascript实现放大镜功能
2020/12/09 Javascript
[00:36]DOTA2勇士令状莱恩声望物品——冥晶之厄展示
2018/05/25 DOTA
python导出hive数据表的schema实例代码
2018/01/22 Python
cmd运行python文件时对结果进行保存的方法
2018/05/16 Python
django项目中使用手机号登录的实例代码
2019/08/15 Python
Python3使用Selenium获取session和token方法详解
2021/02/16 Python
可以随进度显示不同颜色的css3进度条分享
2014/04/11 HTML / CSS
免费获得微软MCSD证书赶快行动吧!
2012/11/13 HTML / CSS
SmartBuyGlasses台湾:名牌眼镜,名牌太阳眼镜及隐形眼镜
2017/01/04 全球购物
eBay英国购物网站:eBay.co.uk
2019/06/19 全球购物
保险专业自荐信范文
2014/02/20 职场文书
担保书格式及范文
2014/04/01 职场文书
会展策划与管理专业求职信
2014/06/09 职场文书
2014年体检中心工作总结
2014/12/23 职场文书
个人先进事迹材料
2014/12/29 职场文书
堂吉诃德读书笔记
2015/06/30 职场文书
python 用递归实现通用爬虫解析器
2021/04/16 Python
python周期任务调度工具Schedule使用详解
2021/11/23 Python
【海涛七七解说】DCG第二周:DK VS 天禄
2022/04/01 DOTA
Apache Hudi 加速传统的批处理模式
2022/04/24 Servers