visualize_entrance_2d.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 入户门投影可视化
  5. 将所有检测到的门映射到 2D 俯视图上:
  6. - 绿色圆点:普通门(从 JSON 中的 doors 提取)
  7. - 红色圆点:入户门(entrance_door_id 对应的门 或 world_position)
  8. """
  9. import json
  10. import os
  11. import numpy as np
  12. import matplotlib.pyplot as plt
  13. from matplotlib.patches import Circle
  14. from pathlib import Path
  15. def load_entrance_json(json_path: str) -> dict:
  16. """加载入户门 JSON 文件"""
  17. with open(json_path, 'r', encoding='utf-8') as f:
  18. return json.load(f)
  19. def visualize_doors_2d(
  20. entrance_data: dict,
  21. output_path: str
  22. ):
  23. """
  24. 在 2D 俯视图上绘制所有门和入户门
  25. Args:
  26. entrance_data: 入户门 JSON 数据
  27. output_path: 输出图片路径
  28. """
  29. scene_name = entrance_data.get('scene_name', 'unknown')
  30. entrance_door_id = entrance_data.get('entrance_door_id')
  31. world_position = entrance_data.get('world_position')
  32. doors = entrance_data.get('doors', [])
  33. print(f"\n处理场景:{scene_name}")
  34. print(f" entrance_door_id: {entrance_door_id}")
  35. print(f" world_position: {world_position}")
  36. print(f" 门数量:{len(doors)}")
  37. # 确定入户门的世界坐标
  38. entrance_world_center = None
  39. entrance_door_obj = None
  40. if entrance_door_id is not None:
  41. # 从 doors 中找到对应的门
  42. for door in doors:
  43. if door.get('door_id') == entrance_door_id:
  44. entrance_world_center = door.get('world_center')
  45. entrance_door_obj = door
  46. print(f" 使用 entrance_door_id={entrance_door_id} 的世界坐标")
  47. break
  48. if entrance_world_center is None and world_position is not None:
  49. entrance_world_center = world_position
  50. print(f" 使用 world_position 作为入户门位置")
  51. if entrance_world_center is None:
  52. print(" ⚠️ 警告:无法确定入户门世界坐标,只绘制绿色门位置")
  53. # 提取所有门的中心坐标(2D 俯视,使用 X-Y 平面)
  54. # world_center = [x, y, z],其中 Y 是前后方向,X 是左右方向
  55. door_centers = []
  56. for door in doors:
  57. center = door.get('world_center')
  58. if center:
  59. door_centers.append({
  60. 'door_id': door.get('door_id'),
  61. 'x': center[0], # X 坐标(左右)
  62. 'y': center[1], # Y 坐标(前后)
  63. 'z': center[2], # Z 坐标(高度)
  64. 'is_entrance': door.get('door_id') == entrance_door_id,
  65. 'source_image': door.get('source_image'),
  66. 'confidence': door.get('confidence'),
  67. 'size': door.get('bounding_box_size', {})
  68. })
  69. if not door_centers:
  70. print(" ⚠️ 没有有效的门坐标")
  71. return
  72. # 创建图表
  73. fig, ax = plt.subplots(figsize=(14, 12))
  74. # 提取坐标
  75. xs = [p['x'] for p in door_centers]
  76. ys = [p['y'] for p in door_centers]
  77. # 计算坐标范围
  78. x_min, x_max = min(xs), max(xs)
  79. y_min, y_max = min(ys), max(ys)
  80. # 设置坐标范围(带边距)
  81. x_margin = max((x_max - x_min) * 0.15, 1.0)
  82. y_margin = max((y_max - y_min) * 0.15, 1.0)
  83. ax.set_xlim(x_min - x_margin, x_max + x_margin)
  84. ax.set_ylim(y_min - y_margin, y_max + y_margin)
  85. # 绘制所有门
  86. for door in door_centers:
  87. is_entrance = (door['door_id'] == entrance_door_id) or \
  88. (entrance_world_center and
  89. np.allclose([door['x'], door['z']],
  90. [entrance_world_center[0], entrance_world_center[2]],
  91. atol=0.1))
  92. if is_entrance:
  93. # 入户门 - 红色实心圆(大)
  94. circle = Circle((door['x'], door['y']), radius=0.4,
  95. facecolor='red', edgecolor='darkred',
  96. linewidth=2)
  97. ax.add_patch(circle)
  98. # 添加标签
  99. label = f"ID:{door['door_id']} (Entrance)"
  100. ax.annotate(label, (door['x'], door['y']),
  101. textcoords="offset points", xytext=(0, 20),
  102. ha='center', color='red', fontsize=11, fontweight='bold')
  103. else:
  104. # 普通门 - 绿色实心圆(小)
  105. circle = Circle((door['x'], door['y']), radius=0.25,
  106. facecolor='green', edgecolor='darkgreen',
  107. linewidth=1.5)
  108. ax.add_patch(circle)
  109. # 添加标签
  110. label = f"ID:{door['door_id']}"
  111. ax.annotate(label, (door['x'], door['y']),
  112. textcoords="offset points", xytext=(0, -15),
  113. ha='center', color='green', fontsize=9)
  114. # 如果只有 world_position 而没有 entrance_door_id,绘制一个蓝色叉号标记
  115. if entrance_door_id is None and world_position is not None:
  116. ax.plot(world_position[0], world_position[1], 'bx', markersize=20,
  117. markeredgewidth=3, label='World Position (Estimated)')
  118. # 添加图例
  119. red_circle = plt.Line2D([], [], marker='o', markersize=18,
  120. markerfacecolor='red', markeredgewidth=2,
  121. markeredgecolor='darkred',
  122. label='Entrance Door')
  123. green_circle = plt.Line2D([], [], marker='o', markersize=14,
  124. markerfacecolor='green', markeredgewidth=1.5,
  125. markeredgecolor='darkgreen',
  126. label='Regular Door')
  127. ax.legend(handles=[red_circle, green_circle], loc='upper right', fontsize=11)
  128. # 设置标签和标题
  129. ax.set_xlabel('X (meter)', fontsize=12)
  130. ax.set_ylabel('Y (meter)', fontsize=12)
  131. ax.set_title(f'Scene: {scene_name}\nDoor Distribution (Red=Entrance, Green=Regular)', fontsize=14)
  132. ax.set_aspect('equal')
  133. ax.grid(True, alpha=0.3)
  134. # 添加信息框
  135. info_text = f"Total Doors: {len(doors)}\n"
  136. if entrance_door_id is not None:
  137. info_text += f"Entrance Door ID: {entrance_door_id}"
  138. elif world_position is not None:
  139. info_text += f"Entrance (from world_position)"
  140. else:
  141. info_text += "Entrance: Not specified"
  142. props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
  143. ax.text(0.02, 0.98, info_text, transform=ax.transAxes, fontsize=10,
  144. verticalalignment='top', bbox=props)
  145. plt.tight_layout()
  146. # 创建输出目录
  147. os.makedirs(os.path.dirname(output_path), exist_ok=True)
  148. plt.savefig(output_path, dpi=150, bbox_inches='tight')
  149. print(f" 可视化结果已保存:{output_path}")
  150. return fig, ax
  151. def process_all_scenes(
  152. entrance_json_base: str,
  153. output_base: str
  154. ):
  155. """
  156. 处理所有场景
  157. Args:
  158. entrance_json_base: entrance_json_output 基础目录
  159. output_base: 输出基础目录
  160. """
  161. json_files = list(Path(entrance_json_base).glob("*.json"))
  162. print(f"找到 {len(json_files)} 个场景文件")
  163. for json_file in json_files:
  164. scene_name = json_file.stem
  165. output_path = Path(output_base) / f"{scene_name}_doors.png"
  166. entrance_data = load_entrance_json(str(json_file))
  167. visualize_doors_2d(entrance_data, str(output_path))
  168. if __name__ == "__main__":
  169. import argparse
  170. parser = argparse.ArgumentParser(
  171. description="入户门 2D 可视化(俯视图)",
  172. formatter_class=argparse.RawDescriptionHelpFormatter,
  173. epilog="""
  174. 示例:
  175. # 处理单个场景
  176. python visualize_entrance_2d.py --scene KJ-dg21mhHcJLW
  177. # 处理所有场景
  178. python visualize_entrance_2d.py --all
  179. # 指定自定义路径
  180. python visualize_entrance_2d.py --all \\
  181. --json-base /path/to/entrance_json_output \\
  182. --output-base /path/to/output
  183. """
  184. )
  185. parser.add_argument("--scene", type=str, help="处理单个场景名称")
  186. parser.add_argument("--all", action="store_true", help="处理所有场景")
  187. # 路径参数
  188. parser.add_argument("--json-base", type=str,
  189. default="/home/gu/视频/door_detect/high_temp/ten_pano_demo/entrance_json_output",
  190. help="entrance_json_output 基础目录")
  191. parser.add_argument("--output-base", type=str,
  192. default="/home/gu/视频/door_detect/high_temp/ten_pano_demo/entrance_vis_2d",
  193. help="输出基础目录")
  194. args = parser.parse_args()
  195. if args.scene:
  196. # 处理单个场景
  197. json_file = Path(args.json_base) / f"{args.scene}.json"
  198. if not json_file.exists():
  199. print(f"❌ JSON 文件不存在:{json_file}")
  200. else:
  201. output_path = Path(args.output_base) / f"{args.scene}_doors.png"
  202. entrance_data = load_entrance_json(str(json_file))
  203. visualize_doors_2d(entrance_data, str(output_path))
  204. elif args.all:
  205. # 处理所有场景
  206. process_all_scenes(
  207. entrance_json_base=args.json_base,
  208. output_base=args.output_base
  209. )
  210. else:
  211. parser.print_help()