我目前正在实现一个模拟,其中需要使用相当于 Blenders 'PANO' 相机和 等距相机模型 将 3D 点转换为鱼眼图像。有谁知道我在哪里可以找到 Blenders 实现 3D->2D 转换的来源(科学论文,甚至技术报告)?
详细来说,我正在寻找:
为什么会问这个问题?虽然透视相机的实现在 bpy_extras 模块中本身可用,但等距相机模型的转换则不然。
我生成的代码如下。目前图像的结果与搅拌机的结果不同,有什么建议吗?
main.py
:
#!/usr/bin/env python3
import numpy as np
np.set_printoptions(precision=4, suppress=True)
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import plotting
def compute_R(alpha_deg, beta_deg, gamma_deg):
## Rotation Matrix R (code from CV2 exercise)
# convert into radians...
alpha = alpha_deg * np.pi / 180
beta = beta_deg * np.pi / 180
gamma = gamma_deg * np.pi / 180
# prepare sine and cosine values
ca = np.cos(alpha)
sa = np.sin(alpha)
cb = np.cos(beta)
sb = np.sin(beta)
cg = np.cos(gamma)
sg = np.sin(gamma)
# rotation about x axis
R_x = np.array([
[1, 0, 0],
[0, ca, -sa],
[0, sa, ca],
], dtype=float)
# rotation about y axis
R_y = np.array([
[ cb, 0, sb],
[ 0, 1, 0],
[-sb, 0, cb]
], dtype=float)
# rotation about z axis
R_z = np.array([
[cg, -sg, 0],
[sg, cg, 0],
[ 0, 0, 1]
], dtype=float)
# compose R_xyz
R = R_z.dot(R_y.dot(R_x))
return R
def cart2spherical_wiki(X_cam):
## from https://en.wikipedia.org/wiki/Spherical_coordinate_system
r = np.sqrt(X_cam[0]**2 + X_cam[1]**2 + X_cam[2]**2)
print(f'manual_{r=}')
#r = np.linalg.norm(X_cam, axis=0)
#print(f'np.linalg_{r=}')
theta = np.arccos(X_cam[2] / r)
phi = -np.arctan2(X_cam[1], X_cam[0])
return theta, phi
def cart2spherical_blender(X_cam, fov):
# can be ignored for the moment
r = np.arctan2(np.sqrt(X_cam[1] * X_cam[1] + X_cam[2] * X_cam[2]), X_cam[0]) / fov
phi = np.arctan2(X_cam[2], X_cam[1])
return r, phi
def spherical2normimg(f_x, f_y, theta, phi):
# from phd-thesis michel
x_img_norm = f_x * theta * np.cos(phi)
y_img_norm = f_y * theta * np.sin(phi)
#print(x_img_norm)
#print(y_img_norm)
return x_img_norm, y_img_norm
def spherical2normimg_blender(r, phi):
# can be ignored for the moment
x_img_norm = r * np.cos(phi) + 0.5
y_img_norm = r * np.sin(phi) + 0.5
return x_img_norm, y_img_norm
def points_to_image(x, y, resolution):
# Sensorpunkt (0,0) liegt im Bild an Position (w/2, h/2)
# deshalb Addition der halben Bildauflösung
# (x,y) - Sensorpunkte
# (x_new, y_new) - Bildpunkte
x_new = x + resolution[0]//2
y_new = y + resolution[1]//2
return x_new, y_new
def main():
# plot input/output?
plot = 'True'
## World points
X_wrld = np.array([
[1.5, 5, 1], # right foot
[2.5, 5, 1], # left foot
[2, 5, 3], # hip
[1, 5, 5], # right hand
[3, 5, 5] # left hand
])
## Camera
C = np.array([2, 1, 4]).transpose() # camera projection center in world coordinates
## Plot World with 3D Points and Camera
fig = plt.figure()
if plot == 'True':
title = 'World coordinate system: 3D Points and Camera'
plt1 = plotting.plot_world(X_wrld, C, elev=0, azim=-90, roll=0, title=title, fig=fig)
# transpose X_wrld
X_wrld = X_wrld.T # take the transpose
# add ones for homogenious representation
row_ones = np.ones([1, X_wrld.shape[1]])
X_wrld_hom = np.concatenate([X_wrld, row_ones], axis=0)
#print(f'{X_wrld_hom=}')
## Use Rotation-Matrix and C for creating H-Matrix (Homography, 4x4)
# orientation / rotation
alpha_deg = 0 # first: rotate about alpha degrees around the camera's x-axis
beta_deg = 270 # second: rotate about beta degrees around the camera's y-axis
gamma_deg = 0 # third: rotate about gamma degrees around the camera's z-axis
# Compute 3x3 Rotation Matrix
R = compute_R(alpha_deg, beta_deg, gamma_deg)
#print(f'{R=}')
# Build H-Matrix (4x4)
# RC ist 3x1
RC = np.zeros([3,1])
RC = R.dot(C)
RC = RC.T
H = np.column_stack( (R, -RC) )
H = np.row_stack( (H, [0, 0, 0, 1]) )
#print(f'{H=}')
# Transformation from world to camera coordinates
X_cam = H.dot(X_wrld_hom)
print(f'{X_cam=}')
if plot == 'True':
title = f'camera coordinate system: camera rotation of {alpha_deg=}, {beta_deg=}, {gamma_deg=}'
#plt2 = plotting.plot_camera(X_cam=X_cam.T, C=[0, 0, 0], elev=180, azim=90, roll=90, title=title, fig=fig)
plt2 = plotting.plot_camera(X_cam=X_cam.T, C=[0, 0, 0], elev=alpha_deg, azim=beta_deg, roll=gamma_deg, title=title, fig=fig)
### Intrinsics ###
# focal length from Canon FD 1:5,6 / 7,5mm (see, e.g. https://de.wikipedia.org/wiki/Fischaugenobjektiv)
f_mm = 0.0075 #7.5mm
# field of view
fov = 180 * np.pi / 180
# Vollformat Sensor, e.g. https://de.wikipedia.org/wiki/Sony_Alpha_1
chip_w = 35.7 * 1e-3 # 36mm
chip_h = 23.8 * 1e-3 # 24mm
# Resolution of camera
res_w = 8640 # px
res_h = 5760 # px
# pixel densities
m_x = res_w / chip_w # px/mm
m_y = res_h / chip_h # px/mm
# focal length normalized with pixel densities
f_x = f_mm * m_x # unitless
f_y = f_mm * m_y # unitless, f_x != f_y if pixel on the sensor is not square
### Intrinsics End ###
## From Camera coordinates (carthesian) to spherical camera coordinates (a) Wiki
theta, phi = cart2spherical_wiki(X_cam)
## From spherical camera coordinates to normalized image coordinates (a) Wiki
x_img_norm, y_img_norm = spherical2normimg(f_x, f_y, theta, phi)
# camera to spherical from blender implementation
#r, phi = cart2spherical_blender(X_cam, fov)
# spherical to normalized image coordinates from blender code
#x_img_norm, y_img_norm = spherical2normimg_blender(r, phi)
if plot == 'True':
title = 'Sensor coordinate system: Camera points on sensor plane'
plt3 = plotting.plot_sensor(x_img_norm=x_img_norm, y_img_norm=y_img_norm, title=title, fig=fig)
# From normalized image coordinates to image coordinates by shifting to left top of sensor
x_img, y_img = points_to_image(x_img_norm, y_img_norm, (res_w, res_h))
if plot == 'True':
title = 'Image coordinate system: points on image plane shifted to left top corner'
plotting.plot_image(x_img, y_img, (0, res_w), (0, res_h), title, fig)
plt.show()
if __name__ == "__main__":
main()
plotting.py
:
#!/usr/bin/env python3
import numpy as np
np.set_printoptions(precision=4, suppress=True)
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
def plot_world(X_wrld, C, elev, azim, roll, title, fig):
plt1 = fig.add_subplot(2, 2, 1, projection='3d')
colors = ['r', 'b', 'g', 'm', 'y']
labels = ['right foot', 'left foot', 'hip', 'right hand', 'left hand']
for X,Y,Z,c,l in zip(X_wrld[:,0], X_wrld[:,1], X_wrld[:,2], colors, labels):
plt1.plot3D(X, Y, Z, c=c, marker=".", linestyle='None', label=l)
plt1.plot3D(C[0], C[1], C[2], c="red", marker="v", linestyle='None', label="camera")
plt1.set(xlabel='X', ylabel='Y', zlabel='Z')
plt1.set_xlim(0, 4)
plt1.set_ylim(0, 6)
plt1.set_zlim(0, 6)
plt1.legend()
plt1.set_title(title)
plt1.view_init(elev=elev, azim=azim, roll=roll, vertical_axis='z')
return plt1
def plot_camera(X_cam, C, elev, azim, roll, title, fig):
plt2 = fig.add_subplot(2, 2, 2, projection='3d')
colors = ['r', 'b', 'g', 'm', 'y']
labels = ['right foot', 'left foot', 'hip', 'right hand', 'left hand']
for X,Y,Z,c,l in zip(X_cam[:,0], X_cam[:,1], X_cam[:,2], colors, labels):
plt2.plot3D(X, Y, Z, c=c, marker=".", linestyle='None', label=l)
plt2.plot3D(C[0], C[1], C[2], c="red", marker="v", linestyle='None', label="camera")
plt2.set(xlabel='X', ylabel='Y', zlabel='Z')
plt2.legend()
plt2.set_title(title)
plt2.view_init(elev=elev, azim=azim, roll=roll, vertical_axis='z')
return plt2
def plot_sensor(x_img_norm, y_img_norm, title, fig):
plt3 = fig.add_subplot(2, 2, 3)
colors = ['r', 'b', 'g', 'm', 'y']
labels = ['right foot', 'left foot', 'hip', 'right hand', 'left hand']
for x, y, c, l in zip(x_img_norm, y_img_norm, colors, labels):
plt3.plot(x, y, c=c, marker=".", linestyle='None', label=l)
plt3.set_title(title)
return plt3
def plot_image(x_img_norm, y_img_norm, limit_x, limit_y, title, fig):
plt4 = fig.add_subplot(2, 2, 4)
colors = ['r', 'b', 'g', 'm', 'y']
labels = ['right foot', 'left foot', 'hip', 'right hand', 'left hand']
for x, y, c, l in zip(x_img_norm, y_img_norm, colors, labels):
plt4.plot(x, y, marker='.', label= l, linestyle='None', c=c)
plt4.set(xlim=limit_x, ylim=limit_y)
plt4.set_title(title)
plt4.legend()
return plt4
我尝试模拟相同的五个点并使用等距相机渲染混合文件,您可以在以下帖子中下载: [1] https://blender.stackexchange.com/q/280271/158481
我尝试了不同的旋转组合(R_x、R_y、R_z),请参阅下面的脚本。我将搅拌机渲染输出的结果与模拟的输出进行了比较,其中差异在于图像坐标系中垂直轴上的反射。
从世界坐标映射到相机坐标:
camera_obj = bpy.context.scene.camera
world_coords = ...
camera_coords = camera_obj.matrix_world.inverted() @ world_coords
受到 Cycles 源代码的启发(
https://github.com/blender/blender/blob/main/intern/cycles/kernel/camera/projection.h中的
fisheye_to_direction()
和direction_to_fisheye()
),我们可以映射从相机空间到最终图像坐标:
camera = camera_obj.data
...
if camera.type == 'PANO':
v = camera_coords
a = v.x / v.z
b = v.y / v.z
r = math.atan(math.sqrt(a**2 + b**2)) / camera.cycles.fisheye_fov / 0.5
phi = math.atan2(v.y, v.x)
x = r * math.cos(phi)
y = r * math.sin(phi)
# x and y are OpenGL style normalized device coordinates (-1,-1 is bottom left, 1,1 is top right)
pixel_x = (0.5*v.x + 0.5) * scene.render.resolution_x
pixel_y = (1 - (0.5*v.y + 0.5)) * scene.render.resolution_y
...