张春成
V2
2022/08/31阅读:34主题:默认主题
鱼眼看世界
鱼眼看世界
很好奇鱼眼镜头里的“扭曲”是如何生成的,于是就有了这个模拟计算。
鱼眼镜头
偶然看到了这个搞全景相机的公司,Instra360。
全景相机,运动相机 - 影石Insta360官网,360度全景运动相机
它勾起了我一直以来的好奇,那就是全景相机的图像为什么是以这样的方式扭曲的?或者更具体一点的问题是,线性变换为什么会造成类似球面的扭曲?

我们首先假设真实的物理场景是一个简单的幕布,幕布用方格的方式进行染色
其中,下标代表求模数
经过颜色映射,即可以得到周期性方格的纹理图样。我们进一步假设这个图样在整个空间中构成一个平面,该平面与视线垂直。

接下来,我们假设有一个鱼眼镜头,那么在它的视野中,每个像素的位置可以用一对角度表示
其中,两个代数分别代表视线与水平和垂直轴之间的夹角,你可以想象成眼睛左右扫视和上下运动时视线的变化,由于眼睛始终在前方,因此它的视野范围不会超过180度。也因此,视线与平面的交点可以表示为三角函数的形式
在映射完成后,只要将位置代入到纹理方程,就可以计算得到该视线角度的颜色值,绘制如下。可以看到,虽然每条线都仍然是直的,但对角线的部分已经能看出扭曲

接下来,我将纹理稍微换个方向,就可以便这个现象更明显一些


代码
以下是实现计算和绘图的代码
import numpy as np
import plotly.express as px
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
# Picture size
width = 400
height = 300
# Cycle length of the texture
length = 50
# Distance between fish eye and scene
distance = 100
# Generate and draw the texture
mat = np.zeros((height, width))
for x in tqdm(range(width)):
for y in range(height):
v = max(x % length, y % length)
# Texture 2 in 45 degrees
# v = (x + y) % length
mat[y, x] = v
fig = px.imshow(mat, title='Texture')
fig.write_image('texture.jpg')
fig.show()
# Generate and draw the fish-eye view
y_angles = np.linspace(-np.pi/2, np.pi/2, height)
x_angles = np.linspace(-np.pi/2, np.pi/2, width)
fish_mat = np.zeros((height, width))
for yj, ya in tqdm(enumerate(y_angles)):
y = distance * np.tan(ya)
for xj, xa in enumerate(x_angles):
x = distance * np.tan(xa)
v = max(x % length, y % length)
# Texture 2 in 45 degrees
# v = (x + y) % length
fish_mat[yj, xj] = v
fig = px.imshow(fish_mat, title='Texture in fish-eye')
fig.write_image('texture-in-fish-eye.jpg')
fig.show()
作者介绍
张春成
V2