Python result changes during cv2.Rodrigues computation

This is very likely an uninitialized array such as returned by np.empty. This together with memory recycling can lead to the kind of effect you are seeing. A minimal example would be:

for a in range(5):
    y = np.empty(3,int)
    x = (np.arange(3)+a)**3
    print(x,y)
    del x

# [0 1 8] [94838139529536              0              0]
# [ 1  8 27] [0 1 8]
# [ 8 27 64] [ 1  8 27]
# [ 27  64 125] [ 8 27 64]
# [ 64 125 216] [ 27  64 125]

Observe how at the first iteration y contains garbage and at each subsequent iteration it contains the value of the previous x because it is assigned its memory which has been freed just before.

We can easily check that in the original example it is also the previous tvec that pops up:

def changes():                              
    rmat=np.eye(4)                      
    tvec=np.array([4,0.0,2.5])
    (rvec, jacobian)=cv2.Rodrigues(rmat)
    print(rvec)

for i in range(3):                    
    changes()                               

# [[4.6609787e-310]
#  [0.0000000e+000]
#  [0.0000000e+000]]
# [[4. ]
#  [0. ]
#  [2.5]]
# [[4. ]
#  [0. ]
#  [2.5]]

We may further speculate that it is the peculiar choice of rmat that triggers the error.

It is probably a bug that eye(4) is accepted at all because, officially, rmat should be 3x1 1x3 or 3x3. Indeed, a 1D rmat that doesn't have 3 Elements is correctly rejected by the Python wrapper. My suspicion is that 2D ´rmat`s are not properly checked at the Python level. The C code then detects the wrong shape does nothing except for returning an error code which the Python code doesn't check for.

Indeed using a rmat=eye(3) the effect goes away:

def changes():
    rmat=np.eye(3)
    tvec=np.array([4,0.0,2.5])
    (rvec, jacobian)=cv2.Rodrigues(rmat)
    print(rvec)

for a in range(3):
    changes()

# [[0.]
#  [0.]
#  [0.]]
# [[0.]
#  [0.]
#  [0.]]
# [[0.]
#  [0.]
#  [0.]]

Definitely, it's a bug in the Rodrigues function...

If you read the corresponding doc, you may see that cv2.Rodrigues has 2 different interfaces:

one that mimics the C++ interface, where the rotation vector (and optionaly the jacobian) are passed by reference and modified by the function

cv2.Rodrigues(src, dst[, jacobian]) --> None

and one (more Pythonic) where the rotation vector and the jacobian are returned as a tuple

cv2.Rodrigues(src) --> dst, jacobian

If you use the first interface, the pb vanishes...

import numpy as np
import cv2

def changes():                              
    rmat=np.eye(4)                      
    tvec=np.zeros(3)
    #(rvec, jacobian)=cv2.Rodrigues(rmat)
    cv2.Rodrigues(rmat, tvec)
    print(tvec)

for i in range(2):                    
    changes()

Result:

[0. 0. 0.]
[0. 0. 0.]

EDIT after further investigation:

The function is even more buggy as expected: when using the first interface, parameters dst and jacobian are not modified, which is in total contracdiction with the docstring:

>>> help(cv2.Rodrigues)
Help on built-in function Rodrigues:

Rodrigues(...)
    Rodrigues(src[, dst[, jacobian]]) -> dst, jacobian
    .   @brief Converts a rotation matrix to a rotation vector or vice versa.
    .   
    .   @param src Input rotation vector (3x1 or 1x3) or rotation matrix (3x3).
    .   @param dst Output rotation matrix (3x3) or rotation vector (3x1 or 1x3), respectively.
    .   @param jacobian Optional output Jacobian matrix, 3x9 or 9x3, which is a matrix of partial
    .   derivatives of the output array components with respect to the input array components.

In other words, this clearly requires a bug report...