Python : 用 PIL 做影像處理(合成)
最近幫朋友用 PIL ( Python Imaging Library )做了一個懶人合成工具,主要是把去背後的簽名檔圖片 (SignPicture),與照片 (Picture) 做合成,並且輸出 (ResultPicture)。
Mac 版:https://nofile.io/f/baZKvwHRFCE
Windows 版:https://nofile.io/f/q9oVkrtYttB
電腦不用安裝 Python 即可使用,內附說明。
這個小小的 Console 程式,使用到了 PIL:Python 中專門做影像處理的套件,最後用 PyInstaller 包成了 Mac 與 Windows 的版本。
簡單的小程式,卻遇到了一些 Bug,以下分享關鍵部分的程式實做與 Debug 過程。
📕 安裝 PIL package
pip install Pillow
想要使用 PIL 你要做的不是安裝 Pil 而是 Pillow。
📗 使用 paste( ) 合併照片與簽名檔
PIL 進行影像合成,常用到的函數有 blend()
、composite()
、paste()
,在這裡 paste()
比較符合我們的需求,先來看看文件是怎麼寫的:
paste()
帶有三個參數:im
、box
、mask
,其中 box
與 mask
沒有設定的預設值為 None
。
im
參數不用說,就是要貼上的圖片,box
則是位置參數。
在我們的例子中,mask
參數是最重要的。簽名檔通常是已經去背過的圖片檔,所以依據上圖內容中第四段的mask
計算方法,我們必須把im
、mask
都設成簽名檔的圖片,以保留簽名檔背景的透明度。
簡單寫一個範例如下:
出來的結果如圖:
如果將以下這段:
resultPicture.paste(imageB_resize, right_bottom, imageB_resize)
寫成:
resultPicture.paste(imageB_resize, right_bottom)
也就是說,不設定 mask
參數的話,結果會是:
就算原本簽名檔背景是透明的,不設定mask
依舊呈現不透明的顏色。
另外,範例中之所以要將簽名檔 resize()
,是因為不管簽名檔案本身的寬、高是多少,一律將寬度縮放到照片的一半。
📘 Bug 研究
上一段範例程式,理論上來說是完全可行的,實際執行卻有可能會出現一個問題:
匯出的照片自動旋轉了!
這個情況真的是滿詭異的,有時候照片明明是正常的,沒有做過旋轉,在程式裡面也沒有 rotate()
或transpose()
,但是結果的圖片就是被旋轉了。
經過抽絲剝繭,發現讀入照片open()
的階段,寬、高就被互換了!
size
屬性為一個 2-tuple:(width, height)
。原圖的 size
值應該要是(3024,4032)
,但是open()
後,print 出來的結果卻是(4032,3024)
。
Google 了一下,居然是照片的 Exif 屬性出了問題。
什麼是 Exif ?
Exif ( Exchangeable image file format ) 記錄了照片的拍攝資訊,幾乎任意拍攝裝置都有。詳情可以參考:
根據這份文件,Exif 中有個叫 Orientation 的 Tag,ID 為 0x0112,我們用這個 ID 寫一段程式來取得圖片的 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 可以使用,值得研究。