图片柱面投影简单实现

转自:https://blog.csdn.net/u010551600/article/details/78461142

柱面投影是图片拼接的前期的一部分工作,以下代码只是简单的实现了投影,还可以优化,

柱面半径设置位图片宽度的一半,即 R = width/2

代码运算流程是 对于dst图片上的每一个像素点,通过公式计算出src上对应的位置(hnum,wnum),把src上这个位置的像素值赋值给dst。
 

#include <iostream>
#include <opencv2/opencv.hpp>
 
using namespace std;
using namespace cv;
 
/*
实现一个简单的图像投影实例,
算法根据csdn博客http://blog.csdn.net/weixinhum/article/details/50611750
该代码对目录下的test图片进行柱面投影
*/
int main()
{
	Mat src = imread("test.jpg");
	Mat dst(src.rows,src.cols,src.type());
 
	int width = src.cols, height = src.rows;
	double x, y;
	double R = width / 2;
	int drcpoint;
	for (int hnum = 0; hnum < height;hnum++)
	{
		for (int wnum = 0; wnum < width;wnum++)
		{
			double k = R / sqrt(R*R + (wnum - width / 2)*(wnum - width / 2));
			x = (wnum - width / 2) / k + width / 2;
			y = (hnum - height / 2) / k + height / 2;
			if (0 < x && x < width && 0 < y &&y < height)
			{
				dst.at<Vec3b>(hnum, wnum) = src.at<Vec3b>(int(y), int(x));
			}
		}
	}
	imshow("origin Image",src);
	imshow("柱面投影图",dst);
 
	waitKey(0);
	return 0;
}

运行结果:

算法思想参考:http://blog.csdn.net/weixinhum/article/details/50611750

图像的柱面投影算法,在360°环形全景应用中几乎一定会用到。而为何要用该算法,可以参考下图:


    从图像中可以看到,該环形全景设备由八个摄像头环形排列而成(需注意环形全景的形态并不固定,摄像头的个数不一定是八个,甚至只有一个摄像头在一直匀速转圈也是可以的)。每个摄像头所拍摄的画面为其前方的实线段区域,为了之后能进行图像的拼接,相邻摄像头之间必须要有图像的重合区域,如上图的红色线段部分(如果能保证刚刚好相接也可以,不过结构难度太高)。

    从不同摄像头的重合区域可以看到,由于摄像头的朝向不同,重合部分图像中的物体并不满足视觉一致性的要求,因此需要将图像进行投影,使其满足图像的一致性要求,为后面的拼接做准备(视觉一致性是全景应用最为关键的问题,无论是柱环形全景还是球形全景,都无法避免,只是所选的投影模型不同罢了)。在环形全景中,一般选择柱面投影算法,将图像分别投影到以 像素焦距+摄像头与圆心距离 为半径的圆柱上。投影后的图像为上图摄像头前方的圆弧。从圆弧上看,图像的重合部分已经满足视觉一致性的要求,可以做拼接。而如何去投影就是本文要介绍的。

    柱面投影的数学模型相对比较简单,把观测点定在圆柱体的中心,图像的像素焦距+摄像头与圆心距离 为圆柱体的半径,则摄像头所拍摄的图像与圆柱体相切。图像上的一点Q与观测点连线,该连线与圆柱面的交点点Q'为图像点Q在柱面上的投影,我们需要做的就是求出点Q(x,y)与点Q'(x',y')之间的换算关系。先看看下图:


    该图的实线部分为投影模型的俯视图,下方的线段a为待投影的图像,圆为圆柱切面,O为观测点。而虚线部分为Y轴方向上的辅助线。现在设A为图像上的任意一点,其坐标为(x,y,z),其中z=-R,则其在x-z坐标系上的投影A'的坐标为(x,-R)。B为我们要求的点A在圆柱面上的投影点,其坐标为(x',y',z'),则其在x-z坐标系上的投影点B'的坐标为(x',z')。

    由于△0BB'与△OAA'相似,△0B'F与△OA'G相似

    则有BB'=kAA',B'F=kA'G,OF=kR(k<1)

    则kx=x',ky=y'

    又OF²+B'F²=R²

    故k²R²+k²x²=R²

    

    又x'=kx,y'=ky

    故

    

    

    由于一般来说图像以左上角为坐标原点,而上面公式中的坐标系以图像的中心为坐标原点,所以在实际图像的计算中,上面的计算公式换为

    

    

    有了上面的公式,便可计算出图像的柱面投影结果。这里需要注意的是我们把x和y写在了等式的左边而x',y'写在了右边,这样做是为了方便我们后面进行插值计算。为什么要这么做可以看下面两张图片

 

    左边图像为待投影图像,右边为直接投影的结果,由于投影后的图像点坐标未必为整数,而图像的坐标需要为整数,所以必将造成误差。表现在右边图像上就是图像有很多显而易见的毛刺。而我们进行双线性插值之后的投影图像如下


    可以看到,其毛刺得到了一定的抑制。上面的图像还说明了经过柱面投影的图像会比原来的图像宽度小,具体小多少跟R有关,由于比较简单,在这里不再给出推导的过程。以上投影过程的主要代码如下
 

