如何从单个 PNG 中分离两个大小为 8 和 16 的位图

How to separate two bit maps of size 8 and 16 from a single PNG

我有来自 SICK Trispector 深度激光扫描仪的图像。图片格式为PNG。 SICK 将其称为 Trispector 2.5D PNG。根据 SICK 的文档,图像包含反射数据和深度数据。但 SICK 不会提供有关如何在不使用其或合作伙伴的软件的情况下使用这些数据的信息。本质上,我需要的是深度数据。反射数据可能很好,但不是必需的。我得到的结果图像是单色的。它似乎在图像的顶部有反射数据,在底部有重叠的高度数据。扫描对象是一箱带瓶盖的啤酒瓶。你可以在这里看到一个例子:

我试过在许多不同的图像查看器中打开图像并查找有关 2.5D 的信息,但它似乎与此无关。在 Matlab 图像预览中,我单独获得了一侧的高度数据,但我不知道如何使用这些信息。请参阅来自 Matlab 预览的以下图像:

有谁知道如何从这样的图像中推断高度数据?也许有人以前使用过 SICK 的 SOPAS 或 SICK 扫描仪,并且了解 SICK 称之为“2.5D PNG”的这种格式。一个 OpenCV 解决方案会很好。

编辑:正如@DanMašek 评论的那样,问题在于将两个不同位深度的图像从单个 PNG 中分离出来。他提供了对该问题的进一步见解,并提供了一个很好的 OpenCV 解决方案,用于将强度图像和深度图像分别分离为 8 位和 16 位:

注意:此信息基于 SICK 支持论坛上的 SICK TriSpector FAQ(不可公开访问,但您可以申请访问)。


SICK TriSpector 生成的 PNG 图像存储两个像素缓冲区的串联:

  • 8 位强度图像
  • 一张 16 位(小端)高度图图像

生成的 PNG 图像具有与每个组件相同的宽度和三倍的高度(因为 PNG 是 8 位,每个像素位置总共有 3 个字节)。

让我们考虑一个简单的例子,其中组件有 3 行和 4 列。 存储在 PNG 中的数据将具有以下结构:

第一步,如上图所示,将图像分成两个部分。 PNG 包含 9 行,其中三分之一是 3 行——因此第 0-2 行包含强度,其余是高度图。强度图可以直接使用,高度图需要进一步处理。

如果我们使用的是小端架构并且不关心可移植性,我们可以利用内存布局,将像素缓冲区重新解释为 16 位无符号整数(在 Python numpy.ndarray.view,在 C++ 中创建一个新的 Mat 包装缓冲区)。

更灵活但速度较慢的方法是手动组合部件。重塑数组以具有正确的行数,然后根据奇数或偶数列号将其拆分(跳过 Python 中的索引)。将每个子数组转化为16bit无符号整数,最后按照公式LSB + 256 * HSB.

组合


Python 中的示例脚本:

import cv2
import numpy as np

img = cv2.imread('combined.png', cv2.IMREAD_GRAYSCALE)
height, width = img.shape

# Determine the actual height of the component images
real_height = height/3

# Extract the top (intensity) part
intensity_img = img[:real_height,...]

# Extract the botton (heightmap) part
# Since this has two values per pixel, also reshape it to have the correct number of rows
heightmap_raw = img[real_height:,...].reshape(real_height,-1)

# The heightmap is 16 bit, two subsequent 8 bit pixels need to be combined
# ABCD -> A+256*B, C+256*D

# Quick but non-portable (needs little-endian architecture)
heightmap_img = heightmap_raw.view(np.uint16)

# Slower but portable
heightmap_a = heightmap_raw[...,::2].astype(np.uint16)
heightmap_b = heightmap_raw[...,1::2].astype(np.uint16)
heightmap_img = heightmap_a + 256 * heightmap_b

# Note: intensity is np.uint8, heightmap is np.uint16

cv2.imwrite('intensity.png', intensity_img)
cv2.imwrite('heightmap.png', heightmap_img)

提取的强度图像:

提取的高度图图像(请注意,原始数据在保存时缩小了 256 倍以供说明,丢失了很多细节):