C/C++ 图像处理(7)------图像の球面贴合算法

    关于图像的球面贴合,是全景应用中比较常见的技术,而现有的一些资源大多不太好,比较晦涩。在经过一段时间的摸索之后,发现了这个博客写的相对可以,本文的实现也将其作为重要的参考,如果看过本文之后有什么不明白或者觉得不好的地方可以去看看。

    在展开本文之前,先来看看下面的两张图片:

C/C++ 图像处理(7)------图像の球面贴合算法_第1张图片   C/C++ 图像处理(7)------图像の球面贴合算法_第2张图片

    左边的图像被贴合到球面上后,其正视图为右边的图像。而我们要研究的是,如何去贴合的过程。

    可以想象,左边的图像是一张极薄的纱,将其蒙到一个大小正好的球面上(薄纱过中点的横轴正好覆盖球赤道的半周),然后正看过去会怎么样。我们将看到一个接近于上面右边图像的图像。为什么说接近而不说相同呢,因为大小是不一样的。左边图像的赤道如果刚刚好覆盖掉球的半个平面,则球的周长将为左边图像的宽度的两倍。假定左边图像的宽度为W,而球的半径为R,则有

    πR=W

    所以球的正视图的原直径应该为

    2R=2*W/π<W

    即球的正视图不会触碰到图像边缘,而为了我们看起来舒服一点,我们希望球的正视图刚刚好就触碰到图像边缘,是故球做完贴合之后其正视图需要做一个等比例的放大,该放大系数为π/2。经过放大,我们就得到了右边的图像。之后,我们把放大系数设为k(注意这里的k值跟图像贴合的球体半径有关)。

    下面我们正式进入图像贴合的研究,还是遵循上面的设想,把左边图像设想成一张极薄的纱,然后将其蒙到一个大小正好的球面上。那么,左边图像的中点将是右边图像的中点,也就是球正面顶点的位置。

    接着,我们将图像的中心点记为点0,然后在左图像上随便取一个点即为点A,假设点A是落在过中点的横轴上的,很容易想象经过贴合后A也将落在右图过中心点的赤道上。进一步想,左图绕中心旋转一个角度之后,原来不在过中点的横轴上的点可能变为其上的点,相应的贴合后的点也会落在右图过中心点的赤道上。也就是说,图像上的任意一点经过贴合之后,将落在原图与中心点连线的线上。接着我们沿着连线切下将得到下面的切面:

C/C++ 图像处理(7)------图像の球面贴合算法_第3张图片

    这便是这篇文章推导的关键,其中最为关键的是弧长等于OA*k这句话(由上面的薄纱模型很容易得出该弧为左图中的OA经过弯曲而来,长度自然相同,而*k是因为图像经过了放大),有了上面这些条件,我们可以列出以下这些公式:


C/C++ 图像处理(7)------图像の球面贴合算法_第4张图片

    推导到最后我们可以看到原图像坐标(X,Y)和贴合后图像坐标(X',Y')的换算关系,其跟上面提到的博客最大不同的地方是将X,Y写在左边而X',Y'写在右边。在本人看来,这样才是合理的,因为后面我们需要去原图像找到对应的像素点取像素值,而映射后图像遍历时坐标是知道的,应该为已知条件。详细的可参考我之前写过的 C/C++ BMP(24位真彩色)图像处理(3)------图像の放大缩小(双线性插值)。

    OK,到这里推导过程就全部结束了,按照上面的公式便可完成图像的映射。其主要的代码如下:

void DealWithImgData(BYTE *srcdata, BYTE *drcdata,int width,int height)//参数一为原图像的数据区首指针,参数二为贴合后图像的数据区首指针,参数三为图像的宽,参数四为图像的高
{
	int l_width = WIDTHBYTES(width* 24);//计算位图的实际宽度并确保它为4byte的倍数 
	double radius1 = height /2;//贴合球面半径
	double radius2 = radius1*radius1;//半价的平方
	double x1, y1;//目标在球正视图中的坐标位置
	double x, y;//目标在球正视图中对应原图的坐标位置
	double middle2 = 2 * radius1 / 3.1416;//计算过程式子
	double matan;//目标与圆心连线与x轴的夹角
	int pixel_point;//遍历图像指针
	int pixel_point_row;//遍历图像行首指针
	double oa;//点对应弧长度
	
	//双线性插值算法相关变量
	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;
 
	for (int hnum = 0; hnum < height; hnum++)
	{
		pixel_point_row = hnum*l_width;
		for (int wnum = 0; wnum < width; wnum++)
		{
			if ((hnum - height / 2)*(hnum - height / 2) + (wnum - width / 2)*(wnum - width / 2) < radius2)//在球体视场内才处理
			{
				pixel_point = pixel_point_row + wnum * 3;//数组位置偏移量,对应于图像的各像素点RGB的起点
				/***********球面贴合***********/
				x1 = wnum - width / 2;
				y1 = height / 2 - hnum;

				if (x1 != 0)
				{
					oa = middle2*asin(sqrt(y1*y1 + x1*x1) / radius1);//这里在确定图像大小的情况下可以用查表法来完成,这样会大大的提高其效率
					matan = atan2(y1, x1);
					x = cos(matan)*oa;
					y = sin(matan)*oa;
				}
				else
				{
					y = asin(y1 / radius1)*middle2;
					x = 0;
				}
				/***********球面贴合***********/

				/***********双线性插值算法***********/
				i_original_img_hnum = (height / 2 - y);
				i_original_img_wnum = (x + width / 2);
				distance_to_a_y = (height / 2 - y) - i_original_img_hnum;
				distance_to_a_x = (x + width / 2) - 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 (hnum == height - 1)
				{
					original_point_c = original_point_a;
					original_point_d = original_point_b;
				}
				if (wnum == width - 1)
				{
					original_point_a = original_point_b;
					original_point_c = original_point_d;
				}

				drcdata[pixel_point + 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[pixel_point + 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[pixel_point + 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;
				/***********双线性插值算法***********/
			}
		}
	}
}

    经过本人的测试,如果每次直接这样算效率是比较低的,所以后来本人改由查表法来完成上面的工作,这份工程和可执行程序都已经打包在一起上传了(由于是X64编译的,需要电脑是64位操作系统才可以运行,如果是32位的则可通过修改工程解决,工程利用OpenCV进行解码,需要自行配置,否则无法运行),如果有兴趣的可以去下载。

    下载地址在这

    下面放出一张处理结果图

C/C++ 图像处理(7)------图像の球面贴合算法_第5张图片





你可能感兴趣的:(C/C++ 图像处理(7)------图像の球面贴合算法)