Source code for machinevisiontoolbox.blocks.camera

import time

import matplotlib.pyplot as plt
import numpy as np
from bdsim.components import FunctionBlock, SourceBlock, TransferBlock
from bdsim.graphics import GraphicsBlock
from spatialmath import SE3, base

from machinevisiontoolbox import mkgrid

"""
Machine Vision blocks:
- have inputs and outputs
- are a subclass of ``FunctionBlock`` |rarr| ``Block`` for kinematics and have no states
- are a subclass of ``TransferBlock`` |rarr| ``Block`` for dynamics and have states

"""
# The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance.


# ------------------------------------------------------------------------ #
[docs] class Camera(FunctionBlock): """ :blockname:`CAMERA` Camera projection block. """ nin = 2 nout = 1 inlabels = ("P", "ξ") outlabels = ("p",)
[docs] def __init__(self, camera=None, args={}, **blockargs): """ :param camera: Camera model, defaults to None :type camera: Camera subclass, optional :param blockargs: Additional options for the block, passed as a dictionary :type blockargs: dict :return: a CAMERA block :rtype: Camera instance Camera projection model. **Block ports** :input pose: Camera pose as an SE3 object. :input P: world points as ndarray(3,N) :output p: image plane points as ndarray(2,N) """ if camera is None: raise ValueError("camera is not defined") super().__init__(**blockargs) self.camera = camera
def output(self, t, inports, x): return [self.camera.project_point(inports[0], pose=inports[1])]
# ------------------------------------------------------------------------ #
[docs] class Visjac_p(FunctionBlock): """ :blockname:`VISJAC_P` Interaction-matrix block for image points. """ nin = 1 nout = 1 inlabels = ("p",) outlabels = ("J",)
[docs] def __init__(self, camera, depth=1, depthest=False, **blockargs): """ :param camera: Camera model, defaults to None :type camera: Camera subclass, optional :param depth: Point depth :type depth: float or ndarray :param depthest: Use depth estimation, defaults to True :type depthest: bool, optional :param blockargs: Additional options for the block, passed as a dictionary :type blockargs: dict :return: a VISJAC_P block :rtype: Visjac_p instance If the Jacobian """ # TODO allow a depth input if camera is None: raise ValueError("camera is not defined") super().__init__(**blockargs) self.camera = camera self.depthest = depthest self.depth = depth
def output(self, t, inports, x): # do depth estimation here J = self.camera.visjac_p(inports[0], self.depth) return [J]
# ------------------------------------------------------------------------ #
[docs] class EstPose_p(FunctionBlock): """ :blockname:`ESTPOSE_P` Pose-estimation block for image points. """ nin = 1 nout = 1 inlabels = ("p",) outlabels = ("ξ",)
[docs] def __init__(self, camera, P, frame="world", method="iterative", **blockargs): """ :param camera: Camera model, defaults to None :type camera: Camera subclass, optional :param P: World point coordinates :type P: ndarray(2,N) :param frame: return pose of points with respect to reference frame which is one of: 'world' [default] or 'camera' :type frame: str, optional :param method: pose estimation algorithm one of: 'iterative' [default], 'epnp', 'p3p', 'ap3p', 'ippe', 'ippe-square' :type method: str, optional :param blockargs: Additional options for the block, passed as a dictionary :type blockargs: dict :return: a ESTPOSE_P block :rtype: EstPose_p instance """ if camera is None: raise ValueError("camera is not defined") super().__init__(**blockargs) self.camera = camera self.P = P self.method = method
def output(self, t, inports, x): p = inports[0] T = self.camera.estpose(self.P, p, method=self.method) return [T]
# ------------------------------------------------------------------------ #
[docs] class ImagePlane(GraphicsBlock): """ :blockname:`IMAGEPLANE` Graphics block that displays points on the image plane. """ nin = 1 nout = 0
[docs] def __init__( self, camera, style=None, labels=None, grid=True, retain=False, watch=False, init=None, **blockargs, ): """ Create a block that plots image plane coordinates. :param camera: a camera model :type camera: Camera instance :param style: styles for each point to be plotted :type style: str or dict, list of strings or dicts; one per line, optional :param grid: draw a grid, defaults to True. Can be boolean or a tuple of options for grid() :type grid: bool or sequence :param retain: keep previous image plane points, defaults to False :type retain: bool, optional :param watch: add these signals to the watchlist, defaults to False :type watch: bool, optional :param init: function to initialize the graphics, defaults to None :type init: callable, optional :param blockargs: Additional options for the block, passed as a dictionary :type blockargs: dict :return: An IMAGEPLANE block :rtype: ImagePlane instance Create a block that plots points on a camera object's virtual image plane. Examples:: SCOPE() SCOPE(nin=2) SCOPE(nin=2, scale=[-1,2]) SCOPE(styles='k--') SCOPE(styles=[{'color': 'blue'}, {'color': 'red', 'linestyle': '--'}]) SCOPE(styles=['k', 'r--']) """ if camera is None: raise ValueError("camera is not defined") self.camera = camera if style is None: style = {} if isinstance(style, dict): default_style = dict( linestyle="none", marker="o", markersize=4, markeredgecolor="black", markerfacecolor="black", ) self.kwargs = {**default_style, **style} self.args = [] elif isinstance(style, str): self.args = [style] self.kwargs = {} else: raise ValueError("bad style, must be str or dict") if init is not None: assert callable(init), "graphics init function must be callable" self.init = init self.retain = retain super().__init__(nin=1, **blockargs) self.grid = grid self.watch = watch
# TODO, wire width # inherit names from wires, block needs to be able to introspect def start(self, simstate): super().start(simstate) # init the arrays that hold the data self.u_data = [] self.v_data = [] self.t_data = [] # create the figures self.fig = self.create_figure(simstate) self.ax = self.fig.add_subplot(111) self.camera._init_imageplane(ax=self.ax) self.ax.set_title(self.name_tex) print("@@@@@@", self.args, self.kwargs) (self.line,) = self.ax.plot(self.u_data, self.v_data, *self.args, **self.kwargs) # grid control if self.grid is True: self.ax.grid(self.grid) elif isinstance(self.grid, (list, tuple)): self.ax.grid(True, *self.grid) if self.init is not None: self.init(self.camera) if self.watch: for wire in self.inports: plug = wire.start # start plug for input wire # append to the watchlist, bdsim.run() will do the rest state.watchlist.append(plug) state.watchnamelist.append(str(plug)) def step(self, t, inports): # inputs are set self.t_data.append(t) u, v = inports[0] if self.retain: self.u_data.append(u) self.v_data.append(v) else: self.u_data = u self.v_data = v self.line.set_data(self.u_data, self.v_data) super().step(t, inports)