""" This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ __author__ = "Théo de la Hogue" __credits__ = [] __copyright__ = "Copyright 2023, Ecole Nationale de l'Aviation Civile (ENAC)" __license__ = "GPLv3" from dataclasses import dataclass, field import math from argaze.ArUcoMarkers import ArUcoMarkersDictionary import numpy import cv2 import cv2.aruco as aruco @dataclass class ArUcoMarker(): """Define ArUco marker class.""" dictionary: ArUcoMarkersDictionary.ArUcoMarkersDictionary """Dictionary to which it belongs.""" identifier: int """Index into dictionary""" size: float = field(default=math.nan) """Size of marker in centimeters.""" corners: numpy.array = field(init=False, repr=False) """Estimated 2D corners position in camera image referential.""" translation: numpy.array = field(init=False, repr=False) """Estimated 3D center position in camera world referential.""" rotation: numpy.array = field(init=False, repr=False) """Estimated 3D marker rotation in camera world referential.""" points: numpy.array = field(init=False, repr=False) """Estimated 3D corners positions in camera world referential.""" @property def center(self) -> numpy.array: """Get 2D center position in camera image referential.""" return self.corners[0].mean(axis=0) def image(self, dpi) -> numpy.array: """Create marker matrix image at a given resolution. !!! warning Marker size have to be setup before. """ assert(not math.isnan(self.size)) dimension = round(self.size * dpi / 2.54) # 1 cm = 2.54 inches matrix = numpy.zeros((dimension, dimension, 1), dtype="uint8") aruco.generateImageMarker(self.dictionary.markers, self.identifier, dimension, matrix, 1) return numpy.repeat(matrix, 3).reshape(dimension, dimension, 3) def draw(self, image: numpy.array, K: numpy.array, D: numpy.array, color: tuple = None, draw_axes: dict = None): """Draw marker in image. Parameters: image: image where to K: D: color: marker color (if None, no marker drawn) draw_axes: enable marker axes drawing !!! warning draw_axes needs marker size and pose estimation. """ # Draw marker if required if color is not None: aruco.drawDetectedMarkers(image, [numpy.array([list(self.corners)])], numpy.array([self.identifier]), color) # Draw marker axes if pose has been estimated, marker have a size and if required if self.translation.size == 3 and self.rotation.size == 9 and not math.isnan(self.size) and draw_axes is not None: cv2.drawFrameAxes(image, numpy.array(K), numpy.array(D), self.rotation, self.translation, self.size, **draw_axes) def save(self, destination_folder, dpi): """Save marker image as .png file into a destination folder.""" filename = f'{self.dictionary.name}_{self.dictionary.format}_{self.identifier}.png' filepath = f'{destination_folder}/{filename}' cv2.imwrite(filepath, self.image(dpi))