2016-11-14 64 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對象中。 –