新增双目yolo实现python代码

This commit is contained in:
keita
2026-09-14 15:05:03 +08:00
parent 538ae4c6c7
commit 3a8e2c7f4b
65 changed files with 2467 additions and 10 deletions
+103
View File
@@ -0,0 +1,103 @@
"""测距计算与有效性判定测试(规格 §3.2/§6.4/§6.6/§7.1/§11.4)。"""
import math
import pytest
from stereo_ranger import (
Detection,
StereoConfig,
confidence_from_cost,
evaluate_validity,
forward_distance_m,
range_error_abs_m,
range_error_rel,
solve_range,
virtual_center_px,
)
CFG = StereoConfig()
def _boxes_for_point(x_m: float, y_m: float, z_m: float, width_px: float = 50.0, height_px: float = 40.0):
"""由左目系 (X, Y, Z) 反推左右目框(同宽,边缘视差 = 中心视差 = F·B/Z)。"""
d_px = CFG.focal_baseline_px_m / z_m
x_l = CFG.cx_px + CFG.fx_px * x_m / z_m
y_l = CFG.cy_px + CFG.fy_px * y_m / z_m
left = Detection(x_l - width_px / 2, y_l - height_px / 2, x_l + width_px / 2, y_l + height_px / 2)
right = Detection(
x_l - width_px / 2 - d_px, y_l - height_px / 2, x_l + width_px / 2 - d_px, y_l + height_px / 2
)
return left, right, d_px
def test_solve_range_recovers_ground_truth():
# §11.4:已知 Z 反推 D 构造框对,断言 Z = F·B/D 回收
left, right, d_px = _boxes_for_point(0.6, 0.3, 6.0)
solution = solve_range(left, right, d_px, CFG)
assert solution.z_m == pytest.approx(6.0)
assert solution.x_m == pytest.approx(0.6)
assert solution.y_m == pytest.approx(0.3)
assert solution.r_m == pytest.approx(math.sqrt(0.6**2 + 0.3**2 + 6.0**2))
def test_forward_distance_rejects_non_positive_disparity():
with pytest.raises(ValueError):
forward_distance_m(0.0, CFG)
with pytest.raises(ValueError):
forward_distance_m(-2.0, CFG)
def test_virtual_center_equals_midline_camera_projection():
# §6.6 恒等式:xL = cx + F(X_C + B/2)/Z,xR = cx + F(X_C − B/2)/Z ⇒ c_mid = cx + F·X_C/Z
z_m = 5.0
x_center_m = 0.57
x_l = CFG.cx_px + CFG.fx_px * (x_center_m + CFG.baseline_m / 2.0) / z_m
x_r = CFG.cx_px + CFG.fx_px * (x_center_m - CFG.baseline_m / 2.0) / z_m
left = Detection(x_l - 30.0, 480.0, x_l + 30.0, 520.0)
right = Detection(x_r - 30.0, 480.0, x_r + 30.0, 520.0)
center_mid = virtual_center_px(left, right)
assert center_mid[0] == pytest.approx(CFG.cx_px + CFG.fx_px * x_center_m / z_m)
assert center_mid[1] == pytest.approx(500.0)
def test_error_model_is_quadratic_in_z():
assert range_error_abs_m(10.0, CFG) == pytest.approx(10.0**2 / CFG.focal_baseline_px_m)
assert range_error_rel(10.0, CFG) == pytest.approx(10.0 / CFG.focal_baseline_px_m)
assert range_error_abs_m(20.0, CFG) == pytest.approx(4.0 * range_error_abs_m(10.0, CFG))
def test_validity_gate_1_out_of_domain():
decision = evaluate_validity(CFG.disparity_min_px - 0.1, 30.0, CFG)
assert not decision.in_domain and not decision.valid
def test_validity_gate_2_error_budget_blocks_far_field():
# 域内(D = 4.8 px → Z = 12 m)但 δZ = 2.5 m > 2 m,预算门拦截
d_px = 4.8
z_m = CFG.focal_baseline_px_m / d_px
decision = evaluate_validity(d_px, z_m, CFG)
assert decision.in_domain and not decision.budget_ok and not decision.valid
def test_validity_gate_2_boundary_sides():
z_limit = CFG.z_error_budget_max_m
assert evaluate_validity(CFG.focal_baseline_px_m / z_limit, z_limit * 0.999, CFG).budget_ok
assert not evaluate_validity(CFG.focal_baseline_px_m / z_limit, z_limit * 1.001, CFG).budget_ok
def test_validity_requires_finite_z():
assert not evaluate_validity(5.0, None, CFG).valid
assert not evaluate_validity(5.0, float("inf"), CFG).valid
def test_domain_boundaries_are_inclusive():
assert evaluate_validity(CFG.disparity_min_px, 19.2, CFG).in_domain
assert evaluate_validity(CFG.disparity_max_px, 1.0, CFG).in_domain
def test_confidence_from_cost_linear_clamped():
assert confidence_from_cost(0.0) == pytest.approx(1.0)
assert confidence_from_cost(0.4) == pytest.approx(0.6)
assert confidence_from_cost(1.0) == pytest.approx(0.0)
assert confidence_from_cost(1.5) == pytest.approx(0.0)