图像柱面投影

由于图像序列是实体景物在不同坐标系下的二维投影,直接对拍摄图像进行拼接无法满足视觉一致性,所以需要将待拼接的图像分别投影到一个标准的坐标系下,然后再进行图像的拼接。全景图生成系统可以采用圆柱体、立方体和球体等模型来实现。由于柱面坐标的变换比较简单并且投影图像与其投影到圆柱表面的位置无关,用其描述的柱面全景图像可在水平方向上满足360度环视,具有较好的视觉效果,因此被广泛采用。

 

原理:
把平面图像投影到圆柱的曲面上。

如下图,四边形GHEF表示待处理原图,投影之后,变成曲面JDILCK(黄色点标注)

                                             

                                                         图片来源:https://www.cnblogs.com/cheermyang/p/5431170.html

俯视图如下,DCE为待处理图像平面,FCG为投影所得曲面。

                          

                                                   图片来源:https://www.cnblogs.com/cheermyang/p/5431170.html

     设原图像宽W,高H,角度FOG为相机视场角度α(一般为45°,即PI/4),圆形半径(焦距)f 有tan 1/2α = W / (2 * f), 则有f = W / (2 * tan(α/2))。

    依次推算出,目标图像的宽(曲线FCG长)W‘ = f * α, 目标图像高H’不变, H‘ = H

 

方式一:以图像左上角坐标为原点

                                      

                                             图片来源:https://www.cnblogs.com/cheermyang/p/5431170.html

公式如下:

                                                                      

 

方式二:以图像中心为坐标原点,即(W/2, H/2),可以简计算公式

                                          

                                               图片来源:https://www.cnblogs.com/cheermyang/p/5431170.html

由于一般来说图像以左上角为坐标原点,所以在编写程序的时候最好采用以方式一。

下面使用OpenCV进行代码实现
1.直接根据方式一进行代码实现
/**
 *Copyright (c) 2018 Young Fan.All Right Reserved.
 *Author: Young Fan
 *Date: 2018.9.30
 *OpenCV version: 3.4.3
 *IDE: Visual Studio 2017
 *Description:
 */
 
#include <iostream>
#include "opencv2/highgui/highgui.hpp"
 
using namespace cv;
using namespace std;
 
int main()
{
    Mat srcImage = imread("lena.jpg");
    Mat dstImage = Mat::zeros(srcImage.size(), CV_8UC3);
 
    int height = srcImage.rows; //原图像的高(即原图像矩阵行数)
    int width = srcImage.cols; //原图像的宽(即原图像矩阵列数)
    int centerX = width / 2; //图像中心横坐标
    int centerY = height / 2; //图像中心纵坐标
    double alpha = CV_PI / 4; //相机视角角度
    double f = width / (2 * tan(alpha / 2)); //焦距(圆的半径)
 
    //循环遍历
    for (int i = 0; i < srcImage.rows; i++)
    {
        for (int j = 0; j < srcImage.cols; j++)
        {
            //注意图像坐标与像素矩阵坐标的区别
            float theta = atan((j - centerX) / f);
            int pointX = f * alpha / 2 + f * theta; //用f * alpha / 2,两边缝隙会不均匀(只有右边有黑缝隙),用width / 2就均匀了
            int pointY = f * (i - centerY) / sqrt((j - centerX) * (j - centerX) + f * f) + centerY;
 
            //像素赋值
            dstImage.at<Vec3b>(pointY, pointX)[0] = srcImage.at<Vec3b>(i, j)[0];
            dstImage.at<Vec3b>(pointY, pointX)[1] = srcImage.at<Vec3b>(i, j)[1];
            dstImage.at<Vec3b>(pointY, pointX)[2] = srcImage.at<Vec3b>(i, j)[2];
        }
    }
 
    imshow("原图", srcImage);
    imshow("柱面投影效果图", dstImage);
 
    waitKey();
 
    return 0;
}
效果图:

如下图所示,效果图左右的黑色缝隙不对称。

