图像柱面投影

由于图像序列是实体景物在不同坐标系下的二维投影,直接对拍摄图像进行拼接无法满足视觉一致性,所以需要将待拼接的图像分别投影到一个标准的坐标系下,然后再进行图像的拼接。全景图生成系统可以采用圆柱体、立方体和球体等模型来实现。由于柱面坐标的变换比较简单并且投影图像与其投影到圆柱表面的位置无关,用其描述的柱面全景图像可在水平方向上满足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.csdn.net/wd1603926823/article/details/49334229
--------------------- 
作者:Young__Fan 
来源:CSDN 
原文:https://blog.csdn.net/Young__Fan/article/details/82952854 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值