Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 1 | import copy |
| 2 | import glog |
| 3 | import json |
| 4 | import math |
| 5 | import numpy as np |
| 6 | import os |
| 7 | |
| 8 | glog.setLevel("INFO") |
| 9 | |
| 10 | |
| 11 | # Quick fix to naming games that happen with bazel |
| 12 | def 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 | |
| 30 | class CameraIntrinsics: |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 31 | |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 32 | def __init__(self): |
| 33 | self.camera_matrix = [] |
| 34 | self.dist_coeffs = [] |
| 35 | |
| 36 | pass |
| 37 | |
| 38 | |
| 39 | class CameraExtrinsics: |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 40 | |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 41 | def __init__(self): |
| 42 | self.R = [] |
| 43 | self.T = [] |
| 44 | |
| 45 | |
| 46 | class CameraParameters: |
Ravago Jones | 5127ccc | 2022-07-31 16:32:45 -0700 | [diff] [blame] | 47 | |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 48 | def __init__(self): |
| 49 | self.camera_int = CameraIntrinsics() |
| 50 | self.camera_ext = CameraExtrinsics() |
| 51 | self.turret_ext = None |
| 52 | self.node_name = "" |
| 53 | self.team_number = -1 |
Jim Ostrowski | fec0c33 | 2022-02-06 23:28:26 -0800 | [diff] [blame] | 54 | self.camera_id = "" |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 55 | self.timestamp = 0 |
| 56 | |
| 57 | |
Milind Upadhyay | 958d446 | 2022-12-26 20:33:14 -0800 | [diff] [blame] | 58 | def compute_extrinsic(camera_roll, camera_pitch, camera_yaw, T_camera, |
| 59 | is_turret): |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 60 | # Compute the extrinsic calibration based on pitch and translation |
| 61 | # Includes camera rotation from robot x,y,z to opencv (z, -x, -y) |
| 62 | |
| 63 | # Also, handle extrinsics for the turret |
| 64 | # The basic camera pose is relative to the center, base of the turret |
| 65 | # TODO<Jim>: Maybe store these to .json files, like with intrinsics? |
| 66 | base_cam_ext = CameraExtrinsics() |
| 67 | turret_cam_ext = CameraExtrinsics() |
| 68 | |
Milind Upadhyay | 958d446 | 2022-12-26 20:33:14 -0800 | [diff] [blame] | 69 | camera_roll_matrix = np.array( |
| 70 | [[1.0, 0.0, 0.0], [0.0, np.cos(camera_roll), -np.sin(camera_roll)], |
| 71 | [0.0, np.sin(camera_roll), |
| 72 | np.cos(camera_roll)]]) |
| 73 | |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 74 | camera_pitch_matrix = np.array( |
| 75 | [[np.cos(camera_pitch), 0.0, |
| 76 | np.sin(camera_pitch)], [0.0, 1.0, 0.0], |
| 77 | [-np.sin(camera_pitch), 0.0, |
| 78 | np.cos(camera_pitch)]]) |
| 79 | |
| 80 | camera_yaw_matrix = np.array( |
| 81 | [[np.cos(camera_yaw), -np.sin(camera_yaw), 0.0], |
| 82 | [np.sin(camera_yaw), np.cos(camera_yaw), 0.0], [0.0, 0.0, 1.0]]) |
| 83 | |
| 84 | robot_to_camera_rotation = np.array([[0., 0., 1.], [-1, 0, 0], [0, -1., |
| 85 | 0]]) |
| 86 | |
| 87 | if is_turret: |
James Kuszmaul | 3f3b1d9 | 2022-03-13 18:01:37 -0700 | [diff] [blame] | 88 | # Turret is just an identity matrix in the middle of the robot. |
| 89 | base_cam_ext.R = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 90 | [0.0, 0.0, 1.0]]) |
| 91 | base_cam_ext.T = np.array([0.0, 0.0, 0.0]) |
Milind Upadhyay | 958d446 | 2022-12-26 20:33:14 -0800 | [diff] [blame] | 92 | turret_cam_ext.R = camera_yaw_matrix @ camera_pitch_matrix @ camera_roll_matrix @ robot_to_camera_rotation |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 93 | turret_cam_ext.T = T_camera |
| 94 | else: |
Milind Upadhyay | 958d446 | 2022-12-26 20:33:14 -0800 | [diff] [blame] | 95 | base_cam_ext.R = camera_yaw_matrix @ camera_pitch_matrix @ camera_roll_matrix @ robot_to_camera_rotation |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 96 | base_cam_ext.T = T_camera |
| 97 | turret_cam_ext = None |
| 98 | |
| 99 | return base_cam_ext, turret_cam_ext |
| 100 | |
| 101 | |
Milind Upadhyay | 37f6fe8 | 2022-04-14 23:00:44 -0700 | [diff] [blame] | 102 | def compute_extrinsic_by_pi(pi_number, team_number): |
James Kuszmaul | 3f3b1d9 | 2022-03-13 18:01:37 -0700 | [diff] [blame] | 103 | # Defaults for all cameras |
Milind Upadhyay | 958d446 | 2022-12-26 20:33:14 -0800 | [diff] [blame] | 104 | camera_roll = 0.0 |
James Kuszmaul | 3f3b1d9 | 2022-03-13 18:01:37 -0700 | [diff] [blame] | 105 | camera_pitch = -35.0 * np.pi / 180.0 |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 106 | camera_yaw = 0.0 |
James Kuszmaul | 3f3b1d9 | 2022-03-13 18:01:37 -0700 | [diff] [blame] | 107 | is_turret = True |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 108 | # Default camera location to robot origin |
| 109 | T = np.array([0.0, 0.0, 0.0]) |
| 110 | |
Milind Upadhyay | 37f6fe8 | 2022-04-14 23:00:44 -0700 | [diff] [blame] | 111 | if team_number == 971: |
| 112 | if pi_number == "pi1": |
| 113 | camera_yaw = 90.0 * np.pi / 180.0 |
| 114 | T = np.array([-11.0 * 0.0254, 5.5 * 0.0254, 29.5 * 0.0254]) |
| 115 | elif pi_number == "pi2": |
| 116 | camera_yaw = 0.0 |
| 117 | T = np.array([-9.5 * 0.0254, -3.5 * 0.0254, 34.5 * 0.0254]) |
| 118 | elif pi_number == "pi3": |
Austin Schuh | d84d431 | 2022-11-07 09:04:48 -0800 | [diff] [blame] | 119 | camera_yaw = 182.0 * np.pi / 180.0 |
Milind Upadhyay | 37f6fe8 | 2022-04-14 23:00:44 -0700 | [diff] [blame] | 120 | T = np.array([-9.5 * 0.0254, 3.5 * 0.0254, 34.5 * 0.0254]) |
| 121 | elif pi_number == "pi4": |
| 122 | camera_yaw = -90.0 * np.pi / 180.0 |
| 123 | T = np.array([-10.25 * 0.0254, -5.0 * 0.0254, 27.5 * 0.0254]) |
| 124 | elif team_number == 9971: |
| 125 | if pi_number == "pi1": |
Austin Schuh | d84d431 | 2022-11-07 09:04:48 -0800 | [diff] [blame] | 126 | camera_yaw = 179.0 * np.pi / 180.0 |
Milind Upadhyay | ce85134 | 2022-09-10 22:57:48 -0700 | [diff] [blame] | 127 | T = np.array([-9.5 * 0.0254, 3.25 * 0.0254, 35.5 * 0.0254]) |
Milind Upadhyay | 37f6fe8 | 2022-04-14 23:00:44 -0700 | [diff] [blame] | 128 | elif pi_number == "pi2": |
| 129 | camera_yaw = 0.0 |
Milind Upadhyay | ce85134 | 2022-09-10 22:57:48 -0700 | [diff] [blame] | 130 | T = np.array([-9.0 * 0.0254, -3.25 * 0.0254, 35.5 * 0.0254]) |
Milind Upadhyay | 37f6fe8 | 2022-04-14 23:00:44 -0700 | [diff] [blame] | 131 | elif pi_number == "pi3": |
| 132 | camera_yaw = 90.0 * np.pi / 180.0 |
Austin Schuh | d84d431 | 2022-11-07 09:04:48 -0800 | [diff] [blame] | 133 | T = np.array([-10.5 * 0.0254, 5.0 * 0.0254, 29.5 * 0.0254]) |
Milind Upadhyay | ce85134 | 2022-09-10 22:57:48 -0700 | [diff] [blame] | 134 | elif pi_number == "pi4": |
| 135 | camera_yaw = -90.0 * np.pi / 180.0 |
Austin Schuh | d84d431 | 2022-11-07 09:04:48 -0800 | [diff] [blame] | 136 | T = np.array([-10.5 * 0.0254, -5.0 * 0.0254, 28.5 * 0.0254]) |
Milind Upadhyay | 958d446 | 2022-12-26 20:33:14 -0800 | [diff] [blame] | 137 | elif team_number == 7971: |
Milind Upadhyay | c5beba1 | 2022-12-17 17:41:20 -0800 | [diff] [blame] | 138 | is_turret = False |
Milind Upadhyay | 958d446 | 2022-12-26 20:33:14 -0800 | [diff] [blame] | 139 | # Cameras are flipped upside down |
| 140 | camera_roll = 180.0 * np.pi / 180.0 |
| 141 | if pi_number == "pi1": |
| 142 | camera_yaw = 90.0 * np.pi / 180.0 |
| 143 | T = np.array([-6.0 * 0.0254, -8.0 * 0.0254, 0.5 * 0.0254]) |
| 144 | elif pi_number == "pi2": |
| 145 | camera_yaw = 0.0 |
| 146 | T = np.array([3.75 * 0.0254, 7.5 * 0.0254, 0.5 * 0.0254]) |
| 147 | elif pi_number == "pi3": |
| 148 | camera_yaw = 0.0 |
| 149 | T = np.array([3.75 * 0.0254, -4.25 * 0.0254, 0.5 * 0.0254]) |
| 150 | elif pi_number == "pi4": |
| 151 | camera_yaw = 0.0 |
| 152 | T = np.array([-6.0 * 0.0254, -7.0 * 0.0254, 0.5 * 0.0254]) |
Milind Upadhyay | 37f6fe8 | 2022-04-14 23:00:44 -0700 | [diff] [blame] | 153 | else: |
| 154 | glog.fatal("Unknown team number for extrinsics") |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 155 | |
Milind Upadhyay | 958d446 | 2022-12-26 20:33:14 -0800 | [diff] [blame] | 156 | return compute_extrinsic(camera_roll, camera_pitch, camera_yaw, T, |
| 157 | is_turret) |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 158 | |
| 159 | |
| 160 | def load_camera_definitions(): |
| 161 | ### CAMERA DEFINITIONS |
| 162 | # We only load in cameras that have a calibration file |
| 163 | # These are stored in y2022/vision/calib_files |
| 164 | # |
| 165 | # Or better yet, use //y2020/vision:calibration to calibrate the camera |
| 166 | # using a Charuco target board |
| 167 | |
| 168 | camera_list = [] |
| 169 | |
| 170 | dir_name = bazel_name_fix('calib_files') |
| 171 | if dir_name is not None: |
| 172 | glog.debug("Searching for calibration files in " + dir_name) |
| 173 | else: |
| 174 | glog.fatal("Failed to find calib_files directory") |
| 175 | |
| 176 | for filename in sorted(os.listdir(dir_name)): |
| 177 | glog.debug("Inspecting %s", filename) |
| 178 | if ("cam-calib-int" in filename |
| 179 | or 'calibration' in filename) and filename.endswith(".json"): |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 180 | # Extract intrinsics from file |
| 181 | calib_file = open(dir_name + "/" + filename, 'r') |
| 182 | calib_dict = json.loads(calib_file.read()) |
| 183 | |
| 184 | team_number = calib_dict["team_number"] |
| 185 | node_name = calib_dict["node_name"] |
Jim Ostrowski | fec0c33 | 2022-02-06 23:28:26 -0800 | [diff] [blame] | 186 | camera_id = "UNKNOWN" |
| 187 | if "camera_id" in calib_dict: |
| 188 | camera_id = calib_dict["camera_id"] |
| 189 | |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 190 | camera_matrix = np.asarray(calib_dict["intrinsics"]).reshape( |
| 191 | (3, 3)) |
| 192 | dist_coeffs = np.asarray(calib_dict["dist_coeffs"]).reshape((1, 5)) |
| 193 | |
| 194 | glog.debug("Found calib for " + node_name + ", team #" + |
| 195 | str(team_number)) |
| 196 | |
| 197 | camera_params = CameraParameters() |
| 198 | # TODO: Need to add reading in extrinsic camera parameters from json |
| 199 | camera_params.camera_ext, camera_params.turret_ext = compute_extrinsic_by_pi( |
Milind Upadhyay | 37f6fe8 | 2022-04-14 23:00:44 -0700 | [diff] [blame] | 200 | node_name, team_number) |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 201 | |
| 202 | camera_params.node_name = node_name |
| 203 | camera_params.team_number = team_number |
Jim Ostrowski | fec0c33 | 2022-02-06 23:28:26 -0800 | [diff] [blame] | 204 | camera_params.camera_id = camera_id |
Jim Ostrowski | 007e2ea | 2022-01-30 13:13:26 -0800 | [diff] [blame] | 205 | camera_params.camera_int.camera_matrix = copy.copy(camera_matrix) |
| 206 | camera_params.camera_int.dist_coeffs = copy.copy(dist_coeffs) |
| 207 | camera_list.append(camera_params) |
| 208 | |
| 209 | return camera_list |