mirror of
https://github.com/AtsushiSakai/PythonRobotics.git
synced 2026-01-12 23:58:04 -05:00
181 lines
5.3 KiB
Python
181 lines
5.3 KiB
Python
# Copyright (c) 2020 Jeff Irion and contributors
|
|
#
|
|
# This file originated from the `graphslam` package:
|
|
#
|
|
# https://github.com/JeffLIrion/python-graphslam
|
|
|
|
r"""A class for odometry edges.
|
|
|
|
"""
|
|
|
|
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
#: The difference that will be used for numerical differentiation
|
|
EPSILON = 1e-6
|
|
|
|
|
|
class EdgeOdometry:
|
|
r"""A class for representing odometry edges in Graph SLAM.
|
|
|
|
Parameters
|
|
----------
|
|
vertices : list[graphslam.vertex.Vertex]
|
|
A list of the vertices constrained by the edge
|
|
information : np.ndarray
|
|
The information matrix :math:`\Omega_j` associated with the edge
|
|
estimate : graphslam.pose.se2.PoseSE2
|
|
The expected measurement :math:`\mathbf{z}_j`
|
|
|
|
Attributes
|
|
----------
|
|
vertices : list[graphslam.vertex.Vertex]
|
|
A list of the vertices constrained by the edge
|
|
information : np.ndarray
|
|
The information matrix :math:`\Omega_j` associated with the edge
|
|
estimate : PoseSE2
|
|
The expected measurement :math:`\mathbf{z}_j`
|
|
|
|
"""
|
|
def __init__(self, vertex_ids, information, estimate, vertices=None):
|
|
self.vertex_ids = vertex_ids
|
|
self.information = information
|
|
self.estimate = estimate
|
|
self.vertices = vertices
|
|
|
|
def calc_error(self):
|
|
r"""Calculate the error for the edge: :math:`\mathbf{e}_j \in \mathbb{R}^\bullet`.
|
|
|
|
.. math::
|
|
|
|
\mathbf{e}_j = \mathbf{z}_j - (p_2 \ominus p_1)
|
|
|
|
|
|
Returns
|
|
-------
|
|
np.ndarray
|
|
The error for the edge
|
|
|
|
"""
|
|
return (self.estimate - (self.vertices[1].pose - self.vertices[0].pose)).to_compact()
|
|
|
|
def calc_chi2(self):
|
|
r"""Calculate the :math:`\chi^2` error for the edge.
|
|
|
|
.. math::
|
|
|
|
\mathbf{e}_j^T \Omega_j \mathbf{e}_j
|
|
|
|
|
|
Returns
|
|
-------
|
|
float
|
|
The :math:`\chi^2` error for the edge
|
|
|
|
"""
|
|
err = self.calc_error()
|
|
|
|
return np.dot(np.dot(np.transpose(err), self.information), err)
|
|
|
|
def calc_chi2_gradient_hessian(self):
|
|
r"""Calculate the edge's contributions to the graph's :math:`\chi^2` error, gradient (:math:`\mathbf{b}`), and Hessian (:math:`H`).
|
|
|
|
Returns
|
|
-------
|
|
float
|
|
The :math:`\chi^2` error for the edge
|
|
dict
|
|
The edge's contribution(s) to the gradient
|
|
dict
|
|
The edge's contribution(s) to the Hessian
|
|
|
|
"""
|
|
chi2 = self.calc_chi2()
|
|
|
|
err = self.calc_error()
|
|
|
|
jacobians = self.calc_jacobians()
|
|
|
|
return chi2, {v.index: np.dot(np.dot(np.transpose(err), self.information), jacobian) for v, jacobian in zip(self.vertices, jacobians)}, {(self.vertices[i].index, self.vertices[j].index): np.dot(np.dot(np.transpose(jacobians[i]), self.information), jacobians[j]) for i in range(len(jacobians)) for j in range(i, len(jacobians))}
|
|
|
|
def calc_jacobians(self):
|
|
r"""Calculate the Jacobian of the edge's error with respect to each constrained pose.
|
|
|
|
.. math::
|
|
|
|
\frac{\partial}{\partial \Delta \mathbf{x}^k} \left[ \mathbf{e}_j(\mathbf{x}^k \boxplus \Delta \mathbf{x}^k) \right]
|
|
|
|
|
|
Returns
|
|
-------
|
|
list[np.ndarray]
|
|
The Jacobian matrices for the edge with respect to each constrained pose
|
|
|
|
"""
|
|
err = self.calc_error()
|
|
|
|
# The dimensionality of the compact pose representation
|
|
dim = len(self.vertices[0].pose.to_compact())
|
|
|
|
return [self._calc_jacobian(err, dim, i) for i in range(len(self.vertices))]
|
|
|
|
def _calc_jacobian(self, err, dim, vertex_index):
|
|
r"""Calculate the Jacobian of the edge with respect to the specified vertex's pose.
|
|
|
|
Parameters
|
|
----------
|
|
err : np.ndarray
|
|
The current error for the edge (see :meth:`EdgeOdometry.calc_error`)
|
|
dim : int
|
|
The dimensionality of the compact pose representation
|
|
vertex_index : int
|
|
The index of the vertex (pose) for which we are computing the Jacobian
|
|
|
|
Returns
|
|
-------
|
|
np.ndarray
|
|
The Jacobian of the edge with respect to the specified vertex's pose
|
|
|
|
"""
|
|
jacobian = np.zeros(err.shape + (dim,))
|
|
p0 = self.vertices[vertex_index].pose.copy()
|
|
|
|
for d in range(dim):
|
|
# update the pose
|
|
delta_pose = np.zeros(dim)
|
|
delta_pose[d] = EPSILON
|
|
self.vertices[vertex_index].pose += delta_pose
|
|
|
|
# compute the numerical derivative
|
|
jacobian[:, d] = (self.calc_error() - err) / EPSILON
|
|
|
|
# restore the pose
|
|
self.vertices[vertex_index].pose = p0.copy()
|
|
|
|
return jacobian
|
|
|
|
def to_g2o(self):
|
|
"""Export the edge to the .g2o format.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The edge in .g2o format
|
|
|
|
"""
|
|
return "EDGE_SE2 {} {} {} {} {} ".format(self.vertex_ids[0], self.vertex_ids[1], self.estimate[0], self.estimate[1], self.estimate[2]) + " ".join([str(x) for x in self.information[np.triu_indices(3, 0)]]) + "\n"
|
|
|
|
def plot(self, color='b'):
|
|
"""Plot the edge.
|
|
|
|
Parameters
|
|
----------
|
|
color : str
|
|
The color that will be used to plot the edge
|
|
|
|
"""
|
|
xy = np.array([v.pose.position for v in self.vertices])
|
|
plt.plot(xy[:, 0], xy[:, 1], color=color)
|