| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 入户门投影可视化
- 将所有检测到的门映射到 2D 俯视图上:
- - 绿色圆点:普通门(从 JSON 中的 doors 提取)
- - 红色圆点:入户门(entrance_door_id 对应的门 或 world_position)
- """
- import json
- import os
- import numpy as np
- import matplotlib.pyplot as plt
- from matplotlib.patches import Circle
- from pathlib import Path
- def load_entrance_json(json_path: str) -> dict:
- """加载入户门 JSON 文件"""
- with open(json_path, 'r', encoding='utf-8') as f:
- return json.load(f)
- def visualize_doors_2d(
- entrance_data: dict,
- output_path: str
- ):
- """
- 在 2D 俯视图上绘制所有门和入户门
- Args:
- entrance_data: 入户门 JSON 数据
- output_path: 输出图片路径
- """
- scene_name = entrance_data.get('scene_name', 'unknown')
- entrance_door_id = entrance_data.get('entrance_door_id')
- world_position = entrance_data.get('world_position')
- doors = entrance_data.get('doors', [])
- print(f"\n处理场景:{scene_name}")
- print(f" entrance_door_id: {entrance_door_id}")
- print(f" world_position: {world_position}")
- print(f" 门数量:{len(doors)}")
- # 确定入户门的世界坐标
- entrance_world_center = None
- entrance_door_obj = None
- if entrance_door_id is not None:
- # 从 doors 中找到对应的门
- for door in doors:
- if door.get('door_id') == entrance_door_id:
- entrance_world_center = door.get('world_center')
- entrance_door_obj = door
- print(f" 使用 entrance_door_id={entrance_door_id} 的世界坐标")
- break
- if entrance_world_center is None and world_position is not None:
- entrance_world_center = world_position
- print(f" 使用 world_position 作为入户门位置")
- if entrance_world_center is None:
- print(" ⚠️ 警告:无法确定入户门世界坐标,只绘制绿色门位置")
- # 提取所有门的中心坐标(2D 俯视,使用 X-Y 平面)
- # world_center = [x, y, z],其中 Y 是前后方向,X 是左右方向
- door_centers = []
- for door in doors:
- center = door.get('world_center')
- if center:
- door_centers.append({
- 'door_id': door.get('door_id'),
- 'x': center[0], # X 坐标(左右)
- 'y': center[1], # Y 坐标(前后)
- 'z': center[2], # Z 坐标(高度)
- 'is_entrance': door.get('door_id') == entrance_door_id,
- 'source_image': door.get('source_image'),
- 'confidence': door.get('confidence'),
- 'size': door.get('bounding_box_size', {})
- })
- if not door_centers:
- print(" ⚠️ 没有有效的门坐标")
- return
- # 创建图表
- fig, ax = plt.subplots(figsize=(14, 12))
- # 提取坐标
- xs = [p['x'] for p in door_centers]
- ys = [p['y'] for p in door_centers]
- # 计算坐标范围
- x_min, x_max = min(xs), max(xs)
- y_min, y_max = min(ys), max(ys)
- # 设置坐标范围(带边距)
- x_margin = max((x_max - x_min) * 0.15, 1.0)
- y_margin = max((y_max - y_min) * 0.15, 1.0)
- ax.set_xlim(x_min - x_margin, x_max + x_margin)
- ax.set_ylim(y_min - y_margin, y_max + y_margin)
- # 绘制所有门
- for door in door_centers:
- is_entrance = (door['door_id'] == entrance_door_id) or \
- (entrance_world_center and
- np.allclose([door['x'], door['z']],
- [entrance_world_center[0], entrance_world_center[2]],
- atol=0.1))
- if is_entrance:
- # 入户门 - 红色实心圆(大)
- circle = Circle((door['x'], door['y']), radius=0.4,
- facecolor='red', edgecolor='darkred',
- linewidth=2)
- ax.add_patch(circle)
- # 添加标签
- label = f"ID:{door['door_id']} (Entrance)"
- ax.annotate(label, (door['x'], door['y']),
- textcoords="offset points", xytext=(0, 20),
- ha='center', color='red', fontsize=11, fontweight='bold')
- else:
- # 普通门 - 绿色实心圆(小)
- circle = Circle((door['x'], door['y']), radius=0.25,
- facecolor='green', edgecolor='darkgreen',
- linewidth=1.5)
- ax.add_patch(circle)
- # 添加标签
- label = f"ID:{door['door_id']}"
- ax.annotate(label, (door['x'], door['y']),
- textcoords="offset points", xytext=(0, -15),
- ha='center', color='green', fontsize=9)
- # 如果只有 world_position 而没有 entrance_door_id,绘制一个蓝色叉号标记
- if entrance_door_id is None and world_position is not None:
- ax.plot(world_position[0], world_position[1], 'bx', markersize=20,
- markeredgewidth=3, label='World Position (Estimated)')
- # 添加图例
- red_circle = plt.Line2D([], [], marker='o', markersize=18,
- markerfacecolor='red', markeredgewidth=2,
- markeredgecolor='darkred',
- label='Entrance Door')
- green_circle = plt.Line2D([], [], marker='o', markersize=14,
- markerfacecolor='green', markeredgewidth=1.5,
- markeredgecolor='darkgreen',
- label='Regular Door')
- ax.legend(handles=[red_circle, green_circle], loc='upper right', fontsize=11)
- # 设置标签和标题
- ax.set_xlabel('X (meter)', fontsize=12)
- ax.set_ylabel('Y (meter)', fontsize=12)
- ax.set_title(f'Scene: {scene_name}\nDoor Distribution (Red=Entrance, Green=Regular)', fontsize=14)
- ax.set_aspect('equal')
- ax.grid(True, alpha=0.3)
- # 添加信息框
- info_text = f"Total Doors: {len(doors)}\n"
- if entrance_door_id is not None:
- info_text += f"Entrance Door ID: {entrance_door_id}"
- elif world_position is not None:
- info_text += f"Entrance (from world_position)"
- else:
- info_text += "Entrance: Not specified"
- props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
- ax.text(0.02, 0.98, info_text, transform=ax.transAxes, fontsize=10,
- verticalalignment='top', bbox=props)
- plt.tight_layout()
- # 创建输出目录
- os.makedirs(os.path.dirname(output_path), exist_ok=True)
- plt.savefig(output_path, dpi=150, bbox_inches='tight')
- print(f" 可视化结果已保存:{output_path}")
- return fig, ax
- def process_all_scenes(
- entrance_json_base: str,
- output_base: str
- ):
- """
- 处理所有场景
- Args:
- entrance_json_base: entrance_json_output 基础目录
- output_base: 输出基础目录
- """
- json_files = list(Path(entrance_json_base).glob("*.json"))
- print(f"找到 {len(json_files)} 个场景文件")
- for json_file in json_files:
- scene_name = json_file.stem
- output_path = Path(output_base) / f"{scene_name}_doors.png"
- entrance_data = load_entrance_json(str(json_file))
- visualize_doors_2d(entrance_data, str(output_path))
- if __name__ == "__main__":
- import argparse
- parser = argparse.ArgumentParser(
- description="入户门 2D 可视化(俯视图)",
- formatter_class=argparse.RawDescriptionHelpFormatter,
- epilog="""
- 示例:
- # 处理单个场景
- python visualize_entrance_2d.py --scene KJ-dg21mhHcJLW
- # 处理所有场景
- python visualize_entrance_2d.py --all
- # 指定自定义路径
- python visualize_entrance_2d.py --all \\
- --json-base /path/to/entrance_json_output \\
- --output-base /path/to/output
- """
- )
- parser.add_argument("--scene", type=str, help="处理单个场景名称")
- parser.add_argument("--all", action="store_true", help="处理所有场景")
- # 路径参数
- parser.add_argument("--json-base", type=str,
- default="/home/gu/视频/door_detect/high_temp/ten_pano_demo/entrance_json_output",
- help="entrance_json_output 基础目录")
- parser.add_argument("--output-base", type=str,
- default="/home/gu/视频/door_detect/high_temp/ten_pano_demo/entrance_vis_2d",
- help="输出基础目录")
- args = parser.parse_args()
- if args.scene:
- # 处理单个场景
- json_file = Path(args.json_base) / f"{args.scene}.json"
- if not json_file.exists():
- print(f"❌ JSON 文件不存在:{json_file}")
- else:
- output_path = Path(args.output_base) / f"{args.scene}_doors.png"
- entrance_data = load_entrance_json(str(json_file))
- visualize_doors_2d(entrance_data, str(output_path))
- elif args.all:
- # 处理所有场景
- process_all_scenes(
- entrance_json_base=args.json_base,
- output_base=args.output_base
- )
- else:
- parser.print_help()
|