本文一步步爲你介紹,如何用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目錄,貓咪照片呢?
沒問題。不只顯示正常,並且大小也已經正常壓縮。
總結一下,經過本文咱們接觸到了如下知識點:
更重要的,是咱們嘗試瞭如何用Python這一腳本語言,幫咱們智能化作出判斷,而且在後臺完成瑣碎的重複操做。
另外,你應該已經瞭解了,完成功能並不意味着完事大吉。爲了讓本身的代碼能夠充分重用、易於共享並提升效能,你須要梳理與整合代碼,將其充分模塊化,只曝露輸入輸出接口給用戶(包括未來的本身),避免固定取值設置。
你以前遇到過須要智能批量調整圖片大小的問題嗎?你是如何解決的?用過哪些工具?它們能自動幫你判斷圖片是否須要壓縮嗎?歡迎留言,把你的經驗和思考分享給你們,咱們一塊兒交流討論。
喜歡請點贊。還能夠微信關注和置頂個人公衆號「玉樹芝蘭」(nkwangshuyi)。
若是你對數據科學感興趣,不妨閱讀個人系列教程索引貼《如何高效入門數據科學?》,裏面還有更多的有趣問題及解法。