calib3d.draw API documentation
Description: Projective Drawing …

# Module `calib3d.draw`

# Projective Drawing

When drawing 3D objets on a 2D canvas, several things need to be considered: - Projection onto the 2D space using the calibration information - Handling lens distortion that make straight lines appear curved - Handling of objects visiblity given the canvas dimensions

Expand source code

    import cv2
    import numpy as np

    from calib3d import Calib, Point2D, Point3D

    __doc__ = r"""

    # Projective Drawing

    When drawing 3D objets on a 2D canvas, several things need to be considered:
    - Projection onto the 2D space using the calibration information
    - Handling lens distortion that make straight lines appear curved
    - Handling of objects visiblity given the canvas dimensions
    """

    class ProjectiveDrawer():
        """ Given the calibration information with `Calib`, and a number of segments
            to decompose straight lines, this objet offer several functions to draw
            on a 2D canvas given 3D coordinates.
        """
        def __init__(self, calib: Calib, color, thickness: int=1, segments: int=10):
            self.color = color
            self.thickness = thickness
            self.calib = calib
            self.segments = segments

        def _polylines(self, canvas, points: Point2D, color=None, thickness: int=None, markersize=None, **kwargs):
            thickness = thickness or self.thickness
            color = color or self.color
            if isinstance(canvas, np.ndarray):
                points = np.array(points.astype(np.int32).T.reshape((-1,1,2)))
                if thickness < 0:
                    cv2.fillPoly(canvas, [points], color=color, **kwargs)
                else:
                    cv2.polylines(canvas, [points], False, color=color, thickness=thickness, **kwargs)
                if markersize:
                    for point in points:
                        cv2.drawMarker(canvas, tuple(point[0]), color=color, markerSize=markersize, thickness=thickness)
            else:
                if thickness < 0:
                    try:
                        import matplotlib as mpl
                        p = mpl.patches.Polygon(np.array(list(zip(points.x, points.y))), facecolor=np.array(color)/255, alpha=.35)
                        canvas.add_patch(p)
                    except ImportError:
                        raise ImportError("The current implementation requires matplotlib")
                else:
                    canvas.plot(points.x, points.y, linewidth=thickness, color=np.array(color)/255, markersize=markersize, **kwargs)

        def polylines(self, canvas, points3D: Point3D, color=None, thickness: int=None, **kwargs):
            self._polylines(canvas, self.calib.project_3D_to_2D(points3D), color=color, thickness=thickness, **kwargs)
            #for point3D1, point3D2 in zip(points3D, points3D.close()[:,1:]):
            #    self.draw_line(canvas, point3D1, point3D2, *args, **kwargs)

        def draw_line(self, canvas, point3D1: Point3D, point3D2: Point3D, color=None, thickness: int=None, only_visible=True, **kwargs):
            if only_visible:
                try:
                    point3D1, point3D2 = visible_segment(self.calib, point3D1, point3D2)
                except ValueError:
                    return
            points3D = Point3D(np.linspace(point3D1, point3D2, self.segments+1))
            self._polylines(canvas, self.calib.project_3D_to_2D(points3D), color=color, thickness=thickness, **kwargs)

        def draw_arc(self, canvas, center, radius, start_angle=0.0, stop_angle=2*np.pi, color=None, thickness=None):
            thickness = thickness or self.thickness
            angles = np.linspace(start_angle, stop_angle, self.segments*4+1)
            xs = np.cos(angles)*radius + center.x
            ys = np.sin(angles)*radius + center.y
            zs = np.ones_like(angles)*center.z
            points3D = Point3D(np.vstack((xs,ys,zs)))
            self._polylines(canvas, self.calib.project_3D_to_2D(points3D), color=color, thickness=thickness)

        def draw_rectangle(self, canvas, point3D1, point3D2):
            c1 = point3D1
            c3 = point3D2
            if point3D1.z == point3D2.z:
                c2 = Point3D(c1.x, c3.y, c1.z)
                c4 = Point3D(c3.x, c1.y, c1.z)
            elif point3D1.x == point3D2.x:
                c2 = Point3D(c1.x, c1.y, c3.z)
                c4 = Point3D(c1.x, c3.y, c1.z)
            elif point3D1.y == point3D2.y:
                c2 = Point3D(c1.x, c1.y, c3.z)
                c4 = Point3D(c3.x, c1.y, c1.z)
            corners = [c1, c2, c3, c4, c1]
            for p1, p2 in zip(corners, corners[1:]):
                self.draw_line(canvas, p1, p2)

        def fill_polygon(self, canvas, points3D):
            points3D = points3D.close().linspace(self.segments)
            self._polylines(canvas, self.calib.project_3D_to_2D(points3D), thickness=-1)

    def visible_segment(calib: Calib, point3D1: Point3D, point3D2: Point3D):
        """ From a segment defined by the given two 3D points, compute the two 3D
            points delimiting the visible segment in the given calib.
        """
        def dichotomy(inside, outside, max_it=10):
            middle = Point3D((inside+outside)/2)
            if max_it == 0:
                return middle
            max_it = max_it - 1
            return dichotomy(middle, outside, max_it) if calib.projects_in(middle) else dichotomy(inside, middle, max_it)
        def find_point_inside(p1, p2, max_it=4):
            assert not calib.projects_in(p1) and not calib.projects_in(p2)
            middle = Point3D((p1+p2)/2)
            if calib.projects_in(middle):
                return middle
            if max_it == 0:
                return None
            point_inside = find_point_inside(middle, p2, max_it-1)
            if point_inside is not None:
                return point_inside
            return find_point_inside(middle, p1, max_it-1)

        p1, p2 = point3D1, point3D2
        if calib.projects_in(p1) and calib.projects_in(p2):
            return p1, p2
        elif calib.projects_in(p1):
            return p1, dichotomy(p1, p2)
        elif calib.projects_in(p2):
            return dichotomy(p2, p1), p2
        else:
            point_inside = find_point_inside(p1, p2)
            if point_inside is None:
                raise ValueError
            return dichotomy(point_inside, p1), dichotomy(point_inside, p2)

