You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
8.5 KiB
Python

8 months ago
import numpy as np
from PIL import Image
import math
import config
from cat import clip_and_rotate_point_cloud, merge_point_clouds
from image import detect_large_holes
def tiff_depth_to_point_clouds(tiff_paths, sn, dedup=True):
"""
将多个 TIFF 深度图转换为点云并拼接在一起后生成 PCD 文件
:param tiff_paths: list of str, TIFF 深度图路径列表
:param sn: str, 设备序列号用于查找配置参数
:param dedup: bool, 是否根据 (x, y) 去重默认开启
:return: list of [x, y, z], 合并后的点云数据
"""
merged_points = []
# Step 1: 遍历每个 TIFF 文件,生成对应的点云
for tiff_path in tiff_paths:
points = tiff_depth_to_point_cloud(tiff_path+".tiff", sn, dedup)
merged_points.extend([points])
# Step 2: 可选:使用 merge_point_clouds 对点云进行 x 轴拼接(若需要)
# 注意:如果每个点云已经带有偏移,则不需要再次拼接
has_voids = True
if hasattr(config, 'CUT_CONFIG_MAP') and isinstance(config.CUT_CONFIG_MAP, dict) and (sn in config.CUT_CONFIG_MAP):
merged_points, xrange = merge_point_clouds(merged_points, config.CUT_CONFIG_MAP[sn],sn)
real_holes = detect_large_holes(merged_points, sn, xrange)
if real_holes.__len__()==0:
has_voids=False
# Step 3: 写入最终的 PCD 文件
output_pcd_path = config.save_path("pcd", f"{sn}_merged.pcd")
write_pcd(output_pcd_path, merged_points)
# print(f"Merged and saved to {output_pcd_path}")
return has_voids
def tiff_depth_to_point_cloud(tiff_path,sn, dedup=True):
"""
TIFF 深度图转换为点云基于给定的视场角计算相机内参
:param tiff_path: str, TIFF 深度图路径
:param hfov_degrees: float or None, 水平视场角单位
:param vfov_degrees: float or None, 垂直视场角单位
:param output_ply_path: str or None, 输出 Pcd 文件路径可选
:param dedup: bool, 是否根据 (x, y) 去重默认开启
:return: list of [x, y, z] 点云数据
"""
hfov_degrees = config.CAMERA_CONFIG_MAP[sn].get("x_angle")
max_z = config.CAMERA_CONFIG_MAP[sn].get("max_z")
vfov_degrees = config.CAMERA_CONFIG_MAP[sn].get("y_angle")
# plane_scaling_ratio = config.CAMERA_CONFIG_MAP[sn].get("plane_scaling_ratio")
# 加载 TIFF 图像
depth_image = Image.open(tiff_path)
depth_array = np.array(depth_image, dtype=np.float32)
height, width = depth_array.shape
# 根据提供的 FOV 计算 fx/fy
if hfov_degrees is not None:
hfov_rad = math.radians(hfov_degrees)
fx = width / (2 * math.tan(hfov_rad / 2))
fy = fx * height / width # 保持像素方形比例
else:
vfov_rad = math.radians(vfov_degrees)
fy = height / (2 * math.tan(vfov_rad / 2))
fx = fy * width / height # 保持像素方形比例
cx = width / 2
cy = height / 2
print(f"Calculated intrinsics: fx={fx:.2f}, fy={fy:.2f}, cx={cx:.2f}, cy={cy:.2f}")
points = []
seen_xy = set() # 用于记录已添加的 (x, y)
# 获取裁剪配置
clip_config = config.CUT_CONFIG_MAP.get(sn)
if clip_config:
min_pt = np.array(clip_config.get("min_pt", [-np.inf] * 3))
max_pt = np.array(clip_config.get("max_pt", [np.inf] * 3))
rotation = np.array(clip_config.get("rotation", [1,0,0,0,1,0,0,0,1])).reshape(3, 3)
else:
min_pt = max_pt = rotation = None
for v in range(height):
for u in range(width):
z = depth_array[v, u] # mm -> m
if z <= 0 or z > max_z:
continue
x = (u - cx) * z / fx
y = (v - cy) * z / fy
# 构造点并旋转(保留浮点精度)
point = np.array([x, y, z])
if clip_config:
rotated_point = (rotation @ point.T).T
if not np.all(rotated_point >= min_pt) or not np.all(rotated_point <= max_pt):
continue
x_final, y_final, z_final = rotated_point
else:
x_final, y_final, z_final = point
# 到这里才进行去重判断
if dedup:
x_int = int(round(x_final))
y_int = int(round(y_final))
z_int = int(round(z_final))
if (x_int, y_int) in seen_xy:
continue
seen_xy.add((x_int, y_int))
# 保留浮点精度写入结果
points.append([x_int, y_int, z_int])
# 可选输出PLY文件用于可视化
if config.CAMERA_CONFIG_MAP[sn].get("save_pcd"):
output_ply_path = config.save_path("pcd",sn+".pcd")
write_pcd(output_ply_path, points)
return points
# 新增点云数据转换函数
def convert_sdk_points(sValue, width, height):
"""
SDK 原始点云数据转换为 NumPy 点云数组
:param sValue: tuple(float), SDK 输出的扁平化点云数据
:param width: int, 点云宽度
:param height: int, 点云高度
:return: np.ndarray(shape=(N,3)), 三维点云数组
"""
points = np.array(sValue).reshape(-1, 3) # 形状转换 (N,3)
# 剔除无效点X/Y/Z 任一为 NaN 或 Inf
mask = np.all(np.isfinite(points), axis=1)
return points[mask]
def process_point_cloud(points, sn, dedup=True):
"""
对原始点云应用旋转裁剪和去重
:param points: np.ndarray(shape=(N,3)), 原始点云数据
:param sn: str, 设备序列号用于加载配置
:param dedup: bool, 是否启用去重
:return: list of [x, y, z], 处理后的点云列表
"""
# 加载配置参数
clip_config = config.CUT_CONFIG_MAP.get(sn)
if clip_config:
min_pt = np.array(clip_config.get("min_pt", [-np.inf] * 3))
max_pt = np.array(clip_config.get("max_pt", [np.inf] * 3))
rotation = np.array(clip_config.get("rotation", [1, 0, 0, 0, 1, 0, 0, 0, 1])).reshape(3, 3)
else:
min_pt = max_pt = rotation = None
processed_points = []
seen_xy = set()
for point in points:
x, y, z = point
# 无效点过滤Z ≤ 0 或超出最大距离)
if z <= 0 or z > config.CAMERA_CONFIG_MAP[sn].get("max_z", np.inf):
continue
# 应用旋转矩阵
if clip_config:
rotated = rotation @ point
if not np.all(rotated >= min_pt) or not np.all(rotated <= max_pt):
continue
x_final, y_final, z_final = rotated
else:
x_final, y_final, z_final = x, y, z
# 去重逻辑(保留浮点精度)
if dedup:
# 使用浮点哈希避免离散化损失
key = (round(x_final, 3), round(y_final, 3))
if key in seen_xy:
continue
seen_xy.add(key)
processed_points.append([x_final, y_final, z_final])
return processed_points
def sValue_to_pcd(sValue,stPointCloudImage,sn):
# 数据格式转换
points = convert_sdk_points(sValue, stPointCloudImage.nWidth, stPointCloudImage.nHeight)
# 应用旋转、裁剪、去重(假设设备序列号已知)
processed_points = process_point_cloud(points, sn, dedup=True)
output_ply_path = config.save_path("pcd", sn + ".pcd")
# 保存结果
write_pcd(output_ply_path, processed_points)
8 months ago
def write_pcd(filename, points):
"""
将点云写入 PCD 文件
:param filename: str, 输出 PCD 文件路径
:param points: list of [x, y, z], 点云数据
"""
with open(filename, 'w') as f:
# 写入 PCD 文件头
f.write("# .PCD v0.7 - Point Cloud Data file format\n")
f.write(f"VERSION 0.7\n")
f.write("FIELDS x y z\n")
f.write("SIZE 4 4 4\n") # float 类型的大小是 4 字节
f.write("TYPE F F F\n") # 每个字段的数据类型
f.write(f"COUNT 1 1 1\n") # 每个字段的数量
f.write(f"WIDTH {len(points)}\n") # 点数量
f.write("HEIGHT 1\n") # 单行表示无结构点云
f.write("VIEWPOINT 0 0 0 1 0 0 0\n") # 默认视角参数
f.write(f"POINTS {len(points)}\n") # 总点数
f.write("DATA ascii\n") # 数据部分以 ASCII 形式存储
# 写入点数据
for point in points:
f.write(f"{point[0]} {point[1]} {point[2]}\n")
print(f"Saved point cloud to {filename}")
if __name__ == '__main__':
tiff_depth_to_point_cloud("D:/git/test/hik3d-python/image/2025-06-26/depth/191330147_-Depth.tiff", "00DA6823936")