Zmiany wyniku w Pythonie podczas obliczeń cv2.Rodrigues

19

Jeśli uruchomię:

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

Dostaję:

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

Wynik wynikający ze changes()zmian.

Nie rozumiem, dlaczego tak jest, a fakt, że przestaje się zmieniać, jeśli tvec=np.zeros(3)linia jest komentowana, sprawia, że ​​czuję, że jest to błąd w systemie.

Ian Carr-de Avelon
źródło
„e-310” to liczby zmiennoprzecinkowe bardzo zbliżone do 0. Wygląda to na ogólny problem z reprezentacją liczb zmiennoprzecinkowych w Pythonie, który może różnić się przy każdym przydziale pamięci.
Aryerez
To jest naprawdę dziwne ... dla mnie też wygląda na błąd.
Julien
1
Najważniejsze, że IMO polega na tym, że zdefiniowanie tvec jako tablicy (ale nie jako int lub łańcucha) ma w ogóle jakiś efekt ... A kiedy to zrobisz, nie ma odwrotu ... Domyślam się, że tvec jest stanem wewnętrznym z cv2.Rodrigues, których nie należy naruszać, ale interfejs wydaje się pozwalać na takie manipulowanie efektem ubocznym ...
Julien
To jest mylące. Jeśli rozwinę pętlę, zadziała, gdy zapiszę wynik np.zeros(3)w dwóch różnych zmiennych. Jeśli nie przechowuję wyniku lub nie użyję tej samej zmiennej dwa razy, nie będzie. Może ktoś z bardziej nieokreśloną wiedzą może rzucić na to trochę światła.
lenistwo
1
Do zobaczenia, to samo widzę w Python3 na Windows ...
Julien

Odpowiedzi:

8

Najprawdopodobniej jest to niezainicjowana tablica, taka jak zwracana przez np.empty. To w połączeniu z recyklingiem pamięci może prowadzić do takiego efektu, jaki widzisz. Minimalny przykład to:

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]

Obserwuj, jak przy pierwszej iteracji yzawiera śmieci, a przy każdej kolejnej iteracji zawiera wartość poprzedniej, xponieważ przypisuje się jej pamięć, która została wcześniej zwolniona.

Możemy łatwo sprawdzić, czy w oryginalnym przykładzie tvecpojawia się również poprzedni :

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

Możemy dalej spekulować, że to szczególny wybór rmatpowoduje błąd.

Prawdopodobnie jest to błąd, który eye(4)w ogóle jest akceptowany, ponieważ oficjalnie rmatpowinien to być 3x1 1x3 lub 3x3. Rzeczywiście, 1D rmat, który nie ma 3 elementów, jest poprawnie odrzucany przez opakowanie Pythona. Podejrzewam, że matematyka 2D nie jest poprawnie sprawdzana na poziomie Pythona. Kod C wykrywa następnie nieprawidłowy kształt i nic nie robi poza zwracaniem kodu błędu, którego kod Python nie sprawdza.

Rzeczywiście, użycie rmat=eye(3)efektu znika:

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
źródło
Na np.emptyzachowanie to jest dobrze znane, ponieważ trwa pamięć bajtów jak przychodzą, bez aktualizowania istniejących wartości. Ale cv2.Rodriguesfunkcja ma zwrócić pewne znaczące wartości, po rygorystycznym obliczeniu. Co więcej, dziwne wartości przedstawione w OP nie mogą być uważane za śmieci, ponieważ wszystkie są bardzo bliskie zeru.
sciroccorics
1
@sciroccorics, czy nie zgodziłbyś się, że mój drugi fragment jest dość przekonujący?
Paul Panzer
Przesłałem PR, aby sprawdzić rozmiar wejściowy.
Catree
3

Zdecydowanie jest to błąd w funkcji Rodrigues ...

Jeśli przeczytasz odpowiedni dokument , możesz zobaczyć, że cv2.Rodriguesma 2 różne interfejsy:

taki, który naśladuje interfejs C ++, w którym wektor obrotu (i opcjonalnie jacobian) jest przekazywany przez odniesienie i modyfikowany przez funkcję

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

i jeden (więcej Python), w którym wektor obrotu i jakobian są zwracane jako krotka

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

Jeśli używasz pierwszego interfejsu, pb znika ...

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

Wynik:

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

EDYCJA po dalszym badaniu:

Funkcja jest jeszcze bardziej błędna, zgodnie z oczekiwaniami: podczas korzystania z pierwszego interfejsu parametry dsti jacobiannie są modyfikowane, co jest całkowicie sprzeczne z dokumentacją:

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

Innymi słowy, to wyraźnie wymaga zgłoszenia błędu ...

sciroccorics
źródło
Inna odpowiedź jest poprawna. Problem pochodzi z np.eye(4). Metoda wymaga wektora obrotu (3x1 lub 1x3) lub macierzy obrotu (3x3). Tutaj z np.eye (4) funkcja tworzy dst z pewnym rozmiarem. Ale ponieważ kształt wejściowy jest nieprawidłowy, metoda nic nie robi i pozostawia ją zunifikowaną. Wskazujesz także na przestarzałą wersję OpenCV. Lepiej jest użyć wersji głównej lub wskazać konkretną wersję: patrz docs.opencv.org .
Catree