图像拼接(一):柱面投影+模板匹配+渐入渐出融合

这种拼接方法的假设前提是:待拼接的两幅图像之间的变换模型是平移模型,即两幅图像同名点位置之间只相差两个未知量:ΔxΔx 和ΔyΔy,自由度为2,模型收得最紧。所以只有所有图像都是用同一水平线或者同一已知倾斜角的摄像机拍摄时,这种方法才适用。 
整个过程为:首先对输入的两幅图像做柱面投影;然后通过模板匹配求取ΔxΔx 和ΔyΔy;最后采用渐入渐出的融合方式拼接两幅图像。

柱面投影
为了保证拼接后的视觉一致性,所以需要将待拼接的图像分别投影到一个标准的坐标系下,然后再进行图像拼接。由于柱面坐标的变换比较简单并且投影图像与其投影到圆柱表面的位置无关,用其描述的柱面全景图像可在水平方向上满足360度环视,具有较好的视觉效果,所以可采用柱面投影完成图像拼接。 
下面为柱面投影采用的公式,x′x′和y′y′为柱面投影后的图像坐标,xx和yy为图像原来的坐标,widthwidth和heightheight为图像宽高,ff为相机焦距,我的理解是:因为widthwidth和heightheight都是在图像坐标系下,所以这个ff是相对于图像的,我是根据图像大小以及视场角最做的估计。 
x′=f∗atan(x−0.5∗widthf)+f∗atan(0.5∗widthf)
x′=f∗atan(x−0.5∗widthf)+f∗atan(0.5∗widthf)
y′=f∗(y−0.5∗height)(x−0.5∗width)2+f2−−−−−−−−−−−−−−−−−−√+0.5∗height
y′=f∗(y−0.5∗height)(x−0.5∗width)2+f2+0.5∗height

下面是柱面校正的代码,事无巨细都是自己写的,因为没有找到上述公式的反演公式,直接正向插值了,好在出来的效果还不错。
/**柱面投影函数
 *参数列表中imgIn为输入图像,f为焦距
 *返回值为柱面投影后的图像
*/
Mat cylinder(Mat imgIn, int f)
{
    int colNum, rowNum;
    colNum = 2 * f*atan(0.5*imgIn.cols / f);//柱面图像宽
    rowNum = 0.5*imgIn.rows*f / sqrt(pow(f, 2)) + 0.5*imgIn.rows;//柱面图像高

    Mat imgOut = Mat::zeros(rowNum, colNum, CV_8UC1);
    Mat_<uchar> im1(imgIn);
    Mat_<uchar> im2(imgOut);

    //正向插值
    int x1(0), y1(0);
    for (int i = 0; i < imgIn.rows; i++)
        for (int j = 0; j < imgIn.cols; j++)
        {
            x1 = f*atan((j - 0.5*imgIn.cols) / f) + f*atan(0.5*imgIn.cols / f);
            y1 = f*(i - 0.5*imgIn.rows) / sqrt(pow(j - 0.5*imgIn.cols, 2) + pow(f, 2)) + 0.5*imgIn.rows;
            if (x1 >= 0 && x1 < colNum&&y1 >= 0 && y1<rowNum)
            {
                im2(y1, x1) = im1(i, j);
            }
        }
    return imgOut;
}
 
模板匹配
通过模板匹配的方法求取平移变换参数

/**求平移量
 *参数表为输入两幅图像(有一定重叠区域)
 *返回值为点类型,存储x,y方向的偏移量
*/
Point2i getOffset(Mat img, Mat img1)
{
    Mat templ(img1, Rect(0, 0.4*img1.rows, 0.2*img1.cols, 0.2*img1.rows));
    Mat result(img.cols - templ.cols + 1, img.rows - templ.rows + 1, CV_8UC1);//result存放匹配位置信息
    matchTemplate(img, templ, result, CV_TM_CCORR_NORMED);
    normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
    double minVal; double maxVal; Point minLoc; Point maxLoc; Point matchLoc;
    minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());
    matchLoc = maxLoc;//获得最佳匹配位置
    int dx = matchLoc.x;
    int dy = matchLoc.y - 0.4*img1.rows;//右图像相对左图像的位移
    Point2i a(dx, dy);
    return a;
}
 
