-
Notifications
You must be signed in to change notification settings - Fork 0
/
parse_ASP_TSAI_camera_calibration_files.py
180 lines (146 loc) · 7.91 KB
/
parse_ASP_TSAI_camera_calibration_files.py
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 20, 2024.
Updated on Fri Mar 15, 2024 to include extrinsics.
@author: Michael Studinger, NASA - Goddard Space Flight Center
"""
# import required modules
import re
def parse_asp_tsai_file(
f_name:str, # ASP camera calibration file using the Tsai camera calibration technique
) -> object: # a class containing the extracted parameters that were found
"""
Parse ASP camera calibration/camera model file using the Tsai camera calibration technique,
extract parameters, and return a class with the extracted values that were found.
For a description of the ASP camera file formats see:
https://stereopipeline.readthedocs.io/en/latest/pinholemodels.html#overview
Note: for camera calibration files that have the extrinsics not yet defined
the extrinsic attributes in the output object will not be populated.
"""
# read camera/lens calibration file for parsing
lens_cal = open(f_name, 'r')
tsai_info = lens_cal.readlines() # reads the entire file
lens_cal.close()
# define a class TsaiParams for organizing the extracted camera model parameters
class TsaiParams():
def __init__(self):
self.fu = None # focal length in horizontal pixel units
self.fv = None # focal length in vertical pixel units
self.cu = None # horizontal offset of the principal point of the camera in the image plane in pixel units, from 0,0
self.cv = None # vertical offset of the principal point of the camera in the image plane in pixel units, from 0,0
self.k1 = None # radial distortion parameter 1
self.k2 = None # radial distortion parameter 2
self.p1 = None # tangential distortion parameter 1
self.p2 = None # tangential distortion parameter 2
self.pitch = None # the size of each pixel in pixles (1.l0) or millimeters or meters
self.center = None # location of the camera center, usually in the geocentric coordinate system (GCC/ECEF)
self.rot_mat = None # rotation matrix describing the camera’s absolute pose in the coordinate system
# create return variable "tsai_params" as a TsaiParams class
tsai_params = TsaiParams()
# the code for parsing the lens calibration files was inspired
# by C. Wayne Wright's (https://github.com/lidar532) function:
# parse_cambot_header in the Jupyter notebook:
# https://github.com/mstudinger/ATM-SfM-Bathymetry/blob/main/Jupyter/CAMBOTv2_convert_GPS_to_camera_pos.ipynb
# define search patterns and group names
# to develop/verify search patterns with https://regex101.com/ make sure to select
# Python™ in the "FLAVOR" tab on the left and then use the string inside r"":
rx_fu = r"fu = (?P<fu>.\d*\.*\d*)"
rx_fv = r"fv = (?P<fv>.\d*\.*\d*)"
rx_cu = r"cu = (?P<cu>.\d*\.*\d*)"
rx_cv = r"cv = (?P<cv>.\d*\.*\d*)"
rx_k1 = r"k1 = (?P<k1>.\d*\.*\d*)" # includes positive and negative values
rx_k2 = r"k2 = (?P<k2>.\d*\.*\d*)"
rx_p1 = r"p1 = (?P<p1>.\d*\.*\d*)"
rx_p2 = r"p2 = (?P<p2>.\d*\.*\d*)"
# read pitch value. if pitch = 1.0 units for focal length etc. are in pixels
rx_pitch = r"pitch = (?P<pitch>.\d*\.*\d*)"
# location of camera center, usually in the geocentric coordinate system (GCC/ECEF)
rx_center = r"C = (?P<x>-*\d+\.\d*) (?P<y>-*\d+\.\d+) *(?P<z>-*\d*\.\d*)"
# rotation matrix describing the camera’s absolute pose in the coordinate system
rx_rot_mat = r"R = (?P<m1>-*\d+\.\d*) (?P<m2>-*\d+\.\d+) *(?P<m3>-*\d*\.\d*) (?P<m4>-*\d+\.\d*) (?P<m5>-*\d+\.\d+) *(?P<m6>-*\d*\.\d*) (?P<m7>-*\d+\.\d*) (?P<m8>-*\d+\.\d+) *(?P<m9>-*\d*\.\d*)"
for line in tsai_info:
re_srch = re.search(rx_fu, line, flags=0)
if re_srch:
tsai_params.fu = float(re_srch.group('fu'))
re_srch = re.search(rx_fv, line, flags=0)
if re_srch:
tsai_params.fv = float(re_srch.group('fv'))
re_srch = re.search(rx_cu, line, flags=0)
if re_srch:
tsai_params.cu = float(re_srch.group('cu'))
re_srch = re.search(rx_cv, line, flags=0)
if re_srch:
tsai_params.cv = float(re_srch.group('cv'))
re_srch = re.search(rx_k1, line, flags=0)
if re_srch:
tsai_params.k1 = float(re_srch.group('k1'))
re_srch = re.search(rx_k2, line, flags=0)
if re_srch:
tsai_params.k2 = float(re_srch.group('k2'))
re_srch = re.search(rx_p1, line, flags=0)
if re_srch:
tsai_params.p1 = float(re_srch.group('p1'))
re_srch = re.search(rx_p2, line, flags=0)
if re_srch:
tsai_params.p2 = float(re_srch.group('p2'))
re_srch = re.search(rx_pitch, line, flags=0)
if re_srch:
tsai_params.pitch = float(re_srch.group('pitch'))
re_srch = re.search(rx_center, line, flags=0)
if re_srch:
tsai_params.x = float(re_srch.group('x'))
tsai_params.y = float(re_srch.group('y'))
tsai_params.z = float(re_srch.group('z'))
re_srch = re.search(rx_rot_mat, line, flags=0)
if re_srch:
tsai_params.m1 = float(re_srch.group('m1'))
tsai_params.m2 = float(re_srch.group('m2'))
tsai_params.m3 = float(re_srch.group('m3'))
tsai_params.m4 = float(re_srch.group('m4'))
tsai_params.m5 = float(re_srch.group('m5'))
tsai_params.m6 = float(re_srch.group('m6'))
tsai_params.m7 = float(re_srch.group('m7'))
tsai_params.m8 = float(re_srch.group('m8'))
tsai_params.m9 = float(re_srch.group('m9'))
return tsai_params
#%% run module/function as script
if __name__ == '__main__':
import os
# set input directory with ASP Tsai camera calibration file for parsing
f_dir_cal = r".." + os.sep + "data" + os.sep + "calibration"
f_dir_tsai = r".." + os.sep + "data" + os.sep + "example_files"
# set Tsai input file name for parsing
# this example file has the extrinsics not yet defined
# f_name = f_dir_cal + os.sep + "CAMBOT_28mm_51500462_ASP_cal_pix_mod.tsai"
# this camera model was created with ortho2pinhole and includes extrincs/camera pose
f_name = f_dir_tsai + os.sep +"IOCAM0_2019_GR_NASA_20190506-131611.4217.tsai"
f_name_short = os.path.basename(f_name)
# execute function
tsai_params_asp = parse_asp_tsai_file(f_name)
if hasattr(tsai_params_asp, 'x') & hasattr(tsai_params_asp, 'm1'):
if (abs(tsai_params_asp.x) > 360.0):
print(f'\n{f_name_short:s}: found camera pose in ECEF coordinates\n')
else:
print(f'\n{f_name_short:s}: found camera pose in geo coordinates\n')
else:
print(f'\n{f_name_short:s}: camera pose is undefined.\n')
# Prosilica GT 4905C camera pixel size is 5.5 μm × 5.5 μm
pixel_mm = 5.5 * 0.001
# display some example values. center section headlines nicely
horz_line = '-----------------------------------------------------------------'
len_horz_line = len(horz_line)
str_title = 'Prosilica GT 4905C camera with Zeiss 28 mm/F2 lens (S/N 51500462)'
str_title = str_title.center(len_horz_line, " ")
str_focal = 'Focal length estimates'
str_focal = str_focal.center(len_horz_line, " ")
str_principal = 'Center/principal point estimates'
str_principal = str_principal.center(len_horz_line, " ")
print(str_title)
print(str_focal)
print(horz_line)
print(f'fu = {tsai_params_asp.fu*pixel_mm:5.2f} mm')
print(f'fv = {tsai_params_asp.fv*pixel_mm:5.2f} mm\n')
print(str_principal)
print(horz_line)
print(f'cu = {tsai_params_asp.cu:7.2f} pixels')
print(f'cv = {tsai_params_asp.cv:7.2f} pixels')