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

这种拼接方法的假设前提是:待拼接的两幅图像之间的变换模型是平移模型,即两幅图像同名点位置之间只相差两个未知量:Δ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;使用它可以方便的检查每个摄像头是否能正常工作。 捕获…

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…

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…

leetcode14. 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 ""。 示例 1: 输入: ["flower","flow","flight"] 输出: "fl" 示例 2: 输入: ["dog","racecar",&quo…

Android在子线程里使用Toast报错Can't toast on a thread that has not called Looper.prepare()

在接android SDK的时候有时候为了方便debug调试查看&#xff0c;通过Toast输出相关信息&#xff0c; 实际上这个是在子线程中输出的&#xff0c;在logcat里查看有如下报错java.lang.RuntimeException: Cant toast on a thread that has not called Looper.prepare()。 解决办法…

虚拟机安装windows2012和虚拟机安装国产系统deepin

虚拟机安装windows2012和虚拟机安装国产系统deepin 一.安装windows20121.安装VMWare虚拟机2.1.注意点一&#xff1a;VMWare虚拟网卡2.2.注意点二&#xff1a;配置虚拟网络编辑器3.安装配置Windows Server 2012 R2 二.虚拟机安装deepin1.deepin官网下载ios镜像2.deepin下载合适的…

leetcode876 链表中间的结点

给定一个带有头结点 head 的非空单链表&#xff0c;返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;[1,2,3,4,5] 输出&#xff1a;此列表中的结点 3 (序列化形式&#xff1a;[3,4,5]) 返回的结点值为 3 。 …

PlayFab(二)如何通过Demo应用来进一步熟悉Playfab

有时候刚开始接触新的平台会两眼一麻黑,不过这个文章希望能给读者一些启示,Playfab默认会给开发者提供一个应用,这里我暂且叫他”我的游戏“; 我通过官网提供的DEMO测试地址: https://www.vanguardoutrider.com/#/ 来为该应用配置服务器。 如果你是第一次进入这个页面想为…

leetcode718 最长重复子数组

给两个整数数组 A 和 B &#xff0c;返回两个数组中公共的、长度最长的子数组的长度。 示例 1: 输入: A: [1,2,3,2,1] B: [3,2,1,4,7] 输出: 3 解释: 长度最长的公共子数组是 [3, 2, 1]。 说明: 1 < len(A), len(B) < 1000 0 < A[i], B[i] < 100 思路&#xf…

leetcode108 将有序数组转换为二叉搜索树

将一个按照升序排列的有序数组&#xff0c;转换为一棵高度平衡二叉搜索树。 本题中&#xff0c;一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 示例: 给定有序数组: [-10,-3,0,5,9], 一个可能的答案是&#xff1a;[0,-3,9,-10,null,…

MachineLearning(12)- RNN-LSTM-tf.nn.rnn_cell

RNN-LSTM1.RNN2.LSTM3. tensorflow 中的RNN-LSTM3.1 tf.nn.rnn_cell.BasicRNNCell()3.2 tf.nn.rnn_cell.BasicLSTMCell()3.3 tf.nn.dynamic_rnn()--多步执行循环神经网络1.RNN RNN-Recurrent Neural Network-循环神经网络 RNN用来处理序列数据。多层感知机MLP层间节点全联接&…

AWS的VPC使用经验(二)

上文说了如何创建自定义VPC网络的EC2实例&#xff0c;这节说如何在多个VPC之间创建对等连接。 这里分别填写自己的VPC和对方的VPC的ID信息&#xff0c;然后在对方的VPC里就能看到有连接请求&#xff0c;在对方的连接请求里选择 “操作”->接受。 到这里已经快要收尾了&…

ubuntu nginx配置负载均衡篇(一)

Nginx 代理服务的配置说明 1、设置 404 页面导向地址 error_page 404 https://www.runnob.com; #错误页 proxy_intercept_errors on; #如果被代理服务器返回的状态码为400或者大于400,设置的error_page配置起作用。默认为off。 2、如果我们的代理只允许接受get,post请求…

坦克大战

效果 map.js var map4 [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,2,2,0,0,2,2,0,0,0,2,2,0,0,2,2,0,0,2,2,0,2,2,0],[0,2,2,0,0,2,2,0,0,0,2,2,3,3,2,2…

windows安装 MySQL5.7服务端

1,安装https://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5.7.30-winx64.zip mysql安装包。 2. 自己配置my.ini [client] port=3306 [mysql] default-character-set=utf8[mysqld] character-set-server=utf8 port=3306 basedir="D:/mysql-5.7.30-winx64/" datad…

screen命令使用说明

有些程序写的很操蛋&#xff0c;比如放到后台执行&#xff0c;但后边还需要再切回前台来重新执行&#xff0c;这个时候我们选择screen工具&#xff1a; screen -d -m -S LoginServer[6001] ./run_login_server.sh 具体的screen命令包含哪些参数&#xff0c;可以参考scree…

看这玩意复习你还会挂科?《数据结构篇》

一&#xff0e;绪论 1.何谓程序设计&#xff1f; 程序 算法 数据结构 2.数据结构的定义 是相互之间存在一种或多种特定关系的数据元素的集合 3.数据、数据元素、数据对象的概念 数据&#xff08;data&#xff09;&#xff1a;对客观事物的符号表示&#xff0c;含义很广&am…