如何用Python智能批量壓縮圖片?

本文一步步爲你介紹,如何用Python自動判斷多張圖片中哪些超出閾值須要壓縮,且保持寬高比。若是你想了解Python圖像處理的基礎知識,歡迎動手來嘗試。python

痛點

我喜歡用Markdown寫文稿,而後發佈到不一樣寫做平臺。個人好友數字遊民Jarod稱其爲「矩陣式發佈」。能這樣作的前提,是Markdown爲咱們帶來了極低的邊際發佈成本。試想若是每一個寫做平臺,都須要我手動插入20-30張圖片,想一想都眼暈,我估計馬上會打消發佈念頭。git

我使用七牛做爲圖牀。圖片連接成功轉換後,選擇一款渲染工具,預覽文稿格式,看圖片、表格、標題等特殊樣式是否顯示正確。github

我曾經用過多種渲染工具。最近我一直在用Md2All編程

這款工具最大的特色,是能保證粘貼到各個寫做平臺時,代碼不會亂掉。bash

點擊右上方的「複製」按鈕,你就能夠在任何一個寫做平臺上,開啓富文本編輯器,而後粘貼進去。微信

工做進行到這一步,已近大功告成。這時,若是你遇到「圖片上傳失敗」的報錯,想必會很影響心情。網絡

圖片上傳失敗,緣由可能有不少。微信公衆平臺

許多狀況下,只是單純由於網絡擁塞。只要你本着愚公移山的精神,往復從新粘貼,總會好的。編輯器

可是微信公衆平臺是個例外。模塊化

你時常會遇到這種狀況——就是那兩張圖片,死活也沒法正常傳上去。

踩坑屢次,不得不手動上傳圖片後。我終於發現了問題所在——微信公衆平臺對圖片大小有限制。

一旦你要上傳的圖片超過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 PIL
pip install -U glob
複製代碼

安裝完畢後,執行:

jupyter notebook
複製代碼

這樣就進入到了Jupyter筆記本環境。咱們新建一個Python 2筆記本。

這樣就出現了一個空白筆記本。

點擊左上角筆記本名稱,修改成有意義的筆記本名「demo-python-resize-image」。

準備工做完畢,下面咱們就能夠用Python讀入並處理圖像文件了。

代碼

咱們首先讀入幾個後面將用到的軟件包。

from glob import glob
from PIL import Image
import os
複製代碼

而後,咱們指定圖片來源目錄。由於圖片存儲在了樣例目錄的子目錄image下面,因此只須要指定爲"image"就行了。

source_dir = 'image'
複製代碼

下面咱們設置壓縮後圖片的輸出目錄。這裏爲了對比清晰,咱們將其設定爲output,也是樣例目錄的子目錄。注意此時這個目錄還不存在。咱們後面會作處理。

target_dir = 'output'
複製代碼

下面,是關鍵環節之一。咱們需要遍歷image目錄,找出所有的圖片名稱。

這裏咱們用到的,是glob軟件包。其中的glob函數能夠在咱們指定的目錄裏,尋找全部符合要求的文件。

filenames = glob('{}/*'.format(source_dir))
複製代碼

咱們使用了星號(*)做爲通配符,意味着咱們要查找image目錄下全部文件的名稱。

輸出filenames試試看。

print(filenames)
複製代碼
['image/squirrel.png', 'image/cat.png']
複製代碼

可見filenames是個列表,裏面包含了我們須要處理的所有圖片文件。

下面,咱們就來嘗試檢測每張圖片的大小。

for filename in filenames:
    with Image.open(filename) as im:
        width, height = im.size
        print(filename, width, height, os.path.getsize(filename))
複製代碼

咱們遍歷filenames中的全部圖片路徑,用PIL對象的size屬性得到圖片的寬度(width)和高度(height)數值。用os.path.getsize()函數來獲取文件大小。

而後,咱們把這些內容按文件分別打印出來。

('image/squirrel.png', 1024, 768, 1466487)
('image/cat.png', 2067, 1163, 2851538)
複製代碼

由於咱們須要判斷某張圖片的大小是否超出微信公衆平臺設置的2M閾值,所以咱們須要計算一下,2M閾值換算成比特,究竟是個多大的的數字,以便後面的比對。