线性融合
采用渐入渐出融合,其实就是在重叠区域,对两幅图像的像素,线性地分配权值。公式:img=d∗img1+(1−d)∗img2img=d∗img1+(1−d)∗img2;其中img为融合后的图像,img1和img2为待拼接的两幅图像。d为重叠区域中某个像素点到边界的距离。

/*渐入渐出拼接
 *参数列表中,img1,img2为待拼接的两幅图像,a为偏移量
 *返回值为拼接后的图像
*/
Mat linearStitch(Mat img, Mat img1, Point2i a)
{
    int d = img.cols - a.x;//过渡区宽度
    int ms = img.rows - abs(a.y);//拼接图行数
    int ns = img.cols + a.x;//拼接图列数
    Mat stitch = Mat::zeros(ms, ns, CV_8UC1);
    //拼接
    Mat_<uchar> ims(stitch);
    Mat_<uchar> im(img);
    Mat_<uchar> im1(img1);

    if (a.y >= 0)
    {
        Mat roi1(stitch, Rect(0, 0, a.x, ms));
        img(Range(a.y, img.rows), Range(0, a.x)).copyTo(roi1);
        Mat roi2(stitch, Rect(img.cols, 0, a.x, ms));
        img1(Range(0, ms), Range(d, img1.cols)).copyTo(roi2);
        for (int i = 0; i < ms; i++)
            for (int j = a.x; j < img.cols; j++)
                ims(i, j) = uchar((img.cols - j) / float(d)*im(i + a.y, j) + (j - a.x) / float(d)*im1(i, j - a.x));

    }
    else
    {
        Mat roi1(stitch, Rect(0, 0, a.x, ms));
        img(Range(0, ms), Range(0, a.x)).copyTo(roi1);
        Mat roi2(stitch, Rect(img.cols, 0, a.x, ms));
        img1(Range(-a.y, img.rows), Range(d, img1.cols)).copyTo(roi2);
        for (int i = 0; i < ms; i++)
            for (int j = a.x; j < img.cols; j++)
                ims(i, j) = uchar((img.cols - j) / float(d)*im(i, j) + (j - a.x) / float(d)*im1(i + abs(a.y), j - a.x));
    }


    return stitch;
}
 
实验效果
写一个包含主函数的文件调用上述方法:

#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include<iostream>
#include<time.h>

int main()
{
    Mat img = imread("frame1.jpg", 0);//左图像
    Mat img1 = imread("frame2.jpg", 0);//右图像
    imshow("源图像-左", img);
    imshow("源图像-右", img1);
    double t = (double)getTickCount();
    //柱形投影
    double t3 = (double)getTickCount();
    img = cylinder(img,1000);
    img1 = cylinder(img1, 1000);
    t3 = ((double)getTickCount() - t3) / getTickFrequency();
    //匹配
    double t1 = (double)getTickCount();
    Point2i a = getOffset(img, img1);
    t1 = ((double)getTickCount() - t1) / getTickFrequency();
    //拼接
    double t2 = (double)getTickCount();
    Mat stitch = linearStitch(img, img1, a);
    t2 = ((double)getTickCount() - t2) / getTickFrequency();
    t = ((double)getTickCount() - t) / getTickFrequency();

    cout << "各阶段耗时:"<< endl;
    cout << "柱面投影:" << t3 << '\n' << "模板匹配:" << t1 << '\n' << "渐入渐出拼接:" << t2 << endl;
    cout << "总时间:" << t << endl;

    imshow("柱面校正-左图像", img);
    imshow("柱面校正-右图像", img1);
    imshow("拼接结果", stitch);
    imwrite("rectify.jpg", img);
    imwrite("rectify1.jpg", img1);
    imwrite("stitch.jpg", stitch);
    waitKey(0);
    return 0;
}
 
