openlpt 2.2.2__tar.gz → 2.2.3__tar.gz
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.
- {openlpt-2.2.2 → openlpt-2.2.3}/PKG-INFO +1 -1
- {openlpt-2.2.2 → openlpt-2.2.3}/_version.py +1 -1
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/views/tracking_settings_view.py +49 -16
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/view.py +231 -30
- {openlpt-2.2.2 → openlpt-2.2.3}/openlpt.egg-info/PKG-INFO +1 -1
- {openlpt-2.2.2 → openlpt-2.2.3}/openlpt.egg-info/SOURCES.txt +1 -0
- openlpt-2.2.3/test/test_plate_error_stats.py +60 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/LICENSE +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/README.md +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/__init__.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/app.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/assets/icon.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/create_shortcut.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/calibration/axis_alignment_guide.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/calibration/plate_calibration.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/calibration/plate_guide.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/calibration/plate_point_detection.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/calibration/wand_calibration_ui_v2.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/calibration/wand_detection_ui.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/calibration/wand_guide.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/index.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/preprocessing.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/results.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/settings.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/docs/tracking.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/main.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/style.qss +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/test_pyqt.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/utils/__init__.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/utils/auto_updater.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/utils/update_checker.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/views/__init__.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/views/camera_calibration_view.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/views/image_preprocessing_view.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/views/results_view.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/views/tracking_view.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/gui/widgets/__init__.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/__init__.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/__init__.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/image_utils.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/plate_calibration/PLATE_CALIBRATION_USER_GUIDE.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/plate_calibration/grid_detector.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/plate_calibration/plate_calibration.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/plate_calibration/plate_point_detection.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/plate_calibration/refraction_plate_calibration.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/plate_calibration/refraction_settings_panel.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/WAND_CALIBRATION_USER_GUIDE.html +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/axis_calibrator_endpoint_sizes.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/full_global_search.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/plane_d_solver.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/pretest_global_search.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/refraction_calibration_BA.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/refraction_settings_panel.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/refraction_wand_calibrator.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/refractive_bootstrap.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/refractive_geometry.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/run_full_global_search.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/wand_calibration_ui.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/wand_calibration_ui_v2.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/wand_calibrator.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/wand_calibrator_target.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/wand_detection_ui.png +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/widgets.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/image_preprocessing/__init__.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/image_preprocessing/cli.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/image_preprocessing/core.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/image_preprocessing/io.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/image_preprocessing/runner.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/image_preprocessing/view.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/image_preprocessing/widgets.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/post_processing/__init__.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/post_processing/processor.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/vsc/__init__.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/vsc/camera_io.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/vsc/optimizer.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/vsc/refraction_optimizer.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/modules/vsc/vsc_service.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/openlpt.egg-info/dependency_links.txt +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/openlpt.egg-info/entry_points.txt +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/openlpt.egg-info/requires.txt +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/openlpt.egg-info/top_level.txt +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/openlpt.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/pyproject.toml +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/setup.cfg +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/setup.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_axis_alignment.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_axis_alignment_baseline.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_axis_alignment_transpose.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_image_preprocessing_cli.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_image_preprocessing_core.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_image_preprocessing_io.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_p0_bootstrap_failure.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_p1_round4_radius_frame_mismatch.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_plane_d_solver.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_plane_pipeline.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_precalibrate_3d_view.py +0 -0
- {openlpt-2.2.2 → openlpt-2.2.3}/tests/test_utils_axis_alignment.py +0 -0
|
@@ -1519,7 +1519,7 @@ class TrackingSettingsView(QWidget):
|
|
|
1519
1519
|
return pts_common.min(axis=0), pts_common.max(axis=0)
|
|
1520
1520
|
|
|
1521
1521
|
def _validate_settings(self):
|
|
1522
|
-
"""Validate current settings by running 2D detection and 3D matching on the
|
|
1522
|
+
"""Validate current settings by running 2D detection and 3D matching on the configured start frame."""
|
|
1523
1523
|
from PySide6.QtWidgets import QProgressDialog, QApplication
|
|
1524
1524
|
from PySide6.QtCore import Qt
|
|
1525
1525
|
self._busy_begin('validate_settings', 'Validating tracking settings')
|
|
@@ -1582,7 +1582,7 @@ class TrackingSettingsView(QWidget):
|
|
|
1582
1582
|
progress.setLabelText("Loading Images...")
|
|
1583
1583
|
QApplication.processEvents()
|
|
1584
1584
|
|
|
1585
|
-
# Load images for the
|
|
1585
|
+
# Load images for the configured start frame.
|
|
1586
1586
|
imgio_list = []
|
|
1587
1587
|
folder_base = os.path.abspath(project_dir).replace('\\', '/') + '/'
|
|
1588
1588
|
for path in basic_settings._image_file_paths:
|
|
@@ -1591,35 +1591,60 @@ class TrackingSettingsView(QWidget):
|
|
|
1591
1591
|
imgio_list.append(io)
|
|
1592
1592
|
|
|
1593
1593
|
num_cams = len(imgio_list)
|
|
1594
|
-
frame_id = 0
|
|
1594
|
+
frame_id = int(getattr(basic_settings, '_frame_start', 0))
|
|
1595
1595
|
image_list = []
|
|
1596
|
+
progress.setLabelText(f"Loading Images (frame {frame_id})...")
|
|
1597
|
+
QApplication.processEvents()
|
|
1596
1598
|
for i in range(num_cams):
|
|
1597
1599
|
image_list.append(imgio_list[i].loadImg(frame_id))
|
|
1598
1600
|
|
|
1599
|
-
progress.setLabelText("Detecting 2D Objects...")
|
|
1601
|
+
progress.setLabelText(f"Detecting 2D Objects (frame {frame_id})...")
|
|
1600
1602
|
QApplication.processEvents()
|
|
1601
1603
|
|
|
1602
1604
|
# Detect 2D objects
|
|
1603
1605
|
obj_finder = lpt.ObjectFinder2D()
|
|
1604
1606
|
obj2d_list = []
|
|
1605
1607
|
total_2d_count = 0
|
|
1608
|
+
per_camera_2d_counts = []
|
|
1606
1609
|
for cam_id in range(num_cams):
|
|
1607
1610
|
obj2ds = obj_finder.findObject2D(image_list[cam_id], obj_cfg)
|
|
1608
1611
|
obj2d_list.append(obj2ds)
|
|
1609
1612
|
count = len(obj2ds)
|
|
1613
|
+
per_camera_2d_counts.append(count)
|
|
1610
1614
|
total_2d_count += count
|
|
1611
|
-
print(f"[Validation] Camera {cam_id}: found {count} 2D objects.")
|
|
1612
|
-
|
|
1615
|
+
print(f"[Validation] Frame {frame_id}, Camera {cam_id}: found {count} 2D objects.")
|
|
1616
|
+
|
|
1613
1617
|
avg_2d_count = total_2d_count / num_cams if num_cams > 0 else 0
|
|
1614
|
-
|
|
1615
|
-
|
|
1618
|
+
camera_count_summary = ", ".join(
|
|
1619
|
+
f"Cam {cam_id}: {count}" for cam_id, count in enumerate(per_camera_2d_counts)
|
|
1620
|
+
) or "No cameras"
|
|
1621
|
+
zero_camera_ids = [cam_id for cam_id, count in enumerate(per_camera_2d_counts) if count == 0]
|
|
1622
|
+
zero_camera_warning = ""
|
|
1623
|
+
if zero_camera_ids:
|
|
1624
|
+
zero_camera_warning = (
|
|
1625
|
+
"\n\nWarning: no 2D objects were detected in camera(s): "
|
|
1626
|
+
+ ", ".join(str(cam_id) for cam_id in zero_camera_ids)
|
|
1627
|
+
+ "."
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
if total_2d_count == 0:
|
|
1631
|
+
progress.close()
|
|
1632
|
+
error_msg = f"Validation failed for frame {frame_id}.\n\n" \
|
|
1633
|
+
"No 2D objects were detected in any camera, so 3D matching cannot proceed.\n\n" \
|
|
1634
|
+
f"Per-camera 2D counts: {camera_count_summary}\n\n" \
|
|
1635
|
+
"Check image paths, frame range, object detection thresholds, and image preprocessing settings."
|
|
1636
|
+
print(f"[Validation] Frame {frame_id}: no 2D objects detected in any camera.")
|
|
1637
|
+
QMessageBox.warning(self, "Validation Failed", error_msg)
|
|
1638
|
+
return
|
|
1639
|
+
|
|
1640
|
+
progress.setLabelText(f"Matching 3D Objects (frame {frame_id}, 2D Avg: {avg_2d_count:.1f})...")
|
|
1616
1641
|
QApplication.processEvents()
|
|
1617
1642
|
|
|
1618
1643
|
# Initial 3D match
|
|
1619
1644
|
stereomath = lpt.StereoMatch(camera_models, obj2d_list, obj_cfg)
|
|
1620
1645
|
obj3d_list = stereomath.match()
|
|
1621
1646
|
count_3d = len(obj3d_list)
|
|
1622
|
-
print(f"[Validation] Initial Match: found {count_3d} 3D objects.")
|
|
1647
|
+
print(f"[Validation] Frame {frame_id} Initial Match: found {count_3d} 3D objects.")
|
|
1623
1648
|
|
|
1624
1649
|
# Step 1: Iterative 2D tolerance increase if 3D count is too low (< 25% of avg 2D)
|
|
1625
1650
|
orig_tol_2d = obj_cfg._sm_param.tol_2d_px
|
|
@@ -1632,7 +1657,7 @@ class TrackingSettingsView(QWidget):
|
|
|
1632
1657
|
current_tol_2d += tol_2d_step
|
|
1633
1658
|
obj_cfg._sm_param.tol_2d_px = current_tol_2d
|
|
1634
1659
|
|
|
1635
|
-
progress.setLabelText(f"Stage 1 (2D Tol): Matching 3D (tol={current_tol_2d:.2f})...")
|
|
1660
|
+
progress.setLabelText(f"Stage 1 (2D Tol): Matching 3D frame {frame_id} (tol={current_tol_2d:.2f})...")
|
|
1636
1661
|
if progress.wasCanceled(): break
|
|
1637
1662
|
QApplication.processEvents()
|
|
1638
1663
|
|
|
@@ -1641,7 +1666,7 @@ class TrackingSettingsView(QWidget):
|
|
|
1641
1666
|
obj3d_list = stereomath.match()
|
|
1642
1667
|
count_3d = len(obj3d_list)
|
|
1643
1668
|
modified_2d = True
|
|
1644
|
-
print(f"[Validation] Retry Match (2D tol={current_tol_2d:.2f}): found {count_3d} 3D objects.")
|
|
1669
|
+
print(f"[Validation] Frame {frame_id} Retry Match (2D tol={current_tol_2d:.2f}): found {count_3d} 3D objects.")
|
|
1645
1670
|
|
|
1646
1671
|
# Step 2: Iterative 3D tolerance increase if still insufficient
|
|
1647
1672
|
orig_tol_3d_mm = obj_cfg._sm_param.tol_3d_mm
|
|
@@ -1654,7 +1679,7 @@ class TrackingSettingsView(QWidget):
|
|
|
1654
1679
|
current_tol_3d_mm += tol_3d_step_mm
|
|
1655
1680
|
obj_cfg._sm_param.tol_3d_mm = current_tol_3d_mm
|
|
1656
1681
|
|
|
1657
|
-
progress.setLabelText(f"Stage 2 (3D Tol): Matching 3D (tol={current_tol_3d_mm:.2f}mm)...")
|
|
1682
|
+
progress.setLabelText(f"Stage 2 (3D Tol): Matching 3D frame {frame_id} (tol={current_tol_3d_mm:.2f}mm)...")
|
|
1658
1683
|
if progress.wasCanceled(): break
|
|
1659
1684
|
QApplication.processEvents()
|
|
1660
1685
|
|
|
@@ -1663,16 +1688,18 @@ class TrackingSettingsView(QWidget):
|
|
|
1663
1688
|
obj3d_list = stereomath.match()
|
|
1664
1689
|
count_3d = len(obj3d_list)
|
|
1665
1690
|
modified_3d = True
|
|
1666
|
-
print(f"[Validation] Retry Match (3D tol={current_tol_3d_mm:.2f}mm): found {count_3d} 3D objects.")
|
|
1691
|
+
print(f"[Validation] Frame {frame_id} Retry Match (3D tol={current_tol_3d_mm:.2f}mm): found {count_3d} 3D objects.")
|
|
1667
1692
|
|
|
1668
1693
|
progress.close()
|
|
1669
1694
|
|
|
1670
1695
|
# Check final result
|
|
1671
1696
|
if count_3d < (avg_2d_count / 4.0):
|
|
1672
|
-
error_msg = f"Validation failed.\n\n" \
|
|
1697
|
+
error_msg = f"Validation failed for frame {frame_id}.\n\n" \
|
|
1673
1698
|
f"Even with 2D tolerance increased by {current_tol_2d - orig_tol_2d:.1f}px " \
|
|
1674
1699
|
f"and 3D tolerance increased by {current_tol_3d_mm - orig_tol_3d_mm:.1f}mm, " \
|
|
1675
1700
|
f"only {count_3d} 3D objects were reconstructed from ~{avg_2d_count:.1f} 2D objects.\n\n" \
|
|
1701
|
+
f"Per-camera 2D counts: {camera_count_summary}" \
|
|
1702
|
+
f"{zero_camera_warning}\n\n" \
|
|
1676
1703
|
"The current camera parameters may be inaccurate or invalid for tracking."
|
|
1677
1704
|
QMessageBox.warning(self, "Validation Failed", error_msg)
|
|
1678
1705
|
else:
|
|
@@ -1694,13 +1721,19 @@ class TrackingSettingsView(QWidget):
|
|
|
1694
1721
|
if modified_3d: adjust_info.append(f"3D tolerance -> {current_tol_3d_mm:.2f}mm")
|
|
1695
1722
|
|
|
1696
1723
|
msg = f"Validation successful with adjustment!\n\n" \
|
|
1724
|
+
f"Validated Frame: {frame_id}\n" \
|
|
1697
1725
|
f"Adjustments: {', '.join(adjust_info)}\n" \
|
|
1698
1726
|
f"3D Objects: {count_3d}\n" \
|
|
1699
|
-
f"Average 2D Objects: {avg_2d_count:.1f}"
|
|
1727
|
+
f"Average 2D Objects: {avg_2d_count:.1f}\n" \
|
|
1728
|
+
f"Per-camera 2D counts: {camera_count_summary}" \
|
|
1729
|
+
f"{zero_camera_warning}"
|
|
1700
1730
|
else:
|
|
1701
1731
|
msg = f"Validation Successful!\n\n" \
|
|
1732
|
+
f"Validated Frame: {frame_id}\n" \
|
|
1702
1733
|
f"3D Objects: {count_3d}\n" \
|
|
1703
|
-
f"Average 2D Objects: {avg_2d_count:.1f}"
|
|
1734
|
+
f"Average 2D Objects: {avg_2d_count:.1f}\n" \
|
|
1735
|
+
f"Per-camera 2D counts: {camera_count_summary}" \
|
|
1736
|
+
f"{zero_camera_warning}"
|
|
1704
1737
|
|
|
1705
1738
|
QMessageBox.information(self, "Validation Result", msg)
|
|
1706
1739
|
|
|
@@ -26,6 +26,144 @@ from PySide6.QtCore import QRect, Signal, QPoint, QThread, QTimer, Slot, QObject
|
|
|
26
26
|
|
|
27
27
|
from .image_utils import load_image_any_depth, to_gray_uint8, to_rgb_uint8
|
|
28
28
|
|
|
29
|
+
|
|
30
|
+
def format_calibration_error_stats(stats):
|
|
31
|
+
"""Format camFile error stats as 'mean,std' or 'None'."""
|
|
32
|
+
if stats is None:
|
|
33
|
+
return "None"
|
|
34
|
+
try:
|
|
35
|
+
mean, std = stats
|
|
36
|
+
if mean is None or std is None:
|
|
37
|
+
return "None"
|
|
38
|
+
mean = float(mean)
|
|
39
|
+
std = float(std)
|
|
40
|
+
if not np.isfinite(mean) or not np.isfinite(std):
|
|
41
|
+
return "None"
|
|
42
|
+
return f"{mean:.8g},{std:.8g}"
|
|
43
|
+
except Exception:
|
|
44
|
+
return "None"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def compute_projection_error_stats(world_points, image_points, rvec, tvec, K, dist):
|
|
48
|
+
"""Return mean/std Euclidean reprojection error in pixels for pinhole data."""
|
|
49
|
+
world = np.asarray(world_points, dtype=np.float64).reshape(-1, 3)
|
|
50
|
+
image = np.asarray(image_points, dtype=np.float64).reshape(-1, 2)
|
|
51
|
+
if world.size == 0 or len(world) != len(image):
|
|
52
|
+
return None
|
|
53
|
+
proj_pts, _ = cv2.projectPoints(world, rvec, tvec, np.asarray(K, dtype=np.float64), np.asarray(dist, dtype=np.float64))
|
|
54
|
+
errors = np.linalg.norm(proj_pts.reshape(-1, 2) - image, axis=1)
|
|
55
|
+
if errors.size == 0:
|
|
56
|
+
return None
|
|
57
|
+
return float(np.mean(errors)), float(np.std(errors))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _camera_projection_matrix_normalized(params):
|
|
61
|
+
"""Return [R|T] for normalized undistorted pinhole observations."""
|
|
62
|
+
R = np.asarray(params.get('R'), dtype=np.float64).reshape(3, 3)
|
|
63
|
+
T = params.get('T', params.get('tvec'))
|
|
64
|
+
T = np.asarray(T, dtype=np.float64).reshape(3, 1)
|
|
65
|
+
return np.hstack([R, T])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _undistort_to_normalized_point(uv, params):
|
|
69
|
+
K = np.asarray(params.get('K'), dtype=np.float64).reshape(3, 3)
|
|
70
|
+
dist = np.asarray(params.get('dist', np.zeros(5)), dtype=np.float64).reshape(-1, 1)
|
|
71
|
+
pts = np.asarray(uv, dtype=np.float64).reshape(1, 1, 2)
|
|
72
|
+
norm = cv2.undistortPoints(pts, K, dist)
|
|
73
|
+
return norm.reshape(2)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _linear_triangulate_normalized(obs_by_cam, all_camera_params):
|
|
77
|
+
"""DLT triangulation from normalized camera observations."""
|
|
78
|
+
rows = []
|
|
79
|
+
for cam_idx, uv in sorted(obs_by_cam.items()):
|
|
80
|
+
params = all_camera_params.get(cam_idx)
|
|
81
|
+
if params is None:
|
|
82
|
+
continue
|
|
83
|
+
try:
|
|
84
|
+
x, y = _undistort_to_normalized_point(uv, params)
|
|
85
|
+
P = _camera_projection_matrix_normalized(params)
|
|
86
|
+
except Exception:
|
|
87
|
+
continue
|
|
88
|
+
rows.append(x * P[2, :] - P[0, :])
|
|
89
|
+
rows.append(y * P[2, :] - P[1, :])
|
|
90
|
+
if len(rows) < 4:
|
|
91
|
+
return None
|
|
92
|
+
A = np.asarray(rows, dtype=np.float64)
|
|
93
|
+
try:
|
|
94
|
+
_, _, vt = np.linalg.svd(A)
|
|
95
|
+
X_h = vt[-1, :]
|
|
96
|
+
except np.linalg.LinAlgError:
|
|
97
|
+
return None
|
|
98
|
+
if abs(X_h[3]) < 1e-12:
|
|
99
|
+
return None
|
|
100
|
+
X = X_h[:3] / X_h[3]
|
|
101
|
+
if not np.all(np.isfinite(X)):
|
|
102
|
+
return None
|
|
103
|
+
return X
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def compute_plate_triangulation_error_stats(all_camera_params, saved_calibration_data):
|
|
107
|
+
"""
|
|
108
|
+
Compute mean/std 3D reconstruction error in mm for shared plate points.
|
|
109
|
+
|
|
110
|
+
Points are grouped by rounded global/world 3D coordinate. Each grouped point
|
|
111
|
+
must have observations from at least two calibrated pinhole cameras; otherwise
|
|
112
|
+
triangulation is unavailable and None is returned.
|
|
113
|
+
"""
|
|
114
|
+
if not all_camera_params or len(all_camera_params) < 2 or not saved_calibration_data:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
valid_cam_params = {}
|
|
118
|
+
for cam_idx, params in all_camera_params.items():
|
|
119
|
+
try:
|
|
120
|
+
_camera_projection_matrix_normalized(params)
|
|
121
|
+
np.asarray(params.get('K'), dtype=np.float64).reshape(3, 3)
|
|
122
|
+
valid_cam_params[int(cam_idx)] = params
|
|
123
|
+
except Exception:
|
|
124
|
+
continue
|
|
125
|
+
if len(valid_cam_params) < 2:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
grouped = {}
|
|
129
|
+
for (cid, _img_path), data in saved_calibration_data.items():
|
|
130
|
+
cid = int(cid)
|
|
131
|
+
if cid not in valid_cam_params:
|
|
132
|
+
continue
|
|
133
|
+
keypoints = data.get('keypoints', [])
|
|
134
|
+
worlds = data.get('world_coords', [])
|
|
135
|
+
for kp, world in zip(keypoints, worlds):
|
|
136
|
+
if world is None or kp is None:
|
|
137
|
+
continue
|
|
138
|
+
world_arr = np.asarray(world, dtype=np.float64).reshape(3)
|
|
139
|
+
if not np.all(np.isfinite(world_arr)):
|
|
140
|
+
continue
|
|
141
|
+
uv = np.asarray(kp.pt, dtype=np.float64).reshape(2)
|
|
142
|
+
if not np.all(np.isfinite(uv)):
|
|
143
|
+
continue
|
|
144
|
+
key = tuple(round(float(v), 6) for v in world_arr)
|
|
145
|
+
entry = grouped.setdefault(key, {'world': world_arr, 'uvs_by_cam': {}})
|
|
146
|
+
entry['uvs_by_cam'].setdefault(cid, []).append(uv)
|
|
147
|
+
|
|
148
|
+
errors = []
|
|
149
|
+
for entry in grouped.values():
|
|
150
|
+
obs_by_cam = {
|
|
151
|
+
cid: np.mean(np.asarray(uvs, dtype=np.float64), axis=0)
|
|
152
|
+
for cid, uvs in entry['uvs_by_cam'].items()
|
|
153
|
+
if len(uvs) > 0
|
|
154
|
+
}
|
|
155
|
+
if len(obs_by_cam) < 2:
|
|
156
|
+
continue
|
|
157
|
+
X = _linear_triangulate_normalized(obs_by_cam, valid_cam_params)
|
|
158
|
+
if X is None:
|
|
159
|
+
continue
|
|
160
|
+
errors.append(float(np.linalg.norm(X - entry['world'])))
|
|
161
|
+
|
|
162
|
+
if not errors:
|
|
163
|
+
return None
|
|
164
|
+
errors = np.asarray(errors, dtype=np.float64)
|
|
165
|
+
return float(np.mean(errors)), float(np.std(errors))
|
|
166
|
+
|
|
29
167
|
class ZoomableImageLabel(QLabel):
|
|
30
168
|
"""
|
|
31
169
|
Label with zoom, pan, and multiple interaction modes.
|
|
@@ -4119,11 +4257,14 @@ class CameraCalibrationView(QWidget):
|
|
|
4119
4257
|
target_cam_idx = self.cal_target_cam_combo.currentIndex()
|
|
4120
4258
|
print(f"Running Plate Calibration for Camera {target_cam_idx+1}...")
|
|
4121
4259
|
|
|
4122
|
-
# 1. Gather all
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4260
|
+
# 1. Gather all global 3D-2D correspondences for this camera.
|
|
4261
|
+
# OpenLPT_calPoints.csv plate points are already expressed in one
|
|
4262
|
+
# global coordinate system, so they must be calibrated as one OpenCV
|
|
4263
|
+
# view instead of one view per image/path.
|
|
4264
|
+
all_kpts_list = []
|
|
4265
|
+
all_world_list = []
|
|
4266
|
+
|
|
4267
|
+
# Data entries that have observations for this camera.
|
|
4127
4268
|
relevant_keys = [k for k in self.saved_calibration_data.keys() if k[0] == target_cam_idx]
|
|
4128
4269
|
|
|
4129
4270
|
if not relevant_keys:
|
|
@@ -4143,14 +4284,31 @@ class CameraCalibrationView(QWidget):
|
|
|
4143
4284
|
world = np.array([wc for _, wc in valid_pairs], dtype=np.float32)
|
|
4144
4285
|
|
|
4145
4286
|
if len(kpts) > 0:
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
if not
|
|
4287
|
+
all_kpts_list.append(kpts)
|
|
4288
|
+
all_world_list.append(world)
|
|
4289
|
+
|
|
4290
|
+
if not all_kpts_list:
|
|
4150
4291
|
QMessageBox.warning(self, "No Data", "No valid point sets found.")
|
|
4151
4292
|
self._busy_end('plate_calibration')
|
|
4152
4293
|
return
|
|
4153
4294
|
|
|
4295
|
+
all_kpts = np.vstack(all_kpts_list).astype(np.float32)
|
|
4296
|
+
all_world = np.vstack(all_world_list).astype(np.float32)
|
|
4297
|
+
print(
|
|
4298
|
+
f"[PlateCalib] Camera {target_cam_idx+1}: using "
|
|
4299
|
+
f"{len(all_world)} global 3D-2D correspondences from "
|
|
4300
|
+
f"{len(relevant_keys)} data entries as a single OpenCV view."
|
|
4301
|
+
)
|
|
4302
|
+
|
|
4303
|
+
if len(all_world) < 6:
|
|
4304
|
+
QMessageBox.warning(
|
|
4305
|
+
self,
|
|
4306
|
+
"Insufficient Data",
|
|
4307
|
+
"At least 6 valid 3D-2D point correspondences are required for plate calibration."
|
|
4308
|
+
)
|
|
4309
|
+
self._busy_end('plate_calibration')
|
|
4310
|
+
return
|
|
4311
|
+
|
|
4154
4312
|
# Image size
|
|
4155
4313
|
w = self.cal_img_width.value()
|
|
4156
4314
|
h = self.cal_img_height.value()
|
|
@@ -4172,18 +4330,21 @@ class CameraCalibrationView(QWidget):
|
|
|
4172
4330
|
dist_num = self.cal_dist_model_combo.currentIndex() if hasattr(self, 'cal_dist_model_combo') else 0
|
|
4173
4331
|
dist_coeffs = np.zeros(5, dtype=np.float32) # Always use 5 for standard pinhole logic
|
|
4174
4332
|
|
|
4175
|
-
# 3. Stage 1: Solve for Pose (Extrinsics) using
|
|
4176
|
-
|
|
4177
|
-
|
|
4333
|
+
# 3. Stage 1: Solve for Pose (Extrinsics) using all global points.
|
|
4334
|
+
try:
|
|
4335
|
+
success_pnp, rvec, tvec = cv2.solvePnP(all_world, all_kpts, K, dist_coeffs)
|
|
4336
|
+
except cv2.error as e:
|
|
4337
|
+
QMessageBox.warning(self, "PnP Failed", f"Initial pose estimation (PnP) error: {str(e)}")
|
|
4338
|
+
self._busy_end('plate_calibration')
|
|
4339
|
+
return
|
|
4178
4340
|
if not success_pnp:
|
|
4179
4341
|
QMessageBox.critical(self, "Error", "Initial pose estimation (PnP) failed.")
|
|
4180
4342
|
self._busy_end('plate_calibration')
|
|
4181
4343
|
return
|
|
4182
|
-
|
|
4344
|
+
|
|
4183
4345
|
# 4. Stage 2: Refine All (Intrinsics + Extrinsics)
|
|
4184
|
-
#
|
|
4185
|
-
|
|
4186
|
-
tvecs = [tvec.copy() for _ in range(len(img_points))]
|
|
4346
|
+
# Pass all global plate correspondences as one OpenCV view so the
|
|
4347
|
+
# returned rvecs_opt[0]/tvecs_opt[0] is the single global pose.
|
|
4187
4348
|
|
|
4188
4349
|
# Calibration Flags: Always use intrinsic guess
|
|
4189
4350
|
flags = cv2.CALIB_USE_INTRINSIC_GUESS
|
|
@@ -4200,16 +4361,28 @@ class CameraCalibrationView(QWidget):
|
|
|
4200
4361
|
|
|
4201
4362
|
try:
|
|
4202
4363
|
rms, K_opt, dist_opt, rvecs_opt, tvecs_opt = cv2.calibrateCamera(
|
|
4203
|
-
|
|
4364
|
+
[all_world], [all_kpts], img_size, K, dist_coeffs,
|
|
4204
4365
|
flags=flags
|
|
4205
4366
|
)
|
|
4206
4367
|
except Exception as e:
|
|
4207
4368
|
QMessageBox.critical(self, "Calibration Failed", f"Optimization error: {str(e)}")
|
|
4208
4369
|
self._busy_end('plate_calibration')
|
|
4209
4370
|
return
|
|
4210
|
-
|
|
4371
|
+
|
|
4372
|
+
proj_pts, _ = cv2.projectPoints(all_world, rvecs_opt[0], tvecs_opt[0], K_opt, dist_opt)
|
|
4373
|
+
proj_pts = proj_pts.reshape(-1, 2)
|
|
4374
|
+
residuals = proj_pts - all_kpts.reshape(-1, 2)
|
|
4375
|
+
point_proj_errors = np.linalg.norm(residuals, axis=1)
|
|
4376
|
+
global_rms = float(np.sqrt(np.mean(point_proj_errors * point_proj_errors)))
|
|
4377
|
+
proj_error_stats = (float(np.mean(point_proj_errors)), float(np.std(point_proj_errors)))
|
|
4378
|
+
print(
|
|
4379
|
+
f"[PlateCalib] Camera {target_cam_idx+1}: OpenCV RMS={float(rms):.6f} px, "
|
|
4380
|
+
f"final global reprojection RMS={global_rms:.6f} px, "
|
|
4381
|
+
f"proj mean/std={proj_error_stats[0]:.6f}/{proj_error_stats[1]:.6f} px"
|
|
4382
|
+
)
|
|
4383
|
+
|
|
4211
4384
|
# 5. Display Results
|
|
4212
|
-
self.lbl_cal_rms.setText(f"{
|
|
4385
|
+
self.lbl_cal_rms.setText(f"{global_rms:.4f} px")
|
|
4213
4386
|
|
|
4214
4387
|
# 6. Store Calibration Results
|
|
4215
4388
|
R_first, _ = cv2.Rodrigues(rvecs_opt[0])
|
|
@@ -4222,22 +4395,25 @@ class CameraCalibrationView(QWidget):
|
|
|
4222
4395
|
'K': K_opt,
|
|
4223
4396
|
'dist': dist_opt,
|
|
4224
4397
|
'img_size': (h, w),
|
|
4225
|
-
'rms':
|
|
4398
|
+
'rms': global_rms,
|
|
4399
|
+
# camFile Camera Calibration Error: per-point Euclidean reprojection
|
|
4400
|
+
# error mean/std in pixels for this camera's merged global plate data.
|
|
4401
|
+
'proj_error': proj_error_stats,
|
|
4402
|
+
'proj_mean': proj_error_stats[0],
|
|
4403
|
+
'proj_std': proj_error_stats[1],
|
|
4226
4404
|
}
|
|
4227
4405
|
|
|
4228
4406
|
# 7. Update 3D View with ALL calibrated cameras
|
|
4229
4407
|
# Reformat for plot_calibration (expects 1-based keys)
|
|
4230
4408
|
cam_viz_data = {idx + 1: params for idx, params in self.all_camera_params.items()}
|
|
4231
4409
|
|
|
4232
|
-
#
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
self.plate_3d_viewer.plot_calibration(cam_viz_data, all_3d)
|
|
4410
|
+
# Show the same merged global 3D points used for calibration.
|
|
4411
|
+
self.plate_3d_viewer.plot_calibration(cam_viz_data, all_world)
|
|
4236
4412
|
self.plate_vis_tabs.setCurrentWidget(self.plate_3d_viewer)
|
|
4237
4413
|
|
|
4238
4414
|
# 8. Notify user (no per-camera save prompt - use Save All button instead)
|
|
4239
4415
|
QMessageBox.information(self, "Calibration Complete",
|
|
4240
|
-
f"Camera {target_cam_idx+1} calibrated successfully.\nRMS: {
|
|
4416
|
+
f"Camera {target_cam_idx+1} calibrated successfully.\nRMS: {global_rms:.4f} px\n\nUse 'Save All Camera Parameters' to export.")
|
|
4241
4417
|
self._busy_end('plate_calibration')
|
|
4242
4418
|
|
|
4243
4419
|
def _collect_plate_refraction_inputs(self):
|
|
@@ -4543,15 +4719,21 @@ class CameraCalibrationView(QWidget):
|
|
|
4543
4719
|
"Text Files (*.txt)")
|
|
4544
4720
|
if not file_path:
|
|
4545
4721
|
return
|
|
4546
|
-
|
|
4722
|
+
|
|
4547
4723
|
try:
|
|
4724
|
+
params = getattr(self, 'all_camera_params', {}).get(cam_idx, {})
|
|
4725
|
+
proj_error = params.get('proj_error')
|
|
4726
|
+
if proj_error is None and 'proj_mean' in params and 'proj_std' in params:
|
|
4727
|
+
proj_error = (params.get('proj_mean'), params.get('proj_std'))
|
|
4728
|
+
tri_error = params.get('tri_error', params.get('triang_error'))
|
|
4729
|
+
|
|
4548
4730
|
with open(file_path, 'w') as f:
|
|
4549
4731
|
f.write("# Camera Model: (PINHOLE/POLYNOMIAL)\n")
|
|
4550
4732
|
f.write("PINHOLE\n")
|
|
4551
4733
|
f.write("# Camera Calibration Error: \n")
|
|
4552
|
-
f.write("
|
|
4734
|
+
f.write(f"{format_calibration_error_stats(proj_error)}\n")
|
|
4553
4735
|
f.write("# Pose Calibration Error: \n")
|
|
4554
|
-
f.write("
|
|
4736
|
+
f.write(f"{format_calibration_error_stats(tri_error)}\n")
|
|
4555
4737
|
f.write("# Image Size: (n_row,n_col)\n")
|
|
4556
4738
|
f.write(f"{img_size[1]},{img_size[0]}\n") # H, W
|
|
4557
4739
|
|
|
@@ -4689,6 +4871,21 @@ class CameraCalibrationView(QWidget):
|
|
|
4689
4871
|
cam_folder = Path(folder) / "camFile"
|
|
4690
4872
|
cam_folder.mkdir(parents=True, exist_ok=True)
|
|
4691
4873
|
|
|
4874
|
+
tri_error_stats = compute_plate_triangulation_error_stats(
|
|
4875
|
+
self.all_camera_params,
|
|
4876
|
+
getattr(self, 'saved_calibration_data', None),
|
|
4877
|
+
)
|
|
4878
|
+
if tri_error_stats is None:
|
|
4879
|
+
print(
|
|
4880
|
+
"[PlateCalib] Multi-camera triangulation error unavailable; "
|
|
4881
|
+
"need at least two calibrated pinhole cameras observing shared global plate points."
|
|
4882
|
+
)
|
|
4883
|
+
else:
|
|
4884
|
+
print(
|
|
4885
|
+
f"[PlateCalib] Pose triangulation error mean/std="
|
|
4886
|
+
f"{tri_error_stats[0]:.6f}/{tri_error_stats[1]:.6f} mm"
|
|
4887
|
+
)
|
|
4888
|
+
|
|
4692
4889
|
saved_count = 0
|
|
4693
4890
|
try:
|
|
4694
4891
|
for cam_idx, params in sorted(self.all_camera_params.items()):
|
|
@@ -4699,14 +4896,18 @@ class CameraCalibrationView(QWidget):
|
|
|
4699
4896
|
R = params['R']
|
|
4700
4897
|
T = params['T']
|
|
4701
4898
|
img_size = params['img_size']
|
|
4899
|
+
proj_error = params.get('proj_error')
|
|
4900
|
+
if proj_error is None and 'proj_mean' in params and 'proj_std' in params:
|
|
4901
|
+
proj_error = (params.get('proj_mean'), params.get('proj_std'))
|
|
4902
|
+
tri_error = params.get('tri_error', params.get('triang_error', tri_error_stats))
|
|
4702
4903
|
|
|
4703
4904
|
with open(file_path, 'w') as f:
|
|
4704
4905
|
f.write("# Camera Model: (PINHOLE/POLYNOMIAL)\n")
|
|
4705
4906
|
f.write("PINHOLE\n")
|
|
4706
4907
|
f.write("# Camera Calibration Error: \n")
|
|
4707
|
-
f.write("
|
|
4908
|
+
f.write(f"{format_calibration_error_stats(proj_error)}\n")
|
|
4708
4909
|
f.write("# Pose Calibration Error: \n")
|
|
4709
|
-
f.write("
|
|
4910
|
+
f.write(f"{format_calibration_error_stats(tri_error)}\n")
|
|
4710
4911
|
f.write("# Image Size: (n_row,n_col)\n")
|
|
4711
4912
|
f.write(f"{img_size[0]},{img_size[1]}\n") # H, W
|
|
4712
4913
|
|
|
@@ -80,6 +80,7 @@ openlpt.egg-info/dependency_links.txt
|
|
|
80
80
|
openlpt.egg-info/entry_points.txt
|
|
81
81
|
openlpt.egg-info/requires.txt
|
|
82
82
|
openlpt.egg-info/top_level.txt
|
|
83
|
+
test/test_plate_error_stats.py
|
|
83
84
|
tests/test_axis_alignment.py
|
|
84
85
|
tests/test_axis_alignment_baseline.py
|
|
85
86
|
tests/test_axis_alignment_transpose.py
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import cv2
|
|
3
|
+
|
|
4
|
+
from modules.camera_calibration.view import (
|
|
5
|
+
format_calibration_error_stats,
|
|
6
|
+
compute_plate_triangulation_error_stats,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _KP:
|
|
11
|
+
def __init__(self, x, y):
|
|
12
|
+
self.pt = (float(x), float(y))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _project(K, R, T, point):
|
|
16
|
+
rvec, _ = cv2.Rodrigues(np.asarray(R, dtype=np.float64))
|
|
17
|
+
uv, _ = cv2.projectPoints(
|
|
18
|
+
np.asarray([point], dtype=np.float64),
|
|
19
|
+
rvec,
|
|
20
|
+
np.asarray(T, dtype=np.float64).reshape(3, 1),
|
|
21
|
+
K,
|
|
22
|
+
np.zeros(5),
|
|
23
|
+
)
|
|
24
|
+
return uv.reshape(2)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_format_calibration_error_stats_writes_mean_std_or_none():
|
|
28
|
+
assert format_calibration_error_stats((1.25, 0.5)) == "1.25,0.5"
|
|
29
|
+
assert format_calibration_error_stats(None) == "None"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_compute_plate_triangulation_error_stats_uses_shared_world_points():
|
|
33
|
+
K = np.array([[800.0, 0.0, 320.0], [0.0, 800.0, 240.0], [0.0, 0.0, 1.0]])
|
|
34
|
+
R0 = np.eye(3)
|
|
35
|
+
T0 = np.zeros((3, 1))
|
|
36
|
+
R1 = np.eye(3)
|
|
37
|
+
T1 = np.array([[-100.0], [0.0], [0.0]])
|
|
38
|
+
points = [np.array([0.0, 0.0, 1000.0]), np.array([50.0, 20.0, 1100.0])]
|
|
39
|
+
saved = {}
|
|
40
|
+
for cid, R, T in [(0, R0, T0), (1, R1, T1)]:
|
|
41
|
+
saved[(cid, "frame.csv")] = {
|
|
42
|
+
"world_coords": points,
|
|
43
|
+
"keypoints": [_KP(*_project(K, R, T, p)) for p in points],
|
|
44
|
+
}
|
|
45
|
+
cams = {
|
|
46
|
+
0: {"K": K, "dist": np.zeros(5), "R": R0, "T": T0},
|
|
47
|
+
1: {"K": K, "dist": np.zeros(5), "R": R1, "T": T1},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
stats = compute_plate_triangulation_error_stats(cams, saved)
|
|
51
|
+
|
|
52
|
+
assert stats is not None
|
|
53
|
+
mean, std = stats
|
|
54
|
+
assert mean < 1e-6
|
|
55
|
+
assert std < 1e-6
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_compute_plate_triangulation_error_stats_returns_none_for_single_camera():
|
|
59
|
+
stats = compute_plate_triangulation_error_stats({0: {}}, {})
|
|
60
|
+
assert stats is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/plate_calibration/grid_detector.py
RENAMED
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/plate_calibration/plate_calibration.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/full_global_search.py
RENAMED
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/plane_d_solver.py
RENAMED
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/pretest_global_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/refractive_bootstrap.py
RENAMED
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/refractive_geometry.py
RENAMED
|
File without changes
|
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/wand_calibration_ui.png
RENAMED
|
File without changes
|
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/wand_calibrator.py
RENAMED
|
File without changes
|
|
File without changes
|
{openlpt-2.2.2 → openlpt-2.2.3}/modules/camera_calibration/wand_calibration/wand_detection_ui.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|