如何计算OpenCV fitLine的残差?

时间:2015-10-12 12:31:44

标签: python performance opencv numpy

我使用OpenCV fitLine将线条拟合到3D点。什么是计算最终拟合残差的最佳方法?或者,因为除了fit之外我还需要残差,是否有比fitLine更好的方法?

以下工作,但必须有更好(更快)的方式。

# fit points
u, v, w, x, y, z = cv2.fitLine(points, cv2.DIST_L2, 0, 1, 0.01)
v = np.array(u[0], v[0], w[0])
p = np.array(x[0], y[0], z[0])

# rotate fit to z axis
k = np.cross(v, [0, 0, 1])
mag = np.linalg.norm(k)
R, _ = cv2.Rodrigues(k * np.arcsin(mag) / mag)

# rotate points and calculate distance to z-axis
rot_points = np.dot(R, (points-p).T)
err = rot_points[0]**2 + rot_points[1]**2

我假设fitLine在估算线时计算残差err,因此自行重新计算它们是一种浪费。基本上,知道我想要行残差,是否有一个比fitLine更好的选择,而fitLine只返回该行?

2 个答案:

答案 0 :(得分:3)

我不知道从cv2.fitLine本身获取残差总和的任何直接方法,我将专注于加速现有代码。现在,在使用相对较多的点进行基准测试时,它显示大部分运行时都花费在最后两行,我们得到rot_pointserr。此外,似乎我们并没有完全使用rot_points的最后一行来计算err,所以希望我们可以通过仅切入前两行来削减一部分运行时间。

让我们深入研究获取rot_pointserr的有效方法。

1)rot_points = np.dot(R,(points-p).T)

此步骤涉及points-p中的广播,稍后通过矩阵乘法减少。现在,broadcasting涉及大量内存使用,如果我们将matrix-multiplication R分别与pointsp分开,则可以跳过这种情况。另外,如前所述,让我们引入前两行切片。因此,我们可以得到rot_points的前两行,如此 -

rot_points_row2 = np.dot(R[:2], (points.T)) - np.dot(R[:2],p[:,None]) 

2)err = rot_points [0] ** 2 + rot_points [1] ** 2

第二步可以通过np.einsum加快效率squaringsum-reduction,就像这样 -

err = np.einsum('ij,ij->j',rot_points_row2,rot_points_row2)

对于相对较小的点数2000,计算magmag = np.linalg.norm(k)的步骤在运行时方面也可能变得很重要。因此,为了加快速度,可以再次使用np.einsum,如此 -

mag = np.sqrt(np.einsum('i,i->',k,k))

运行时测试

让我们使用2000空间中3D点的随机数组作为输入points,并使用原始方法和建议的方法查看相关的运行时数最后两行。

In [44]: # Setup input points
    ...: N = 2000
    ...: points = np.random.rand(N,3)
    ...: 
    ...: u, v, w, x, y, z = cv2.fitLine(points, cv2.DIST_L2, 0, 1, 0.01)
    ...: v = np.array([u[0], v[0], w[0]])
    ...: p = np.array([x[0], y[0], z[0]])
    ...: 
    ...: # rotate fit to z axis
    ...: k = np.cross(v, [0, 0, 1])
    ...: mag = np.linalg.norm(k)
    ...: R, _ = cv2.Rodrigues(k * np.arcsin(mag) / mag)
    ...: 
    ...: # rotate points and calculate distance to z-axis
    ...: rot_points = np.dot(R, (points-p).T)
    ...: err = rot_points[0]**2 + rot_points[1]**2
    ...: 

让我们运行我们提出的方法,并根据原始输出验证其输出 -

In [45]: rot_points_row2 = np.dot(R[:2], (points.T)) - np.dot(R[:2],p[:,None]) 
    ...: err2 = np.einsum('ij,ij->j',rot_points_row2,rot_points_row2)
    ...: 

In [46]: np.allclose(rot_points[:2],rot_points_row2)
Out[46]: True

In [47]: np.allclose(err,err2)
Out[47]: True

最后也是最重要的是,让我们来看看这些代码部分 -

In [48]: %timeit np.dot(R, (points-p).T) # Original code
10000 loops, best of 3: 79.5 µs per loop

In [49]: %timeit np.dot(R[:2], (points.T)) - np.dot(R[:2],p[:,None]) # Proposed
10000 loops, best of 3: 49.7 µs per loop

In [50]: %timeit rot_points[0]**2 + rot_points[1]**2  # Original code
100000 loops, best of 3: 12.6 µs per loop

In [51]: %timeit np.einsum('ij,ij->j',rot_points_row2,rot_points_row2) # Proposed
100000 loops, best of 3: 11.7 µs per loop

当点数增加到很大数量时,运行时看起来更有希望。有N = 5000000分,我们得到 -

In [59]: %timeit np.dot(R, (points-p).T) # Original code
1 loops, best of 3: 410 ms per loop

In [60]: %timeit np.dot(R[:2], (points.T)) - np.dot(R[:2],p[:,None]) # Proposed
1 loops, best of 3: 254 ms per loop

In [61]: %timeit rot_points[0]**2 + rot_points[1]**2  # Original code
10 loops, best of 3: 144 ms per loop

In [62]: %timeit np.einsum('ij,ij->j',rot_points_row2,rot_points_row2) # Proposed
10 loops, best of 3: 77.5 ms per loop

答案 1 :(得分:1)

我想提一下,我将PCAfitLine进行了比较,以提高速度。对于点数大于~1000的所有情况,fitLine比PCA更快。

PCA直接为您提供特征向量,因此您不需要Rodrigues(但这次应该可以忽略不计)。因此,可能是,优化的关键在于其余的代码,除非有更快的方式来适应模型,除了fitLine和PCA。

我不太清楚PCA相关的数学,所以我在下面的段落中错了。

特征值给出了新本征空间每个维度的方差。如果我们考虑一个简单的二维情形,我认为你可以采用较小的特征值并将其乘以数据集中的点数N(或者它是N-1?)以获得平方残差和。同样,我们可以将其扩展到3D案例。由于PCA给出了特征值,因此需要简单的标量乘法和加法来得到平方残差和。

我正在添加代码(c ++)供您参考。

RNG rng;
float a = -0.1, b = 0.1;
int rows = 3, cols = 1024*2;

Mat data = Mat::zeros(rows, cols, CV_32F);

for (int i = 0; i < cols; i++)
{
    Vec3f& v = data.at<Vec3f>(i);
    v = Vec3f(i+rng.uniform(a, b), i+rng.uniform(a, b), i+rng.uniform(a, b));
}

Mat datat = data.t();

Vec6f line;
fitLine(datat, line, CV_DIST_L2, 0, 1, 0.01);

PCA pca(datat, Mat(), CV_PCA_DATA_AS_ROW);

cout << "fitLine:\n" << line << endl;
cout << "\nPCA:\n" << pca.eigenvalues << endl;
cout << pca.eigenvectors << endl;