首先读取两幅源图像: 

柱面投影之后: 


 
最后的拼接结果: 

拼接时间效率: 
测试环境为Intel(R) Core(TM) i3-2350M CPU @ 2.3GHz;Win 10+VS2013;源图像像素分辨率为640*480。 


可以看出整体效果还行,但细节很差,比如重叠区域的墙上电线出现了重影。时间效率方面,自己写的柱面投影函数耗时明显,有待优化
--------------------- 
作者:czl389 
来源:CSDN 
原文:https://blog.csdn.net/czl389/article/details/54599253 
版权声明:本文为博主原创文章,转载请附上博文链接!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/444662.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图像拼接(二):OpenCV同时打开两个摄像头捕获视频

使用OpenCV实现同时打开两个USB摄像头&#xff0c;并实时显示视频。如果未检测有两个摄像头&#xff0c;程序会结束并发出“摄像头未安装好”的警告。这里推荐一个小巧的摄像头视频捕捉软件&#xff1a;amcap&#xff0c;使用它可以方便的检查每个摄像头是否能正常工作。 捕获…

Git(10)-merge

Merge1. 无冲突合并2. 有冲突合并-手动解决3. git diff in merge4. 废弃合并5. 合并策略merge相关的操作的命令 git checkout master git merge alternate # 解决冲突 ..... git add file_1 git commit -m "Add slternate line 5, 6" git reset --hard HEAD # b…

elasticsearch的Linux下安装报错问题解决

1.启动报错如下: vim /etc/security/limits.conf 然后修改如下 * soft nofile 65536 * hard nofile 65536sudo vi /etc/pam.d/common-session 添加 session required pam_limits.so sudo vi /etc/pam.d/common-session-noninteractive 添加 session required pam_limits.so…

leetcode120. 三角形最小路径和

给定一个三角形&#xff0c;找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 例如&#xff0c;给定三角形&#xff1a; [ [2], [3,4], [6,5,7], [4,1,8,3] ] 自顶向下的最小路径和为 11&#xff08;即&#xff0c;2 3 5 1 11&#xff0…

Elasticsearchan相关插件和工具安装

1、下载elasticsearch-head的源码包 地址&#xff1a;https://github.com/mobz/elasticsearch-head/releases 2、安装node运行环境 地址&#xff1a;https://nodejs.org/en/download/ 3、安装完node之后编译elasticsearch-head 执行npm install -g grunt-cli编译源码 执行…

Git(11)-cherry-pick、reset、rebase

更改提交&#xff0c;版本回退1.get reset 重置HEAD指针的指向2.git cherry-pick3.git revert4.git commit --amend修改提交5.git rebase 变基提交5.1 git rebase --onto5.2rebase 产生冲突&#xff0c;解决冲突/终止变基5.3git rebase -i6. rebase Vs mergegit 提供了【修改】…

Elasticsearch集群节点配置详解

注意&#xff1a;如果是在局域网中运行elasticsearch集群也是很简单的&#xff0c;只要cluster.name设置一致&#xff0c;并且机器在同一网段下&#xff0c;启动的es会自动发现对方&#xff0c;组成集群。 三、配置浅涉 elasticsearch的config文件夹里面有两个配置文件&#…

MongoDB修改器使用

欢迎关注我的新微信公众号 ipgame,有什么问题可以提供交流的平台,欢迎大家讨论。 对于文档的更新除替换外,针对某个或多个文档只需要部分更新可使用原子的更新修改器,能够高效的进行文档更新。更新修改器是中特殊的键, 用来指定复杂的操作,比如增加、删除或者调整键,还…

Git(12)-stash, reflog

git stash1. git stash2. reflog命令概览git stash save "WIP:xxxxx" # save后可以跟笔记&#xff0c;WIP:work in process git stash list # 查看存储状态栈的条目 git stash pop # 当前工作目录和索引还原至最近一次save操作的内容…

cmake生成Win64位工程