## Functions

`def visible_segment(calib: Calib, point3D1: Point3D, point3D2: Point3D)`

  From a segment defined by the given two 3D points, compute the two 3D points delimiting the visible segment in the given calib.

  Expand source code

      def visible_segment(calib: Calib, point3D1: Point3D, point3D2: Point3D):
          """ From a segment defined by the given two 3D points, compute the two 3D
              points delimiting the visible segment in the given calib.
          """
          def dichotomy(inside, outside, max_it=10):
              middle = Point3D((inside+outside)/2)
              if max_it == 0:
                  return middle
              max_it = max_it - 1
              return dichotomy(middle, outside, max_it) if calib.projects_in(middle) else dichotomy(inside, middle, max_it)
          def find_point_inside(p1, p2, max_it=4):
              assert not calib.projects_in(p1) and not calib.projects_in(p2)
              middle = Point3D((p1+p2)/2)
              if calib.projects_in(middle):
                  return middle
              if max_it == 0:
                  return None
              point_inside = find_point_inside(middle, p2, max_it-1)
              if point_inside is not None:
                  return point_inside
              return find_point_inside(middle, p1, max_it-1)

          p1, p2 = point3D1, point3D2
          if calib.projects_in(p1) and calib.projects_in(p2):
              return p1, p2
          elif calib.projects_in(p1):
              return p1, dichotomy(p1, p2)
          elif calib.projects_in(p2):
              return dichotomy(p2, p1), p2
          else:
              point_inside = find_point_inside(p1, p2)
              if point_inside is None:
                  raise ValueError
              return dichotomy(point_inside, p1), dichotomy(point_inside, p2)

## Classes

