如何用Python智能批量壓縮圖片?
本文一步步為你介紹,如何用Python自動判斷多張圖片中哪些超出閾值需要壓縮,且保持寬高比。如果你想了解Python圖像處理的基礎知識,歡迎動手來嘗試。
痛點
我喜歡用Markdown寫文稿,然後發布到不同寫作平台。我的好友數字遊民Jarod稱其為「矩陣式發布」。能這樣做的前提,是Markdown為我們帶來了極低的邊際發布成本。試想如果每個寫作平台,都需要我手動插入20-30張圖片,想想都眼暈,我估計立刻會打消發布念頭。
我使用七牛作為圖床。圖片鏈接成功轉換後,選擇一款渲染工具,預覽文稿格式,看圖片、表格、標題等特殊樣式是否顯示正確。
我曾經用過多種渲染工具。最近我一直在用Md2All。
這款工具最大的特點,是能保證粘貼到各個寫作平台時,代碼不會亂掉。
點擊右上方的「複製」按鈕,你就可以在任何一個寫作平台上,開啟富文本編輯器,然後粘貼進去。
工作進行到這一步,已近大功告成。這時,如果你遇到「圖片上傳失敗」的報錯,想必會很影響心情。
圖片上傳失敗,原因可能有很多。
許多情況下,只是單純因為網路擁塞。只要你本著愚公移山的精神,往複重新粘貼,總會好的。
但是微信公眾平台是個例外。
你時常會遇到這種情況——就是那兩張圖片,死活也無法正常傳上去。
踩坑多次,不得不手動上傳圖片後。我終於發現了問題所在——微信公眾平台對圖片大小有限制。
一旦你要上傳的圖片超過2M,就無法正常粘貼上傳了。
莫非我寫作文章時,還要一一檢驗每張插圖的大小?超過閾值的圖片壓縮,然後再上傳?
對我這種插圖愛好者來說,這個工作太過瑣碎和枯燥了。
你可能會問,不是有許多工具可以批量修改圖片大小嗎?例如JPEGmini和TinyPNG之類的?
確實有,但是它們不完全符合我的需求。
首先,我並不需要壓縮全部圖像。壓縮後的圖片,確實在手機上看起來跟原圖毫無區別。但我用的圖片,很多是教程里的示例。學生可能需要放大到一定程度,甚至要在大屏幕上打開,來查看代碼或者運行結果的細節。只要原圖沒超過2M,還是保持原貌比較穩妥。
其次,我懶。每次寫完文章,還得手動運行一個應用,找出這篇文章對應的圖片,拖動進去……不好意思,這活兒我懶得干。
幸好,凡是簡單重複的枯燥活兒,都是電腦的拿手好戲。否則我們學編程幹什麼?
我用Python做個程序,替我找出全部大於2M的圖片,進行壓縮。壓縮的時候,須要保持圖片的寬高比例。
如果你對Python圖像預處理功能比較感興趣,不妨跟著我的介紹,一起試試看。
數據
我已經為你準備好了樣例圖片和執行代碼,並且存儲在了一個Github項目中。請訪問這個鏈接,下載壓縮包後,解壓查看。
可以看到,在image目錄下,有2個png格式的圖像文件。
我們打開來看看,一張cat.png是可愛的貓咪。
另一張,是小松鼠。
猜猜哪張圖片更大?
小松鼠這張圖片,尺寸低於2M。貓咪那張,卻有2.9M,不符合微信公眾平台的要求。
我們下面要用Python自行判斷這些圖片中,哪些超過了2M,需要進行壓縮。
然後,對超過2M的圖片,按照原先的寬高比壓縮後,存儲到一個指定的文件夾裡面去。
環境
我們使用Python集成運行環境Anaconda。
請到這個網址 下載最新版的Anaconda。下拉頁面,找到下載位置。根據你目前使用的系統,網站會自動推薦給你適合的版本下載。我使用的是macOS,下載文件格式為pkg。
下載頁面區左側是Python 3.6版,右側是2.7版。請選擇2.7版本。
雙擊下載後的pkg文件,根據中文提示一步步安裝即可。
安裝好Anaconda後,我們還需要確保安裝幾個必要的軟體包。
請到你的「終端」(Linux, macOS)或者「命令提示符」(Windows)下面,進入咱們剛剛下載解壓後的樣例目錄。
執行以下命令:
pip install -U PILnpip install -U globn
安裝完畢後,執行:
jupyter notebookn
這樣就進入到了Jupyter筆記本環境。我們新建一個Python 2筆記本。
這樣就出現了一個空白筆記本。
點擊左上角筆記本名稱,修改為有意義的筆記本名「demo-python-resize-image」。
準備工作完畢,下面我們就可以用Python讀入並處理圖像文件了。
代碼
我們首先讀入幾個後面將用到的軟體包。
from glob import globnfrom PIL import Imagenimport osn
然後,我們指定圖片來源目錄。因為圖片存儲在了樣例目錄的子目錄image下面,所以只需要指定為"image"就好了。
source_dir = imagen
下面我們設置壓縮後圖片的輸出目錄。這裡為了對比清晰,我們將其設定為output,也是樣例目錄的子目錄。注意此時這個目錄還不存在。我們後面會做處理。
target_dir = outputn
下面,是關鍵環節之一。我們須要遍歷image目錄,找出全部的圖片名稱。
這裡我們用到的,是glob軟體包。其中的glob函數可以在我們指定的目錄里,尋找所有符合要求的文件。
filenames = glob({}/*.format(source_dir))n
我們使用了星號(*)作為通配符,意味著我們要查找image目錄下所有文件的名稱。
輸出filenames試試看。
print(filenames)nn[image/squirrel.png, image/cat.png]n
可見filenames是個列表,裡面包含了咱們需要處理的全部圖片文件。
下面,我們就來嘗試檢測每張圖片的大小。
for filename in filenames:n with Image.open(filename) as im:n width, height = im.sizen print(filename, width, height, os.path.getsize(filename))n
我們遍歷filenames中的所有圖片路徑,用PIL對象的size屬性獲得圖片的寬度(width)和高度(height)數值。用os.path.getsize()
函數來獲取文件大小。
然後,我們把這些內容按文件分別列印出來。
(image/squirrel.png, 1024, 768, 1466487)n(image/cat.png, 2067, 1163, 2851538)n
因為我們需要判斷某張圖片的大小是否超出微信公眾平台設置的2M閾值,因此我們需要計算一下,2M閾值換算成比特,到底是個多大的的數字,以便後面的比對。
2*1024*1024n
計算結果如下:
2097152n
顯然,剛才的列印結果裡面,cat.png圖像超出了這個閾值。
我們心裡有數了。
下面就把閾值(threshold)設置為這個數值。
threshold = 2*1024*1024n
我們來看看自己的直覺和程序判斷的實際情況是否一致:
for filename in filenames:n filesize = os.path.getsize(filename)n if filesize >= threshold:n print(filename)n
此處我們要求Python列印全部超出閾值的文件路徑。結果如下:
image/cat.pngn
測試結果正確。程序只需要調整貓咪照片的尺寸。
正式進行壓縮和輸出之前,我們需要建立輸出目錄。雖然前面我們設定了,這個子目錄叫做output,但是實際的演示目錄里,它還尚未創建。
我們先用os.path.exists()
函數判定這個目錄是否存在。當判定為不存在時,我們採用os.makedirs()
函數來創建它。
if not os.path.exists(target_dir):n os.makedirs(target_dir)n
下面我們計算一下,對需要壓縮的圖片,新的寬度和高度應該是多少。
for filename in filenames:n filesize = os.path.getsize(filename)n if filesize >= threshold:n print(filename)n with Image.open(filename) as im:n width, height = im.sizen new_width = 1024n new_height = int(new_width * height * 1.0 / width)n print(adjusted size:, new_width, new_height)n
我們把新的寬度設置為了1024,然後按照同等寬高比例算出新的高度取值。
注意這裡寬度和高度必須設置為整數類型,否則會報錯。
輸出結果如下:
image/cat.pngn(adjusted size:, 1024, 576)n
為了把貓咪照片壓縮為寬度1024的圖片,我們需要設定高度為576,以保證壓縮後的圖片與原始圖片的寬高比一致。
下面我們續寫函數,正式調用PIL的resize函數將新的圖片設定為新的寬度和高度數值。然後,我們使用PIL的save函數,把生成的圖片存儲到指定的路徑。
for filename in filenames:n filesize = os.path.getsize(filename)n if filesize >= threshold:n print(filename)n with Image.open(filename) as im:n width, height = im.sizen new_width = 1024n new_height = int(new_width * height * 1.0 / width)n resized_im = im.resize((new_width, new_height))n output_filename = filename.replace(source_dir, target_dir)n resized_im.save(output_filename)n
輸出結果還是需要壓縮的圖片路徑。
image/cat.pngn
壓縮成功了嗎?
我們打開樣例目錄看看。
可以看到,output子目錄已經自動生成。裡面有一張圖片。名稱依然是cat.png。它的大小已經變成了836KB。我們打開它,看看顯示是否正確。
依然是這張可愛的貓咪。看不出與原圖有什麼顯著的區別,而且寬高比也正常。測試成功。
整合
但是這裡,我們還需要完成一個重要步驟——把之前的代碼進行整合。
許多初學者寫代碼,總會忽略這一步。
雖然你的代碼已經成功完成了預期的任務,但如不及時進行整理,過一段時間再來看,你會抓不住頭緒。
想想看,等你回來的時候,你的Jupyter Notebook是這個樣子的:
你不僅會忘了不同函數之間的調用關係,而且對於哪些參數需要設定,都一頭霧水。
沒錯,這就是人腦的工作特點——我們會遺忘。
所以,趁熱打鐵,把你做過的功能進行模塊化整合很有必要。
整合後,你實現的功能就成了一個有機的整體,只通過參數和外部交互。你只需要用注釋告訴自己參數設置的含義。後面再需要調用相關功能的時候,就可以直接通過參數變化,拿來就用了。
趁著記憶猶新,咱們把剛剛全部的功能整合到一個函數裡面。
def resize_images(source_dir, target_dir, threshold):n filenames = glob({}/*.format(source_dir))n if not os.path.exists(target_dir):n os.makedirs(target_dir)n for filename in filenames:n filesize = os.path.getsize(filename)n if filesize >= threshold:n print(filename)n with Image.open(filename) as im:n width, height = im.sizen new_width = 1024n new_height = int(new_width * height * 1.0 / width)n resized_im = im.resize((new_width, new_height))n output_filename = filename.replace(source_dir, target_dir)n resized_im.save(output_filename)n
這個函數暴露給外部的介面,是3個參數:
source_dir
:圖片源目錄target_dir
:壓縮圖片輸出目錄threshold
:閾值
檢查一下,我們會發現不對勁的地方——雖然閾值是我們將來可以調整的選項,但是壓縮的時候,圖片的寬度卻是手動設定的數值(1024)。這樣將來面對一個閾值高出3倍的寫作平台,我們依然把圖片壓縮到這麼小,似乎有些矯枉過正。
另外,如果這張圖片是那種極為長的圖,那即便寬度不是很長,也可能會因為高度超出閾值。單純調整寬度到1024,也許會失效。
解決辦法也很簡單,我們設置高度,然後對應調整寬度。
你可以看到,因為我們把代碼集成整理在一處,許多原先我們可能考慮不周的問題,此時就紛紛顯現了出來。
了解了問題所在,我們來調整一下代碼。
我們因為要通過閾值計算寬度或者高度,所以需要引入數學計算模塊。
import mathn
調整後的函數如下:
def resize_images(source_dir, target_dir, threshold):n filenames = glob({}/*.format(source_dir))n if not os.path.exists(target_dir):n os.makedirs(target_dir)n for filename in filenames:n filesize = os.path.getsize(filename)n if filesize >= threshold:n print(filename)n with Image.open(filename) as im:n width, height = im.sizen if width >= height:n new_width = int(math.sqrt(threshold/2))n new_height = int(new_width * height * 1.0 / width)n else:n new_height = int(math.sqrt(threshold/2))n new_width = int(new_height * width * 1.0 / height)n resized_im = im.resize((new_width, new_height))n output_filename = filename.replace(source_dir, target_dir)n resized_im.save(output_filename)n
這樣,將來無論你的圖片目錄在哪裡,你要滿足哪個寫作平台的圖片大小要求,都可以通過簡單設置這樣幾個數值,調用函數來完成新需求。
我們嘗試用原先的參數取值,執行一次。
執行之前,我們刪除掉output目錄,以測試功能。
然後執行模塊化之後的函數。
resize_images(source_dir, target_dir, threshold)n
執行時,依然只是輸出需要壓縮的文件路徑。
image/cat.pngn
檢查剛剛又重新生成的output目錄,貓咪照片呢?
沒問題。不僅顯示正常,而且大小也已經正常壓縮。
小結
總結一下,通過本文我們接觸到了以下知識點:
- 如何利用glob軟體包遍歷指定目錄,獲得符合條件的全部文件路徑列表;
- 如何用PIL圖像處理工具讀取圖像文件,檢查寬度、高度,重新設定圖像大小,並且存儲新生成的圖像;
- 如何用os函數庫檢查文件或目錄是否存在,創建目錄,以及獲取文件尺寸。
更重要的,是我們嘗試了如何用Python這一腳本語言,幫我們智能化做出判斷,並且在後台完成瑣碎的重複操作。
另外,你應該已經了解了,完成功能並不意味著完事大吉。為了讓自己的代碼可以充分重用、易於共享並提高效能,你需要梳理與整合代碼,將其充分模塊化,只曝露輸入輸出介面給用戶(包括將來的自己),避免固定取值設置。
討論
你之前遇到過需要智能批量調整圖片大小的問題嗎?你是如何解決的?用過哪些工具?它們能自動幫你判斷圖片是否需要壓縮嗎?歡迎留言,把你的經驗和思考分享給大家,我們一起交流討論。
如果你對我的文章感興趣,歡迎點贊,並且微信關注和置頂我的公眾號「玉樹芝蘭」(nkwangshuyi)。
如果本文可能對你身邊的親友有幫助,也歡迎你把本文通過微博或朋友圈分享給他們。讓他們一起參與到我們的討論中來。
推薦閱讀:
※跟黃哥學習python第五章
※Python GUI教程(十):創建一個複雜的GUI
※Python培訓是運維還是全棧,有什麼區別嗎?
※使用Python計算文章中的字詞頻率丨學習筆記和反思
※GAFT-一個使用Python實現的遺傳演算法框架