2*1024*1024
複製代碼

計算結果以下:

2097152
複製代碼

顯然,剛纔的打印結果裏面,cat.png圖像超出了這個閾值。

咱們內心有數了。

下面就把閾值(threshold)設置爲這個數值。

threshold = 2*1024*1024
複製代碼

咱們來看看本身的直覺和程序判斷的實際狀況是否一致:

for filename in filenames:
    filesize = os.path.getsize(filename)
    if filesize >= threshold:
        print(filename)
複製代碼

此處咱們要求Python打印所有超出閾值的文件路徑。結果以下:

image/cat.png
複製代碼

測試結果正確。程序只須要調整貓咪照片的尺寸。

正式進行壓縮和輸出以前,咱們須要創建輸出目錄。雖然前面咱們設定了,這個子目錄叫作output,可是實際的演示目錄裏,它還還沒有建立。

咱們先用os.path.exists()函數斷定這個目錄是否存在。當斷定爲不存在時,咱們採用os.makedirs()函數來建立它。

if not os.path.exists(target_dir):
    os.makedirs(target_dir)
複製代碼

下面咱們計算一下,對須要壓縮的圖片,新的寬度和高度應該是多少。

for filename in filenames:
    filesize = os.path.getsize(filename)
    if filesize >= threshold:
        print(filename)
        with Image.open(filename) as im:
            width, height = im.size
            new_width = 1024
            new_height = int(new_width * height * 1.0 / width)
            print('adjusted size:', new_width, new_height)
複製代碼

咱們把新的寬度設置爲了1024,而後按照同等寬高比例算出新的高度取值。

注意這裏寬度和高度必須設置爲整數類型,不然會報錯。

輸出結果以下:

image/cat.png
('adjusted size:', 1024, 576)
複製代碼

爲了把貓咪照片壓縮爲寬度1024的圖片,咱們須要設定高度爲576,以保證壓縮後的圖片與原始圖片的寬高比一致。

下面咱們續寫函數,正式調用PIL的resize函數將新的圖片設定爲新的寬度和高度數值。而後,咱們使用PIL的save函數,把生成的圖片存儲到指定的路徑。

for filename in filenames:
    filesize = os.path.getsize(filename)
    if filesize >= threshold:
        print(filename)
        with Image.open(filename) as im:
            width, height = im.size
            new_width = 1024
            new_height = int(new_width * height * 1.0 / width)
            resized_im = im.resize((new_width, new_height))
            output_filename = filename.replace(source_dir, target_dir)
            resized_im.save(output_filename)
複製代碼

輸出結果仍是須要壓縮的圖片路徑。

image/cat.png
複製代碼

壓縮成功了嗎?

咱們打開樣例目錄看看。

能夠看到,output子目錄已經自動生成。裏面有一張圖片。名稱依然是cat.png。它的大小已經變成了836KB。咱們打開它,看看顯示是否正確。

依然是這張可愛的貓咪。看不出與原圖有什麼顯著的區別,並且寬高比也正常。測試成功。

整合

可是這裏,咱們還須要完成一個重要步驟——把以前的代碼進行整合。

許多初學者寫代碼,總會忽略這一步。

雖然你的代碼已經成功完成了預期的任務,但如不及時進行整理,過一段時間再來看,你會抓不住頭緒。

想一想看,等你回來的時候,你的Jupyter Notebook是這個樣子的:

你不只會忘了不一樣函數之間的調用關係,並且對於哪些參數須要設定,都一頭霧水。

沒錯,這就是人腦的工做特色——咱們會遺忘。

因此,趁熱打鐵,把你作過的功能進行模塊化整合頗有必要。

整合後,你實現的功能就成了一個有機的總體,只經過參數和外部交互。你只須要用註釋告訴本身參數設置的含義。後面再須要調用相關功能的時候,就能夠直接經過參數變化,拿來就用了。

趁着記憶猶新,我們把剛剛所有的功能整合到一個函數裏面。

