程序(1)自动化脚本
/media/dongdong/新加卷/0ubuntu20/1slam/数据/2RTK目录下存在很多组数据集,每个数据集
City1-buildings,City2-skyscrapers,City3-outskirts,City4_outskirts2,City5_wilderness,data_1_nwpuUp/data3_1130_13pm,
data_3_jianda,data_4_city。
每个数据集下面又存在很多个数据集,例如data_4_city/400_450/out_slam,
现在将out_slam下面的goslam中的一级目录所有非文件夹文件(例如trajectory_系列.txt,localization_map.html,evaluation_metrics.json)拷贝到对应数据集/media/dongdong/DD_XS1/2数据下面,对应到各组
City1-buildings,City2-skyscrapers,City3-outskirts,City4_outskirts2,City5_wilderness,data_1_nwpuUp/data3_1130_13pm,
data_3_jianda,data_4_city下面。
#!/usr/bin/env bash
set -Eeuo pipefail
# 顺序测试 8 个场景中的 45 条查询序列,并汇总 final 3D RMSE 和失败率。
# 默认重新执行全部序列;传入 --resume 时跳过已有有效 batch_summary.json 的序列。
# 传入 --list 时只打印测试清单,不执行定位。
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PROGRAM="${SCRIPT_DIR}/v3_dinov3_colmap_mast3r_ba.py"
RESULT_NAME="Atlas-Loc22_Roma2Dark3r_NoGS"
BASE="/media/dongdong/新加卷/0ubuntu20/1slam/数据/2RTK"
MODE="run"
case "${1:-}" in
"") ;;
--resume) MODE="resume" ;;
--list) MODE="list" ;;
*) echo "用法:$0 [--resume|--list]" >&2; exit 2 ;;
esac
# 格式:场景名|场景根目录|地图目录(相对场景根目录)|查询 images 目录(相对场景根目录)
CASES=(
"City1-buildings|${BASE}/City1-buildings|map_cloudy_0303_11pm_127m|location11_night_0224_21pm_125m/pic_0224_night_yintian_2131pm_125/images"
"City1-buildings|${BASE}/City1-buildings|map_cloudy_0303_11pm_127m|location21_fog_0325_8pm_133m/images"
"City1-buildings|${BASE}/City1-buildings|map_cloudy_0303_11pm_127m|location31_season_cloudy_lowlight_0224_17pm_129m/pic_0224_day_yintian_1730pm_129.1/images"
"City1-buildings|${BASE}/City1-buildings|map_cloudy_0303_11pm_127m|location41_sun_0307_11pm_135m/images"
"City1-buildings|${BASE}/City1-buildings|map_cloudy_0303_11pm_127m|location51_cloudy_0306_15pm_135m/images"
"City2-skyscrapers|${BASE}/City2-skyscrapers|map_cloudy_0227_15pm_129m|location11_night_0302_21pm_132m/image_03032000_132.5/images"
"City2-skyscrapers|${BASE}/City2-skyscrapers|map_cloudy_0227_15pm_129m|location21_fog_0325_7pm_133m/images"
"City2-skyscrapers|${BASE}/City2-skyscrapers|map_cloudy_0227_15pm_129m|location31_season_sun_lowlight_0325_17pm_135m/images"
"City2-skyscrapers|${BASE}/City2-skyscrapers|map_cloudy_0227_15pm_129m|location41_sun_0312_12pm_135m/images"
"City2-skyscrapers|${BASE}/City2-skyscrapers|map_cloudy_0227_15pm_129m|location51_cloudy_0303_15pm_135m/images"
"City3-outskirts|${BASE}/City3-outskirts|map_cloudy_0227_14pm_127m|location11_night_0302_21pm_132m/image_137.5/images"
"City3-outskirts|${BASE}/City3-outskirts|map_cloudy_0227_14pm_127m|location21_heavyfog_0325_8pm_133m/images"
"City3-outskirts|${BASE}/City3-outskirts|map_cloudy_0227_14pm_127m|location31_season_cloudy_0331_16pm_135m/images"
"City3-outskirts|${BASE}/City3-outskirts|map_cloudy_0227_14pm_127m|location41_sun_0312_12pm_135/images"
"City3-outskirts|${BASE}/City3-outskirts|map_cloudy_0227_14pm_127m|location51_rain_0227_16pm_130m/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location11_night_0225_20pm_134m/images_0225_20pm_134m/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location11_night_0225_20pm_134m/images_0225_21pm_132m/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location12_night_0306_20pm_135m/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location21_rain_0331_13pm_135m/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location31_season_0325_15pm_135m/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location41_sun_0312_16pm_135m/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location42_sun_0312_12pm_135m/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location43_sun_0307_14pm/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location_lowlight_0225_15pm_129m/image_02251430_yin_129/images"
"City4_outskirts2|${BASE}/City4_outskirts2|map_cloudy_0303_15pm_135m|location_lowlight_0225_15pm_129m/image_02251500_yin_127/images"
"City5_wilderness|${BASE}/City5_wilderness|map_cloudy_0228_17pm_132|location1_night_0312_19pm_135/images"
"City5_wilderness|${BASE}/City5_wilderness|map_cloudy_0228_17pm_132|location1_night_0312_20pm_140m/images"
"City5_wilderness|${BASE}/City5_wilderness|map_cloudy_0228_17pm_132|location2_season_0325_16pm_135m/images"
"City5_wilderness|${BASE}/City5_wilderness|map_cloudy_0228_17pm_132|location3_cloudy_0306_16pm_135m/images"
"data3_1130_13pm|${BASE}/data_1_nwpuUp/data3_1130_13pm|300_map_12pm|280_260/images"
"data3_1130_13pm|${BASE}/data_1_nwpuUp/data3_1130_13pm|300_map_12pm|300_location_12pm/images"
"data3_1130_13pm|${BASE}/data_1_nwpuUp/data3_1130_13pm|300_map_12pm|300_location_14pm/images"
"data3_1130_13pm|${BASE}/data_1_nwpuUp/data3_1130_13pm|300_map_12pm|320_340_360/images"
"data3_1130_13pm|${BASE}/data_1_nwpuUp/data3_1130_13pm|300_map_12pm|400_440/images"
"data3_1130_13pm|${BASE}/data_1_nwpuUp/data3_1130_13pm|300_map_12pm|460_500/images"
"data_3_jianda|${BASE}/data_3_jianda|300_map_3pm|280_260/images"
"data_3_jianda|${BASE}/data_3_jianda|300_map_3pm|300_location_3pm/images"
"data_3_jianda|${BASE}/data_3_jianda|300_map_3pm|320_340_360/images"
"data_3_jianda|${BASE}/data_3_jianda|300_map_3pm|400_450/images"
"data_3_jianda|${BASE}/data_3_jianda|300_map_3pm|450_500/images"
"data_4_city|${BASE}/data_4_city|300_map_2pm|280_260/images"
"data_4_city|${BASE}/data_4_city|300_map_2pm|300_locatiopn_2pm/images"
"data_4_city|${BASE}/data_4_city|300_map_2pm|320_340_360/images"
"data_4_city|${BASE}/data_4_city|300_map_2pm|400_450/images"
"data_4_city|${BASE}/data_4_city|300_map_2pm|460_500/images"
)
if [[ "${#CASES[@]}" -ne 45 ]]; then
echo "内部错误:应有 45 组,实际为 ${#CASES[@]} 组。" >&2
exit 3
fi
# 优先执行三组 NWPU/建大/城市数据,其余 City 数据保持原有顺序。
ORDERED_CASES=()
for priority_group in "data3_1130_13pm" "data_3_jianda" "data_4_city" \
"City1-buildings" "City2-skyscrapers" "City3-outskirts" \
"City4_outskirts2" "City5_wilderness"; do
for case_item in "${CASES[@]}"; do
[[ "${case_item%%|*}" == "${priority_group}" ]] && ORDERED_CASES+=("${case_item}")
done
done
CASES=("${ORDERED_CASES[@]}")
unset ORDERED_CASES
if [[ "${MODE}" == "list" ]]; then
for i in "${!CASES[@]}"; do
IFS='|' read -r group root map_rel query_rel <<< "${CASES[$i]}"
printf '%2d %-20s %s\n' "$((i + 1))" "${group}" "${root}/${query_rel}"
done
exit 0
fi
[[ -f "${PROGRAM}" ]] || { echo "错误:找不到程序 ${PROGRAM}" >&2; exit 1; }
command -v conda >/dev/null 2>&1 || { echo "错误:找不到 conda 命令。" >&2; exit 1; }
source "$(conda info --base)/etc/profile.d/conda.sh"
conda activate py311_AnylocMast3r
RUN_ID="$(date '+%Y%m%d_%H%M%S')"
RUN_DIR="${SCRIPT_DIR}/batch_test_logs/${RUN_ID}"
mkdir -p "${RUN_DIR}"
SUMMARY_TSV="${RUN_DIR}/summary.tsv"
SUMMARY_CSV="${RUN_DIR}/summary.csv"
printf 'index\tgroup\tquery\tstatus\texit_code\ttotal\tlocalized\tfailed\tfailure_rate_pct\tfinal_3d_rmse_m\toutput_dir\n' > "${SUMMARY_TSV}"
write_row() {
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$@" >> "${SUMMARY_TSV}"
}
read_summary() {
python -c '
import json, sys
d = json.load(open(sys.argv[1], encoding="utf-8"))
s = (((d.get("error_stats") or {}).get("final") or {}).get("3d") or {})
vals = [d.get("total_queries"), d.get("localized"), d.get("failed"), d.get("drop_rate_pct"), s.get("rmse_m")]
print("\t".join("NA" if v is None else str(v) for v in vals))
' "$1"
}
cd "${SCRIPT_DIR}"
total="${#CASES[@]}"
for i in "${!CASES[@]}"; do
IFS='|' read -r group root map_rel query_rel <<< "${CASES[$i]}"
index="$((i + 1))"
map_dir="${root}/${map_rel}"
query_dir="${root}/${query_rel}"
query_parent="${query_dir%/images}"
output_dir="${query_parent}/out_slam/${RESULT_NAME}"
summary_json="${output_dir}/batch_summary.json"
safe_name="$(printf '%s_%s' "${group}" "${query_rel%/images}" | tr '/ ()' '____')"
log_file="${RUN_DIR}/$(printf '%02d' "${index}")_${safe_name}.log"
echo
echo "===== [${index}/${total}] ${group} :: ${query_rel%/images} ====="
if [[ ! -d "${map_dir}" || ! -d "${query_dir}" ]]; then
echo "目录缺失:map=${map_dir} query=${query_dir}" | tee "${log_file}"
write_row "${index}" "${group}" "${query_rel}" "MISSING_DIR" "NA" "NA" "NA" "NA" "NA" "NA" "${output_dir}"
continue
fi
if [[ "${MODE}" == "resume" && -f "${summary_json}" ]]; then
IFS=$'\t' read -r frames localized failed fail_rate rmse < <(read_summary "${summary_json}")
write_row "${index}" "${group}" "${query_rel}" "SKIPPED_EXISTING" "0" "${frames}" "${localized}" "${failed}" "${fail_rate}" "${rmse}" "${output_dir}"
echo "已有汇总,跳过:${summary_json}"
continue
fi
marker="${RUN_DIR}/.${index}.started"
touch "${marker}"
set +e
python "${PROGRAM}" \
--dataset "${map_dir}" \
--query-start 1 \
--query "${query_dir}" \
--query-end -1 \
--output-dir "${output_dir}" \
--vpr-top-k 5 \
--device cuda \
--vpr-min-correspondences 8 \
--pnp-reproj-threshold 2 \
--ba-prune-threshold 1.5 \
--vpr-query-resize 512 \
--no-gs-refine \
--match-max-points 1024 \
--no-print-log 2>&1 | tee "${log_file}"
exit_code="${PIPESTATUS[0]}"
set -e
# 只接受本轮新生成的汇总,避免程序失败后误读旧结果。
if [[ -f "${summary_json}" && "${summary_json}" -nt "${marker}" ]]; then
IFS=$'\t' read -r frames localized failed fail_rate rmse < <(read_summary "${summary_json}")
if [[ "${exit_code}" -eq 0 ]]; then status="DONE"; else status="DONE_EXIT_${exit_code}"; fi
write_row "${index}" "${group}" "${query_rel}" "${status}" "${exit_code}" "${frames}" "${localized}" "${failed}" "${fail_rate}" "${rmse}" "${output_dir}"
printf '结果:RMSE=%s m,失败率=%s%%(%s/%s)\n' "${rmse}" "${fail_rate}" "${failed}" "${frames}"
else
write_row "${index}" "${group}" "${query_rel}" "NO_NEW_SUMMARY" "${exit_code}" "NA" "NA" "NA" "NA" "NA" "${output_dir}"
echo "错误:本轮未生成汇总文件,详见 ${log_file}" >&2
fi
echo "本组运行结束,等待 6 秒后继续……"
sleep 6
done
# 生成便于表格软件读取的 CSV,并打印总体帧失败率。
python - "${SUMMARY_TSV}" "${SUMMARY_CSV}" <<'PY'
import csv
import sys
src, dst = sys.argv[1:]
with open(src, encoding="utf-8", newline="") as f:
rows = list(csv.DictReader(f, delimiter="\t"))
with open(dst, "w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys() if rows else [])
writer.writeheader()
writer.writerows(rows)
valid = [r for r in rows if r["total"].isdigit() and r["failed"].isdigit()]
total = sum(int(r["total"]) for r in valid)
failed = sum(int(r["failed"]) for r in valid)
rate = 100.0 * failed / total if total else float("nan")
print("\n==================== 总体统计 ====================")
print(f"已取得统计的序列:{len(valid)}/{len(rows)}")
print(f"总帧数:{total}")
print(f"失败/剔除帧:{failed}")
print(f"总体失败率:{rate:.2f}%")
print(f"逐组 TSV:{src}")
print(f"逐组 CSV:{dst}")
PY
浙公网安备 33010602011771号