Files
massive-cloud/alg/point_porj/archive/generate_shifted_train_pointcloud.py

447 lines
12 KiB
Python

import os
import argparse
import numpy as np
import open3d as o3d
from OCC.Core.STEPControl import STEPControl_Reader
from OCC.Core.IFSelect import IFSelect_RetDone
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
from OCC.Core.TopExp import TopExp_Explorer
from OCC.Core.TopAbs import TopAbs_FACE
from OCC.Core.BRep import BRep_Tool
try:
from OCC.Core.TopoDS import topods
USE_TOPODS = "topods"
except ImportError:
from OCC.Core.TopoDS import TopoDS
USE_TOPODS = "TopoDS"
# ============================================================
# STEP -> Open3D Mesh
# ============================================================
def read_step_file(step_path):
reader = STEPControl_Reader()
status = reader.ReadFile(step_path)
if status != IFSelect_RetDone:
raise RuntimeError(f"STEP 文件读取失败: {step_path}")
reader.TransferRoots()
shape = reader.OneShape()
print("[OK] STEP loaded:", step_path)
return shape
def occ_shape_to_numpy_mesh(shape):
vertices = []
triangles = []
explorer = TopExp_Explorer(shape, TopAbs_FACE)
face_count = 0
tri_count = 0
while explorer.More():
current_shape = explorer.Current()
if USE_TOPODS == "topods":
face = topods.Face(current_shape)
else:
face = TopoDS.Face(current_shape)
location = face.Location()
triangulation = BRep_Tool.Triangulation(face, location)
if triangulation is None:
explorer.Next()
continue
trsf = location.Transformation()
base_index = len(vertices)
for i in range(1, triangulation.NbNodes() + 1):
pnt = triangulation.Node(i)
pnt.Transform(trsf)
vertices.append([
pnt.X(),
pnt.Y(),
pnt.Z()
])
for i in range(1, triangulation.NbTriangles() + 1):
tri = triangulation.Triangle(i)
n1, n2, n3 = tri.Get()
triangles.append([
base_index + n1 - 1,
base_index + n2 - 1,
base_index + n3 - 1
])
tri_count += 1
face_count += 1
explorer.Next()
vertices = np.asarray(vertices, dtype=np.float64)
triangles = np.asarray(triangles, dtype=np.int32)
print("[OK] CAD mesh extracted")
print(" CAD faces:", face_count)
print(" mesh vertices:", vertices.shape)
print(" mesh triangles:", triangles.shape)
if vertices.shape[0] == 0 or triangles.shape[0] == 0:
raise RuntimeError("CAD 三角网格提取失败")
return vertices, triangles
def step_to_open3d_mesh(step_path, mesh_deflection=0.1):
shape = read_step_file(step_path)
print("[INFO] Meshing CAD...")
mesh_algo = BRepMesh_IncrementalMesh(shape, mesh_deflection)
mesh_algo.Perform()
vertices, triangles = occ_shape_to_numpy_mesh(shape)
mesh = o3d.geometry.TriangleMesh()
mesh.vertices = o3d.utility.Vector3dVector(vertices)
mesh.triangles = o3d.utility.Vector3iVector(triangles)
mesh.remove_duplicated_vertices()
mesh.remove_duplicated_triangles()
mesh.remove_degenerate_triangles()
mesh.compute_vertex_normals()
return mesh
# ============================================================
# 点云处理
# ============================================================
def make_translation_matrix(dx, dy, dz):
T = np.eye(4, dtype=np.float64)
T[0, 3] = dx
T[1, 3] = dy
T[2, 3] = dz
return T
def crop_front_tail_by_x(pcd, ratio=0.08):
"""
按 X 轴裁剪车头和车尾。
front: X 最小端
tail: X 最大端
"""
points = np.asarray(pcd.points)
min_bound = points.min(axis=0)
max_bound = points.max(axis=0)
x_min = min_bound[0]
x_max = max_bound[0]
x_len = x_max - x_min
front_min = min_bound.copy()
front_max = max_bound.copy()
front_min[0] = x_min
front_max[0] = x_min + ratio * x_len
tail_min = min_bound.copy()
tail_max = max_bound.copy()
tail_min[0] = x_max - ratio * x_len
tail_max[0] = x_max
front_box = o3d.geometry.AxisAlignedBoundingBox(
min_bound=front_min,
max_bound=front_max
)
tail_box = o3d.geometry.AxisAlignedBoundingBox(
min_bound=tail_min,
max_bound=tail_max
)
front_pcd = pcd.crop(front_box)
tail_pcd = pcd.crop(tail_box)
print("[OK] Crop front/tail by X")
print(" X range:", x_min, x_max)
print(" front points:", np.asarray(front_pcd.points).shape)
print(" tail points:", np.asarray(tail_pcd.points).shape)
return front_pcd, tail_pcd
def add_gaussian_noise(pcd, noise_std=0.0):
"""
给点云添加高斯噪声。
noise_std=0 表示不加噪声。
"""
if noise_std <= 0:
return pcd
points = np.asarray(pcd.points)
noise = np.random.normal(
loc=0.0,
scale=noise_std,
size=points.shape
)
noisy_points = points + noise
noisy_pcd = o3d.geometry.PointCloud()
noisy_pcd.points = o3d.utility.Vector3dVector(noisy_points)
if pcd.has_colors():
noisy_pcd.colors = pcd.colors
if pcd.has_normals():
noisy_pcd.normals = pcd.normals
return noisy_pcd
def visualize_pair(cad_pcd, shifted_pcd):
cad_vis = o3d.geometry.PointCloud(cad_pcd)
shifted_vis = o3d.geometry.PointCloud(shifted_pcd)
cad_vis.paint_uniform_color([0.0, 0.5, 1.0])
shifted_vis.paint_uniform_color([1.0, 0.0, 0.0])
o3d.visualization.draw_geometries(
[cad_vis, shifted_vis],
window_name="Blue = CAD sampled point cloud, Red = shifted test point cloud"
)
# ============================================================
# 主函数
# ============================================================
def generate_shifted_train_pointcloud(
cad_path,
output_dir,
sample_points=1000000,
mesh_deflection=0.1,
crop_ratio=0.08,
dx=100.0,
dy=20.0,
dz=-10.0,
noise_std=0.0,
visualize=True
):
os.makedirs(output_dir, exist_ok=True)
# 1. CAD -> mesh
cad_mesh = step_to_open3d_mesh(
cad_path,
mesh_deflection=mesh_deflection
)
# 2. mesh -> 点云
print(f"[INFO] Sampling CAD surface: {sample_points} points")
cad_pcd = cad_mesh.sample_points_uniformly(
number_of_points=sample_points
)
cad_pcd_path = os.path.join(output_dir, "cad_sampled_whole.ply")
o3d.io.write_point_cloud(cad_pcd_path, cad_pcd)
print("[OK] Saved:", cad_pcd_path)
# 3. 裁剪原始 CAD 点云的车头 / 车尾
cad_front, cad_tail = crop_front_tail_by_x(
cad_pcd,
ratio=crop_ratio
)
o3d.io.write_point_cloud(
os.path.join(output_dir, "cad_front.ply"),
cad_front
)
o3d.io.write_point_cloud(
os.path.join(output_dir, "cad_tail.ply"),
cad_tail
)
# 4. 生成固定平移矩阵
T = make_translation_matrix(dx, dy, dz)
print("\n========== Fixed Translation ==========")
print("dx:", dx)
print("dy:", dy)
print("dz:", dz)
print("T:")
print(T)
# 5. 对整车点云施加固定平移
shifted_pcd = o3d.geometry.PointCloud(cad_pcd)
shifted_pcd.transform(T)
# 6. 可选:加噪声,模拟真实扫描点云
shifted_pcd = add_gaussian_noise(
shifted_pcd,
noise_std=noise_std
)
shifted_path = os.path.join(output_dir, "shifted_whole_pointcloud.ply")
o3d.io.write_point_cloud(shifted_path, shifted_pcd)
print("[OK] Saved:", shifted_path)
# 7. 裁剪平移后的车头 / 车尾
shifted_front, shifted_tail = crop_front_tail_by_x(
shifted_pcd,
ratio=crop_ratio
)
shifted_front_path = os.path.join(output_dir, "shifted_front.ply")
shifted_tail_path = os.path.join(output_dir, "shifted_tail.ply")
o3d.io.write_point_cloud(shifted_front_path, shifted_front)
o3d.io.write_point_cloud(shifted_tail_path, shifted_tail)
print("[OK] Saved:", shifted_front_path)
print("[OK] Saved:", shifted_tail_path)
# 8. 保存真实位移矩阵
matrix_path = os.path.join(output_dir, "GT_translation_matrix.txt")
np.savetxt(matrix_path, T, fmt="%.10f")
print("[OK] Saved:", matrix_path)
# 9. 保存真实位移向量
translation_path = os.path.join(output_dir, "GT_translation_vector.txt")
np.savetxt(
translation_path,
np.array([dx, dy, dz], dtype=np.float64),
fmt="%.10f"
)
print("[OK] Saved:", translation_path)
# 10. 可视化
if visualize:
# 百万级点云显示会卡,所以这里可视化时降采样
cad_vis = cad_pcd.voxel_down_sample(voxel_size=20.0)
shifted_vis = shifted_pcd.voxel_down_sample(voxel_size=20.0)
visualize_pair(cad_vis, shifted_vis)
return {
"cad_sampled_path": cad_pcd_path,
"shifted_pointcloud_path": shifted_path,
"shifted_front_path": shifted_front_path,
"shifted_tail_path": shifted_tail_path,
"translation_matrix_path": matrix_path,
"translation_vector": np.array([dx, dy, dz], dtype=np.float64)
}
# ============================================================
# 命令行入口
# ============================================================
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="从整车 CAD 生成测试点云,并施加固定前后/整体位移"
)
parser.add_argument(
"--cad",
default=r"datasets/CAD/M02车.stp",
help="整车 CAD STEP/STP 文件路径"
)
parser.add_argument(
"--output_dir",
default=r"datasets\shift_test_output",
help="输出目录"
)
parser.add_argument(
"--sample_points",
type=int,
default=1000000,
help="从 CAD 表面采样的点数,默认 1000000"
)
parser.add_argument(
"--mesh_deflection",
type=float,
default=0.1,
help="CAD 三角化精度,越小越精细,默认 0.1"
)
parser.add_argument(
"--crop_ratio",
type=float,
default=0.08,
help="车头/车尾裁剪比例,默认 0.08"
)
parser.add_argument(
"--dx",
type=float,
default=100.0,
help="X 方向固定平移量"
)
parser.add_argument(
"--dy",
type=float,
default=20.0,
help="Y 方向固定平移量"
)
parser.add_argument(
"--dz",
type=float,
default=-10.0,
help="Z 方向固定平移量"
)
parser.add_argument(
"--noise_std",
type=float,
default=0.0,
help="高斯噪声标准差,默认 0 表示不加噪声"
)
parser.add_argument(
"--no_visualize",
action="store_true",
help="不显示可视化窗口"
)
args = parser.parse_args()
result = generate_shifted_train_pointcloud(
cad_path=args.cad,
output_dir=args.output_dir,
sample_points=args.sample_points,
mesh_deflection=args.mesh_deflection,
crop_ratio=args.crop_ratio,
dx=args.dx,
dy=args.dy,
dz=args.dz,
noise_std=args.noise_std,
visualize=not args.no_visualize
)
print("\n========== Result ==========")
print("CAD sampled point cloud:", result["cad_sampled_path"])
print("Shifted whole point cloud:", result["shifted_pointcloud_path"])
print("Shifted front point cloud:", result["shifted_front_path"])
print("Shifted tail point cloud:", result["shifted_tail_path"])
print("GT translation vector:", result["translation_vector"])
print("GT matrix:", result["translation_matrix_path"])