void DealWithImgData(BYTE *srcdata, BYTE *drcdata,int width,int height)//参数一为原图像的数据区首指针,参数二为投影后图像的数据区首指针,参数三为图像的宽,参数四为图像的高
{
	//双线性插值算法
	int i_original_img_hnum, i_original_img_wnum;//目标点坐标
	double distance_to_a_y, distance_to_a_x;//在原图像中与a点的水平距离  
	int original_point_a, original_point_b, original_point_c, original_point_d;
 
	int l_width = WIDTHBYTES(width* 24);//计算位图的实际宽度并确保它为4byte的倍数
	int drcpoint;
	double R = 1200;//像素距离
	double x, y;
	for (int hnum = 0; hnum < height; hnum++)
	{
		for (int wnum = 0; wnum < width; wnum++)
		{
			drcpoint = l_width*hnum + wnum * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点
			//柱面投影
			double k = R / sqrt(R*R + (wnum- width / 2) * (wnum - width / 2));
			x = (wnum - width / 2) / k + width / 2;
			y = (hnum - height / 2) / k + height / 2;
			if (x >= 0 && y >= 0 && x < width && y < height)
			{
				/***********双线性插值算法***********/
				i_original_img_hnum = y;
				i_original_img_wnum = x;
				distance_to_a_y = y - i_original_img_hnum;
				distance_to_a_x = x - i_original_img_wnum;//在原图像中与a点的垂直距离  
 
				original_point_a = i_original_img_hnum*l_width + i_original_img_wnum * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点A    
				original_point_b = original_point_a + 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点B  
				original_point_c = original_point_a + l_width;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点C   
				original_point_d = original_point_c + 3;//数组位置偏移量,对应于图像的各像素点RGB的起点,相当于点D  
 
				if (i_original_img_hnum == height - 1)
				{
					original_point_c = original_point_a;
					original_point_d = original_point_b;
				}
				if (i_original_img_wnum == width - 1)
				{
					original_point_a = original_point_b;
					original_point_c = original_point_d;
				}
 
				drcdata[drcpoint + 0] =
					srcdata[original_point_a + 0] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
					srcdata[original_point_b + 0] * distance_to_a_x*(1 - distance_to_a_y) +
					srcdata[original_point_c + 0] * distance_to_a_y*(1 - distance_to_a_x) +
					srcdata[original_point_c + 0] * distance_to_a_y*distance_to_a_x;
				drcdata[drcpoint + 1] =
					srcdata[original_point_a + 1] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
					srcdata[original_point_b + 1] * distance_to_a_x*(1 - distance_to_a_y) +
					srcdata[original_point_c + 1] * distance_to_a_y*(1 - distance_to_a_x) +
					srcdata[original_point_c + 1] * distance_to_a_y*distance_to_a_x;
				drcdata[drcpoint + 2] =
					srcdata[original_point_a + 2] * (1 - distance_to_a_x)*(1 - distance_to_a_y) +
					srcdata[original_point_b + 2] * distance_to_a_x*(1 - distance_to_a_y) +
					srcdata[original_point_c + 2] * distance_to_a_y*(1 - distance_to_a_x) +
					srcdata[original_point_c + 2] * distance_to_a_y*distance_to_a_x;
				/***********双线性插值算法***********/
			}
		}
	}
}

完结。