Reputation: 3974
I have been working through the Hartley and Zisserman multiple view geometry text and have implemented the gold standard algorithm for computing the Fundamental matrix. This requires solving a non-linear minimization problem using Levenberg-Marquardt.
I implemented this with scipy.optimize.least_squares
, but the performance is orders of magnitude slower that similar (e.g., same functionality) matlab code that uses lsqnonlin
. In neither case am I supplying the Jacobian or a mask of the sparsity of the Jacobian.
With respect to compute times, this is true for the range of available scipy solvers. I wonder if an alternative exists that has similar performance (numerical & speed) to matlab or if moving to a wrapped, compiled solver is going to be necessary?
Edit for the code request comment. I am trying to limit the total amount of code inserted.
Matlab:
P2GS = lsqnonlin(@(h)ReprojErrGS(corres1,PF1,corres2,h),PF2);
function REGS = ReprojErrGS(corres1,PF1,corres2,PF2)
%Find estimated 3D point by Triangulation method
XwEst = TriangulationGS(corres1,PF1,corres2,PF2);
%Reprojection Back to the image
x1hat = PF1*XwEst;
x1hat = x1hat ./ repmat(x1hat(3,:),3,1);
x2hat = PF2*XwEst;
x2hat = x2hat ./ repmat(x2hat(3,:),3,1);
%Find root mean squared distance error
dist = ((corres1 - x1hat).*(corres1 - x1hat)) + ((corres2 - x2hat).* (corres2 - x2hat));
REGS = sqrt(sum(sum(dist)) / size(corres1,2));
Triangulation is the standard method, iterating over all points, setting up Ax=0 and solving using SVD.
Python:
# Using 'trf' for performance, swap to 'lm' for levenberg-marquardt
result = optimize.least_squares(projection_error, p1.ravel(), args=(p, pt.values, pt1.values), method='trf')
# Inputs are pandas dataframe, hence the .values
# Triangulate the correspondences
xw_est = triangulate(pt, pt1, p, p1)
# SciPy does not like 2d multi-dimensional variables, so reshape
if p1.shape != (3,4):
p1 = p1.reshape(3,4)
xhat = p.dot(xw_est).T
xhat /= xhat[:,-1][:,np.newaxis]
x2hat = p1.dot(xw_est).T
x2hat /= x2hat[:,-1][:,np.newaxis]
# Compute error
dist = (pt - xhat)**2 + (pt1 - x2hat)**2
reproj_error = np.sqrt(np.sum(dist, axis=1) / len(pt))
# print(reproj_error)
return reproj_error
This should be fully vectorized. Triangulation is as above. I can add that could but would likely link a gist to keep the question size managable.
Upvotes: 5
Views: 6209
Reputation: 381
A reason for the huge difference in speed could probably be that lsqnonlin of matlab is able to detect the sparse structure of the Jacobian matrix and therefore computes it a lot faster. On the other side scipy's least_squares doesn't handle sparse Jacobian matrices and computes every element of the Jacobian like in the standard case (also the unnecessary entries).
In this particular optimization problem (Gold Standard Algorithm to determine the fundamental matrix), the sparse computation of the Jacobian is crucial to obtain a good performance like mentioned and described in Hartley and Zisserman. (several minutes up to >10, depending on the number of point correspondences used for the computation of F)
This is a link to a Paython Wrapper of a sparse LM implementation. But it is a bit more complicated to make it run:
http://www.bmp.ds.mpg.de/pysparselm.html
I hope scipy's least_squares will soon get an upgrade to handle sparse Jacobians. :) (Trusted Region Reflective of scipy least squares is able to handle sparse Jacobians, check this tutorial. However, since LM is actually the MINPACK implementation, you have to look elsewhere for an implementation of sparse LM.)
Upvotes: 1
Reputation: 26030
least_squares
is very new. As of Fall 2015, there were no alternatives in SciPy land. Otherwise, there's e.g. Ceres.
There surely are many opportunities to speed up least_squares
--- pull requests are gladly accepted :-). The first thing to check is that SciPy is linked to a decent LAPACK implementation though.
Upvotes: 2