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