aboutsummaryrefslogtreecommitdiff
path: root/src/argaze/utils/camera_calibrate.py
blob: f55f0cec92577951aaf94feb4a9fd65888e59fd1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env python

import argparse
import os
import time

from argaze.ArUcoMarkers import ArUcoMarkersDictionary, ArUcoBoard, ArUcoDetector, ArUcoCamera

import cv2

def main():
    """
    Captures board pictures and finally outputs camera calibration data into a calibration.json file.

    - Export and print a calibration board using aruco_calibration_board_export.py script.
    - Place the calibration board face to the camera.
    - Move the calibration board in a manner to view it entirely on screen in various configurations (orientation and distance):  
    The script will automatically take pictures. Do this step with a good lighting and a clear background.
    - Once enough pictures have been captured (~20), press Esc key then, wait for the camera calibration processing.
    - Finally, check rms parameter: it should be between 0. and 1. if the calibration succeeded (lower is better).

    ### Reference:
    - [Camera calibration using ArUco marker tutorial](https://automaticaddison.com/how-to-perform-camera-calibration-using-opencv/)
    """

    # Manage arguments
    parser = argparse.ArgumentParser(description=main.__doc__.split('-')[0])
    parser.add_argument('columns', metavar='COLS_NUMBER', type=int, default=7, help='expected board columns number')
    parser.add_argument('rows', metavar='ROWS_NUMBER', type=int, default=5, help='expected board rows number')
    parser.add_argument('square_size', metavar='SQUARE_SIZE', type=float, default=5, help='expected square size in cm')
    parser.add_argument('marker_size', metavar='MARKER_SIZE', type=float, default=3, help='expected marker size in cm')
    parser.add_argument('dictionary', metavar='DICTIONARY', type=ArUcoMarkersDictionary.ArUcoMarkersDictionary, default='DICT_ARUCO_ORIGINAL', help='expected dictionary name: DICT_4X4_50, DICT_4X4_100, DICT_4X4_250, DICT_4X4_1000, DICT_5X5_50, DICT_5X5_100, DICT_5X5_250, DICT_5X5_1000, DICT_6X6_50, DICT_6X6_100, DICT_6X6_250, DICT_6X6_1000, DICT_7X7_50, DICT_7X7_100, DICT_7X7_250, DICT_7X7_1000, DICT_ARUCO_ORIGINAL, DICT_APRILTAG_16h5, DICT_APRILTAG_25h9, DICT_APRILTAG_36h10, DICT_APRILTAG_36h11')
    
    parser.add_argument('-d', '--device', metavar='DEVICE', type=int, default=0, help='video capture device id')
    parser.add_argument('-o', '--output', metavar='OUT', type=str, default='.', help='destination folder filepath')
    args = parser.parse_args()

    # Enable camera video capture
    video_capture = cv2.VideoCapture(args.device)

    frame_width = int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH))
    frame_height = int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))

    # Create aruco camera
    aruco_camera = ArUcoCamera.ArUcoCamera(dimensions=(frame_width, frame_height))

    # Create aruco board
    aruco_board = ArUcoBoard.ArUcoBoard(args.columns, args.rows, args.square_size, args.marker_size, args.dictionary)

    # Create aruco detector
    aruco_detector = ArUcoDetector.ArUcoDetector(dictionary=args.dictionary, marker_size=args.marker_size)

    print(f'{aruco_camera.dimensions[0]}x{aruco_camera.dimensions[1]} pixels camera calibration starts')
    print("Waiting for calibration board...")

    expected_markers_number = aruco_board.markers_number
    expected_corners_number = aruco_board.corners_number

    # Capture loop
    try:

        # Capture frames with a full displayed board inside
        while video_capture.isOpened():

            success, video_frame = video_capture.read()

            if success:

                # Detect calibration board
                aruco_detector.detect_board(video_frame, aruco_board, expected_markers_number)

                # Draw detected markers
                aruco_detector.draw_detected_markers(video_frame)

                # Draw current calibration data count
                cv2.putText(video_frame, f'Capture: {aruco_camera.calibration_data_count}', (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
                cv2.imshow('Camera Calibration', video_frame)

                # If all board corners are detected
                if aruco_detector.board_corners_number == expected_corners_number:

                    # Draw board corners to notify a capture is done
                    aruco_detector.draw_board(video_frame)

                    # Append calibration data
                    aruco_camera.store_calibration_data(aruco_detector.board_corners, aruco_detector.board_corners_identifier)

                    cv2.imshow('Camera Calibration', video_frame)

            # Stop calibration by pressing 'Esc' key
            if cv2.waitKey(1) == 27:
                break

    # Stop calibration on 'ctrl+C' interruption
    except KeyboardInterrupt:
        pass

    # Stop frame display
    cv2.destroyAllWindows()

    print('\nCalibrating camera...')
    aruco_camera.calibrate(aruco_board)

    print('\nCalibration succeeded!')
    print(f'\nRMS:\n{aruco_camera.rms}')
    print(f'\nDimensions:\n{frame_width}x{frame_height}')
    print(f'\nCamera matrix:\n{aruco_camera.K}')
    print(f'\nDistortion coefficients:\n{aruco_camera.D}')

    aruco_camera.to_json(f'{args.output}/calibration.json')

    print(f'\ncalibration.json file exported into {args.output} folder')

if __name__ == '__main__':

    main()