Le résultat de Python change pendant le calcul de cv2.Rodrigues

19

Si je cours:

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

Je reçois:

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

Donc, le résultat des changes()changements.

Je ne comprends pas pourquoi cela, et le fait qu'il cesse de changer si la tvec=np.zeros(3)ligne est commentée, me fait penser que c'est un bogue dans le système.

Ian Carr-de Avelon
la source
"e-310" sont des nombres flottants très proches de 0. Cela ressemble au problème général avec la représentation des nombres flottants en python, qui peut varier à chaque allocation de mémoire.
Aryerez
C'est vraiment bizarre ... ça ressemble aussi à un bug pour moi.
Julien
1
L'IMO est principalement définie par le fait que définir tvec comme un tableau (mais pas comme un int ou une chaîne) a un effet ... Et une fois que vous l'avez fait, pas de retour en arrière ... Je suppose que tvec est un état interne de cv2.Rodrigues qui ne devraient pas être falsifiées, mais l'interface semble permettre une telle falsification par effet secondaire ...
Julien
Ceci est déroutant. Si je déroule la boucle, cela fonctionnera lorsque je stocke le résultat de np.zeros(3)dans deux variables différentes . Si je ne stocke pas le résultat ou n'utilise pas la même variable deux fois, ce ne sera pas le cas. Peut-être que quelqu'un avec une connaissance plus engourdie peut nous éclairer à ce sujet.
paresseux
1
Pour info, je vois la même chose en Python3 sous Windows ...
Julien

Réponses:

8

Il s'agit très probablement d'un tableau non initialisé tel que renvoyé par np.empty. Ceci, combiné au recyclage de la mémoire, peut conduire au type d'effet que vous voyez. Un exemple minimal serait:

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]

Observez comment, à la première itération, ycontient des ordures et à chaque itération suivante, il contient la valeur de la précédente, xcar sa mémoire qui a été libérée juste avant lui est attribuée.

Nous pouvons facilement vérifier que dans l'exemple d'origine, c'est également le précédent tvecqui apparaît:

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

Nous pouvons en outre spéculer que c'est le choix particulier rmatqui déclenche l'erreur.

C'est probablement un bogue qui eye(4)est accepté du tout parce que, officiellement, rmatdevrait être 3x1 1x3 ou 3x3. En effet, un 1D rmatqui n'a pas 3 éléments est correctement rejeté par le wrapper Python. Je soupçonne que les «mat» 2D ne sont pas correctement vérifiés au niveau Python. Le code C détecte alors la mauvaise forme ne fait rien, sauf pour renvoyer un code d'erreur que le code Python ne vérifie pas.

En effet, utiliser un rmat=eye(3)effet disparaît:

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
la source
Car np.emptyce comportement est bien connu, car il prend des octets de mémoire au fur et à mesure, sans mettre à jour les valeurs existantes. Mais la cv2.Rodriguesfonction est censée retourner des valeurs significatives, après un calcul rigoureux. De plus, les valeurs étranges présentées dans le PO peuvent difficilement être considérées comme des ordures, car elles sont toutes très proches de zéro.
sciroccorics
1
@sciroccorics ne seriez-vous pas d'accord pour dire que mon deuxième extrait est assez convaincant?
Paul Panzer
J'ai soumis un PR pour vérifier la taille d'entrée.
Catree
3

C'est définitivement un bug dans la fonction Rodrigues ...

Si vous lisez le document correspondant , vous pouvez voir qu'il cv2.Rodriguesa 2 interfaces différentes:

celui qui imite l'interface C ++, où le vecteur de rotation (et éventuellement le jacobien) sont passés par référence et modifiés par la fonction

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

et un (plus Pythonic) où le vecteur de rotation et le jacobien sont retournés comme un tuple

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

Si vous utilisez la première interface, le pb disparaît ...

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

Résultat:

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

EDIT après enquête supplémentaire:

La fonction est encore plus boguée comme prévu: lors de l'utilisation de la première interface, les paramètres dstet jacobianne sont pas modifiés, ce qui est en totale contraction avec la 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.

En d'autres termes, cela nécessite clairement un rapport de bug ...

sciroccorics
la source
L'autre réponse est correcte. Le problème vient de np.eye(4). La méthode nécessite un vecteur de rotation (3x1 ou 1x3) ou une matrice de rotation (3x3). Ici, avec np.eye (4), la fonction crée dst avec une certaine taille. Mais comme la forme d'entrée est erronée, la méthode ne fait rien et la laisse unialisée. En outre, vous pointez vers une version obsolète d'OpenCV. Il est préférable d'utiliser la version principale ou de pointer vers une version spécifique: voir docs.opencv.org .
Catree