使用 Numpy 读取使用 C++ 数据类型生成的二进制文件

Posted

技术标签:

【中文标题】使用 Numpy 读取使用 C++ 数据类型生成的二进制文件【英文标题】:Reading a Binary File that was generated with C++ data types Using Numpy 【发布时间】:2017-11-25 23:04:12 【问题描述】:

我正在尝试读取在 C++ 中生成的具有以下结构的已知数据类型的二进制文件:

    uint64_t shot;
    uint32_t status;
    double easting, northing, altitude;
    uint32_t  zoneNumber;

    char zoneLetter;

    float vNorth, vEast, vDown;
    float qw, qx, qy, qz;
    float acel_x, acel_y, acel_z, gyro_x, gyro_y, gyro_z;

    float rollStdDev, pitchStdDev, yawStdDev;
    float northingStdDev, eastingStdDev, altitudeStdDev;
    float vNorthStdDev, vEastStdDev, vDownStdDev;

这里发生的唯一奇怪是“区域字母”是一个 1 字节的字符,后面有 3 个字节的填充。所以字符应该从第 40 字节开始,然后 vNorth 应该从第 44 字节开始。

这是我尝试使用 numpy 使用 python 读取这个二进制文件。我不确定我是否正确转换了 c++ 双打,而且我很确定我的问题来自读取字符。

问题在于,在第一次迭代镜头和状态读取正确,但此后读取的所有内容对于实际数据的外观没有任何意义。所以我认为从字节 12-44 读取字符或重复读取存在问题。

dt2 = np.dtype([('ShotNum', np.uint64), ('Status', np.uint32), ('easting', np.float_),\
            ('northing', np.float_),('alt', np.float_), ('Znum', np.uint32),('letter', np.character),\
            ('vnorth', np.float32),('veast', np.float32),('vdown', np.float32),\
            ('qw', np.float32),('qx', np.float32),('qy', np.float32),('qz', np.float32),\
            ('acX', np.float32),('acY',np.float32),('acZ', np.float32),('gyX', np.float32),\
            ('gyY', np.float32),('gyZ', np.float32),('rollSTD', np.float32),('pitSTD', np.float32),\
            ('yawSTD', np.float32),('northSTD', np.float32),('eastSTD', np.float32),('altSTD', np.float32),\
            ('vnorthSTD', np.float32),('veastSTD', np.float32),('vDownSTD', np.float32)])

这是 c++ 显式结构

[thread: 892] mainwindow.cpp(65): Sizeof NavigationSolution:  136  bytes
[thread: 892] mainwindow.cpp(66): shot
[thread: 892] mainwindow.cpp(67):    bytes:  8
[thread: 892] mainwindow.cpp(68):   offset:  0
[thread: 892] mainwindow.cpp(69): status
[thread: 892] mainwindow.cpp(70):    bytes:  4
[thread: 892] mainwindow.cpp(71):   offset:  8
[thread: 892] mainwindow.cpp(72): easting
[thread: 892] mainwindow.cpp(73):    bytes:  8
[thread: 892] mainwindow.cpp(74):   offset:  16
[thread: 892] mainwindow.cpp(75): northing
[thread: 892] mainwindow.cpp(76):    bytes:  8
[thread: 892] mainwindow.cpp(77):   offset:  24
[thread: 892] mainwindow.cpp(78): altitude
[thread: 892] mainwindow.cpp(79):    bytes:  8
[thread: 892] mainwindow.cpp(80):   offset:  32
[thread: 892] mainwindow.cpp(81): zoneNumber
[thread: 892] mainwindow.cpp(82):    bytes:  4
[thread: 892] mainwindow.cpp(83):   offset:  40
[thread: 892] mainwindow.cpp(84): zoneLetter
[thread: 892] mainwindow.cpp(85):    bytes:  1
[thread: 892] mainwindow.cpp(86):   offset:  44
[thread: 892] mainwindow.cpp(87): vNorth
[thread: 892] mainwindow.cpp(88):    bytes:  4
[thread: 892] mainwindow.cpp(89):   offset:  48
[thread: 892] mainwindow.cpp(90): vEast
[thread: 892] mainwindow.cpp(91):    bytes:  4
[thread: 892] mainwindow.cpp(92):   offset:  52
[thread: 892] mainwindow.cpp(93): vDown
[thread: 892] mainwindow.cpp(94):    bytes:  4
[thread: 892] mainwindow.cpp(95):   offset:  56

…等

【问题讨论】:

“我很确定我的问题……” 你还没有解释你的问题。这使得帮助您解决它们变得困难。 :) 我认为('letter', np.character) 不会起作用。您需要考虑所有四个字节(字母和填充)。尝试将其替换为 ('letter', 'S1'), ('padding', 'S3') 为了更清楚一点而编辑了问题。我会试一试沃伦 我尝试了您的解决方案 Warren,但输出仍然没有意义。我不确定 C++ double 是否是 python numpy float_ 二进制文件究竟是如何创建的?您显示的 C++ 变量是否实际上是 struct 中的字段,并且 struct 是作为单个内存块写出的吗?还是每个字段单独写?如果是前者,请注意编译器可能会向结构添加填充(可能是为了改善内存对齐)。例如,编译器可能在status 字段之后添加了四个字节,以使easting 对齐八字节。 【参考方案1】:

您必须考虑编译器添加到struct 的填充。例如easting 的偏移量是 16 字节,而不是 12。编译器添加了四个字节的填充,大概是为了给 easting 一个八字节对齐。

如果这项工作是长期项目的一部分,请三思而后行使用这种格式。它依赖于编译器。如果生成二进制文件的 C++ 代码使用不同的编译器(或者甚至使用相同的编译器但不同的编译器选项)编译,则二进制文件中的填充可能会更改,Python 代码将无法正确读取文件。

【讨论】:

【参考方案2】:

Warren Weckesser 指出您的问题源于 NumPy dtype 中缺少填充。虽然您确实可以通过添加显式填充字段来手动修复它,但您也可以使用 align=True 选项自动修复它 np.dtype

对齐:bool,可选

向字段添加填充以匹配 C 编译器会输出类似的 C-struct。

此外,您可以考虑在 NumPy 中使用嵌套数组来表示您的结构,例如,而不是这样:

('qw', np.float32),('qx', np.float32),('qy', np.float32),('qz', np.float32)

你可以这样做:

('q', np.float32, 4)

那么arr.q 中的任何值都将是一个长度为 4 的数组,这取决于您的应用程序可能更容易处理。

【讨论】:

很棒的提示!是否有用于填充结构字段的 C/C++ 标准?还是只是碰巧流行的 C/C++ 编译器都做同样的事情? @WarrenWeckesser:它是实现定义的。但是,如果 OP 对他的 C++ 结构的实现定义性质感到满意,他可能会对 Python 中的相同感到满意。如果 OP 想要跨平台,则解决方案相反:NumPy 中的align=False(默认),以及用于在 C++ 中启用结构打包的编译器特定编译指示。然后,如果需要,重新添加显式填充。 例如,double easting 在 32 位 x86 上之前没有填充(我们可以推测 OP 使用的是 64 位系统)。

以上是关于使用 Numpy 读取使用 C++ 数据类型生成的二进制文件的主要内容,如果未能解决你的问题,请参考以下文章

将 numpy 指针 (dtype=np.bool) 传递给 C++

Numpy常用方法及应用总汇

尝试将自定义 C++ 矩阵传递给 numpy 数组

Numpy库使用

如何将 numpy 二维数组作为一种可以用 C++ 读取的二进制格式存储到磁盘上

在 python swig 中读取 c++ 2d 数组