21

If I run:

import numpy as np
import cv2

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

for i in range(2):
    changes()

I get:

[[6.92798859e-310]
 [2.19380404e-316]
 [1.58101007e-322]]
[[0.]
 [0.]
 [0.]]

So the result from changes() changes.

I don't understand why that is, and the fact that it stops changing if the tvec=np.zeros(3) line is commented out, makes me feel that this is a bug in the system.

nathancy
  • 42,661
  • 14
  • 115
  • 137
  • "e-310" are floating numbers very close to 0. It looks like the general issue with python floating numbers representation, which can vary on every allocation of memory. – Aryerez Nov 21 '19 at 07:44
  • This is seriously weird... looks like a bug to me too. – Julien Nov 21 '19 at 07:45
  • 1
    The main thing IMO is that defining tvec as an array (but not as an int or string) has an effect at all... And once you've done it, no turning back... My guess is tvec is an internal state of cv2.Rodrigues that shouldn't be tampered with, yet the interface seems to allow such tampering by side effect... – Julien Nov 21 '19 at 07:51
  • This is confusing. If I unroll the loop, it will work when I store the result of `np.zeros(3)` in *two different* variables. If I don't store the result or use the same variable twice, it will not. Maybe someone with more numpy knowledge can shed some light on this. – sloth Nov 21 '19 at 07:55
  • Are you using Python 2.7? Also, is your question about the steps one should undertake to make the result uniform, say: `[[ 0.] [ 0.] [ 0.]] [[ 0.] [ 0.] [ 0.]]`? – Arn Nov 21 '19 at 16:56
  • Yes I am using Python 2.7. I'm on an Ubuntu 14 system which I've added lots to, so after running memcheck I tried it on pythonanywhere.com's system to reassure myself that it's not simply that my system is broken. I've made the example in the normal way by cutting back to the smallest amount of code that still seems to have the problem. I can easily make a work around by storing the result first time. The question really is a) am I going mad here? b) is there something about Python I should know? c) is this a system bug? d) can the bug be put down to: Python, numpy, opencv or something else? – Ian Carr-de Avelon Nov 21 '19 at 17:09
  • 1
    FYI, I see the same thing in Python3 on Windows... – Julien Nov 21 '19 at 22:19
  • My guess is that it is some kind of memory allocation error on the C++ code, on the destination matrix. If you add `rvec = None` at the end of the function, then `changes()` stops changing. But at least I can answer item _a_: Yes, you are going mad. And you're dragging all of us with you :) – caxcaxcoatl Dec 18 '19 at 03:53
  • `np.eye(4)` is not correct. Use `np.eye(3)` instead. What happens is that the function creates `rvec` and `jacobian` matrices to some size but not initialized. Then, since the input shape is wrong it does nothing. Size check has been added in [PR](https://github.com/opencv/opencv/pull/16242). – Catree Dec 27 '19 at 14:08
  • Have you reported this bug to OpenCV yet? – oarfish Jan 23 '21 at 06:16
  • No, now I understand my mistake I can live with it as it is. – Ian Carr-de Avelon Jan 24 '21 at 09:51

2 Answers2

8

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.]]
Paul Panzer
  • 51,835
  • 3
  • 54
  • 99
  • For `np.empty` this behavior is well known, because it takes memory bytes as they come, without updating existing values. But the `cv2.Rodrigues` function is supposed to return some meaningful values, after rigorous computation. Moreover, the strange values presented in the OP can hardly be considered as garbage, as they are all very close to zero. – sciroccorics Dec 18 '19 at 18:08
  • 1
    @sciroccorics wouldn't you agree that my second snippet is pretty compelling? – Paul Panzer Dec 18 '19 at 18:56
  • I have submitted a [PR](https://github.com/opencv/opencv/pull/16242) to check for the input size. – Catree Dec 27 '19 at 00:58
4

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...

sciroccorics
  • 2,357
  • 1
  • 8
  • 21
  • [Other answer](https://stackoverflow.com/a/59397786/6055233) is correct. The problem comes from `np.eye(4)`. The method requires (3x1 or 1x3) rotation vector or (3x3) rotation matrix. Here with np.eye(4) the function creates dst with some size. But since the input shape is wrong, the method does nothing and leaves it unitialized. Also, you are pointing to an obsolete version of OpenCV. It is better to use the master version or to point to a specific version: see https://docs.opencv.org/. – Catree Dec 27 '19 at 14:18