算法核心思想:横看成岭侧成峰,远近高低各不同!
author:崔星星
date:2024.9.24
https://ww2.mathworks.cn/matlabcentral/fileexchange/173310-360
本示例展示了360°全景图像在单位球面的纹理渲染平滑过渡算法,重点阐述了球体内外相机位置、方向发生突变的情况下,依旧能保证渲染图像可以平滑过渡转换,视觉上不会引起画面突变切换障碍。球体内外过渡在3种典型模式("fisheye","perspective","stereo")下主要表现为下图中的4种箭头转换关系,详细给出了算法原理和代码实践!
为了能够较顺滑的在透视和鱼眼两种视角平滑过渡渲染,数学上表现为单位球体内,外之间视角变换,不能简单的对各个变量进行线性插值(仅适用球体内的小行星和透视之间的模式),我特意画出如下成像示意图:
两种视角模式:
- 从球心
$O\left(0,0,0\right)$ 向目标点$A\left(1,0,0\right)$ 方向观看,黑色的单位球面纹理在归一化成像平面$\pi$ 上的投影为透视无畸变投影(Gnomonic projection) - 从点
$V\left(2,0,0\right)$ 向目标点$A\left(1,0,0\right)$ 方向观看,黑色的单位球面纹理在归一化成像平面$\pi$ 上的投影为鱼眼投影(Fisheye Projection)
要在上述两种模式之间使得观察图像平滑过渡,那么每个投影像素点坐标要连续变化,即不能突变,表现在投影成像平面
相应地,单位球体上弧
现问题变为:已知点
在
可求解
$$ \left\lbrace \begin{array}{ll} x= & l*\cos \left(\theta \right)\newline y= & \frac{y_0 l\cos \left(\theta \right)}{x_0 }\newline z= & \frac{z_0 l\cos \left(\theta \right)}{x_0 } \end{array}\right. $$
syms r l theta
equ = r.^2==(r-1).^2+l.^2-2*l*(r-1).*cos(pi-theta)
equ =
sol = solve(equ,l)
sol = $\displaystyle \left(\begin{array}{c} \cos \left(\theta \right)+\sqrt{r^2 ,{\cos \left(\theta \right)}^2 -2,r,{\cos \left(\theta \right)}^2 +2,r+{\cos \left(\theta \right)}^2 -1}-r,\cos \left(\theta \right)\newline \cos \left(\theta \right)-\sqrt{r^2 ,{\cos \left(\theta \right)}^2 -2,r,{\cos \left(\theta \right)}^2 +2,r+{\cos \left(\theta \right)}^2 -1}-r,\cos \left(\theta \right) \end{array}\right)$
从求解的结果sol看出有2个解,取其中一个有效解:
l = sol(1)
l =
带入最终公式,得到点
syms x_0 y_0 z_0
x = l*cos(theta)
x =
y = y_0*x/x_0
y =
z = z_0*x/x_0
z =
x = subs(x,theta,pi/6)
x =
vpa(limit(x,r,inf))
ans =
当
把上述推导出的点
%% 演示透视转鱼眼,光滑过渡效果!
[X,Y,Z] = sphere(50);
figure(Position=[20,20,800,800]);
ax = gca;
% mesh(ax,X,Y,Z)
hold(ax,"on");
ax.DataAspectRatio = [1,1,1];
axis(ax,"off");
%% 纹理
frame = imread("https://raw.githubusercontent.com/cuixing158/360-panorama-viewer-app/main/data/360panorama.jpg");
s = surf(ax,X,Y,Z,frame,'FaceColor','texture','EdgeColor','none');
ax.Projection="perspective";
ax.CameraPosition = [2,0,0];
ax.CameraViewAngle=60;
ax.CameraUpVector = [0,0,-1];
ax.CameraTarget = [1,0,0];
maskCoord = s.XData>cosd(180/2);
XData = s.XData(maskCoord);
YData = s.YData(maskCoord);
ZData = s.ZData(maskCoord);
theta = acosd(XData);
k = 0.01:0.05:1;
for r = 1./k
% 根据上述公式计算
x = cosd(theta).*(cosd(theta) + sqrt(r.^2.*cosd(theta).^2 - 2*r.*cosd(theta).^2 + 2*r + cosd(theta).^2 - 1) - r.*cosd(theta));
y = (YData.*x)./XData;
z = (ZData.*x)./XData;
s.XData(maskCoord) = x;
s.YData(maskCoord) = y;
s.ZData(maskCoord) = z;
drawnow;
end
上述代码演示了透视转鱼眼的顺滑效果,但当曲率
类似地,小行星和鱼眼之间的转换关系我画出如下成像示意图:
两种视角模式:
- 从点
$M\left(-1,0,0\right)$ 向目标点$A\left(1,0,0\right)$ 方向观看,黑色的单位球面纹理在归一化成像平面$\pi$ 上的投影为透视小行星投影(Stereographic projection) - 从点
$V\left(2,0,0\right)$ 向目标点$A\left(1,0,0\right)$ 方向观看,黑色的单位球面纹理在归一化成像平面$\pi$ 上的投影为鱼眼投影(Fisheye Projection)
要在上述两种模式之间使得观察图像平滑过渡,那么每个投影像素点坐标同样要连续变化,即不能突变,表现在投影成像平面
相应地,单位球体上弧
现问题变为:已知点
在
可求解
上述公式是假定球心
syms r l theta
equ = r.^2==(r-2).^2+l.^2-2*l*(r-2).*cos(pi-theta)
equ =
sol = solve(equ,l)
sol = $\displaystyle \left(\begin{array}{c} 2,\cos \left(\theta \right)-\sqrt{r^2 ,{\cos \left(\theta \right)}^2 -4,r,{\cos \left(\theta \right)}^2 +4,r+4,{\cos \left(\theta \right)}^2 -4}-r,\cos \left(\theta \right)\newline 2,\cos \left(\theta \right)+\sqrt{r^2 ,{\cos \left(\theta \right)}^2 -4,r,{\cos \left(\theta \right)}^2 +4,r+4,{\cos \left(\theta \right)}^2 -4}-r,\cos \left(\theta \right) \end{array}\right)$
从求解的结果sol看出有2个解,取有效的那个解:
l = sol(2)
l =
带入最终公式,得到点
syms x_0 y_0 z_0
x = l*cos(theta)-1
x =
y = y_0*(x+1)/(x_0+1)
y =
z = z_0*(x+1)/(x_0+1)
z =
x = subs(x,theta,pi/3 )
x =
limit(x,r,inf)
ans =
当给定
把上述推导出的点
%% 演示鱼眼转小行星,光滑过渡效果!
[X,Y,Z] = sphere(50);
fig = figure(Position=[20,20,800,800]);
ax = gca;
% mesh(ax,X,Y,Z)
hold(ax,"on");
ax.DataAspectRatio = [1,1,1];
axis(ax,"off")
littlePlanetFov = 150; % 小行星视场角,越大图像越夸张
fisheyeFov = 60; % 鱼眼摄像机观察单位球的视场角
% 纹理和设置相机参数
frame = imread("https://raw.githubusercontent.com/cuixing158/360-panorama-viewer-app/main/data/360panorama.jpg");
s = surf(ax,X,Y,Z,frame,'FaceColor','texture','EdgeColor','none');
ax.Projection="perspective";
ax.CameraPosition = [2,0,0];
ax.CameraUpVector = [0,0,-1];
ax.CameraTarget = [1,0,0];
ax.CameraViewAngle = littlePlanetFov;
% 旋转一定角度,让其有代表性的小行星图
direction = [0,1,0];
rotate(s,direction,90);% 正90度对应地面,-90度对应天空
% 对x正半轴的坐标点做变换即可
maskCoord = s.XData>cosd(360/2);
XData = s.XData(maskCoord);
YData = s.YData(maskCoord);
ZData = s.ZData(maskCoord);
norma = sqrt((XData+1).^2+YData.^2+ZData.^2);
normb = 1;
theta = acosd((XData+1)./(norma*normb));
k = 1:-0.02:0.01;% 曲率变化范围
fovQuerys = interp1([k(1),k(end)],[fisheyeFov,littlePlanetFov],k);
idx = 1;
for r = 1./k
% 根据上述公式计算
x = cosd(theta).*(2*cosd(theta) + sqrt(r.^2.*cosd(theta).^2 - 4*r.*cosd(theta).^2 + 4*r + 4*cosd(theta).^2 - 4) - r.*cosd(theta)) - 1;
y = (YData.*(x+1))./(XData+1);
z = (ZData.*(x+1))./(XData+1);
s.XData(maskCoord) = x;
s.YData(maskCoord) = y;
s.ZData(maskCoord) = z;
ax.CameraViewAngle = fovQuerys(idx);
drawnow;
idx = idx+1;
end
上面动画在小行星视图变化太快,鱼眼视图比较缓慢,怎么做到“快进慢出”?下面对曲率
k = logspace(0,-2,50);% 对数变化
fovQuerys = interp1([k(1),k(end)],[fisheyeFov,littlePlanetFov],k);
idx = 1;
for r = 1./k
% 根据上述公式计算
x = cosd(theta).*(2*cosd(theta) + sqrt(r.^2.*cosd(theta).^2 - 4*r.*cosd(theta).^2 + 4*r + 4*cosd(theta).^2 - 4) - r.*cosd(theta)) - 1;
y = (YData.*(x+1))./(XData+1);
z = (ZData.*(x+1))./(XData+1);
s.XData(maskCoord) = x;
s.YData(maskCoord) = y;
s.ZData(maskCoord) = z;
ax.CameraViewAngle = fovQuerys(idx);
drawnow;
idx = idx+1;
end
同样,上述代码演示了鱼眼转小行星的顺滑效果,但当曲率