from typing import List, Optional, Union
import motion3d as m3d
import numpy as np
from excalibur.optimization.qcqp import generate_quadratic_cost_matrix
from excalibur.utils.logging import logger
from . import dq
from .base import HandEyeCalibrationBase
from ..utils.fix_translation import fix_translation_hand_eye
from ..utils.conditioning import get_conditioning
[docs]class DualQuaternionQCQP(HandEyeCalibrationBase):
"""| Online Extrinsic Calibration based on Per-Sensor Ego-Motion Using Dual Quaternions
| M. Horn, T. Wodtko, M. Buchholz, and K. Dietmayer
| IEEE Robotics and Automation Letters (Vol. 6, Issue 2), 2021"""
[docs] @staticmethod
def name():
return 'DualQuaternionQCQP'
[docs] def __init__(self, normalize: bool = False):
super().__init__()
self._transforms_a = None
self._Mlist = None
self._Q = None
self._normalize = normalize
[docs] def set_Mlist(self, Mlist, weights=None):
self._Mlist = Mlist
self._Q = generate_quadratic_cost_matrix(self._Mlist, weights, normalize=self._normalize)
[docs] def set_Q(self, Q):
self._Mlist = None
self._Q = Q
[docs] def _calibrate(self, **kwargs):
if self._Q is None:
raise RuntimeError("Q matrix is missing")
# process kwargs
up_approx = None
if 'up_approx' in kwargs:
up_approx = kwargs['up_approx']
del kwargs['up_approx']
# optimize
result = dq.optimization.optimize_qcqp(self._Q, **kwargs)
# post-processing
if result.success:
if 't_norm' in kwargs:
# fix translation
if up_approx is not None:
result = fix_translation_hand_eye(result, self._transforms_a, up_approx=up_approx)
else:
logger.warning("Approximate up vector is required for fixing the translation")
elif result.aux_data['is_global']:
# conditioning
result.aux_data['conditioning'] = get_conditioning(self._Q, result.calib)
return result
[docs]class DualQuaternionQCQPPlanar(DualQuaternionQCQP):
"""| Online Extrinsic Calibration based on Per-Sensor Ego-Motion Using Dual Quaternions
| M. Horn, T. Wodtko, M. Buchholz, and K. Dietmayer
| IEEE Robotics and Automation Letters (Vol. 6, Issue 2), 2021"""
[docs] @staticmethod
def name():
return 'DualQuaternionQCQPPlanar'
[docs] def __init__(self, normalize: bool = False):
super().__init__(normalize=normalize)
self.configure(planar_only=True)
self._plane_a = None
self._plane_b = None
[docs] def _calibrate(self, **kwargs):
if self._plane_a is None or self._plane_b is None:
raise RuntimeError("Planes must be provided before calibrating.")
if self._Q is None:
raise RuntimeError("Q matrix is missing")
# calibrate
result = super()._calibrate(**kwargs)
# adjust transform and return
result.calib = (self._plane_a.inverse() * result.calib * self._plane_b).normalized_()
return result
[docs]class DualQuaternionQCQPScaled(HandEyeCalibrationBase):
"""| Globally Optimal Multi-Scale Monocular Hand-Eye Calibration Using Dual Quaternions
| T. Wodtko, M. Horn, M. Buchholz, and K. Dietmayer
| International Conference on 3D Vision (3DV), 2021"""
[docs] @staticmethod
def name():
return 'DualQuaternionQCQPScaled'
[docs] def __init__(self, normalize: bool = False):
super().__init__()
self._Mlist = None
self._Q = None
self._normalize = normalize
[docs] def set_Mlist(self, Mlist, weights=None):
self._Mlist = Mlist
self._Q = generate_quadratic_cost_matrix(self._Mlist, weights, normalize=self._normalize)
[docs] def set_Q(self, Q):
self._Mlist = None
self._Q = Q
[docs] def _calibrate(self, **kwargs):
if self._Q is None:
raise RuntimeError("Q matrix is missing")
return dq.optimization.optimize_qcqp_scaled(self._Q, **kwargs)