1 影像與圖形資料的處理
上一回我們談過了圖Ş介面E式的撰寫,這一ơ我們要a論圖Ş (影像) 本n的處理,而討論的內容會集中?Python Imaging Library (PIL) 這一套程式n上?/p>
PIL ?Python 下最有名的媄像處理套Ӟp多不同的模組所i成Q並且提供了a多的處理功能,允許我們在單?Python E式裡進行影像的處理。用像 PIL a樣的程式n套g可以q助我們把_֊集中在媄像處理的工作本nQ避免迷失在底層的演法裡面?/p>
由於影像處理牽涉C大量的數孔R,因此 PIL 中有a多的模i是?C 語言所寫成的,以提昇處理的效率。不過,在用的時候,我們當然不必在意這樣的問,只管攑ֿ地用是了?/p>
1.1 PIL 能為你作的事
PIL 具備 (但不限於) 以下的能力:
- ? 十種圖檔格式的讀寫能力。常見的 JPEG, PNG, BMP, GIF, TIFF {格式,都在 PIL 的支援之列。另外,PIL 也支援黑白、灰階、自a調色盤、RGB true color、帶有透明屬性的 RBG true color、CMYK 及其它數E的影像模式。相畉全?
- 基本的媄像資料操作:裁切、^UR旋轉、改變尺寸、調|?(transpose)、剪下與g{等?
- 強化圖ŞQ亮度、色ѝ對比、銳利度?
- 色彩處理?
- PIL 提供十數E濾?(filter)。當Ӟ這個數目遠遠不能與 Photoshop® ?GIMP® 這樣的專業特效處理軟體相比;?PIL 提供的這些N可以用在 Python E式裡面Q提供批ơ化處理的能力?
- PIL 可以在媄像中J圖製點、線、面、幾何Ş狀、填ѝ文字等{?
接下來,我們開始一步一步地?Python/PIL 的媄像處理程式設a進行a論?/p>
2 轉換圖檔格式
市面上有a多影像處理E式Q一般h最常用它們來處理的工作大概就是圖檔格式轉換了Q這是影像處理軟體最基本的功能,PIL 當然也要支援?/p>
假設我們有一?JPG 檔案Q名字叫?sample01.jpgQ那|以下的程式會把這個檔案載?PythonQ?/p>
>>> import Image
>>> im = Image.open( "sample01.jpg" )
im 這個物件是?Image.open() Ҏ所產生Z?Image 物g。我們可以用 Image 物g內的屬性來查詢關於此檔案的資訊Q?/p>
>>> print im.format, im.size, im.mode
JPEG (2288, 1712) RGB
? 式字串放?format 屬性裡Q尺寸放?size 屬性裡Q?(調色? 模式攑֜ mode 屬性裡。從以上的執行結果,可以看出來我們讀的確實是一?JPEG 檔案Q檔案的寸?2288 像素寬?712 像素高,調色盤是 RGB 全彩模式?/p>
既然我們已E把圖檔讀入了 PythonQ要處理它就單了。利?Image 別?save() ҎQ可以把檔案儲存?PIL 支援的格式:
>>> im.save( "fileout.png" )
如果圖檔很大Q這會׃一點時間。Image.save() Ҏ會根據欲存檔的副檔名Q自動判斯存圖檔的格式 (剛剛我們用?open() 函式也會這樣??/p>
save() 可以指定存檔格式。在以下的例子裡Q我們把存檔格式指定?JPEGQ?/p>
>>> im.save( "fileout.png", "JPEG" )
這時候副檔名是無所的?/p>
只處理一兩個檔案的時候,使用 Python 直譯器就相當合適。然而若要處理一大群檔案Q譬如把一整個目錄的 JPEG 檔轉換為 PNG 檔,那麼寫成一個程式檔會比較方便,例如Q?/p>
#!/usr/bin/env python
from glob import glob
from os.path import splitext
import Image
jpglist = glob( "python_imaging_pix/*.[jJ][pP][gG]" )
for jpg in jpglist:
im = Image.open(jpg)
png = splitext(jpg)[0]+".png"
im.save(png)
print png
只要在一個放?*.jpg ?*.JPG 檔案的目錄裡面執行這個指令稿Q它會把所有的 JPEG 檔轉?PNG 檔案Q?/p>
$ ./convertdir.py
file0001.png
file0002.png
.
.
file9999.png
既然 PIL 會從檔名偉|常用的檔案格式,存檔時我們通常都不會指定存檔格式?/p>
? 而,依據檔案格式的不同,save() Ҏ提供了不同的達R參數。以 JPEG 而言Q它可以接受 quality (?1 ?100 的整數,預設?75)、optimize (真假? ?progression (真假?。在以下的例子裡Q我們以 100 ? quality 來儲?JPEG 檔案Q?/p>
>>> im.save( "quality100.jpg", quality=100 )
要訣
PIL 也支?EPS (Encapsulate PostScript) 格式的寫入。TeX 的用者可以利?PIL 來簡單地把圖檔轉?EPS 以供 TeX compiler 使用?/p>
3 改變影像與製作縮?/a>
在了解了基本的圖檔轉換之後,我們來看看如何媄像進行寸斚w的修攏VPIL ?Image 物g提供?resize ҎQ以埯影像的縮攑ַ作。用我們的 sample01.jpg 檔案來當例子Q?/p>
>>> im = Image.open( "sample01.jpg" )
>>> print im.size
(2288, 1712)
>>> width = 400
>>> ratio = float(width)/im.size[0]
>>> height = int(im.size[1]*ratio)
>>> nim = im.resize( (width, height), Image.BILINEAR )
>>> print nim.size
(400, 299)
>>> nim.save( "resized.jpg" )
然後我們就會得到比較小?resized.jpgQ?/p>

resize() 這個方法會傛_一個新?Image 物gQ所以舊?Image 不會被更動。resize() 接受兩個參數,W一個用來指定變更後的大,是一個雙元素 tupleQ分別用以指定媄像的寬與高;W二個參數可以省略,是用來指定變更時使用的內插法Q預a是 Image.NEAREST (取最q點)Q這裡我們指定為品質比較好的 Image.BILINEAR?/p>
resize() 可以把媄像放大縮,在用時一定要傛_寬與高。上面的E式會先限定新媄像的寬,再根據舊影像的長寬比例來出新媄像的高應該是多少Q最後把寸值傳?resize() 厅R由此可知,resize() 是允a我們不{比例縮攄Q?/p>
>>> width = 400
>>> height = 100
>>> nim2 = im.resize( (width, height), Image.BILINEAR )
>>> nim2.save( "resize2wide.jpg" )
會得到Ş狀奇怪的^圖Q?/p>

我們可以Q意改變新影像的尺寸倹{?/p>
另一個常用的操作是旋轉;rotate() Ҏ可以用來旋轉影像。它取兩個參數,W一個參數是一個逆時針的度數Q第二個參數則也是影像處理時的內插法,可省略:
>>> nim3 = nim.rotate( 45, Image.BILINEAR )
>>> nim3.save( "rotated.jpg" )
rotate() 並不會改變媄像的寸 (dimension)Q所以你會看刎ͼ

出現了黑邊。如果我們想要連媄像尺怸赯動,得要改用 transpose() ҎQ?/p>
>>> nim4 = nim.transpose( Image.ROTATE_90 )
>>> nim4.save( "transposed90.jpg" )
i果是:

transpose() Ҏ接受 Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_DOWN, ROTATE_90, ROTATE_180, ROTATE_270 {五E參數;其中後三E的旋轉均為逆時針。rotate() Ҏ會對像素資料進行內插Q?transpose() 則只是轉|像素資料,所以沒有內插參數可以設定,也不會媄響媄像的品質?/p>
^放與旋轉是最常用的兩個操 作,而在其中Q縮圖的製作可能是特別常用的QPIL 縮圖提供了一個方便的 thumbnail() Ҏ。thumbnail() 會直接修? Image 物g本nQ所以速度能比 resize() 更快Q也消耗更的a憶體。它不接受指定內插法的參數,而且只能^小影像Q不能放大媄像;用法是:
>>> im = Image.open( "sample01.jpg" )
>>> im.thumbnail( (400,100) )
>>> im.save( "thumbnail.jpg" )
>>> print im.size
(133, 100)
thumbnail() 在接受尺寸參數的時候,行為?resize() 不同Qresize() 允許我們不{比例進行^放Q但 thumbnail() 只能進行{比例縮,並且是以镗寬中比較小的那一個值為基準。因此,上面的程式所作出?thumbnail.jpg 變成?133*100 的小圖片Q?/p>

有了這些操作Q我們可以很輕易地執行媄像管理的d?/p>
4 修改圖Ş內容
除了可以針對圖Ş的尺怽變更之外QPIL 更提供我們變更媄像內容的能力。這樣Q我們就不只能對影像進行理Q而能更進一步地利用E式來把影像的內Ҏ成我們想要的樣子?/p>
我們從「貼圖」開始:
>>> baseim = Image.open( "resized.jpg" )
>>> floatim = Image.open( "thumbnail.jpg" )
>>> baseim.paste( floatim, (150, 50) )
>>> baseim.save( "pasted.jpg" )
利用 paste() ҎQ把之前作的 thumbnail.jpg 貼到 resized.jpg 裡面去:

此種用法?paste() Ҏ要求兩個參數,W一是要g?ImageQ第二是要貼上的位置。第二個參數有三種指定的方式:
- NoneQ不指定位置與尺寸,那麼 pasted() 會假a要g?Image 與被g?Image 的尺寸完全相同?
- (left, upper)Q雙元素 tuple。pasted() 會把要貼上的 Image 的左上角齊在指定的位置?
- (left, upper, right, lower)Q四元素 tuple。paste()` 除了會把 Image 的左上角齊外,也會齊右下角。不過基本上這種寫法和上面那一E一樣,因為 paste() 要求要貼上的影像與這裡指定的尺怸_所以不可能出現不同的兩i?right, lower?
除了「貼圖」之外,我們還可以媄像的內容進行裁切Q?/p>
>>> im = Image.open( "sample01.jpg" )
>>> nim = im.crop( (700, 300, 1500, 1300) )
>>> nim.thumbnail( (400,400) )
>>> nim.save( "croped.jpg" )
(因為裁切之後的圖形還是大了點Q所以再^圖一? 得到的結果是Q?/p>

crop() 接受?box 參數指定要裁切的左、上、右、下四個邊界|形成一個矩形?/p>
除了剪貼之外QPIL 還可以用內建的N (filter) 作一些特效處理。這些N都放?ImageFilter 模組裡面Q用前要先匯入這個模i:
>>> import ImageFilter
我們用個例子,剛剛裁切的 "No Riding" 止牌作 20 ?blur (p化)Q來看看 PIL N的效果:
>>> im = Image.open( "croped.jpg" )
>>> nim = im
>>> for i in range(20): nim = nim.filter( ImageFilter.BLUR )
...
>>> nim.save( "blured.jpg" )
你應該看不出來它?"No Riding" 了吧Q?/p>

使用N的基本語法是Q?/p>
newim = im.filter( ImageFilter.FILTERNAME )
? ?FILTERNAME ?PIL 中支援的N名稱Q目前有QBLUR, CONTOUR, DETAIL, EDGE_ENHANCE, EDGE_ENHANCE_MORE, EMBOSS, FIND_EDGES, SMOOTH, SMOOTH_MORE, SHARPENQ此處就不一一介紹了,但徏C可以自己來把每一個濾鏡都試試看?/p>
利用NQ我們可以對同一的影像進行相同的特效處理。當Ӟ影像Ҏ需要很_的調_在自動化作業中通常只能達到很粗略的效果Q但 PIL 既然提供了,我們的自動E序擁有更多的工具可以使用?/p>
5 ?PIL 製作新媄?/a>
除了已存在的媄像進行R修之外Q從雉始徏立新影像也是很重要的工作。PIL 中的 ImageDraw 模組提供i我們繪製媄像內容的能力。在使用 ImageDraw 之前Q要先徏立好I白的新影像Q?/p>
>>> import ImageDraw
>>> im = Image.new( "RGB", (400,300) )
>>> draw = ImageDraw.Draw( im )
最 後徏Z?draw 是一?ImageDraw 物g會提供各E繪製媄像的Ҏ。針幾何圖形,draw 物g提供 arc() (弧線)、chord() (?、line() (R段)、ellipse() (圓)、point() (?、rectangle() (矩Ş) ?polygon() (多邊?。不過,我們不準備a論q何圖Ş的繪製;怿這些Ҏ的用對一般h來說應該都很直覺才是?/p>
要訣
? 可以在指令行輸入 pydoc ImageDraw.ImageDraw.<<methodname>> 來查詢上q方? (<<methodname>>) 的說明,譬如 pydoc ImageDraw.ImageDraw.line?/p>
這裡要介紹的不是q何圖ŞQ而是文字的繪製。我們要再介紹一個模i:ImageFontQ並且以實例來說明如何用 PIL 「寫字」:
>>> import Image, ImageDraw, ImageFont
>>> font = ImageFont.truetype( "
... "/usr/share/fonts/truetype/freefont/FreeMono.ttf", 24 )
>>> im = Image.new( "RGB", (400,300) )
>>> draw = ImageDraw.Draw( im )
>>> draw.text( (20,20), "TEXT", font=font )
>>> im.save( "text.jpg" )
這樣在一個黑色底圖上用白{寫?"TEXT" 四個大字:

? 著一一說明剛剛作的動作。首先我們用 ImageFont ?truetype() 函式建立了一?TrueType 字型Q大設定為 16 點。truetype() 函式的第一個參數必須是字型檔的搜尋路徑Q第二個參數是字型的點數。然後依序徏立媄像物件與 draw 物g。寫字的動作? draw 物g?text() Ҏ來完成,它接受兩個參數,分別是文字的左上角點、字Ԍ另外可以?font 達R來指定所使用的字? (若不指定Q便使用預設字型)?/p>
?1.1.4 版之前,PIL 是只能用點陣字型的。現? PIL 加入?TrueType 向量字型的支_於要「寫字」的Z說實在是一大福韟뀂對點陣字來說,x變字型的大小得要更換字型才作得到Q但 TrueType 沒有這個限制。如果我們想要寫出兩串不同大的文字Q這樣作就可以了:
>>> largefont = ImageFont.truetype( "
... "/usr/share/fonts/truetype/freefont/FreeMono.ttf", 48 )
>>> smallfont = ImageFont.truetype( "
... "/usr/share/fonts/truetype/freefont/FreeMono.ttf", 24 )
>>> im = Image.new( "RBG", (400,300) )
>>> draw = ImageDraw.Draw( im )
>>> draw.text( (20,20), "SmallTEXT", font=smallfont )
>>> draw.text( (20,120), "LargeTEXT", font=largefont )
>>> im.save( "multitext.jpg" )
i果如:

以上是?PIL 裡徏立文字圖形的Ҏ?/p>
最後,我們要說明如何改變J製圖Ş (文字) 時的色Q繪圖時畫筆的顏色是透過 draw 物g?ink 屬性來改變的:
>>> draw.ink = 0 + 255*256 + 0*256*256
以上會把畫筆a成E色。ink 值必須要是一個整數,其值由色彩?RGB 值算出。舉qր?ink 值的例子Q?/p>
- 紅色?ink 值應a為 255(R) + 0(G)*256 + 0(B)*256*256Q?
- 藍色?ink 值應a為 0(R) + 0(G)*256 + 255(B)*256*256Q?
- 靛色?ink 值應a為 0(R) + 255(G)*256 + 255(B)*256*256
所a定?ink 會媄響所有後U的J圖動作?/p>
6 i語
本文介紹了方便好用的 PIL 套gQ可以讓我們用 Python 撰寫影像處理的程式。我們對圖檔的格式處理、尺寸處理以及內容的R修都作了討論,最後也說明如何從零開始創作一個媄像?/p>
網頁程式來說,動態產生單的媄像是特別有用的功能,可以用來補 HTML ?CSS 的不之處。利?PIL 來執行批ơ媄像處理的工作Q更能省L們許多的操作時間。相信讀者能從其中發珑֮所提供的生產力?/p>