MiDaS数据格式详解:PFM与PNG文件读写源码解析

MiDaS数据格式详解:PFM与PNG文件读写源码解析

【免费下载链接】MiDaS Code for robust monocular depth estimation described in "Ranftl et. al., Towards Robust Monocular Depth Estimation: Mixing Datasets for Zero-shot Cross-dataset Transfer, TPAMI 2022" 【免费下载链接】MiDaS 项目地址: https://gitcode.com/gh_mirrors/mi/MiDaS

引言:深度估计中的数据格式挑战

在单目深度估计(Monocular Depth Estimation)领域,精确的深度图存储与交换是算法开发与应用部署的关键环节。MiDaS(Monocular Depth Estimation)作为当前最先进的开源深度估计算法之一,采用PFM(Portable Float Map)格式存储高精度浮点深度数据,同时支持PNG(Portable Network Graphics)格式的可视化输出。本文将系统解析这两种格式的技术细节,结合MiDaS源码实现,提供从文件结构到工程实践的完整指南。

PFM格式技术规范

1. 文件结构解析

PFM格式采用二进制存储,由三部分组成:

[文件头][图像尺寸][缩放因子][像素数据]

文件头标识

  • PF\n:彩色图像(3通道浮点数据)
  • Pf\n:灰度图像(单通道浮点数据)

关键技术特性

  • 支持任意尺寸的浮点图像存储
  • 原生支持大/小端字节序(Endian)标识
  • 像素数据按行存储,从上到下(与PNG相反)

2. MiDaS中的PFM读写实现

2.1 读取实现(read_pfm函数)
def read_pfm(path):
    with open(path, "rb") as file:
        # 解析文件头
        header = file.readline().rstrip()
        if header.decode("ascii") == "PF":
            color = True  # 彩色图像标识
        elif header.decode("ascii") == "Pf":
            color = False  # 深度图为灰度图像
        else:
            raise Exception("Not a PFM file: " + path)

        # 解析图像尺寸
        dim_match = re.match(r"^(\d+)\s(\d+)\s$", file.readline().decode("ascii"))
        if dim_match:
            width, height = map(int, dim_match.groups())
        else:
            raise Exception("Malformed PFM header.")

        # 解析缩放因子与字节序
        scale = float(file.readline().decode("ascii").rstrip())
        if scale < 0:
            endian = "<"  # 小端字节序
            scale = -scale
        else:
            endian = ">"  # 大端字节序

        # 读取像素数据
        data = np.fromfile(file, endian + "f")  # 按指定字节序读取float32
        shape = (height, width, 3) if color else (height, width)
        data = np.reshape(data, shape)
        data = np.flipud(data)  # 垂直翻转校正存储顺序
        
        return data, scale
2.2 写入实现(write_pfm函数)
def write_pfm(path, image, scale=1):
    with open(path, "wb") as file:
        # 验证数据类型
        if image.dtype.name != "float32":
            raise Exception("Image dtype must be float32.")
            
        # 垂直翻转以符合PFM存储顺序
        image = np.flipud(image)
        
        # 确定图像类型
        if len(image.shape) == 3 and image.shape[2] == 3:
            color = True  # 彩色图像
        elif (len(image.shape) == 2 or 
              len(image.shape) == 3 and image.shape[2] == 1):
            color = False  # 深度图为单通道
        else:
            raise Exception("Image must have H x W x 3, H x W x 1 or H x W dimensions.")
            
        # 写入文件头
        file.write("PF\n".encode() if color else "Pf\n".encode())
        file.write(f"{image.shape[1]} {image.shape[0]}\n".encode())
        
        # 处理字节序
        endian = image.dtype.byteorder
        if endian == "<" or (endian == "=" and sys.byteorder == "little"):
            scale = -scale  # 负缩放因子表示小端字节序
            
        file.write(f"{scale}\n".encode())
        image.tofile(file)  # 写入二进制浮点数据

3. 错误处理机制

MiDaS实现了多层次的异常检测:

  1. 文件类型验证:通过文件头严格检查PFM标识
  2. 维度解析:使用正则表达式确保尺寸格式正确
  3. 数据类型强制:要求输入图像必须为float32类型
  4. 形状验证:确保深度图为H×W单通道结构

PNG格式深度图处理

1. 格式转换原理

PNG格式存储深度数据面临两大挑战:

  • 浮点数据到整数的映射
  • 可视化与精度的平衡

MiDaS采用线性缩放映射策略:

