Python结果在cv2.Rodrigues计算过程中发生变化。

21

如果我运行:

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()
我理解为:"我得到:"
[[6.92798859e-310]
 [2.19380404e-316]
 [1.58101007e-322]]
[[0.]
 [0.]
 [0.]]

所以从changes()的结果会发生改变。

我不明白为什么会这样,而且如果注释掉tvec=np.zeros(3) 这一行代码,它就停止改变,这让我觉得这是系统的一个bug。


1
我认为最重要的是将tvec定义为数组(而不是int或string)确实会产生影响...一旦你这样做了,就无法回头了... 我猜测tvec是cv2.Rodrigues的内部状态,不应该被篡改,但界面似乎允许通过副作用进行这种篡改... - Julien
1
顺便说一句,在Windows上的Python3中我也看到了同样的情况... - Julien
我的猜测是C++代码中目标矩阵存在某种内存分配错误。如果在函数末尾添加rvec = None,则changes()停止更改。但至少我可以回答问题_a_:是的,你正在发疯。而且你还拖着我们一起发疯 :) - caxcaxcoatl
np.eye(4) 不正确,请使用 np.eye(3)。函数创建了 rvecjacobian 矩阵到一定的大小,但没有初始化。由于输入形状不正确,它什么也没做。在 PR 中添加了大小检查。 - Catree
你已经向OpenCV报告了这个bug吗? - oarfish
显示剩余6条评论
2个回答

8

这很可能是一个未初始化的数组,比如由np.empty返回的数组。这与内存回收结合在一起,会导致您看到的这种效果。一个最简单的例子是:

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]

观察第一次迭代时,y包含垃圾值,在每个后续迭代中,它包含前一个x的值,因为它被分配了刚刚释放的内存。

我们可以轻松地验证在原始示例中也是前一个tvec出现:

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

我们可以进一步推测,正是rmat的特殊选择触发了错误。

很可能eye(4)被接受是个bug,因为官方上来说,rmat应该是3x1、1x3或3x3。事实上,一个没有3个元素的1Drmat被Python包正确地拒绝了。我怀疑2D的´rmat`在Python层面上没有被正确检查。然后C代码检测到错误形状,除了返回一个错误代码外什么也不做,而Python代码没有检查错误。

使用rmat=eye(3)时,效果消失:

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

对于np.empty,这种行为是众所周知的,因为它按照内存字节来获取数据,而不更新现有值。但是,cv2.Rodrigues函数应该返回一些有意义的值,经过严格的计算。此外,OP中呈现的奇怪值很难被视为垃圾,因为它们都非常接近于零。 - sciroccorics
1
@sciroccorics,你不觉得我的第二个片段非常有说服力吗? - Paul Panzer
我已经提交了一个PR,以检查输入大小。 - Catree

4

显然,这是Rodrigues函数中的一个错误...

如果您阅读相应文档,您可能会看到cv2.Rodrigues有两种不同的接口:

一种模仿C++接口,其中旋转向量(和可选的Jacobian矩阵)通过引用传递并由函数修改

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

还有一种更加Pythonic的方式,将旋转向量和雅可比矩阵作为元组返回。

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

如果您使用第一个接口,pb 会消失...
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()

结果:

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

进一步调查后编辑:

这个函数的问题比预期的还要严重:在使用第一个接口时,参数dstjacobian没有被修改,这与文档字符串完全相矛盾:

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

换句话说,这显然需要一份错误报告...

其他回答 是正确的。问题来自于 np.eye(4)。该方法需要一个 (3x1 或 1x3) 的旋转向量或者一个(3x3) 的旋转矩阵。在这里,np.eye(4) 创建了一个具有某些大小的 dst。但是,由于输入形状不正确,该方法什么也没做,将其未初始化。另外,你指向了 OpenCV 的一个过时版本。最好使用主版本或指向特定版本:请参阅 https://docs.opencv.org/. - Catree

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接