我使用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只返回该行?
答案 0 :(得分:3)
我不知道从cv2.fitLine
本身获取残差总和的任何直接方法,我将专注于加速现有代码。现在,在使用相对较多的点进行基准测试时,它显示大部分运行时都花费在最后两行,我们得到rot_points
和err
。此外,似乎我们并没有完全使用rot_points
的最后一行来计算err
,所以希望我们可以通过仅切入前两行来削减一部分运行时间。
让我们深入研究获取rot_points
和err
的有效方法。
1)rot_points = np.dot(R,(points-p).T)
此步骤涉及points-p
中的广播,稍后通过矩阵乘法减少。现在,broadcasting
涉及大量内存使用,如果我们将matrix-multiplication
R
分别与points
和p
分开,则可以跳过这种情况。另外,如前所述,让我们引入前两行切片。因此,我们可以得到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
加快效率squaring
和sum-reduction
,就像这样 -
err = np.einsum('ij,ij->j',rot_points_row2,rot_points_row2)
对于相对较小的点数2000
,计算mag
:mag = 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)
我想提一下,我将PCA与fitLine进行了比较,以提高速度。对于点数大于~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;