196 lines
6.9 KiB
Python
196 lines
6.9 KiB
Python
"""可视化工具 - 用于显示点云、平面、ROI等"""
|
||
import numpy as np
|
||
import open3d as o3d
|
||
from pathlib import Path
|
||
from typing import List, Tuple, Optional
|
||
|
||
|
||
class Visualizer:
|
||
"""点云和测量结果可视化工具"""
|
||
|
||
@staticmethod
|
||
def visualize_roi_in_context(
|
||
full_cloud: np.ndarray,
|
||
roi_cloud: np.ndarray,
|
||
bbox_min: List[float],
|
||
bbox_max: List[float],
|
||
title: str = "ROI in Context",
|
||
downsample_full: int = 10,
|
||
save_path: Optional[str] = None
|
||
):
|
||
"""
|
||
在完整点云上下文中可视化ROI区域
|
||
|
||
Args:
|
||
full_cloud: 完整点云 (N, 3)
|
||
roi_cloud: ROI区域点云 (M, 3)
|
||
bbox_min: 包围盒最小坐标 [x, y, z]
|
||
bbox_max: 包围盒最大坐标 [x, y, z]
|
||
title: 窗口标题
|
||
downsample_full: 完整点云降采样倍数(每N个点取1个)
|
||
save_path: 保存截图路径(可选)
|
||
"""
|
||
# 降采样完整点云以提高显示性能
|
||
full_cloud_downsampled = full_cloud[::downsample_full]
|
||
|
||
# 创建完整点云对象(灰色)
|
||
pcd_full = o3d.geometry.PointCloud()
|
||
pcd_full.points = o3d.utility.Vector3dVector(full_cloud_downsampled)
|
||
pcd_full.paint_uniform_color([0.7, 0.7, 0.7]) # 灰色
|
||
|
||
# 创建ROI点云对象(红色)
|
||
pcd_roi = o3d.geometry.PointCloud()
|
||
pcd_roi.points = o3d.utility.Vector3dVector(roi_cloud)
|
||
pcd_roi.paint_uniform_color([1.0, 0.0, 0.0]) # 红色
|
||
|
||
# 创建包围盒线框
|
||
bbox = Visualizer._create_bbox_lineset(bbox_min, bbox_max, color=[0, 1, 0])
|
||
|
||
# 可视化
|
||
geometries = [pcd_full, pcd_roi, bbox]
|
||
o3d.visualization.draw_geometries(
|
||
geometries,
|
||
window_name=title,
|
||
width=1200,
|
||
height=800,
|
||
point_show_normal=False
|
||
)
|
||
|
||
# 保存截图(如果指定)
|
||
if save_path:
|
||
vis = o3d.visualization.Visualizer()
|
||
vis.create_window(window_name=title, width=1200, height=800, visible=False)
|
||
for geom in geometries:
|
||
vis.add_geometry(geom)
|
||
vis.update_renderer()
|
||
vis.capture_screen_image(save_path)
|
||
vis.destroy_window()
|
||
print(f"截图已保存: {save_path}")
|
||
|
||
@staticmethod
|
||
def visualize_plane_measurement(
|
||
source_cloud: np.ndarray,
|
||
target_cloud: np.ndarray,
|
||
source_plane_normal: np.ndarray,
|
||
source_plane_point: np.ndarray,
|
||
target_plane_normal: np.ndarray,
|
||
target_plane_point: np.ndarray,
|
||
sample_points: np.ndarray,
|
||
distances: np.ndarray,
|
||
title: str = "Plane Measurement",
|
||
save_path: Optional[str] = None
|
||
):
|
||
"""
|
||
可视化平面到平面的距离测量
|
||
|
||
Args:
|
||
source_cloud: 源平面点云
|
||
target_cloud: 目标平面点云
|
||
source_plane_normal: 源平面法向量
|
||
source_plane_point: 源平面上的点
|
||
target_plane_normal: 目标平面法向量
|
||
target_plane_point: 目标平面上的点
|
||
sample_points: 采样点
|
||
distances: 每个采样点到目标平面的距离
|
||
title: 窗口标题
|
||
save_path: 保存截图路径
|
||
"""
|
||
# 创建源平面点云(蓝色)
|
||
pcd_source = o3d.geometry.PointCloud()
|
||
pcd_source.points = o3d.utility.Vector3dVector(source_cloud)
|
||
pcd_source.paint_uniform_color([0.0, 0.5, 1.0]) # 蓝色
|
||
|
||
# 创建目标平面点云(绿色)
|
||
pcd_target = o3d.geometry.PointCloud()
|
||
pcd_target.points = o3d.utility.Vector3dVector(target_cloud)
|
||
pcd_target.paint_uniform_color([0.0, 1.0, 0.0]) # 绿色
|
||
|
||
# 创建采样点(红色)
|
||
pcd_samples = o3d.geometry.PointCloud()
|
||
pcd_samples.points = o3d.utility.Vector3dVector(sample_points)
|
||
pcd_samples.paint_uniform_color([1.0, 0.0, 0.0]) # 红色
|
||
|
||
# 创建距离线(从采样点到目标平面的投影)
|
||
lines = []
|
||
for i, (point, dist) in enumerate(zip(sample_points, distances)):
|
||
# 计算投影点
|
||
projection = point - dist * target_plane_normal
|
||
lines.append([point, projection])
|
||
|
||
# 创建线集
|
||
line_points = []
|
||
line_indices = []
|
||
for i, (start, end) in enumerate(lines):
|
||
line_points.append(start)
|
||
line_points.append(end)
|
||
line_indices.append([2*i, 2*i+1])
|
||
|
||
lineset = o3d.geometry.LineSet()
|
||
lineset.points = o3d.utility.Vector3dVector(line_points)
|
||
lineset.lines = o3d.utility.Vector2iVector(line_indices)
|
||
lineset.paint_uniform_color([1.0, 0.5, 0.0]) # 橙色
|
||
|
||
# 可视化
|
||
geometries = [pcd_source, pcd_target, pcd_samples, lineset]
|
||
o3d.visualization.draw_geometries(
|
||
geometries,
|
||
window_name=title,
|
||
width=1200,
|
||
height=800,
|
||
point_show_normal=False
|
||
)
|
||
|
||
if save_path:
|
||
vis = o3d.visualization.Visualizer()
|
||
vis.create_window(window_name=title, width=1200, height=800, visible=False)
|
||
for geom in geometries:
|
||
vis.add_geometry(geom)
|
||
vis.update_renderer()
|
||
vis.capture_screen_image(save_path)
|
||
vis.destroy_window()
|
||
print(f"截图已保存: {save_path}")
|
||
|
||
@staticmethod
|
||
def _create_bbox_lineset(
|
||
bbox_min: List[float],
|
||
bbox_max: List[float],
|
||
color: List[float] = [0, 1, 0]
|
||
) -> o3d.geometry.LineSet:
|
||
"""创建包围盒线框"""
|
||
# 8个顶点
|
||
points = [
|
||
[bbox_min[0], bbox_min[1], bbox_min[2]], # 0
|
||
[bbox_max[0], bbox_min[1], bbox_min[2]], # 1
|
||
[bbox_max[0], bbox_max[1], bbox_min[2]], # 2
|
||
[bbox_min[0], bbox_max[1], bbox_min[2]], # 3
|
||
[bbox_min[0], bbox_min[1], bbox_max[2]], # 4
|
||
[bbox_max[0], bbox_min[1], bbox_max[2]], # 5
|
||
[bbox_max[0], bbox_max[1], bbox_max[2]], # 6
|
||
[bbox_min[0], bbox_max[1], bbox_max[2]], # 7
|
||
]
|
||
|
||
# 12条边
|
||
lines = [
|
||
[0, 1], [1, 2], [2, 3], [3, 0], # 底面
|
||
[4, 5], [5, 6], [6, 7], [7, 4], # 顶面
|
||
[0, 4], [1, 5], [2, 6], [3, 7], # 竖边
|
||
]
|
||
|
||
lineset = o3d.geometry.LineSet()
|
||
lineset.points = o3d.utility.Vector3dVector(points)
|
||
lineset.lines = o3d.utility.Vector2iVector(lines)
|
||
lineset.paint_uniform_color(color)
|
||
|
||
return lineset
|
||
|
||
@staticmethod
|
||
def save_roi_pointcloud(
|
||
roi_cloud: np.ndarray,
|
||
save_path: str
|
||
):
|
||
"""保存ROI点云为PLY文件"""
|
||
pcd = o3d.geometry.PointCloud()
|
||
pcd.points = o3d.utility.Vector3dVector(roi_cloud)
|
||
o3d.io.write_point_cloud(save_path, pcd)
|
||
print(f"ROI点云已保存: {save_path}")
|