来自二进制值的枕头黑白图像

Pillow black and white image from binary values

我有一个包含图像像素数据的二进制文件,该文件仅包含等于 0 或 1(0x00 或 0x01)的字节。我想根据这些数据创建黑白图像。

到目前为止我的代码在 Python 中使用 Pillow 的可重现示例(注意我通常会从文件加载数据而不是就地创建):

import numpy as np
from PIL import Image, ImageOps

w = 128 # image could be much bigger, keeping it small for the example
h = 128

data = np.random.randint(2, size=w*h, dtype=np.uint8).tobytes()

img = Image.frombuffer('L', (h,w), data)
img.show()

问题是,图像将像素数据解释为灰度,因此值 1 几乎是黑色。我希望 1 为白色(即灰度为 255),0 为黑色。

Pillow 中是否有功能“只知道” 我想要 1=white & 0=black(而不是 255=white & 0=黑色)?

编辑

在下面的答案的帮助下,我找到了很多解决方案,我可以在这些解决方案中修改我的数据以将 1 替换为 255...我已经发布了包含这些解决方案结果的答案。其中一些速度非常快,因此可能不会获得更多性能。

但如果有一个很好的解决方案可以完全避免这种开销,i.e.to 直接告诉 Pillow 将 1 视为白色,将 0 视为黑色,那将是理想的。

你可以边读边改数据,像这样:

with open("file.dat", "rb") as f:
    byte = f.read(1)
    while byte != b"":
        if byte !=0:
            # append to data 255
        else:
            # append to data 0
        byte = f.read(1)

尝试这样做,在发送到 PIL 之前更改缓冲区:

import numpy as np
from PIL import Image, ImageOps

w = 128
h = 128

data = np.random.randint(2, size=w*h, dtype=np.uint8).tobytes()
data = bytes([0 if b==0 else 255 for b in data])
img = Image.frombuffer('L', (h,w), data)
img.show()

您可以尝试使用 bbe https://sourceforge.net/projects/bbe-/ 或类似的方法将源数据中的“1”更改为“255”。

bbe -e 's/\x01/\xff/g' file.dat > file_new.dat

变化:

\x01\x00\x00\x01\x01\x01\x00\x00\x00\x00...

至:

\xff\x00\x00\xff\xff\xff\x00\x00\x00\x00...


这是我的测试代码(只是为了确定):

import random
from PIL import Image, ImageOps
import os

w = 128
h = 128

# make the data array
data = b''.join([random.randint(0,1).to_bytes(1,'big') for _ in range(w*h)])

# save the data to a file
file = open('file.dat', 'wb')
file.write(data)
file.close()

# make an image with autocontrast
img = Image.frombuffer('L', (h,w), data)
img = ImageOps.autocontrast(img)
img.save('img.png')

# replace bytes in the data file
os.system(r"bbe -e 's/\x01/\xff/g' file.dat > file_new.dat")

# read the new data
file = open('file_new.dat', 'rb')
data_new = file.read()
file.close()

# make an image with no autocontrast
img = Image.frombuffer('L', (h,w), data_new)
img.save('img_new.png')

输出(img.png / img_new.png):

更新

感谢@MarkSetchell 的评论将我指向 ,我使用 Palette 直接告诉 Pillow 将 0 视为黑色,将 1 视为白色。

代码如下:

def create_images_palette():
    palette = [  0,  0,  0,
               255,255,255]
    palette = palette + [0]*(768-len(palette))
    imgs = []
    with open(filename, 'rb') as ifile:
        for data in iter(partial(ifile.read, w*h), b''):
            img = Image.frombuffer('L', (h,w), data)
            img.putpalette(palette)
            imgs.append(img)
    return imgs

结果与以下测试的获胜者相比,但这次我使用了 w=1024、h=1024、N=1000(对我的使用来说更现实):

create_images3         0.42854620320013054
create_images6         0.32936501539988966
create_images7         0.31196588300008443
create_images_palette  0.21011565389999304

所以调色板解决方案获胜。


在答案的帮助下,我测试了一些解决方案,我可以在这些解决方案中修改我的数据以将 1 替换为 255。以下是这些测试的结果。

我会接受关于这个问题的答案,根据问题,告诉 Pillow 直接将 1 视为白色,将 0 视为黑色。 如果做不到,这些解决方案中的一些为我的需要工作和表现良好。

请注意,在我的实际应用程序中,我可以在一个二进制文件中包含大量图像 back-to-back 的数据。这些解决方案反映了这一点。

import numpy as np
import os
from functools import partial
from PIL import Image, ImageOps

w = 128 # image could be much bigger, keeping it small for the example
h = 128
N = 100

