├── Images
├── pose_test.jpg
├── pose_output.gif
├── test_image_1.png
├── output_sample.png
└── pose_output_image.png
├── calibration_matrix.npy
├── distortion_coefficients.npy
├── LICENSE
├── generate_aruco_tags.py
├── detect_aruco_images.py
├── detect_aruco_video.py
├── utils.py
├── pose_estimation.py
├── calibration.py
└── README.md
/Images/pose_test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSNCodes/ArUCo-Markers-Pose-Estimation-Generation-Python/HEAD/Images/pose_test.jpg
--------------------------------------------------------------------------------
/Images/pose_output.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSNCodes/ArUCo-Markers-Pose-Estimation-Generation-Python/HEAD/Images/pose_output.gif
--------------------------------------------------------------------------------
/Images/test_image_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSNCodes/ArUCo-Markers-Pose-Estimation-Generation-Python/HEAD/Images/test_image_1.png
--------------------------------------------------------------------------------
/calibration_matrix.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSNCodes/ArUCo-Markers-Pose-Estimation-Generation-Python/HEAD/calibration_matrix.npy
--------------------------------------------------------------------------------
/Images/output_sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSNCodes/ArUCo-Markers-Pose-Estimation-Generation-Python/HEAD/Images/output_sample.png
--------------------------------------------------------------------------------
/Images/pose_output_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSNCodes/ArUCo-Markers-Pose-Estimation-Generation-Python/HEAD/Images/pose_output_image.png
--------------------------------------------------------------------------------
/distortion_coefficients.npy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSNCodes/ArUCo-Markers-Pose-Estimation-Generation-Python/HEAD/distortion_coefficients.npy
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 SowmiyaNarayanan G
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/generate_aruco_tags.py:
--------------------------------------------------------------------------------
1 | '''
2 | Sample Command:-
3 | python generate_aruco_tags.py --id 24 --type DICT_5X5_100 -o tags/
4 | '''
5 |
6 |
7 | import numpy as np
8 | import argparse
9 | from utils import ARUCO_DICT
10 | import cv2
11 | import sys
12 |
13 |
14 | ap = argparse.ArgumentParser()
15 | ap.add_argument("-o", "--output", required=True, help="path to output folder to save ArUCo tag")
16 | ap.add_argument("-i", "--id", type=int, required=True, help="ID of ArUCo tag to generate")
17 | ap.add_argument("-t", "--type", type=str, default="DICT_ARUCO_ORIGINAL", help="type of ArUCo tag to generate")
18 | ap.add_argument("-s", "--size", type=int, default=200, help="Size of the ArUCo tag")
19 | args = vars(ap.parse_args())
20 |
21 |
22 | # Check to see if the dictionary is supported
23 | if ARUCO_DICT.get(args["type"], None) is None:
24 | print(f"ArUCo tag type '{args['type']}' is not supported")
25 | sys.exit(0)
26 |
27 | arucoDict = cv2.aruco.Dictionary_get(ARUCO_DICT[args["type"]])
28 |
29 | print("Generating ArUCo tag of type '{}' with ID '{}'".format(args["type"], args["id"]))
30 | tag_size = args["size"]
31 | tag = np.zeros((tag_size, tag_size, 1), dtype="uint8")
32 | cv2.aruco.drawMarker(arucoDict, args["id"], tag_size, tag, 1)
33 |
34 | # Save the tag generated
35 | tag_name = f'{args["output"]}/{args["type"]}_id_{args["id"]}.png'
36 | cv2.imwrite(tag_name, tag)
37 | cv2.imshow("ArUCo Tag", tag)
38 | cv2.waitKey(0)
39 | cv2.destroyAllWindows()
--------------------------------------------------------------------------------
/detect_aruco_images.py:
--------------------------------------------------------------------------------
1 | '''
2 | Sample Command:-
3 | python detect_aruco_images.py --image Images/test_image_1.png --type DICT_5X5_100
4 | '''
5 | import numpy as np
6 | from utils import ARUCO_DICT, aruco_display
7 | import argparse
8 | import cv2
9 | import sys
10 |
11 |
12 | ap = argparse.ArgumentParser()
13 | ap.add_argument("-i", "--image", required=True, help="path to input image containing ArUCo tag")
14 | ap.add_argument("-t", "--type", type=str, default="DICT_ARUCO_ORIGINAL", help="type of ArUCo tag to detect")
15 | ap.add_argument("-s", "--save_output", action="store_false")
16 | args = vars(ap.parse_args())
17 |
18 |
19 |
20 | print("Loading image...")
21 | image = cv2.imread(args["image"])
22 | h,w,_ = image.shape
23 | width=600
24 | height = int(width*(h/w))
25 | image = cv2.resize(image, (width, height), interpolation=cv2.INTER_CUBIC)
26 |
27 |
28 | # verify that the supplied ArUCo tag exists and is supported by OpenCV
29 | if ARUCO_DICT.get(args["type"], None) is None:
30 | print(f"ArUCo tag type '{args['type']}' is not supported")
31 | sys.exit(0)
32 |
33 | # load the ArUCo dictionary, grab the ArUCo parameters, and detect
34 | # the markers
35 | print("Detecting '{}' tags....".format(args["type"]))
36 | arucoDict = cv2.aruco.getPredefinedDictionary(ARUCO_DICT[args["type"]])
37 | arucoParams = cv2.aruco.DetectorParameters()
38 | arucoDetector = cv2.aruco.ArucoDetector(arucoDict, arucoParams)
39 | corners, ids, rejected = arucoDetector.detectMarkers(image)
40 |
41 | detected_markers = aruco_display(corners, ids, rejected, image)
42 | cv2.imshow("Image", detected_markers)
43 |
44 | if args["save_output"]:
45 | cv2.imwrite("output_sample.png",detected_markers)
46 |
47 | cv2.waitKey(0)
--------------------------------------------------------------------------------
/detect_aruco_video.py:
--------------------------------------------------------------------------------
1 | '''
2 | Sample Command:-
3 | python detect_aruco_video.py --type DICT_5X5_100 --camera True
4 | python detect_aruco_video.py --type DICT_5X5_100 --camera False --video test_video.mp4
5 | '''
6 |
7 | import numpy as np
8 | from utils import ARUCO_DICT, aruco_display
9 | import argparse
10 | import time
11 | import cv2
12 | import sys
13 |
14 |
15 | ap = argparse.ArgumentParser()
16 | ap.add_argument("-i", "--camera", required=True, help="Set to True if using webcam")
17 | ap.add_argument("-v", "--video", help="Path to the video file")
18 | ap.add_argument("-t", "--type", type=str, default="DICT_ARUCO_ORIGINAL", help="Type of ArUCo tag to detect")
19 | args = vars(ap.parse_args())
20 |
21 | if args["camera"].lower() == "true":
22 | video = cv2.VideoCapture(0)
23 | time.sleep(2.0)
24 |
25 | else:
26 | if args["video"] is None:
27 | print("[Error] Video file location is not provided")
28 | sys.exit(1)
29 |
30 | video = cv2.VideoCapture(args["video"])
31 |
32 | if ARUCO_DICT.get(args["type"], None) is None:
33 | print(f"ArUCo tag type '{args['type']}' is not supported")
34 | sys.exit(0)
35 |
36 | arucoDict = cv2.aruco.getPredefinedDictionary(ARUCO_DICT[args["type"]])
37 | arucoParams = cv2.aruco.DetectorParameters()
38 | arucoDetector = cv2.aruco.ArucoDetector(arucoDict, arucoParams)
39 |
40 | while True:
41 | ret, frame = video.read()
42 |
43 | if ret is False:
44 | break
45 |
46 | h, w, _ = frame.shape
47 |
48 | width=1000
49 | height = int(width*(h/w))
50 | frame = cv2.resize(frame, (width, height), interpolation=cv2.INTER_CUBIC)
51 | corners, ids, rejected = arucoDetector.detectMarkers(frame)
52 |
53 | detected_markers = aruco_display(corners, ids, rejected, frame)
54 |
55 | cv2.imshow("Image", detected_markers)
56 |
57 | key = cv2.waitKey(1) & 0xFF
58 | if key == ord("q"):
59 | break
60 |
61 | cv2.destroyAllWindows()
62 | video.release()
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | import cv2
2 |
3 | ARUCO_DICT = {
4 | "DICT_4X4_50": cv2.aruco.DICT_4X4_50,
5 | "DICT_4X4_100": cv2.aruco.DICT_4X4_100,
6 | "DICT_4X4_250": cv2.aruco.DICT_4X4_250,
7 | "DICT_4X4_1000": cv2.aruco.DICT_4X4_1000,
8 | "DICT_5X5_50": cv2.aruco.DICT_5X5_50,
9 | "DICT_5X5_100": cv2.aruco.DICT_5X5_100,
10 | "DICT_5X5_250": cv2.aruco.DICT_5X5_250,
11 | "DICT_5X5_1000": cv2.aruco.DICT_5X5_1000,
12 | "DICT_6X6_50": cv2.aruco.DICT_6X6_50,
13 | "DICT_6X6_100": cv2.aruco.DICT_6X6_100,
14 | "DICT_6X6_250": cv2.aruco.DICT_6X6_250,
15 | "DICT_6X6_1000": cv2.aruco.DICT_6X6_1000,
16 | "DICT_7X7_50": cv2.aruco.DICT_7X7_50,
17 | "DICT_7X7_100": cv2.aruco.DICT_7X7_100,
18 | "DICT_7X7_250": cv2.aruco.DICT_7X7_250,
19 | "DICT_7X7_1000": cv2.aruco.DICT_7X7_1000,
20 | "DICT_ARUCO_ORIGINAL": cv2.aruco.DICT_ARUCO_ORIGINAL,
21 | "DICT_APRILTAG_16h5": cv2.aruco.DICT_APRILTAG_16h5,
22 | "DICT_APRILTAG_25h9": cv2.aruco.DICT_APRILTAG_25h9,
23 | "DICT_APRILTAG_36h10": cv2.aruco.DICT_APRILTAG_36h10,
24 | "DICT_APRILTAG_36h11": cv2.aruco.DICT_APRILTAG_36h11
25 | }
26 |
27 | def aruco_display(corners, ids, rejected, image):
28 | if len(corners) > 0:
29 | # flatten the ArUco IDs list
30 | ids = ids.flatten()
31 | # loop over the detected ArUCo corners
32 | for (markerCorner, markerID) in zip(corners, ids):
33 | # extract the marker corners (which are always returned in
34 | # top-left, top-right, bottom-right, and bottom-left order)
35 | corners = markerCorner.reshape((4, 2))
36 | (topLeft, topRight, bottomRight, bottomLeft) = corners
37 | # convert each of the (x, y)-coordinate pairs to integers
38 | topRight = (int(topRight[0]), int(topRight[1]))
39 | bottomRight = (int(bottomRight[0]), int(bottomRight[1]))
40 | bottomLeft = (int(bottomLeft[0]), int(bottomLeft[1]))
41 | topLeft = (int(topLeft[0]), int(topLeft[1]))
42 |
43 | cv2.line(image, topLeft, topRight, (0, 255, 0), 2)
44 | cv2.line(image, topRight, bottomRight, (0, 255, 0), 2)
45 | cv2.line(image, bottomRight, bottomLeft, (0, 255, 0), 2)
46 | cv2.line(image, bottomLeft, topLeft, (0, 255, 0), 2)
47 | # compute and draw the center (x, y)-coordinates of the ArUco
48 | # marker
49 | cX = int((topLeft[0] + bottomRight[0]) / 2.0)
50 | cY = int((topLeft[1] + bottomRight[1]) / 2.0)
51 | cv2.circle(image, (cX, cY), 4, (0, 0, 255), -1)
52 | # draw the ArUco marker ID on the image
53 | cv2.putText(image, str(markerID),(topLeft[0], topLeft[1] - 10), cv2.FONT_HERSHEY_SIMPLEX,
54 | 0.5, (0, 255, 0), 2)
55 | print("[Inference] ArUco marker ID: {}".format(markerID))
56 | # show the output image
57 | return image
--------------------------------------------------------------------------------
/pose_estimation.py:
--------------------------------------------------------------------------------
1 | '''
2 | Sample Usage:-
3 | python pose_estimation.py --K_Matrix calibration_matrix.npy --D_Coeff distortion_coefficients.npy --type DICT_5X5_100
4 | '''
5 |
6 |
7 | import numpy as np
8 | import cv2
9 | import sys
10 | from utils import ARUCO_DICT
11 | import argparse
12 | import time
13 |
14 |
15 | def pose_esitmation(frame, aruco_dict_type, matrix_coefficients, distortion_coefficients):
16 |
17 | '''
18 | frame - Frame from the video stream
19 | matrix_coefficients - Intrinsic matrix of the calibrated camera
20 | distortion_coefficients - Distortion coefficients associated with your camera
21 |
22 | return:-
23 | frame - The frame with the axis drawn on it
24 | '''
25 |
26 | gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
27 | cv2.aruco_dict = cv2.aruco.Dictionary_get(aruco_dict_type)
28 | parameters = cv2.aruco.DetectorParameters_create()
29 |
30 |
31 | corners, ids, rejected_img_points = cv2.aruco.detectMarkers(gray, cv2.aruco_dict,parameters=parameters,
32 | cameraMatrix=matrix_coefficients,
33 | distCoeff=distortion_coefficients)
34 |
35 | # If markers are detected
36 | if len(corners) > 0:
37 | for i in range(0, len(ids)):
38 | # Estimate pose of each marker and return the values rvec and tvec---(different from those of camera coefficients)
39 | rvec, tvec, markerPoints = cv2.aruco.estimatePoseSingleMarkers(corners[i], 0.02, matrix_coefficients,
40 | distortion_coefficients)
41 | # Draw a square around the markers
42 | cv2.aruco.drawDetectedMarkers(frame, corners)
43 |
44 | # Draw Axis
45 | cv2.aruco.drawAxis(frame, matrix_coefficients, distortion_coefficients, rvec, tvec, 0.01)
46 |
47 | return frame
48 |
49 | if __name__ == '__main__':
50 |
51 | ap = argparse.ArgumentParser()
52 | ap.add_argument("-k", "--K_Matrix", required=True, help="Path to calibration matrix (numpy file)")
53 | ap.add_argument("-d", "--D_Coeff", required=True, help="Path to distortion coefficients (numpy file)")
54 | ap.add_argument("-t", "--type", type=str, default="DICT_ARUCO_ORIGINAL", help="Type of ArUCo tag to detect")
55 | args = vars(ap.parse_args())
56 |
57 |
58 | if ARUCO_DICT.get(args["type"], None) is None:
59 | print(f"ArUCo tag type '{args['type']}' is not supported")
60 | sys.exit(0)
61 |
62 | aruco_dict_type = ARUCO_DICT[args["type"]]
63 | calibration_matrix_path = args["K_Matrix"]
64 | distortion_coefficients_path = args["D_Coeff"]
65 |
66 | k = np.load(calibration_matrix_path)
67 | d = np.load(distortion_coefficients_path)
68 |
69 | video = cv2.VideoCapture(0)
70 | time.sleep(2.0)
71 |
72 | while True:
73 | ret, frame = video.read()
74 |
75 | if not ret:
76 | break
77 |
78 | output = pose_esitmation(frame, aruco_dict_type, k, d)
79 |
80 | cv2.imshow('Estimated Pose', output)
81 |
82 | key = cv2.waitKey(1) & 0xFF
83 | if key == ord('q'):
84 | break
85 |
86 | video.release()
87 | cv2.destroyAllWindows()
--------------------------------------------------------------------------------
/calibration.py:
--------------------------------------------------------------------------------
1 | '''
2 | Sample Usage:-
3 | python calibration.py --dir calibration_checkerboard/ --square_size 0.024
4 | '''
5 |
6 | import numpy as np
7 | import cv2
8 | import os
9 | import argparse
10 |
11 |
12 | def calibrate(dirpath, square_size, width, height, visualize=False):
13 | """ Apply camera calibration operation for images in the given directory path. """
14 |
15 | # termination criteria
16 | criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
17 |
18 | # prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(8,6,0)
19 | objp = np.zeros((height*width, 3), np.float32)
20 | objp[:, :2] = np.mgrid[0:width, 0:height].T.reshape(-1, 2)
21 |
22 | objp = objp * square_size
23 |
24 | # Arrays to store object points and image points from all the images.
25 | objpoints = [] # 3d point in real world space
26 | imgpoints = [] # 2d points in image plane.
27 |
28 | images = os.listdir(dirpath)
29 |
30 | for fname in images:
31 | img = cv2.imread(os.path.join(dirpath, fname))
32 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
33 |
34 | # Find the chess board corners
35 | ret, corners = cv2.findChessboardCorners(gray, (width, height), None)
36 |
37 | # If found, add object points, image points (after refining them)
38 | if ret:
39 | objpoints.append(objp)
40 |
41 | corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
42 | imgpoints.append(corners2)
43 |
44 | # Draw and display the corners
45 | img = cv2.drawChessboardCorners(img, (width, height), corners2, ret)
46 |
47 | if visualize:
48 | cv2.imshow('img',img)
49 | cv2.waitKey(0)
50 |
51 |
52 | ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
53 |
54 | return [ret, mtx, dist, rvecs, tvecs]
55 |
56 |
57 | if __name__ == '__main__':
58 | ap = argparse.ArgumentParser()
59 | ap.add_argument("-d", "--dir", required=True, help="Path to folder containing checkerboard images for calibration")
60 | ap.add_argument("-w", "--width", type=int, help="Width of checkerboard (default=9)", default=9)
61 | ap.add_argument("-t", "--height", type=int, help="Height of checkerboard (default=6)", default=6)
62 | ap.add_argument("-s", "--square_size", type=float, default=1, help="Length of one edge (in metres)")
63 | ap.add_argument("-v", "--visualize", type=str, default="False", help="To visualize each checkerboard image")
64 | args = vars(ap.parse_args())
65 |
66 | dirpath = args['dir']
67 | # 2.4 cm == 0.024 m
68 | # square_size = 0.024
69 | square_size = args['square_size']
70 |
71 | width = args['width']
72 | height = args['height']
73 |
74 | if args["visualize"].lower() == "true":
75 | visualize = True
76 | else:
77 | visualize = False
78 |
79 | ret, mtx, dist, rvecs, tvecs = calibrate(dirpath, square_size, visualize=visualize, width=width, height=height)
80 |
81 | print(mtx)
82 | print(dist)
83 |
84 | np.save("calibration_matrix", mtx)
85 | np.save("distortion_coefficients", dist)
86 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ArUCo-Markers-Pose-Estimation-Generation-Python
2 |
3 | This repository contains all the code you need to generate an ArucoTag,
4 | detect ArucoTags in images and videos, and then use the detected tags
5 | to estimate the pose of the object. In addition to this, I have also
6 | included the code required to obtain the calibration matrix for your
7 | camera.
8 |
9 |
10 |
11 | ## 1. ArUCo Marker Generation
12 | The file `generate_aruco_tags.py` contains the code for ArUCo Marker Generation.
13 | You need to specify the type of marker you want to generate.
14 |
15 | The command for running is :-
16 | `python generate_aruco_tags.py --id 24 --type DICT_5X5_100 --output tags/`
17 |
18 | You can find more details on other parameters using `python generate_aruco_tags.py --help`
19 |
20 | ## 2. ArUCo Marker Detection
21 | The files `detect_aruco_images.py` and `detect_aruco_video.py` contains the code for detecting
22 | ArUCo Markers in images and videos respectively. You need to specify the path to the image or
23 | video file and the type of marker you want to detect.
24 |
25 | The command for running is :-
26 | **For inference on images**
27 | `python detect_aruco_images.py --image Images/test_image_1.png --type DICT_5X5_100`
28 | **For inference using webcam feed**
29 | `python detect_aruco_video.py --type DICT_5X5_100 --camera True `
30 | **For inference using video file**
31 | `python detect_aruco_video.py --type DICT_5X5_100 --camera False --video test_video.mp4`
32 |
33 | You can find more details on other parameters using `python detect_aruco_images.py --help`
34 | and `python detect_aruco_video.py --help`
35 |
36 | ## 3. Calibration
37 | The file `calibration.py` contains the code necessary for calibrating your camera. This step
38 | has several pre-requisites. You need to have a folder containing a set of checkerboard images
39 | taken using your camera. Make sure that these checkerboard images are of different poses and
40 | orientation. You need to provide the path to this directory and the size of the square in metres.
41 | You can also change the shape of the checkerboard pattern using the parameters given. Make sure this
42 | matches with your checkerboard pattern. This code will generate two numpy files `calibration_matrix.npy` and `distortion_coefficients.npy`. These files are required to execute the next step that involves pose estimation.
43 | Note that the calibration and distortion numpy files given in my repository is obtained specifically for my camera
44 | and might not work well for yours.
45 |
46 | The command for running is :-
47 | `python calibration.py --dir calibration_checkerboard/ --square_size 0.024`
48 |
49 | You can find more details on other parameters using `python calibration.py --help`
50 |
51 | ## 4. Pose Estimation
52 | The file `pose_estimation.py` contains the code that performs pose estimation after detecting the
53 | ArUCo markers. This is done in real-time for each frame obtained from the web-cam feed. You need to specify
54 | the path to the camera calibration matrix and distortion coefficients obtained from the previous step as well
55 | as the type for ArUCo marker you want to detect. Note that this code could be easily modified to perform
56 | pose estimation on images and video files.
57 |
58 | The command for running is :-
59 | `python pose_estimation.py --K_Matrix calibration_matrix.npy --D_Coeff distortion_coefficients.npy --type DICT_5X5_100`
60 |
61 |
62 | You can find more details on other parameters using `python pose_estimation.py --help`
63 |
64 | ## Output
65 |
66 |
67 |
68 |
69 |
70 | ### Notes
71 | The `utils.py` contains the ArUCo Markers dictionary and the other utility function to display the detected markers.
72 |
73 | Feel free to reach out to me in case of any issues.
74 | If you find this repo useful in any way please do star ⭐️ it so that others can reap it's benefits as well.
75 |
76 | Happy Learning! Keep chasing your dreams!
77 |
78 | ## References
79 | 1. https://docs.opencv.org/4.x/d9/d6d/tutorial_table_of_content_aruco.html
80 | 2. https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html
81 |
--------------------------------------------------------------------------------