2022年7月10日 星期日

Python 學習筆記 : 用 Pillow 處理圖片

我在去年初為了還一本書整理了其中 Pillow 套件的用法, 參考 :


但最近在研究如何裁切 Matplotlib 所繪製的表格圖片時, 發現該書介紹的 Pillow 內容較少, 例如要用到的 crop() 方法就沒介紹, 所以將其重要用法重新整理, 參考書籍如下 :  

# Python 自動化的樂趣 (碁峰 2017) 第 17 章

Pillow 官方文件參考 : 


Pillow 套件在大量圖檔的處理自動化上非常有用. 


一. PIL 套件中的常用模組 : 


 PIL 套件常用模組 說明
 ImageColor 色彩表示方式的轉換
 Image 圖片處理模組
 ImageDraw 繪圖模組
 ImageFont 字型模組


其中 Image 是主要的影像處理模組, ImageDraw 與 ImageColor 則是繪圖模組, 而 ImageColor 則是轉換色彩表示法的輔助模組. 


二. ImageColor 模組用法 : 

此模組主要用來做光影色彩表示方式的轉換, 可將下列 3 種顏色字串 (大小寫均可) 轉成 10 進位的色碼 tuple, 因為 Pillow 套件的圖片處理模組 Image 與繪圖模組 ImageDraw 中的物件方法會用到色碼元組當參數 :
  • 16 進位色碼字串 : 例如 "#ff0000" 或 "#FF0000"
  • 色彩名稱字串 : 例如 "RED" 或 'red'
  • rgb() 函式字串 : 例如 'rgb(255, 0, 0)' 或 'rgb(100%, 0%, 0%)'
注意, 電腦程式都是使用 RGB 加色法模型 (additive color model), 因為螢幕是光的呈現, 而光是以加色法原理顯色的, 這有別於塗料或印刷所使用的減色法模型 (subtractive color model), 此模型使用 CMYK (青, 洋紅, 黃, 黑) 四種顏色來混出各種顏色.  

首先用 Python 小工具 members.py 模組來查詢其公開成員, 關於 members 模組參考 : 


將 members.py 下載到目前工作目錄後呼叫其 list_members() 函式即可得到其公開成員列表 :

>>> from PIL import ImageColor   
>>> import members as mbr   
>>> mbr.list_members(ImageColor)      
Image <class 'module'>
colormap <class 'dict'>
getcolor <class 'function'>
getrgb <class 'function'>
re <class 'module'>

可見 PIL 套件的 ImageColor 模組有 5 個公開成員, 其中 Image 與 re 都是匯入的模組, colormap 是定義顏色名稱與其對應 16 進位色碼的字典 :

