mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-19 00:41:41 +00:00
Save calibration data and show preliminary GUI (#1078)
* Serialize all calibration data * Run lint * typing nit * fix code * move these tables around some * Add cool formatting * add request to get snapshots by resolution and camera * re-enable all resolutions * add wip so i can change computers (SQUASH ME AND KILL ME AHHHH) * Get everything working but viewing snapshots * Update RequestHandler.java * Update CameraCalibrationInfoCard.vue * Update CameraCalibrationInfoCard.vue * add observation viewer * round * fix illiegal import * Swap to PNG and serialize insolution * move import/export buttons TO THE TOP * Update WebsocketDataTypes.ts * Add snapshotname to observation * Refactor to serialize snapshot image itself * Run lint * Use new base64 image data in info card * Update SettingTypes.ts * Create calibration json -> mrcal converter script * Update calibrationUtils.py * Fix calibrate NPEs in teest * Run lint * Always run cornersubpix * Update CameraCalibrationInfoCard.vue Update CameraCalibrationInfoCard.vue * Update OpenCVHelp.java * Update OpenCVHelp.java * Replace test mode camera JSONs * Run wpiformat * Revert intrinsics but keep other data * Remove misc comments * Rename JsonMat->JsonImageMat and add calobject_warp * Update Server.java * Rename cameraExtrinsics to distCoeffs * fix typing issues * use util methods * Formatting fixes * fix styling * move to devTools * remove unneeded or unused imports * Remove fixed-right css If its really that big of a deal, we can add it back later, kind of a drag to fix rn. * Create util method * Remove extra legacy calibration things --------- Co-authored-by: Sriman Achanta <68172138+srimanachanta@users.noreply.github.com>
This commit is contained in:
134
devTools/calibrationUtils.py
Normal file
134
devTools/calibrationUtils.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import base64
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
import os
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
@dataclass
|
||||
class Resolution:
|
||||
width: int
|
||||
height: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class JsonMatOfDoubles:
|
||||
rows: int
|
||||
cols: int
|
||||
type: int
|
||||
data: list[float]
|
||||
|
||||
|
||||
@dataclass
|
||||
class JsonMat:
|
||||
rows: int
|
||||
cols: int
|
||||
type: int
|
||||
data: str # Base64-encoded PNG data
|
||||
|
||||
|
||||
@dataclass
|
||||
class Point2:
|
||||
x: float
|
||||
y: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Translation3d:
|
||||
x: float
|
||||
y: float
|
||||
z: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Quaternion:
|
||||
X: float
|
||||
Y: float
|
||||
Z: float
|
||||
W: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Rotation3d:
|
||||
quaternion: Quaternion
|
||||
|
||||
|
||||
@dataclass
|
||||
class Pose3d:
|
||||
translation: Translation3d
|
||||
rotation: Rotation3d
|
||||
|
||||
|
||||
@dataclass
|
||||
class Point3:
|
||||
x: float
|
||||
y: float
|
||||
z: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Observation:
|
||||
# Expected feature 3d location in the camera frame
|
||||
locationInObjectSpace: list[Point3]
|
||||
# Observed location in pixel space
|
||||
locationInImageSpace: list[Point2]
|
||||
# (measured location in pixels) - (expected from FK)
|
||||
reprojectionErrors: list[Point2]
|
||||
# Solver optimized board poses
|
||||
optimisedCameraToObject: Pose3d
|
||||
# If we should use this observation when re-calculating camera calibration
|
||||
includeObservationInCalibration: bool
|
||||
snapshotName: str
|
||||
# The actual image the snapshot is from
|
||||
snapshotData: JsonMat
|
||||
|
||||
|
||||
@dataclass
|
||||
class CameraCalibration:
|
||||
resolution: Resolution
|
||||
cameraIntrinsics: JsonMatOfDoubles
|
||||
distCoeffs: JsonMatOfDoubles
|
||||
observations: list[Observation]
|
||||
|
||||
|
||||
def convert_photon_to_mrcal(photon_cal_json_path: str, output_folder: str):
|
||||
"""
|
||||
Unpack a Photon calibration JSON (eg, photon_calibration_Microsoft_LifeCam_HD-3000_800x600.json) into
|
||||
the output_folder directory with images and corners.vnl file for use with mrcal.
|
||||
"""
|
||||
with open(photon_cal_json_path, "r") as cal_json:
|
||||
# Convert to nested objects instead of nameddicts on json-loads
|
||||
class Generic:
|
||||
@classmethod
|
||||
def from_dict(cls, dict):
|
||||
obj = cls()
|
||||
obj.__dict__.update(dict)
|
||||
return obj
|
||||
|
||||
camera_cal_data: CameraCalibration = json.loads(
|
||||
cal_json.read(), object_hook=Generic.from_dict
|
||||
)
|
||||
|
||||
# Create output_folder if not exists
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
|
||||
# Decode each image and save it as a png
|
||||
for obs in camera_cal_data.observations:
|
||||
image = obs.snapshotData.data
|
||||
decoded_data = base64.b64decode(image)
|
||||
np_data = np.frombuffer(decoded_data, np.uint8)
|
||||
img = cv2.imdecode(np_data, cv2.IMREAD_UNCHANGED)
|
||||
cv2.imwrite(f"{output_folder}/{obs.snapshotName}", img)
|
||||
|
||||
# And create a VNL file for use with mrcal
|
||||
with open(f"{output_folder}/corners.vnl", "w+") as vnl_file:
|
||||
vnl_file.write("# filename x y level\n")
|
||||
|
||||
for obs in camera_cal_data.observations:
|
||||
for corner in obs.locationInImageSpace:
|
||||
# Always level zero
|
||||
vnl_file.write(f"{obs.snapshotName} {corner.x} {corner.y} 0\n")
|
||||
|
||||
vnl_file.flush()
|
||||
Reference in New Issue
Block a user