def resize_images(source_dir, target_dir, threshold):
    filenames = glob('{}/*'.format(source_dir))
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
    for filename in filenames:
        filesize = os.path.getsize(filename)
        if filesize >= threshold:
            print(filename)
            with Image.open(filename) as im:
                width, height = im.size
                new_width = 1024
                new_height = int(new_width * height * 1.0 / width)
                resized_im = im.resize((new_width, new_height))
                output_filename = filename.replace(source_dir, target_dir)
                resized_im.save(output_filename)
複製代碼

這個函數暴露給外部的接口,是3個參數:

  • source_dir:圖片源目錄
  • target_dir:壓縮圖片輸出目錄
  • threshold:閾值

檢查一下,咱們會發現不對勁的地方——雖然閾值是咱們未來能夠調整的選項,可是壓縮的時候,圖片的寬度倒是手動設定的數值(1024)。這樣未來面對一個閾值高出3倍的寫做平臺,咱們依然把圖片壓縮到這麼小,彷佛有些矯枉過正。

另外,若是這張圖片是那種極爲長的圖,那即使寬度不是很長,也可能會由於高度超出閾值。單純調整寬度到1024,也許會失效。

解決辦法也很簡單,咱們設置高度,而後對應調整寬度。

你能夠看到,由於咱們把代碼集成整理在一處,許多原先咱們可能考慮不周的問題,此時就紛紛顯現了出來。

瞭解了問題所在,咱們來調整一下代碼。

咱們由於要經過閾值計算寬度或者高度,因此須要引入數學計算模塊。

import math
複製代碼

調整後的函數以下:

def resize_images(source_dir, target_dir, threshold):
    filenames = glob('{}/*'.format(source_dir))
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
    for filename in filenames:
        filesize = os.path.getsize(filename)
        if filesize >= threshold:
            print(filename)
            with Image.open(filename) as im:
                width, height = im.size
                if width >= height:
                    new_width = int(math.sqrt(threshold/2))
                    new_height = int(new_width * height * 1.0 / width)
                else:
                    new_height = int(math.sqrt(threshold/2))
                    new_width = int(new_height * width * 1.0 / height)
                resized_im = im.resize((new_width, new_height))
                output_filename = filename.replace(source_dir, target_dir)
                resized_im.save(output_filename)
複製代碼

這樣,未來不管你的圖片目錄在哪裏,你要知足哪一個寫做平臺的圖片大小要求,均可以經過簡單設置這樣幾個數值,調用函數來完成新需求。

咱們嘗試用原先的參數取值,執行一次。

執行以前,咱們刪除掉output目錄,以測試功能。

而後執行模塊化以後的函數。

resize_images(source_dir, target_dir, threshold)
複製代碼

執行時,依然只是輸出須要壓縮的文件路徑。

image/cat.png
複製代碼

檢查剛剛又從新生成的output目錄,貓咪照片呢?

沒問題。不只顯示正常,並且大小也已經正常壓縮。

小結

總結一下,經過本文咱們接觸到了如下知識點:

  • 如何利用glob軟件包遍歷指定目錄,得到符合條件的所有文件路徑列表;
  • 如何用PIL圖像處理工具讀取圖像文件,檢查寬度、高度,從新設定圖像大小,而且存儲新生成的圖像;
  • 如何用os函數庫檢查文件或目錄是否存在,建立目錄,以及獲取文件尺寸。

更重要的,是咱們嘗試瞭如何用Python這一腳本語言,幫咱們智能化作出判斷,而且在後臺完成瑣碎的重複操做。

另外,你應該已經瞭解了,完成功能並不意味着完事大吉。爲了讓本身的代碼能夠充分重用、易於共享並提升效能,你須要梳理與整合代碼,將其充分模塊化,只曝露輸入輸出接口給用戶(包括未來的本身),避免固定取值設置。

討論

你以前遇到過須要智能批量調整圖片大小的問題嗎?你是如何解決的?用過哪些工具?它們能自動幫你判斷圖片是否須要壓縮嗎?歡迎留言,把你的經驗和思考分享給你們,咱們一塊兒交流討論。

喜歡請點贊。還能夠微信關注和置頂個人公衆號「玉樹芝蘭」(nkwangshuyi)

若是你對數據科學感興趣,不妨閱讀個人系列教程索引貼《如何高效入門數據科學?》,裏面還有更多的有趣問題及解法。

相關文章
相關標籤/搜索