图像柱面投影

版权声明:如需转载,请注明出处。 https://blog.csdn.net/Young__Fan/article/details/82952854

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

                                  

猜你喜欢

转载自blog.csdn.net/Young__Fan/article/details/82952854