2. 从深度图像生成点云
任务目标
本案例将使用模拟的深度相机内参,把一张640×480的深度图像(每个像素存储毫米单位的深度值)转换为相机坐标系下的三维点云,并以XYZ格式保存,同时输出点云数量与有效点比例用于验证。
环境与依赖
需要 numpy 和 Pillow(用于读取PNG深度图)。若需可视化,可选用 open3d 或 matplotlib,但本案例核心转换不依赖它们。
实现原理
根据针孔相机模型,给定焦距 fx, fy 和光心 cx, cy,深度图中像素 (u, v) 处深度值 d(单位:米)对应的三维坐标为:
x = (u - cx) * d / fx
y = (v - cy) * d / fy
z = d
深度值为0的像素视为无效测量,直接剔除。
关键完整代码
import numpy as np
from PIL import Image
def depth_to_pointcloud(depth_image_path, fx, fy, cx, cy, depth_scale=0.001):
"""
将深度图像(16bit PNG, 单位mm)转换为三维点云。
返回: points (N,3) float32, 单位米; valid_ratio (float)
"""
depth = np.array(Image.open(depth_image_path), dtype=np.float32)
h, w = depth.shape
# 生成像素坐标网格
v, u = np.mgrid[0:h, 0:w].astype(np.float32)
# 深度单位转换: mm -> m
d = depth * depth_scale
# 有效掩码: 深度值 > 0
mask = d > 0
# 反投影
x = (u - cx) * d / fx
y = (v - cy) * d / fy
z = d
# 拼接并过滤
points = np.stack((x, y, z), axis=-1)[mask]
valid_ratio = mask.sum() / (h * w)
return points, valid_ratio
# 模拟深度相机参数 (640x480, 水平视场角约60°)
fx = fy = 525.0
cx, cy = 319.5, 239.5
# 生成一张模拟深度图用于演示 (平面场景: 距离2米处的平面)
h, w = 480, 640
depth_mm = np.full((h, w), 2000, dtype=np.uint16) # 2000mm = 2m
# 添加一个矩形凸起区域 (距离1.5米)
depth_mm[150:330, 200:440] = 1500
# 添加一个圆形空洞 (无效区域)
yy, xx = np.mgrid[0:h, 0:w]
circle_mask = (xx - 400)**2 + (yy - 100)**2 < 50**2
depth_mm[circle_mask] = 0
Image.fromarray(depth_mm).save('sim_depth.png')
# 执行转换
points, ratio = depth_to_pointcloud('sim_depth.png', fx, fy, cx, cy)
print(f"有效点比例: {ratio:.2%}")
print(f"点云点数: {len(points)}")
# 保存为XYZ格式 (每行: x y z)
np.savetxt('pointcloud.xyz', points, fmt='%.3f')
print("点云已保存至 pointcloud.xyz")
运行与验证
- 将上述代码保存为
depth2pcd.py,在终端运行python depth2pcd.py。 - 程序会生成模拟深度图
sim_depth.png,并输出点云文件pointcloud.xyz。 - 验证方法:检查输出的有效点比例。模拟图中总像素为 640×480=307200,其中圆形空洞面积约 π×50²≈7854 像素,因此有效点比例应约为 (307200-7854)/307200 ≈ 97.44%。点云数量应约为 299346。
- 打开
pointcloud.xyz,前几行应类似0.000 0.000 2.000(左上角像素),且所有点的z值只应为 1.500 或 2.000(对应两个深度平面)。
可视化(可选)
若已安装 open3d,可运行以下代码快速查看点云形状:
import open3d as o3d
import numpy as np
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(np.loadtxt('pointcloud.xyz'))
o3d.visualization.draw_geometries([pcd])
预期可看到两个平行平面(2m处的大平面和1.5m处的矩形凸起),且圆形区域无点。
常见问题
- 深度图单位:本案例假设PNG为16bit且单位是毫米。若深度图单位是米,需将
depth_scale改为1.0。 - 坐标方向:生成的x轴向右,y轴向下,z轴指向场景前方(与相机光轴一致)。这是OpenCV/ROS中常见的相机坐标系约定。