3d点云映射到2d方法
方法
基于法向量与三个坐标系平面夹角的余弦值得到三张点云数据的映射
- 输入数据: 输入三维点云数据,每个点的坐标和法向量。
- 计算法向量余弦值: 对于每个点,计算其法向量与三个坐标系平面的夹角余弦值。
假设法向量为 N → = ( N x , N y , N z ) \overrightarrow{N}=(N_x,N_y,N_z) N=(Nx,Ny,Nz) ,对于三个坐标系平面,其余弦值计算如下:
- 以 X 轴为法向量:
c o s ( θ x ) = N x ∥ N ∥ cos(\theta_x)=\frac{N_x}{\|N\|} cos(θx)=∥N∥Nx - 以 Y 轴为法向量:
c o s ( θ y ) = N y ∥ N ∥ cos(\theta_y)=\frac{N_y}{\|N\|} cos(θy)=∥N∥Ny - 以 Z 轴为法向量:
c o s ( θ z ) = N z ∥ N ∥ cos(\theta_z)=\frac{N_z}{\|N\|} cos(θz)=∥N∥Nz
其中, ∥ N ∥ \|N\| ∥N∥表示法向量的模。
- 选择映射平面: 对于每个点,选择余弦值最大的坐标系平面作为映射平面。即,对于每个点,比较 c o s ( θ x ) cos(\theta_x) cos(θx), c o s ( θ y ) cos(\theta_y) cos(θy),和 c o s ( θ z ) cos(\theta_z) cos(θz),选择最大的那个对应的坐标系平面。
- 映射到平面: 将每个点投影到选择的映射平面上。这可以通过将点的坐标投影到相应的坐标系平面上来实现。如果选择了 X 轴为映射平面,那么投影后的坐标为 ( 0 , Y , Z ) (0,Y,Z) (0,Y,Z);选择 Y 轴为映射平面,则投影后的坐标为 ( X , 0 , Z ) (X,0,Z) (X,0,Z);选择 Z 轴为映射平面,则投影后的坐标为 ( X , Y , 0 ) (X,Y,0) (X,Y,0)。
- 生成映射平面图: 将所有点映射到平面上后,生成映射平面图,可以通过在二维空间上绘制点的坐标来实现
import open3d as o3d
import numpy as np
import cv2
def img_norm(img):
img_max = (img[img != 0]).max()
img_min = (img[img != 0]).min()
img_new = (img-img_min)*255.0/(img_max-img_min)
th = (0-img_min)*255.0/(img_max - img_min)
img_new[img_new==th] = 0
img_new = cv2.medianBlur(img_new.astype(np.float32), 3)
return img_new
def point_cloud_to_image(point_cloud_path, image_path):
# 读取点云数据
pcd = o3d.io.read_point_cloud(point_cloud_path)
points = np.asarray(pcd.points)
# 检查点云是否为空
if len(points) == 0:
print("错误:点云为空。")
return None
# y轴则为垂直深度方向,使用z轴作为y,
# 剔除 points[:, 1] 大于深度中值的点
# depth_threshold = np.median(points[:, 1])
# 找到x轴上最大值的点的索引
max_x_index = np.argmax(points[:, 0])
# 得到对应的y轴的值
corresponding_y_value = points[max_x_index, 1]
filtered_points = points[points[:, 1] <= corresponding_y_value]
print(filtered_points)
# 生成一个 Open3D 点云对象
filtered_point_cloud = o3d.geometry.PointCloud()
# 将 NumPy 数组转换为 Open3D 点云
filtered_point_cloud.points = o3d.utility.Vector3dVector(filtered_points)
# 估算法线
filtered_point_cloud.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=2, max_nn=50))
# 获取法线和点坐标
normals = np.asarray(filtered_point_cloud.normals)
print(normals)
# 计算每个点法向量与三维空间三个平面的夹角余弦值
cosines_x = normals[:, 0]
cosines_y = normals[:, 1]
cosines_z = normals[:, 2]
# 计算点云的深度信息
depth = filtered_points[:, 1]
# normalized_depth = (depth - np.min(depth)) / (np.max(depth) - np.min(depth))
# # 将深度信息映射到[0, 255]
# depth_channel = (normalized_depth * 255).astype(np.uint8)
# 获取点云的范围
min_x, max_x = np.min(filtered_points[:, 0]), np.max(filtered_points[:, 0])
min_z, max_z = np.min(filtered_points[:, 2]), np.max(filtered_points[:, 2])
print(min_x, max_x)
print(min_z, max_z)
print(np.min(filtered_points[:, 1]), np.max(filtered_points[:, 1]))
# 计算图像尺寸
image_width = int(max_x - min_x) + 1
image_height = int(max_z - min_z) + 1
print(image_width)
print(image_height)
# 创建图像
depth_image = np.zeros((image_height, image_width))
x_cosin_img = np.zeros((image_height, image_width))
y_cosin_img = np.zeros((image_height, image_width))
z_cosin_img = np.zeros((image_height, image_width))
rst_img = np.zeros((image_height, image_width, 3))
print(depth_image.shape)
# 将深度信息填充到深度图像中
for x, y, z, x_ar, y_ar, z_ar in zip(filtered_points[:, 0], filtered_points[:, 1], filtered_points[:, 2], cosines_x, cosines_y, cosines_z):
depth_image[int(z-min_z), int(x-min_x)] = y
x_cosin_img[int(z-min_z), int(x-min_x)] = x_ar
y_cosin_img[int(z-min_z), int(x-min_x)] = y_ar
z_cosin_img[int(z-min_z), int(x-min_x)] = z_ar
# print("x_cosin_img:",x_cosin_img)
# print("x_cosin_img:",x_cosin_img.max())
depth_image = img_norm(depth_image)
x_cosin_img = img_norm(x_cosin_img)
y_cosin_img = img_norm(y_cosin_img)
z_cosin_img = img_norm(z_cosin_img)
# 保存深度图像
cv2.imwrite('depth_image.jpg', depth_image)
cv2.imwrite('x_cosin_img.jpg', x_cosin_img)
cv2.imwrite('y_cosin_img.jpg', y_cosin_img)
cv2.imwrite('z_cosin_img.jpg', z_cosin_img)
rst_img[:,:,0] = depth_image
rst_img[:,:,1] = x_cosin_img
rst_img[:,:,2] = z_cosin_img
cv2.imwrite("rst.jpg", rst_img)
# return depth_image
# 示例用法
point_cloud_to_image(skin_path, 'depth_image.jpg')