首先说明一下,为啥会在上下左右出现黑色空隙?

     因为一张平面图像在进行柱面投影的时候,原图被转化为柱面图的形式显示在与原来尺寸一样的黑色背景图上,柱面投影效果图上有一部分像素点无法找到与原图与之对应坐标变换的点,所以会出现默认颜色为黑色的缝隙。按照直观的说法就是,原图转为柱面图像时在平面上被缩小了一些。

 那么为啥,只有右边有缝隙,而左边没有呢?

     这是因为推算的坐标变换公式所求出的柱面投影坐标,是在柱面上建立的坐标系,但是我们在代码实现并显示的时候,用的是一张平面图来呈现这个柱面图,也就是侧视图,所以要想得到左右对称图像,要在代码35行求横坐标的时候,将图像宽度要以原图的宽度为准,这样在显示的时候可以使左右缝隙更加对称。

                                       

 

2.改进代码,是左右缝隙对称
只用改进35行的代码就行,将“f * alpha / 2”改为“width / 2”,这样在显示的时候可以使左右缝隙更加对称。

/**
 *Copyright (c) 2018 Young Fan.All Right Reserved.
 *Author: Young Fan
 *Date: 2018.9.30
 *OpenCV version: 3.4.3
 *IDE: Visual Studio 2017
 *Description:
 */
 
#include <iostream>
#include "opencv2/highgui/highgui.hpp"
 
using namespace cv;
using namespace std;
 
int main()
{
    Mat srcImage = imread("lena.jpg");
    Mat dstImage = Mat::zeros(srcImage.size(), CV_8UC3);
 
    int height = srcImage.rows; //原图像的高(即原图像矩阵行数)
    int width = srcImage.cols; //原图像的宽(即原图像矩阵列数)
    int centerX = width / 2; //图像中心横坐标
    int centerY = height / 2; //图像中心纵坐标
    double alpha = CV_PI / 4; //相机视角角度
    double f = width / (2 * tan(alpha / 2)); //焦距(圆的半径)
 
    //循环遍历
    for (int i = 0; i < srcImage.rows; i++)
    {
        for (int j = 0; j < srcImage.cols; j++)
        {
            //注意图像坐标与像素矩阵坐标的区别
            float theta = atan((j - centerX) / f);
            int pointX = width / 2 + f * theta; //注意这里用width / 2,用f * alpha两边缝隙不均匀(只有右边有黑缝隙)
            int pointY = f * (i - centerY) / sqrt((j - centerX) * (j - centerX) + f * f) + centerY;
 
            //像素赋值
            dstImage.at<Vec3b>(pointY, pointX)[0] = srcImage.at<Vec3b>(i, j)[0];
            dstImage.at<Vec3b>(pointY, pointX)[1] = srcImage.at<Vec3b>(i, j)[1];
            dstImage.at<Vec3b>(pointY, pointX)[2] = srcImage.at<Vec3b>(i, j)[2];
        }
    }
 
    imshow("原图", srcImage);
    imshow("柱面投影效果图", dstImage);
 
    waitKey();
 
    return 0;
}
效果图:

                                      

3.如果你不想要出现黑色缝隙,可以把左右黑色缝隙去除
          需要定义合适尺寸的目标图,然后就是目标图的列数(图像的横坐标)要左移,具体见下面代码。

/**
 *Copyright (c) 2018 Young Fan.All Right Reserved.
 *Author: Young Fan
 *Date: 2018.10.6
 *OpenCV version: 3.4.3
 *IDE: Visual Studio 2017
 *Description:
 */
 
#include <iostream>
#include "opencv2/highgui/highgui.hpp"
 
using namespace cv;
using namespace std;
 
