Python - 从二进制文件中读取10位整数

9

我有一个包含一系列10位整数的二进制文件。我想要读取它并将这些值存储到列表中。

下面的代码可以实现这个功能,它会读取my_file并将整数值填充到pixels中:

file = open("my_file", "rb")

pixels = []
new10bitsByte = ""

try:
    byte = file.read(1)
    while byte:
        bits = bin(ord(byte))[2:].rjust(8, '0')
        for bit in reversed(bits):
            new10bitsByte += bit
            if len(new10bitsByte) == 10:
                pixels.append(int(new10bitsByte[::-1], 2))
                new10bitsByte = ""             
    byte = file.read(1)

finally:
    file.close()

将字节读入位,再将其读回“10位”字节似乎不太优雅。有没有更好的方法?

对于8位或16位整数,我可以直接使用file.read(size)并将结果直接转换为整数。但是,由于每个值存储在1.25个字节中,我需要类似于file.read(1.25)这样的东西……


2
请查看这里的前两个答案:https://dev59.com/5Wgv5IYBdhLWcg3wTvLL - juanpa.arrivillaga
@juanpa.arrivillaga 谢谢!据我所知,在Python中没有办法按10位读取文件,我必须逐字节读取文件,然后“切割”字节以获取我的“10位”字节。 - jbgt
2
您可能希望一次读取40位,即5个字节。这些包含4个完整的10位数字,您应该能够一次性提取它们。 - MisterMiyagi
1
MisterMiyagi说的没错。看起来你正在使用Python 2,是吗?除非输入文件真的很大,否则将其全部读入内存可能会更有效率,而不是逐字节读取它。顺便说一句,bits = format(ord(byte), '08b')比使用bin函数更高效。但实际上,最好使用MisterMiyagi的建议,而不是这个绕弯的转换算法。 - PM 2Ring
300兆字节相当大,因此最好不要一次性读取整个文件,因为Python数据结构可能会占用相当多的RAM。 - PM 2Ring
显示剩余3条评论
3个回答

3
这是一个不使用文本字符串转换进行位运算的生成器。希望它更有效率一些。:)
为了测试它,我将range(1024)中的所有数字写入BytesIO流中,这个流类似于一个二进制文件。
from io import BytesIO

def tenbitread(f):
    ''' Generate 10 bit (unsigned) integers from a binary file '''
    while True:
        b = f.read(5)
        if len(b) == 0:
            break
        n = int.from_bytes(b, 'big')

        #Split n into 4 10 bit integers
        t = []
        for i in range(4):
            t.append(n & 0x3ff)
            n >>= 10
        yield from reversed(t)

# Make some test data: all the integers in range(1024),
# and save it to a byte stream
buff = BytesIO()

maxi = 1024
n = 0
for i in range(maxi):
    n = (n << 10) | i
    #Convert the 40 bit integer to 5 bytes & write them
    if i % 4 == 3:
        buff.write(n.to_bytes(5, 'big'))
        n = 0

# Rewind the stream so we can read from it
buff.seek(0)

# Read the data in 10 bit chunks
a = list(tenbitread(buff))

# Check it 
print(a == list(range(maxi)))    

输出

True

list(tenbitread(buff)) 是将生成器的输出转换为列表的最简单方式,但是您也可以轻松地迭代值,例如

for v in tenbitread(buff):


for i, v in enumerate(tenbitread(buff)):

如果您想要索引以及数据值。


这是一个小端版本的生成器,它可以给出与您的代码相同的结果。

def tenbitread(f):
    ''' Generate 10 bit (unsigned) integers from a binary file '''
    while True:
        b = f.read(5)
        if not len(b):
            break
        n = int.from_bytes(b, 'little')

        #Split n into 4 10 bit integers
        for i in range(4):
            yield n & 0x3ff
            n >>= 10

我们可以通过“展开”for循环来稍微改进这个版本,这样就可以摆脱最后的掩码和移位操作。
def tenbitread(f):
    ''' Generate 10 bit (unsigned) integers from a binary file '''
    while True:
        b = f.read(5)
        if not len(b):
            break
        n = int.from_bytes(b, 'little')

        #Split n into 4 10 bit integers
        yield n & 0x3ff
        n >>= 10
        yield n & 0x3ff
        n >>= 10
        yield n & 0x3ff
        n >>= 10
        yield n 

这个操作应该会使速度稍微更快...


它在小端版本上完美运行,并且比我的初始代码快7倍 :) 非常感谢! - jbgt
@Jean-BaptisteMartin:有一个微小的优化可以进行。我不知道它是否会显著加快速度,但值得一试。我很快会在我的答案中添加它。 - PM 2Ring
这很棒!但是你如何获得有符号的整数而不是无符号的? - Taaam
1
@Taaam 这并不太难。我们可以使用 ^ 位异或运算符来实现。要获取10位有符号数,将 n & 0x3ff 更改为 ((n & 0x3ff) ^ 512) - 512。你可以省略内部括号:(n & 0x3ff ^ 512) - 512,但我认为它们使得代码更易读。 - PM 2Ring

