wild_test
This commit is contained in:
1 parent
136ba0ce89
commit
60d973fd25
4 files changed
+93
-27
No files matched your search
@@ -62,18 +62,19 @@ cd /home/zhangw/scan-calibrator
|
|||||||
① 当前是【野外部署阶段】(回复 1)还是【办公室测试阶段】(回复 2)?
|
① 当前是【野外部署阶段】(回复 1)还是【办公室测试阶段】(回复 2)?
|
||||||
> 2
|
> 2
|
||||||
|
|
||||||
② 请在Web界面中固定本次扫描使用的zoom,再手动把摄像头移到扫描区域的【左上角】。
|
② 请在Web界面中设置左上角zoom,再手动把摄像头移到扫描区域的【左上角】。
|
||||||
到位后回复 1;回复 3 随时退出。
|
到位后回复 1;回复 3 随时退出。
|
||||||
> 1
|
> 1
|
||||||
已读取: pan=19120 (191.20°), tilt=990 (9.90°), zoom=810 (8.10×)
|
已读取: pan=19120 (191.20°), tilt=990 (9.90°), zoom=810 (8.10×)
|
||||||
|
|
||||||
③ 请保持zoom不变,手动把摄像头移到扫描区域的【右下角】。
|
③ 请手动把摄像头移到扫描区域的【右下角】。
|
||||||
|
右下角可以使用不同zoom,程序会取两个角点zoom的平均值作为最终扫描zoom。
|
||||||
到位后回复 1;回复 3 随时退出。
|
到位后回复 1;回复 3 随时退出。
|
||||||
> 1
|
> 1
|
||||||
已读取: pan=13420 (134.20°), tilt=-340 (-3.40°), zoom=810 (8.10×)
|
已读取: pan=13420 (134.20°), tilt=-340 (-3.40°), zoom=810 (8.10×)
|
||||||
|
|
||||||
④ 这一步用于测量当前zoom下的水平/垂直视场。
|
④ 这一步用于测量当前zoom下的水平/垂直视场。
|
||||||
请保持zoom不变,优先把摄像头移到扫描区域内部、距离较远且纹理分布在整幅画面的位置。
|
请把摄像头移到扫描区域内部、距离较远且纹理分布在整幅画面的位置。
|
||||||
请避开纯天空、纯墙面、水面、玻璃、近距离物体和重复图案;
|
请避开纯天空、纯墙面、水面、玻璃、近距离物体和重复图案;
|
||||||
回复1后不要再操作Web云台,程序会自动移动、拍照并返回起点。
|
回复1后不要再操作Web云台,程序会自动移动、拍照并返回起点。
|
||||||
到位后回复 1;回复 3 随时退出。
|
到位后回复 1;回复 3 随时退出。
|
||||||
@@ -156,7 +157,7 @@ cd /home/zhangw/scan-calibrator
|
|||||||
|
|
||||||
### ② 记录左上角
|
### ② 记录左上角
|
||||||
|
|
||||||
现场人员先在Web界面设置固定zoom,并移动到扫描区域左上角。回复`1`后,向导通过
|
现场人员先在Web界面设置左上角zoom,并移动到扫描区域左上角。回复`1`后,向导通过
|
||||||
smart-cam的HTTP接口读取PTZ并记录,等价于:
|
smart-cam的HTTP接口读取PTZ并记录,等价于:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -168,22 +169,24 @@ smart-cam的HTTP接口读取PTZ并记录,等价于:
|
|||||||
|
|
||||||
### ③ 记录右下角
|
### ③ 记录右下角
|
||||||
|
|
||||||
保持zoom不变,移动到右下角。回复`1`后等价于:
|
移动到右下角;这里允许根据覆盖范围使用不同zoom。回复`1`后等价于:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./calibrate.sh ptz
|
./calibrate.sh ptz
|
||||||
./calibrate.sh mark bottom-right
|
./calibrate.sh mark bottom-right
|
||||||
```
|
```
|
||||||
|
|
||||||
向导会比较两个角点的zoom;不一致时立即停止,避免用不同视场计算网格。
|
向导会显示两个角点的zoom,并取算术平均值作为最终扫描zoom。第四步开始测量前,
|
||||||
|
程序会自动把镜头切换到该平均zoom,因此视场标定与最终配置使用同一焦段。
|
||||||
|
|
||||||
### ④ 自动计算pan_step和tilt_step
|
### ④ 自动计算pan_step和tilt_step
|
||||||
|
|
||||||
现场人员保持zoom不变,把镜头移到扫描区域内部、距离较远且纹理丰富的位置。
|
现场人员把镜头移到扫描区域内部、距离较远且纹理丰富的位置。
|
||||||
回复`1`后,向导会:
|
回复`1`后,向导会:
|
||||||
|
|
||||||
1. 抓取基准图;
|
1. 抓取基准图;
|
||||||
2. 沿pan方向尝试若干候选角度,每次抓图后回到起点;
|
2. 沿pan方向先从小角度探测;取得首个有效样本后立即反算当前zoom的视场,
|
||||||
|
并自适应补测约82%、72%、60%、48%、35%重叠附近的候选角度,每次抓图后回到起点;
|
||||||
3. 用ORB特征、RANSAC和单轴画面位移求水平视场;
|
3. 用ORB特征、RANSAC和单轴画面位移求水平视场;
|
||||||
4. 沿tilt方向重复测量;
|
4. 沿tilt方向重复测量;
|
||||||
5. 每个方向统一筛选相互一致的视场样本;获得4个覆盖充分、离散度不超过8%的
|
5. 每个方向统一筛选相互一致的视场样本;获得4个覆盖充分、离散度不超过8%的
|
||||||
@@ -203,8 +206,9 @@ smart-cam的HTTP接口读取PTZ并记录,等价于:
|
|||||||
这一步不能省略。两个角点只能确定扫描范围,无法确定当前摄像头型号和zoom下的一幅画面
|
这一步不能省略。两个角点只能确定扫描范围,无法确定当前摄像头型号和zoom下的一幅画面
|
||||||
覆盖多少角度。
|
覆盖多少角度。
|
||||||
|
|
||||||
不要把画面对准纯天空、纯墙、水面、玻璃、近距离物体或重复图案,否则无法可靠匹配。
|
不要把画面对准纯天空、纯墙、纯水面、玻璃、近距离物体或重复图案。水面场景应让岸线、
|
||||||
这一步不能改变zoom;自动对焦不影响zoom。标定失败时可以只更换第四步的位置重试,
|
浮标等固定物体横向分布在画面中,不能只依赖不断变化的水纹。
|
||||||
|
测量过程中程序会锁定两个角点的平均zoom;自动对焦不影响zoom。标定失败时可以只更换第四步的位置重试,
|
||||||
不需要重新记录左上角和右下角。
|
不需要重新记录左上角和右下角。
|
||||||
|
|
||||||
### ⑤ 显示最终参数
|
### ⑤ 显示最终参数
|
||||||
|
|||||||
+76
-12
@@ -72,9 +72,32 @@ def remove_session_snapshots(paths: set[str]) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def candidate_deltas(axis: str) -> list[int]:
|
def candidate_deltas(axis: str) -> list[int]:
|
||||||
|
"""Coarse probes from narrow to wide FOV.
|
||||||
|
|
||||||
|
Once any probe provides a usable FOV estimate, estimate_axis replaces the
|
||||||
|
wide tail with scene/camera-specific deltas. Dense small values are
|
||||||
|
important at high optical zoom, where a jump from 150 to 250 PTZ units can
|
||||||
|
go directly from useful overlap to no common image.
|
||||||
|
"""
|
||||||
if axis == "pan":
|
if axis == "pan":
|
||||||
return [150, 250, 350, 450, 600, 800, 1100, 1500, 2100]
|
return [50, 75, 110, 150, 220, 320, 470, 700, 1050, 1500, 2100]
|
||||||
return [80, 120, 170, 220, 280, 350, 450, 600, 850, 1150]
|
return [25, 40, 60, 90, 130, 190, 280, 420, 620, 850, 1150]
|
||||||
|
|
||||||
|
|
||||||
|
def fov_guided_deltas(delta: int, overlap: float, axis: str) -> list[int]:
|
||||||
|
"""Choose probes that retain enough shared image for robust matching."""
|
||||||
|
shift = 1.0 - overlap
|
||||||
|
if not 0.04 <= shift <= 0.92:
|
||||||
|
return []
|
||||||
|
estimated_fov = delta / shift
|
||||||
|
minimum = 20 if axis == "pan" else 12
|
||||||
|
# Fractions are image displacement/FOV, hence target overlaps are roughly
|
||||||
|
# 82%, 72%, 60%, 48% and 35%. The middle probes provide the strongest
|
||||||
|
# cross-check, while the last one confirms the wider displacement model.
|
||||||
|
return sorted({
|
||||||
|
max(minimum, round(estimated_fov * fraction))
|
||||||
|
for fraction in (0.18, 0.28, 0.40, 0.52, 0.65)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def choose_direction(axis: str, origin: dict[str, int], points: dict) -> int:
|
def choose_direction(axis: str, origin: dict[str, int], points: dict) -> int:
|
||||||
@@ -239,7 +262,14 @@ def estimate_axis(axis: str, origin: dict[str, int], points: dict,
|
|||||||
failure_reasons: dict[str, int] = {}
|
failure_reasons: dict[str, int] = {}
|
||||||
say(f"\n正在标定 {axis}_step,摄像头会自动移动并回到起点……")
|
say(f"\n正在标定 {axis}_step,摄像头会自动移动并回到起点……")
|
||||||
|
|
||||||
for delta in candidate_deltas(axis):
|
pending = candidate_deltas(axis)
|
||||||
|
tested: set[int] = set()
|
||||||
|
adaptive_seeded = False
|
||||||
|
while pending:
|
||||||
|
delta = pending.pop(0)
|
||||||
|
if delta in tested:
|
||||||
|
continue
|
||||||
|
tested.add(delta)
|
||||||
result = core.measure_delta(
|
result = core.measure_delta(
|
||||||
axis, delta, direction, 1.0, baseline=baseline, origin=origin
|
axis, delta, direction, 1.0, baseline=baseline, origin=origin
|
||||||
)
|
)
|
||||||
@@ -270,6 +300,22 @@ def estimate_axis(axis: str, origin: dict[str, int], points: dict,
|
|||||||
else:
|
else:
|
||||||
quality = f"有效特征={inliers},内点率={ratio*100:.1f}%"
|
quality = f"有效特征={inliers},内点率={ratio*100:.1f}%"
|
||||||
say(f" step={delta}: 重叠约 {overlap*100:.1f}%,{quality}")
|
say(f" step={delta}: 重叠约 {overlap*100:.1f}%,{quality}")
|
||||||
|
if not adaptive_seeded:
|
||||||
|
guided = fov_guided_deltas(delta, overlap, axis)
|
||||||
|
if guided:
|
||||||
|
# Keep only a small safety tail beyond the guided range. Very
|
||||||
|
# large fixed probes cannot match once the current optical FOV
|
||||||
|
# has been measured and only waste time.
|
||||||
|
cap = round(max(guided) * 1.15)
|
||||||
|
pending = sorted({
|
||||||
|
value for value in guided + pending
|
||||||
|
if value not in tested and value <= cap
|
||||||
|
})
|
||||||
|
adaptive_seeded = True
|
||||||
|
say(
|
||||||
|
f" 已根据首个有效样本自适应生成后续step:"
|
||||||
|
f"{', '.join(str(value) for value in pending)}"
|
||||||
|
)
|
||||||
feature_reliable = inliers >= 50 and ratio >= 0.45
|
feature_reliable = inliers >= 50 and ratio >= 0.45
|
||||||
phase_reliable = (
|
phase_reliable = (
|
||||||
result.get("match_method") == "phase_correlation"
|
result.get("match_method") == "phase_correlation"
|
||||||
@@ -423,8 +469,19 @@ def estimate_axis(axis: str, origin: dict[str, int], points: dict,
|
|||||||
def calculate_steps(points: dict, snapshots: set[str], mount: str) -> None:
|
def calculate_steps(points: dict, snapshots: set[str], mount: str) -> None:
|
||||||
origin = camera_ptz()
|
origin = camera_ptz()
|
||||||
zooms = [int(points[name]["zoom"]) for name in ("top-left", "bottom-right")]
|
zooms = [int(points[name]["zoom"]) for name in ("top-left", "bottom-right")]
|
||||||
if max(zooms + [origin["zoom"]]) - min(zooms + [origin["zoom"]]) > 10:
|
target_zoom = round(sum(zooms) / len(zooms))
|
||||||
core.fail("三个位置的zoom不一致;请固定焦段后重新开始")
|
if origin["zoom"] != target_zoom:
|
||||||
|
say(
|
||||||
|
f"两个角点zoom的平均值为{target_zoom}({target_zoom/100:.2f}×);"
|
||||||
|
"正在自动切换到该zoom后再测量视场……"
|
||||||
|
)
|
||||||
|
origin = core.move({**origin, "zoom": target_zoom})
|
||||||
|
time.sleep(1.0)
|
||||||
|
if abs(int(origin["zoom"]) - target_zoom) > 10:
|
||||||
|
core.fail(
|
||||||
|
f"摄像头未能到达平均zoom:目标={target_zoom},实际={origin['zoom']}"
|
||||||
|
)
|
||||||
|
core.print_ptz(origin, "平均zoom到位")
|
||||||
top_left = points["top-left"]
|
top_left = points["top-left"]
|
||||||
bottom_right = points["bottom-right"]
|
bottom_right = points["bottom-right"]
|
||||||
# This check is only advisory and runs before signed pan direction has
|
# This check is only advisory and runs before signed pan direction has
|
||||||
@@ -624,7 +681,7 @@ def main() -> None:
|
|||||||
mount = "hanging" if stage == "1" else "desktop"
|
mount = "hanging" if stage == "1" else "desktop"
|
||||||
|
|
||||||
confirm_position(
|
confirm_position(
|
||||||
"② 请在Web界面中固定本次扫描使用的zoom,"
|
"② 请在Web界面中设置左上角需要的zoom,"
|
||||||
"再手动把摄像头移到扫描区域的【左上角】。"
|
"再手动把摄像头移到扫描区域的【左上角】。"
|
||||||
)
|
)
|
||||||
top_left = camera_ptz()
|
top_left = camera_ptz()
|
||||||
@@ -633,11 +690,16 @@ def main() -> None:
|
|||||||
core.save_state(state)
|
core.save_state(state)
|
||||||
|
|
||||||
confirm_position(
|
confirm_position(
|
||||||
"③ 请保持zoom不变,手动把摄像头移到扫描区域的【右下角】。"
|
"③ 请手动把摄像头移到扫描区域的【右下角】。\n"
|
||||||
|
"右下角可以使用不同zoom,程序会取两个角点zoom的平均值作为最终扫描zoom。"
|
||||||
)
|
)
|
||||||
bottom_right = camera_ptz()
|
bottom_right = camera_ptz()
|
||||||
if abs(top_left["zoom"] - bottom_right["zoom"]) > 10:
|
average_zoom = round((top_left["zoom"] + bottom_right["zoom"]) / 2)
|
||||||
core.fail("左右角的zoom不一致,请固定焦段后重新运行")
|
say(
|
||||||
|
f"两个角点zoom:左上={top_left['zoom']/100:.2f}×,"
|
||||||
|
f"右下={bottom_right['zoom']/100:.2f}×;"
|
||||||
|
f"最终取平均={average_zoom/100:.2f}×。"
|
||||||
|
)
|
||||||
state = core.load_state()
|
state = core.load_state()
|
||||||
state["points"]["bottom-right"] = bottom_right
|
state["points"]["bottom-right"] = bottom_right
|
||||||
core.save_state(state)
|
core.save_state(state)
|
||||||
@@ -646,10 +708,12 @@ def main() -> None:
|
|||||||
while True:
|
while True:
|
||||||
confirm_position(
|
confirm_position(
|
||||||
"④ 这一步用于测量当前zoom下的水平/垂直视场。\n"
|
"④ 这一步用于测量当前zoom下的水平/垂直视场。\n"
|
||||||
"请保持zoom不变,优先把摄像头移到扫描区域内部、"
|
"请优先把摄像头移到扫描区域内部、"
|
||||||
"距离较远且纹理分布在整幅画面的位置。\n"
|
"距离较远且纹理分布在整幅画面的位置。\n"
|
||||||
"请避开纯天空、纯墙面、水面、玻璃、近距离物体和重复图案;"
|
"请避开纯天空、纯墙面、纯水面、玻璃、近距离物体和重复图案;"
|
||||||
"回复1后不要再操作Web云台,程序会自动移动、拍照并返回起点。"
|
"水面场景需要让岸线、浮标或其他固定物体横向分布在画面中;"
|
||||||
|
"回复1后不要再操作Web云台,程序会先切换到两个角点的平均zoom,"
|
||||||
|
"再自动移动、拍照并返回起点。"
|
||||||
)
|
)
|
||||||
say("\n正在计算,请不要操作摄像头或Web控制页面……")
|
say("\n正在计算,请不要操作摄像头或Web控制页面……")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -422,8 +422,6 @@ def command_generate(args: argparse.Namespace) -> None:
|
|||||||
f"相位响应={phase_response:.2f});"
|
f"相位响应={phase_response:.2f});"
|
||||||
f"请在纹理丰富且尽量远离摄像头的位置重新执行calibrate {axis}"
|
f"请在纹理丰富且尽量远离摄像头的位置重新执行calibrate {axis}"
|
||||||
)
|
)
|
||||||
if abs(int(top_left["zoom"]) - int(bottom_right["zoom"])) > 10:
|
|
||||||
fail("左上角和右下角zoom不一致,请固定zoom后重新记录两个角点")
|
|
||||||
zoom = round((int(top_left["zoom"]) + int(bottom_right["zoom"])) / 2)
|
zoom = round((int(top_left["zoom"]) + int(bottom_right["zoom"])) / 2)
|
||||||
pan_start, pan_end, pan_span, visual_pan_direction = scan_pan_range(
|
pan_start, pan_end, pan_span, visual_pan_direction = scan_pan_range(
|
||||||
int(top_left["pan"]),
|
int(top_left["pan"]),
|
||||||
|
|||||||
+3
-3
@@ -99,8 +99,8 @@ cd /home/zhangw/scan-calibrator
|
|||||||
|
|
||||||
### 第一步:固定zoom和焦点
|
### 第一步:固定zoom和焦点
|
||||||
|
|
||||||
先通过Web界面把摄像头调到准备扫描时使用的zoom。整个标定过程、左上角、右下角和
|
左上角和右下角可以按现场覆盖需要分别设置zoom。最终扫描zoom取两个角点读数的算术平均值,
|
||||||
step测量必须使用同一个zoom。
|
step测量必须在这个平均zoom下进行;交互式向导会在第四步自动切换到平均zoom。
|
||||||
|
|
||||||
注意:zoom是焦段/视场角;focus是对焦清晰度,两者不是同一个参数。
|
注意:zoom是焦段/视场角;focus是对焦清晰度,两者不是同一个参数。
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ step测量必须使用同一个zoom。
|
|||||||
|
|
||||||
### 第三步:记录右下角
|
### 第三步:记录右下角
|
||||||
|
|
||||||
手动移动到扫描区域右下角,zoom保持不变:
|
手动移动到扫描区域右下角;zoom可以与左上角不同:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./calibrate.sh ptz
|
./calibrate.sh ptz
|
||||||
|
|||||||
Reference in new issue
Block a user