You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从RGB-D全景图获取有效3D模型?投影畸变问题求助

问题:RGB-D全景图投影3D空间时出现畸变导致场景失真

尝试将RGB-D全景图投影回3D空间,但重投影结果存在畸变,3D场景失真,相关效果图与全景图如下:
畸变效果图
原始全景图

以下是实现代码:

import argparse
import os
import cv2
import numpy as np
import open3d

class PointCloudReader():

    def __init__(self, resolution="full", random_level=0, generate_color=False, generate_normal=False):
        self.random_level = random_level
        self.resolution = resolution
        self.generate_color = generate_color
        self.point_cloud = self.generate_point_cloud(self.random_level, color=self.generate_color)

    def generate_point_cloud(self, random_level=0, color=False, normal=False):
        coords = []
        colors = []

        # Load and resize depth image
        depth_image_path = 'DPT/output_monodepth/basel_stapfelberg_panorama.png'
        depth_img = cv2.imread(depth_image_path, cv2.IMREAD_ANYDEPTH)
        depth_img = cv2.resize(depth_img, (depth_img.shape[1] // 2, depth_img.shape[0] // 2))

        # Load and resize RGB image
        equirectangular_image = 'DPT/input/basel_stapfelberg_panorama.png'
        rgb_img = cv2.imread(equirectangular_image)
        rgb_img = cv2.resize(rgb_img, (depth_img.shape[1], depth_img.shape[0]))

        # Define parameters for conversion
        focal_length = depth_img.shape[1] / 2
        sensor_width = 36
        sensor_height = 24
        y_ticks = np.deg2rad(np.arange(0, 360, 360 / depth_img.shape[1]))
        x_ticks = np.deg2rad(np.arange(-90, 90, 180 / depth_img.shape[0]))

        # Compute spherical coordinates
        theta, phi = np.meshgrid(y_ticks, x_ticks)
        depth = depth_img + np.random.random(depth_img.shape) * random_level
        x_sphere = depth * np.cos(phi) * np.sin(theta)
        y_sphere = depth * np.sin(phi)
        z_sphere = depth * np.cos(phi) * np.cos(theta)

        # Convert spherical coordinates to camera coordinates
        x_cam = x_sphere.flatten()
        y_cam = -y_sphere.flatten()
        z_cam = z_sphere.flatten()

        coords = np.stack((x_cam, y_cam, z_cam), axis=-1)

        if color:
            colors = rgb_img.reshape(-1, 3) / 255.0

        points = {'coords': coords}

        if color:
            points['colors'] = colors

        return points

    def visualize(self):
        pcd = open3d.geometry.PointCloud()
        pcd.points = open3d.utility.Vector3dVector(self.point_cloud['coords'])
        if self.generate_color:
            pcd.colors = open3d.utility.Vector3dVector(self.point_cloud['colors'])
        open3d.visualization.draw_geometries([pcd])

def main(args):
    reader = PointCloudReader(random_level=10, generate_color=True, generate_normal=False)
    reader.visualize()

if __name__ == "__main__":
    main(None)
解决方案

以下是修复畸变、生成有效3D点云的关键调整:

  • 修正坐标转换与轴系对齐
    代码中对y_cam的无差别取反可能导致上下轴颠倒,需根据全景图的像素方向调整。如果全景图的行从上到下对应俯仰角从90°到-90°,则保留取反;否则移除取反,确保相机坐标系(右手系)与像素坐标一致:

    # 修正后的坐标转换(根据实际图像方向调整)
    x_cam = x_sphere.flatten()
    y_cam = y_sphere.flatten()  # 移除不必要的取反,或根据图像方向决定是否保留
    z_cam = z_sphere.flatten()
    
  • 修复俯仰角采样范围
    当前x_ticks的生成方式未覆盖90°仰角,导致顶部区域采样缺失。改用linspace均匀覆盖-90°到90°的完整范围:

    x_ticks = np.deg2rad(np.linspace(-90, 90, depth_img.shape[0]))
    

    若全景图顶部对应-90°、底部对应90°,则反转顺序:np.linspace(90, -90, depth_img.shape[0])

  • 移除随机噪声干扰
    代码中给深度值添加的随机噪声会直接造成点云毛刺和失真,应删除或仅在调试时启用:

    depth = depth_img  # 移除随机噪声,使用原始深度数据
    
  • 确认深度图的物理单位
    检查深度图是否为归一化值,若为0-255的8位图像,需转换为实际物理深度(如米):

    # 示例:假设深度范围为0-10米,归一化到0-255
    depth = depth_img / 255.0 * 10.0
    
  • 对齐RGB与深度图的采样方式
    缩放图像时,深度图使用最近邻插值避免深度值被平滑,RGB图使用线性插值保证颜色过渡自然:

    depth_img = cv2.resize(depth_img, (depth_img.shape[1]//2, depth_img.shape[0]//2), interpolation=cv2.INTER_NEAREST)
    rgb_img = cv2.resize(rgb_img, (depth_img.shape[1], depth_img.shape[0]), interpolation=cv2.INTER_LINEAR)
    
  • 修正球面坐标生成逻辑
    确保meshgrid生成的theta(方位角)和phi(俯仰角)与图像的行列完全对应,theta对应全景图的列(0-360°),phi对应行(-90°到90°),当前代码的meshgrid顺序正确,但需确认图像的行列方向是否匹配。

修正后的完整代码示例

import argparse
import os
import cv2
import numpy as np
import open3d

class PointCloudReader():

    def __init__(self, resolution="full", random_level=0, generate_color=False, generate_normal=False):
        self.random_level = random_level
        self.resolution = resolution
        self.generate_color = generate_color
        self.point_cloud = self.generate_point_cloud(self.random_level, color=self.generate_color)

    def generate_point_cloud(self, random_level=0, color=False, normal=False):
        coords = []
        colors = []

        # Load and resize depth image
        depth_image_path = 'DPT/output_monodepth/basel_stapfelberg_panorama.png'
        depth_img = cv2.imread(depth_image_path, cv2.IMREAD_ANYDEPTH)
        # 深度图用最近邻插值
        depth_img = cv2.resize(depth_img, (depth_img.shape[1] // 2, depth_img.shape[0] // 2), interpolation=cv2.INTER_NEAREST)

        # Load and resize RGB image
        equirectangular_image = 'DPT/input/basel_stapfelberg_panorama.png'
        rgb_img = cv2.imread(equirectangular_image)
        # RGB图用线性插值
        rgb_img = cv2.resize(rgb_img, (depth_img.shape[1], depth_img.shape[0]), interpolation=cv2.INTER_LINEAR)

        # Define parameters for conversion
        y_ticks = np.deg2rad(np.linspace(0, 360, depth_img.shape[1], endpoint=False))
        # 完整覆盖-90到90度俯仰角
        x_ticks = np.deg2rad(np.linspace(-90, 90, depth_img.shape[0]))

        # Compute spherical coordinates
        theta, phi = np.meshgrid(y_ticks, x_ticks)
        # 移除随机噪声,使用原始深度
        depth = depth_img
        # 转换为实际深度(根据你的深度图格式调整)
        # depth = depth_img / 255.0 * 10.0

        x_sphere = depth * np.cos(phi) * np.sin(theta)
        y_sphere = depth * np.sin(phi)
        z_sphere = depth * np.cos(phi) * np.cos(theta)

        # Convert spherical coordinates to camera coordinates(根据图像方向调整y轴)
        x_cam = x_sphere.flatten()
        y_cam = -y_sphere.flatten()  # 若图像上下颠倒则保留取反,否则删除负号
        z_cam = z_sphere.flatten()

        coords = np.stack((x_cam, y_cam, z_cam), axis=-1)

        if color:
            colors = rgb_img.reshape(-1, 3) / 255.0

        points = {'coords': coords}

        if color:
            points['colors'] = colors

        return points

    def visualize(self):
        pcd = open3d.geometry.PointCloud()
        pcd.points = open3d.utility.Vector3dVector(self.point_cloud['coords'])
        if self.generate_color:
            pcd.colors = open3d.utility.Vector3dVector(self.point_cloud['colors'])
        open3d.visualization.draw_geometries([pcd])

def main(args):
    reader = PointCloudReader(generate_color=True)
    reader.visualize()

if __name__ == "__main__":
    main(None)

内容的提问来源于stack exchange,提问作者Hannah Stark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 15:10:04