1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html
4 
5 #include "precomp.hpp"
6 
7 #include "sparse_block_matrix.hpp"
8 
9 // matrix form of conjugation
10 static const cv::Matx44d M_Conj{ 1,  0,  0,  0,
11                                  0, -1,  0,  0,
12                                  0,  0, -1,  0,
13                                  0,  0,  0, -1 };
14 
15 // matrix form of quaternion multiplication from left side
m_left(cv::Quatd q)16 static inline cv::Matx44d m_left(cv::Quatd q)
17 {
18     // M_left(a)* V(b) =
19     //    = (I_4 * a0 + [ 0 | -av    [    0 | 0_1x3
20     //                   av | 0_3] +  0_3x1 | skew(av)]) * V(b)
21 
22     double w = q.w, x = q.x, y = q.y, z = q.z;
23     return { w, -x, -y, -z,
24              x,  w, -z,  y,
25              y,  z,  w, -x,
26              z, -y,  x,  w };
27 }
28 
29 // matrix form of quaternion multiplication from right side
m_right(cv::Quatd q)30 static inline cv::Matx44d m_right(cv::Quatd q)
31 {
32     // M_right(b)* V(a) =
33     //    = (I_4 * b0 + [ 0 | -bv    [    0 | 0_1x3
34     //                   bv | 0_3] +  0_3x1 | skew(-bv)]) * V(a)
35 
36     double w = q.w, x = q.x, y = q.y, z = q.z;
37     return { w, -x, -y, -z,
38              x,  w,  z, -y,
39              y, -z,  w,  x,
40              z,  y, -x,  w };
41 }
42 
43 // precaution against "unused function" warning when there's no Eigen
44 #if defined(HAVE_EIGEN)
45 // jacobian of quaternionic (exp(x)*q) : R_3 -> H near x == 0
expQuatJacobian(cv::Quatd q)46 static inline cv::Matx43d expQuatJacobian(cv::Quatd q)
47 {
48     double w = q.w, x = q.x, y = q.y, z = q.z;
49     return cv::Matx43d(-x, -y, -z,
50                         w,  z, -y,
51                        -z,  w,  x,
52                         y, -x,  w);
53 }
54 #endif
55 
56 // concatenate matrices vertically
57 template<typename _Tp, int m, int n, int k> static inline
concatVert(const cv::Matx<_Tp,m,n> & a,const cv::Matx<_Tp,k,n> & b)58 cv::Matx<_Tp, m + k, n> concatVert(const cv::Matx<_Tp, m, n>& a, const cv::Matx<_Tp, k, n>& b)
59 {
60     cv::Matx<_Tp, m + k, n> res;
61     for (int i = 0; i < m; i++)
62     {
63         for (int j = 0; j < n; j++)
64         {
65             res(i, j) = a(i, j);
66         }
67     }
68     for (int i = 0; i < k; i++)
69     {
70         for (int j = 0; j < n; j++)
71         {
72             res(m + i, j) = b(i, j);
73         }
74     }
75     return res;
76 }
77 
78 // concatenate matrices horizontally
79 template<typename _Tp, int m, int n, int k> static inline
concatHor(const cv::Matx<_Tp,m,n> & a,const cv::Matx<_Tp,m,k> & b)80 cv::Matx<_Tp, m, n + k> concatHor(const cv::Matx<_Tp, m, n>& a, const cv::Matx<_Tp, m, k>& b)
81 {
82     cv::Matx<_Tp, m, n + k> res;
83 
84     for (int i = 0; i < m; i++)
85     {
86         for (int j = 0; j < n; j++)
87         {
88             res(i, j) = a(i, j);
89         }
90     }
91     for (int i = 0; i < m; i++)
92     {
93         for (int j = 0; j < k; j++)
94         {
95             res(i, n + j) = b(i, j);
96         }
97     }
98     return res;
99 }
100 
101 namespace cv
102 {
103 namespace kinfu
104 {
105 namespace detail
106 {
107 
108 class PoseGraphImpl : public detail::PoseGraph
109 {
110     struct Pose3d
111     {
112         Vec3d t;
113         Quatd q;
114 
Pose3dcv::kinfu::detail::PoseGraphImpl::Pose3d115         Pose3d() : t(), q(1, 0, 0, 0) { }
116 
Pose3dcv::kinfu::detail::PoseGraphImpl::Pose3d117         Pose3d(const Matx33d& rotation, const Vec3d& translation)
118             : t(translation), q(Quatd::createFromRotMat(rotation).normalize())
119         { }
120 
Pose3dcv::kinfu::detail::PoseGraphImpl::Pose3d121         explicit Pose3d(const Matx44d& pose) :
122             Pose3d(pose.get_minor<3, 3>(0, 0), Vec3d(pose(0, 3), pose(1, 3), pose(2, 3)))
123         { }
124 
operator *cv::kinfu::detail::PoseGraphImpl::Pose3d125         inline Pose3d operator*(const Pose3d& otherPose) const
126         {
127             Pose3d out(*this);
128             out.t += q.toRotMat3x3(QUAT_ASSUME_UNIT) * otherPose.t;
129             out.q = out.q * otherPose.q;
130             return out;
131         }
132 
getAffinecv::kinfu::detail::PoseGraphImpl::Pose3d133         Affine3d getAffine() const
134         {
135             return Affine3d(q.toRotMat3x3(QUAT_ASSUME_UNIT), t);
136         }
137 
inversecv::kinfu::detail::PoseGraphImpl::Pose3d138         inline Pose3d inverse() const
139         {
140             Pose3d out;
141             out.q = q.conjugate();
142             out.t = -(out.q.toRotMat3x3(QUAT_ASSUME_UNIT) * t);
143             return out;
144         }
145 
normalizeRotationcv::kinfu::detail::PoseGraphImpl::Pose3d146         inline void normalizeRotation()
147         {
148             q = q.normalize();
149         }
150     };
151 
152     /*! \class GraphNode
153      *  \brief Defines a node/variable that is optimizable in a posegraph
154      *
155      *  Detailed description
156      */
157     struct Node
158     {
159     public:
Nodecv::kinfu::detail::PoseGraphImpl::Node160         explicit Node(size_t _nodeId, const Affine3d& _pose)
161             : id(_nodeId), isFixed(false), pose(_pose.rotation(), _pose.translation())
162         { }
163 
getPosecv::kinfu::detail::PoseGraphImpl::Node164         Affine3d getPose() const
165         {
166             return pose.getAffine();
167         }
setPosecv::kinfu::detail::PoseGraphImpl::Node168         void setPose(const Affine3d& _pose)
169         {
170             pose = Pose3d(_pose.rotation(), _pose.translation());
171         }
172 
173     public:
174         size_t id;
175         bool isFixed;
176         Pose3d pose;
177     };
178 
179     /*! \class PoseGraphEdge
180      *  \brief Defines the constraints between two PoseGraphNodes
181      *
182      *  Detailed description
183      */
184     struct Edge
185     {
186     public:
187         explicit Edge(size_t _sourceNodeId, size_t _targetNodeId, const Affine3f& _transformation,
188                       const Matx66f& _information = Matx66f::eye());
189 
operator ==cv::kinfu::detail::PoseGraphImpl::Edge190         bool operator==(const Edge& edge)
191         {
192             if ((edge.sourceNodeId == sourceNodeId && edge.targetNodeId == targetNodeId) ||
193                 (edge.sourceNodeId == targetNodeId && edge.targetNodeId == sourceNodeId))
194                 return true;
195             return false;
196         }
197 
198     public:
199         size_t sourceNodeId;
200         size_t targetNodeId;
201         Pose3d pose;
202         Matx66f sqrtInfo;
203     };
204 
205 
206 public:
PoseGraphImpl()207     PoseGraphImpl() : nodes(), edges()
208     { }
~PoseGraphImpl()209     virtual ~PoseGraphImpl() CV_OVERRIDE
210     { }
211 
212     // Node may have any id >= 0
213     virtual void addNode(size_t _nodeId, const Affine3d& _pose, bool fixed) CV_OVERRIDE;
isNodeExist(size_t nodeId) const214     virtual bool isNodeExist(size_t nodeId) const CV_OVERRIDE
215     {
216         return (nodes.find(nodeId) != nodes.end());
217     }
218 
setNodeFixed(size_t nodeId,bool fixed)219     virtual bool setNodeFixed(size_t nodeId, bool fixed) CV_OVERRIDE
220     {
221         auto it = nodes.find(nodeId);
222         if (it != nodes.end())
223         {
224             it->second.isFixed = fixed;
225             return true;
226         }
227         else
228             return false;
229     }
230 
isNodeFixed(size_t nodeId) const231     virtual bool isNodeFixed(size_t nodeId) const CV_OVERRIDE
232     {
233         auto it = nodes.find(nodeId);
234         if (it != nodes.end())
235             return it->second.isFixed;
236         else
237             return false;
238     }
239 
getNodePose(size_t nodeId) const240     virtual Affine3d getNodePose(size_t nodeId) const CV_OVERRIDE
241     {
242         auto it = nodes.find(nodeId);
243         if (it != nodes.end())
244             return it->second.getPose();
245         else
246             return Affine3d();
247     }
248 
getNodesIds() const249     virtual std::vector<size_t> getNodesIds() const CV_OVERRIDE
250     {
251         std::vector<size_t> ids;
252         for (const auto& it : nodes)
253         {
254             ids.push_back(it.first);
255         }
256         return ids;
257     }
258 
getNumNodes() const259     virtual size_t getNumNodes() const CV_OVERRIDE
260     {
261         return nodes.size();
262     }
263 
264     // Edges have consequent indices starting from 0
addEdge(size_t _sourceNodeId,size_t _targetNodeId,const Affine3f & _transformation,const Matx66f & _information=Matx66f::eye ())265     virtual void addEdge(size_t _sourceNodeId, size_t _targetNodeId, const Affine3f& _transformation,
266                          const Matx66f& _information = Matx66f::eye()) CV_OVERRIDE
267     {
268         Edge e(_sourceNodeId, _targetNodeId, _transformation, _information);
269         edges.push_back(e);
270     }
271 
getEdgeStart(size_t i) const272     virtual size_t getEdgeStart(size_t i) const CV_OVERRIDE
273     {
274         return edges[i].sourceNodeId;
275     }
276 
getEdgeEnd(size_t i) const277     virtual size_t getEdgeEnd(size_t i) const CV_OVERRIDE
278     {
279         return edges[i].targetNodeId;
280     }
281 
getNumEdges() const282     virtual size_t getNumEdges() const CV_OVERRIDE
283     {
284         return edges.size();
285     }
286 
287     // checks if graph is connected and each edge connects exactly 2 nodes
288     virtual bool isValid() const CV_OVERRIDE;
289 
290     // calculate cost function based on current nodes parameters
291     virtual double calcEnergy() const CV_OVERRIDE;
292 
293     // calculate cost function based on provided nodes parameters
294     double calcEnergyNodes(const std::map<size_t, Node>& newNodes) const;
295 
296     // Termination criteria are max number of iterations and min relative energy change to current energy
297     // Returns number of iterations elapsed or -1 if max number of iterations was reached or failed to optimize
298     virtual int optimize(const cv::TermCriteria& tc = cv::TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, 1e-6)) CV_OVERRIDE;
299 
300     std::map<size_t, Node> nodes;
301     std::vector<Edge>   edges;
302 };
303 
304 
addNode(size_t _nodeId,const Affine3d & _pose,bool fixed)305 void PoseGraphImpl::addNode(size_t _nodeId, const Affine3d& _pose, bool fixed)
306 {
307     Node node(_nodeId, _pose);
308     node.isFixed = fixed;
309 
310     size_t id = node.id;
311     const auto& it = nodes.find(id);
312     if (it != nodes.end())
313     {
314         std::cout << "duplicated node, id=" << id << std::endl;
315         nodes.insert(it, { id, node });
316     }
317     else
318     {
319         nodes.insert({ id, node });
320     }
321 }
322 
323 
324 // Cholesky decomposition of symmetrical 6x6 matrix
llt6(Matx66d m)325 static inline cv::Matx66d llt6(Matx66d m)
326 {
327     Matx66d L;
328     for (int i = 0; i < 6; i++)
329     {
330         for (int j = 0; j < (i + 1); j++)
331         {
332             double sum = 0;
333             for (int k = 0; k < j; k++)
334                 sum += L(i, k) * L(j, k);
335 
336             if (i == j)
337                 L(i, i) = sqrt(m(i, i) - sum);
338             else
339                 L(i, j) = (1.0 / L(j, j) * (m(i, j) - sum));
340         }
341     }
342     return L;
343 }
344 
Edge(size_t _sourceNodeId,size_t _targetNodeId,const Affine3f & _transformation,const Matx66f & _information)345 PoseGraphImpl::Edge::Edge(size_t _sourceNodeId, size_t _targetNodeId, const Affine3f& _transformation,
346                           const Matx66f& _information) :
347                           sourceNodeId(_sourceNodeId),
348                           targetNodeId(_targetNodeId),
349                           pose(_transformation.rotation(), _transformation.translation()),
350                           sqrtInfo(llt6(_information))
351 { }
352 
353 
isValid() const354 bool PoseGraphImpl::isValid() const
355 {
356     size_t numNodes = getNumNodes();
357     size_t numEdges = getNumEdges();
358 
359     if (!numNodes || !numEdges)
360         return false;
361 
362     std::unordered_set<size_t> nodesVisited;
363     std::vector<size_t> nodesToVisit;
364 
365     nodesToVisit.push_back(nodes.begin()->first);
366 
367     bool isGraphConnected = false;
368     while (!nodesToVisit.empty())
369     {
370         size_t currNodeId = nodesToVisit.back();
371         nodesToVisit.pop_back();
372         nodesVisited.insert(currNodeId);
373         // Since each node does not maintain its neighbor list
374         for (size_t i = 0; i < numEdges; i++)
375         {
376             const Edge& potentialEdge = edges.at(i);
377             size_t nextNodeId = (size_t)(-1);
378 
379             if (potentialEdge.sourceNodeId == currNodeId)
380             {
381                 nextNodeId = potentialEdge.targetNodeId;
382             }
383             else if (potentialEdge.targetNodeId == currNodeId)
384             {
385                 nextNodeId = potentialEdge.sourceNodeId;
386             }
387             if (nextNodeId != (size_t)(-1))
388             {
389                 if (nodesVisited.count(nextNodeId) == 0)
390                 {
391                     nodesToVisit.push_back(nextNodeId);
392                 }
393             }
394         }
395     }
396 
397     isGraphConnected = (nodesVisited.size() == numNodes);
398 
399     CV_LOG_INFO(NULL, "nodesVisited: " << nodesVisited.size() << " IsGraphConnected: " << isGraphConnected);
400 
401     bool invalidEdgeNode = false;
402     for (size_t i = 0; i < numEdges; i++)
403     {
404         const Edge& edge = edges.at(i);
405         // edges have spurious source/target nodes
406         if ((nodesVisited.count(edge.sourceNodeId) != 1) ||
407             (nodesVisited.count(edge.targetNodeId) != 1))
408         {
409             invalidEdgeNode = true;
410             break;
411         }
412     }
413     return isGraphConnected && !invalidEdgeNode;
414 }
415 
416 
417 //////////////////////////
418 // Optimization itself //
419 ////////////////////////
420 
poseError(Quatd sourceQuat,Vec3d sourceTrans,Quatd targetQuat,Vec3d targetTrans,Quatd rotMeasured,Vec3d transMeasured,Matx66d sqrtInfoMatrix,bool needJacobians,Matx<double,6,4> & sqj,Matx<double,6,3> & stj,Matx<double,6,4> & tqj,Matx<double,6,3> & ttj,Vec6d & res)421 static inline double poseError(Quatd sourceQuat, Vec3d sourceTrans, Quatd targetQuat, Vec3d targetTrans,
422                                Quatd rotMeasured, Vec3d transMeasured, Matx66d sqrtInfoMatrix, bool needJacobians,
423                                Matx<double, 6, 4>& sqj, Matx<double, 6, 3>& stj,
424                                Matx<double, 6, 4>& tqj, Matx<double, 6, 3>& ttj,
425                                Vec6d& res)
426 {
427     // err_r = 2*Im(conj(rel_r) * measure_r) = 2*Im(conj(target_r) * source_r * measure_r)
428     // err_t = conj(source_r) * (target_t - source_t) * source_r - measure_t
429 
430     Quatd sourceQuatInv = sourceQuat.conjugate();
431     Vec3d deltaTrans = targetTrans - sourceTrans;
432 
433     Quatd relativeQuat = sourceQuatInv * targetQuat;
434     Vec3d relativeTrans = sourceQuatInv.toRotMat3x3(cv::QUAT_ASSUME_UNIT) * deltaTrans;
435 
436     //! Definition should actually be relativeQuat * rotMeasured.conjugate()
437     Quatd deltaRot = relativeQuat.conjugate() * rotMeasured;
438 
439     Vec3d terr = relativeTrans - transMeasured;
440     Vec3d rerr = 2.0 * Vec3d(deltaRot.x, deltaRot.y, deltaRot.z);
441     Vec6d rterr(terr[0], terr[1], terr[2], rerr[0], rerr[1], rerr[2]);
442 
443     res = sqrtInfoMatrix * rterr;
444 
445     if (needJacobians)
446     {
447         // d(err_r) = 2*Im(d(conj(target_r) * source_r * measure_r)) = < measure_r is constant > =
448         // 2*Im((conj(d(target_r)) * source_r + conj(target_r) * d(source_r)) * measure_r)
449         // d(target_r) == 0:
450         //  # d(err_r) = 2*Im(conj(target_r) * d(source_r) * measure_r)
451         //  # V(d(err_r)) = 2 * M_Im * M_right(measure_r) * M_left(conj(target_r)) * V(d(source_r))
452         //  # d(err_r) / d(source_r) = 2 * M_Im * M_right(measure_r) * M_left(conj(target_r))
453         Matx34d drdsq = 2.0 * (m_right(rotMeasured) * m_left(targetQuat.conjugate())).get_minor<3, 4>(1, 0);
454 
455         // d(source_r) == 0:
456         //  # d(err_r) = 2*Im(conj(d(target_r)) * source_r * measure_r)
457         //  # V(d(err_r)) = 2 * M_Im * M_right(source_r * measure_r) * M_Conj * V(d(target_r))
458         //  # d(err_r) / d(target_r) = 2 * M_Im * M_right(source_r * measure_r) * M_Conj
459         Matx34d drdtq = 2.0 * (m_right(sourceQuat * rotMeasured) * M_Conj).get_minor<3, 4>(1, 0);
460 
461         // d(err_t) = d(conj(source_r) * (target_t - source_t) * source_r) =
462         // conj(source_r) * (d(target_t) - d(source_t)) * source_r +
463         // conj(d(source_r)) * (target_t - source_t) * source_r +
464         // conj(source_r) * (target_t - source_t) * d(source_r) =
465         // <conj(a*b) == conj(b)*conj(a), conj(target_t - source_t) = - (target_t - source_t), 2 * Im(x) = (x - conj(x))>
466         // conj(source_r) * (d(target_t) - d(source_t)) * source_r +
467         // 2 * Im(conj(source_r) * (target_t - source_t) * d(source_r))
468         // d(*_t) == 0:
469         //  # d(err_t) = 2 * Im(conj(source_r) * (target_t - source_t) * d(source_r))
470         //  # V(d(err_t)) = 2 * M_Im * M_left(conj(source_r) * (target_t - source_t)) * V(d(source_r))
471         //  # d(err_t) / d(source_r) = 2 * M_Im * M_left(conj(source_r) * (target_t - source_t))
472         Matx34d dtdsq = 2 * m_left(sourceQuatInv * Quatd(0, deltaTrans[0], deltaTrans[1], deltaTrans[2])).get_minor<3, 4>(1, 0);
473         // deltaTrans is rotated by sourceQuatInv, so the jacobian is rot matrix of sourceQuatInv by +1 or -1
474         Matx33d dtdtt = sourceQuatInv.toRotMat3x3(QUAT_ASSUME_UNIT);
475         Matx33d dtdst = -dtdtt;
476 
477         Matx33d z;
478         sqj = concatVert(dtdsq, drdsq);
479         tqj = concatVert(Matx34d(), drdtq);
480         stj = concatVert(dtdst, z);
481         ttj = concatVert(dtdtt, z);
482 
483         stj = sqrtInfoMatrix * stj;
484         ttj = sqrtInfoMatrix * ttj;
485         sqj = sqrtInfoMatrix * sqj;
486         tqj = sqrtInfoMatrix * tqj;
487     }
488 
489     return res.ddot(res);
490 }
491 
492 
calcEnergy() const493 double PoseGraphImpl::calcEnergy() const
494 {
495     return calcEnergyNodes(nodes);
496 }
497 
498 
499 // estimate current energy
calcEnergyNodes(const std::map<size_t,Node> & newNodes) const500 double PoseGraphImpl::calcEnergyNodes(const std::map<size_t, Node>& newNodes) const
501 {
502     double totalErr = 0;
503     for (const auto& e : edges)
504     {
505         Pose3d srcP = newNodes.at(e.sourceNodeId).pose;
506         Pose3d tgtP = newNodes.at(e.targetNodeId).pose;
507 
508         Vec6d res;
509         Matx<double, 6, 3> stj, ttj;
510         Matx<double, 6, 4> sqj, tqj;
511         double err = poseError(srcP.q, srcP.t, tgtP.q, tgtP.t, e.pose.q, e.pose.t, e.sqrtInfo,
512                                /* needJacobians = */ false, sqj, stj, tqj, ttj, res);
513 
514         totalErr += err;
515     }
516     return totalErr * 0.5;
517 };
518 
519 
520 #if defined(HAVE_EIGEN)
521 
522 // from Ceres, equation energy change:
523 // eq. energy = 1/2 * (residuals + J * step)^2 =
524 // 1/2 * ( residuals^2 + 2 * residuals^T * J * step + (J*step)^T * J * step)
525 // eq. energy change = 1/2 * residuals^2 - eq. energy =
526 // residuals^T * J * step + 1/2 * (J*step)^T * J * step =
527 // (residuals^T * J + 1/2 * step^T * J^T * J) * step =
528 // step^T * ((residuals^T * J)^T + 1/2 * (step^T * J^T * J)^T) =
529 // 1/2 * step^T * (2 * J^T * residuals + J^T * J * step) =
530 // 1/2 * step^T * (2 * J^T * residuals + (J^T * J + LMDiag - LMDiag) * step) =
531 // 1/2 * step^T * (2 * J^T * residuals + (J^T * J + LMDiag) * step - LMDiag * step) =
532 // 1/2 * step^T * (J^T * residuals - LMDiag * step) =
533 // 1/2 * x^T * (jtb - lmDiag^T * x)
calcJacCostChange(const std::vector<double> & jtb,const std::vector<double> & x,const std::vector<double> & lmDiag)534 static inline double calcJacCostChange(const std::vector<double>& jtb,
535                                        const std::vector<double>& x,
536                                        const std::vector<double>& lmDiag)
537 {
538     double jdiag = 0.0;
539     for (size_t i = 0; i < x.size(); i++)
540     {
541         jdiag += x[i] * (jtb[i] - lmDiag[i] * x[i]);
542     }
543     double costChange = jdiag * 0.5;
544     return costChange;
545 };
546 
547 
548 // J := J * d_inv, d_inv = make_diag(di)
549 // J^T*J := (J * d_inv)^T * J * d_inv = diag(di)* (J^T * J)* diag(di) = eltwise_mul(J^T*J, di*di^T)
550 // J^T*b := (J * d_inv)^T * b = d_inv^T * J^T*b = eltwise_mul(J^T*b, di)
doJacobiScaling(BlockSparseMat<double,6,6> & jtj,std::vector<double> & jtb,const std::vector<double> & di)551 static inline void doJacobiScaling(BlockSparseMat<double, 6, 6>& jtj, std::vector<double>& jtb, const std::vector<double>& di)
552 {
553     // scaling J^T*J
554     for (auto& ijv : jtj.ijValue)
555     {
556         Point2i bpt = ijv.first;
557         Matx66d& m = ijv.second;
558         for (int i = 0; i < 6; i++)
559         {
560             for (int j = 0; j < 6; j++)
561             {
562                 Point2i pt(bpt.x * 6 + i, bpt.y * 6 + j);
563                 m(i, j) *= di[pt.x] * di[pt.y];
564             }
565         }
566     }
567 
568     // scaling J^T*b
569     for (size_t i = 0; i < di.size(); i++)
570     {
571         jtb[i] *= di[i];
572     }
573 }
574 
575 
optimize(const cv::TermCriteria & tc)576 int PoseGraphImpl::optimize(const cv::TermCriteria& tc)
577 {
578     if (!isValid())
579     {
580         CV_LOG_INFO(NULL, "Invalid PoseGraph that is either not connected or has invalid nodes");
581         return -1;
582     }
583 
584     size_t numNodes = getNumNodes();
585     size_t numEdges = getNumEdges();
586 
587     // Allocate indices for nodes
588     std::vector<size_t> placesIds;
589     std::map<size_t, size_t> idToPlace;
590     for (const auto& ni : nodes)
591     {
592         if (!ni.second.isFixed)
593         {
594             idToPlace[ni.first] = placesIds.size();
595             placesIds.push_back(ni.first);
596         }
597     }
598 
599     size_t nVarNodes = placesIds.size();
600     if (!nVarNodes)
601     {
602         CV_LOG_INFO(NULL, "PoseGraph contains no non-constant nodes, skipping optimization");
603         return -1;
604     }
605 
606     if (numEdges == 0)
607     {
608         CV_LOG_INFO(NULL, "PoseGraph has no edges, no optimization to be done");
609         return -1;
610     }
611 
612     CV_LOG_INFO(NULL, "Optimizing PoseGraph with " << numNodes << " nodes and " << numEdges << " edges");
613 
614     size_t nVars = nVarNodes * 6;
615     BlockSparseMat<double, 6, 6> jtj(nVarNodes);
616     std::vector<double> jtb(nVars);
617 
618     double energy = calcEnergyNodes(nodes);
619     double oldEnergy = energy;
620 
621     CV_LOG_INFO(NULL, "#s" << " energy: " << energy);
622 
623     // options
624     // stop conditions
625     bool checkIterations = (tc.type & TermCriteria::COUNT);
626     bool checkEps = (tc.type & TermCriteria::EPS);
627     const unsigned int maxIterations = tc.maxCount;
628     const double minGradientTolerance = 1e-6;
629     const double stepNorm2Tolerance = 1e-6;
630     const double relEnergyDeltaTolerance = tc.epsilon;
631     // normalize jacobian columns for better conditioning
632     // slows down sparse solver, but maybe this'd be useful for some other solver
633     const bool jacobiScaling = false;
634     const double minDiag = 1e-6;
635     const double maxDiag = 1e32;
636 
637     const double initialLambdaLevMarq = 0.0001;
638     const double initialLmUpFactor = 2.0;
639     const double initialLmDownFactor = 3.0;
640 
641     // finish reasons
642     bool tooLong          = false; // => not found
643     bool smallGradient    = false; // => found
644     bool smallStep        = false; // => found
645     bool smallEnergyDelta = false; // => found
646 
647     // column scale inverted, for jacobian scaling
648     std::vector<double> di(nVars);
649 
650     double lmUpFactor = initialLmUpFactor;
651     double lambdaLevMarq = initialLambdaLevMarq;
652 
653     unsigned int iter = 0;
654     bool done = false;
655     while (!done)
656     {
657         jtj.clear();
658         std::fill(jtb.begin(), jtb.end(), 0.0);
659 
660         // caching nodes jacobians
661         std::vector<cv::Matx<double, 7, 6>> cachedJac;
662         for (auto id : placesIds)
663         {
664             Pose3d p = nodes.at(id).pose;
665             Matx43d qj = expQuatJacobian(p.q);
666             // x node layout is (rot_x, rot_y, rot_z, trans_x, trans_y, trans_z)
667             // pose layout is (q_w, q_x, q_y, q_z, trans_x, trans_y, trans_z)
668             Matx<double, 7, 6> j = concatVert(concatHor(qj, Matx43d()),
669                                               concatHor(Matx33d(), Matx33d::eye()));
670             cachedJac.push_back(j);
671         }
672 
673         // fill jtj and jtb
674         for (const auto& e : edges)
675         {
676             size_t srcId = e.sourceNodeId, dstId = e.targetNodeId;
677             const Node& srcNode = nodes.at(srcId);
678             const Node& dstNode = nodes.at(dstId);
679 
680             Pose3d srcP = srcNode.pose;
681             Pose3d tgtP = dstNode.pose;
682             bool srcFixed = srcNode.isFixed;
683             bool dstFixed = dstNode.isFixed;
684 
685             Vec6d res;
686             Matx<double, 6, 3> stj, ttj;
687             Matx<double, 6, 4> sqj, tqj;
688             poseError(srcP.q, srcP.t, tgtP.q, tgtP.t, e.pose.q, e.pose.t, e.sqrtInfo,
689                       /* needJacobians = */ true, sqj, stj, tqj, ttj, res);
690 
691             size_t srcPlace = (size_t)(-1), dstPlace = (size_t)(-1);
692             Matx66d sj, tj;
693             if (!srcFixed)
694             {
695                 srcPlace = idToPlace.at(srcId);
696                 sj = concatHor(sqj, stj) * cachedJac[srcPlace];
697 
698                 jtj.refBlock(srcPlace, srcPlace) += sj.t() * sj;
699 
700                 Vec6f jtbSrc = sj.t() * res;
701                 for (int i = 0; i < 6; i++)
702                 {
703                     jtb[6 * srcPlace + i] += -jtbSrc[i];
704                 }
705             }
706 
707             if (!dstFixed)
708             {
709                 dstPlace = idToPlace.at(dstId);
710                 tj = concatHor(tqj, ttj) * cachedJac[dstPlace];
711 
712                 jtj.refBlock(dstPlace, dstPlace) += tj.t() * tj;
713 
714                 Vec6f jtbDst = tj.t() * res;
715                 for (int i = 0; i < 6; i++)
716                 {
717                     jtb[6 * dstPlace + i] += -jtbDst[i];
718                 }
719             }
720 
721             if (!(srcFixed || dstFixed))
722             {
723                 Matx66d sjttj = sj.t() * tj;
724                 jtj.refBlock(srcPlace, dstPlace) += sjttj;
725                 jtj.refBlock(dstPlace, srcPlace) += sjttj.t();
726             }
727         }
728 
729         CV_LOG_INFO(NULL, "#LM#s" << " energy: " << energy);
730 
731         // do the jacobian conditioning improvement used in Ceres
732         if (jacobiScaling)
733         {
734             // L2-normalize each jacobian column
735             // vec d = {d_j = sum(J_ij^2) for each column j of J} = get_diag{ J^T * J }
736             // di = { 1/(1+sqrt(d_j)) }, extra +1 to avoid div by zero
737             if (iter == 0)
738             {
739                 for (size_t i = 0; i < nVars; i++)
740                 {
741                     double ds = sqrt(jtj.valElem(i, i)) + 1.0;
742                     di[i] = 1.0 / ds;
743                 }
744             }
745 
746             doJacobiScaling(jtj, jtb, di);
747         }
748 
749         double gradientMax = 0.0;
750         // gradient max
751         for (size_t i = 0; i < nVars; i++)
752         {
753             gradientMax = std::max(gradientMax, abs(jtb[i]));
754         }
755 
756         // Save original diagonal of jtj matrix for LevMarq
757         std::vector<double> diag(nVars);
758         for (size_t i = 0; i < nVars; i++)
759         {
760             diag[i] = jtj.valElem(i, i);
761         }
762 
763         // Solve using LevMarq and get delta transform
764         bool enoughLm = false;
765 
766         decltype(nodes) tempNodes = nodes;
767 
768         while (!enoughLm && !done)
769         {
770             // form LevMarq matrix
771             std::vector<double> lmDiag(nVars);
772             for (size_t i = 0; i < nVars; i++)
773             {
774                 double v = diag[i];
775                 double ld = std::min(max(v * lambdaLevMarq, minDiag), maxDiag);
776                 lmDiag[i] = ld;
777                 jtj.refElem(i, i) = v + ld;
778             }
779 
780             CV_LOG_INFO(NULL, "sparse solve...");
781 
782             // use double or convert everything to float
783             std::vector<double> x;
784             bool solved = jtj.sparseSolve(jtb, x, false);
785 
786             CV_LOG_INFO(NULL, (solved ? "OK" : "FAIL"));
787 
788             double costChange = 0.0;
789             double jacCostChange = 0.0;
790             double stepQuality = 0.0;
791             double xNorm2 = 0.0;
792             if (solved)
793             {
794                 jacCostChange = calcJacCostChange(jtb, x, lmDiag);
795 
796                 // x squared norm
797                 for (size_t i = 0; i < nVars; i++)
798                 {
799                     xNorm2 += x[i] * x[i];
800                 }
801 
802                 // undo jacobi scaling
803                 if (jacobiScaling)
804                 {
805                     for (size_t i = 0; i < nVars; i++)
806                     {
807                         x[i] *= di[i];
808                     }
809                 }
810 
811                 tempNodes = nodes;
812 
813                 // Update temp nodes using x
814                 for (size_t i = 0; i < nVarNodes; i++)
815                 {
816                     Vec6d dx(&x[i * 6]);
817                     Vec3d deltaRot(dx[0], dx[1], dx[2]), deltaTrans(dx[3], dx[4], dx[5]);
818                     Pose3d& p = tempNodes.at(placesIds[i]).pose;
819 
820                     p.q = Quatd(0, deltaRot[0], deltaRot[1], deltaRot[2]).exp() * p.q;
821                     p.t += deltaTrans;
822                 }
823 
824                 // calc energy with temp nodes
825                 energy = calcEnergyNodes(tempNodes);
826 
827                 costChange = oldEnergy - energy;
828 
829                 stepQuality = costChange / jacCostChange;
830 
831                 CV_LOG_INFO(NULL, "#LM#" << iter
832                                << " energy: " << energy
833                                << " deltaEnergy: " << costChange
834                                << " deltaEqEnergy: " << jacCostChange
835                                << " max(J^T*b): " << gradientMax
836                                << " norm2(x): " << xNorm2
837                                << " deltaEnergy/energy: " << costChange / energy);
838             }
839 
840             if (!solved || costChange < 0)
841             {
842                 // failed to optimize, increase lambda and repeat
843 
844                 lambdaLevMarq *= lmUpFactor;
845                 lmUpFactor *= 2.0;
846 
847                 CV_LOG_INFO(NULL, "LM goes up, lambda: " << lambdaLevMarq << ", old energy: " << oldEnergy);
848             }
849             else
850             {
851                 // optimized successfully, decrease lambda and set variables for next iteration
852                 enoughLm = true;
853 
854                 lambdaLevMarq *= std::max(1.0 / initialLmDownFactor, 1.0 - pow(2.0 * stepQuality - 1.0, 3));
855                 lmUpFactor = initialLmUpFactor;
856 
857                 smallGradient = (gradientMax < minGradientTolerance);
858                 smallStep = (xNorm2 < stepNorm2Tolerance);
859                 smallEnergyDelta = (costChange / energy < relEnergyDeltaTolerance);
860 
861                 nodes = tempNodes;
862 
863                 CV_LOG_INFO(NULL, "#" << iter << " energy: " << energy);
864 
865                 oldEnergy = energy;
866 
867                 CV_LOG_INFO(NULL, "LM goes down, lambda: " << lambdaLevMarq << " step quality: " << stepQuality);
868             }
869 
870             iter++;
871 
872             tooLong = (iter >= maxIterations);
873 
874             done = ( (checkIterations && tooLong) || smallGradient || smallStep || (checkEps && smallEnergyDelta) );
875         }
876     }
877 
878     // if maxIterations is given as a stop criteria, let's consider tooLong as a successful finish
879     bool found = (smallGradient || smallStep || smallEnergyDelta || (checkIterations && tooLong));
880 
881     CV_LOG_INFO(NULL, "Finished: " << (found ? "" : "not") << "found");
882     if (smallGradient)
883         CV_LOG_INFO(NULL, "Finish reason: gradient max val dropped below threshold");
884     if (smallStep)
885         CV_LOG_INFO(NULL, "Finish reason: step size dropped below threshold");
886     if (smallEnergyDelta)
887         CV_LOG_INFO(NULL, "Finish reason: relative energy change between iterations dropped below threshold");
888     if (tooLong)
889         CV_LOG_INFO(NULL, "Finish reason: max number of iterations reached");
890 
891     return (found ? iter : -1);
892 }
893 
894 #else
optimize(const cv::TermCriteria &)895 int PoseGraphImpl::optimize(const cv::TermCriteria& /*tc*/)
896 {
897     CV_Error(Error::StsNotImplemented, "Eigen library required for sparse matrix solve during pose graph optimization, dense solver is not implemented");
898 }
899 #endif
900 
901 
create()902 Ptr<detail::PoseGraph> detail::PoseGraph::create()
903 {
904     return makePtr<PoseGraphImpl>();
905 }
906 
~PoseGraph()907 PoseGraph::~PoseGraph() { }
908 
909 }  // namespace detail
910 }  // namespace kinfu
911 }  // namespace cv
912