>>> from PIL import ImageColor    
>>> ImageColor.colormap   
{'aliceblue': '#f0f8ff', 'antiquewhite': '#faebd7', 'aqua': '#00ffff', .... (略)

而 getrgb() 與 getcolor() 則為顏色字串轉成 10 進位的 RGB 色碼 tuple, 兩個函式功能類似, 差別只是 getColor() 多了一個 mode 參數可選擇要傳回 RGB 還是 RGBA 的 tuple, 其中 A 是 0~255 的透明度數值, 0 為全透明 (這時不管 R, G, B 值為多少都看不到顏色), 255 為完全不透明, 這要 PNG 檔才有支援. 其語法如下 :

ImageColor.getrgb(color)      
ImageColor.getcolor(color, mode)      

其中 color 為上述的三種色彩描述字串, mode 為傳回值之模式字串, getcolor() 必須傳入這兩個參數, mode 可傳入 'RGB' (JPG 檔) 或 'RGBA' (PNG 檔), 前者傳回 (R, G, B) 元組, 後者傳回 (R, G, B, A) 元組. 顏色字串參數 color 不分大小寫, 即 'red' 或 'RED', '#ff0000' 或 '#FF0000' 皆可. 

Pillow 的色彩名稱採用了 HTML 的色彩名稱, 參考 : 


getrgb() 函式只會傳回 (R, G, B) 元組, 例如 : 

>>> ImageColor.getrgb('#ff0000')   
(255, 0, 0)
>>> ImageColor.getrgb('#0000FF')    
(0, 0, 255)
>>> ImageColor.getrgb('yellow')  
(255, 255, 0)
>>> ImageColor.getrgb('YELLOW')   
(255, 255, 0)
>>> ImageColor.getrgb('rgb(255, 0, 0)')    
(255, 0, 0)
>>> ImageColor.getrgb('RGB(255, 0, 0)')    
(255, 0, 0)

getcolor() 則會根據 mode 傳回 RGB 或 RGBA 元組, 例如 : 

>>> ImageColor.getcolor('#ff0000', 'RGBA')   
(255, 0, 0, 255)
>>> ImageColor.getcolor('#ff0000', 'RGB')       
(255, 0, 0)
>>> ImageColor.getcolor('yellow', 'RGB')   
(255, 255, 0)
>>> ImageColor.getcolor('yellow', 'RGBA')    
(255, 255, 0, 255)

注意, 'RGBA' 模式傳回的 A 都是 255 (不透明), 這樣 R, G, B 設定才會完全顯色. 


三. Image 模組用法 : 

Image 模組是 PIL 套件中的圖像處理主體, 教學文件參考 :


可用 members 模組的 list_members() 函式來檢視其公開成員 : 

>>> import members as mbr
>>> mbr.list_members(Image)    
ADAPTIVE <class 'int'>
AFFINE <class 'int'>
ANTIALIAS <class 'int'>
BICUBIC <class 'int'>
BILINEAR <class 'int'>
BOX <class 'int'>
CONTAINER <class 'int'>
CUBIC <class 'int'>
Callable <class 'abc.ABCMeta'>
DECODERS <class 'dict'>
DEFAULT_STRATEGY <class 'int'>
DecompressionBombError <class 'type'>
DecompressionBombWarning <class 'type'>
ENCODERS <class 'dict'>
EXTENSION <class 'dict'>
EXTENT <class 'int'>
Exif <class 'abc.ABCMeta'>
FASTOCTREE <class 'int'>
FILTERED <class 'int'>
FIXED <class 'int'>
FLIP_LEFT_RIGHT <class 'int'>
FLIP_TOP_BOTTOM <class 'int'>
FLOYDSTEINBERG <class 'int'>
HAMMING <class 'int'>
HUFFMAN_ONLY <class 'int'>
ID <class 'list'>
Image <class 'type'>
ImageMode <class 'module'>
ImagePointHandler <class 'type'>
ImageTransformHandler <class 'type'>
LANCZOS <class 'int'>
LIBIMAGEQUANT <class 'int'>
LINEAR <class 'int'>
MAXCOVERAGE <class 'int'>
MAX_IMAGE_PIXELS <class 'int'>
MEDIANCUT <class 'int'>
MESH <class 'int'>
MIME <class 'dict'>
MODES <class 'list'>
MutableMapping <class 'abc.ABCMeta'>
NEAREST <class 'int'>
NONE <class 'int'>
NORMAL <class 'int'>
OPEN <class 'dict'>
ORDERED <class 'int'>
PERSPECTIVE <class 'int'>
Path <class 'type'>
QUAD <class 'int'>
RASTERIZE <class 'int'>
RLE <class 'int'>
ROTATE_180 <class 'int'>
ROTATE_270 <class 'int'>
ROTATE_90 <class 'int'>
SAVE <class 'dict'>
SAVE_ALL <class 'dict'>
SEQUENCE <class 'int'>
TRANSPOSE <class 'int'>
TRANSVERSE <class 'int'>
TiffTags <class 'module'>
USE_CFFI_ACCESS <class 'bool'>
UnidentifiedImageError <class 'type'>
WEB <class 'int'>
alpha_composite <class 'function'>
atexit <class 'module'>
blend <class 'function'>
builtins <class 'module'>
cffi <class 'module'>
coerce_e <class 'function'>
composite <class 'function'>
core <class 'module'>
deferred_error <class 'type'>
effect_mandelbrot <class 'function'>
effect_noise <class 'function'>
eval <class 'function'>
fromarray <class 'function'>
frombuffer <class 'function'>
frombytes <class 'function'>
fromqimage <class 'function'>
fromqpixmap <class 'function'>
getmodebandnames <class 'function'>
getmodebands <class 'function'>
getmodebase <class 'function'>
getmodetype <class 'function'>
i32le <class 'function'>
init <class 'function'>
io <class 'module'>
isImageType <class 'function'>
isPath <class 'function'>
linear_gradient <class 'function'>
logger <class 'logging.Logger'>
logging <class 'module'>
math <class 'module'>
merge <class 'function'>
new <class 'function'>
numbers <class 'module'>
open <class 'function'>
os <class 'module'>
preinit <class 'function'>
radial_gradient <class 'function'>
register_decoder <class 'function'>
register_encoder <class 'function'>
register_extension <class 'function'>
register_extensions <class 'function'>
register_mime <class 'function'>
register_open <class 'function'>
register_save <class 'function'>
register_save_all <class 'function'>
registered_extensions <class 'function'>
struct <class 'module'>
sys <class 'module'>
tempfile <class 'module'>
warnings <class 'module'>
xml <class 'module'>

雖然成員眾多, 但實際上常用的函式是下面兩個 :


 Image 模組常用函式 說明
 new(mode, size, color=0) 建立新的圖片物件, mode='RGB' 或 'RGBA', size=(w, h), 預設黑色
 open(filename) 開啟指定檔名之圖檔


new() 會依據傳入參數 mode 建立一個 Image 物件, 若要建立無透明度的 JPEG 檔須傳入 mode='RGB', 若要建立有透明度的 PNG 檔則傳入 mode='RGBA', 參數 size 為一個 (寬度, 高度) 畫素元組, 參數 color 可用色彩名例如 'yellow' 或色碼 '#ffff00' (不分大小寫), 如果沒有傳入 color, 預設是黑色背景 (color=0). 例如 : 

>>> img=Image.new('RGB', (300, 200), 'yellow')   # 300*200 px 黃底
>>> type(img)     
<class 'PIL.Image.Image'>      

此 Image 物件有一個 save() 方法可將物件存成圖片檔案, 例如 : 

>>> img.save('test.jpg')     

這時目前工作目錄下就會出現 300x200 黃底的 test.jpg 圖檔了 :




而 open() 函式則用來開啟已存在的圖檔, 以下範例使用我家的三隻小貓圖片 : 


例如 : 

>>> img=Image.open('kitten.jpg')    
>>> type(img)     
<class 'PIL.Image.Image'> 

可見 open() 也是傳回一個 Image 物件. 然後呼叫此物件之 show() 方法會啟動預設看圖軟體顯示此圖片, 例如 : 

>>> img.show()   

結果如下 : 



 
接下來進一步來檢視 Image 物件, 可呼叫 members 模組的 list_members() 函式並傳入上面已建立的 Image 物件即可檢視其有公開成員 : 

>>> import members as mbr   
>>> mbr.list_members(img)    
alpha_composite <class 'method'>
category <class 'int'>
close <class 'method'>
convert <class 'method'>
copy <class 'method'>
crop <class 'method'>
draft <class 'method'>
effect_spread <class 'method'>
entropy <class 'method'>
filter <class 'method'>
format <class 'NoneType'>
format_description <class 'NoneType'>
frombytes <class 'method'>
getbands <class 'method'>
getbbox <class 'method'>
getchannel <class 'method'>
getcolors <class 'method'>
getdata <class 'method'>
getexif <class 'method'>
getextrema <class 'method'>
getim <class 'method'>
getpalette <class 'method'>
getpixel <class 'method'>
getprojection <class 'method'>
height <class 'int'>
histogram <class 'method'>
im <class 'ImagingCore'>
info <class 'dict'>
load <class 'method'>
mode <class 'str'>
palette <class 'NoneType'>
paste <class 'method'>
point <class 'method'>
putalpha <class 'method'>
putdata <class 'method'>
putpalette <class 'method'>
putpixel <class 'method'>
pyaccess <class 'NoneType'>
quantize <class 'method'>
readonly <class 'int'>
reduce <class 'method'>
remap_palette <class 'method'>
resize <class 'method'>
rotate <class 'method'>
save <class 'method'>
seek <class 'method'>
show <class 'method'>
size <class 'tuple'>
split <class 'method'>
tell <class 'method'>
thumbnail <class 'method'>
tobitmap <class 'method'>
tobytes <class 'method'>
toqimage <class 'method'>
toqpixmap <class 'method'>
transform <class 'method'>
transpose <class 'method'>
verify <class 'method'>
width <class 'int'>

Image 物件的常用屬性如下 : 

 Image 物件常用屬性 說明
 width 圖片寬度 (px)
 height 圖片高度 (px)
 size 圖片寬度與高度組成之 tuple (px)
 format 圖片格式 (字串), 例如 'PNG', 'JPG' 等
 format_description 圖片格式的描述 (字串), 例如 'Potable network graphics'
 info 圖片資訊字典, 包含 dpi 等訊息
 mode 圖片模式 (字串), 例如 'RGB' 或 'RGBN' 

Image 物件的常用方法如下 : 

 Image 物件常用方法 說明
 save(filename) 將物件存成圖檔
 show() 以作業系統預設看圖軟體顯示圖片物件
 crop(box)  剪裁圖片只留下 box 區域之像素, box 為 (左, 上, 右, 下) 座標元組
 copy() 複製圖片物件 (傳回新的 Image 物件)
 paste(image, (x, y)) 在圖片物件左上座標 (x, y) 貼上 image 物件 (圖片會被更改)
 resize((w, h)) 調整圖片物件為指定寬高尺寸 tuple (w, h) 後傳回新圖片物件
 rotate(d [, expand=False]) 將圖片逆時針旋轉 d 角度後傳回新圖片物件, expand=是否放大版面
 transpose(method) 將圖片做逕向翻轉後傳回新圖片物件, method=0 (左右), 1 (上下)
 getpixel((x, y)) 取得座標 (x, y) 的像素值, 傳回 (R, G, B) 或 (R, G, B, A) 元組
 putpixel((x, y), color) 在座標 (x, y) 填入像素色彩值 color=(R, G, B) 或 (R, G, B, A)
 convert(mode) 依據指定模式轉換圖片, 傳回新圖片物件. mode=1 轉成黑白圖片
 split() 將圖片中的 RGB 三原色分離為三個 Image 物件, 傳回 (R, G, B) 元組
 merge(mode, bands) 將單通道圖像 bands 以指定 mode 合併傳回新圖片物件

首先來測試 Image 物件的屬性, 例如 : 

>>> img=Image.open('kitten.jpg')   
>>> img.width   
918
>>> img.height   
446
>>> img.size       
(918, 446)
>>> img.mode    
'RGB'
>>> img.format     
'JPEG'
>>> img.format_description   
'JPEG (ISO 10918)'
>>> img.info   
{'jfif': 257, 'jfif_version': (1, 1), 'dpi': (300, 300), 'jfif_unit': 1, 'jfif_density': (300, 300)}

接下來測試 Image 物件的方法, 其中 save() 與 show() 已在上面測試過不再重複. 


1. 裁切圖片 :

呼叫 Image 物件的 crop(box) 方法即可依照傳入參數將指定區域 box 裁切出來成為新圖片物件傳回, 原物件不受影響. 參數 box 是 (x1, y1, x2, y2) 的座標區域, 其中 (x1, y1) 為左上角座標, (x2, y2) 為右下角座標. 注意, 裁切時不包含 (x2, y2) 這個像素點以及這一列與這一行之像素, 就像 range(1, 5) 不包含 5 一樣.  

以上面三隻小貓圖片為例, 若要將最右邊那隻小貓的頭像裁切出來的話, 可先用小畫家或 Picpick 之類的圖片編輯軟體找出要裁切區域的上左下右座標, 然後組成要傳入 cro() 的 box 元組, 例如此處的欲裁切區域是 box=(532, 165, 747, 382) : 




然後呼叫 crop() 方法進行裁切 : 

>>> kitten_right=img.crop((532, 165, 747, 382))    
>>> kitten_right.show()      

結果如下 : 




參考 :

“python clip image ” Code Answer


2. 複製圖片 :

呼叫 Image 物件的 copy() 方法會傳回該物件的副本物件 (新物件), 例如 :

>>> img2=img.copy()      
>>> img   
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=918x446 at 0x219B41F5828>
>>> img2     
<PIL.Image.Image image mode=RGB size=918x446 at 0x219B42083C8
>>> img.size   
(918, 446)
>>> img2.size   
(918, 446)

可見 copy() 會傳回尺寸一樣的新 Image 物件, 呼叫 img2.show() 可知同樣是三隻小貓圖片. 


3. 貼上圖片 :

呼叫 Image 物件的 paste(img, (x, y)) 方法會在圖片中指定的 (x, y) 左上座標處貼上傳入之另一個圖片物件. 注意, paste() 不會傳回新圖片, 而是直接更改原圖片物件. 例如可將上面裁切下來的最右邊小貓頭像 kitten_right 貼到圖片副本 img2 中 :

>>> img2.paste(kitten_right, (687, 13))   
>>> img2.show()    

結果如下 : 




可見貓咪頭像被貼在左上角座標 (687, 13) 的位置. 注意, 以上複製貼上均為物件操作, 並未使用到作業系統的剪貼簿. 


4. 更改圖片尺寸 :

呼叫 Image 物件的 resize((w, h)) 方法會調整圖片的尺寸為寬 w 高 h 的新圖片後傳回, 例如上面三隻小貓的圖片 img 尺寸為 (918, 446), 可用 resize() 將其縮小為長寬各一半 (即 1/4)  :

>>> w, h=img.size  
>>> w, h   
(918, 446)   
>>> img3=img.resize((int(w/2), int(h/2)))      
>>> img3.size      
(459, 223)   

此處先用 size 屬性取得原圖 img 的寬與高 (w, h)=(918, 446), 然後呼叫 resize() 時將寬高都除以 2 以元組傳入, 傳回值為面積縮小為原圖 1/4 的新圖片物件, 尺寸為 (459, 223). 注意, resize((w, h)) 的傳入參數是 tuple (w, h), 不是 resize(w, h), 這樣會出現錯誤. 

沒有留言 :