Python : 用 PIL 做影像處理(合成)

Syashin Chen
7 min readMar 4, 2018

--

最近幫朋友用 PIL ( Python Imaging Library )做了一個懶人合成工具,主要是把去背後的簽名檔圖片 (SignPicture),與照片 (Picture) 做合成,並且輸出 (ResultPicture)。

Mac 版:https://nofile.io/f/baZKvwHRFCE

Windows 版:https://nofile.io/f/q9oVkrtYttB

電腦不用安裝 Python 即可使用,內附說明。

程式使用示意

這個小小的 Console 程式,使用到了 PILPython 中專門做影像處理的套件,最後用 PyInstaller 包成了 Mac Windows 的版本。

簡單的小程式,卻遇到了一些 Bug,以下分享關鍵部分的程式實做與 Debug 過程。

📕 安裝 PIL package

pip install Pillow

想要使用 PIL 你要做的不是安裝 Pil 而是 Pillow

📗 使用 paste( ) 合併照片與簽名檔

PIL 進行影像合成,常用到的函數有 blend()composite()paste(),在這裡 paste() 比較符合我們的需求,先來看看文件是怎麼寫的:

paste( ) 函數細節

paste() 帶有三個參數:imboxmask,其中 boxmask沒有設定的預設值為 None

im 參數不用說,就是要貼上的圖片,box則是位置參數。

在我們的例子中,mask 參數是最重要的。簽名檔通常是已經去背過的圖片檔,所以依據上圖內容中第四段mask計算方法,我們必須把immask都設成簽名檔的圖片,以保留簽名檔背景的透明度

簡單寫一個範例如下:

出來的結果如圖:

mask 設定為簽名檔 (imageB_resize)

如果將以下這段:

resultPicture.paste(imageB_resize, right_bottom, imageB_resize)

寫成:

resultPicture.paste(imageB_resize, right_bottom)

也就是說,不設定 mask 參數的話,結果會是:

不設定 mask

就算原本簽名檔背景是透明的,不設定mask依舊呈現不透明的顏色

另外,範例中之所以要將簽名檔 resize(),是因為不管簽名檔案本身的寬、高是多少,一律將寬度縮放到照片的一半

📘 Bug 研究

上一段範例程式,理論上來說是完全可行的,實際執行卻有可能會出現一個問題

匯出的照片自動旋轉了!

這個情況真的是滿詭異的,有時候照片明明是正常的,沒有做過旋轉,在程式裡面也沒有 rotate()transpose(),但是結果的圖片就是被旋轉了。

原本的照片是直立的 (左),輸出的結果卻被旋轉 (右)

經過抽絲剝繭,發現讀入照片open()的階段,就被互換了!

size屬性為一個 2-tuple:(width, height)。原圖的 size 值應該要是(3024,4032)但是open() ,print 出來的結果卻是(4032,3024)

image 的 size 為 ( 4032,3024 )

Google 了一下,居然是照片的 Exif 屬性出了問題。

什麼是 Exif ?

Exif ( Exchangeable image file format ) 記錄了照片的拍攝資訊,幾乎任意拍攝裝置都有。詳情可以參考:

根據這份文件,Exif 中有個叫 Orientation TagID 0x0112,我們用這個 ID 寫一段程式來取得圖片的 Orientation 值。

Orientation Tag 定義
取得圖片的 Orientation

得出來的值是:6,根據 Orientation Tag 的定義,6 = Rotate 90 CW,也就是說雖然照片在電腦裡面看起來是正常的,但它的 Orientation 資訊卻是 CW ( ClockWise ) 90 度。

2018/03/15 補充:為什麼 Orientation 會被記錄旋轉了90度?可以參考這篇 SegmentFault 的貼文:利用Pillow打开图片时,图片被旋转了。簡單來說,就是在使用拍攝裝置的時候,裝置的物理旋轉會被記錄下來,但是在該裝置的畫面仍舊是顯示正立的(軟體會自動處理)。然而,用 Pillow 打開圖片的時候,是直接讀取圖片的資訊,並不會自動幫你旋轉成正立。

想把這張照片的輸出變回正常,可以利用 transpose(Image.ROTATE_270)將它再往 CW 方向轉 270 度。

上一段的範例程式我們可以改寫成:

#開啟照片imageA = Image.open('範例圖片C.jpg'),與 imageA = imageA.convert('RGBA') 之間,加上:

if hasattr(imageA, '_getexif'):
orientation = 0x0112
exif = imageA._getexif()
if exif is not None and orientation in exif.keys():
orientation = exif[orientation]
rotations = {
3: Image.ROTATE_180,
6: Image.ROTATE_270,
8: Image.ROTATE_90
}
if orientation in rotations.keys():
imageA = imageA.transpose(rotations[orientation])

來判斷圖片是否有 exif 資訊,並且對 orientation 做處理。

📙 心得

將前面的範例,改寫成比較完整能用的程式,連結如下:

這次本來只是聽聽朋友的需求,想輕鬆解決,意外學到了不少關於照片檔案的知識。雖然以前就聽過 Exif,但遇到實際要處理的狀況卻是第一次,也看了不少文章,獲益良多。

影像處理牽涉到了大量的數學,PIL 將這些演算法包成了 package 方便我們開發,雖然這次程式只用了最基本的 Image Module,文件還有 對影像的細節操作 ImageDraw、ImageFilter 等一堆 Module 可以使用,值得研究。

📚 參考資料

--

--

Syashin Chen

轉行滿兩年的小RD,喜歡做簡單有趣的東西,常常不小心複雜化。