filename = 'byte_imgs.dat'
data = np.random.randint(2, size=w*h*N, dtype=np.uint8).tobytes()
f = open(filename, 'wb')
f.write(data)
f.close()
print("image data written to file")

def create_images1():
    imgs = []
    with open(filename, 'rb') as ifile:
        for data in iter(partial(ifile.read, w*h), b''):
            img = Image.frombuffer('L', (h,w), data)
            img = ImageOps.autocontrast(img)
            imgs.append(img)
    return imgs

def create_images2():
    imgs = []
    with open(filename, 'rb') as ifile:
        for data in iter(partial(ifile.read, w*h), b''):
            data = bytes([0 if b==0 else 255 for b in data])
            img = Image.frombuffer('L', (h,w), data)
            imgs.append(img)
    return imgs

def create_images3():
    imgs = []
    with open(filename, 'rb') as ifile:
        for data in iter(partial(ifile.read, w*h), b''):
            mem = memoryview(data).cast('B', shape=[w,h])
            arr = np.asarray(mem)
            img = Image.fromarray(arr*255)
            imgs.append(img)
    return imgs

def create_images4():
    data = bytearray(w*h)
    imgs = []
    with open(filename, "rb") as f:
        byte = f.read(1)
        while byte != b'':
            for i in range(w*h):
                data[i] = int.from_bytes(byte, "big") * 0xFF
                byte = f.read(1)
            img = Image.frombuffer('L', (h,w), bytes(data))
            imgs.append(img)
    return imgs

def create_images5():
    imgs = []
    with open(filename, "rb") as f:
        i = 0
        data = bytearray()
        byte = f.read(1)
        while byte != b'':
            if byte != b'\x00':
                data.append(0xff)
            else:
                data.append(0x00)
            byte = f.read(1)
            i+=1
            if i == w*h:
                img = Image.frombuffer('L', (h,w), bytes(data))
                imgs.append(img)
                i=0
                data = bytearray()
    return imgs

def create_images6():
    imgs = []
    with open(filename, 'rb') as ifile:
        while True:
            arr = np.fromfile(ifile, dtype=np.uint8, count=w*h)
            if arr.size < w*h:
                break
            img = Image.fromarray(arr.reshape(w,h)*255)
            imgs.append(img)
    return imgs

def create_images7():
    imgs = []
    with open(filename, 'rb') as ifile:
        for dat in iter(partial(ifile.read, w*h), b''):
            arr = np.frombuffer(dat, dtype=np.uint8).reshape((w,h))
            img = Image.fromarray(arr*255)
            imgs.append(img)
    return imgs

def create_images8():
    imgs = []
    data = np.fromfile(filename, dtype=np.int8)
    n = int(data.size / (w*h))
    for i in range(n):
        offset = i*w*h
        state = np.reshape(data[offset:offset+w*h], (w, h))
        img = Image.fromarray(state*255)
        imgs.append(img)
    return imgs


def create_images9():
    os.system(r"bbe -e 's/\x01/\xff/g' byte_imgs.dat > byte_imgs_new.dat")
    imgs = []
    with open('byte_imgs_new.dat', 'rb') as ifile:
        for data in iter(partial(ifile.read, w*h), b''):
            img = Image.frombuffer('L', (h,w), data)
            imgs.append(img)
    return imgs

import timeit
number = 10
print("create_images1", timeit.timeit('[func() for func in (create_images1,)]', number=number, globals=globals()) / number)
print("create_images2", timeit.timeit('[func() for func in (create_images2,)]', number=number, globals=globals()) / number)
print("create_images3", timeit.timeit('[func() for func in (create_images3,)]', number=number, globals=globals()) / number)
print("create_images4", timeit.timeit('[func() for func in (create_images4,)]', number=number, globals=globals()) / number)
print("create_images5", timeit.timeit('[func() for func in (create_images5,)]', number=number, globals=globals()) / number)
print("create_images6", timeit.timeit('[func() for func in (create_images6,)]', number=number, globals=globals()) / number)
print("create_images7", timeit.timeit('[func() for func in (create_images7,)]', number=number, globals=globals()) / number)
print("create_images8", timeit.timeit('[func() for func in (create_images8,)]', number=number, globals=globals()) / number)
print("create_images9", timeit.timeit('[func() for func in (create_images9,)]', number=number, globals=globals()) / number)

结果

以秒为单位报告的每个函数的平均运行时间。 create_images3()create_images7() 是本次测试的明显赢家。

create_images1 0.012226119600018136
create_images2 0.09197459420001905
create_images3 0.0021811368000271615
create_images4 0.30249598119999066
create_images5 0.3393335546000344
create_images6 0.0033311289999801374
create_images7 0.0021913534999839614
create_images8 0.015457254699958867
create_images9 0.044248268000046664