2016-11-14 37 views
3

编辑:这个问题已经被标记为重复?我的问题显然是关于优化这个过程,而不是如何去做。我甚至提供了代码来证明我已经知道后者。在互联网大厅监视器上标记它们之前,甚至在标题之前读过这些问题吗?Python的PIL:查找图像的大小,而不写为文件

我有下面的代码块来压缩用PIL的图像,直到所述图像是具有一定规模下。

from PIL import Image 
import os 

def compress(image_file, max_size, scale): 
    while os.path.getsize(image_file) > max_size: 
     pic = Image.open(image_file) 
     original_size = pic.size 
     pic = pic.resize((int(original_size[0] * scale), 
      int(original_size[1] * scale)), 
      Image.ANTIALIAS) 
     pic.save(image_file, optimize=True, quality=95) 

在这段代码中,我使用os.path.getsize(image_file)来获得图像的大小。然而,这意味着文件必须保存在每次循环运行pic.save(image_file, optimize=True, quality=95

这个过程需要很长的时间。

有没有办法通过某种方式来获取图像的大小PILImage对象pic

+0

您不清楚您要做什么。循环调整图像大小,因此保存是必要的。如果你想要的只是尺寸信息,那么不要调整大小或保存文件。 – aris

+1

我假设你保存为JPEG格式。通过将文件数据保存到内存中的BytesIO对象而不是磁盘,可以节省一些时间。这也将使得获得最终文件大小的速度更快。但是,它不会加速编码过程。顺便说一句,使用质量95没有太大的意义。它非常慢,它产生大文件大小,90和95之间的视觉差异很少引人注意。根据图像的性质,85通常是相当不错的。 –

+1

我还要提到的是,图像中PIL /枕头缩放程序是不是质量非常高,虽然你可能没有注意到,如果图像是足够大,并且是有很多平滑色调过渡,而不是计算机生成图像的照片有很多高对比度的区域。此外,你应该**不**逐步编辑JPEG。不要保存缩放的图像,然后重新加载它,并重新缩放已缩放的图像。这样会很快失去质量。如果您必须尝试不同的缩放比例,直到文件大小足够小,从原始文件生成每个新版本。 –

回答

6

使用io.BytesIO()将图像保存到内存中。它也可能是更好的从原始文件中的每个时间进行如下调整:

from PIL import Image 
import os 
import io 

def compress(original_file, max_size, scale): 
    assert(0.0 < scale < 1.0) 
    orig_image = Image.open(original_file) 
    cur_size = orig_image.size 

    while True: 
     cur_size = (int(cur_size[0] * scale), int(cur_size[1] * scale)) 
     resized_file = orig_image.resize(cur_size, Image.ANTIALIAS) 

     with io.BytesIO() as file_bytes: 
      resized_file.save(file_bytes, optimize=True, quality=95, format='jpeg') 

      if file_bytes.tell() <= max_size: 
       file_bytes.seek(0, 0) 
       with open(original_file, 'wb') as f_output: 
        f_output.write(file_bytes.read()) 
       break 

compress(r"c:\mytest.jpg", 10240, 0.9) 

因此,这将需要该文件,直到达到一个合适的大小规模下来0.9每次尝试。然后它会覆盖原始文件。


作为一个替代方法,你可以创建秤的列表尝试,例如[0.01, 0.02 .... 0.99, 1]然后使用二进制印章,以确定哪些规模的结果在最近的一个文件大小来max_size如下:

def compress(original_file, max_size): 
    save_opts={'optimize':True, 'quality':95, 'format':'jpeg'} 
    orig_image = Image.open(original_file) 
    width, height = orig_image.size 
    scales = [scale/1000 for scale in range(1, 1001)] # e.g. [0.001, 0.002 ... 1.0] 

    lo = 0 
    hi = len(scales) 

    while lo < hi: 
     mid = (lo + hi) // 2 

     scaled_size = (int(width * scales[mid]), int(height * scales[mid])) 
     resized_file = orig_image.resize(scaled_size, Image.ANTIALIAS) 

     file_bytes = io.BytesIO() 
     resized_file.save(file_bytes, **save_opts) 
     size = file_bytes.tell() 
     print(size, scales[mid]) 

     if size < max_size: 
      lo = mid + 1 
     else: 
      hi = mid 

    scale = scales[max(0, lo-1)] 
    print("Using scale:", scale) 
    orig_image.resize((int(width * scale), int(height * scale)), Image.ANTIALIAS).save(original_file, **save_opts) 

所以对于10000一个max_size,循环首先尝试的0.501规模,如果过大0.251受审等等。当max_size=1024将尝试以下比例:

180287 0.501 
56945 0.251 
17751 0.126 
5371 0.063 
10584 0.095 
7690 0.079 
9018 0.087 
10140 0.091 
9336 0.089 
9948 0.09 
Using scale: 0.09 
+0

一个应该检查scale> 0.0和<1.0因为潜入无尽的循环或记忆吃怪物...... :) – ferdy

1

可能的话,你可以使用StringIO的在内存中执行的操作在this answer看看。

import StringIO 

output = StringIO.StringIO() 
image.save(output) 
contents = output.getvalue() 
output.close() 

image.save(output, format="GIF") 
+2

答案相当老旧。 'StringIO'对Python 2来说很好,但是在Python 3中你需要使用'io.BytesIO()'。 –

+0

好的,这可能是Py3中的一种方式。但原理是一样的:在RAM中使用一个变量,在其上执行操作,然后将图像保存回文件中。 – ferdy

+0

当然。但是OP正在寻找Python 3解决方案,并且在Python 3中'import StringIO'引发了'ImportError:'没有名为'StringIO'的模块。当然,您不能将图像中的字节放入Python 3字符串中,您需要将它们放入字节或bytearray对象中。 –