depth_normalized = (depth - depth_min) / (depth_max - depth_min)
uint_data = depth_normalized * max_val  # max_val=255(8位)或65535(16位)

2. 写入实现(write_depth函数)

def write_depth(path, depth, grayscale, bits=1):
    # 处理非有限值
    if not np.isfinite(depth).all():
        depth = np.nan_to_num(depth, nan=0.0, posinf=0.0, neginf=0.0)
        print("WARNING: Non-finite depth values present")
        
    # 线性归一化到目标位深范围
    depth_min = depth.min()
    depth_max = depth.max()
    max_val = (2**(8*bits)) - 1  # 8位:255, 16位:65535
    
    if depth_max - depth_min > np.finfo("float").eps:
        out = max_val * (depth - depth_min) / (depth_max - depth_min)
    else:
        out = np.zeros(depth.shape, dtype=depth.dtype)
        
    # 色彩映射处理
    if not grayscale:
        out = cv2.applyColorMap(np.uint8(out), cv2.COLORMAP_INFERNO)
        
    # 按位深写入PNG
    if bits == 1:
        cv2.imwrite(path + ".png", out.astype("uint8"))  # 8位PNG
    elif bits == 2:
        cv2.imwrite(path + ".png", out.astype("uint16"))  # 16位PNG

3. 关键参数对比

参数8位PNG16位PNGPFM
数据类型uint8uint16float32
动态范围0-2550-65535±3.4×10³⁸
文件大小最小中等最大
用途快速可视化精确存档算法输入/输出
无损性可视化有损精度有限完全无损

工程实践指南

1. 格式选择决策树

mermaid

2. 性能优化建议

  • 内存管理:PFM加载时使用np.fromfile直接映射,避免中间拷贝
  • 错误处理:实现try-except捕获PFM解析异常,尤其针对:
    try:
        depth_data, scale = read_pfm("depth.pfm")
    except Exception as e:
        print(f"PFM解析失败: {str(e)}")
        # 实现降级方案,如加载备用PNG
    
  • 批处理优化:处理序列帧时复用文件句柄,避免频繁IO操作

3. 跨平台兼容性

  • 字节序处理:Windows默认小端,Linux/macOS默认大端,需依赖PFM的缩放因子符号自动适配
  • 路径处理:使用os.path模块确保跨平台路径兼容性:
    import os
    pfm_path = os.path.join(output_dir, f"frame_{frame_idx:06d}.pfm")
    

常见问题解决方案

Q1: PFM文件体积过大如何处理?

A1: 可采用zstd压缩,MiDaS推荐命令:

# 压缩
zstd -19 depth.pfm -o depth.pfm.zst
# 解压(保持原格式兼容)
zstdcat depth.pfm.zst > depth.pfm

Q2: 如何验证PNG深度图精度?

A2: 实现校验函数:

def verify_depth_accuracy(original_pfm, png_path, bits=2):
    # 读取原始PFM与PNG重建深度
    depth_gt, _ = read_pfm(original_pfm)
    depth_png = cv2.imread(png_path, cv2.IMREAD_ANYDEPTH)
    
    # 计算均方误差
    mse = np.mean((depth_gt - depth_png) ** 2)
    return mse < 1e-3  # 16位PNG典型误差阈值

Q3: 浏览器中如何预览PFM文件?

A3: MiDaS提供转换工具链:

# PFM转彩色PNG(带深度色标)
depth_data, _ = read_pfm("input.pfm")
write_depth("preview", depth_data, grayscale=False, bits=2)

总结与扩展

PFM格式作为MiDaS的核心数据交换格式,提供了科学计算所需的浮点精度;而PNG格式则满足了可视化与轻量化需求。通过本文解析的read_pfmwrite_pfmwrite_depth三个核心函数,开发者可实现深度数据的完整生命周期管理。

扩展应用方向:

  • 集成到ROS2消息机制(参考ros/midas_cpp包实现)
  • 移动端优化(mobile/android目录提供PNG高效解码方案)
  • 与ONNX Runtime结合实现跨平台部署(tf/run_onnx.py

【免费下载链接】MiDaS Code for robust monocular depth estimation described in "Ranftl et. al., Towards Robust Monocular Depth Estimation: Mixing Datasets for Zero-shot Cross-dataset Transfer, TPAMI 2022" 【免费下载链接】MiDaS 项目地址: https://gitcode.com/gh_mirrors/mi/MiDaS

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值