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的Tornado框架中session支持的实现代码
Aug 21 Python
python selenium UI自动化解决验证码的4种方法
Jan 05 Python
python使用pycharm环境调用opencv库
Feb 11 Python
tensorflow创建变量以及根据名称查找变量
Mar 10 Python
python基础教程项目四之新闻聚合
Apr 02 Python
Django中使用Whoosh进行全文检索的方法
Mar 31 Python
pytorch实现mnist分类的示例讲解
Jan 10 Python
python torch.utils.data.DataLoader使用方法
Apr 02 Python
pyCharm 实现关闭代码检查
Jun 09 Python
Python 如何创建一个简单的REST接口
Jul 30 Python
Python爬虫之Selenium实现关闭浏览器
Dec 04 Python
python数据分析之用sklearn预测糖尿病
Apr 22 Python
matplotlib如何设置坐标轴刻度的个数及标签的方法总结
PyQt5结合QtDesigner实现文本框读写操作
Python中seaborn库之countplot的数据可视化使用
Python爬取某拍短视频
anaconda python3.8安装后降级
OpenCV-Python实现人脸美白算法的实例
Matplotlib可视化之添加让统计图变得简单易懂的注释
You might like
Laravel学习教程之model validation的使用示例
2017/10/23 PHP
JS控制显示隐藏兼容问题(IE6、IE7、IE8)
2010/04/01 Javascript
jQuery EasyUI API 中文文档 - ComboBox组合框
2011/10/07 Javascript
JavaScript prototype属性深入介绍
2012/11/27 Javascript
Knockoutjs快速入门(经典)
2012/12/24 Javascript
JS 精确统计网站访问量的实例代码
2013/07/05 Javascript
jquery ajax应用中iframe自适应高度问题解决方法
2014/04/12 Javascript
Javascript中获取对象的原型对象的方法小结
2015/02/25 Javascript
nodejs爬虫抓取数据乱码问题总结
2015/07/03 NodeJs
JQuery实现简单的图片滑动切换特效
2015/11/22 Javascript
详解vue-router 2.0 常用基础知识点之导航钩子
2017/05/10 Javascript
JS实现图片居中悬浮效果
2017/12/25 Javascript
jQuery实现右侧抽屉式在线客服功能
2017/12/25 jQuery
koa上传excel文件并解析的实现方法
2018/08/09 Javascript
vue 监听键盘回车事件详解 @keyup.enter || @keyup.enter.native
2018/08/25 Javascript
原生JS实现的跳一跳小游戏完整实例
2019/01/27 Javascript
监控Nodejs的性能实例代码
2019/07/02 NodeJs
Vue使用vue-draggable 插件在不同列表之间拖拽功能
2020/03/12 Javascript
vue实现在进行增删改操作后刷新页面
2020/08/05 Javascript
[04:48]DOTA2亚洲邀请赛林书豪为VGJ加油
2017/04/01 DOTA
Python中逗号的三种作用实例分析
2015/06/08 Python
全面了解Nginx, WSGI, Flask之间的关系
2018/01/09 Python
Python求两个圆的交点坐标或三个圆的交点坐标方法
2018/11/07 Python
python web自制框架之接受url传递过来的参数实例
2018/12/17 Python
django模板加载静态文件的方法步骤
2019/03/01 Python
使用Python在Windows下获取USB PID&amp;VID的方法
2019/07/02 Python
Sperry官网:帆船鞋创始品牌
2016/09/07 全球购物
捷克母婴用品购物网站:Feedo.cz
2020/12/28 全球购物
什么时候用assert
2015/05/08 面试题
法学专业个人求职信
2013/09/26 职场文书
常务副总经理任命书
2014/06/05 职场文书
大专学生求职自荐信
2014/07/06 职场文书
个人工作作风整改措施思想汇报
2014/10/13 职场文书
交通处罚决定书
2015/06/24 职场文书
孩子满月酒答谢词
2015/09/30 职场文书
人事行政部各岗位职责说明书!
2019/07/15 职场文书