Files
yolov26_3d/eval_tools/docs/DUPLICATE_CASE_NAMES_FIX_CN.md
2026-06-24 09:35:46 +08:00

8.2 KiB
Executable File
Raw Blame History

两级路径下重复 Case 名称问题修复

问题描述

在使用两级路径结构(path_depth: 2)时,发现:

  • 数据加载阶段: 找到 155 个 cases
  • 评测阶段: 只处理了 59 个 cases
Found 155 case(s) in detection root: ... (path_depth=2)
Processing case [1/59]: seq-03 (1278 frames)

问题原因

在两级路径结构下,不同的 level1 目录可能包含相同名称的 case

det_root/
    dataset_A/
        seq-03/    ← 同名 case
        seq-27/
    dataset_B/
        seq-03/    ← 同名 case
        seq-28/

原有代码在评测阶段按 case 名称分组时,只使用了 case_name 作为键:

# 原有代码(有问题)
cases = {}
for pair in self.image_pairs:
    case_name = pair['case']  # 只使用 case 名称
    if case_name not in cases:
        cases[case_name] = []
    cases[case_name].append(pair)

这导致:

  • dataset_A/seq-03dataset_B/seq-03 都被归到 cases['seq-03']
  • 两个不同的 case 被合并成一个
  • 155 个实际 case 被合并成 59 个唯一名称的 case

解决方案

修改分组逻辑

evaluate_2d()evaluate_3d() 方法中,使用唯一的 case 标识符进行分组:

# 修复后的代码
cases = {}
for pair in self.image_pairs:
    # 创建唯一的 case 标识符
    level1_name = pair.get('level1_name')
    case_name = pair['case']
    if level1_name:
        case_key = f"{level1_name}/{case_name}"  # 两级路径: "dataset_A/seq-03"
    else:
        case_key = case_name  # 一级路径: "seq-03"

    if case_key not in cases:
        cases[case_key] = []
    cases[case_key].append(pair)

更新循环变量

将循环中的 case_name 改为 case_key,并在所有相关位置使用:

# 修复前
for case_idx, (case_name, case_pairs) in enumerate(cases.items(), 1):
    print(f"Processing case [{case_idx}/{len(cases)}]: {case_name} ...")
    self.per_case_metrics_2d[case_name] = ...

# 修复后
for case_idx, (case_key, case_pairs) in enumerate(cases.items(), 1):
    print(f"Processing case [{case_idx}/{len(cases)}]: {case_key} ...")
    self.per_case_metrics_2d[case_key] = ...

修改的文件

eval_tools/evaluator/evaluator.py

修改了三个方法:

  1. evaluate_2d() - 2D 评测的分组逻辑(第 494-556 行)
  2. evaluate_3d() - 3D 评测的分组逻辑(第 580-662 行)
  3. _write_per_case_reports() - Per-case 报告生成,添加文件名安全处理(第 862-864 行)

主要改动:

  • 第 494-507 行2D 评测的分组和循环
  • 第 556 行:保存 per-case 结果时使用 case_key
  • 第 580-596 行3D 评测的分组和循环
  • 第 627、636、640、662 行:保存 detailed matches 时使用 case_key
  • 第 862-864 行:生成报告文件时将 "/" 替换为 "_"

测试验证

创建了测试脚本 eval_tools/tests/test_duplicate_case_names.py

python eval_tools/tests/test_duplicate_case_names.py

测试结果:

============================================================
Test Summary
============================================================
✓ TEST PASSED

The fix correctly handles duplicate case names across
different level1 directories by using unique case identifiers.

测试验证了:

  1. 数据加载阶段正确识别所有 cases包括重名的
  2. 评测阶段正确处理所有 cases不会合并重名的
  3. Per-case 报告使用唯一标识符

预期效果

修复后,运行评测时:

修复前

Found 155 case(s) in detection root: ... (path_depth=2)
Processing case [1/59]: seq-03 (1278 frames)  ← 合并了多个同名 case

修复后

Found 155 case(s) in detection root: ... (path_depth=2)
Processing case [1/155]: dataset_A/seq-03 (640 frames)
Processing case [2/155]: dataset_B/seq-03 (638 frames)  ← 正确分开
Processing case [3/155]: dataset_A/seq-27 (512 frames)
...

Case 标识符格式

