4、点云数据生成(二):从深度图生成点云,理解相机内参与点云坐标映射关系

上一章我们聊了怎么用代码生成模拟点云。这一章,咱们来点真格的——从深度图生成点云。

深度图是什么?说白了,就是一张灰度图。每个像素的值,代表相机到物体的距离。你拿个Kinect或者RealSense拍一下,得到的就是这玩意儿。但光有深度图还不够,你得知道每个像素对应到三维空间哪个点。这就引出了今天的主角——相机内参。

核心一句话:深度图 + 相机内参 = 点云。缺一不可。

4.1 相机内参到底是什么?

我刚开始接触这个的时候,也被一堆参数搞晕过。其实没那么复杂。相机内参就是描述相机内部光学特性的矩阵。它告诉我们:三维空间里的一个点,是怎么投影到二维图像上的。

标准的内参矩阵长这样:

K = [fx,  0, cx]
    [ 0, fy, cy]
    [ 0,  0,  1]

这里:

  • fx, fy:焦距,单位是像素。说白了就是镜头到成像平面的距离,用像素个数来衡量。
  • cx, cy:主点坐标。一般是图像中心,但不一定完全在正中间。我遇到过一台相机,主点偏了十几个像素,一开始没注意,生成的点云整个歪了。

你可能会问:为什么焦距要用像素表示?嗯,这是为了方便计算。实际物理焦距除以像素尺寸,就得到fx和fy。你不需要自己算,相机标定的时候这些值就出来了。

4.2 从像素到三维点——坐标映射公式

好,现在我们有深度图了,也知道内参了。怎么把像素坐标(u, v)和深度值d,变成三维点(x, y, z)?

公式其实就三行:

z = d
x = (u - cx) * z / fx
y = (v - cy) * z / fy

等等,这里有个坑。深度图里的d,单位是什么?毫米?米?还是别的?

注意:不同深度传感器的深度单位可能不同。Kinect一般是毫米,有些ToF相机是米。做映射前一定要确认单位,否则点云会缩放到奇怪的比例。

我个人习惯在代码里先统一成米。这样后续处理方便,可视化也直观。

4.3 代码实现——从深度图到点云

光说不练假把式。咱们直接上代码。这个函数我写过不下十次了,每次项目不同,但核心逻辑都一样。

import numpy as np

def depth_to_pointcloud(depth_image, K, scale=1000.0):
    """
    从深度图生成点云
    
    参数:
        depth_image: (H, W) 深度图,单位毫米
        K: 相机内参矩阵 [fx, 0, cx; 0, fy, cy; 0, 0, 1]
        scale: 深度缩放因子,默认1000(毫米转米)
    
    返回:
        points: (N, 3) 点云数组
    """
    fx, fy = K[0, 0], K[1, 1]
    cx, cy = K[0, 2], K[1, 2]
    
    h, w = depth_image.shape
    # 生成像素坐标网格
    u, v = np.meshgrid(np.arange(w), np.arange(h))
    
    # 深度值转米
    z = depth_image.astype(np.float32) / scale
    
    # 计算三维坐标
    x = (u - cx) * z / fx
    y = (v - cy) * z / fy
    
    # 堆叠成点云
    points = np.stack([x, y, z], axis=-1)
    
    # 去掉深度为0的无效点
    valid = z > 0
    return points[valid]

这段代码看着简单,但我曾经在这上面栽过跟头。你猜怎么着?我忘了处理深度值为0的像素。那些点投影到无穷远,点云里出现了一堆乱七八糟的噪点。嗯,从那以后,我每次都会加一个有效点过滤。

4.4 核心逻辑流程图

为了让你更直观地理解整个过程,我画了一张图:

深度图 → 点云 映射流程 深度图 (H×W, 每个像素存距离) 相机内参 K (fx, fy, cx, cy) 步骤1:生成像素网格 meshgrid(u, v) 步骤2:深度单位转换 毫米 → 米 步骤3:坐标映射 x=(u-cx)*z/fx 点云 (N, 3) 三维坐标 过滤无效点 核心公式:z = d, x = (u - cx) * z / fx, y = (v - cy) * z / fy 注意:深度单位统一、过滤无效点、确认主点偏移 💡 我习惯在映射前先检查深度图是否有NaN或Inf值 这些异常点会导致点云出现飞点

4.5 避坑指南——我踩过的那些坑

做深度图转点云,看起来简单,但坑不少。我一个个说:

  • 坑一:深度单位不统一。我曾经从两个不同传感器拿数据,一个用毫米,一个用厘米。合并点云的时候,形状完全对不上。排查了半天才发现是单位问题。
  • 坑二:主点偏移没考虑。有些相机的主点不在图像正中心。如果你直接用(w/2, h/2)代替cx, cy,点云会整体偏移。我建议每次都用标定得到的真实值。
  • 坑三:深度图有空洞。有些像素没有深度值(比如黑色物体吸光了)。这些点直接扔掉会丢失信息。我一般用插值或者邻域填充来处理。

我的小技巧:生成点云后,先可视化看看形状对不对。如果物体看起来被拉伸了,八成是fx/fy比例不对。如果整体偏移,检查cx/cy。如果尺度不对,检查深度单位。

4.6 进阶——反向映射:从点云到深度图

你可能会问:反过来行不行?当然可以。把三维点投影回图像平面,就得到了深度图。这在点云配准、重投影误差计算中很常用。

公式就是正向映射的逆过程:

u = fx * x / z + cx
v = fy * y / z + cy
d = z

不过要注意,反向映射得到的u, v不一定是整数。你需要做插值。我一般用最近邻插值,速度快,效果也还行。

4.7 总结一下

从深度图生成点云,说白了就是三件事:拿到内参、套用公式、过滤无效点。但细节决定成败。单位、主点、空洞处理,每一个都可能让你的点云面目全非。

我个人建议,刚开始做的时候,先用一个已知形状的物体(比如一个平面或者球体)做测试。生成点云后看看形状对不对。这样能快速发现问题。

好了,这一章就到这里。代码和公式都在上面了,拿去用吧。


公众号:蓝海资料掘金营,微信deep3321