2

添加一个基于Numpy的解决方案,适用于解包大型10位打包字节缓冲区,例如您可能从AVT和FLIR相机接收到的缓冲区。

这是对类似问题的@cyrilgaudefroy's answer的10位版本;在那里,您还可以找到一个Numba替代方案,能够获得额外的速度提升。

import numpy as np

def read_uint10(byte_buf):
    data = np.frombuffer(byte_buf, dtype=np.uint8)
    # 5 bytes contain 4 10-bit pixels (5x8 == 4x10)
    b1, b2, b3, b4, b5 = np.reshape(data, (data.shape[0]//5, 5)).astype(np.uint16).T
    o1 = (b1 << 2) + (b2 >> 6)
    o2 = ((b2 % 64) << 4) + (b3 >> 4)
    o3 = ((b3 % 16) << 6) + (b4 >> 2)
    o4 = ((b4 % 4) << 8) + b5

    unpacked =  np.reshape(np.concatenate((o1[:, None], o2[:, None], o3[:, None], o4[:, None]), axis=1),  4*o1.shape[0])
    return unpacked

如果返回缓冲区而不是Numpy数组,则可以省略Reshape:
unpacked =  np.concatenate((o1[:, None], o2[:, None], o3[:, None], o4[:, None]), axis=1).tobytes()

如果图片尺寸已知,可以直接进行重塑,例如:
unpacked =  np.reshape(np.concatenate((o1[:, None], o2[:, None], o3[:, None], o4[:, None]), axis=1), (1024, 1024))

如果使用模运算符让您感到困惑,可以尝试玩弄以下内容:
np.unpackbits(np.array([255%64], dtype=np.uint8))

编辑:事实证明,联合视觉Mako-U相机采用了与我最初提出的顺序不同的排序:

o1 = ((b2 % 4) << 8) + b1
o2 = ((b3 % 16) << 6) + (b2 >> 2)
o3 = ((b4 % 64) << 4) + (b3 >> 4)
o4 = (b5 << 2) + (b4 >> 6)

因此,如果您的特定设置下的图像一开始看起来很奇怪,您可能需要测试不同的顺序。


1
由于 Python 中没有直接按 x 位读取文件的方法,因此我们必须逐字节读取。听从 MisterMiyagi 和 PM 2Ring 的建议,我将代码修改为以每次读取 5 个字节(即 40 位)的方式读取文件,然后将结果字符串分成 4 个 10 位数字,而不是逐位循环。结果证明,这比我的先前代码快了两倍。
file = open("my_file", "rb")

pixels = []
exit_loop = False

try:
    while not exit_loop:
        # Read 5 consecutive bytes into fiveBytesString
        fiveBytesString = ""
        for i in range(5):
            byte = file.read(1)
            if not byte:
                exit_loop = True
                break
            byteString = format(ord(byte), '08b')
            fiveBytesString += byteString[::-1]
        # Split fiveBytesString into 4 10-bit numbers, and add them to pixels
        pixels.extend([int(fiveBytesString[i:i+10][::-1], 2) for i in range(0, 40, 10) if len(fiveBytesString[i:i+10]) > 0])

finally:
    file.close()

1). 我不确定你为什么要使用 [::-1] 进行反转操作。 2). 在尝试将 fiveBytesString 转换为整数之前,您需要检查它是否为空。 3). exit 不是一个很好的变量名,因为它会遮盖 exit() 函数。虽然像这样将其用作标志并没有错误,但对于其他人阅读您的代码可能会有点困惑。 :) - PM 2Ring
  1. 这是因为我已经知道我的输出应该是什么(我正在尝试自己进行转换,但我已经有了输出文件)。例如,前5个字节是01001011、01010100、11100001、10000101、00011000。我知道第一个输出数字应该是20、23、21、37。为了找到正确的输出,我不得不反转字节、连接它们、拆分它们并再次反转结果。我不知道输入文件是如何创建的,我只是猜测我必须做这些反转才能得到我的输出...
  2. 和 3) 已编辑,谢谢!
- jbgt
啊,好的。我已经添加了一个新版本。现在它给出的值与你的代码相同。但是,我不明白你是如何从 [0b01001011, 0b01010100, 0b11100001, 0b10000101, 0b00011000] 得到 [20, 23, 21, 37] 的。 - PM 2Ring
我刚意识到我给你的字节是错误的,非常抱歉!但是你更新后的生成器已经可以正常处理我的文件了,现在我认为我对二进制文件操作有了更好的理解,感谢你的帮助! - jbgt
非常愉快!感谢您的接受。如果您发布了正确的字节,我会更快地回答的。 :) 顺便说一下,您可能会喜欢看看我去年写的这个答案,它采用了稍微不同的位操作方法。 - PM 2Ring
确实很有趣!也许对我现在处理文件的工作没有什么用,但是阅读起来还是很有趣的! - jbgt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接