Note
Go to the end to download the full example code.
Blender: Stereo Camera Calibration & Parameters¶
This tutorial demonstrates how to define a stereo camera system, export its intrinsic and extrinsic calibration parameters in standard formats (YAML and MatchID), and reload calibration data.
Stereo calibration defines: - Intrinsic parameters: focal length, pixel size, sensor grid, principal point. - Extrinsic parameters: 3D rotation and translation vector between cameras.
Workflow: 1. Define a base perspective camera and construct a convergent stereo system. 2. Export the ground truth stereo calibration to YAML and MatchID formats. 3. Reload calibration data to verify parameter fidelity. 4. Calculate calibration image counts for physical target pose sweeps.
1. Define base camera and build convergent stereo system¶
cam_base = render.Camera(
pixels_num=np.array((1540, 1040)),
pixels_size=np.array((0.00345, 0.00345)),
pos_world=np.array((0.0, 0.0, 400.0)),
rot_world=Rotation.identity(),
roi_cent_world=np.zeros(3),
focal_length=15.0,
)
stereo_angle = 15.0 # degrees
stereo_cameras = render.stereo_build_faceon(cam_base, stereo_angle)
cam_0, cam_1 = stereo_cameras
print(
"Stereo baseline distance (mm):",
render.stereo_calc_baseline(cam_0, cam_1),
)
print("Stereo rotation (Euler xyz deg):")
print(render.stereo_calc_angles(cam_0, cam_1).relative_euler_xyz_degrees)
2. Export calibration in YAML and MatchID formats¶
output_dir = (
Path.cwd() / "pyvale-output"
/ "render3d_ex2d_blender_calibration"
)
output_dir.mkdir(parents=True, exist_ok=True)
# Save standard Pyvale YAML calibration:
yaml_path = output_dir / "calibration" / "calibration.yaml"
render.stereo_save_calibration_yaml(cam_0, cam_1, yaml_path)
print(f"Saved Pyvale calibration to: {yaml_path}")
# Save MatchID compatible calibration:
mid_path = output_dir / "calibration" / "calibration.caldat"
render.stereo_save_calibration_matchid(cam_0, cam_1, mid_path)
print(f"Saved MatchID calibration to: {mid_path}")
3. Reload and inspect calibration parameters¶
reloaded_0, reloaded_1 = render.stereo_build_from_calibration(
calibration_path=yaml_path,
pos_world_0=cam_base.pos_world,
rot_world_0=cam_base.rot_world,
focal_length=cam_base.focal_length,
)
ext_reloaded = render.stereo_calc_extrinsics(reloaded_0, reloaded_1)
ext_original = render.stereo_calc_extrinsics(cam_0, cam_1)
assert np.allclose(
ext_reloaded.translation_cam1_in_cam0,
ext_original.translation_cam1_in_cam0,
)
print("Successfully verified round-trip calibration parameters.")
4. Plan calibration target pose sweeps¶
For experimental DIC calibration, targets are translated and rotated through the field of view. We can compute the total pose count:
cal_data = render.BlenderCalibrationData(
angle_lims=(-10.0, 10.0),
angle_step=5.0,
plunge_lims=(-5.0, 5.0),
plunge_step=5.0,
)
total_images = render.calibration_image_count(cal_data)
print(f"Standard calibration sweep image count: {total_images}")
The generated Pyvale calibration file is embedded below.
Cam0_Cx [pixels]: 770.0
Cam0_Cy [pixels]: 520.0
Cam0_Fs [pixels]: 0.0
Cam0_Fx [pixels]: 4347.826086956522
Cam0_Fy [pixels]: 4347.826086956522
Cam0_Kappa 1: 0.0
Cam0_Kappa 2: 0.0
Cam0_Kappa 3: 0.0
Cam0_P1: 0.0
Cam0_P2: 0.0
Cam1_Cx [pixels]: 770.0
Cam1_Cy [pixels]: 520.0
Cam1_Fs [pixels]: 0.0
Cam1_Fx [pixels]: 4347.826086956522
Cam1_Fy [pixels]: 4347.826086956522
Cam1_Kappa 1: 0.0
Cam1_Kappa 2: 0.0
Cam1_Kappa 3: 0.0
Cam1_P1: 0.0
Cam1_P2: 0.0
Phi [deg]: -14.999999999999996
Psi [deg]: 0.0
Theta [deg]: 0.0
Tx [mm]: -103.52761804100828
Ty [mm]: 0.0
Tz [mm]: -27.7401416484059