Python:读取12位二进制文​​件

时间:2017-06-24 11:09:58

标签: python python-3.x binary unpack

我正在尝试使用Python 3阅读包含图像(视频)的12位二进制文​​件。

要读取类似的文件但以16位编码,以下内容非常有效:

import numpy as np
images = np.memmap(filename_video, dtype=np.uint16, mode='r', shape=(nb_frames, height, width))

其中filename_video是可以从另一个文件读取的视频的文件和nb_frames,高度和宽度特征。通过'工作得很好'我的意思是快:在我的计算机上读取140帧的640x256视频大约需要1毫秒。

据我所知,当文件以12位编码时我不能使用它,因为没有uint12类型。所以我要做的是读取一个12位文件并将其存储在一个16位的uint数组中。以下摘自(Python: reading 12 bit packed binary image),有效:

with open(filename_video, 'rb') as f:
    data=f.read()
images=np.zeros(int(2*len(data)/3),dtype=np.uint16)
ii=0
for jj in range(0,int(len(data))-2,3):
    a=bitstring.Bits(bytes=data[jj:jj+3],length=24)
    images[ii],images[ii+1] = a.unpack('uint:12,uint:12')
    ii=ii+2
images = np.reshape(images,(nb_frames,height,width))

然而,这非常慢:读取640x256视频时,我的机器只有5帧需要大约11.5秒。理想情况下,我希望能够像使用memmap读取8位或16位文件一样有效地读取12位文件。或者至少慢10 ^ 5倍。我怎么能加快速度呢?

这是一个文件示例: http://s000.tinyupload.com/index.php?file_id=26973488795334213426 (nb_frames = 5,高度= 256,宽度= 640)。

4 个答案:

答案 0 :(得分:6)

我的实现与@ max9111提出的实现略有不同,该实现不需要调用unpackbits

它直接通过将中间字节切成两半并使用numpy的二进制运算从三个连续的uint12中创建两个uint8值。在下文中,假设data_chunks是一个二进制字符串,其中包含任意数目的12位整数的信息(因此,其长度必须为3的倍数)。

def read_uint12(data_chunk):
    data = np.frombuffer(data_chunk, dtype=np.uint8)
    fst_uint8, mid_uint8, lst_uint8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.uint16).T
    fst_uint12 = (fst_uint8 << 4) + (mid_uint8 >> 4)
    snd_uint12 = ((mid_uint8 % 16) << 8) + lst_uint8
    return np.reshape(np.concatenate((fst_uint12[:, None], snd_uint12[:, None]), axis=1), 2 * fst_uint12.shape[0])

我以其他实现作为基准,这种方法在〜5 Mb输入下被证明快了约4倍:
read_uint12_unpackbits每个循环65.5 ms±1.11 ms(平均±标准偏差,共运行7次,每个循环10个循环) read_uint12每个循环14 ms±513 µs(平均±标准偏差,共运行7次,每个循环100个循环)

答案 1 :(得分:3)

修改

在@Cyril Gaudefroy回答的帮助下,我使用Numba创建了一个编译解决方案。

import numba as nb
import numpy as np
@nb.njit(nb.uint16[::1](nb.uint8[::1]),fastmath=True,parallel=True)
def nb_read_uint12(data_chunk):
  """data_chunk is a contigous 1D array of uint8 data)
  eg.data_chunk = np.frombuffer(data_chunk, dtype=np.uint8)"""

  #ensure that the data_chunk has the right length
  assert np.mod(data_chunk.shape[0],3)==0

  out=np.empty(data_chunk.shape[0]//3*2,dtype=np.uint16)

  for i in nb.prange(data_chunk.shape[0]//3):
    fst_uint8=np.uint16(data_chunk[i*3])
    mid_uint8=np.uint16(data_chunk[i*3+1])
    lst_uint8=np.uint16(data_chunk[i*3+2])

    out[i*2] =   (fst_uint8 << 4) + (mid_uint8 >> 4)
    out[i*2+1] = ((mid_uint8 % 16) << 8) + lst_uint8

  return out

<强>计时

num_Frames=10
data_chunk=np.random.randint(low=0,high=255,size=np.int(640*256*1.5*num_Frames),dtype=np.uint8)

Cyril Gaudefroy(numpy only): 11ms  ->225MB/s
Numba version:               1.1ms ->2.25GB/s

以前的版本(不推荐)

如果Numba不是一个选项,请参阅@Cyril Gaudefroys回答。

当我读到这个问题时,我认为必须有一个简单的答案,但我失败了。不过我写了一个简单(但很难看)的代码,比你的例子快300倍,在我的笔记本电脑上实现了大约25 MB / s(Core i5 3210M)。

def read_uint12(filename_video,nb_frames,height,width):

    data=np.fromfile(filename_video, dtype=np.uint8)
    data=np.unpackbits(data)
    data=data.reshape((data.shape[0]/12,12))

    images=np.zeros(data_2.shape[0],dtype=np.uint16)
    for i in xrange(0,12):
        images+=2**i*data[:,11-i]

    images = np.reshape(images,(nb_frames,height,width))
    return images

答案 2 :(得分:2)

发现@cyrilgaudefroy答案很有用。但是,最初,它不适用于我的12位压缩二进制图像数据。发现这种特殊情况下的包装有些不同。 “中间”字节包含最低有效半字节。三元组的字节1和3是十二个字节中的最高8位。因此将@cyrilgaudefroy答案修改为:

def read_uint12(data_chunk):
    data = np.frombuffer(data_chunk, dtype=np.uint8)
    fst_uint8, mid_uint8, lst_uint8 = np.reshape(data, (data.shape[0] // 3, 3)).astype(np.uint16).T
    fst_uint12 = (fst_uint8 << 4) + (mid_uint8 >> 4)
    snd_uint12 = (lst_uint8 << 4) + (np.bitwise_and(15, mid_uint8))
    return np.reshape(np.concatenate((fst_uint12[:, None], snd_uint12[:, None]), axis=1), 2 * fst_uint12.shape[0])

答案 3 :(得分:0)

这是另一种变化。我的数据格式是:

第一个uint12:第二个uint8的最低有效4位中的最高有效位+第一个uint8的最低有效8位中的

第二个uint12:第三个uint8的最高有效8位+第二个uint8的最高有效4位的最低有效4

相应的代码是:

def read_uint12(data_chunk):
    data = np.frombuffer(data_chunk, dtype=np.uint8)
    fst_uint8, mid_uint8, lst_uint8 = numpy.reshape(data, (data.shape[0] // 3, 3)).astype(numpy.uint16).T
    fst_uint12 = ((mid_uint8 & 0x0F) << 8) | fst_uint8
    snd_uint12 = (lst_uint8 << 4) | ((mid_uint8 & 0xF0) >> 4)
    return numpy.reshape(numpy.concatenate((fst_uint12[:, None], snd_uint12[:, None]), axis=1), 2 * fst_uint12.shape[0])