Search code examples
pythonopencvlinear-algebraprojection

Deprojection from 2D to 3D is wrong regardless of method


I am trying to deproject a 2D image point to a 3D point, and there seems to be just as much misinformation as there is information out there on how to do it. I have the problem modeled in UE4, where I know that:

Camera Location: (1077,1133,450)

Camera Rotation (degrees): yaw = 90, pitch = 345, roll=0

Camera Horizontal FOV (degrees): 54.43224

Camera Vertical FOV (degrees): 32.68799

Camera Shear: 0

Camera Resolution: 1280x720

Object Location in world: (923,2500,0)

Object Location in image frame: (771,427)

From the above data and this method, I have intrinsic camera matrix:

K = [[1.24444399e+03 0.00000000e+00 6.40000000e+02]
     [0.00000000e+00 1.22760403e+03 3.60000000e+02]
     [0.00000000e+00 0.00000000e+00 1.00000000e+00]]

And rotation matrix:

R = [[ 5.91458986e-17 -1.00000000e+00 -1.58480958e-17]
     [ 9.65925826e-01  6.12323400e-17 -2.58819045e-01]
     [ 2.58819045e-01  0.00000000e+00  9.65925826e-01]]

which I verified with this tool.

I first tried to do this without using the intrinsic camera matrix using this method but that did not work because the intrinsic parameters are, in fact, required

I next attempted this solution, which I implemented in python, removing the code that would have calculated the intrinsic and extrinsic parameters I already have:

def deproject_pixel(u,v):
   inv_camera_rotation_matrix = np.linalg.inv(camera_rotation_matrix)
   inv_camera_intrinsic_matrix = np.linalg.inv(K)

   uv_matrix = np.array([
       [u],
       [v],
       [1]])

   tx = 1077
   ty = 1133
   tz = 450

   txyz_matrix = np.array([
       [-tx],
       [-ty],
       [-tz]])

   Tx_Ty_Tz = np.dot(camera_rotation_matrix,txyz_matrix)

   ls = inv_camera_rotation_matrix @ inv_camera_intrinsic_matrix @ uv_matrix
   rs = inv_camera_rotation_matrix @ Tx_Ty_Tz

   s = rs[2][0] / ls[2][0]
   world_point = s*ls-rs

   return world_point

I believe the above code is equivalent to this coded solution in C++ but perhaps I made a mistake?

Running deproject_pixel(771,427) returns (929,1182,0), which is close in X but very far off in Y

I then tried another implementation that requires the full camera matrix M:

M = [email protected]((camera_rotation_matrix,Tx_Ty_Tz),1)
def deproject_pixel_2(u,v):
   A = (M[0][1]-M[2][1]*u)/(M[1][1]-M[2][1]*v)
   B = (M[2][0]*u-M[0][0])/(M[2][0]*v-M[1][0])

   X = ((M[1][3]-M[2][3]*v)*A-M[0][3]+M[2][3]*u ) / (A*(M[2][0]*v-M[1][0])-M[2][0]*u+M[0][0])
   Y = (M[0][3]-M[2][3]*u-B*(M[1][3]-M[2][3]*v)) / (B*(M[1][1]-M[2][1]*v)-M[0][1]+M[2][1]*u)

   world_point = [X,Y,0]
   return world_point

But once again, running deproject_pixel_2(771,427) returns (929,1182,0), which is close in X but very far off in Y

Can anyone please point out what I am doing wrong here? Is a matrix calculation incorrect? Are both of these implementations simultaneously wrong in the same way?

UPDATE 1 I moved the camera system to zero and removed rotation. I can now work out rotation offsets if I rotate along a single axis, but combining multiple axes of rotation changes what the offsets need to be, so I can now properly deproject only if there is a single axis of rotation. Suggestions? I have learned that Unreal may deal with rotation differently than standard notation suggests.

Further reading:

Transforming 2D image coordinates to 3D world coordinates with z = 0

OpenCV unproject 2D points to 3D with known depth `Z`

Get 3D coordinates from 2D image pixel if extrinsic and intrinsic parameters are known

https://answers.opencv.org/question/62779/image-coordinate-to-world-coordinate-opencv/

http://ksimek.github.io/2013/08/13/intrinsic/


Solution

  • It's just UE4 being odd about the rotation matrix. I modeled the problem in Blender 2.79 and everything works perfectly if you rotate the camera with ZYX (ypr) and add a 180 degree offset to the roll (so 180-reported_angle) in the code.

    Upshot is know your engine and how it does or does not conform to published standards!