Alterações no resultado do Python durante o cálculo do cv2.

19

Se eu correr:

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

Eu recebo:

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

Então, o resultado das changes()mudanças.

Eu não entendo por que isso é, e o fato de ele parar de mudar se a tvec=np.zeros(3)linha for comentada, me faz sentir que isso é um bug no sistema.

Ian Carr-de Avelon
fonte
"e-310" são números flutuantes muito próximos de 0. Parece que o problema geral da representação de números flutuantes python, que pode variar em cada alocação de memória.
Aryerez
Isso é muito estranho ... parece um bug para mim também.
Julien
11
A principal coisa da IMO é que a definição de tvec como uma matriz (mas não como um int ou string) tem efeito algum ... E uma vez feito isso, não há como voltar atrás ... Meu palpite é que tvec é um estado interno de cv2.Rodrigues que não deve ser adulterados, ainda que a interface parece permitir tal violação por efeito colateral ...
Julien
Isso é confuso. Se eu desenrolar o loop, ele funcionará quando eu armazenar o resultado np.zeros(3)em duas variáveis diferentes . Se eu não armazenar o resultado ou usar a mesma variável duas vezes, não será. Talvez alguém com um conhecimento mais entorpecido possa esclarecer isso.
preguiça
11
FYI, eu vejo a mesma coisa em Python3 no Windows ... #
Julien

Respostas:

8

É muito provável que uma matriz não inicializada, como retornada por np.empty. Isso, juntamente com a reciclagem da memória, pode levar ao tipo de efeito que você está vendo. Um exemplo mínimo seria:

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 como na primeira iteração ycontém lixo e a cada iteração subseqüente, ele contém o valor do anterior, xporque é atribuída sua memória que foi liberada pouco antes.

Podemos verificar facilmente que, no exemplo original, também é o anterior tvecque aparece:

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 ainda que é a escolha peculiar de rmatquem desencadeia o erro.

Provavelmente, é um erro eye(4)aceito, porque, oficialmente, rmatdeve ser 3x1 1x3 ou 3x3. De fato, um 1D rmatque não possui 3 elementos é corretamente rejeitado pelo wrapper Python. Minha suspeita é que os 2D´rmat`s não são devidamente verificados no nível do Python. O código C, em seguida, detecta que a forma incorreta não faz nada, exceto retornar um código de erro que o código Python não verifica.

De fato, usar um rmat=eye(3)efeito 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
fonte
Pois np.emptyesse comportamento é bem conhecido, porque leva os bytes de memória à medida que eles chegam, sem atualizar os valores existentes. Mas a cv2.Rodriguesfunção deve retornar alguns valores significativos, após um cálculo rigoroso. Além disso, os valores estranhos apresentados no OP dificilmente podem ser considerados lixo, pois estão todos muito próximos de zero.
sciroccorics
11
@sciroccorics você não concorda que meu segundo trecho é bastante atraente?
Paul Panzer
Enviei um PR para verificar o tamanho da entrada.
Catree 27/12/19
3

Definitivamente, é um bug na função Rodrigues ...

Se você ler o documento correspondente , poderá ver que cv2.Rodriguespossui 2 interfaces diferentes:

aquele que imita a interface C ++, onde o vetor de rotação (e opcionalmente o jacobiano) é passado por referência e modificado pela função

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

e um (mais Pythonic) onde o vetor de rotação e o jacobiano são retornados como uma tupla

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

Se você usar a primeira interface, o 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 após uma investigação mais aprofundada:

A função é ainda mais incorreta conforme o esperado: ao usar a primeira interface, os parâmetros dste jacobiannão são modificados, o que contraria totalmente a doutrina:

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

Em outras palavras, isso claramente requer um relatório de erro ...

sciroccorics
fonte
Outra resposta está correta. O problema vem np.eye(4). O método requer vetor de rotação (3x1 ou 1x3) ou matriz de rotação (3x3). Aqui com np.eye (4) a função cria dst com algum tamanho. Mas como o formato de entrada está errado, o método não faz nada e o deixa unitizado. Além disso, você está apontando para uma versão obsoleta do OpenCV. É melhor usar a versão principal ou apontar para uma versão específica: consulte docs.opencv.org .
Catree 27/12/19