一级路径(path_depth: 1

  • Case 标识符: seq-03
  • 显示格式: seq-03

两级路径(path_depth: 2

  • Case 标识符: dataset_A/seq-03
  • 显示格式: dataset_A/seq-03

Per-Case 报告

Per-case 报告文件名会将 "/" 替换为 "_" 以兼容文件系统:

一级路径

per_case_reports/
    seq-03_report.txt
    seq-27_report.txt

两级路径

per_case_reports/
    dataset_A_seq-03_report.txt  ← "/" 被替换为 "_"
    dataset_B_seq-03_report.txt
    dataset_A_seq-27_report.txt

重要: 由于文件系统不允许文件名中包含 "/",在生成报告文件时,case_key 中的 "/" 会被自动替换为 "_"。这个转换在 _write_per_case_reports() 方法中自动完成:

# 在 evaluator.py 中
for case_name in sorted(case_names):
    # 将 "/" 替换为 "_" 以兼容文件系统
    safe_case_name = case_name.replace('/', '_')
    case_report_path = os.path.join(per_case_dir, f'{safe_case_name}_report.txt')

向后兼容性

  • 对于一级路径结构(path_depth: 1level1_nameNonecase_key 就是 case_name
  • 行为与之前完全相同,不会有任何变化
  • 所有现有配置和脚本无需修改

使用示例

示例 1: 查看评测输出

修复前(合并了重名 case

Processing case [1/59]: seq-03 (1278 frames)
  seq-03: 100%|████████| 1278/1278 [00:05<00:00, 245.67it/s]

修复后(正确分开):

Processing case [1/155]: dataset_A/seq-03 (640 frames)
  dataset_A/seq-03: 100%|████████| 640/640 [00:02<00:00, 248.12it/s]

Processing case [2/155]: dataset_B/seq-03 (638 frames)
  dataset_B/seq-03: 100%|████████| 638/638 [00:02<00:00, 246.89it/s]

示例 2: 查看 Per-Case 报告

# 列出所有 per-case 报告
ls evaluation_results/*/per_case_reports/

# 输出(修复后):
dataset_A_seq-03_report.txt
dataset_A_seq-27_report.txt
dataset_B_seq-03_report.txt
dataset_B_seq-28_report.txt
...

示例 3: 查看评测报告 JSON

import json

with open('evaluation_results/.../evaluation_report.json', 'r') as f:
    report = json.load(f)

# 查看 per-case 2D 结果
for case_key, metrics in report['per_case_2d'].items():
    print(f"{case_key}: mAP={metrics['overall']['map']:.4f}")

# 输出(修复后):
# dataset_A/seq-03: mAP=0.8234
# dataset_A/seq-27: mAP=0.8156
# dataset_B/seq-03: mAP=0.8312
# dataset_B/seq-28: mAP=0.8089

故障排查

问题:仍然看到 case 数量不匹配

检查项:

  1. 确认已经更新到最新代码
  2. 确认 path_depth: 2 已设置
  3. 检查是否有其他原因导致 case 被跳过(如缺少文件)

调试方法:

# 在 load_data_from_paths 后添加调试输出
print(f"Total image pairs: {len(evaluator.image_pairs)}")
print(f"Unique cases: {len(set(pair['case'] for pair in evaluator.image_pairs))}")
print(f"Unique case_keys: {len(set(f\"{pair.get('level1_name', '')}/{pair['case']}\" for pair in evaluator.image_pairs))}")

问题Per-case 报告文件名包含特殊字符

原因: case_key 中的 "/" 在文件名中不合法,会被操作系统误认为是目录分隔符

错误示例:

# 错误:会尝试创建 per_case_reports/20251115/seq-03_report.txt
case_report_path = os.path.join(per_case_dir, f'{case_key}_report.txt')
# 如果 case_key = "20251115/seq-03",会导致 FileNotFoundError

解决方案: 代码会自动将 "/" 替换为 "_"

# 正确:创建 per_case_reports/20251115_seq-03_report.txt
safe_case_name = case_key.replace('/', '_')
case_report_path = os.path.join(per_case_dir, f'{safe_case_name}_report.txt')

修复位置: evaluator.py 第 862-864 行

相关文档

总结

此修复确保了评测系统在两级路径结构下能够正确处理重复的 case 名称,通过使用唯一的 case 标识符(level1/case)来区分不同 level1 目录下的同名 case。修改保持了向后兼容性不影响现有的一级路径结构使用。

修复后,评测结果将更加准确,每个 case 都会被独立评测和报告,不会因为名称重复而被错误地合并。