Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import cv2 | |
| import open3d as o3d | |
| from scipy.spatial.transform import Rotation | |
| import argparse | |
| import os | |
| class LiDARCameraCalibrator: | |
| def __init__(self, point_cloud_path, image_path, camera_matrix, dist_coeffs): | |
| """ | |
| Initialize the calibrator with point cloud and image data | |
| Args: | |
| point_cloud_path (str): Path to LiDAR point cloud file (.pcd, .ply, .bin, etc.) | |
| image_path (str): Path to camera image | |
| camera_matrix (np.array): 3x3 camera intrinsic matrix | |
| dist_coeffs (np.array): Camera distortion coefficients | |
| """ | |
| # Load point cloud | |
| self.points_3d = self.load_point_cloud(point_cloud_path) | |
| # Load image | |
| self.image = cv2.imread(image_path) | |
| self.original_image = self.image.copy() | |
| self.height, self.width = self.image.shape[:2] | |
| # Camera parameters | |
| self.camera_matrix = camera_matrix | |
| self.dist_coeffs = dist_coeffs | |
| # self.fixed_translation = np.array([0.676, 0.0, 1.486]) | |
| # self.fixed_transform = np.eye(4) | |
| # self.fixed_transform[:3, 3] = self.fixed_translation | |
| # Initial transformation parameters (adjustable via trackbars) | |
| # tx=1.932+0.676, ty=0.25+0, tz=1.4+1.486, rx=0, ry=-2.0, rz=1.5 | |
| self.tx = -1.16 # Translation X | |
| self.ty = -0.23 # Translation Y | |
| self.tz = 0.42 # Translation Z | |
| self.rx = 0.0 # Rotation X (degrees) | |
| self.ry = 2.0 # Rotation Y (degrees) | |
| self.rz = -0.55 # Rotation Z (degrees) | |
| # Trackbar ranges (scaled by 1000 for precision) | |
| self.t_range = 50000 # ±50m in mm | |
| self.r_range = 18000 # ±180 degrees in 0.01 degree units | |
| # Setup OpenCV window and trackbars | |
| self.setup_gui() | |
| def load_point_cloud(self, file_path): | |
| """ | |
| Load point cloud from various formats including .bin files | |
| Args: | |
| file_path (str): Path to point cloud file | |
| Returns: | |
| np.array: Nx3 array of 3D points | |
| """ | |
| file_ext = os.path.splitext(file_path)[1].lower() | |
| if file_ext == '.bin': | |
| # Load binary point cloud (common format for KITTI, nuScenes, etc.) | |
| # Assumes format: [x, y, z, intensity] or [x, y, z, intensity, ring, time, etc.] | |
| points = np.fromfile(file_path, dtype=np.float32) | |
| # Determine the number of fields per point | |
| # Common formats: 4 (x,y,z,intensity), 5 (x,y,z,intensity,ring), 6+ (additional fields) | |
| if len(points) % 4 == 0: | |
| points = points.reshape(-1, 4) | |
| points_3d = points[:, :3] # Take only x, y, z | |
| self.intensities = points[:, 3] # Store intensity for potential use | |
| # print(self.intensities.max()) | |
| # print(self.intensities.min()) | |
| print(f"Loaded {len(points_3d)} points from .bin file (4 fields per point)") | |
| elif len(points) % 6 == 0: | |
| points = points.reshape(-1, 6) | |
| points_3d = points[:, :3] # Take only x, y, z | |
| self.intensities = points[:, 3] # Store intensity | |
| print(f"Loaded {len(points_3d)} points from .bin file (6 fields per point)") | |
| else: | |
| # Try to infer the structure or default to 3D points only | |
| # Assume the most common case where points are stored as [x,y,z,...] | |
| n_fields = 4 # Default assumption | |
| if len(points) % 3 == 0 and len(points) % 4 != 0: | |
| n_fields = 3 | |
| points = points.reshape(-1, n_fields) | |
| points_3d = points[:, :3] | |
| if n_fields > 3: | |
| self.intensities = points[:, 3] | |
| else: | |
| self.intensities = None | |
| print(f"Loaded {len(points_3d)} points from .bin file ({n_fields} fields per point)") | |
| return points_3d | |
| else: | |
| # Use Open3D for standard formats (.pcd, .ply, etc.) | |
| try: | |
| pcd = o3d.io.read_point_cloud(file_path) | |
| points_3d = np.asarray(pcd.points) | |
| self.intensities = None # Open3D formats don't always have intensity | |
| print(f"Loaded {len(points_3d)} points using Open3D") | |
| return points_3d | |
| except Exception as e: | |
| raise ValueError(f"Could not load point cloud {file_path}: {str(e)}") | |
| def setup_gui(self): | |
| """Setup OpenCV window and trackbars for real-time adjustment""" | |
| cv2.namedWindow('LiDAR-Camera Calibration', cv2.WINDOW_NORMAL) | |
| cv2.resizeWindow('LiDAR-Camera Calibration', 1200, 800) | |
| # Create trackbars for translation (in mm for precision) | |
| cv2.createTrackbar('TX (mm)', 'LiDAR-Camera Calibration', | |
| int(self.tx * 1000) + self.t_range, 2 * self.t_range, self.update_transform) | |
| cv2.createTrackbar('TY (mm)', 'LiDAR-Camera Calibration', | |
| int(self.ty * 1000) + self.t_range, 2 * self.t_range, self.update_transform) | |
| cv2.createTrackbar('TZ (mm)', 'LiDAR-Camera Calibration', | |
| int(self.tz * 1000) + self.t_range, 2 * self.t_range, self.update_transform) | |
| # Create trackbars for rotation (in 0.01 degree units) | |
| cv2.createTrackbar('RX (0.01°)', 'LiDAR-Camera Calibration', | |
| int(self.rx * 100) + self.r_range, 2 * self.r_range, self.update_transform) | |
| cv2.createTrackbar('RY (0.01°)', 'LiDAR-Camera Calibration', | |
| int(self.ry * 100) + self.r_range, 2 * self.r_range, self.update_transform) | |
| cv2.createTrackbar('RZ (0.01°)', 'LiDAR-Camera Calibration', | |
| int(self.rz * 100) + self.r_range, 2 * self.r_range, self.update_transform) | |
| # Additional controls | |
| cv2.createTrackbar('Point Size', 'LiDAR-Camera Calibration', 2, 10, self.update_transform) | |
| cv2.createTrackbar('Max Distance (m)', 'LiDAR-Camera Calibration', 100, 200, self.update_transform) | |
| cv2.createTrackbar('Min Distance (m)', 'LiDAR-Camera Calibration', 0, 50, self.update_transform) | |
| cv2.createTrackbar('Intensity Filter', 'LiDAR-Camera Calibration', 0, 100, self.update_transform) | |
| def set_initial_values(self, tx=0, ty=0, tz=0, rx=0, ry=0, rz=0): | |
| """Set initial transformation values""" | |
| self.tx, self.ty, self.tz = tx, ty, tz | |
| self.rx, self.ry, self.rz = rx, ry, rz | |
| # Update trackbars | |
| cv2.setTrackbarPos('TX (mm)', 'LiDAR-Camera Calibration', int(tx * 1000) + self.t_range) | |
| cv2.setTrackbarPos('TY (mm)', 'LiDAR-Camera Calibration', int(ty * 1000) + self.t_range) | |
| cv2.setTrackbarPos('TZ (mm)', 'LiDAR-Camera Calibration', int(tz * 1000) + self.t_range) | |
| cv2.setTrackbarPos('RX (0.01°)', 'LiDAR-Camera Calibration', int(rx * 100) + self.r_range) | |
| cv2.setTrackbarPos('RY (0.01°)', 'LiDAR-Camera Calibration', int(ry * 100) + self.r_range) | |
| cv2.setTrackbarPos('RZ (0.01°)', 'LiDAR-Camera Calibration', int(rz * 100) + self.r_range) | |
| def update_transform(self, val): | |
| """Callback function for trackbar updates""" | |
| # Get current trackbar values | |
| self.tx = (cv2.getTrackbarPos('TX (mm)', 'LiDAR-Camera Calibration') - self.t_range) / 1000.0 | |
| self.ty = (cv2.getTrackbarPos('TY (mm)', 'LiDAR-Camera Calibration') - self.t_range) / 1000.0 | |
| self.tz = (cv2.getTrackbarPos('TZ (mm)', 'LiDAR-Camera Calibration') - self.t_range) / 1000.0 | |
| self.rx = (cv2.getTrackbarPos('RX (0.01°)', 'LiDAR-Camera Calibration') - self.r_range) / 100.0 | |
| self.ry = (cv2.getTrackbarPos('RY (0.01°)', 'LiDAR-Camera Calibration') - self.r_range) / 100.0 | |
| self.rz = (cv2.getTrackbarPos('RZ (0.01°)', 'LiDAR-Camera Calibration') - self.r_range) / 100.0 | |
| # Update visualization | |
| self.visualize_projection() | |
| def get_transformation_matrix(self): | |
| """Calculate 4x4 transformation matrix from current parameters""" | |
| # Create rotation matrix from Euler angles (XYZ order) | |
| rotation = Rotation.from_euler('xyz', [self.rx, self.ry, self.rz], degrees=True) | |
| # R = rotation.as_matrix() | |
| coord_transform = np.array([ | |
| [0, -1, 0], | |
| [0, 0, -1], | |
| [1, 0, 0] | |
| ]) | |
| R = rotation.as_matrix() #@ coord_transform | |
| # Create translation vector | |
| t = np.array([self.tx, self.ty, self.tz]) | |
| # Build 4x4 transformation matrix | |
| variable_T = np.eye(4) | |
| variable_T[:3, :3] = R | |
| variable_T[:3, 3] = t | |
| # T = variable_T @ self.fixed_transform | |
| T = variable_T | |
| # Convert combined transform to camera coordinate frame | |
| C4 = np.eye(4) | |
| C4[:3, :3] = coord_transform # maps robot -> camera | |
| T_camera = C4 @ T | |
| return T_camera | |
| def project_points(self): | |
| """Project 3D LiDAR points to image coordinates""" | |
| if len(self.points_3d) == 0: | |
| return np.array([]), np.array([]) | |
| # Apply transformation | |
| T = self.get_transformation_matrix() | |
| points_homo = np.column_stack([self.points_3d, np.ones(len(self.points_3d))]) | |
| transformed_points = (T @ points_homo.T).T[:, :3] | |
| # Filter points that are in front of camera | |
| valid_mask = transformed_points[:, 2] > 0 | |
| valid_points = transformed_points[valid_mask] | |
| valid_inten = self.intensities[valid_mask] | |
| if len(valid_points) == 0: | |
| return np.array([]), np.array([]) | |
| # Filter by distance | |
| max_dist = cv2.getTrackbarPos('Max Distance (m)', 'LiDAR-Camera Calibration') | |
| min_dist = cv2.getTrackbarPos('Min Distance (m)', 'LiDAR-Camera Calibration') | |
| distances = np.linalg.norm(valid_points, axis=1) | |
| dist_mask = (distances >= min_dist) & (distances <= max_dist) | |
| valid_points = valid_points[dist_mask] | |
| valid_inten = valid_inten[dist_mask] | |
| valid_distances = distances[dist_mask] | |
| # Filter by intensity if available | |
| if hasattr(self, 'intensities') and self.intensities is not None: | |
| intensity_threshold = cv2.getTrackbarPos('Intensity Filter', 'LiDAR-Camera Calibration') / 100.0 | |
| # Apply original valid_mask and dist_mask to intensities | |
| original_valid_mask = np.arange(len(self.points_3d))[valid_mask[:len(valid_mask)]][:len(valid_points)] | |
| if len(original_valid_mask) > 0 and len(original_valid_mask) <= len(self.intensities): | |
| valid_intensities = self.intensities[original_valid_mask] | |
| # Normalize intensities to 0-1 range | |
| if len(valid_intensities) > 0: | |
| intensity_norm = (valid_intensities - np.min(valid_intensities)) / (np.max(valid_intensities) - np.min(valid_intensities) + 1e-8) | |
| intensity_mask = intensity_norm >= intensity_threshold | |
| valid_points = valid_points[intensity_mask] | |
| valid_inten = valid_inten[intensity_mask] | |
| valid_distances = valid_distances[intensity_mask] | |
| if len(valid_points) == 0: | |
| return np.array([]), np.array([]) | |
| # Project to image coordinates | |
| rvec = np.zeros(3) # No additional rotation | |
| tvec = np.zeros(3) # No additional translation | |
| image_points, _ = cv2.projectPoints( | |
| valid_points, rvec, tvec, self.camera_matrix, self.dist_coeffs | |
| ) | |
| image_points = image_points.reshape(-1, 2) | |
| # Filter points within image bounds | |
| h, w = self.height, self.width | |
| valid_img_mask = ((image_points[:, 0] >= 0) & (image_points[:, 0] < w) & | |
| (image_points[:, 1] >= 0) & (image_points[:, 1] < h)) | |
| return image_points[valid_img_mask], valid_distances[valid_img_mask], valid_inten[valid_img_mask] | |
| def visualize_projection(self): | |
| """Update the visualization with current transformation""" | |
| # Reset image | |
| self.image = self.original_image.copy() | |
| # Project points | |
| projected_points, distances, intensities = self.project_points() | |
| if len(projected_points) == 0: | |
| cv2.putText(self.image, "No valid points to display", (50, 50), | |
| cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) | |
| else: | |
| # Color points by distance (closer = red, farther = blue) | |
| max_dist = cv2.getTrackbarPos('Max Distance (m)', 'LiDAR-Camera Calibration') | |
| normalized_dist = distances.clip(0, 100) / min(max_dist,distances.max()) | |
| normalized_intensity = intensities/255 | |
| point_size = cv2.getTrackbarPos('Point Size', 'LiDAR-Camera Calibration') | |
| if point_size == 0: | |
| point_size = 1 | |
| # for i, (point, dist) in enumerate(zip(projected_points, normalized_dist)): | |
| for i, (point, dist) in enumerate(zip(projected_points, normalized_intensity)): | |
| # Color from red (close) to blue (far) | |
| color = (255 * (1 - dist), 0, 255 * dist) | |
| cv2.circle(self.image, (int(point[0]), int(point[1])), | |
| point_size, color, -1) | |
| # Add parameter information | |
| info_text = [ | |
| f"TX: {self.tx:.3f}m TY: {self.ty:.3f}m TZ: {self.tz:.3f}m", | |
| f"RX: {self.rx:.2f}° RY: {self.ry:.2f}° RZ: {self.rz:.2f}°", | |
| f"Projected points: {len(projected_points)}", | |
| "Press 'q' to quit, 's' to save parameters, 'r' to reset" | |
| ] | |
| for i, text in enumerate(info_text): | |
| cv2.putText(self.image, text, (10, 30 + i * 25), | |
| cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA) | |
| cv2.putText(self.image, text, (10, 30 + i * 25), | |
| cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1, cv2.LINE_AA) | |
| cv2.imshow('LiDAR-Camera Calibration', self.image) | |
| def save_parameters(self, filename="calibration_params.txt"): | |
| """Save current transformation parameters to file""" | |
| T = self.get_transformation_matrix() | |
| with open(filename, 'w') as f: | |
| f.write("LiDAR-Camera Calibration Parameters\n") | |
| f.write("=" * 40 + "\n\n") | |
| f.write(f"Translation (meters):\n") | |
| f.write(f" TX: {self.tx:.6f}\n") | |
| f.write(f" TY: {self.ty:.6f}\n") | |
| f.write(f" TZ: {self.tz:.6f}\n\n") | |
| f.write(f"Rotation (degrees):\n") | |
| f.write(f" RX: {self.rx:.6f}\n") | |
| f.write(f" RY: {self.ry:.6f}\n") | |
| f.write(f" RZ: {self.rz:.6f}\n\n") | |
| f.write("4x4 Transformation Matrix:\n") | |
| f.write(str(T) + "\n") | |
| print(f"Parameters saved to {filename}") | |
| def run(self): | |
| """Start the interactive calibration tool""" | |
| print("LiDAR-Camera Calibration Tool") | |
| print("Use trackbars to adjust transformation parameters") | |
| print("Controls:") | |
| print(" 'q' - Quit") | |
| print(" 's' - Save current parameters") | |
| print(" 'r' - Reset to initial values") | |
| # Initial visualization | |
| self.visualize_projection() | |
| while True: | |
| key = cv2.waitKey(30) & 0xFF | |
| if key == ord('q'): | |
| break | |
| elif key == ord('s'): | |
| self.save_parameters() | |
| elif key == ord('r'): | |
| self.set_initial_values() | |
| self.visualize_projection() | |
| cv2.destroyAllWindows() | |
| def create_camera_matrix(focal_length_mm, principal_point_x_pixels, principal_point_y_pixels, | |
| pixels_per_mm_x, pixels_per_mm_y): | |
| """ | |
| Create camera matrix from physical camera parameters. | |
| Args: | |
| focal_length_mm: Focal length in millimeters | |
| principal_point_x_pixels: Principal point X coordinate in pixels | |
| principal_point_y_pixels: Principal point Y coordinate in pixels | |
| pixels_per_mm_x: Pixels per millimeter in X direction | |
| pixels_per_mm_y: Pixels per millimeter in Y direction | |
| Returns: | |
| camera_matrix: 3x3 camera intrinsic matrix | |
| """ | |
| # Debug: Check inputs | |
| # print(f"Input values:") | |
| # print(f" focal_length_mm: {focal_length_mm}") | |
| # print(f" principal_point_x_pixels: {principal_point_x_pixels}") | |
| # print(f" principal_point_y_pixels: {principal_point_y_pixels}") | |
| # print(f" pixels_per_mm_x: {pixels_per_mm_x}") | |
| # print(f" pixels_per_mm_y: {pixels_per_mm_y}") | |
| # Check for None or invalid values | |
| if any(x is None for x in [focal_length_mm, principal_point_x_pixels, principal_point_y_pixels, | |
| pixels_per_mm_x, pixels_per_mm_y]): | |
| print("ERROR: One or more input parameters is None!") | |
| return None | |
| if pixels_per_mm_x == 0 or pixels_per_mm_y == 0: | |
| print("ERROR: pixels_per_mm cannot be zero!") | |
| return None | |
| try: | |
| # Convert focal length from mm to pixels | |
| fx = focal_length_mm * pixels_per_mm_x | |
| fy = focal_length_mm * pixels_per_mm_y | |
| # Principal point is already in pixels | |
| cx = principal_point_x_pixels | |
| cy = principal_point_y_pixels | |
| # print(f"DEBUG: Camera matrix calculation:") | |
| # print(f" fx = {focal_length_mm} * {pixels_per_mm_x} = {fx}") | |
| # print(f" fy = {focal_length_mm} * {pixels_per_mm_y} = {fy}") | |
| # print(f" cx = {cx}") | |
| # print(f" cy = {cy}") | |
| camera_matrix = np.array([ | |
| [fx, 0, cx], | |
| [0, fy, cy], | |
| [0, 0, 1] | |
| ], dtype=np.float64) | |
| # print(f"DEBUG: Final camera matrix:\n{camera_matrix}") | |
| return camera_matrix | |
| except Exception as e: | |
| print(f"ERROR in create_camera_matrix: {e}") | |
| return None | |
| def create_sample_data(): | |
| """Create sample point cloud and camera parameters for testing""" | |
| # Generate sample point cloud (random points in a cube) | |
| np.random.seed(42) | |
| n_points = 1000 | |
| points = np.random.uniform(-10, 10, (n_points, 3)) | |
| points[:, 2] += 20 # Move points in front of camera | |
| # Add intensity values | |
| intensities = np.random.uniform(0, 255, n_points) | |
| # Create .bin file (KITTI format: x, y, z, intensity) | |
| bin_data = np.column_stack([points, intensities]).astype(np.float32) | |
| bin_data.tofile("sample_pointcloud.bin") | |
| # Also create .pcd for compatibility | |
| pcd = o3d.geometry.PointCloud() | |
| pcd.points = o3d.utility.Vector3dVector(points) | |
| o3d.io.write_point_cloud("sample_pointcloud.pcd", pcd) | |
| # Create sample camera matrix (typical values) | |
| focal_length_mm = 12.0 # Your focal length in mm | |
| principal_point_x_pixels = 960 # Principal point X in pixels | |
| principal_point_y_pixels = 600 # Principal point Y in pixels | |
| pixels_per_mm_x = 170.648 # Your pixels per mm in X | |
| pixels_per_mm_y = 170.648 # Your pixels per mm in Y | |
| # Create camera matrix | |
| camera_matrix = create_camera_matrix( | |
| focal_length_mm, principal_point_x_pixels, principal_point_y_pixels, | |
| pixels_per_mm_x, pixels_per_mm_y | |
| ) | |
| # camera_matrix = np.array([ | |
| # [800, 0, 320], | |
| # [0, 800, 240], | |
| # [0, 0, 1] | |
| # ], dtype=np.float32) | |
| # Sample distortion coefficients | |
| # dist_coeffs = np.array([0.1, -0.2, 0, 0, 0.1], dtype=np.float32) | |
| dist_coeffs = np.array([0.0, 0.0, 0.0, 0.0, 0.0]) | |
| # Create a simple test image | |
| test_image = np.zeros((1200, 1920, 3), dtype=np.uint8) | |
| cv2.putText(test_image, "Sample Camera Image", (150, 240), | |
| cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) | |
| cv2.imwrite("sample_image.jpg", test_image) | |
| return camera_matrix, dist_coeffs | |
| def main(): | |
| parser = argparse.ArgumentParser(description='LiDAR-Camera Calibration Tool') | |
| parser.add_argument('--pcd', type=str, help='Path to point cloud file (.pcd, .ply, .bin, etc.)') | |
| parser.add_argument('--image', type=str, help='Path to camera image') | |
| parser.add_argument('--demo', action='store_true', help='Run with sample data') | |
| args = parser.parse_args() | |
| if args.demo or (not args.pcd or not args.image): | |
| print("Creating sample data for demonstration...") | |
| camera_matrix, dist_coeffs = create_sample_data() | |
| pcd_path = "sample_pointcloud.bin" # Use .bin file for demo | |
| img_path = "sample_image.jpg" | |
| else: | |
| pcd_path = args.pcd | |
| img_path = args.image | |
| # You'll need to provide your camera calibration parameters here | |
| focal_length_mm = 12.0 # Your focal length in mm | |
| principal_point_x_pixels = 960 # Principal point X in pixels | |
| principal_point_y_pixels = 600 # Principal point Y in pixels | |
| pixels_per_mm_x = 170.648 # Your pixels per mm in X | |
| pixels_per_mm_y = 170.648 # Your pixels per mm in Y | |
| # Create camera matrix | |
| camera_matrix = create_camera_matrix( | |
| focal_length_mm, principal_point_x_pixels, principal_point_y_pixels, | |
| pixels_per_mm_x, pixels_per_mm_y | |
| ) | |
| # camera_matrix = np.array([ | |
| # [800, 0, 320], | |
| # [0, 800, 240], | |
| # [0, 0, 1] | |
| # ], dtype=np.float32) | |
| # Sample distortion coefficients | |
| # dist_coeffs = np.array([0.1, -0.2, 0, 0, 0.1], dtype=np.float32) | |
| dist_coeffs = np.array([0.0, 0.0, 0.0, 0.0, 0.0]) | |
| # Check if files exist | |
| if not os.path.exists(pcd_path): | |
| print(f"Error: Point cloud file {pcd_path} not found") | |
| return | |
| if not os.path.exists(img_path): | |
| print(f"Error: Image file {img_path} not found") | |
| return | |
| # Initialize calibrator | |
| calibrator = LiDARCameraCalibrator(pcd_path, img_path, camera_matrix, dist_coeffs) | |
| # Set initial transformation values (modify these as needed) | |
| # calibrator.set_initial_values(tx=1.932, ty=0, tz=1.4, rx=0, ry=-2.0, rz=1.5) | |
| # calibrator.set_initial_values(tx=1.932, ty=0.25, tz=1.4, rx=0.0, ry=-2.0, rz=1.5) | |
| calibrator.set_initial_values(tx=-1.256, ty=-0.1, tz=0.5, rx=0.0, ry=2.2, rz=-0.55) | |
| # calibrator.set_initial_values(tx=-1.16, ty=-0.23, tz=0.42, rx=0.0, ry=2.0, rz=-0.55) | |
| # calibrator.set_initial_values(tx=1.932, ty=0.25, tz=1.486, rx=0, ry=-2.0, rz=1.5) | |
| # calibrator.set_initial_values(tx=0.23, ty=-0.42, tz=-1.16, rx=-2.5, ry=0.46, rz=-0.7) | |
| # calibrator.set_initial_values(tx=0.192, ty=-0.641, tz=-1.474, rx=-2.51, ry=0.46, rz=-0.69) | |
| # Run the calibration tool | |
| calibrator.run() | |
| if __name__ == "__main__": | |
| main( |