This commit is contained in:
Ankle committed 2026-08-07 08:28:37 +08:00
1 parent 60d973fd25
commit 33d87ac9d9
1 file changed
+42 -16
+42 -16
View File
@@ -63,7 +63,10 @@ def camera_ptz() -> dict[str, int]:
def remove_session_snapshots(paths: set[str]) -> None:
for path in sorted(paths):
if not path.startswith("/smart-cam/data/scan/calibration/"):
if not path.startswith((
"/smart-cam/data/scan/manual/",
"/smart-cam/data/scan/calibration/",
)):
continue
subprocess.run(
["docker", "exec", "smart-cam", "rm", "-f", "--", path],
@@ -109,20 +112,31 @@ def choose_direction(axis: str, origin: dict[str, int], points: dict) -> int:
return 1 if high - origin["tilt"] >= origin["tilt"] - low else -1
def consistent_samples(reliable: list[tuple[int, float, float, dict]]) -> list:
"""Return the largest group that estimates one mutually consistent FOV."""
def consistent_samples(reliable: list[tuple[int, float, float, dict]],
maximum_spread: float = 0.15) -> list:
"""Return the largest group with a bounded end-to-end FOV spread.
The previous seed-radius clustering allowed one sample at -12% and
another at +12% into the same cluster. That cluster could therefore have
nearly 24% end-to-end spread and was immediately rejected by the later
15% check. Use sorted contiguous windows so membership and validation
apply the same definition of consistency.
"""
if not reliable:
return []
clusters = [
[
sample for sample in reliable
if abs(sample[2] - seed[2]) / max(seed[2], 1.0) <= 0.12
]
for seed in reliable
]
return max(
clusters, key=lambda cluster: (len(cluster), -max(x[2] for x in cluster))
)
ordered = sorted(reliable, key=lambda sample: sample[2])
candidates = []
for begin in range(len(ordered)):
for end in range(begin, len(ordered)):
window = ordered[begin:end + 1]
_, spread = fov_statistics(window)
if spread <= maximum_spread:
candidates.append(window)
return max(candidates, key=lambda group: (
len(group),
-fov_statistics(group)[1],
sum(int(sample[3].get("inliers") or 0) for sample in group),
))
def fov_statistics(samples: list[tuple[int, float, float, dict]]) -> tuple[float, float]:
@@ -321,8 +335,17 @@ def estimate_axis(axis: str, origin: dict[str, int], points: dict,
result.get("match_method") == "phase_correlation"
and float(result.get("phase_response") or 0) >= core.PHASE_MIN_RESPONSE
)
if (feature_reliable or phase_reliable) and 0.08 <= overlap <= 0.80:
reliable.append((delta, overlap, delta / (1.0 - overlap), result))
if (feature_reliable or phase_reliable) and 0.08 <= overlap <= 0.90:
# Hikvision absolute PTZ movement is quantized and can also have
# backlash. At high optical zoom, using the requested delta
# instead of the SDK-reported actual movement creates a large,
# systematic FOV error. Shortfall samples have already been
# rejected above, so the actual movement is authoritative here.
actual_delta = int(result.get("actual_axis_motion") or delta)
reliable.append((
actual_delta, overlap,
actual_delta / (1.0 - overlap), result,
))
if len(reliable) >= 4:
provisional = consistent_samples(reliable)
if len(provisional) >= 4:
@@ -372,7 +395,10 @@ def estimate_axis(axis: str, origin: dict[str, int], points: dict,
for sample in consistent
)
allowed_spread = 0.08
if not strong_pair:
# Two independently measured FOVs that agree within 5% are already a
# strong geometric cross-check. Do not reject such a pair merely
# because a natural scene supplied 74 rather than 100 ORB inliers.
if not strong_pair and spread > 0.05:
core.move(origin)
core.fail(
f"{axis}方向目前只有2个一致样本,但特征质量不足以使用双样本规则;"