blob: 30057a59657b4924b2ccb026efd3acf9126abb1f [file] [log] [blame]
Jim Ostrowski007e2ea2022-01-30 13:13:26 -08001import copy
2import glog
3import json
4import math
5import numpy as np
6import os
7
8glog.setLevel("INFO")
9
10
11# Quick fix to naming games that happen with bazel
12def bazel_name_fix(filename):
13 ret_name = filename
14 try:
15 from bazel_tools.tools.python.runfiles import runfiles
16 r = runfiles.Create()
17 ret_name = r.Rlocation('org_frc971/y2022/vision/' + filename)
18 #print("Trying directory: ", ret_name)
19 except:
20 print("Failed bazel_name_fix")
21 pass
22
23 ### TODO<Jim>: Need to figure out why this isn't working
24 ### Hardcoding for now
25 if ret_name == None:
26 ret_name = '/home/jim/code/FRC/971-Robot-Code/y2022/vision/calib_files'
27 return ret_name
28
29
30class CameraIntrinsics:
31 def __init__(self):
32 self.camera_matrix = []
33 self.dist_coeffs = []
34
35 pass
36
37
38class CameraExtrinsics:
39 def __init__(self):
40 self.R = []
41 self.T = []
42
43
44class CameraParameters:
45 def __init__(self):
46 self.camera_int = CameraIntrinsics()
47 self.camera_ext = CameraExtrinsics()
48 self.turret_ext = None
49 self.node_name = ""
50 self.team_number = -1
Jim Ostrowskifec0c332022-02-06 23:28:26 -080051 self.camera_id = ""
Jim Ostrowski007e2ea2022-01-30 13:13:26 -080052 self.timestamp = 0
53
54
55def compute_extrinsic(camera_pitch, camera_yaw, T_camera, is_turret):
56 # Compute the extrinsic calibration based on pitch and translation
57 # Includes camera rotation from robot x,y,z to opencv (z, -x, -y)
58
59 # Also, handle extrinsics for the turret
60 # The basic camera pose is relative to the center, base of the turret
61 # TODO<Jim>: Maybe store these to .json files, like with intrinsics?
62 base_cam_ext = CameraExtrinsics()
63 turret_cam_ext = CameraExtrinsics()
64
65 camera_pitch_matrix = np.array(
66 [[np.cos(camera_pitch), 0.0,
67 np.sin(camera_pitch)], [0.0, 1.0, 0.0],
68 [-np.sin(camera_pitch), 0.0,
69 np.cos(camera_pitch)]])
70
71 camera_yaw_matrix = np.array(
72 [[np.cos(camera_yaw), -np.sin(camera_yaw), 0.0],
73 [np.sin(camera_yaw), np.cos(camera_yaw), 0.0], [0.0, 0.0, 1.0]])
74
75 robot_to_camera_rotation = np.array([[0., 0., 1.], [-1, 0, 0], [0, -1.,
76 0]])
77
78 if is_turret:
79 # Turret camera has default heading 180 deg away from the robot x
80 base_cam_ext.R = np.array([[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0],
81 [0.0, 0.0, 1.0]])
82 base_cam_ext.T = np.array([0.0, 0.0, 0.0])
83 turret_cam_ext.R = camera_yaw_matrix @ camera_pitch_matrix @ robot_to_camera_rotation
84 turret_cam_ext.T = T_camera
85 else:
86 base_cam_ext.R = camera_yaw_matrix @ camera_pitch_matrix @ robot_to_camera_rotation
87 base_cam_ext.T = T_camera
88 turret_cam_ext = None
89
90 return base_cam_ext, turret_cam_ext
91
92
93def compute_extrinsic_by_pi(pi_number):
94 # Defaults for non-turret camera
95 camera_pitch = -20.0 * np.pi / 180.0
96 camera_yaw = 0.0
97 is_turret = False
98 # Default camera location to robot origin
99 T = np.array([0.0, 0.0, 0.0])
100
101 if pi_number == "pi1":
102 # This is the turret camera
103 camera_pitch = -10.0 * np.pi / 180.0
104 is_turret = True
105 T = np.array([7.5 * 0.0254, -5.5 * 0.0254, 41.0 * 0.0254])
106 elif pi_number == "pi2":
107 T = np.array([4.5 * 0.0254, 3.75 * 0.0254, 26.0 * 0.0254])
108 elif pi_number == "pi3":
109 camera_yaw = 90.0 * np.pi / 180.0
110 T = np.array([-2.75 * 0.0254, 5.25 * 0.0254, 25.25 * 0.0254])
111 elif pi_number == "pi4":
112 camera_yaw = -90.0 * np.pi / 180.0
113 T = np.array([-2.75 * 0.0254, -6 * 0.0254, 26 * 0.0254])
114 elif pi_number == "pi5":
115 camera_yaw = 180.0 * np.pi / 180.0
116 T = np.array([-7.25 * 0.0254, -4.24 * 0.0254, 16.5 * 0.0254])
117
118 return compute_extrinsic(camera_pitch, camera_yaw, T, is_turret)
119
120
121def load_camera_definitions():
122 ### CAMERA DEFINITIONS
123 # We only load in cameras that have a calibration file
124 # These are stored in y2022/vision/calib_files
125 #
126 # Or better yet, use //y2020/vision:calibration to calibrate the camera
127 # using a Charuco target board
128
129 camera_list = []
130
131 dir_name = bazel_name_fix('calib_files')
132 if dir_name is not None:
133 glog.debug("Searching for calibration files in " + dir_name)
134 else:
135 glog.fatal("Failed to find calib_files directory")
136
137 for filename in sorted(os.listdir(dir_name)):
138 glog.debug("Inspecting %s", filename)
139 if ("cam-calib-int" in filename
140 or 'calibration' in filename) and filename.endswith(".json"):
141
142 # Extract intrinsics from file
143 calib_file = open(dir_name + "/" + filename, 'r')
144 calib_dict = json.loads(calib_file.read())
145
146 team_number = calib_dict["team_number"]
147 node_name = calib_dict["node_name"]
Jim Ostrowskifec0c332022-02-06 23:28:26 -0800148 camera_id = "UNKNOWN"
149 if "camera_id" in calib_dict:
150 camera_id = calib_dict["camera_id"]
151
Jim Ostrowski007e2ea2022-01-30 13:13:26 -0800152 camera_matrix = np.asarray(calib_dict["intrinsics"]).reshape(
153 (3, 3))
154 dist_coeffs = np.asarray(calib_dict["dist_coeffs"]).reshape((1, 5))
155
156 glog.debug("Found calib for " + node_name + ", team #" +
157 str(team_number))
158
159 camera_params = CameraParameters()
160 # TODO: Need to add reading in extrinsic camera parameters from json
161 camera_params.camera_ext, camera_params.turret_ext = compute_extrinsic_by_pi(
162 node_name)
163
164 camera_params.node_name = node_name
165 camera_params.team_number = team_number
Jim Ostrowskifec0c332022-02-06 23:28:26 -0800166 camera_params.camera_id = camera_id
Jim Ostrowski007e2ea2022-01-30 13:13:26 -0800167 camera_params.camera_int.camera_matrix = copy.copy(camera_matrix)
168 camera_params.camera_int.dist_coeffs = copy.copy(dist_coeffs)
169 camera_list.append(camera_params)
170
171 return camera_list