aboutsummaryrefslogtreecommitdiff
path: root/src/argaze/ArUcoMarkers
diff options
context:
space:
mode:
Diffstat (limited to 'src/argaze/ArUcoMarkers')
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoBoard.py79
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoCamera.py72
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoMarkers.py71
-rw-r--r--src/argaze/ArUcoMarkers/ArUcoTracker.py157
-rw-r--r--src/argaze/ArUcoMarkers/README.md13
-rw-r--r--src/argaze/ArUcoMarkers/__init__.py1
-rw-r--r--src/argaze/ArUcoMarkers/utils/A3_board_35cmx25cm_markers_4X4_3cm.pdfbin0 -> 127329 bytes
-rw-r--r--src/argaze/ArUcoMarkers/utils/A4_markers_4x4_3cm.pdfbin0 -> 30225 bytes
-rw-r--r--src/argaze/ArUcoMarkers/utils/_board_A3.afdesignbin0 -> 512504 bytes
-rw-r--r--src/argaze/ArUcoMarkers/utils/_markers.afdesignbin0 -> 533859 bytes
10 files changed, 393 insertions, 0 deletions
diff --git a/src/argaze/ArUcoMarkers/ArUcoBoard.py b/src/argaze/ArUcoMarkers/ArUcoBoard.py
new file mode 100644
index 0000000..fa76303
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/ArUcoBoard.py
@@ -0,0 +1,79 @@
+import numpy
+import cv2 as cv
+import cv2.aruco as aruco
+
+# Built-in ArUco dictionaries from OpenCV library
+ARUCO_DICT = {
+ 'DICT_4X4_50': aruco.DICT_4X4_50,
+ 'DICT_4X4_100': aruco.DICT_4X4_100,
+ 'DICT_4X4_250': aruco.DICT_4X4_250,
+ 'DICT_4X4_1000': aruco.DICT_4X4_1000,
+ 'DICT_5X5_50': aruco.DICT_5X5_50,
+ 'DICT_5X5_100': aruco.DICT_5X5_100,
+ 'DICT_5X5_250': aruco.DICT_5X5_250,
+ 'DICT_5X5_1000': aruco.DICT_5X5_1000,
+ 'DICT_6X6_50': aruco.DICT_6X6_50,
+ 'DICT_6X6_100': aruco.DICT_6X6_100,
+ 'DICT_6X6_250': aruco.DICT_6X6_250,
+ 'DICT_6X6_1000': aruco.DICT_6X6_1000,
+ 'DICT_7X7_50': aruco.DICT_7X7_50,
+ 'DICT_7X7_100': aruco.DICT_7X7_100,
+ 'DICT_7X7_250': aruco.DICT_7X7_250,
+ 'DICT_7X7_1000': aruco.DICT_7X7_1000,
+ 'DICT_ARUCO_ORIGINAL': aruco.DICT_ARUCO_ORIGINAL
+}
+
+class ArUcoBoard():
+
+ # initialisation
+ def __init__(self, aruco_dictionary_name, columns, rows, square_size, marker_size):
+
+ # check aruco dictionary name
+ if ARUCO_DICT.get(aruco_dictionary_name, None) is None:
+ raise NameError(f'Bad ArUco dictionnary name: {aruco_dictionary_name}')
+
+ dict_name_split = aruco_dictionary_name.split('_')
+
+ self.__aruco_dict_format = dict_name_split[1]
+ self.__aruco_dict_number = int(dict_name_split[2])
+
+ # load ArUco dictionary
+ self.__aruco_dict = aruco.Dictionary_get(ARUCO_DICT[aruco_dictionary_name])
+
+ # store property
+ self.__columns = columns
+ self.__rows = rows
+ self.__square_size = square_size # in cm
+ self.__marker_size = marker_size # in cm
+
+ # create board model
+ self.__board = aruco.CharucoBoard_create(self.__columns, self.__rows, self.__square_size/100., self.__marker_size/100., self.__aruco_dict)
+
+ # destruction
+ def __del__(self):
+ pass
+
+ # access to the board model
+ def get_model(self):
+
+ return self.__board
+
+ # access to the board markers ids
+ def get_ids(self):
+
+ return self.__board.ids
+
+ # access to the number of columns and rows
+ def get_size(self):
+
+ return self.__board.getChessboardSize()
+
+ # save a picture of the calibration board
+ def export(self, destination_folder, dpi):
+
+ output_filename = f'board_{self.__columns*self.__square_size}cmx{self.__rows*self.__square_size}cm_markers_{self.__aruco_dict_format}_{self.__marker_size}cm.png'
+
+ dimension = [int(e * self.__board.getSquareLength() * 254 * dpi) for e in self.__board.getChessboardSize()] # 1 meter = 254 inches
+
+ cv.imwrite(f'{destination_folder}/{output_filename}', self.__board.draw(dimension))
+
diff --git a/src/argaze/ArUcoMarkers/ArUcoCamera.py b/src/argaze/ArUcoMarkers/ArUcoCamera.py
new file mode 100644
index 0000000..163391f
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/ArUcoCamera.py
@@ -0,0 +1,72 @@
+import json
+import numpy
+import cv2.aruco as aruco
+
+class ArUcoCamera():
+
+ # initialisation
+ def __init__(self):
+
+ self.__rms = 0 # root mean square error
+ self.__K = [] # camera matrix (focal lengths and principal point)
+ self.__D = [] # distortion coefficients
+
+ # define calibration data
+ self.__corners_set_number = 0
+ self.__corners_set = []
+ self.__corners_set_ids = []
+
+ # destruction
+ def __del__(self):
+ pass
+
+ # load camera calibration data
+ def load_calibration_file(self, camera_calibration_filepath):
+
+ with open(camera_calibration_filepath) as calibration_file:
+
+ calibration_data = json.load(calibration_file)
+
+ self.__rms = calibration_data['rms']
+ self.__K = numpy.asarray(calibration_data['camera matrix'])
+ self.__D = numpy.asarray(calibration_data['distortion coefficients'])
+
+ def save_calibration_file(self, camera_calibration_filepath):
+
+ calibration_data = {'rms': self.__rms, 'camera matrix': self.__K.tolist(), 'distortion coefficients': self.__D.tolist()}
+
+ with open(camera_calibration_filepath, 'w', encoding='utf-8') as calibration_file:
+
+ json.dump(calibration_data, calibration_file, ensure_ascii=False, indent=4)
+
+ def get_rms(self):
+ return self.__rms
+
+ def get_K(self):
+ return self.__K
+
+ def get_D(self):
+ return self.__D
+
+ def calibrate(self, board, frame_width, frame_height):
+
+ if self.__corners_set_number > 0:
+
+ self.__rms, self.__K, self.__D, r, t = aruco.calibrateCameraCharuco(self.__corners_set, self.__corners_set_ids, board.get_model(), [frame_width, frame_height], None, None)
+
+ def reset_calibration_data(self, corners, corners_ids):
+
+ self.__corners_set_number = 0
+ self.__corners_set = []
+ self.__corners_set_ids = []
+
+ def store_calibration_data(self, corners, corners_ids):
+
+ self.__corners_set_number += 1
+ self.__corners_set.append(corners)
+ self.__corners_set_ids.append(corners_ids)
+
+ def get_calibration_data_count(self):
+
+ return self.__corners_set_number
+
diff --git a/src/argaze/ArUcoMarkers/ArUcoMarkers.py b/src/argaze/ArUcoMarkers/ArUcoMarkers.py
new file mode 100644
index 0000000..1499218
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/ArUcoMarkers.py
@@ -0,0 +1,71 @@
+import numpy
+import cv2 as cv
+import cv2.aruco as aruco
+
+# Built-in ArUco dictionaries from OpenCV library
+ARUCO_DICT = {
+ 'DICT_4X4_50': aruco.DICT_4X4_50,
+ 'DICT_4X4_100': aruco.DICT_4X4_100,
+ 'DICT_4X4_250': aruco.DICT_4X4_250,
+ 'DICT_4X4_1000': aruco.DICT_4X4_1000,
+ 'DICT_5X5_50': aruco.DICT_5X5_50,
+ 'DICT_5X5_100': aruco.DICT_5X5_100,
+ 'DICT_5X5_250': aruco.DICT_5X5_250,
+ 'DICT_5X5_1000': aruco.DICT_5X5_1000,
+ 'DICT_6X6_50': aruco.DICT_6X6_50,
+ 'DICT_6X6_100': aruco.DICT_6X6_100,
+ 'DICT_6X6_250': aruco.DICT_6X6_250,
+ 'DICT_6X6_1000': aruco.DICT_6X6_1000,
+ 'DICT_7X7_50': aruco.DICT_7X7_50,
+ 'DICT_7X7_100': aruco.DICT_7X7_100,
+ 'DICT_7X7_250': aruco.DICT_7X7_250,
+ 'DICT_7X7_1000': aruco.DICT_7X7_1000,
+ 'DICT_ARUCO_ORIGINAL': aruco.DICT_ARUCO_ORIGINAL
+}
+
+class ArUcoMarkers():
+
+ # initialisation
+ def __init__(self, aruco_dictionary_name):
+
+ # check aruco dictionary name
+ if ARUCO_DICT.get(aruco_dictionary_name, None) is None:
+ raise NameError(f'Bad ArUco dictionnary name: {aruco_dictionary_name}')
+
+ dict_name_split = aruco_dictionary_name.split('_')
+
+ self.__aruco_dict_format = dict_name_split[1]
+ self.__aruco_dict_number = int(dict_name_split[2])
+
+ # load ArUco dictionary
+ self.__aruco_dict = aruco.Dictionary_get(ARUCO_DICT[aruco_dictionary_name])
+
+ # destruction
+ def __del__(self):
+ pass
+
+ # save one marker
+ def export(self, destination_folder, dpi, i):
+
+ if i >= 0 and i < self.__aruco_dict_number:
+
+ output_filename = f'marker_{self.__aruco_dict_format}_{i}.png'
+
+ # create marker
+ marker = numpy.zeros((dpi, dpi, 1), dtype="uint8")
+ aruco.drawMarker(self.__aruco_dict, i, dpi, marker, 1)
+
+ # save marker into destination folder
+ cv.imwrite(f'{destination_folder}/{output_filename}', marker)
+
+ else:
+ raise ValueError(f'Bad ArUco index: {i}')
+
+ # save all markers
+ def export_all(self, destination_folder, dpi):
+
+ for i in range(self.__aruco_dict_number):
+
+ self.export(destination_folder, dpi, i)
+
+
diff --git a/src/argaze/ArUcoMarkers/ArUcoTracker.py b/src/argaze/ArUcoMarkers/ArUcoTracker.py
new file mode 100644
index 0000000..1b05e4a
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/ArUcoTracker.py
@@ -0,0 +1,157 @@
+import numpy
+import cv2 as cv
+import cv2.aruco as aruco
+
+# Built-in ArUco dictionaries from OpenCV library
+ARUCO_DICT = {
+ 'DICT_4X4_50': aruco.DICT_4X4_50,
+ 'DICT_4X4_100': aruco.DICT_4X4_100,
+ 'DICT_4X4_250': aruco.DICT_4X4_250,
+ 'DICT_4X4_1000': aruco.DICT_4X4_1000,
+ 'DICT_5X5_50': aruco.DICT_5X5_50,
+ 'DICT_5X5_100': aruco.DICT_5X5_100,
+ 'DICT_5X5_250': aruco.DICT_5X5_250,
+ 'DICT_5X5_1000': aruco.DICT_5X5_1000,
+ 'DICT_6X6_50': aruco.DICT_6X6_50,
+ 'DICT_6X6_100': aruco.DICT_6X6_100,
+ 'DICT_6X6_250': aruco.DICT_6X6_250,
+ 'DICT_6X6_1000': aruco.DICT_6X6_1000,
+ 'DICT_7X7_50': aruco.DICT_7X7_50,
+ 'DICT_7X7_100': aruco.DICT_7X7_100,
+ 'DICT_7X7_250': aruco.DICT_7X7_250,
+ 'DICT_7X7_1000': aruco.DICT_7X7_1000,
+ 'DICT_ARUCO_ORIGINAL': aruco.DICT_ARUCO_ORIGINAL
+}
+
+class ArUcoTracker():
+
+ # initialisation
+ def __init__(self, aruco_dictionary_name, marker_length, camera):
+
+ # check aruco dictionary name
+ if ARUCO_DICT.get(aruco_dictionary_name, None) is None:
+ raise NameError(f'Bad ArUco dictionnary name: {aruco_dictionary_name}')
+
+ # load ArUco dictionary
+ self.__aruco_dict = aruco.Dictionary_get(ARUCO_DICT[aruco_dictionary_name])
+
+ # define marker length in centimeter
+ self.__marker_length = marker_length
+
+ # define camera
+ self.__camera = camera
+
+ # setup ArUco detection parameters
+ self.__aruco_param = aruco.DetectorParameters_create()
+ self.__aruco_param.cornerRefinementMethod = aruco.CORNER_REFINE_CONTOUR
+
+ # define tracked markers data
+ self.__markers_corners = []
+ self.__markers_ids = []
+ self.__rvecs = []
+ self.__tvecs = []
+ self.__points = []
+
+ # define tracked board data
+ self.__board = None
+ self.__board_corners_number = 0
+ self.__board_corners = []
+ self.__board_corners_ids = []
+
+ # destruction
+ def __del__(self):
+ pass
+
+ # track ArUco markers in frame
+ def track(self, frame, estimate_pose = True):
+
+ # DON'T MIRROR FRAME : it makes the markers detection to fail
+
+ # detect markers from gray picture
+ gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
+ self.__markers_corners, self.__markers_ids, rejectedPoints = aruco.detectMarkers(gray, self.__aruco_dict, parameters = self.__aruco_param)
+
+ if len(self.__markers_corners) > 0 and estimate_pose:
+
+ # markers pose estimation
+ self.__rvecs, self.__tvecs, self.__points = aruco.estimatePoseSingleMarkers(self.__markers_corners, self.__marker_length, self.__camera.get_K(), self.__camera.get_D())
+
+ else:
+
+ self.__rvecs = []
+ self.__tvecs = []
+ self.__points = []
+
+ # track ArUco markers board in frame setting up the number of detected markers needed to agree detection
+ def track_board(self, frame, board, expected_markers_number):
+
+ # DON'T MIRROR FRAME : it makes the markers detection to fail
+
+ # detect markers from gray picture
+ gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
+ self.__markers_corners, self.__markers_ids, rejectedPoints = aruco.detectMarkers(gray, self.__aruco_dict, parameters = self.__aruco_param)
+
+ # if all board markers are detected
+ if self.get_markers_number() == expected_markers_number:
+
+ self.__board = board
+ self.__board_corners_number, self.__board_corners, self.__board_corners_ids = aruco.interpolateCornersCharuco(self.__markers_corners, self.__markers_ids, gray, self.__board.get_model())
+
+ else:
+
+ self.__board = None
+ self.__board_corners_number = 0
+ self.__board_corners = []
+ self.__board_corners_ids = []
+
+ # draw tracked markers in frame
+ def draw(self, frame):
+
+ # draw detected markers square
+ if len(self.__markers_corners) > 0:
+
+ aruco.drawDetectedMarkers(frame, self.__markers_corners, self.__markers_ids)
+
+ # draw marker axis if pose has been estimated
+ if len(self.__rvecs) > 0:
+
+ for (i, marker_id) in enumerate(self.__markers_ids):
+
+ aruco.drawAxis(frame, self.__camera.get_K(), self.__camera.get_D(), self.__rvecs[i], self.__tvecs[i], self.__marker_length)
+
+ # draw tracked board corners in frame
+ def draw_board(self, frame):
+
+ if self.__board != None:
+
+ cv.drawChessboardCorners(frame, ((self.__board.get_size()[0] - 1 ), (self.__board.get_size()[1] - 1)), self.__board_corners, True)
+
+ # access to tracked markers data
+ def get_markers_number(self):
+ return len(self.__markers_corners)
+
+ def get_markers_ids(self):
+ return self.__markers_ids
+
+ def get_marker_corners(self, i):
+ return self.__markers_corners[i]
+
+ def get_marker_rotation(self, i):
+ return self.__rvecs[i]
+
+ def get_marker_translation(self, i):
+ return self.__tvecs[i]
+
+ def get_marker_points(self, i):
+ return self.__points[i]
+
+ # access to tracked board data
+ def get_board_corners_number(self):
+ return self.__board_corners_number
+
+ def get_board_corners_ids(self):
+ return self.__board_corners_ids
+
+ def get_board_corners(self):
+ return self.__board_corners
+
diff --git a/src/argaze/ArUcoMarkers/README.md b/src/argaze/ArUcoMarkers/README.md
new file mode 100644
index 0000000..f79be36
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/README.md
@@ -0,0 +1,13 @@
+# ArUcoMarkers
+
+_Class interface to work with OpenCV ArUco markers._
+https://docs.opencv.org/4.x/d5/dae/tutorial_aruco_detection.html
+
+## ArUcoBoard.py
+
+## ArUcoCamera.py
+
+## ArUcoMarkers.py
+
+## ArUcoTracker.py
+
diff --git a/src/argaze/ArUcoMarkers/__init__.py b/src/argaze/ArUcoMarkers/__init__.py
new file mode 100644
index 0000000..188e407
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/__init__.py
@@ -0,0 +1 @@
+__all__ = ['ArUcoBoard', 'ArUcoCamera', 'ArUcoMarkers', 'ArUcoTracker'] \ No newline at end of file
diff --git a/src/argaze/ArUcoMarkers/utils/A3_board_35cmx25cm_markers_4X4_3cm.pdf b/src/argaze/ArUcoMarkers/utils/A3_board_35cmx25cm_markers_4X4_3cm.pdf
new file mode 100644
index 0000000..7725730
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/utils/A3_board_35cmx25cm_markers_4X4_3cm.pdf
Binary files differ
diff --git a/src/argaze/ArUcoMarkers/utils/A4_markers_4x4_3cm.pdf b/src/argaze/ArUcoMarkers/utils/A4_markers_4x4_3cm.pdf
new file mode 100644
index 0000000..412684f
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/utils/A4_markers_4x4_3cm.pdf
Binary files differ
diff --git a/src/argaze/ArUcoMarkers/utils/_board_A3.afdesign b/src/argaze/ArUcoMarkers/utils/_board_A3.afdesign
new file mode 100644
index 0000000..57c6588
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/utils/_board_A3.afdesign
Binary files differ
diff --git a/src/argaze/ArUcoMarkers/utils/_markers.afdesign b/src/argaze/ArUcoMarkers/utils/_markers.afdesign
new file mode 100644
index 0000000..8443b9a
--- /dev/null
+++ b/src/argaze/ArUcoMarkers/utils/_markers.afdesign
Binary files differ