使用cmake编译64的dll 一开始使用cmake --build .来生成了dll&#xff0c;在导入到java项目中使用的时候&#xff0c;才发现是32位的。导致程序不能正常运行&#xff0c;报错如下&#xff1a; Exception in thread "main" java.lang.UnsatisfiedLinkError Cant load…

leetcode 106. 从中序与后序遍历序列构造二叉树

根据一棵树的中序遍历与后序遍历构造二叉树。 注意: 你可以假设树中没有重复的元素。 例如&#xff0c;给出 中序遍历 inorder [9,3,15,20,7] 后序遍历 postorder [9,15,7,20,3] 返回如下的二叉树&#xff1a; 3 / \ 9 20 / \ 15 7 思路&#xff1a;和前…

Mat矩阵(图像容器)的创建及CV_8UC1,CV_8UC2等参数详解

一&#xff09;Mat矩阵(图像容器)创建时CV_8UC1,CV_8UC2等参数详解 1--Mat不但是一个非常有用的图像容器类,同时也是一个通用的矩阵类 2--创建一个Mat对象的方法很多 3--使用Mat图像容器类创建Mat类的对象 //! default constructor Mat(); //! constructs …

TensorFlow(1)-模型相关基础概念

TensorFlow-11.Graph对象2.Session对象3.Variabels变量4. placeholders与feed_dict5. tf.train.Saver() 模型参数保存、加载Tensorflow 中文官网教程–2.0版本的官方教程 TensorFlow教程&#xff1a;TensorFlow快速入门教程&#xff08;非常详细&#xff09; pytorch Vs tensor…

memcache的使用入门C++代码

下载源码编译&#xff0c;memcached就是生成的主程序&#xff0c;启动可指定端口&#xff0c;memcached作为server端&#xff0c;依然是我们熟悉的cs模式&#xff0c;使用两个client一个setkey&#xff0c;一个getkey一百万个做测试。 ./memcached -d -m 300 -p 11211 -u root…

leetcode78 子集

给定一组不含重复元素的整数数组 nums&#xff0c;返回该数组所有可能的子集&#xff08;幂集&#xff09;。 说明&#xff1a;解集不能包含重复的子集。 示例: 输入: nums [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ] 思路&…

Fiddler抓包工具使用

先下载Fiddler 欢迎关注我的新微信公众号 ipgame&#xff0c;有什么问题可以提供交流的平台&#xff0c;欢迎大家讨论。 电脑最好是笔记本&#xff0c;这样能和手机保持统一局域网内&#xff1b;其他不多说&#xff0c;直接说步骤了。 一.对PC&#xff08;笔记本&#xff0…

Tensorboard--模型可视化工具

Tensorboard1.tensorboard in tensorflow1.1 tensorboard的启动过程1.2 tf.summary 可视化类型1.3 tf.summary 使用demo2.tensorboard in pytorch2.1 SummaryWriter 使用demo12.2 tSummaryWriter 使用demo22.3 tensorboard 数据再读取tensorboard in tensorflow &#xff1a;te…

opencv findContours 报错_acrt_first_block == header

报错_acrt_first_block header 之前一直使用OpenCV3.3VS2015 void AOIAlgorithm::findUnits(Mat& blkGray, vector<vector<cv::Point>> & blkContours) {Mat blkOBW;blur(blkGray, blkGray, cv::Size(5, 5));threshold(blkGray, blkOBW, 0, 255, CV_THR…

TensorFlow(2)-训练数据载入

tensorflow 训练数据载入1. tf.data.Dataset2. dataset 创建数据集的方式2.1 tf.data.Dataset.from_tensor_slices()2.2 tf.data.TextLineDataset()2.3 tf.data.FixedLengthRecordDataset()2.4 tf.data.TFRecordDataset()3. dateset 迭代操作iterator3.1 make_one_shot_iterato…

leetcode66. 加一

给定一个由整数组成的非空数组所表示的非负整数&#xff0c;在该数的基础上加一。 最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#xff0c;这个整数不会以零开头。 示例 1: 输入: [1,2,3] 输出: [1,2,4] 解释: 输入…