Blender cameras convention

I would like to create cameras looking at origin uniformly distributed in a sphere. I got most of it right...but the cameras are looking away from the origin. I know I can get it right by a certain combination of rotations, but I want to be sure that what I am doing is 100% correct. Can someone familiar with this help me point out what's the right way to do it?

By changing the lookat slightly in the "wrong" way I can get what is expected result. However, what I would like to do is apply the coordinate transform matrix to the camera matrix after the "right" lookat is computed(right lookat==x-right,y-down,z-forward,i.e. the cv convention).

There is an additional problem with the code that looks right. The camera scale is negated for some reason. I thought scale is not affected by transform matrix. Having negative scale causes some other problem when I am converting the camera back to cv work.

code to create cameras

import bpy
import bmesh
import random
import math
import numpy as np

from mathutils import Matrix

def sphere_golden_spiral(num_pts=1000):
from numpy import pi, cos, sin, arccos, arange
indices = arange(0, num_pts, dtype=float) + 0.5
phi = arccos(1 - 2*indices/num_pts)
theta = pi * (1 + 5**0.5) * indices
x, y, z = cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi)
points = np.stack((x,y,z),axis=-1)
return points

def norm(v):
return v/np.linalg.norm(v)

def lookat(eye,at=np.array([0.,0,0]),up=np.array([0.,1,0])):
z = norm(eye-at)
x = norm(np.cross(z,up))
y = np.cross(x,z)
z = -z
view_mat = np.zeros((4, 4))
t =np.array([0.,0,0])
t[0] = -np.dot(x,eye)
t[1] = -np.dot(y,eye)
t[2] = -np.dot(z,eye)
view_mat[0,:3]=x
view_mat[1,:3]=y
view_mat[2,:3]=z
view_mat[:3,3]=t
view_mat[3,3]=1
return np.linalg.inv(view_mat)

def rotx(a):
from math import cos,sin
return np.array([[1,0,0,0],
[0,cos(a),-sin(a),0],
[0,sin(a),cos(a),0],
[0,0,0,1]])

def roty(a):
from math import cos,sin
return np.array([[cos(a),0,sin(a),0],
[0,1,0,0],
[-sin(a),0,cos(a),0],
[0,0,0,1]])

def rotz(a):
from math import cos,sin
return np.array([[cos(a),-sin(a),0],
[sin(a),cos(a),0,0],
[0,0,1,0],
[0,0,0,1]])

def cams_sphere(points):
points = np.array(sphere_golden_spiral(points))
group = bpy.data.groups.new('Trg_sphere')
context = bpy.context
for i in range(len(points)):
p = points[i]
cam = context.object
cam.name='Traj_sphere_%i'%i
transform = lookat(p*10)
cam.matrix_world = Matrix(transform)

cams_sphere(100)

• For a non-coding result you can create a UV Sphere, zero out all the Transform and Rotations values of the Camera and then Parent the Camera to the UV Sphere. In the Properties of the UV Sphere open Instancing and Verts. – rob Aug 29 '19 at 12:02
• or you could just add the cameras and then add to them a "track to" constraint (which is particularly useful if you then want to move your origin) – Tareyes Aug 29 '19 at 12:14
• Unfortunately, I need to be able to compute camera matrix manually correctly :(, as the rendered images will then be used to do 3d reconstruction which produces an estimated camera poses. Then I would like to be able to compare the ground truth camera poses computed here to poses produced by the algorithm(which should only differ by a rotation and translation offset) – Zaw Lin Aug 29 '19 at 12:19
• The first line of lookat should be z = -norm(eye-at) (with a minus) – lemon Aug 29 '19 at 13:19
• @lemon, it's wrong(I just run the code). And also I think mathematically, z should be negated after the second cross product? – Zaw Lin Aug 30 '19 at 17:41

I'm assuming that this is not a math.se question, but a blender.se question, therefore [frame challenge] I'd suggest to use the builtin types and methods.

If you want to know why your math doesn't work, Blender.SE might not be the right place.

I will add some more tips to restructure your code, but take them with grain of salt, as I am no software dev.

• I would also discourage the excessive scoping of types from imports at the start of functions. I can see at a glance that np.sin is a numpy function, but have a hard time otherwise.
• The x, y, z tuple in the sphere_golden_spiral function was barely readable.

PEP: Limit all lines to a maximum of 79 characters

• Separate the lookat function from a "position" functions. Right now the lookat function produces a matrix containing the position. This is not very intuitive.
• Use enumerate if you want both index and object. (Not range(len(...)).)
• If you want to multiply your numpy array times 10, don't multiply the individual elements, multiply them all at once.

• Replace look_at with the built-in to_track_quat.

• Use the built-in Vector, Quaternion and Matrix types.

A modified script.

import bpy
import numpy as np
import mathutils

def sphere_golden_spiral(num_pts=1000):
indices = np.arange(0, num_pts, dtype=float) + 0.5

phi = np.arccos(1 - 2*indices/num_pts)
theta = np.pi * (1 + 5**0.5) * indices

x = np.cos(theta) * np.sin(phi)
y = np.sin(theta) * np.sin(phi)
z = np.cos(phi)
points = np.stack((x,y,z),axis=-1)
return points

def cams_sphere(num_pts):
points = sphere_golden_spiral(num_pts) * 10

for i, point in enumerate(points):
cam = bpy.context.object
cam.name='Traj_sphere_%i'%i

cam.location = mathutils.Vector(point)
cam.rotation_mode = 'QUATERNION'
cam.rotation_quaternion = cam.location.to_track_quat('Z', 'Y')

cams_sphere(100)


Obviously, you can use the longer (possibly more stable) version with a matrix multiplication.

        bpy.ops.object.camera_add()
cam = bpy.context.object
cam.name='Traj_sphere_%i'%i

location = mathutils.Vector(point)
rotation = location.to_track_quat('Z', 'Y')

mat_loc = mathutils.Matrix.Translation(location)
mat_rot = rotation.to_matrix().to_4x4()

cam.matrix_world = mat_loc * mat_rot

• Thanks! I am checking out your changes. Yes, your method is indeed doing what I want to do. Might you also know what is wrong with my lookat code? – Zaw Lin Aug 30 '19 at 17:40