Files
PythonRobotics/SLAM/GraphBasedSLAM/graphslam/edge/edge_odometry.py

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)