int main()
{
    Mat srcImage = imread("lena.jpg");
 
    int height = srcImage.rows; //原图像的高(即原图像矩阵行数)
    int width = srcImage.cols; //原图像的宽(即原图像矩阵列数)
    int centerX = width / 2; //图像中心横坐标
    int centerY = height / 2; //图像中心纵坐标
    double alpha = CV_PI / 4; //相机视角角度
    double f = width / (2 * tan(alpha / 2)); //焦距(圆的半径)
 
    //求左右黑色缝隙宽度
    int len = cvRound(width / 2 - f * alpha / 2); //cvRound:取整
 
    //定义合适的目标图
    Mat dstImage = Mat::zeros(srcImage.rows, width - 2 * len, CV_8UC3); //注意尺寸
 
    //循环遍历
    for (int i = 0; i < srcImage.rows; i++)
    {
        for (int j = 0; j < srcImage.cols; j++)
        {
            //注意图像坐标与像素矩阵坐标的区别
            float theta = atan((j - centerX) / f);
            int pointX = cvRound(width / 2 + f * theta); //注意这里用width / 2,用f * alpha / 2,两边缝隙会不均匀(只有右边有黑缝隙)
            int pointY = cvRound(f * (i - centerY) / sqrt((j - centerX) * (j - centerX) + f * f) + centerY);
 
            //像素赋值,此时要将列数(图像横坐标)往左移,与初始的黑色图像边缘对其,即pointX - len
            dstImage.at<Vec3b>(pointY, pointX - len)[0] = srcImage.at<Vec3b>(i, j)[0];
            dstImage.at<Vec3b>(pointY, pointX - len)[1] = srcImage.at<Vec3b>(i, j)[1];
            dstImage.at<Vec3b>(pointY, pointX - len)[2] = srcImage.at<Vec3b>(i, j)[2];
        }
    }
 
    imshow("原图", srcImage);
    imshow("柱面投影效果图", dstImage);
 
    waitKey();
 
    return 0;
}
去除黑色缝隙效果图:

                                    

参考博客:

https://www.cnblogs.com/cheermyang/p/5431170.html

https://blog.youkuaiyun.com/wd1603926823/article/details/49334229
--------------------- 
作者:Young__Fan 
来源:优快云 
原文:https://blog.youkuaiyun.com/Young__Fan/article/details/82952854 
版权声明:本文为博主原创文章,转载请附上博文链接!

全景图像拼接是将多幅有重叠区域的图像拼接成全视角360o的平面图像。全景图像拼接技术是一种基于图像绘制技术、图像处理及计算机几何学等多领域的综合技术,由于对硬件要求低,真实感较好等优点,是目前虚拟现实、三维重建等的重点研究方向。鱼眼图像是通过在相机上安装超广视角的鱼眼镜头所拍摄,具有视域广,一次成像为非线性图像的特点,比普通视觉图像拼接所需的原图像少,效率高,但拼接难度大。 本文介绍了鱼眼图像拼接技术的研究背景、应用领域。重点研究了图像拼接技术中的鱼眼桶形畸变校正、投影变换理论、SIFT匹配以及图像融合等技术,对比分析了它们的理论基础、实现方式、运算性能以及还有的不足。 鱼眼图像形变严重,首先需要校正为符合人类视觉的线性图像。针对传统的经纬度校正法还存在一定拱形失真的问题,本文提出了渐进方程校正法,此方法不考虑用镜头参数来构建复杂的投影模型,完成鱼眼图像的校正,单帧图像视觉效果较好且更利于拼接;针对多帧图像的拼接问题,考虑到鱼眼图像的特点,只截取每帧图像的部分区域进行定位匹配,在此区域进行SIFT特征的全景图像拼接,运算量比全局SIFT算法大幅减少,耗时相应降低;为生成实际景物连贯的全景图像,本文采用柱面投影变换,为了解决投影变换后图像产生阶梯现象,文中对比了几种常用算法,并采用双线性插值算法有效解决该问题;在图像融合过程中,分析比较了几种经典算法,选择渐入渐出融合算法对待拼接的图像进行处理,使得拼接后图像重叠区域过渡平滑,基本实现无缝拼接,在图像处理的速度和效果达到一定的均衡。 最后,为了便于全景图像的理解和观察,本文还设计了全景柱形环绕浏览人机交互界面,实现了垂直180o,水平360o空间的动态环绕视觉观察。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值