图片柱面投影简单实现
转自: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;
/***********双线性插值算法***********/
}
}
}
}
完结。