`class ProjectiveDrawer (calib: Calib, color, thickness: int = 1, segments: int = 10)`

  Given the calibration information with `Calib`, and a number of segments to decompose straight lines, this objet offer several functions to draw on a 2D canvas given 3D coordinates.

  Expand source code

      class ProjectiveDrawer():
          """ Given the calibration information with `Calib`, and a number of segments
              to decompose straight lines, this objet offer several functions to draw
              on a 2D canvas given 3D coordinates.
          """
          def __init__(self, calib: Calib, color, thickness: int=1, segments: int=10):
              self.color = color
              self.thickness = thickness
              self.calib = calib
              self.segments = segments

          def _polylines(self, canvas, points: Point2D, color=None, thickness: int=None, markersize=None, **kwargs):
              thickness = thickness or self.thickness
              color = color or self.color
              if isinstance(canvas, np.ndarray):
                  points = np.array(points.astype(np.int32).T.reshape((-1,1,2)))
                  if thickness < 0:
                      cv2.fillPoly(canvas, [points], color=color, **kwargs)
                  else:
                      cv2.polylines(canvas, [points], False, color=color, thickness=thickness, **kwargs)
                  if markersize:
                      for point in points:
                          cv2.drawMarker(canvas, tuple(point[0]), color=color, markerSize=markersize, thickness=thickness)
              else:
                  if thickness < 0:
                      try:
                          import matplotlib as mpl
                          p = mpl.patches.Polygon(np.array(list(zip(points.x, points.y))), facecolor=np.array(color)/255, alpha=.35)
                          canvas.add_patch(p)
                      except ImportError:
                          raise ImportError("The current implementation requires matplotlib")
                  else:
                      canvas.plot(points.x, points.y, linewidth=thickness, color=np.array(color)/255, markersize=markersize, **kwargs)

          def polylines(self, canvas, points3D: Point3D, color=None, thickness: int=None, **kwargs):
              self._polylines(canvas, self.calib.project_3D_to_2D(points3D), color=color, thickness=thickness, **kwargs)
              #for point3D1, point3D2 in zip(points3D, points3D.close()[:,1:]):
              #    self.draw_line(canvas, point3D1, point3D2, *args, **kwargs)

          def draw_line(self, canvas, point3D1: Point3D, point3D2: Point3D, color=None, thickness: int=None, only_visible=True, **kwargs):
              if only_visible:
                  try:
                      point3D1, point3D2 = visible_segment(self.calib, point3D1, point3D2)
                  except ValueError:
                      return
              points3D = Point3D(np.linspace(point3D1, point3D2, self.segments+1))
              self._polylines(canvas, self.calib.project_3D_to_2D(points3D), color=color, thickness=thickness, **kwargs)

          def draw_arc(self, canvas, center, radius, start_angle=0.0, stop_angle=2*np.pi, color=None, thickness=None):
              thickness = thickness or self.thickness
              angles = np.linspace(start_angle, stop_angle, self.segments*4+1)
              xs = np.cos(angles)*radius + center.x
              ys = np.sin(angles)*radius + center.y
              zs = np.ones_like(angles)*center.z
              points3D = Point3D(np.vstack((xs,ys,zs)))
              self._polylines(canvas, self.calib.project_3D_to_2D(points3D), color=color, thickness=thickness)

          def draw_rectangle(self, canvas, point3D1, point3D2):
              c1 = point3D1
              c3 = point3D2
              if point3D1.z == point3D2.z:
                  c2 = Point3D(c1.x, c3.y, c1.z)
                  c4 = Point3D(c3.x, c1.y, c1.z)
              elif point3D1.x == point3D2.x:
                  c2 = Point3D(c1.x, c1.y, c3.z)
                  c4 = Point3D(c1.x, c3.y, c1.z)
              elif point3D1.y == point3D2.y:
                  c2 = Point3D(c1.x, c1.y, c3.z)
                  c4 = Point3D(c3.x, c1.y, c1.z)
              corners = [c1, c2, c3, c4, c1]
              for p1, p2 in zip(corners, corners[1:]):
                  self.draw_line(canvas, p1, p2)

          def fill_polygon(self, canvas, points3D):
              points3D = points3D.close().linspace(self.segments)
              self._polylines(canvas, self.calib.project_3D_to_2D(points3D), thickness=-1)

  ### Methods

  `def draw_arc(self, canvas, center, radius, start_angle=0.0, stop_angle=6.283185307179586, color=None, thickness=None)`
    Expand source code

        def draw_arc(self, canvas, center, radius, start_angle=0.0, stop_angle=2*np.pi, color=None, thickness=None):
            thickness = thickness or self.thickness
            angles = np.linspace(start_angle, stop_angle, self.segments*4+1)
            xs = np.cos(angles)*radius + center.x
            ys = np.sin(angles)*radius + center.y
            zs = np.ones_like(angles)*center.z
            points3D = Point3D(np.vstack((xs,ys,zs)))
            self._polylines(canvas, self.calib.project_3D_to_2D(points3D), color=color, thickness=thickness)

  `def draw_line(self, canvas, point3D1: Point3D, point3D2: Point3D, color=None, thickness: int = None, only_visible=True, **kwargs)`
    Expand source code

        def draw_line(self, canvas, point3D1: Point3D, point3D2: Point3D, color=None, thickness: int=None, only_visible=True, **kwargs):
            if only_visible:
                try:
                    point3D1, point3D2 = visible_segment(self.calib, point3D1, point3D2)
                except ValueError:
                    return
            points3D = Point3D(np.linspace(point3D1, point3D2, self.segments+1))
            self._polylines(canvas, self.calib.project_3D_to_2D(points3D), color=color, thickness=thickness, **kwargs)

  `def draw_rectangle(self, canvas, point3D1, point3D2)`
    Expand source code

        def draw_rectangle(self, canvas, point3D1, point3D2):
            c1 = point3D1
            c3 = point3D2
            if point3D1.z == point3D2.z:
                c2 = Point3D(c1.x, c3.y, c1.z)
                c4 = Point3D(c3.x, c1.y, c1.z)
            elif point3D1.x == point3D2.x:
                c2 = Point3D(c1.x, c1.y, c3.z)
                c4 = Point3D(c1.x, c3.y, c1.z)
            elif point3D1.y == point3D2.y:
                c2 = Point3D(c1.x, c1.y, c3.z)
                c4 = Point3D(c3.x, c1.y, c1.z)
            corners = [c1, c2, c3, c4, c1]
            for p1, p2 in zip(corners, corners[1:]):
                self.draw_line(canvas, p1, p2)

  `def fill_polygon(self, canvas, points3D)`
    Expand source code

        def fill_polygon(self, canvas, points3D):
            points3D = points3D.close().linspace(self.segments)
            self._polylines(canvas, self.calib.project_3D_to_2D(points3D), thickness=-1)

  `def polylines(self, canvas, points3D: Point3D, color=None, thickness: int = None, **kwargs)`
    Expand source code

        def polylines(self, canvas, points3D: Point3D, color=None, thickness: int=None, **kwargs):
            self._polylines(canvas, self.calib.project_3D_to_2D(points3D), color=color, thickness=thickness, **kwargs)