photonlibpy 2025.0.0a0__py3-none-any.whl → 2025.0.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- photonlibpy/__init__.py +2 -2
- photonlibpy/estimation/__init__.py +5 -0
- photonlibpy/estimation/cameraTargetRelation.py +25 -0
- photonlibpy/estimation/openCVHelp.py +200 -0
- photonlibpy/estimation/rotTrlTransform3d.py +32 -0
- photonlibpy/estimation/targetModel.py +137 -0
- photonlibpy/estimation/visionEstimation.py +91 -0
- photonlibpy/generated/MultiTargetPNPResultSerde.py +12 -0
- photonlibpy/generated/PhotonPipelineMetadataSerde.py +23 -4
- photonlibpy/generated/PhotonPipelineResultSerde.py +19 -2
- photonlibpy/generated/PhotonTrackedTargetSerde.py +40 -0
- photonlibpy/generated/PnpResultSerde.py +19 -0
- photonlibpy/generated/TargetCornerSerde.py +12 -0
- photonlibpy/generated/__init__.py +0 -1
- photonlibpy/networktables/NTTopicSet.py +64 -0
- photonlibpy/networktables/__init__.py +1 -0
- photonlibpy/packet.py +123 -8
- photonlibpy/photonCamera.py +10 -7
- photonlibpy/photonPoseEstimator.py +5 -5
- photonlibpy/simulation/__init__.py +5 -0
- photonlibpy/simulation/photonCameraSim.py +408 -0
- photonlibpy/simulation/simCameraProperties.py +661 -0
- photonlibpy/simulation/videoSimUtil.py +2 -0
- photonlibpy/simulation/visionSystemSim.py +237 -0
- photonlibpy/simulation/visionTargetSim.py +50 -0
- photonlibpy/targeting/TargetCorner.py +5 -1
- photonlibpy/targeting/__init__.py +1 -1
- photonlibpy/targeting/multiTargetPNPResult.py +10 -4
- photonlibpy/targeting/photonPipelineResult.py +12 -5
- photonlibpy/targeting/photonTrackedTarget.py +13 -5
- photonlibpy/version.py +2 -2
- {photonlibpy-2025.0.0a0.dist-info → photonlibpy-2025.0.0b2.dist-info}/METADATA +6 -2
- photonlibpy-2025.0.0b2.dist-info/RECORD +36 -0
- {photonlibpy-2025.0.0a0.dist-info → photonlibpy-2025.0.0b2.dist-info}/WHEEL +1 -1
- photonlibpy-2025.0.0a0.dist-info/RECORD +0 -22
- {photonlibpy-2025.0.0a0.dist-info → photonlibpy-2025.0.0b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,661 @@
|
|
1
|
+
import logging
|
2
|
+
import math
|
3
|
+
import typing
|
4
|
+
|
5
|
+
import cv2 as cv
|
6
|
+
import numpy as np
|
7
|
+
from wpimath.geometry import Rotation2d, Rotation3d, Translation3d
|
8
|
+
from wpimath.units import hertz, seconds
|
9
|
+
|
10
|
+
from ..estimation import RotTrlTransform3d
|
11
|
+
|
12
|
+
|
13
|
+
class SimCameraProperties:
|
14
|
+
def __init__(self, path: str | None = None, width: int = 0, height: int = 0):
|
15
|
+
self.resWidth: int = -1
|
16
|
+
self.resHeight: int = -1
|
17
|
+
self.camIntrinsics: np.ndarray = np.zeros((3, 3)) # [3,3]
|
18
|
+
self.distCoeffs: np.ndarray = np.zeros((8, 1)) # [8,1]
|
19
|
+
self.avgErrorPx: float = 0.0
|
20
|
+
self.errorStdDevPx: float = 0.0
|
21
|
+
self.frameSpeed: seconds = 0.0
|
22
|
+
self.exposureTime: seconds = 0.0
|
23
|
+
self.avgLatency: seconds = 0.0
|
24
|
+
self.latencyStdDev: seconds = 0.0
|
25
|
+
self.viewplanes: list[np.ndarray] = [] # [3,1]
|
26
|
+
|
27
|
+
if path is None:
|
28
|
+
self.setCalibration(960, 720, fovDiag=Rotation2d(math.radians(90.0)))
|
29
|
+
else:
|
30
|
+
raise Exception("not yet implemented")
|
31
|
+
|
32
|
+
def setCalibration(
|
33
|
+
self,
|
34
|
+
width: int,
|
35
|
+
height: int,
|
36
|
+
*,
|
37
|
+
fovDiag: Rotation2d | None = None,
|
38
|
+
newCamIntrinsics: np.ndarray | None = None,
|
39
|
+
newDistCoeffs: np.ndarray | None = None,
|
40
|
+
):
|
41
|
+
# Should be an inverted XOR on the args to differentiate between the signatures
|
42
|
+
|
43
|
+
has_fov_args = fovDiag is not None
|
44
|
+
has_matrix_args = newCamIntrinsics is not None and newDistCoeffs is not None
|
45
|
+
|
46
|
+
if (has_fov_args and has_matrix_args) or (
|
47
|
+
not has_matrix_args and not has_fov_args
|
48
|
+
):
|
49
|
+
raise Exception("not a correct function sig")
|
50
|
+
|
51
|
+
if has_fov_args:
|
52
|
+
if fovDiag.degrees() < 1.0 or fovDiag.degrees() > 179.0:
|
53
|
+
fovDiag = Rotation2d.fromDegrees(
|
54
|
+
max(min(fovDiag.degrees(), 179.0), 1.0)
|
55
|
+
)
|
56
|
+
logging.error(
|
57
|
+
"Requested invalid FOV! Clamping between (1, 179) degrees..."
|
58
|
+
)
|
59
|
+
|
60
|
+
resDiag = math.sqrt(width * width + height * height)
|
61
|
+
diagRatio = math.tan(fovDiag.radians() / 2.0)
|
62
|
+
fovWidth = Rotation2d(math.atan((diagRatio * (width / resDiag)) * 2))
|
63
|
+
fovHeight = Rotation2d(math.atan(diagRatio * (height / resDiag)) * 2)
|
64
|
+
|
65
|
+
newDistCoeffs = np.zeros((8, 1))
|
66
|
+
|
67
|
+
cx = width / 2.0 - 0.5
|
68
|
+
cy = height / 2.0 - 0.5
|
69
|
+
|
70
|
+
fx = cx / math.tan(fovWidth.radians() / 2.0)
|
71
|
+
fy = cy / math.tan(fovHeight.radians() / 2.0)
|
72
|
+
|
73
|
+
newCamIntrinsics = np.array([[fx, 0.0, cx], [0.0, fy, cy], [0.0, 0.0, 1.0]])
|
74
|
+
|
75
|
+
# really convince python we are doing the right thing
|
76
|
+
assert newCamIntrinsics is not None
|
77
|
+
assert newDistCoeffs is not None
|
78
|
+
|
79
|
+
self.resWidth = width
|
80
|
+
self.resHeight = height
|
81
|
+
self.camIntrinsics = newCamIntrinsics
|
82
|
+
self.distCoeffs = newDistCoeffs
|
83
|
+
|
84
|
+
p = [
|
85
|
+
Translation3d(
|
86
|
+
1.0,
|
87
|
+
Rotation3d(
|
88
|
+
0.0,
|
89
|
+
0.0,
|
90
|
+
(self.getPixelYaw(0) + Rotation2d(math.pi / 2.0)).radians(),
|
91
|
+
),
|
92
|
+
),
|
93
|
+
Translation3d(
|
94
|
+
1.0,
|
95
|
+
Rotation3d(
|
96
|
+
0.0,
|
97
|
+
0.0,
|
98
|
+
(self.getPixelYaw(width) + Rotation2d(math.pi / 2.0)).radians(),
|
99
|
+
),
|
100
|
+
),
|
101
|
+
Translation3d(
|
102
|
+
1.0,
|
103
|
+
Rotation3d(
|
104
|
+
0.0,
|
105
|
+
0.0,
|
106
|
+
(self.getPixelPitch(0) + Rotation2d(math.pi / 2.0)).radians(),
|
107
|
+
),
|
108
|
+
),
|
109
|
+
Translation3d(
|
110
|
+
1.0,
|
111
|
+
Rotation3d(
|
112
|
+
0.0,
|
113
|
+
0.0,
|
114
|
+
(self.getPixelPitch(height) + Rotation2d(math.pi / 2.0)).radians(),
|
115
|
+
),
|
116
|
+
),
|
117
|
+
]
|
118
|
+
|
119
|
+
self.viewplanes = []
|
120
|
+
|
121
|
+
for i in p:
|
122
|
+
self.viewplanes.append(np.array([i.X(), i.Y(), i.Z()]))
|
123
|
+
|
124
|
+
def setCalibError(self, newAvgErrorPx: float, newErrorStdDevPx: float):
|
125
|
+
self.avgErrorPx = newAvgErrorPx
|
126
|
+
self.errorStdDevPx = newErrorStdDevPx
|
127
|
+
|
128
|
+
def setFPS(self, fps: hertz):
|
129
|
+
self.frameSpeed = max(1.0 / fps, self.exposureTime)
|
130
|
+
|
131
|
+
def setExposureTime(self, newExposureTime: seconds):
|
132
|
+
self.exposureTime = newExposureTime
|
133
|
+
self.frameSpeed = max(self.frameSpeed, self.exposureTime)
|
134
|
+
|
135
|
+
def setAvgLatency(self, newAvgLatency: seconds):
|
136
|
+
self.vgLatency = newAvgLatency
|
137
|
+
|
138
|
+
def setLatencyStdDev(self, newLatencyStdDev: seconds):
|
139
|
+
self.latencyStdDev = newLatencyStdDev
|
140
|
+
|
141
|
+
def getResWidth(self) -> int:
|
142
|
+
return self.resWidth
|
143
|
+
|
144
|
+
def getResHeight(self) -> int:
|
145
|
+
return self.resHeight
|
146
|
+
|
147
|
+
def getResArea(self) -> int:
|
148
|
+
return self.resWidth * self.resHeight
|
149
|
+
|
150
|
+
def getAspectRatio(self) -> float:
|
151
|
+
return 1.0 * self.resWidth / self.resHeight
|
152
|
+
|
153
|
+
def getIntrinsics(self) -> np.ndarray:
|
154
|
+
return self.camIntrinsics
|
155
|
+
|
156
|
+
def getDistCoeffs(self) -> np.ndarray:
|
157
|
+
return self.distCoeffs
|
158
|
+
|
159
|
+
def getFPS(self) -> hertz:
|
160
|
+
return 1.0 / self.frameSpeed
|
161
|
+
|
162
|
+
def getFrameSpeed(self) -> seconds:
|
163
|
+
return self.frameSpeed
|
164
|
+
|
165
|
+
def getExposureTime(self) -> seconds:
|
166
|
+
return self.exposureTime
|
167
|
+
|
168
|
+
def getAverageLatency(self) -> seconds:
|
169
|
+
return self.avgLatency
|
170
|
+
|
171
|
+
def getLatencyStdDev(self) -> seconds:
|
172
|
+
return self.latencyStdDev
|
173
|
+
|
174
|
+
def getContourAreaPercent(self, points: list[typing.Tuple[float, float]]) -> float:
|
175
|
+
return (
|
176
|
+
cv.contourArea(cv.convexHull(np.array(points))) / self.getResArea() * 100.0
|
177
|
+
)
|
178
|
+
|
179
|
+
def getPixelYaw(self, pixelX: float) -> Rotation2d:
|
180
|
+
fx = self.camIntrinsics[0, 0]
|
181
|
+
cx = self.camIntrinsics[0, 2]
|
182
|
+
xOffset = cx - pixelX
|
183
|
+
return Rotation2d(fx, xOffset)
|
184
|
+
|
185
|
+
def getPixelPitch(self, pixelY: float) -> Rotation2d:
|
186
|
+
fy = self.camIntrinsics[1, 1]
|
187
|
+
cy = self.camIntrinsics[1, 2]
|
188
|
+
yOffset = cy - pixelY
|
189
|
+
return Rotation2d(fy, -yOffset)
|
190
|
+
|
191
|
+
def getPixelRot(self, point: typing.Tuple[int, int]) -> Rotation3d:
|
192
|
+
return Rotation3d(
|
193
|
+
0.0,
|
194
|
+
self.getPixelPitch(point[1]).radians(),
|
195
|
+
self.getPixelYaw(point[0]).radians(),
|
196
|
+
)
|
197
|
+
|
198
|
+
def getCorrectedPixelRot(self, point: typing.Tuple[float, float]) -> Rotation3d:
|
199
|
+
fx = self.camIntrinsics[0, 0]
|
200
|
+
cx = self.camIntrinsics[0, 2]
|
201
|
+
xOffset = cx - point[0]
|
202
|
+
|
203
|
+
fy = self.camIntrinsics[1, 1]
|
204
|
+
cy = self.camIntrinsics[1, 2]
|
205
|
+
yOffset = cy - point[1]
|
206
|
+
|
207
|
+
yaw = Rotation2d(fx, xOffset)
|
208
|
+
pitch = Rotation2d(fy / math.cos(math.atan(xOffset / fx)), -yOffset)
|
209
|
+
return Rotation3d(0.0, pitch.radians(), yaw.radians())
|
210
|
+
|
211
|
+
def getHorizFOV(self) -> Rotation2d:
|
212
|
+
left = self.getPixelYaw(0)
|
213
|
+
right = self.getPixelYaw(self.resWidth)
|
214
|
+
return left - right
|
215
|
+
|
216
|
+
def getVertFOV(self) -> Rotation2d:
|
217
|
+
above = self.getPixelPitch(0)
|
218
|
+
below = self.getPixelPitch(self.resHeight)
|
219
|
+
return below - above
|
220
|
+
|
221
|
+
def getDiagFOV(self) -> Rotation2d:
|
222
|
+
return Rotation2d(
|
223
|
+
math.hypot(self.getHorizFOV().radians(), self.getVertFOV().radians())
|
224
|
+
)
|
225
|
+
|
226
|
+
def getVisibleLine(
|
227
|
+
self, camRt: RotTrlTransform3d, a: Translation3d, b: Translation3d
|
228
|
+
) -> typing.Tuple[float | None, float | None]:
|
229
|
+
relA = camRt.apply(a)
|
230
|
+
relB = camRt.apply(b)
|
231
|
+
|
232
|
+
if relA.X() <= 0.0 and relB.X() <= 0.0:
|
233
|
+
return (None, None)
|
234
|
+
|
235
|
+
av = np.array([relA.X(), relA.Y(), relA.Z()])
|
236
|
+
bv = np.array([relB.X(), relB.Y(), relB.Z()])
|
237
|
+
abv = bv - av
|
238
|
+
|
239
|
+
aVisible = True
|
240
|
+
bVisible = True
|
241
|
+
|
242
|
+
for normal in self.viewplanes:
|
243
|
+
aVisibility = av.dot(normal)
|
244
|
+
if aVisibility < 0:
|
245
|
+
aVisible = False
|
246
|
+
|
247
|
+
bVisibility = bv.dot(normal)
|
248
|
+
if bVisibility < 0:
|
249
|
+
bVisible = False
|
250
|
+
if aVisibility <= 0 and bVisibility <= 0:
|
251
|
+
return (None, None)
|
252
|
+
|
253
|
+
if aVisible and bVisible:
|
254
|
+
return (0.0, 1.0)
|
255
|
+
|
256
|
+
intersections = [float("nan"), float("nan"), float("nan"), float("nan")]
|
257
|
+
|
258
|
+
# Optionally 3x1 vector
|
259
|
+
ipts: typing.List[np.ndarray | None] = [None, None, None, None]
|
260
|
+
|
261
|
+
for i, normal in enumerate(self.viewplanes):
|
262
|
+
a_projn = (av.dot(normal) / normal.dot(normal)) * normal
|
263
|
+
|
264
|
+
if abs(abv.dot(normal)) < 1.0e-5:
|
265
|
+
continue
|
266
|
+
intersections[i] = a_projn.dot(a_projn) / -(abv.dot(a_projn))
|
267
|
+
|
268
|
+
apv = intersections[i] * abv
|
269
|
+
intersectpt = av + apv
|
270
|
+
ipts[i] = intersectpt
|
271
|
+
|
272
|
+
for j in range(1, len(self.viewplanes)):
|
273
|
+
if j == 0:
|
274
|
+
continue
|
275
|
+
oi = (i + j) % len(self.viewplanes)
|
276
|
+
onormal = self.viewplanes[oi]
|
277
|
+
if intersectpt.dot(onormal) < 0:
|
278
|
+
intersections[i] = float("nan")
|
279
|
+
ipts[i] = None
|
280
|
+
break
|
281
|
+
|
282
|
+
if not ipts[i]:
|
283
|
+
continue
|
284
|
+
|
285
|
+
for j in range(i - 1, 0 - 1):
|
286
|
+
oipt = ipts[j]
|
287
|
+
if not oipt:
|
288
|
+
continue
|
289
|
+
|
290
|
+
diff = oipt - intersectpt
|
291
|
+
if abs(diff).max() < 1e-4:
|
292
|
+
intersections[i] = float("nan")
|
293
|
+
ipts[i] = None
|
294
|
+
break
|
295
|
+
|
296
|
+
inter1 = float("nan")
|
297
|
+
inter2 = float("nan")
|
298
|
+
for inter in intersections:
|
299
|
+
if not math.isnan(inter):
|
300
|
+
if math.isnan(inter1):
|
301
|
+
inter1 = inter
|
302
|
+
else:
|
303
|
+
inter2 = inter
|
304
|
+
|
305
|
+
if not math.isnan(inter2):
|
306
|
+
max_ = max(inter1, inter2)
|
307
|
+
min_ = min(inter1, inter2)
|
308
|
+
if aVisible:
|
309
|
+
min_ = 0
|
310
|
+
if bVisible:
|
311
|
+
max_ = 1
|
312
|
+
return (min_, max_)
|
313
|
+
elif not math.isnan(inter1):
|
314
|
+
if aVisible:
|
315
|
+
return (0, inter1)
|
316
|
+
if bVisible:
|
317
|
+
return (inter1, 1)
|
318
|
+
return (inter1, None)
|
319
|
+
else:
|
320
|
+
return (None, None)
|
321
|
+
|
322
|
+
def estPixelNoise(self, points: np.ndarray) -> np.ndarray:
|
323
|
+
assert points.shape[1] == 1, points.shape
|
324
|
+
assert points.shape[2] == 2, points.shape
|
325
|
+
if self.avgErrorPx == 0 and self.errorStdDevPx == 0:
|
326
|
+
return points
|
327
|
+
|
328
|
+
noisyPts: list[list] = []
|
329
|
+
for p in points:
|
330
|
+
error = np.random.normal(self.avgErrorPx, self.errorStdDevPx, 1)[0]
|
331
|
+
errorAngle = np.random.uniform(-math.pi, math.pi)
|
332
|
+
noisyPts.append(
|
333
|
+
[
|
334
|
+
[
|
335
|
+
float(p[0, 0] + error * math.cos(errorAngle)),
|
336
|
+
float(p[0, 1] + error * math.sin(errorAngle)),
|
337
|
+
]
|
338
|
+
]
|
339
|
+
)
|
340
|
+
retval = np.array(noisyPts, dtype=np.float32)
|
341
|
+
assert points.shape == retval.shape, retval
|
342
|
+
return retval
|
343
|
+
|
344
|
+
def estLatency(self) -> seconds:
|
345
|
+
return max(
|
346
|
+
float(np.random.normal(self.avgLatency, self.latencyStdDev, 1)[0]),
|
347
|
+
0.0,
|
348
|
+
)
|
349
|
+
|
350
|
+
def estSecUntilNextFrame(self) -> seconds:
|
351
|
+
return self.frameSpeed + max(0.0, self.estLatency() - self.frameSpeed)
|
352
|
+
|
353
|
+
@classmethod
|
354
|
+
def PERFECT_90DEG(cls) -> typing.Self:
|
355
|
+
return cls()
|
356
|
+
|
357
|
+
@classmethod
|
358
|
+
def PI4_LIFECAM_320_240(cls) -> typing.Self:
|
359
|
+
prop = cls()
|
360
|
+
prop.setCalibration(
|
361
|
+
320,
|
362
|
+
240,
|
363
|
+
newCamIntrinsics=np.array(
|
364
|
+
[
|
365
|
+
[328.2733242048587, 0.0, 164.8190261141906],
|
366
|
+
[0.0, 318.0609794305216, 123.8633838438093],
|
367
|
+
[0.0, 0.0, 1.0],
|
368
|
+
]
|
369
|
+
),
|
370
|
+
newDistCoeffs=np.array(
|
371
|
+
[
|
372
|
+
[
|
373
|
+
0.09957946553445934,
|
374
|
+
-0.9166265114485799,
|
375
|
+
0.0019519890627236526,
|
376
|
+
-0.0036071725380870333,
|
377
|
+
1.5627234622420942,
|
378
|
+
0,
|
379
|
+
0,
|
380
|
+
0,
|
381
|
+
]
|
382
|
+
]
|
383
|
+
),
|
384
|
+
)
|
385
|
+
prop.setCalibError(0.21, 0.0124)
|
386
|
+
prop.setFPS(30.0)
|
387
|
+
prop.setAvgLatency(30.0e-3)
|
388
|
+
prop.setLatencyStdDev(10.0e-3)
|
389
|
+
return prop
|
390
|
+
|
391
|
+
@classmethod
|
392
|
+
def PI4_LIFECAM_640_480(cls) -> typing.Self:
|
393
|
+
prop = cls()
|
394
|
+
prop.setCalibration(
|
395
|
+
640,
|
396
|
+
480,
|
397
|
+
newCamIntrinsics=np.array(
|
398
|
+
[
|
399
|
+
[669.1428078983059, 0.0, 322.53377249329213],
|
400
|
+
[0.0, 646.9843137061716, 241.26567383784163],
|
401
|
+
[0.0, 0.0, 1.0],
|
402
|
+
]
|
403
|
+
),
|
404
|
+
newDistCoeffs=np.array(
|
405
|
+
[
|
406
|
+
[
|
407
|
+
0.12788470750464645,
|
408
|
+
-1.2350335805796528,
|
409
|
+
0.0024990767286192732,
|
410
|
+
-0.0026958287600230705,
|
411
|
+
2.2951386729115537,
|
412
|
+
0,
|
413
|
+
0,
|
414
|
+
0,
|
415
|
+
]
|
416
|
+
]
|
417
|
+
),
|
418
|
+
)
|
419
|
+
prop.setCalibError(0.26, 0.046)
|
420
|
+
prop.setFPS(15.0)
|
421
|
+
prop.setAvgLatency(65.0e-3)
|
422
|
+
prop.setLatencyStdDev(15.0e-3)
|
423
|
+
return prop
|
424
|
+
|
425
|
+
@classmethod
|
426
|
+
def LL2_640_480(cls) -> typing.Self:
|
427
|
+
prop = cls()
|
428
|
+
prop.setCalibration(
|
429
|
+
640,
|
430
|
+
480,
|
431
|
+
newCamIntrinsics=np.array(
|
432
|
+
[
|
433
|
+
[511.22843367007755, 0.0, 323.62049380211096],
|
434
|
+
[0.0, 514.5452336723849, 261.8827920543568],
|
435
|
+
[0.0, 0.0, 1.0],
|
436
|
+
]
|
437
|
+
),
|
438
|
+
newDistCoeffs=np.array(
|
439
|
+
[
|
440
|
+
[
|
441
|
+
0.1917469998873756,
|
442
|
+
-0.5142936883324216,
|
443
|
+
0.012461562046896614,
|
444
|
+
0.0014084973492408186,
|
445
|
+
0.35160648971214437,
|
446
|
+
0,
|
447
|
+
0,
|
448
|
+
0,
|
449
|
+
]
|
450
|
+
]
|
451
|
+
),
|
452
|
+
)
|
453
|
+
prop.setCalibError(0.25, 0.05)
|
454
|
+
prop.setFPS(15.0)
|
455
|
+
prop.setAvgLatency(35.0e-3)
|
456
|
+
prop.setLatencyStdDev(8.0e-3)
|
457
|
+
return prop
|
458
|
+
|
459
|
+
@classmethod
|
460
|
+
def LL2_960_720(cls) -> typing.Self:
|
461
|
+
prop = cls()
|
462
|
+
prop.setCalibration(
|
463
|
+
960,
|
464
|
+
720,
|
465
|
+
newCamIntrinsics=np.array(
|
466
|
+
[
|
467
|
+
[769.6873145148892, 0.0, 486.1096609458122],
|
468
|
+
[0.0, 773.8164483705323, 384.66071662358354],
|
469
|
+
[0.0, 0.0, 1.0],
|
470
|
+
]
|
471
|
+
),
|
472
|
+
newDistCoeffs=np.array(
|
473
|
+
[
|
474
|
+
[
|
475
|
+
0.189462064814501,
|
476
|
+
-0.49903003669627627,
|
477
|
+
0.007468423590519429,
|
478
|
+
0.002496885298683693,
|
479
|
+
0.3443122090208624,
|
480
|
+
0,
|
481
|
+
0,
|
482
|
+
0,
|
483
|
+
]
|
484
|
+
]
|
485
|
+
),
|
486
|
+
)
|
487
|
+
prop.setCalibError(0.35, 0.10)
|
488
|
+
prop.setFPS(10.0)
|
489
|
+
prop.setAvgLatency(50.0e-3)
|
490
|
+
prop.setLatencyStdDev(15.0e-3)
|
491
|
+
return prop
|
492
|
+
|
493
|
+
@classmethod
|
494
|
+
def LL2_1280_720(cls) -> typing.Self:
|
495
|
+
prop = cls()
|
496
|
+
prop.setCalibration(
|
497
|
+
1280,
|
498
|
+
720,
|
499
|
+
newCamIntrinsics=np.array(
|
500
|
+
[
|
501
|
+
[1011.3749416937393, 0.0, 645.4955139388737],
|
502
|
+
[0.0, 1008.5391755084075, 508.32877656020196],
|
503
|
+
[0.0, 0.0, 1.0],
|
504
|
+
]
|
505
|
+
),
|
506
|
+
newDistCoeffs=np.array(
|
507
|
+
[
|
508
|
+
[
|
509
|
+
0.13730101577061535,
|
510
|
+
-0.2904345656989261,
|
511
|
+
8.32475714507539e-4,
|
512
|
+
-3.694397782014239e-4,
|
513
|
+
0.09487962227027584,
|
514
|
+
0,
|
515
|
+
0,
|
516
|
+
0,
|
517
|
+
]
|
518
|
+
]
|
519
|
+
),
|
520
|
+
)
|
521
|
+
prop.setCalibError(0.37, 0.06)
|
522
|
+
prop.setFPS(7.0)
|
523
|
+
prop.setAvgLatency(60.0e-3)
|
524
|
+
prop.setLatencyStdDev(20.0e-3)
|
525
|
+
return prop
|
526
|
+
|
527
|
+
@classmethod
|
528
|
+
def OV9281_640_480(cls) -> typing.Self:
|
529
|
+
prop = cls()
|
530
|
+
prop.setCalibration(
|
531
|
+
640,
|
532
|
+
480,
|
533
|
+
newCamIntrinsics=np.array(
|
534
|
+
[
|
535
|
+
[627.1573807284262, 0, 307.79423851611824],
|
536
|
+
[0, 626.6621595938243, 219.02625533911998],
|
537
|
+
[0, 0, 1],
|
538
|
+
]
|
539
|
+
),
|
540
|
+
newDistCoeffs=np.array(
|
541
|
+
[
|
542
|
+
[
|
543
|
+
0.054834081023049625,
|
544
|
+
-0.15994111706817074,
|
545
|
+
-0.0017587106009926158,
|
546
|
+
-0.0014671022483263552,
|
547
|
+
0.049742166267499596,
|
548
|
+
0,
|
549
|
+
0,
|
550
|
+
0,
|
551
|
+
],
|
552
|
+
]
|
553
|
+
),
|
554
|
+
)
|
555
|
+
prop.setCalibError(0.25, 0.05)
|
556
|
+
prop.setFPS(30.0)
|
557
|
+
prop.setAvgLatency(60.0e-3)
|
558
|
+
prop.setLatencyStdDev(20.0e-3)
|
559
|
+
return prop
|
560
|
+
|
561
|
+
@classmethod
|
562
|
+
def OV9281_800_600(cls) -> typing.Self:
|
563
|
+
prop = cls()
|
564
|
+
prop.setCalibration(
|
565
|
+
800,
|
566
|
+
600,
|
567
|
+
newCamIntrinsics=np.array(
|
568
|
+
[
|
569
|
+
[783.9467259105329, 0, 384.7427981451478],
|
570
|
+
[0, 783.3276994922804, 273.7828191739],
|
571
|
+
[0, 0, 1],
|
572
|
+
]
|
573
|
+
),
|
574
|
+
newDistCoeffs=np.array(
|
575
|
+
[
|
576
|
+
[
|
577
|
+
0.054834081023049625,
|
578
|
+
-0.15994111706817074,
|
579
|
+
-0.0017587106009926158,
|
580
|
+
-0.0014671022483263552,
|
581
|
+
0.049742166267499596,
|
582
|
+
0,
|
583
|
+
0,
|
584
|
+
0,
|
585
|
+
],
|
586
|
+
]
|
587
|
+
),
|
588
|
+
)
|
589
|
+
prop.setCalibError(0.25, 0.05)
|
590
|
+
prop.setFPS(25.0)
|
591
|
+
prop.setAvgLatency(60.0e-3)
|
592
|
+
prop.setLatencyStdDev(20.0e-3)
|
593
|
+
return prop
|
594
|
+
|
595
|
+
@classmethod
|
596
|
+
def OV9281_1280_720(cls) -> typing.Self:
|
597
|
+
prop = cls()
|
598
|
+
prop.setCalibration(
|
599
|
+
1280,
|
600
|
+
720,
|
601
|
+
newCamIntrinsics=np.array(
|
602
|
+
[
|
603
|
+
[940.7360710926395, 0, 615.5884770322365],
|
604
|
+
[0, 939.9932393907364, 328.53938300868],
|
605
|
+
[0, 0, 1],
|
606
|
+
]
|
607
|
+
),
|
608
|
+
newDistCoeffs=np.array(
|
609
|
+
[
|
610
|
+
[
|
611
|
+
0.054834081023049625,
|
612
|
+
-0.15994111706817074,
|
613
|
+
-0.0017587106009926158,
|
614
|
+
-0.0014671022483263552,
|
615
|
+
0.049742166267499596,
|
616
|
+
0,
|
617
|
+
0,
|
618
|
+
0,
|
619
|
+
],
|
620
|
+
]
|
621
|
+
),
|
622
|
+
)
|
623
|
+
prop.setCalibError(0.25, 0.05)
|
624
|
+
prop.setFPS(15.0)
|
625
|
+
prop.setAvgLatency(60.0e-3)
|
626
|
+
prop.setLatencyStdDev(20.0e-3)
|
627
|
+
return prop
|
628
|
+
|
629
|
+
@classmethod
|
630
|
+
def OV9281_1920_1080(cls) -> typing.Self:
|
631
|
+
prop = cls()
|
632
|
+
prop.setCalibration(
|
633
|
+
1920,
|
634
|
+
1080,
|
635
|
+
newCamIntrinsics=np.array(
|
636
|
+
[
|
637
|
+
[1411.1041066389591, 0, 923.3827155483548],
|
638
|
+
[0, 1409.9898590861046, 492.80907451301994],
|
639
|
+
[0, 0, 1],
|
640
|
+
]
|
641
|
+
),
|
642
|
+
newDistCoeffs=np.array(
|
643
|
+
[
|
644
|
+
[
|
645
|
+
0.054834081023049625,
|
646
|
+
-0.15994111706817074,
|
647
|
+
-0.0017587106009926158,
|
648
|
+
-0.0014671022483263552,
|
649
|
+
0.049742166267499596,
|
650
|
+
0,
|
651
|
+
0,
|
652
|
+
0,
|
653
|
+
],
|
654
|
+
]
|
655
|
+
),
|
656
|
+
)
|
657
|
+
prop.setCalibError(0.25, 0.05)
|
658
|
+
prop.setFPS(10.0)
|
659
|
+
prop.setAvgLatency(60.0e-3)
|
660
|
+
prop.setLatencyStdDev(20.0e-3)
|
661
|
+
return prop
|