Python 内存中numpy阵列(图像,uint8)的有损压缩

Python 内存中numpy阵列(图像,uint8)的有损压缩,python,performance,numpy,compression,image-compression,Python,Performance,Numpy,Compression,Image Compression,我正在尝试将包含1.000.000个图像的数据集加载到内存中。作为标准的numpy阵列(uint8),所有图像加起来都会占用大约100gb的RAM,但我需要将其降低到

我正在尝试将包含1.000.000个图像的数据集加载到内存中。作为标准的numpy阵列(uint8),所有图像加起来都会占用大约100gb的RAM,但我需要将其降低到<50gb,同时仍然能够快速地将图像读回numpy(这就是将所有内容都保存在内存中的全部意义)。像blosc这样的无损压缩只减少了大约10%的文件大小,所以我选择了JPEG压缩。最起码的例子:

import io
from PIL import Image

numpy_array = (255 * np.random.rand(256, 256, 3)).astype(np.uint8)
image = Image.fromarray(numpy_array)
output = io.BytesIO()
image.save(output, format='JPEG')
在运行时,我通过以下方式读取图像:

[np.array(Image.open(output)) for _ in range(1000)]

JPEG压缩非常有效(<10 GB),但将1000幅图像读回numpy阵列所需的时间约为2.3秒,这严重影响了我的实验性能。我正在寻找在压缩和读取速度之间进行更好权衡的建议。

我仍然不确定我是否理解您的意图,但我创建了一些虚拟图像,并进行了如下测试。我将展示我是如何做到这一点的,以防其他人想尝试其他方法并想要一个数据集

首先,我使用GNU ParallelImageMagick创建了1000幅图像,如下所示:

parallel convert -depth 8 -size 256x256 xc:red +noise random -fill white -gravity center -pointsize 72 -annotate 0 "{}" -alpha off s_{}.png ::: {0..999}
这给了我1000张名为
s_0.png
s_999.png
的图像,图像663如下所示:

parallel convert -depth 8 -size 256x256 xc:red +noise random -fill white -gravity center -pointsize 72 -annotate 0 "{}" -alpha off s_{}.png ::: {0..999}

然后我做了我认为您正试图做的事情-尽管很难从您的代码中辨别:

#!/usr/local/bin/python3

import io
import time
import numpy as np
from PIL import Image

# Create BytesIO object
output = io.BytesIO()

# Load all 1,000 images and write into BytesIO object
for i in range(1000):
   name="s_{}.png".format(i)
   print("Opening image: {}".format(name))
   im = Image.open(name)
   im.save(output, format='JPEG',quality=50)
   nbytes = output.getbuffer().nbytes
   print("BytesIO size: {}".format(nbytes))

# Read back images from BytesIO ito list
start=time.clock()
l=[np.array(Image.open(output)) for _ in range(1000)]
diff=time.clock()-start
print("Time: {}".format(diff))
从BytesIO对象读取所有1000个图像并将其转换为numpy数组需要2.4秒

然后,我将图像的颜色减为256色(我同意这是有损的-就像您的方法一样),并保存了一个调色板图像对象列表,稍后我可以通过调用以下命令轻松地将其转换回numpy数组:

np.array(ImageList[i].convert('RGB'))
将数据存储为调色板图像可以节省66%的空间,因为每个像素只存储一个字节的调色板索引,而不是3个字节的RGB,因此它比您寻求的50%压缩要好

#!/usr/local/bin/python3

import io
import time
import numpy as np
from PIL import Image

# Empty list of images
ImageList = []

# Load all 1,000 images 
for i in range(1000):
   name="s_{}.png".format(i)
   print("Opening image: {}".format(name))
   im = Image.open(name)
   # Add palettised image to list
   ImageList.append(im.quantize(colors=256, method=2))

# Read back images into numpy arrays
start=time.clock()
l=[np.array(ImageList[i].convert('RGB')) for i in range(1000)]
diff=time.clock()-start
print("Time: {}".format(diff))

# Quick test
# Image.fromarray(l[999]).save("result.png")

现在需要0.2秒而不是2.4秒-希望您未说明的应用程序可以接受颜色精度损失:-)

您能澄清一下您想做什么吗?您说您想读取1000000个图像(可能是从磁盘读取的),但您的代码生成了随机图像,所以它似乎不具有代表性?你说阅读1000张图片需要2.3秒,但我以为你有一百万张?您似乎没有提到任何形式的线程或
joblib
,但这通常是在多核CPU时代提高性能的最佳方法之一。对不起,我现在不明白……亲爱的@MarkSetchell,请原谅我的困惑!是的,我给出的玩具示例仅适用于随机图像(使示例尽可能简短),但在我的实验中,每个numpy阵列都是自然图像。另外,我只是阅读了1000张图片(而不是1000000张),只是为了简化计时。关于多线程,你是对的,我开始尝试异步列表理解。这当然可以放在最上面,但与正确的压缩/速度权衡是正交的。在我的机器上,将数组作为uint8放在前面至少比将其作为float64并缩小快5倍。我的意思是
image=np.random.randint(256,size=(256256,3),dtype=np.uint8)
image=(255*np.random.rand(256256,3)).astype(np.uint8)快5倍。
我对此做了一些测试,让我吃惊的是解码JPEG相当慢。然后,我尝试使用颜色减少而不是DCT作为减少数据大小的方法。我不知道你的照片是什么样子,但是我发现我可以很好地压缩iPhone图像,如果我先将颜色减少到32色而不抖动,然后我所有的像素将是32个数字中的一个,并且使用
blosc
可以很好地压缩图像,所以也许可以尝试一下这种方法,它应该能够为您节省同样多的空间,但希望解压缩速度更快。。。我用其他工具做了实验。亲爱的@mark setchel,这是一个结合速度和内存效率的好方法!在我的应用程序中(基本上是计算机视觉模型的机器学习培训),颜色精度的损失是完全可以接受的。非常好-很高兴能提供帮助。祝你的项目好运!