El resultado de Python cambia durante el cálculo de cv2.Rodrigues

19

Si corro:

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()

Yo obtengo:

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

Entonces el resultado de los changes()cambios.

No entiendo por qué es así, y el hecho de que deje de cambiar si tvec=np.zeros(3)se comenta la línea, me hace sentir que se trata de un error en el sistema.

Ian Carr-de Avelon
fuente
"e-310" son números flotantes muy cercanos a 0. Parece el problema general con la representación de números flotantes de Python, que puede variar en cada asignación de memoria.
Aryerez
Esto es realmente extraño ... también me parece un error.
Julien
1
Lo principal de IMO es que definir tvec como una matriz (pero no como int o string) tiene un efecto en absoluto ... Y una vez que lo ha hecho, no hay vuelta atrás ... Supongo que tvec es un estado interno de cv2.Rodrigues que no deben ser manipulados, pero la interfaz parece permitir tal manipulación por efecto secundario ...
Julien
Esto es confuso. Si desenrollo el bucle, funcionará cuando almacene el resultado np.zeros(3)en dos variables diferentes . Si no almaceno el resultado o uso la misma variable dos veces, no lo hará. Tal vez alguien con un conocimiento más complejo pueda arrojar algo de luz sobre esto.
perezoso
1
FYI, veo lo mismo en Python3 en Windows ...
Julien

Respuestas:

8

Es muy probable que se trate de una matriz no inicializada, como la devuelta por np.empty. Esto junto con el reciclaje de la memoria puede conducir al tipo de efecto que está viendo. Un ejemplo mínimo sería:

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 cómo en la primera iteración ycontiene basura y en cada iteración posterior contiene el valor de la anterior xporque se le asigna su memoria que se ha liberado justo antes.

Podemos comprobar fácilmente que en el ejemplo original también tvecaparece el anterior :

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

Podemos especular aún más que es la elección peculiar de lo rmatque desencadena el error.

Probablemente sea un error que eye(4)sea ​​aceptado porque, oficialmente, rmatdebería ser 3x1 1x3 o 3x3. De hecho, un rmatenvoltorio de Python rechaza correctamente un 1D que no tiene 3 Elementos. Mi sospecha es que los 2D 'rmat`s no se verifican correctamente en el nivel de Python. El código C luego detecta que la forma incorrecta no hace nada excepto devolver un código de error que el código Python no verifica.

De hecho, el uso de un rmat=eye(3)efecto desaparece:

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
fuente
Para np.emptyeste comportamiento es bien conocido, porque se necesita la memoria bytes como vienen, sin actualizar los valores existentes. Pero cv2.Rodriguesse supone que la función devuelve algunos valores significativos, después de un cálculo riguroso. Además, los valores extraños presentados en el OP difícilmente pueden considerarse basura, ya que todos están muy cerca de cero.
sciroccorics
1
@sciroccorics, ¿no estaría de acuerdo con que mi segundo fragmento es bastante convincente?
Paul Panzer
He enviado un RP para verificar el tamaño de entrada.
Catree
3

Definitivamente, es un error en la función Rodrigues ...

Si lees el documento correspondiente , puedes ver que cv2.Rodriguestiene 2 interfaces diferentes:

uno que imita la interfaz de C ++, donde el vector de rotación (y opcionalmente el jacobiano) se pasa por referencia y se modifica por la función

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

y uno (más pitónico) donde el vector de rotación y el jacobiano se devuelven como una tupla

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

Si usa la primera interfaz, el pb desaparece ...

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()

Resultado:

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

EDITAR después de una investigación adicional:

La función es aún más defectuosa como se esperaba: cuando se usa la primera interfaz, los parámetros dsty jacobianno se modifican, lo que está en total contradicción con la cadena de documentación:

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

En otras palabras, esto claramente requiere un informe de error ...

sciroccorics
fuente
Otra respuesta es correcta. El problema viene de np.eye(4). El método requiere un vector de rotación (3x1 o 1x3) o una matriz de rotación (3x3). Aquí con np.eye (4) la función crea dst con algún tamaño. Pero como la forma de entrada es incorrecta, el método no hace nada y la deja unitaria. Además, está apuntando a una versión obsoleta de OpenCV. Es mejor usar la versión maestra o apuntar a una versión específica: ver docs.opencv.org .
Catree